diff options
author | Kevin Liu <congxiliu@google.com> | 2024-03-12 23:51:48 +0000 |
---|---|---|
committer | Kevin Liu <congxiliu@google.com> | 2024-03-13 02:14:32 +0000 |
commit | f30e0656e46c3480ebbc46377df709b63a68470f (patch) | |
tree | 617af920454e82793aa4d35c8e7cc2b84dd789d4 | |
parent | 91e288ba7054a1cb4a95f178e82c3a140eb4d708 (diff) | |
parent | 7c688cc0794f6c62fd82dc825b5b55d6991e2490 (diff) | |
download | robolectric-f30e0656e46c3480ebbc46377df709b63a68470f.tar.gz |
Merge branch 'upstream-google' into roboUpdate
Pulling updates from Github to Android
Bug: 323922587
Test: mma && atest CtesqueRoboTests
Change-Id: I09cbfb2246c40f333819959e5faf6de3b0567bdd
534 files changed, 12594 insertions, 5039 deletions
diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 66f95c264..808af6dd2 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -17,5 +17,3 @@ updates: # don't auto update nativeruntime-dist-compat since it needs # to be updated with code changes together - dependency-name: "org.robolectric:nativeruntime-dist-compat" - # don't auto update androidx-annotation since it brings higher Kotlin dependency - - dependency-name: "androidx.annotation:annotation" diff --git a/.github/workflows/check_code_style.yml b/.github/workflows/check_code_style.yml index 00c0d13a4..5c020d298 100644 --- a/.github/workflows/check_code_style.yml +++ b/.github/workflows/check_code_style.yml @@ -11,6 +11,10 @@ on: paths-ignore: - '**.md' +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + permissions: contents: read @@ -24,9 +28,9 @@ jobs: fetch-depth: 0 - name: Set up JDK 17 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: - distribution: 'zulu' + distribution: 'adopt' java-version: 17 - name: Download google-java-format 1.9 diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 000000000..2e9a93f02 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,83 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: [ "master" ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ "master" ] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + analyze: + name: Analyze + # Runner size impacts CodeQL analysis time. To learn more, please see: + # - https://gh.io/recommended-hardware-resources-for-running-codeql + # - https://gh.io/supported-runners-and-hardware-resources + # - https://gh.io/using-larger-runners + # Consider using larger runners for possible analysis time improvements. + runs-on: ubuntu-22.04 + timeout-minutes: 360 + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'java-kotlin' ] + # CodeQL supports [ 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby' ] + # Use only 'java-kotlin' to analyze code written in Java, Kotlin or both + # Use only 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both + # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + distribution: 'adopt' + java-version: 17 + + - uses: gradle/actions/setup-gradle@v3 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + + # ℹ️ Command-line programs to run using the OS shell. + # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + - name: Build + run: | + SKIP_ERRORPRONE=true SKIP_JAVADOC=true \ + ./gradlew assemble testClasses --parallel --stacktrace --no-watch-fs + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + with: + category: "/language:${{matrix.language}}" diff --git a/.github/workflows/copybara_build_and_test.yml b/.github/workflows/copybara_build_and_test.yml index e4036f82b..55a995360 100644 --- a/.github/workflows/copybara_build_and_test.yml +++ b/.github/workflows/copybara_build_and_test.yml @@ -4,6 +4,10 @@ on: pull_request: branches: [ google ] +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + permissions: contents: read @@ -15,12 +19,12 @@ jobs: - uses: actions/checkout@v4 - name: Set up JDK 17 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: - distribution: 'zulu' + distribution: 'adopt' java-version: 17 - - uses: gradle/gradle-build-action@v2.9.0 + - uses: gradle/actions/setup-gradle@v3 - name: Build run: | @@ -36,5 +40,6 @@ jobs: -Drobolectric.alwaysIncludeVariantMarkersInTestName=true \ -Drobolectric.enabledSdks=34 \ -Dorg.gradle.workers.max=2 \ + -Drobolectric.usePreinstrumentedJars=false \ -x :integration_tests:nativegraphics:test \ ) diff --git a/.github/workflows/gradle_tasks_validation.yml b/.github/workflows/gradle_tasks_validation.yml index f3ac12e54..f8d065d2b 100644 --- a/.github/workflows/gradle_tasks_validation.yml +++ b/.github/workflows/gradle_tasks_validation.yml @@ -11,27 +11,14 @@ on: paths-ignore: - '**.md' +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + permissions: contents: read jobs: - run_checkForApiChanges: - runs-on: ubuntu-22.04 - - steps: - - uses: actions/checkout@v4 - - - name: Set up JDK - uses: actions/setup-java@v3 - with: - distribution: 'zulu' - java-version: 17 - - - uses: gradle/gradle-build-action@v2.9.0 - - - name: Run checkForApiChanges - run: ./gradlew checkForApiChanges - run_aggregateDocs: runs-on: ubuntu-22.04 @@ -39,12 +26,12 @@ jobs: - uses: actions/checkout@v4 - name: Set up JDK - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: - distribution: 'zulu' + distribution: 'adopt' java-version: 17 - - uses: gradle/gradle-build-action@v2.9.0 + - uses: gradle/actions/setup-gradle@v3 - name: Run aggregateDocs run: ./gradlew clean aggregateDocs @@ -56,12 +43,12 @@ jobs: - uses: actions/checkout@v4 - name: Set up JDK - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: - distribution: 'zulu' + distribution: 'adopt' java-version: 17 - - uses: gradle/gradle-build-action@v2.9.0 + - uses: gradle/actions/setup-gradle@v3 - name: Run javadocJar run: ./gradlew clean javadocJar @@ -76,12 +63,12 @@ jobs: submodules: recursive - name: Set up JDK - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: - distribution: 'zulu' + distribution: 'adopt' java-version: 17 - - uses: gradle/gradle-build-action@v2.9.0 + - uses: gradle/actions/setup-gradle@v3 - name: Run :preinstrumented:instrumentAll run: ./gradlew :preinstrumented:instrumentAll diff --git a/.github/workflows/gradle_wrapper_validation.yml b/.github/workflows/gradle_wrapper_validation.yml index 0209ccd59..722f2caff 100644 --- a/.github/workflows/gradle_wrapper_validation.yml +++ b/.github/workflows/gradle_wrapper_validation.yml @@ -11,6 +11,10 @@ on: paths-ignore: - '**.md' +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + permissions: contents: read @@ -20,4 +24,4 @@ jobs: runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 - - uses: gradle/wrapper-validation-action@v1 + - uses: gradle/wrapper-validation-action@v2 diff --git a/.github/workflows/graphics_tests.yml b/.github/workflows/graphics_tests.yml index a64e8af7e..1ba2e5670 100644 --- a/.github/workflows/graphics_tests.yml +++ b/.github/workflows/graphics_tests.yml @@ -11,6 +11,10 @@ on: paths-ignore: - '**.md' +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + permissions: contents: read @@ -18,23 +22,29 @@ jobs: graphics_tests: strategy: matrix: - device: [ macos-12, ubuntu-22.04, self-hosted ] + device: [ macos-13, ubuntu-22.04, macos-14 ] runs-on: ${{ matrix.device }} steps: - uses: actions/checkout@v4 - name: Set up JDK 17 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: - distribution: 'zulu' + distribution: 'adopt' java-version: 17 - - uses: gradle/gradle-build-action@v2.9.0 + - uses: gradle/actions/setup-gradle@v3 + + - name: Show runner info + run: | + uname -a - name: Run unit tests run: | - SKIP_ERRORPRONE=true SKIP_JAVADOC=true ./gradlew :integration_tests:nativegraphics:testDebugUnitTest \ + SKIP_ERRORPRONE=true SKIP_JAVADOC=true ./gradlew \ + :integration_tests:nativegraphics:testDebugUnitTest \ + :integration_tests:roborazzi:verifyRoborazziDebug \ --info --stacktrace --continue \ --parallel \ --no-watch-fs \ @@ -42,9 +52,12 @@ jobs: -Dorg.gradle.workers.max=2 - name: Upload Test Results - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: always() with: - name: test_results - path: '**/build/test-results/**/TEST-*.xml' - + name: test_results_${{ matrix.device }} + path: | + **/build/test-results/**/TEST-*.xml + **/roborazzi/build/reports/* + **/roborazzi/src/screenshots/* + **/roborazzi/build/outputs/roborazzi/* diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 544beb7b3..66ca84bb9 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -11,6 +11,10 @@ on: paths-ignore: - '**.md' +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + permissions: contents: read @@ -25,12 +29,12 @@ jobs: - uses: actions/checkout@v4 - name: Set up JDK 17 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: - distribution: 'zulu' + distribution: 'adopt' java-version: 17 - - uses: gradle/gradle-build-action@v2.9.0 + - uses: gradle/actions/setup-gradle@v3 - name: Build run: | @@ -49,12 +53,12 @@ jobs: - uses: actions/checkout@v4 - name: Set up JDK 17 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: - distribution: 'zulu' + distribution: 'adopt' java-version: 17 - - uses: gradle/gradle-build-action@v2.9.0 + - uses: gradle/actions/setup-gradle@v3 - name: Run unit tests run: | @@ -64,18 +68,20 @@ jobs: --no-watch-fs \ -Drobolectric.enabledSdks=${{ matrix.api-versions }} \ -Drobolectric.alwaysIncludeVariantMarkersInTestName=true \ + -Drobolectric.usePreinstrumentedJars=false \ -Dorg.gradle.workers.max=2 \ - -x :integration_tests:nativegraphics:test + -x :integration_tests:nativegraphics:test \ + -x :integration_tests:roborazzi:test - name: Upload Test Results - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: always() with: name: test_results_${{ matrix.api-versions }} path: '**/build/test-results/**/TEST-*.xml' instrumentation-tests: - runs-on: macos-12 + runs-on: ubuntu-22.04 timeout-minutes: 60 needs: build @@ -89,27 +95,36 @@ jobs: - uses: actions/checkout@v4 - name: Set up JDK 17 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: - distribution: 'zulu' + distribution: 'adopt' java-version: 17 - - uses: gradle/gradle-build-action@v2.9.0 + - uses: gradle/actions/setup-gradle@v3 + + - name: Enable KVM group perms + run: | + echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules + sudo udevadm control --reload-rules + sudo udevadm trigger --name-match=kvm - name: Determine emulator target id: determine-target run: | TARGET="google_apis" + if [[ ${{ matrix.api-level }} -ge 34 ]]; then + TARGET="aosp_atd" + fi echo "TARGET=$TARGET" >> $GITHUB_OUTPUT - name: AVD cache - uses: actions/cache@v3 + uses: actions/cache@v4 id: avd-cache with: path: | ~/.android/avd/* ~/.android/adb* - key: avd-${{ matrix.api-level }}-${{ env.cache-version }} + key: avd-ubuntu-${{ matrix.api-level }}-${{ env.cache-version }} - name: Create AVD and generate snapshot for caching if: steps.avd-cache.outputs.cache-hit != 'true' @@ -120,7 +135,7 @@ jobs: arch: x86_64 force-avd-creation: false emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none - disable-animations: false + disable-animations: true script: echo "Generated AVD snapshot for caching." - name: Run device tests @@ -133,15 +148,14 @@ jobs: force-avd-creation: false emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none disable-animations: true - disable-spellchecker: true profile: Nexus One script: | - ./gradlew cAT || ./gradlew cAT || ./gradlew cAT || exit 1 + SKIP_ERRORPRONE=true SKIP_JAVADOC=true ./gradlew cAT --info --stacktrace --no-watch-fs -Dorg.gradle.workers.max=2 - name: Upload test results if: always() - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: test-results-${{ matrix.api-level }}-${{ steps.determine-target.outputs.TARGET }}-${{ matrix.shard }} path: | @@ -159,12 +173,12 @@ jobs: - uses: actions/checkout@v4 - name: Set up JDK 17 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: - distribution: 'zulu' + distribution: 'adopt' java-version: 17 - - uses: gradle/gradle-build-action@v2.9.0 + - uses: gradle/actions/setup-gradle@v3 - name: Publish run: | diff --git a/.github/workflows/validate_commit_message.yml b/.github/workflows/validate_commit_message.yml index b541e4e93..57124e8b2 100644 --- a/.github/workflows/validate_commit_message.yml +++ b/.github/workflows/validate_commit_message.yml @@ -4,6 +4,10 @@ on: pull_request: branches: [ master, google ] +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + permissions: contents: read diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index e69de29bb..000000000 --- a/.gitmodules +++ /dev/null diff --git a/annotations/src/main/java/org/robolectric/annotation/Implements.java b/annotations/src/main/java/org/robolectric/annotation/Implements.java index d366003eb..84da01ec6 100644 --- a/annotations/src/main/java/org/robolectric/annotation/Implements.java +++ b/annotations/src/main/java/org/robolectric/annotation/Implements.java @@ -68,6 +68,17 @@ public @interface Implements { Class<? extends ShadowPicker<?>> shadowPicker() default DefaultShadowPicker.class; /** + * If set to true, Robolectric will invoke the native method variant instead of the no-op variant. + * This requires the native method to be bound, or an {@link UnsatisfiedLinkError} will occur. + * + * <p>{@link Implements#callNativeMethodsByDefault()} has precedence over {@link + * Implements#callThroughByDefault()} For instance, if both {@link + * Implements#callNativeMethodsByDefault()} and {@link Implements#callThroughByDefault()} are + * true, the native method variant will be preferred over the no-op native variant. + */ + boolean callNativeMethodsByDefault() default false; + + /** * An interface used as the default for the {@code picker} param. Indicates that no custom {@link * ShadowPicker} is being used. */ diff --git a/build.gradle b/build.gradle index d944c3689..29ad8a91c 100644 --- a/build.gradle +++ b/build.gradle @@ -17,6 +17,7 @@ buildscript { classpath libs.kotlin.gradle classpath libs.spotless.gradle classpath libs.detekt.gradle + classpath libs.roborazzi.gradle } } diff --git a/buildSrc/src/main/groovy/AndroidSdk.groovy b/buildSrc/src/main/groovy/AndroidSdk.groovy index 12454ec66..59bbb5541 100644 --- a/buildSrc/src/main/groovy/AndroidSdk.groovy +++ b/buildSrc/src/main/groovy/AndroidSdk.groovy @@ -1,5 +1,5 @@ class AndroidSdk implements Comparable<AndroidSdk> { - static final PREINSTRUMENTED_VERSION = 4 + static final PREINSTRUMENTED_VERSION = 5 static final KITKAT = new AndroidSdk(19, "4.4_r1", "r2") static final LOLLIPOP = new AndroidSdk(21, "5.0.2_r3", "r0") diff --git a/buildSrc/src/main/groovy/CheckApiChangesPlugin.groovy b/buildSrc/src/main/groovy/CheckApiChangesPlugin.groovy deleted file mode 100644 index 2f1476cf3..000000000 --- a/buildSrc/src/main/groovy/CheckApiChangesPlugin.groovy +++ /dev/null @@ -1,389 +0,0 @@ -import org.gradle.api.Plugin -import org.gradle.api.Project -import org.objectweb.asm.ClassReader -import org.objectweb.asm.tree.AnnotationNode -import org.objectweb.asm.tree.ClassNode -import org.objectweb.asm.tree.MethodNode - -import java.util.jar.JarEntry -import java.util.jar.JarInputStream -import java.util.regex.Pattern - -import static org.objectweb.asm.Opcodes.ACC_PRIVATE -import static org.objectweb.asm.Opcodes.ACC_PROTECTED -import static org.objectweb.asm.Opcodes.ACC_PUBLIC -import static org.objectweb.asm.Opcodes.ACC_SYNTHETIC - -class CheckApiChangesPlugin implements Plugin<Project> { - @Override - void apply(Project project) { - project.extensions.create("checkApiChanges", CheckApiChangesExtension) - - project.configurations { - checkApiChangesFrom - checkApiChangesTo - } - - project.afterEvaluate { - project.checkApiChanges.from.each { - project.dependencies.checkApiChangesFrom(it) { - transitive = false - } - } - - project.checkApiChanges.to.findAll { it instanceof String }.each { - project.dependencies.checkApiChangesTo(it) { - transitive = false - force = true - } - } - } - - project.task('checkForApiChanges', dependsOn: 'jar') { - doLast { - Map<ClassMethod, Change> changedClassMethods = new TreeMap<>() - - def fromUrls = project.configurations.checkApiChangesFrom*.toURI()*.toURL() - println "fromUrls = ${fromUrls*.toString()*.replaceAll("^.*/", "")}" - - def jarUrls = project.checkApiChanges.to - .findAll { it instanceof Project } - .collect { it.jar.archivePath.toURL() } - def toUrls = jarUrls + project.configurations.checkApiChangesTo*.toURI()*.toURL() - println "toUrls = ${toUrls*.toString()*.replaceAll("^.*/", "")}" - - Analysis prev = new Analysis(fromUrls) - Analysis cur = new Analysis(toUrls) - - Set<String> allMethods = new TreeSet<>(prev.classMethods.keySet()) - allMethods.addAll(cur.classMethods.keySet()) - - Set<ClassMethod> deprecatedNotRemoved = new TreeSet<>() - Set<ClassMethod> newlyDeprecated = new TreeSet<>() - - for (String classMethodName : allMethods) { - ClassMethod prevClassMethod = prev.classMethods.get(classMethodName) - ClassMethod curClassMethod = cur.classMethods.get(classMethodName) - - if (prevClassMethod == null) { - // added - if (curClassMethod.visible) { - changedClassMethods.put(curClassMethod, Change.ADDED) - } - } else if (curClassMethod == null) { - def theClass = prevClassMethod.classNode.name.replace('/', '.') - def methodDesc = prevClassMethod.methodDesc - while (curClassMethod == null && cur.parents[theClass] != null) { - theClass = cur.parents[theClass] - def parentMethodName = "${theClass}#${methodDesc}" - curClassMethod = cur.classMethods[parentMethodName] - } - - // removed - if (curClassMethod == null && prevClassMethod.visible && !prevClassMethod.deprecated) { - if (classMethodName.contains("getActivityTitle")) { - println "hi!" - } - changedClassMethods.put(prevClassMethod, Change.REMOVED) - } - } else { - if (prevClassMethod.deprecated) { - deprecatedNotRemoved << prevClassMethod; - } else if (curClassMethod.deprecated) { - newlyDeprecated << prevClassMethod; - } -// println "changed: $classMethodName" - } - } - - String prevClassName = null - def introClass = { classMethod -> - if (classMethod.className != prevClassName) { - prevClassName = classMethod.className - println "\n$prevClassName:" - } - } - - def entryPoints = project.checkApiChanges.entryPoints - Closure matchesEntryPoint = { ClassMethod classMethod -> - for (String entryPoint : entryPoints) { - if (classMethod.className.matches(entryPoint)) { - return true - } - } - return false - } - - def expectedREs = project.checkApiChanges.expectedChanges.collect { Pattern.compile(it) } - - for (Map.Entry<ClassMethod, Change> change : changedClassMethods.entrySet()) { - def classMethod = change.key - def changeType = change.value - - def showAllChanges = true // todo: only show stuff that's interesting... - if (matchesEntryPoint(classMethod) || showAllChanges) { - String classMethodDesc = classMethod.desc - def expected = expectedREs.any { it.matcher(classMethodDesc).find() } - if (!expected) { - introClass(classMethod) - - switch (changeType) { - case Change.ADDED: - println "+ ${classMethod.methodDesc}" - break - case Change.REMOVED: - println "- ${classMethod.methodDesc}" - break - } - } - } - } - - if (!deprecatedNotRemoved.empty) { - println "\nDeprecated but not removed:" - for (ClassMethod classMethod : deprecatedNotRemoved) { - introClass(classMethod) - println "* ${classMethod.methodDesc}" - } - } - - if (!newlyDeprecated.empty) { - println "\nNewly deprecated:" - for (ClassMethod classMethod : newlyDeprecated) { - introClass(classMethod) - println "* ${classMethod.methodDesc}" - } - } - } - } - } - - static class Analysis { - final Map<String, String> parents = new HashMap<>() - final Map<String, ClassMethod> classMethods = new HashMap<>() - - Analysis(List<URL> baseUrls) { - for (URL url : baseUrls) { - if (url.protocol == 'file') { - def file = new File(url.path) - def stream = new FileInputStream(file) - def jarStream = new JarInputStream(stream) - while (true) { - JarEntry entry = jarStream.nextJarEntry - if (entry == null) break - - if (!entry.directory && entry.name.endsWith(".class")) { - def reader = new ClassReader(jarStream) - def classNode = new ClassNode() - reader.accept(classNode, ClassReader.SKIP_CODE | ClassReader.SKIP_FRAMES) - - def superName = classNode.superName.replace('/', '.') - if (!"java.lang.Object".equals(superName)) { - parents[classNode.name.replace('/', '.')] = superName - } - - if (bitSet(classNode.access, ACC_PUBLIC) || bitSet(classNode.access, ACC_PROTECTED)) { - for (MethodNode method : classNode.methods) { - def classMethod = new ClassMethod(classNode, method, url) - if (!bitSet(method.access, ACC_SYNTHETIC)) { - classMethods.put(classMethod.desc, classMethod) - } - } - } - } - } - stream.close() - } - } - classMethods - } - - } - - static enum Change { - REMOVED, - ADDED, - } - - static class ClassMethod implements Comparable<ClassMethod> { - final ClassNode classNode - final MethodNode methodNode - final URL originUrl - - ClassMethod(ClassNode classNode, MethodNode methodNode, URL originUrl) { - this.classNode = classNode - this.methodNode = methodNode - this.originUrl = originUrl - } - - boolean equals(o) { - if (this.is(o)) return true - if (getClass() != o.class) return false - - ClassMethod that = (ClassMethod) o - - if (classNode.name != that.classNode.name) return false - if (methodNode.name != that.methodNode.name) return false - if (methodNode.signature != that.methodNode.signature) return false - - return true - } - - int hashCode() { - int result - result = (classNode.name != null ? classNode.name.hashCode() : 0) - result = 31 * result + (methodNode.name != null ? methodNode.name.hashCode() : 0) - result = 31 * result + (methodNode.signature != null ? methodNode.signature.hashCode() : 0) - return result - } - - public String getDesc() { - return "$className#$methodDesc" - } - - boolean hasParent() { - parentClassName() != "java/lang/Object" - } - - String parentClassName() { - classNode.superName - } - - private String getMethodDesc() { - def args = new StringBuilder() - def returnType = new StringBuilder() - def buf = args - - int arrayDepth = 0 - def write = { typeName -> - if (buf.size() > 0) buf.append(", ") - buf.append(typeName) - for (; arrayDepth > 0; arrayDepth--) { - buf.append("[]") - } - } - - def chars = methodNode.desc.toCharArray() - def i = 0 - - def readObj = { - if (buf.size() > 0) buf.append(", ") - def objNameBuf = new StringBuilder() - for (; i < chars.length; i++) { - char c = chars[i] - if (c == ';' as char) break - objNameBuf.append((c == '/' as char) ? '.' : c) - } - buf.append(objNameBuf.toString().replaceAll(/^java\.lang\./, '')) - } - - for (; i < chars.length;) { - def c = chars[i++] - switch (c) { - case '(': break; - case ')': buf = returnType; break; - case '[': arrayDepth++; break; - case 'Z': write('boolean'); break; - case 'B': write('byte'); break; - case 'S': write('short'); break; - case 'I': write('int'); break; - case 'J': write('long'); break; - case 'F': write('float'); break; - case 'D': write('double'); break; - case 'C': write('char'); break; - case 'L': readObj(); break; - case 'V': write('void'); break; - } - } - "$methodAccessString ${isHiddenApi() ? "@HiddenApi " : ""}${isImplementation() ? "@Implementation " : ""}$methodNode.name(${args.toString()}): ${returnType.toString()}" - } - - @Override - public String toString() { - internalName - } - - private String getInternalName() { - classNode.name + "#$methodInternalName" - } - - private String getMethodInternalName() { - "$methodNode.name$methodNode.desc" - } - - private String getSignature() { - methodNode.signature == null ? "()V" : methodNode.signature - } - - private String getClassName() { - classNode.name.replace('/', '.') - } - - boolean isDeprecated() { - containsAnnotation(classNode.visibleAnnotations, "Ljava/lang/Deprecated;") || - containsAnnotation(methodNode.visibleAnnotations, "Ljava/lang/Deprecated;") - } - - boolean isImplementation() { - containsAnnotation(methodNode.visibleAnnotations, "Lorg/robolectric/annotation/Implementation;") - } - - boolean isHiddenApi() { - containsAnnotation(methodNode.visibleAnnotations, "Lorg/robolectric/annotation/HiddenApi;") - } - - String getMethodAccessString() { - return getAccessString(methodNode.access) - } - - private String getClassAccessString() { - return getAccessString(classNode.access) - } - - String getAccessString(int access) { - if (bitSet(access, ACC_PROTECTED)) { - return "protected" - } else if (bitSet(access, ACC_PUBLIC)) { - return "public" - } else if (bitSet(access, ACC_PRIVATE)) { - return "private" - } else { - return "[package]" - } - } - - boolean isVisible() { - (bitSet(classNode.access, ACC_PUBLIC) || bitSet(classNode.access, ACC_PROTECTED)) && - (bitSet(methodNode.access, ACC_PUBLIC) || bitSet(methodNode.access, ACC_PROTECTED)) && - !bitSet(classNode.access, ACC_SYNTHETIC) && - !(classNode.name =~ /\$[0-9]/) && - !(methodNode.name =~ /^access\$/ || methodNode.name == '<clinit>') - } - - private static boolean containsAnnotation(List<AnnotationNode> annotations, String annotationInternalName) { - for (AnnotationNode annotationNode : annotations) { - if (annotationNode.desc == annotationInternalName) { - return true - } - } - return false - } - - @Override - int compareTo(ClassMethod o) { - internalName <=> o.internalName - } - } - - private static boolean bitSet(int field, int bit) { - (field & bit) == bit - } -} - -class CheckApiChangesExtension { - String[] from - Object[] to - - String[] entryPoints - String[] expectedChanges -}
\ No newline at end of file diff --git a/buildSrc/src/main/groovy/org/robolectric/gradle/AndroidProjectConfigPlugin.groovy b/buildSrc/src/main/groovy/org/robolectric/gradle/AndroidProjectConfigPlugin.groovy index fc2ac09a1..40442bfdc 100644 --- a/buildSrc/src/main/groovy/org/robolectric/gradle/AndroidProjectConfigPlugin.groovy +++ b/buildSrc/src/main/groovy/org/robolectric/gradle/AndroidProjectConfigPlugin.groovy @@ -19,7 +19,7 @@ public class AndroidProjectConfigPlugin implements Plugin<Project> { } minHeapSize = "2048m" - maxHeapSize = "8192m" + maxHeapSize = "12288m" if (System.env['GRADLE_MAX_PARALLEL_FORKS'] != null) { maxParallelForks = Integer.parseInt(System.env['GRADLE_MAX_PARALLEL_FORKS']) diff --git a/buildSrc/src/main/groovy/org/robolectric/gradle/GradleManagedDevicePlugin.groovy b/buildSrc/src/main/groovy/org/robolectric/gradle/GradleManagedDevicePlugin.groovy index cdcd3ca83..2589e8327 100644 --- a/buildSrc/src/main/groovy/org/robolectric/gradle/GradleManagedDevicePlugin.groovy +++ b/buildSrc/src/main/groovy/org/robolectric/gradle/GradleManagedDevicePlugin.groovy @@ -20,7 +20,7 @@ class GradleManagedDevicePlugin implements Plugin<Project> { nexusOneApi34(ManagedVirtualDevice) { device = "Nexus One" apiLevel = 34 - systemImageSource = "aosp" + systemImageSource = "aosp-atd" } } } diff --git a/buildSrc/src/main/groovy/org/robolectric/gradle/RoboJavaModulePlugin.groovy b/buildSrc/src/main/groovy/org/robolectric/gradle/RoboJavaModulePlugin.groovy index adffa3810..d48f89e1c 100644 --- a/buildSrc/src/main/groovy/org/robolectric/gradle/RoboJavaModulePlugin.groovy +++ b/buildSrc/src/main/groovy/org/robolectric/gradle/RoboJavaModulePlugin.groovy @@ -61,7 +61,7 @@ class RoboJavaModulePlugin implements Plugin<Project> { } minHeapSize = "1024m" - maxHeapSize = "8192m" + maxHeapSize = "12288m" if (System.env['GRADLE_MAX_PARALLEL_FORKS'] != null) { maxParallelForks = Integer.parseInt(System.env['GRADLE_MAX_PARALLEL_FORKS']) diff --git a/dependencies.gradle b/dependencies.gradle index 204e3d354..86602da99 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -1,6 +1,4 @@ ext { - apiCompatVersion = libs.versions.robolectric.compat.get() - // https://github.com/gradle/gradle/issues/21267 axtCoreVersion = libs.versions.androidx.test.core.get() axtJunitVersion = libs.versions.androidx.test.ext.junit.get() diff --git a/errorprone/build.gradle b/errorprone/build.gradle index 5fc561605..ab8db7dcb 100644 --- a/errorprone/build.gradle +++ b/errorprone/build.gradle @@ -38,8 +38,6 @@ dependencies { // Testing dependencies testImplementation libs.junit4 testImplementation libs.truth - testImplementation(libs.error.prone.test.helpers) { - exclude group: 'junit', module: 'junit' // because it depends on a snapshot!? - } + testImplementation libs.error.prone.test.helpers testCompileOnly(AndroidSdk.MAX_SDK.coordinates) } diff --git a/errorprone/src/main/java/org/robolectric/errorprone/bugpatterns/DeprecatedMethodsCheck.java b/errorprone/src/main/java/org/robolectric/errorprone/bugpatterns/DeprecatedMethodsCheck.java index 6ed6c5ad2..2784a7b95 100644 --- a/errorprone/src/main/java/org/robolectric/errorprone/bugpatterns/DeprecatedMethodsCheck.java +++ b/errorprone/src/main/java/org/robolectric/errorprone/bugpatterns/DeprecatedMethodsCheck.java @@ -4,7 +4,6 @@ import static com.google.errorprone.BugPattern.SeverityLevel.WARNING; import static com.google.errorprone.matchers.Description.NO_MATCH; import static com.google.errorprone.matchers.Matchers.instanceMethod; import static com.google.errorprone.matchers.Matchers.staticMethod; -import static com.google.errorprone.util.ASTHelpers.hasAnnotation; import static org.robolectric.errorprone.bugpatterns.Helpers.isCastableTo; import static org.robolectric.errorprone.bugpatterns.Helpers.isInShadowClass; @@ -19,6 +18,7 @@ import com.google.errorprone.fixes.Fix; import com.google.errorprone.fixes.SuggestedFix; import com.google.errorprone.matchers.Description; import com.google.errorprone.matchers.method.MethodMatchers.MethodNameMatcher; +import com.google.errorprone.util.ASTHelpers; import com.sun.source.tree.ClassTree; import com.sun.source.tree.ImportTree; import com.sun.source.tree.MethodInvocationTree; @@ -41,7 +41,6 @@ import org.robolectric.annotation.Implements; */ @AutoService(BugChecker.class) @BugPattern( - name = "DeprecatedMethods", summary = "Prefer supported APIs.", severity = WARNING, documentSuppression = false, @@ -127,7 +126,8 @@ public class DeprecatedMethodsCheck extends BugChecker implements ClassTreeMatch @Override public Void visitClass(ClassTree classTree, VisitorState visitorState) { boolean priorInShadowClass = inShadowClass; - inShadowClass = hasAnnotation(classTree, Implements.class, visitorState); + inShadowClass = + ASTHelpers.hasAnnotation(classTree, Implements.class.getName(), visitorState); try { return super.visitClass(classTree, visitorState); } finally { diff --git a/errorprone/src/main/java/org/robolectric/errorprone/bugpatterns/Helpers.java b/errorprone/src/main/java/org/robolectric/errorprone/bugpatterns/Helpers.java index 234b96e5b..96dab0134 100644 --- a/errorprone/src/main/java/org/robolectric/errorprone/bugpatterns/Helpers.java +++ b/errorprone/src/main/java/org/robolectric/errorprone/bugpatterns/Helpers.java @@ -1,7 +1,6 @@ package org.robolectric.errorprone.bugpatterns; import static com.google.errorprone.util.ASTHelpers.findEnclosingNode; -import static com.google.errorprone.util.ASTHelpers.hasAnnotation; import com.google.errorprone.VisitorState; import com.google.errorprone.predicates.TypePredicate; @@ -14,7 +13,7 @@ import com.sun.tools.javac.code.Type; import com.sun.tools.javac.tree.JCTree.JCClassDecl; import org.robolectric.annotation.Implements; -/** Matchers for {@link ShadowUsageCheck}. */ +/** Matchers for {@link DeprecatedMethodsCheck}. */ public class Helpers { /** Match sub-types or implementations of the given type. */ @@ -33,7 +32,7 @@ public class Helpers { ? (JCClassDecl) leaf : findEnclosingNode(state.getPath(), JCClassDecl.class); - return hasAnnotation(classDecl, Implements.class, state); + return ASTHelpers.hasAnnotation(classDecl, Implements.class.getName(), state); } /** Matches implementations of the given interface. */ diff --git a/errorprone/src/main/java/org/robolectric/errorprone/bugpatterns/RobolectricShadow.java b/errorprone/src/main/java/org/robolectric/errorprone/bugpatterns/RobolectricShadow.java index 646c78e60..7ec111108 100644 --- a/errorprone/src/main/java/org/robolectric/errorprone/bugpatterns/RobolectricShadow.java +++ b/errorprone/src/main/java/org/robolectric/errorprone/bugpatterns/RobolectricShadow.java @@ -49,7 +49,6 @@ import org.robolectric.annotation.Implements; * @author christianw@google.com (Christian Williams) */ @BugPattern( - name = "RobolectricShadow", summary = "Robolectric @Implementation methods should be protected.", severity = SUGGESTION, documentSuppression = false, diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 0efed346b..8e569c11a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,9 +1,8 @@ [versions] -robolectric-compat = "4.11.1" -robolectric-nativeruntime-dist-compat = "1.0.4" +robolectric-nativeruntime-dist-compat = "1.0.8" # https://developer.android.com/studio/releases -android-gradle = "8.1.4" +android-gradle = "8.2.2" # https://github.com/google/conscrypt/tags conscrypt = "2.5.2" @@ -28,16 +27,16 @@ error-prone-javac = "9+181-r4173-1" error-prone-gradle = "3.1.0" # https://kotlinlang.org/docs/releases.html#release-details -kotlin = "1.9.20" +kotlin = "1.9.22" # https://github.com/Kotlin/kotlinx.coroutines/releases/ kotlinx-coroutines = '1.7.3' # https://github.com/diffplug/spotless/blob/main/plugin-gradle/CHANGES.md -spotless-gradle = "6.22.0" +spotless-gradle = "6.25.0" # https://detekt.dev/changelog -detekt-gradle = "1.23.3" +detekt-gradle = "1.23.5" # https://hc.apache.org/news.html apache-http-core = "4.0.1" @@ -51,6 +50,7 @@ auto-common = "1.2.2" auto-service = "1.1.1" auto-value = "1.10.4" +# https://github.com/google/compile-testing/releases compile-testing = "0.21.0" # https://github.com/google/guava/releases @@ -60,11 +60,12 @@ guava-jre = "31.1-jre" gson = "2.10.1" # https://github.com/google/truth/releases -truth = "1.1.5" +truth = "1.4.0" # https://github.com/unicode-org/icu/releases -icu4j = "74.1" +icu4j = "74.2" +# https://www.eclemma.org/jacoco/ jacoco = "0.8.11" # https://github.com/javaee/javax.annotation/tags @@ -79,7 +80,7 @@ jetbrains-annotations = "24.1.0" junit4 = "4.13.2" # https://github.com/google/libphonenumber/releases -libphonenumber = "8.13.25" +libphonenumber = "8.13.30" # https://github.com/mockito/mockito/releases mockito = "4.11.0" @@ -87,6 +88,9 @@ mockito = "4.11.0" # https://github.com/mockk/mockk/releases mockk = "1.13.7" +# https://github.com/takahirom/roborazzi/releases +roborazzi = "1.9.0" + # https://square.github.io/okhttp/changelogs/changelog/ okhttp = "4.12.0" @@ -96,7 +100,7 @@ powermock = "2.0.9" sqlite4java = "1.0.392" # https://developer.android.com/jetpack/androidx/versions -androidx-annotation = "1.3.0" +androidx-annotation = "1.7.1" androidx-appcompat = "1.6.1" androidx-biometric = "1.1.0" androidx-constraintlayout = "2.1.4" @@ -104,15 +108,14 @@ androidx-core = "1.12.0" androidx-fragment = "1.6.2" androidx-multidex = "2.0.1" androidx-window = "1.2.0" +androidx-room = "2.6.1" # https://github.com/android/android-test/tags -androidx-test-annotation = "1.0.1" androidx-test-core = "1.5.0" androidx-test-espresso = "3.5.1" androidx-test-ext-junit = "1.1.5" androidx-test-ext-truth = "1.5.0" -androidx-test-monitor="1.6.1" -androidx-test-orchestrator="1.4.2" +androidx-test-monitor = "1.6.1" androidx-test-runner = "1.5.2" androidx-test-services = "1.4.2" @@ -120,6 +123,9 @@ androidx-test-services = "1.4.2" androidx-fragment-for-shadows = "1.2.0" play-services-base-for-shadows = "8.4.0" +# https://developers.google.com/android/guides/releases +play-services-basement = "18.0.1" + [libraries] android-gradle = { module = "com.android.tools.build:gradle", version.ref = "android-gradle" } kotlin-gradle = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } @@ -127,7 +133,7 @@ spotless-gradle = { module = "com.diffplug.spotless:spotless-plugin-gradle", ver detekt-gradle = { module = "io.gitlab.arturbosch.detekt:detekt-gradle-plugin", version.ref = "detekt-gradle" } kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" } -kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinx-coroutines"} +kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinx-coroutines" } auto-common = { module = "com.google.auto:auto-common", version.ref = "auto-common" } auto-service-annotations = { module = "com.google.auto.service:auto-service-annotations", version.ref = "auto-service" } @@ -147,12 +153,12 @@ compile-testing = { module = "com.google.testing.compile:compile-testing", versi aggregate-javadocs-gradle = { module = "com.netflix.nebula:gradle-aggregate-javadocs-plugin", version.ref = "aggregate-javadocs-gradle" } -error-prone-core = { module = "com.google.errorprone:error_prone_core", version.ref = "error-prone" } +error-prone-core = { module = "com.google.errorprone:error_prone_core", version.ref = "error-prone" } error-prone-annotations = { module = "com.google.errorprone:error_prone_annotation", version.ref = "error-prone" } -error-prone-refaster= { module = "com.google.errorprone:error_prone_refaster", version.ref = "error-prone" } +error-prone-refaster = { module = "com.google.errorprone:error_prone_refaster", version.ref = "error-prone" } error-prone-check-api = { module = "com.google.errorprone:error_prone_check_api", version.ref = "error-prone" } error-prone-test-helpers = { module = "com.google.errorprone:error_prone_test_helpers", version.ref = "error-prone" } -error-prone-javac = { module = "com.google.errorprone:javac", version.ref = "error-prone-javac" } +error-prone-javac = { module = "com.google.errorprone:javac", version.ref = "error-prone-javac" } error-prone-gradle = { module = "net.ltgt.gradle:gradle-errorprone-plugin", version.ref = "error-prone-gradle" } @@ -167,12 +173,11 @@ hamcrest-junit = { module = "org.hamcrest:hamcrest-junit", version.ref = "hamcre icu4j = { module = "com.ibm.icu:icu4j", version.ref = "icu4j" } -jacoco-agent = { module = "org.jacoco:org.jacoco.agent", version.ref = "jacoco" } junit4 = { module = "junit:junit", version.ref = "junit4" } javax-annotation-api = { module = "javax.annotation:javax.annotation-api", version.ref = "javax-annotation-api" } javax-annotation-jsr250-api = { module = "javax.annotation:jsr250-api", version.ref = "javax-annotation-jsr250-api" } -javax-inject = { module = "javax.inject:javax.inject", version.ref = "javax.inject" } +javax-inject = { module = "javax.inject:javax.inject", version.ref = "javax-inject" } jetbrains-annotations = { module = "org.jetbrains:annotations", version.ref = "jetbrains-annotations" } @@ -202,6 +207,10 @@ mockito = { module = "org.mockito:mockito-core", version.ref = "mockito" } mockito-inline = { module = "org.mockito:mockito-inline", version.ref = "mockito" } mockk = { module = "io.mockk:mockk", version.ref = "mockk" } +roborazzi = { module = "io.github.takahirom.roborazzi:roborazzi", version.ref = "roborazzi" } +roborazzi-rule = { module = "io.github.takahirom.roborazzi:roborazzi-junit-rule", version.ref = "roborazzi" } +roborazzi-gradle = { module = "io.github.takahirom.roborazzi:roborazzi-gradle-plugin", version.ref = "roborazzi" } + androidx-annotation = { module = "androidx.annotation:annotation", version.ref = "androidx-annotation" } androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "androidx-appcompat" } androidx-biometric = { module = "androidx.biometric:biometric", version.ref = "androidx-biometric" } @@ -211,26 +220,17 @@ androidx-fragment = { module = "androidx.fragment:fragment", version.ref = "andr androidx-fragment-testing = { module = "androidx.fragment:fragment-testing", version.ref = "androidx-fragment" } androidx-multidex = { module = "androidx.multidex:multidex", version.ref = "androidx-multidex" } androidx-window = { module = "androidx.window:window", version.ref = "androidx-window" } +androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "androidx-room" } +androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "androidx-room" } -androidx-test-annotation = { module = "androidx.test:annotation", version.ref = "androidx-test-annotation" } androidx-test-core = { module = "androidx.test:core", version.ref = "androidx-test-core" } androidx-test-monitor = { module = "androidx.test:monitor", version.ref = "androidx-test-monitor" } -androidx-test-orchestrator = { module = "androidx.test:orchestrator", version.ref = "androidx-test-orchestrator" } androidx-test-rules = { module = "androidx.test:rules", version.ref = "androidx-test-core" } androidx-test-runner = { module = "androidx.test:runner", version.ref = "androidx-test-runner" } androidx-test-services = { module = "androidx.test.services:test-services", version.ref = "androidx-test-services" } -androidx-test-services-storage = { module = "androidx.test.services:storage", version.ref = "androidx-test-services" } androidx-test-espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "androidx-test-espresso" } -androidx-test-espresso-accessibility = { module = "androidx.test.espresso:espresso-accessibility", version.ref = "androidx-test-espresso" } -androidx-test-espresso-contrib = { module = "androidx.test.espresso:espresso-contrib", version.ref = "androidx-test-espresso" } androidx-test-espresso-intents = { module = "androidx.test.espresso:espresso-intents", version.ref = "androidx-test-espresso" } -androidx-test-espresso-remote = { module = "androidx.test.espresso:espresso-remote", version.ref = "androidx-test-espresso" } -androidx-test-espresso-web = { module = "androidx.test.espresso:espresso-web", version.ref = "androidx-test-espresso" } - -androidx-test-espresso-idling-resource = { module = "androidx.test.espresso:espresso-idling-resource", version.ref = "androidx-test-espresso" } -androidx-test-espresso-idling-concurrent = { module = "androidx.test.espresso.idling:idling-concurrent", version.ref = "androidx-test-espresso" } -androidx-test-espresso-idling-net = { module = "androidx.test.espresso.idling:idling-net", version.ref = "androidx-test-espresso" } androidx-test-ext-junit = { module = "androidx.test.ext:junit", version.ref = "androidx-test-ext-junit" } androidx-test-ext-truth = { module = "androidx.test.ext:truth", version.ref = "androidx-test-ext-truth" } @@ -239,9 +239,11 @@ androidx-fragment-for-shadows = { module = "androidx.fragment:fragment", version play-services-base-for-shadows = { module = "com.google.android.gms:play-services-base", version.ref = "play-services-base-for-shadows" } play-services-basement-for-shadows = { module = "com.google.android.gms:play-services-basement", version.ref = "play-services-base-for-shadows" } +play-services-basement = { module = "com.google.android.gms:play-services-basement", version.ref = "play-services-basement" } + [bundles] -play-services-base-for-shadows = [ "androidx-fragment-for-shadows", "play-services-base-for-shadows", "play-services-basement-for-shadows" ] -powermock = [ "powermock-module-junit4", "powermock-module-junit4-rule", "powermock-api-mockito2", "powermock-classloading-xstream" ] -sqlite4java-native = [ "sqlite4java-osx", "sqlite4java-linux-amd64", "sqlite4java-win32-x64", "sqlite4java-linux-i386", "sqlite4java-win32-x86" ] +play-services-base-for-shadows = ["androidx-fragment-for-shadows", "play-services-base-for-shadows", "play-services-basement-for-shadows"] +powermock = ["powermock-module-junit4", "powermock-module-junit4-rule", "powermock-api-mockito2", "powermock-classloading-xstream"] +sqlite4java-native = ["sqlite4java-osx", "sqlite4java-linux-amd64", "sqlite4java-win32-x64", "sqlite4java-linux-i386", "sqlite4java-win32-x86"] [plugins] diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar Binary files differindex c1962a79e..d64cd4917 100644 --- a/gradle/wrapper/gradle-wrapper.jar +++ b/gradle/wrapper/gradle-wrapper.jar diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index c30b486a8..a80b22ce5 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists @@ -83,7 +83,8 @@ done # This is normally unused # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -130,10 +131,13 @@ location of your Java installation." fi else JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. @@ -141,7 +145,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac @@ -149,7 +153,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then '' | soft) :;; #( *) # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -198,11 +202,11 @@ fi # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ diff --git a/gradlew.bat b/gradlew.bat index 6689b85be..7101f8e46 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -43,11 +43,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
-echo.
-echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
+echo. 1>&2
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
goto fail
@@ -57,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute
-echo.
-echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
+echo. 1>&2
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
goto fail
diff --git a/integration_tests/agp/build.gradle b/integration_tests/agp/build.gradle index 14a8b862b..71b9e74f7 100644 --- a/integration_tests/agp/build.gradle +++ b/integration_tests/agp/build.gradle @@ -22,7 +22,7 @@ android { dependencies { // Testing dependencies - testImplementation project(path: ':testapp') + testImplementation project(":testapp") testImplementation project(":robolectric") testImplementation project(":integration_tests:agp:testsupport") diff --git a/integration_tests/androidx/build.gradle b/integration_tests/androidx/build.gradle index 8f722119a..2664b2c2e 100644 --- a/integration_tests/androidx/build.gradle +++ b/integration_tests/androidx/build.gradle @@ -31,7 +31,7 @@ dependencies { implementation libs.androidx.window // Testing dependencies - testImplementation project(path: ':testapp') + testImplementation project(":testapp") testImplementation project(":robolectric") testImplementation libs.junit4 testImplementation libs.androidx.test.core diff --git a/integration_tests/androidx_test/src/main/java/org/robolectric/integrationtests/axt/ActivityWithAppCompatMenu.java b/integration_tests/androidx_test/src/main/java/org/robolectric/integrationtests/axt/ActivityWithAppCompatMenu.java index 937abf213..8f17a77b5 100644 --- a/integration_tests/androidx_test/src/main/java/org/robolectric/integrationtests/axt/ActivityWithAppCompatMenu.java +++ b/integration_tests/androidx_test/src/main/java/org/robolectric/integrationtests/axt/ActivityWithAppCompatMenu.java @@ -1,10 +1,10 @@ package org.robolectric.integrationtests.axt; import android.os.Bundle; -import androidx.appcompat.app.AppCompatActivity; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; +import androidx.appcompat.app.AppCompatActivity; import org.robolectric.integration.axt.R; /** {@link EspressoWithMenuTest} fixture activity that uses appcompat menu's */ diff --git a/integration_tests/androidx_test/src/main/res/layout/espresso_activity.xml b/integration_tests/androidx_test/src/main/res/layout/espresso_activity.xml index c412b90f0..639ac1b11 100644 --- a/integration_tests/androidx_test/src/main/res/layout/espresso_activity.xml +++ b/integration_tests/androidx_test/src/main/res/layout/espresso_activity.xml @@ -10,7 +10,8 @@ <EditText android:id="@+id/edit_text" android:layout_width="wrap_content" - android:layout_height="wrap_content" /> + android:layout_height="wrap_content" + android:inputType="textMultiLine" /> <Button android:id="@+id/button" diff --git a/integration_tests/androidx_test/src/main/res/menu/menu.xml b/integration_tests/androidx_test/src/main/res/menu/menu.xml index 05ff42d6f..33517017f 100644 --- a/integration_tests/androidx_test/src/main/res/menu/menu.xml +++ b/integration_tests/androidx_test/src/main/res/menu/menu.xml @@ -3,6 +3,5 @@ <item android:id="@+id/menu_filter" android:title="menu_title" - - android:showAsAction="never" /> + app:showAsAction="never" /> </menu>
\ No newline at end of file diff --git a/integration_tests/androidx_test/src/sharedTest/AndroidManifest.xml b/integration_tests/androidx_test/src/sharedTest/AndroidManifest.xml index 7f55dcc85..5ba96949d 100644 --- a/integration_tests/androidx_test/src/sharedTest/AndroidManifest.xml +++ b/integration_tests/androidx_test/src/sharedTest/AndroidManifest.xml @@ -1,7 +1,10 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest xmlns:android="http://schemas.android.com/apk/res/android"> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools"> - <application> + <application + android:appComponentFactory="org.robolectric.integrationtests.axt.ActivityScenarioTest$CustomAppComponentFactory" + tools:replace="android:appComponentFactory"> <activity android:name="org.robolectric.integrationtests.axt.ActivityTestRuleTest$TranscriptActivity" android:exported="true"/> @@ -18,6 +21,9 @@ <activity android:name="org.robolectric.integrationtests.axt.ActivityScenarioTest$TranscriptActivity" android:exported = "true"/> + <activity + android:name="org.robolectric.integrationtests.axt.ActivityScenarioTest$ActivityWithCustomConstructor" + android:exported = "true"/> <activity-alias android:name="org.robolectric.integrationtests.axt.ActivityScenarioTestAlias" android:targetActivity="org.robolectric.integrationtests.axt.ActivityScenarioTest$TranscriptActivity" /> diff --git a/integration_tests/androidx_test/src/test/AndroidManifest-ActivityScenario.xml b/integration_tests/androidx_test/src/test/AndroidManifest-ActivityScenario.xml index 9bcc80ee9..62402b2fd 100644 --- a/integration_tests/androidx_test/src/test/AndroidManifest-ActivityScenario.xml +++ b/integration_tests/androidx_test/src/test/AndroidManifest-ActivityScenario.xml @@ -7,13 +7,17 @@ android:minSdkVersion="19" android:targetSdkVersion="34"/> - <application> + <application + android:appComponentFactory="org.robolectric.integrationtests.axt.ActivityScenarioTest$CustomAppComponentFactory"> <activity android:name="org.robolectric.integrationtests.axt.ActivityScenarioTest$LifecycleOwnerActivity" android:exported="true"/> <activity android:name="org.robolectric.integrationtests.axt.ActivityScenarioTest$TranscriptActivity" android:exported = "true"/> + <activity + android:name="org.robolectric.integrationtests.axt.ActivityScenarioTest$ActivityWithCustomConstructor" + android:exported="true"/> <activity-alias android:name="org.robolectric.integrationtests.axt.ActivityScenarioTestAlias" android:targetActivity="org.robolectric.integrationtests.axt.ActivityScenarioTest$TranscriptActivity" /> diff --git a/integration_tests/androidx_test/src/test/java/org/robolectric/integrationtests/axt/ActivityScenarioTest.java b/integration_tests/androidx_test/src/test/java/org/robolectric/integrationtests/axt/ActivityScenarioTest.java index a444f1dfe..8f9760846 100644 --- a/integration_tests/androidx_test/src/test/java/org/robolectric/integrationtests/axt/ActivityScenarioTest.java +++ b/integration_tests/androidx_test/src/test/java/org/robolectric/integrationtests/axt/ActivityScenarioTest.java @@ -1,19 +1,22 @@ package org.robolectric.integrationtests.axt; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2; +import static android.os.Build.VERSION_CODES.P; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertThrows; import android.app.Activity; +import android.app.AppComponentFactory; import android.app.UiAutomation; import android.content.Context; import android.content.Intent; import android.content.res.Configuration; import android.os.Bundle; import android.os.Looper; -import androidx.fragment.app.Fragment; -import androidx.appcompat.app.AppCompatActivity; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.appcompat.R; +import androidx.appcompat.app.AppCompatActivity; +import androidx.fragment.app.Fragment; import androidx.lifecycle.Lifecycle.State; import androidx.test.core.app.ActivityScenario; import androidx.test.core.app.ApplicationProvider; @@ -106,6 +109,32 @@ public class ActivityScenarioTest { callbacks.clear(); } + public static class ActivityWithCustomConstructor extends Activity { + private final int intValue; + + public ActivityWithCustomConstructor(int intValue) { + this.intValue = intValue; + } + + public int getIntValue() { + return intValue; + } + } + + public static class CustomAppComponentFactory extends AppComponentFactory { + + @NonNull + @Override + public Activity instantiateActivity( + @NonNull ClassLoader cl, @NonNull String className, @Nullable Intent intent) + throws ClassNotFoundException, IllegalAccessException, InstantiationException { + if (className.contains(ActivityWithCustomConstructor.class.getName())) { + return new ActivityWithCustomConstructor(100); + } + return super.instantiateActivity(cl, className, intent); + } + } + @Test public void launch_callbackSequence() { try (ActivityScenario<TranscriptActivity> activityScenario = @@ -247,7 +276,6 @@ public class ActivityScenarioTest { } } - @Config(minSdk = JELLY_BEAN_MR2) @Test public void setRotation_recreatesActivity() { UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation(); @@ -314,4 +342,17 @@ public class ActivityScenarioTest { }); } } + + @Test + @Config(minSdk = P) + public void launchActivityWithCustomConstructor() { + try (ActivityScenario<ActivityWithCustomConstructor> activityScenario = + ActivityScenario.launch(ActivityWithCustomConstructor.class)) { + assertThat(activityScenario.getState()).isEqualTo(State.RESUMED); + activityScenario.onActivity( + activity -> { + assertThat(activity.getIntValue()).isEqualTo(100); + }); + } + } } diff --git a/integration_tests/androidx_test/src/test/java/org/robolectric/integrationtests/axt/EspressoWithSwitchCompatTest.java b/integration_tests/androidx_test/src/test/java/org/robolectric/integrationtests/axt/EspressoWithSwitchCompatTest.java index 13f8fc252..a7606ce51 100644 --- a/integration_tests/androidx_test/src/test/java/org/robolectric/integrationtests/axt/EspressoWithSwitchCompatTest.java +++ b/integration_tests/androidx_test/src/test/java/org/robolectric/integrationtests/axt/EspressoWithSwitchCompatTest.java @@ -19,7 +19,9 @@ import org.robolectric.integration.axt.R; public class EspressoWithSwitchCompatTest { @Test public void switchCompatTest() { - ActivityScenario.launch(ActivityWithSwitchCompat.class); - onView(withId(R.id.switch_compat_2)).check(matches(isCompletelyDisplayed())).perform(click()); + try (ActivityScenario<ActivityWithSwitchCompat> scenario = + ActivityScenario.launch(ActivityWithSwitchCompat.class)) { + onView(withId(R.id.switch_compat_2)).check(matches(isCompletelyDisplayed())).perform(click()); + } } } diff --git a/integration_tests/compat-target28/build.gradle b/integration_tests/compat-target28/build.gradle index a124b5eaf..7e11b884e 100644 --- a/integration_tests/compat-target28/build.gradle +++ b/integration_tests/compat-target28/build.gradle @@ -38,7 +38,7 @@ android { dependencies { implementation libs.kotlin.stdlib - testImplementation project(path: ':testapp') + testImplementation project(":testapp") testImplementation project(":robolectric") testImplementation libs.junit4 testImplementation libs.truth diff --git a/integration_tests/compat-target28/src/main/AndroidManifest.xml b/integration_tests/compat-target28/src/main/AndroidManifest.xml index a0c0db960..9419a324e 100644 --- a/integration_tests/compat-target28/src/main/AndroidManifest.xml +++ b/integration_tests/compat-target28/src/main/AndroidManifest.xml @@ -1,4 +1,8 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="org.robolectric.integrationtests.compattarget28"> - <application /> +<manifest xmlns:tools="http://schemas.android.com/tools" + package="org.robolectric.integrationtests.compattarget28" + xmlns:android="http://schemas.android.com/apk/res/android"> + <application + android:appComponentFactory="org.robolectric.integrationtests.compattarget28.TestAppComponentFactory" + tools:targetApi="p" /> </manifest> diff --git a/integration_tests/compat-target28/src/main/java/org/robolectric/integrationtests/compattarget28/MainActivity.java b/integration_tests/compat-target28/src/main/java/org/robolectric/integrationtests/compattarget28/MainActivity.java new file mode 100644 index 000000000..88722c541 --- /dev/null +++ b/integration_tests/compat-target28/src/main/java/org/robolectric/integrationtests/compattarget28/MainActivity.java @@ -0,0 +1,21 @@ +package org.robolectric.integrationtests.compattarget28; + +import android.app.Activity; + +public class MainActivity extends Activity { + public enum CreationSource { + DEFAULT_CONSTRUCTOR, + CUSTOM_CONSTRUCTOR + } + + public final CreationSource creationSource; + + @SuppressWarnings("unused") + public MainActivity() { + this(CreationSource.DEFAULT_CONSTRUCTOR); + } + + public MainActivity(CreationSource creationSource) { + this.creationSource = creationSource; + } +} diff --git a/integration_tests/compat-target28/src/main/java/org/robolectric/integrationtests/compattarget28/TestAppComponentFactory.java b/integration_tests/compat-target28/src/main/java/org/robolectric/integrationtests/compattarget28/TestAppComponentFactory.java new file mode 100644 index 000000000..8f16ee0e7 --- /dev/null +++ b/integration_tests/compat-target28/src/main/java/org/robolectric/integrationtests/compattarget28/TestAppComponentFactory.java @@ -0,0 +1,21 @@ +package org.robolectric.integrationtests.compattarget28; + +import android.app.Activity; +import android.app.AppComponentFactory; +import android.content.Intent; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class TestAppComponentFactory extends AppComponentFactory { + + @NotNull + @Override + public Activity instantiateActivity( + @NotNull ClassLoader cl, @NotNull String className, @Nullable Intent intent) + throws ClassNotFoundException, IllegalAccessException, InstantiationException { + if (className.equals(MainActivity.class.getName())) { + return new MainActivity(MainActivity.CreationSource.CUSTOM_CONSTRUCTOR); + } + return super.instantiateActivity(cl, className, intent); + } +} diff --git a/integration_tests/compat-target28/src/test/java/org/robolectric/integration/compat/target28/NormalCompatibilityTest.kt b/integration_tests/compat-target28/src/test/java/org/robolectric/integration/compat/target28/NormalCompatibilityTest.kt index 15e04799e..8521b5698 100644 --- a/integration_tests/compat-target28/src/test/java/org/robolectric/integration/compat/target28/NormalCompatibilityTest.kt +++ b/integration_tests/compat-target28/src/test/java/org/robolectric/integration/compat/target28/NormalCompatibilityTest.kt @@ -14,8 +14,13 @@ import com.google.common.truth.Truth.assertThat import org.junit.Test import org.junit.runner.RunWith import org.robolectric.Robolectric +import org.robolectric.Robolectric.buildActivity import org.robolectric.RobolectricTestRunner import org.robolectric.RuntimeEnvironment +import org.robolectric.Shadows +import org.robolectric.annotation.Config +import org.robolectric.integrationtests.compattarget28.MainActivity +import org.robolectric.integrationtests.compattarget28.MainActivity.CreationSource import org.robolectric.testapp.TestActivity @RunWith(RobolectricTestRunner::class) @@ -40,8 +45,11 @@ class NormalCompatibilityTest { } @Test - fun `Initialize Activity succeed`() { - Robolectric.setupActivity(TestActivity::class.java) + fun `Initialize Activity and its shadow succeed`() { + buildActivity(TestActivity::class.java).use { controller -> + val activity = controller.setup().get() + Shadows.shadowOf(activity) + } } @Test @@ -71,7 +79,20 @@ class NormalCompatibilityTest { srcRect, bitmap, listener, - Handler(Looper.getMainLooper()) + Handler(Looper.getMainLooper()), ) } + + @Test + fun `MainActivity created correctly using AppComponentFactory`() { + val activity = Robolectric.setupActivity(MainActivity::class.java) + assertThat(activity.creationSource).isEqualTo(CreationSource.CUSTOM_CONSTRUCTOR) + } + + @Test + @Config(minSdk = 19, maxSdk = 27) + fun `MainActivity created correctly using default constructor on api lower than 28`() { + val activity = Robolectric.setupActivity(MainActivity::class.java) + assertThat(activity.creationSource).isEqualTo(CreationSource.DEFAULT_CONSTRUCTOR) + } } diff --git a/integration_tests/ctesque/src/sharedTest/java/android/app/UiAutomationTest.kt b/integration_tests/ctesque/src/sharedTest/java/android/app/UiAutomationTest.kt index dd2ef15d9..fc31e5a23 100644 --- a/integration_tests/ctesque/src/sharedTest/java/android/app/UiAutomationTest.kt +++ b/integration_tests/ctesque/src/sharedTest/java/android/app/UiAutomationTest.kt @@ -1,7 +1,6 @@ package android.app import android.content.res.Configuration -import android.os.Build.VERSION_CODES.JELLY_BEAN_MR2 import android.view.Surface import androidx.test.core.app.ActivityScenario import androidx.test.ext.junit.runners.AndroidJUnit4 @@ -9,13 +8,11 @@ import androidx.test.platform.app.InstrumentationRegistry import com.google.common.truth.Truth.assertThat import org.junit.Test import org.junit.runner.RunWith -import org.robolectric.annotation.Config import org.robolectric.annotation.internal.DoNotInstrument import org.robolectric.testapp.TestActivity @Suppress("DEPRECATION") @DoNotInstrument -@Config(minSdk = JELLY_BEAN_MR2) @RunWith(AndroidJUnit4::class) class UiAutomationTest { @Test @@ -53,4 +50,4 @@ class UiAutomationTest { } } } -}
\ No newline at end of file +} diff --git a/integration_tests/ctesque/src/sharedTest/java/android/graphics/BitmapTest.java b/integration_tests/ctesque/src/sharedTest/java/android/graphics/BitmapTest.java index 75c328860..109194022 100644 --- a/integration_tests/ctesque/src/sharedTest/java/android/graphics/BitmapTest.java +++ b/integration_tests/ctesque/src/sharedTest/java/android/graphics/BitmapTest.java @@ -1,8 +1,5 @@ package android.graphics; -import static android.os.Build.VERSION_CODES.JELLY_BEAN; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; -import static android.os.Build.VERSION_CODES.KITKAT; import static android.os.Build.VERSION_CODES.LOLLIPOP; import static android.os.Build.VERSION_CODES.M; import static android.os.Build.VERSION_CODES.O; @@ -159,7 +156,6 @@ public class BitmapTest { } @Test - @Config(minSdk = JELLY_BEAN) public void checkBitmapNotRecycled() throws IOException { InputStream inputStream = resources.getAssets().open("robolectric.png"); BitmapFactory.Options options = new BitmapFactory.Options(); @@ -301,8 +297,6 @@ public class BitmapTest { } @Test - @Config(minSdk = KITKAT) - @SdkSuppress(minSdkVersion = KITKAT) public void reconfigure_drawPixel() { Bitmap bitmap = Bitmap.createBitmap(100, 50, Bitmap.Config.ARGB_8888); bitmap.reconfigure(50, 100, Bitmap.Config.ARGB_8888); @@ -374,16 +368,11 @@ public class BitmapTest { Bitmap.createBitmap(/* width= */ 1, /* height= */ 1, Bitmap.Config.ARGB_8888) .isMutable()) .isTrue(); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { - assertThat( - Bitmap.createBitmap( - (DisplayMetrics) null, - /* width= */ 1, - /* height= */ 1, - Bitmap.Config.ARGB_8888) - .isMutable()) - .isTrue(); - } + assertThat( + Bitmap.createBitmap( + (DisplayMetrics) null, /* width= */ 1, /* height= */ 1, Bitmap.Config.ARGB_8888) + .isMutable()) + .isTrue(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { assertThat( Bitmap.createBitmap( @@ -423,28 +412,26 @@ public class BitmapTest { Bitmap.Config.ARGB_8888) .isMutable()) .isFalse(); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { - assertThat( - Bitmap.createBitmap( - /* display= */ null, - /* colors= */ new int[] {0}, - /* width= */ 1, - /* height= */ 1, - Bitmap.Config.ARGB_8888) - .isMutable()) - .isFalse(); - assertThat( - Bitmap.createBitmap( - /* display= */ null, - /* colors= */ new int[] {0}, - /* offset= */ 0, - /* stride= */ 1, - /* width= */ 1, - /* height= */ 1, - Bitmap.Config.ARGB_8888) - .isMutable()) - .isFalse(); - } + assertThat( + Bitmap.createBitmap( + /* display= */ null, + /* colors= */ new int[] {0}, + /* width= */ 1, + /* height= */ 1, + Bitmap.Config.ARGB_8888) + .isMutable()) + .isFalse(); + assertThat( + Bitmap.createBitmap( + /* display= */ null, + /* colors= */ new int[] {0}, + /* offset= */ 0, + /* stride= */ 1, + /* width= */ 1, + /* height= */ 1, + Bitmap.Config.ARGB_8888) + .isMutable()) + .isFalse(); } @Test @@ -507,8 +494,6 @@ public class BitmapTest { } } - @Config(minSdk = JELLY_BEAN_MR1) - @SdkSuppress(minSdkVersion = JELLY_BEAN_MR1) @Test public void createBitmap_premultiplied() { // ARGB_8888 has alpha by default, is premultiplied. diff --git a/integration_tests/ctesque/src/sharedTest/java/android/text/format/TimeTest.java b/integration_tests/ctesque/src/sharedTest/java/android/text/format/TimeTest.java index 0e3f989f5..41d066dfd 100644 --- a/integration_tests/ctesque/src/sharedTest/java/android/text/format/TimeTest.java +++ b/integration_tests/ctesque/src/sharedTest/java/android/text/format/TimeTest.java @@ -1,6 +1,5 @@ package android.text.format; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -10,7 +9,6 @@ import static org.junit.Assert.assertTrue; import android.util.TimeFormatException; import androidx.test.ext.junit.runners.AndroidJUnit4; -import androidx.test.filters.SdkSuppress; import java.util.Arrays; import java.util.TimeZone; import org.junit.After; @@ -240,7 +238,6 @@ public class TimeTest { } @Test - @SdkSuppress(minSdkVersion = JELLY_BEAN_MR1) public void shouldFormat() { Time t = new Time(Time.TIMEZONE_UTC); t.set(3600000L); @@ -250,7 +247,6 @@ public class TimeTest { } @Test - @SdkSuppress(minSdkVersion = JELLY_BEAN_MR1) public void shouldFormatAndroidStrings() { Time t = new Time("UTC"); // NOTE: month is zero-based. diff --git a/integration_tests/ctesque/src/sharedTest/java/android/util/RationalTest.java b/integration_tests/ctesque/src/sharedTest/java/android/util/RationalTest.java new file mode 100644 index 000000000..69a088b31 --- /dev/null +++ b/integration_tests/ctesque/src/sharedTest/java/android/util/RationalTest.java @@ -0,0 +1,534 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This test is created from Android CTS tests: + * + * https://cs.android.com/android/platform/superproject/main/+/main:cts/tests/tests/util/src/android/util/cts/RationalTest.java + */ + +package android.util; + +import static android.util.Rational.NEGATIVE_INFINITY; +import static android.util.Rational.NaN; +import static android.util.Rational.POSITIVE_INFINITY; +import static android.util.Rational.ZERO; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import android.os.Build; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InvalidObjectException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.lang.reflect.Field; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.annotation.Config; +import org.robolectric.annotation.internal.DoNotInstrument; + +@DoNotInstrument +@Config(minSdk = Build.VERSION_CODES.LOLLIPOP) +@RunWith(AndroidJUnit4.class) +public class RationalTest { + + /** (1,1) */ + private static final Rational UNIT = new Rational(1, 1); + + @Test + public void testConstructor() { + + // Simple case + Rational r = new Rational(1, 2); + assertEquals(1, r.getNumerator()); + assertEquals(2, r.getDenominator()); + + // Denominator negative + r = new Rational(-1, 2); + assertEquals(-1, r.getNumerator()); + assertEquals(2, r.getDenominator()); + + // Numerator negative + r = new Rational(1, -2); + assertEquals(-1, r.getNumerator()); + assertEquals(2, r.getDenominator()); + + // Both negative + r = new Rational(-1, -2); + assertEquals(1, r.getNumerator()); + assertEquals(2, r.getDenominator()); + + // Infinity. + r = new Rational(1, 0); + assertEquals(1, r.getNumerator()); + assertEquals(0, r.getDenominator()); + + // Negative infinity. + r = new Rational(-1, 0); + assertEquals(-1, r.getNumerator()); + assertEquals(0, r.getDenominator()); + + // NaN. + r = new Rational(0, 0); + assertEquals(0, r.getNumerator()); + assertEquals(0, r.getDenominator()); + } + + @Test + public void testEquals() { + Rational r = new Rational(1, 2); + assertEquals(1, r.getNumerator()); + assertEquals(2, r.getDenominator()); + + assertEquals(r, r); + assertFalse(r.equals(null)); + assertFalse(r.equals(new Object())); + + Rational twoThirds = new Rational(2, 3); + assertFalse(r.equals(twoThirds)); + assertFalse(twoThirds.equals(r)); + + Rational fourSixths = new Rational(4, 6); + assertEquals(twoThirds, fourSixths); + assertEquals(fourSixths, twoThirds); + + Rational moreComplicated = new Rational(5 * 6 * 7 * 8 * 9, 1 * 2 * 3 * 4 * 5); + Rational moreComplicated2 = new Rational(5 * 6 * 7 * 8 * 9 * 78, 1 * 2 * 3 * 4 * 5 * 78); + assertEquals(moreComplicated, moreComplicated2); + assertEquals(moreComplicated2, moreComplicated); + + // Ensure negatives are fine + twoThirds = new Rational(-2, 3); + fourSixths = new Rational(-4, 6); + assertEquals(twoThirds, fourSixths); + assertEquals(fourSixths, twoThirds); + + moreComplicated = new Rational(-5 * 6 * 7 * 8 * 9, 1 * 2 * 3 * 4 * 5); + moreComplicated2 = new Rational(-5 * 6 * 7 * 8 * 9 * 78, 1 * 2 * 3 * 4 * 5 * 78); + assertEquals(moreComplicated, moreComplicated2); + assertEquals(moreComplicated2, moreComplicated); + + // Zero is always equal to itself + Rational zero2 = new Rational(0, 100); + assertEquals(ZERO, zero2); + assertEquals(zero2, ZERO); + + // NaN is always equal to itself + Rational nan = NaN; + Rational nan2 = new Rational(0, 0); + assertTrue(nan.equals(nan)); + assertTrue(nan.equals(nan2)); + assertTrue(nan2.equals(nan)); + assertFalse(nan.equals(r)); + assertFalse(r.equals(nan)); + + // Infinities of the same sign are equal. + Rational posInf = POSITIVE_INFINITY; + Rational posInf2 = new Rational(2, 0); + Rational negInf = NEGATIVE_INFINITY; + Rational negInf2 = new Rational(-2, 0); + assertEquals(posInf, posInf); + assertEquals(negInf, negInf); + assertEquals(posInf, posInf2); + assertEquals(negInf, negInf2); + + // Infinities aren't equal to anything else. + assertFalse(posInf.equals(negInf)); + assertFalse(negInf.equals(posInf)); + assertFalse(negInf.equals(r)); + assertFalse(posInf.equals(r)); + assertFalse(r.equals(negInf)); + assertFalse(r.equals(posInf)); + assertFalse(posInf.equals(nan)); + assertFalse(negInf.equals(nan)); + assertFalse(nan.equals(posInf)); + assertFalse(nan.equals(negInf)); + } + + @Test + public void testReduction() { + Rational moreComplicated = new Rational(5 * 78, 7 * 78); + assertEquals(new Rational(5, 7), moreComplicated); + assertEquals(5, moreComplicated.getNumerator()); + assertEquals(7, moreComplicated.getDenominator()); + + Rational posInf = new Rational(5, 0); + assertEquals(1, posInf.getNumerator()); + assertEquals(0, posInf.getDenominator()); + assertEquals(POSITIVE_INFINITY, posInf); + + Rational negInf = new Rational(-100, 0); + assertEquals(-1, negInf.getNumerator()); + assertEquals(0, negInf.getDenominator()); + assertEquals(NEGATIVE_INFINITY, negInf); + + Rational zero = new Rational(0, -100); + assertEquals(0, zero.getNumerator()); + assertEquals(1, zero.getDenominator()); + assertEquals(ZERO, zero); + + Rational flipSigns = new Rational(1, -1); + assertEquals(-1, flipSigns.getNumerator()); + assertEquals(1, flipSigns.getDenominator()); + + Rational flipAndReduce = new Rational(100, -200); + assertEquals(-1, flipAndReduce.getNumerator()); + assertEquals(2, flipAndReduce.getDenominator()); + } + + @Test + public void testCompareTo() { + // unit is equal to itself + verifyCompareEquals(UNIT, new Rational(1, 1)); + + // NaN is greater than anything but NaN + verifyCompareEquals(NaN, new Rational(0, 0)); + verifyGreaterThan(NaN, UNIT); + verifyGreaterThan(NaN, POSITIVE_INFINITY); + verifyGreaterThan(NaN, NEGATIVE_INFINITY); + verifyGreaterThan(NaN, ZERO); + + // Positive infinity is greater than any other non-NaN + verifyCompareEquals(POSITIVE_INFINITY, new Rational(1, 0)); + verifyGreaterThan(POSITIVE_INFINITY, UNIT); + verifyGreaterThan(POSITIVE_INFINITY, NEGATIVE_INFINITY); + verifyGreaterThan(POSITIVE_INFINITY, ZERO); + + // Negative infinity is smaller than any other non-NaN + verifyCompareEquals(NEGATIVE_INFINITY, new Rational(-1, 0)); + verifyLessThan(NEGATIVE_INFINITY, UNIT); + verifyLessThan(NEGATIVE_INFINITY, POSITIVE_INFINITY); + verifyLessThan(NEGATIVE_INFINITY, ZERO); + + // A finite number with the same denominator is trivially comparable + verifyGreaterThan(new Rational(3, 100), new Rational(1, 100)); + verifyGreaterThan(new Rational(3, 100), ZERO); + + // Compare finite numbers with different divisors + verifyGreaterThan(new Rational(5, 25), new Rational(1, 10)); + verifyGreaterThan(new Rational(5, 25), ZERO); + + // Compare finite numbers with different signs + verifyGreaterThan(new Rational(5, 25), new Rational(-1, 10)); + verifyLessThan(new Rational(-5, 25), ZERO); + } + + @Test + public void testConvenienceMethods() { + // isFinite + verifyFinite(ZERO, true); + verifyFinite(NaN, false); + verifyFinite(NEGATIVE_INFINITY, false); + verifyFinite(POSITIVE_INFINITY, false); + verifyFinite(UNIT, true); + + // isInfinite + verifyInfinite(ZERO, false); + verifyInfinite(NaN, false); + verifyInfinite(NEGATIVE_INFINITY, true); + verifyInfinite(POSITIVE_INFINITY, true); + verifyInfinite(UNIT, false); + + // isNaN + verifyNaN(ZERO, false); + verifyNaN(NaN, true); + verifyNaN(NEGATIVE_INFINITY, false); + verifyNaN(POSITIVE_INFINITY, false); + verifyNaN(UNIT, false); + + // isZero + verifyZero(ZERO, true); + verifyZero(NaN, false); + verifyZero(NEGATIVE_INFINITY, false); + verifyZero(POSITIVE_INFINITY, false); + verifyZero(UNIT, false); + } + + @Test + public void testValueConversions() { + // Unit, simple case + verifyValueEquals(UNIT, 1.0f); + verifyValueEquals(UNIT, 1.0); + verifyValueEquals(UNIT, 1L); + verifyValueEquals(UNIT, 1); + verifyValueEquals(UNIT, (short) 1); + + // Zero, simple case + verifyValueEquals(ZERO, 0.0f); + verifyValueEquals(ZERO, 0.0); + verifyValueEquals(ZERO, 0L); + verifyValueEquals(ZERO, 0); + verifyValueEquals(ZERO, (short) 0); + + // NaN is 0 for integers, not-a-number for floating point + verifyValueEquals(NaN, Float.NaN); + verifyValueEquals(NaN, Double.NaN); + verifyValueEquals(NaN, 0L); + verifyValueEquals(NaN, 0); + verifyValueEquals(NaN, (short) 0); + + // Positive infinity, saturates upwards for integers + verifyValueEquals(POSITIVE_INFINITY, Float.POSITIVE_INFINITY); + verifyValueEquals(POSITIVE_INFINITY, Double.POSITIVE_INFINITY); + verifyValueEquals(POSITIVE_INFINITY, Long.MAX_VALUE); + verifyValueEquals(POSITIVE_INFINITY, Integer.MAX_VALUE); + verifyValueEquals(POSITIVE_INFINITY, (short) -1); + + // Negative infinity, saturates downwards for integers + verifyValueEquals(NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY); + verifyValueEquals(NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY); + verifyValueEquals(NEGATIVE_INFINITY, Long.MIN_VALUE); + verifyValueEquals(NEGATIVE_INFINITY, Integer.MIN_VALUE); + verifyValueEquals(NEGATIVE_INFINITY, (short) 0); + + // Normal finite values, round down for integers + final Rational oneQuarter = new Rational(1, 4); + verifyValueEquals(oneQuarter, 1.0f / 4.0f); + verifyValueEquals(oneQuarter, 1.0 / 4.0); + verifyValueEquals(oneQuarter, 0L); + verifyValueEquals(oneQuarter, 0); + verifyValueEquals(oneQuarter, (short) 0); + + final Rational nineFifths = new Rational(9, 5); + verifyValueEquals(nineFifths, 9.0f / 5.0f); + verifyValueEquals(nineFifths, 9.0 / 5.0); + verifyValueEquals(nineFifths, 1L); + verifyValueEquals(nineFifths, 1); + verifyValueEquals(nineFifths, (short) 1); + + final Rational negativeHundred = new Rational(-1000, 10); + verifyValueEquals(negativeHundred, -100.f / 1.f); + verifyValueEquals(negativeHundred, -100.0 / 1.0); + verifyValueEquals(negativeHundred, -100L); + verifyValueEquals(negativeHundred, -100); + verifyValueEquals(negativeHundred, (short) -100); + + // Short truncates if the result is too large + verifyValueEquals(new Rational(Integer.MAX_VALUE, 1), (short) Integer.MAX_VALUE); + verifyValueEquals(new Rational(0x00FFFFFF, 1), (short) 0x00FFFFFF); + verifyValueEquals(new Rational(0x00FF00FF, 1), (short) 0x00FF00FF); + } + + @Test + public void testSerialize() throws ClassNotFoundException, IOException, NoSuchFieldException { + /* + * Check correct [de]serialization + */ + verifyEqualsAfterSerializing(ZERO); + verifyEqualsAfterSerializing(NaN); + verifyEqualsAfterSerializing(NEGATIVE_INFINITY); + verifyEqualsAfterSerializing(POSITIVE_INFINITY); + verifyEqualsAfterSerializing(UNIT); + verifyEqualsAfterSerializing(new Rational(100, 200)); + verifyEqualsAfterSerializing(new Rational(-100, 200)); + verifyEqualsAfterSerializing(new Rational(5, 1)); + verifyEqualsAfterSerializing(new Rational(Integer.MAX_VALUE, Integer.MIN_VALUE)); + + /* + * Check bad deserialization fails + */ + try { + Rational badZero = createIllegalRational(0, 100); // [0, 100] , should be [0, 1] + Rational results = serializeRoundTrip(badZero); + fail("Deserializing " + results + " should not have succeeded"); + } catch (InvalidObjectException e) { + // OK + } + + try { + Rational badPosInfinity = createIllegalRational(100, 0); // [100, 0] , should be [1, 0] + Rational results = serializeRoundTrip(badPosInfinity); + fail("Deserializing " + results + " should not have succeeded"); + } catch (InvalidObjectException e) { + // OK + } + + try { + Rational badNegInfinity = createIllegalRational(-100, 0); // [-100, 0] , should be [-1, 0] + Rational results = serializeRoundTrip(badNegInfinity); + fail("Deserializing " + results + " should not have succeeded"); + } catch (InvalidObjectException e) { + // OK + } + + try { + Rational badReduced = createIllegalRational(2, 4); // [2,4] , should be [1, 2] + Rational results = serializeRoundTrip(badReduced); + fail("Deserializing " + results + " should not have succeeded"); + } catch (InvalidObjectException e) { + // OK + } + + try { + Rational badReducedNeg = createIllegalRational(-2, 4); // [-2, 4] should be [-1, 2] + Rational results = serializeRoundTrip(badReducedNeg); + fail("Deserializing " + results + " should not have succeeded"); + } catch (InvalidObjectException e) { + // OK + } + } + + @Test + public void testParseRational() { + assertEquals(new Rational(1, 2), Rational.parseRational("3:+6")); + assertEquals(new Rational(1, 2), Rational.parseRational("-3:-6")); + assertEquals(Rational.NaN, Rational.parseRational("NaN")); + assertEquals(Rational.POSITIVE_INFINITY, Rational.parseRational("Infinity")); + assertEquals(Rational.NEGATIVE_INFINITY, Rational.parseRational("-Infinity")); + assertEquals(Rational.ZERO, Rational.parseRational("0/261")); + assertEquals(Rational.NaN, Rational.parseRational("0/-0")); + assertEquals(Rational.POSITIVE_INFINITY, Rational.parseRational("1000/+0")); + assertEquals(Rational.NEGATIVE_INFINITY, Rational.parseRational("-1000/-0")); + + Rational r = new Rational(10, 15); + assertEquals(r, Rational.parseRational(r.toString())); + } + + @Test(expected = NumberFormatException.class) + public void testParseRationalInvalid1() { + Rational.parseRational("1.5"); + } + + @Test(expected = NumberFormatException.class) + public void testParseRationalInvalid2() { + Rational.parseRational("239"); + } + + private static void verifyValueEquals(Rational object, float expected) { + assertEquals("Checking floatValue() for " + object + ";", expected, object.floatValue(), 0.0f); + } + + private static void verifyValueEquals(Rational object, double expected) { + assertEquals( + "Checking doubleValue() for " + object + ";", expected, object.doubleValue(), 0.0f); + } + + private static void verifyValueEquals(Rational object, long expected) { + assertEquals("Checking longValue() for " + object + ";", expected, object.longValue()); + } + + private static void verifyValueEquals(Rational object, int expected) { + assertEquals("Checking intValue() for " + object + ";", expected, object.intValue()); + } + + private static void verifyValueEquals(Rational object, short expected) { + assertEquals("Checking shortValue() for " + object + ";", expected, object.shortValue()); + } + + private static void verifyFinite(Rational object, boolean expected) { + verifyAction("finite", object, expected, object.isFinite()); + } + + private static void verifyInfinite(Rational object, boolean expected) { + verifyAction("infinite", object, expected, object.isInfinite()); + } + + private static void verifyNaN(Rational object, boolean expected) { + verifyAction("NaN", object, expected, object.isNaN()); + } + + private static void verifyZero(Rational object, boolean expected) { + verifyAction("zero", object, expected, object.isZero()); + } + + private static <T> void verifyAction(String action, T object, boolean expected, boolean actual) { + String expectedMessage = expected ? action : ("not " + action); + assertEquals("Expected " + object + " to be " + expectedMessage, expected, actual); + } + + private static <T extends Comparable<? super T>> void verifyLessThan(T left, T right) { + assertTrue( + "Expected (LR) left " + left + " to be less than right " + right, + left.compareTo(right) < 0); + assertTrue( + "Expected (RL) left " + left + " to be less than right " + right, + right.compareTo(left) > 0); + } + + private static <T extends Comparable<? super T>> void verifyGreaterThan(T left, T right) { + assertTrue( + "Expected (LR) left " + left + " to be greater than right " + right, + left.compareTo(right) > 0); + assertTrue( + "Expected (RL) left " + left + " to be greater than right " + right, + right.compareTo(left) < 0); + } + + private static <T extends Comparable<? super T>> void verifyCompareEquals(T left, T right) { + assertTrue( + "Expected (LR) left " + left + " to be compareEquals to right " + right, + left.compareTo(right) == 0); + assertTrue( + "Expected (RL) left " + left + " to be compareEquals to right " + right, + right.compareTo(left) == 0); + } + + private static <T extends Serializable> byte[] serialize(T obj) throws IOException { + ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); + try (ObjectOutputStream objectStream = new ObjectOutputStream(byteStream)) { + objectStream.writeObject(obj); + } + return byteStream.toByteArray(); + } + + private static <T extends Serializable> T deserialize(byte[] array, Class<T> klass) + throws IOException, ClassNotFoundException { + ByteArrayInputStream bais = new ByteArrayInputStream(array); + ObjectInputStream ois = new ObjectInputStream(bais); + Object obj = ois.readObject(); + return klass.cast(obj); + } + + @SuppressWarnings("unchecked") + private static <T extends Serializable> T serializeRoundTrip(T obj) + throws IOException, ClassNotFoundException { + Class<T> klass = (Class<T>) obj.getClass(); + byte[] arr = serialize(obj); + T serialized = deserialize(arr, klass); + return serialized; + } + + private static <T extends Serializable> void verifyEqualsAfterSerializing(T obj) + throws ClassNotFoundException, IOException { + T serialized = serializeRoundTrip(obj); + assertEquals("Expected values to be equal after serialization round-trip", obj, serialized); + } + + private static Rational createIllegalRational(int numerator, int denominator) { + Rational r = new Rational(numerator, denominator); + mutateField(r, "mNumerator", numerator); + mutateField(r, "mDenominator", denominator); + return r; + } + + private static <T> void mutateField(T object, String name, int value) { + try { + Field f = object.getClass().getDeclaredField(name); + f.setAccessible(true); + f.set(object, value); + } catch (NoSuchFieldException e) { + throw new AssertionError(e); + } catch (IllegalAccessException e) { + throw new AssertionError(e); + } catch (IllegalArgumentException e) { + throw new AssertionError(e); + } + } +} diff --git a/integration_tests/ctesque/src/sharedTest/java/android/webkit/CookieManagerTest.java b/integration_tests/ctesque/src/sharedTest/java/android/webkit/CookieManagerTest.java index ea784e73b..351cf82ac 100644 --- a/integration_tests/ctesque/src/sharedTest/java/android/webkit/CookieManagerTest.java +++ b/integration_tests/ctesque/src/sharedTest/java/android/webkit/CookieManagerTest.java @@ -2,12 +2,8 @@ package android.webkit; import static com.google.common.truth.Truth.assertThat; -import android.os.Build.VERSION; -import android.os.Build.VERSION_CODES; -import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.After; -import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.annotation.internal.DoNotInstrument; @@ -17,14 +13,6 @@ import org.robolectric.annotation.internal.DoNotInstrument; @RunWith(AndroidJUnit4.class) public class CookieManagerTest { - @Before - public void setUp() { - // Required to initialize native CookieManager for emulators with SDK < 19. - if (VERSION.SDK_INT < VERSION_CODES.KITKAT) { - CookieSyncManager.createInstance(ApplicationProvider.getApplicationContext()); - } - } - @After public void tearDown() { CookieManager.getInstance().removeAllCookie(); @@ -67,4 +55,22 @@ public class CookieManagerTest { String cookie = cookieManager.getCookie(httpsUrl); assertThat(cookie).isEqualTo("ID=test-id"); } + + @Test + public void shouldSetAndGetCookieWithWhitespacesInUrlParameters() { + CookieManager cookieManager = CookieManager.getInstance(); + String url = "http://www.google.com/?q=This is a test query"; + String value = "my cookie"; + cookieManager.setCookie(url, value); + assertThat(cookieManager.getCookie(url)).isEqualTo(value); + } + + @Test + public void shouldSetAndGetCookieWithEncodedWhitespacesInUrlParameters() { + CookieManager cookieManager = CookieManager.getInstance(); + String url = "http://www.google.com/?q=This%20is%20a%20test%20query"; + String value = "my cookie"; + cookieManager.setCookie(url, value); + assertThat(cookieManager.getCookie(url)).isEqualTo(value); + } } diff --git a/integration_tests/jacoco-offline/build.gradle b/integration_tests/jacoco-offline/build.gradle index e0d34c9e0..ea668f70a 100644 --- a/integration_tests/jacoco-offline/build.gradle +++ b/integration_tests/jacoco-offline/build.gradle @@ -14,7 +14,6 @@ configurations { jacocoRuntime } - dependencies { testCompileOnly AndroidSdk.MAX_SDK.coordinates testRuntimeOnly AndroidSdk.MAX_SDK.coordinates diff --git a/integration_tests/kotlin/build.gradle b/integration_tests/kotlin/build.gradle index a8bf910c8..550f08a45 100644 --- a/integration_tests/kotlin/build.gradle +++ b/integration_tests/kotlin/build.gradle @@ -23,6 +23,7 @@ compileTestKotlin { dependencies { api project(":robolectric") compileOnly AndroidSdk.MAX_SDK.coordinates + implementation libs.androidx.annotation testCompileOnly AndroidSdk.MAX_SDK.coordinates testRuntimeOnly AndroidSdk.MAX_SDK.coordinates diff --git a/integration_tests/kotlin/src/test/kotlin/org/robolectric/integrationtests/kotlin/CustomShadowImageView.kt b/integration_tests/kotlin/src/test/kotlin/org/robolectric/integrationtests/kotlin/CustomShadowImageView.kt index 9b2a83a4c..2affab380 100644 --- a/integration_tests/kotlin/src/test/kotlin/org/robolectric/integrationtests/kotlin/CustomShadowImageView.kt +++ b/integration_tests/kotlin/src/test/kotlin/org/robolectric/integrationtests/kotlin/CustomShadowImageView.kt @@ -1,21 +1,21 @@ package org.robolectric.integrationtests.kotlin import android.widget.ImageView -import androidx.annotation.DrawableRes import org.robolectric.annotation.Implementation import org.robolectric.annotation.Implements import org.robolectric.annotation.RealObject +import org.robolectric.shadows.ShadowView @Implements(ImageView::class) -open class CustomShadowImageView { +open class CustomShadowImageView : ShadowView() { @RealObject lateinit var realImageView: ImageView - @DrawableRes - var setImageResource: Int = 0 + var longClickPerformed: Boolean = false private set @Implementation - protected fun setImageResource(resId: Int) { - setImageResource = resId + protected override fun performLongClick(): Boolean { + longClickPerformed = true + return super.performLongClick() } } diff --git a/integration_tests/kotlin/src/test/kotlin/org/robolectric/integrationtests/kotlin/CustomShadowImageViewTest.kt b/integration_tests/kotlin/src/test/kotlin/org/robolectric/integrationtests/kotlin/CustomShadowImageViewTest.kt index 52a5b8bde..e641233d8 100644 --- a/integration_tests/kotlin/src/test/kotlin/org/robolectric/integrationtests/kotlin/CustomShadowImageViewTest.kt +++ b/integration_tests/kotlin/src/test/kotlin/org/robolectric/integrationtests/kotlin/CustomShadowImageViewTest.kt @@ -1,10 +1,12 @@ package org.robolectric.integrationtests.kotlin +import android.app.Activity +import android.view.ViewGroup import android.widget.ImageView -import androidx.test.core.app.ApplicationProvider import com.google.common.truth.Truth.assertThat import org.junit.Test import org.junit.runner.RunWith +import org.robolectric.Robolectric import org.robolectric.RobolectricTestRunner import org.robolectric.annotation.Config import org.robolectric.shadow.api.Shadow @@ -14,12 +16,14 @@ import org.robolectric.shadow.api.Shadow class CustomShadowImageViewTest { @Test fun `use custom ShadowImageView`() { - val imageView = ImageView(ApplicationProvider.getApplicationContext()) + val activity = Robolectric.setupActivity(Activity::class.java) + val imageView = ImageView(activity) + (activity.findViewById(android.R.id.content) as ViewGroup).addView(imageView) val shadowImageView = Shadow.extract<CustomShadowImageView>(imageView) assertThat(shadowImageView).isNotNull() assertThat(shadowImageView.realImageView).isSameInstanceAs(imageView) val resourceId = Int.MAX_VALUE - imageView.setImageResource(resourceId) - assertThat(shadowImageView.setImageResource).isEqualTo(resourceId) + imageView.performLongClick() + assertThat(shadowImageView.longClickPerformed).isTrue() } } diff --git a/integration_tests/memoryleaks/build.gradle b/integration_tests/memoryleaks/build.gradle index 183138101..1b1f3f4aa 100644 --- a/integration_tests/memoryleaks/build.gradle +++ b/integration_tests/memoryleaks/build.gradle @@ -22,15 +22,13 @@ android { includeAndroidResources = true } } - } dependencies { // Testing dependencies - testImplementation project(path: ':testapp') + testImplementation project(":testapp") testImplementation project(":robolectric") testImplementation libs.junit4 testImplementation libs.guava.testlib - testImplementation libs.guava.testlib testImplementation libs.androidx.fragment } diff --git a/integration_tests/nativegraphics/Android.bp b/integration_tests/nativegraphics/Android.bp index b5a7ffe20..06d38add9 100644 --- a/integration_tests/nativegraphics/Android.bp +++ b/integration_tests/nativegraphics/Android.bp @@ -59,6 +59,7 @@ android_robolectric_test { "android.test.base", "android.test.mock", "truth", + "guava-android-testlib", ], upstream: true, java_resource_dirs: ["config"], diff --git a/integration_tests/nativegraphics/build.gradle b/integration_tests/nativegraphics/build.gradle index a243ea85c..7abd82afd 100644 --- a/integration_tests/nativegraphics/build.gradle +++ b/integration_tests/nativegraphics/build.gradle @@ -38,4 +38,5 @@ dependencies { testImplementation libs.truth testImplementation libs.junit4 testImplementation libs.mockito + testImplementation libs.guava.testlib } diff --git a/integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/ShadowNativeAllocationRegistryTest.java b/integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/ShadowNativeAllocationRegistryTest.java index 14f74de93..0fce5d5af 100644 --- a/integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/ShadowNativeAllocationRegistryTest.java +++ b/integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/ShadowNativeAllocationRegistryTest.java @@ -1,16 +1,45 @@ package org.robolectric.integrationtests.nativegraphics; +import static android.os.Build.VERSION_CODES.O; +import static android.os.Build.VERSION_CODES.S; +import static com.google.common.truth.Truth.assertThat; +import static org.robolectric.util.reflector.Reflector.reflector; + import android.graphics.Bitmap; import android.graphics.Color; +import android.graphics.Matrix; +import com.google.common.testing.GcFinalization; +import java.lang.ref.WeakReference; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; +import org.robolectric.util.reflector.Accessor; +import org.robolectric.util.reflector.ForType; +import org.robolectric.util.reflector.Static; @RunWith(RobolectricTestRunner.class) -@Config(sdk = 30) +@Config(minSdk = O) public final class ShadowNativeAllocationRegistryTest { + @Test + public void applyFreeFunction_matrix() throws Exception { + WeakReference<Matrix> weakMatrix = new WeakReference<>(newMatrix()); + // Invokes 'applyFreeFunction' when the matrix is GC'd. + GcFinalization.awaitClear(weakMatrix); + } + + // Creates a new Matrix as a local variable, which is eligible for GC when it goes out + // of scope. + private Matrix newMatrix() { + Matrix matrix = new Matrix(); + long pointer = reflector(MatrixReflector.class, matrix).getNativeInstance(); + long freeFunction = reflector(MatrixReflector.class).nGetNativeFinalizer(); + assertThat(pointer).isNotEqualTo(0); + assertThat(freeFunction).isNotEqualTo(0); + return matrix; + } + @Config(sdk = S) // No need to re-run on multiple SDK levels @Test public void nativeAllocationRegistryStressTest() { for (int i = 0; i < 10_000; i++) { @@ -21,4 +50,13 @@ public final class ShadowNativeAllocationRegistryTest { } } } + + @ForType(Matrix.class) + interface MatrixReflector { + @Accessor("native_instance") + long getNativeInstance(); + + @Static + long nGetNativeFinalizer(); + } } diff --git a/integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/ShadowNativeBitmapFactoryTest.java b/integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/ShadowNativeBitmapFactoryTest.java index 31961b6f4..418cf93f6 100644 --- a/integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/ShadowNativeBitmapFactoryTest.java +++ b/integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/ShadowNativeBitmapFactoryTest.java @@ -43,9 +43,11 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileDescriptor; +import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.io.RandomAccessFile; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -488,6 +490,78 @@ public class ShadowNativeBitmapFactoryTest { assertNull(BitmapFactory.decodeFileDescriptor(input, r, opt2)); } + @Test + public void testDecodeFileDescriptor2() throws IOException { + ParcelFileDescriptor pfd = obtainParcelDescriptor(obtainPath()); + FileDescriptor input = pfd.getFileDescriptor(); + Bitmap b = BitmapFactory.decodeFileDescriptor(input); + + assertNotNull(b); + // Test the bitmap size + assertEquals(START_HEIGHT, b.getHeight()); + assertEquals(START_WIDTH, b.getWidth()); + } + + @Test + public void testDecodeFileDescriptor3() throws IOException { + for (TestImage testImage : testImages()) { + // Arbitrary offsets to use. If the offset of the FD matches the offset of the image, + // decoding should succeed, but if they do not match, decoding should fail. + final long[] actualOffsets = new long[] {0, 17}; + for (int j = 0; j < actualOffsets.length; ++j) { + long actualOffset = actualOffsets[j]; + String path = obtainPath(testImage.id, actualOffset); + RandomAccessFile file = new RandomAccessFile(path, "r"); + FileDescriptor fd = file.getFD(); + assertTrue(fd.valid()); + + // Set the offset to ACTUAL_OFFSET + file.seek(actualOffset); + assertEquals(file.getFilePointer(), actualOffset); + + // Now decode. This should be successful and leave the offset + // unchanged. + Bitmap b = BitmapFactory.decodeFileDescriptor(fd); + assertNotNull(b); + assertEquals(file.getFilePointer(), actualOffset); + + // Now use the other offset. It should fail to decode, and + // the offset should remain unchanged. + long otherOffset = actualOffsets[(j + 1) % actualOffsets.length]; + assertFalse(otherOffset == actualOffset); + file.seek(otherOffset); + assertEquals(file.getFilePointer(), otherOffset); + + b = BitmapFactory.decodeFileDescriptor(fd); + assertNull(b); + assertEquals(file.getFilePointer(), otherOffset); + } + } + } + + @Test + public void testDecodeFileDescriptor_seekPositionUnchanged() throws IOException { + int numEmptyBytes = 25; + // Create a file that contains 25 empty bytes as well as the image contents of R.drawable.start + File imageFile = obtainFile(R.drawable.start, numEmptyBytes); + FileInputStream fis = new FileInputStream(imageFile.getAbsoluteFile()); + + // Set the seek position to the start of the image data + assertThat(fis.skip(numEmptyBytes)).isEqualTo(numEmptyBytes); + Rect r = new Rect(1, 1, 1, 1); + int bytesAvailable = fis.available(); + Bitmap b = BitmapFactory.decodeFileDescriptor(fis.getFD(), r, opt1); + assertThat(b).isNotNull(); + + // Check that the seek position hasn't changed + assertThat(fis.available()).isEqualTo(bytesAvailable); + + // Test the bitmap size + assertEquals(START_HEIGHT, b.getHeight()); + assertEquals(START_WIDTH, b.getWidth()); + fis.close(); + } + private byte[] obtainArray() { ByteArrayOutputStream stm = new ByteArrayOutputStream(); Options opt = new BitmapFactory.Options(); diff --git a/integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/ShadowNativeBitmapTest.java b/integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/ShadowNativeBitmapTest.java index 714e12da7..fbbff298c 100644 --- a/integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/ShadowNativeBitmapTest.java +++ b/integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/ShadowNativeBitmapTest.java @@ -60,6 +60,7 @@ import org.robolectric.RuntimeEnvironment; import org.robolectric.shadow.api.Shadow; import org.robolectric.shadows.ShadowBitmap; import org.robolectric.shadows.ShadowNativeBitmap; +import org.robolectric.versioning.AndroidVersions.U; @org.robolectric.annotation.Config(minSdk = O) @RunWith(RobolectricTestRunner.class) @@ -1580,6 +1581,7 @@ public class ShadowNativeBitmapTest { assertFalse(bitmap2.sameAs(bitmap1)); } + @org.robolectric.annotation.Config(maxSdk = U.SDK_INT) // TODO(hoisie): fix in V and above @Test public void testSameAs_hardware() { Bitmap bitmap1 = BitmapFactory.decodeResource(res, R.drawable.robot, HARDWARE_OPTIONS); @@ -1588,7 +1590,13 @@ public class ShadowNativeBitmapTest { Bitmap bitmap4 = BitmapFactory.decodeResource(res, R.drawable.start, HARDWARE_OPTIONS); assertTrue(bitmap1.sameAs(bitmap2)); assertTrue(bitmap2.sameAs(bitmap1)); - assertFalse(bitmap1.sameAs(bitmap3)); + + // Note: on an emulator or real device, the HARDWARE bitmap1 and the Software bitmap3 differ + // because the pixels cannot be read from the underlying hardware buffer. However, after the fix + // from r.android.com/2887086, Robolectric does actually properly fill the buffer content of a + // HARDWARE bitmap, which means it now is the same as its non-hardware counterpart. + assertTrue(bitmap1.sameAs(bitmap3)); + assertFalse(bitmap1.sameAs(bitmap4)); } @@ -1732,6 +1740,13 @@ public class ShadowNativeBitmapTest { assertThat(bitmap.getColorSpace()).isEqualTo(ColorSpace.get(ColorSpace.Named.ADOBE_RGB)); } + @org.robolectric.annotation.Config(minSdk = U.SDK_INT) + @Test + public void noGainmap_returnsNull() { + Bitmap bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888); + assertThat(bitmap.getGainmap()).isNull(); + } + @Test public void compress_thenDecodeStream_sameAs() { Bitmap bitmap = Bitmap.createBitmap(/* width= */ 10, /* height= */ 10, Bitmap.Config.ARGB_8888); diff --git a/integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/ShadowNativePaintTest.java b/integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/ShadowNativePaintTest.java index 924da1a2c..1705f2b3f 100644 --- a/integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/ShadowNativePaintTest.java +++ b/integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/ShadowNativePaintTest.java @@ -48,6 +48,7 @@ import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; import org.robolectric.util.reflector.ForType; +import org.robolectric.versioning.AndroidVersions.U; @RunWith(RobolectricTestRunner.class) @Config(minSdk = O) @@ -1989,6 +1990,7 @@ public class ShadowNativePaintTest { } } + @Config(maxSdk = U.SDK_INT) // TODO(hoisie): fix in V and above @Test public void testElegantText() { final Paint p = new Paint(); diff --git a/integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/ShadowNativeRuntimeShaderTest.java b/integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/ShadowNativeRuntimeShaderTest.java index 9f06cf59a..4d373f9ab 100644 --- a/integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/ShadowNativeRuntimeShaderTest.java +++ b/integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/ShadowNativeRuntimeShaderTest.java @@ -15,6 +15,7 @@ import org.junit.runner.RunWith; import org.robolectric.annotation.Config; import org.robolectric.util.ReflectionHelpers; import org.robolectric.util.ReflectionHelpers.ClassParameter; +import org.robolectric.versioning.AndroidVersions.U; @Config(minSdk = S) @RunWith(AndroidJUnit4.class) @@ -68,7 +69,8 @@ public class ShadowNativeRuntimeShaderTest { ClassParameter.from(boolean.class, false)); } - @Config(minSdk = TIRAMISU) + /** {@link #SKSL} does not compile on V and above. */ + @Config(minSdk = TIRAMISU, maxSdk = U.SDK_INT) @Test public void testConstructorT() { var unused = new RuntimeShader(SKSL); diff --git a/integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/ShadowNativeSurfaceTest.java b/integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/ShadowNativeSurfaceTest.java new file mode 100644 index 000000000..c3326ccc6 --- /dev/null +++ b/integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/ShadowNativeSurfaceTest.java @@ -0,0 +1,31 @@ +package org.robolectric.integrationtests.nativegraphics; + +import static android.os.Build.VERSION_CODES.O; + +import android.graphics.SurfaceTexture; +import android.view.Surface; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.annotation.Config; +import org.robolectric.shadow.api.Shadow; + +@RunWith(AndroidJUnit4.class) +@Config(minSdk = O) +public class ShadowNativeSurfaceTest { + @Test + public void surface_construction() { + // Invoke the public/hidden no-op constructor. Although it's public, it's not available in a + // Gradle environment, because integration_tests/nativegrapics is a com.android.library project, + // which uses the stubs jar, so only public signatures available during compile-time. + Surface s = Shadow.newInstanceOf(Surface.class); + s.release(); + } + + @Test + public void surface_construction_surfaceTexture() { + SurfaceTexture st = new SurfaceTexture(false); + Surface s = new Surface(st); + s.release(); + } +} diff --git a/integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/ShadowNativeVectorDrawableTest.java b/integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/ShadowNativeVectorDrawableTest.java index 7ffe54f13..9b4f6ee0a 100644 --- a/integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/ShadowNativeVectorDrawableTest.java +++ b/integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/ShadowNativeVectorDrawableTest.java @@ -50,6 +50,7 @@ import org.junit.runner.RunWith; import org.robolectric.annotation.Config; import org.robolectric.shadow.api.Shadow; import org.robolectric.shadows.ShadowDrawable; +import org.robolectric.versioning.AndroidVersions.U; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -217,26 +218,31 @@ public class ShadowNativeVectorDrawableTest { resources = context.getResources(); } + @Config(maxSdk = U.SDK_INT) // TODO(hoisie): update this test for V @Test public void testBasicVectorDrawables() throws XmlPullParserException, IOException { verifyVectorDrawables(BASIC_ICON_RES_IDS, BASIC_GOLDEN_IMAGES, null); } + @Config(maxSdk = U.SDK_INT) // TODO(hoisie): update this test for V @Test public void testLMVectorDrawables() throws XmlPullParserException, IOException { verifyVectorDrawables(L_M_ICON_RES_IDS, L_M_GOLDEN_IMAGES, null); } + @Config(maxSdk = U.SDK_INT) // TODO(hoisie): update this test for V @Test public void testNVectorDrawables() throws XmlPullParserException, IOException { verifyVectorDrawables(N_ICON_RES_IDS, N_GOLDEN_IMAGES, null); } + @Config(maxSdk = U.SDK_INT) // TODO(hoisie): update this test for V @Test public void testVectorDrawableGradient() throws XmlPullParserException, IOException { verifyVectorDrawables(GRADIENT_ICON_RES_IDS, GRADIENT_GOLDEN_IMAGES, null); } + @Config(maxSdk = U.SDK_INT) // TODO(hoisie): update this test for V @Test public void testColorStateList() throws XmlPullParserException, IOException { for (int i = 0; i < STATEFUL_STATE_SETS.length; i++) { diff --git a/integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/SystemFontsQTest.java b/integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/SystemFontsQTest.java new file mode 100644 index 000000000..5d0b745c0 --- /dev/null +++ b/integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/SystemFontsQTest.java @@ -0,0 +1,25 @@ +package org.robolectric.integrationtests.nativegraphics; + +import static android.os.Build.VERSION_CODES.Q; +import static android.os.Build.VERSION_CODES.R; + +import android.graphics.fonts.SystemFonts; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.annotation.Config; + +@RunWith(AndroidJUnit4.class) +public class SystemFontsQTest { + /** + * A test to ensure that {@link SystemFonts#getAvailableFonts} will trigger RNG to load if it is + * called early in a test run. + * + * <p>This should be the first test that is run in this test class. + */ + @Config(minSdk = Q, maxSdk = R) + @Test + public void getAvailableFonts() { + SystemFonts.getAvailableFonts(); + } +} diff --git a/integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/testing/util/DrawCountDown.java b/integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/testing/util/DrawCountDown.java deleted file mode 100644 index e1ad49593..000000000 --- a/integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/testing/util/DrawCountDown.java +++ /dev/null @@ -1,53 +0,0 @@ -package org.robolectric.integrationtests.nativegraphics.testing.util; - -import android.view.View; -import android.view.ViewTreeObserver.OnPreDrawListener; -import java.util.HashSet; -import java.util.Set; - -public class DrawCountDown implements OnPreDrawListener { - private static Set<DrawCountDown> pendingCallbacks = new HashSet<>(); - - private int drawCount; - private View targetView; - private Runnable runnable; - - private DrawCountDown(View targetView, int countFrames, Runnable countReachedListener) { - this.targetView = targetView; - drawCount = countFrames; - runnable = countReachedListener; - } - - @Override - public boolean onPreDraw() { - if (drawCount <= 0) { - synchronized (pendingCallbacks) { - pendingCallbacks.remove(this); - } - targetView.getViewTreeObserver().removeOnPreDrawListener(this); - runnable.run(); - } else { - drawCount--; - targetView.postInvalidate(); - } - return true; - } - - public static void countDownDraws( - View targetView, int countFrames, Runnable onDrawCountReachedListener) { - DrawCountDown counter = new DrawCountDown(targetView, countFrames, onDrawCountReachedListener); - synchronized (pendingCallbacks) { - pendingCallbacks.add(counter); - } - targetView.getViewTreeObserver().addOnPreDrawListener(counter); - } - - public static void cancelPending() { - synchronized (pendingCallbacks) { - for (DrawCountDown counter : pendingCallbacks) { - counter.targetView.getViewTreeObserver().removeOnPreDrawListener(counter); - } - pendingCallbacks.clear(); - } - } -} diff --git a/integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/testing/util/SneakyThrow.java b/integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/testing/util/SneakyThrow.java deleted file mode 100644 index 6b4367a88..000000000 --- a/integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/testing/util/SneakyThrow.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.robolectric.integrationtests.nativegraphics.testing.util; - -/** - * Provides a hacky method that always throws {@code t} even if {@code t} is a checked exception. - * and is not declared to be thrown. - * - * <p>See http://www.mail-archive.com/javaposse@googlegroups.com/msg05984.html - */ -public final class SneakyThrow { - /** - * A hacky method that always throws {@code t} even if {@code t} is a checked exception, and is - * not declared to be thrown. - */ - public static void sneakyThrow(Throwable t) { - SneakyThrow.<RuntimeException>sneakyThrowInternal(t); - } - - @SuppressWarnings({"unchecked"}) - private static <T extends Throwable> void sneakyThrowInternal(Throwable t) throws T { - throw (T) t; - } - - private SneakyThrow() {} -} diff --git a/integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/testing/util/WebViewReadyHelper.java b/integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/testing/util/WebViewReadyHelper.java deleted file mode 100644 index 80e96c982..000000000 --- a/integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/testing/util/WebViewReadyHelper.java +++ /dev/null @@ -1,61 +0,0 @@ -package org.robolectric.integrationtests.nativegraphics.testing.util; - -import android.view.ViewTreeObserver.OnDrawListener; -import android.webkit.WebView; -import android.webkit.WebView.VisualStateCallback; -import android.webkit.WebViewClient; -import java.util.concurrent.CountDownLatch; - -public final class WebViewReadyHelper { - // Hacky quick-fix similar to DrawActivity's DrawCounterListener - // TODO: De-dupe this against DrawCounterListener and fix this cruft - private static final int DEBUG_REQUIRE_EXTRA_FRAMES = 1; - private int drawCount = 0; - - private final CountDownLatch latch; - private final WebView webView; - - public WebViewReadyHelper(WebView webView, CountDownLatch latch) { - this.webView = webView; - this.latch = latch; - this.webView.setWebViewClient(client); - } - - public void loadData(String data) { - webView.loadData(data, null, null); - } - - private WebViewClient client = - new WebViewClient() { - @Override - public void onPageFinished(WebView view, String url) { - webView.postVisualStateCallback(0, visualStateCallback); - } - }; - - private VisualStateCallback visualStateCallback = - new VisualStateCallback() { - @Override - public void onComplete(long requestId) { - webView.getViewTreeObserver().addOnDrawListener(onDrawListener); - webView.invalidate(); - } - }; - - private OnDrawListener onDrawListener = - new OnDrawListener() { - @Override - public void onDraw() { - if (++drawCount <= DEBUG_REQUIRE_EXTRA_FRAMES) { - webView.postInvalidate(); - return; - } - - webView.post( - () -> { - webView.getViewTreeObserver().removeOnDrawListener(onDrawListener); - latch.countDown(); - }); - } - }; -} diff --git a/integration_tests/play_services/build.gradle b/integration_tests/play_services/build.gradle index 0e05dd6b3..c7e522020 100644 --- a/integration_tests/play_services/build.gradle +++ b/integration_tests/play_services/build.gradle @@ -11,5 +11,5 @@ dependencies { testRuntimeOnly AndroidSdk.MAX_SDK.coordinates testImplementation libs.junit4 testImplementation libs.truth - testImplementation "com.google.android.gms:play-services-basement:18.0.1" + testImplementation libs.play.services.basement } diff --git a/integration_tests/roborazzi/build.gradle b/integration_tests/roborazzi/build.gradle new file mode 100644 index 000000000..7c7000c5b --- /dev/null +++ b/integration_tests/roborazzi/build.gradle @@ -0,0 +1,65 @@ +import org.robolectric.gradle.AndroidProjectConfigPlugin + +apply plugin: 'com.android.library' +apply plugin: AndroidProjectConfigPlugin +apply plugin: 'kotlin-android' +apply plugin: "com.diffplug.spotless" +apply plugin: "io.github.takahirom.roborazzi" + +spotless { + kotlin { + target '**/*.kt' + ktfmt('0.42').googleStyle() + } +} + +android { + compileSdk 34 + namespace 'org.robolectric.integration.roborazzi' + + defaultConfig { + minSdk 21 + targetSdk 34 + } + + compileOptions { + sourceCompatibility = '1.8' + targetCompatibility = '1.8' + } + + kotlinOptions { + jvmTarget = '1.8' + } + + testOptions { + unitTests { + includeAndroidResources = true + all { + // For Roborazzi users, please use Roborazzi plugin and gradle.properties instead of this. + // https://takahirom.github.io/roborazzi/how-to-use.html#roborazzi-gradle-properties-options + + // Change naming strategy of screenshots. + // org.robolectric.....RoborazziCaptureTest.checkDialogRendering.png -> RoborazziCaptureTest.checkDialogRendering.png + systemProperty 'roborazzi.record.namingStrategy', 'testClassAndMethod' + + // Use RoborazziRule's base path when you use captureRoboImage(path). + systemProperty 'roborazzi.record.filePathStrategy', 'relativePathFromRoborazziContextOutputDirectory' + } + } + } + androidComponents { + beforeVariants(selector().all()) { variantBuilder -> + // Roborazzi does not support AndroidTest. + variantBuilder.enableAndroidTest = false + } + } +} + +dependencies { + api project(":robolectric") + testImplementation libs.androidx.test.core + testImplementation libs.junit4 + testImplementation libs.truth + testImplementation libs.roborazzi + testImplementation libs.roborazzi.rule +} diff --git a/integration_tests/roborazzi/src/screenshots/RoborazziCaptureTest.checkDialogRendering[31].png b/integration_tests/roborazzi/src/screenshots/RoborazziCaptureTest.checkDialogRendering[31].png Binary files differnew file mode 100644 index 000000000..19bd098c3 --- /dev/null +++ b/integration_tests/roborazzi/src/screenshots/RoborazziCaptureTest.checkDialogRendering[31].png diff --git a/integration_tests/roborazzi/src/screenshots/RoborazziCaptureTest.checkViewWithElevationRendering[31].png b/integration_tests/roborazzi/src/screenshots/RoborazziCaptureTest.checkViewWithElevationRendering[31].png Binary files differnew file mode 100644 index 000000000..5780ff86c --- /dev/null +++ b/integration_tests/roborazzi/src/screenshots/RoborazziCaptureTest.checkViewWithElevationRendering[31].png diff --git a/integration_tests/roborazzi/src/test/java/org/robolectric/integration/roborazzi/RoborazziCaptureTest.kt b/integration_tests/roborazzi/src/test/java/org/robolectric/integration/roborazzi/RoborazziCaptureTest.kt new file mode 100644 index 000000000..9adbe305e --- /dev/null +++ b/integration_tests/roborazzi/src/test/java/org/robolectric/integration/roborazzi/RoborazziCaptureTest.kt @@ -0,0 +1,196 @@ +package org.robolectric.integration.roborazzi + +import android.app.Activity +import android.app.AlertDialog +import android.app.Application +import android.content.ComponentName +import android.graphics.Color +import android.graphics.drawable.ColorDrawable +import android.os.Build.VERSION_CODES.S +import android.os.Bundle +import android.widget.FrameLayout +import android.widget.LinearLayout +import android.widget.TextView +import androidx.test.core.app.ActivityScenario +import androidx.test.platform.app.InstrumentationRegistry +import com.github.takahirom.roborazzi.ExperimentalRoborazziApi +import com.github.takahirom.roborazzi.RoborazziOptions +import com.github.takahirom.roborazzi.RoborazziRule +import com.github.takahirom.roborazzi.captureScreenRoboImage +import kotlin.reflect.KClass +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.Shadows +import org.robolectric.annotation.Config +import org.robolectric.annotation.GraphicsMode +import org.robolectric.integration.roborazzi.RoborazziDialogTestActivity.Companion.OUTPUT_DIRECTORY_PATH + +/** + * Integration Test for Roborazzi + * + * This test is not intended to obstruct the release of Robolectric. In the event that issues are + * detected which do not stem from Robolectric, the test can be temporarily disabled, and an issue + * can be reported on the Roborazzi repository. + * + * Run ./gradlew integration_tests:roborazzi:recordRoborazziDebug + * -Drobolectric.alwaysIncludeVariantMarkersInTestName=true to record the reference + * screenshots(golden images). Run ./gradlew integration_tests:roborazzi:verifyRoborazziDebug + * -Drobolectric.alwaysIncludeVariantMarkersInTestName=true to check the screenshots. + */ +@RunWith(RobolectricTestRunner::class) +@Config(sdk = [S]) +@GraphicsMode(GraphicsMode.Mode.NATIVE) +@OptIn(ExperimentalRoborazziApi::class) +class RoborazziCaptureTest { + @get:Rule + val roborazziRule = + RoborazziRule( + options = + RoborazziRule.Options( + outputDirectoryPath = OUTPUT_DIRECTORY_PATH, + roborazziOptions = + RoborazziOptions( + recordOptions = + RoborazziOptions.RecordOptions( + resizeScale = 0.5, + ) + ) + ) + ) + + @Test + // For reducing repository size, we use small size + @Config(qualifiers = "w50dp-h40dp") + fun checkViewWithElevationRendering() { + hardwareRendererEnvironment { + setupActivity(RoborazziViewWithElevationTestActivity::class) + + captureScreenWithRoborazzi() + } + } + + @Test + // For reducing repository size, we use small size + @Config(qualifiers = "w110dp-h120dp") + fun checkDialogRendering() { + hardwareRendererEnvironment { + setupActivity(RoborazziDialogTestActivity::class) + + captureScreenWithRoborazzi() + } + } + + private fun setupActivity(activityClass: KClass<out Activity>) { + registerActivityToPackageManager(checkNotNull(activityClass.java.canonicalName)) + ActivityScenario.launch(activityClass.java) + } + + private fun captureScreenWithRoborazzi() { + try { + captureScreenRoboImage() + } catch (e: AssertionError) { + throw AssertionError( + """ + |${e.message} + |Please check the screenshot in $OUTPUT_DIRECTORY_PATH + |If you want to update the screenshot, + |run `./gradlew integration_tests:roborazzi:recordRoborazziDebug -Drobolectric.alwaysIncludeVariantMarkersInTestName=true` and commit the changes. + |""" + .trimMargin(), + e + ) + } + } + + companion object { + const val USE_HARDWARE_RENDERER_NATIVE_ENV = "robolectric.screenshot.hwrdr.native" + } +} + +private fun registerActivityToPackageManager(activity: String) { + val appContext: Application = + InstrumentationRegistry.getInstrumentation().targetContext.applicationContext as Application + Shadows.shadowOf(appContext.packageManager) + .addActivityIfNotPresent( + ComponentName( + appContext.packageName, + activity, + ) + ) +} + +private fun hardwareRendererEnvironment(block: () -> Unit) { + val originalHwrdrOption = + System.getProperty(RoborazziCaptureTest.USE_HARDWARE_RENDERER_NATIVE_ENV, null) + // This cause ClassNotFoundException: java.nio.NioUtils + // TODO: Remove comment out after fix this issue + // https://github.com/robolectric/robolectric/issues/8081#issuecomment-1858726896 + // System.setProperty(USE_HARDWARE_RENDERER_NATIVE_ENV, "true") + try { + block() + } finally { + if (originalHwrdrOption == null) { + System.clearProperty(RoborazziCaptureTest.USE_HARDWARE_RENDERER_NATIVE_ENV) + } else { + System.setProperty(RoborazziCaptureTest.USE_HARDWARE_RENDERER_NATIVE_ENV, originalHwrdrOption) + } + } +} + +private class RoborazziViewWithElevationTestActivity : Activity() { + + override fun onCreate(savedInstanceState: Bundle?) { + setTheme(android.R.style.Theme_Light_NoTitleBar) + super.onCreate(savedInstanceState) + setContentView( + LinearLayout(this).apply { + orientation = LinearLayout.VERTICAL + fun Int.toDp(): Int = (this * resources.displayMetrics.density).toInt() + + // View with elevation + addView( + FrameLayout(this@RoborazziViewWithElevationTestActivity).apply { + background = ColorDrawable(Color.MAGENTA) + elevation = 10f + addView(TextView(this.context).apply { text = "Txt" }) + }, + LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.MATCH_PARENT + ) + .apply { setMargins(10.toDp(), 10.toDp(), 10.toDp(), 10.toDp()) } + ) + } + ) + } +} + +private class RoborazziDialogTestActivity : Activity() { + override fun onCreate(savedInstanceState: Bundle?) { + setTheme(android.R.style.Theme_DeviceDefault_Light_NoActionBar) + super.onCreate(savedInstanceState) + setContentView( + LinearLayout(this).apply { + orientation = LinearLayout.VERTICAL + fun Int.toDp(): Int = (this * resources.displayMetrics.density).toInt() + + // View with elevation + addView( + TextView(this.context).apply { text = "Under the dialog" }, + LinearLayout.LayoutParams( + LinearLayout.LayoutParams.WRAP_CONTENT, + LinearLayout.LayoutParams.WRAP_CONTENT + ) + .apply { setMargins(10.toDp(), 10.toDp(), 10.toDp(), 10.toDp()) } + ) + } + ) + AlertDialog.Builder(this).setTitle("Dlg").setPositiveButton("OK") { _, _ -> }.show() + } + + companion object { + const val OUTPUT_DIRECTORY_PATH = "src/screenshots" + } +} diff --git a/integration_tests/room/build.gradle b/integration_tests/room/build.gradle index 1798d40ba..4a12143be 100644 --- a/integration_tests/room/build.gradle +++ b/integration_tests/room/build.gradle @@ -22,17 +22,16 @@ android { includeAndroidResources = true } } - } dependencies { // Testing dependencies - testImplementation project(path: ':testapp') + testImplementation project(":testapp") testImplementation project(":robolectric") testImplementation libs.junit4 testImplementation libs.guava.testlib testImplementation libs.guava.testlib testImplementation libs.truth - implementation 'androidx.room:room-runtime:2.6.0' - annotationProcessor 'androidx.room:room-compiler:2.6.0' + implementation libs.androidx.room.runtime + annotationProcessor libs.androidx.room.compiler } diff --git a/nativeruntime/build.gradle b/nativeruntime/build.gradle index e784984ab..65f1e60a9 100644 --- a/nativeruntime/build.gradle +++ b/nativeruntime/build.gradle @@ -1,5 +1,3 @@ -import groovy.json.JsonSlurper -import java.nio.charset.StandardCharsets import org.robolectric.gradle.DeployedRoboJavaModulePlugin import org.robolectric.gradle.RoboJavaModulePlugin diff --git a/nativeruntime/src/main/java/org/robolectric/nativeruntime/BitmapNatives.java b/nativeruntime/src/main/java/org/robolectric/nativeruntime/BitmapNatives.java index b273771c4..8bc2e3656 100644 --- a/nativeruntime/src/main/java/org/robolectric/nativeruntime/BitmapNatives.java +++ b/nativeruntime/src/main/java/org/robolectric/nativeruntime/BitmapNatives.java @@ -130,5 +130,7 @@ public final class BitmapNatives { public static native boolean nativeIsBackedByAshmem(long nativePtr); + public static native void nativeCopyColorSpaceP(long srcBitmap, long dstBitmap); + private BitmapNatives() {} } diff --git a/nativeruntime/src/main/java/org/robolectric/nativeruntime/DefaultNativeRuntimeLoader.java b/nativeruntime/src/main/java/org/robolectric/nativeruntime/DefaultNativeRuntimeLoader.java index 3892453dd..78c9b20a8 100644 --- a/nativeruntime/src/main/java/org/robolectric/nativeruntime/DefaultNativeRuntimeLoader.java +++ b/nativeruntime/src/main/java/org/robolectric/nativeruntime/DefaultNativeRuntimeLoader.java @@ -75,8 +75,7 @@ public class DefaultNativeRuntimeLoader implements NativeRuntimeLoader { "loadNativeRuntime", () -> { extractDirectory = new TempDirectory("nativeruntime"); - System.setProperty( - "robolectric.nativeruntime.languageTag", Locale.getDefault().toLanguageTag()); + System.setProperty("icu.locale.default", Locale.getDefault().toLanguageTag()); if (Build.VERSION.SDK_INT >= O) { maybeCopyFonts(extractDirectory); } @@ -97,9 +96,9 @@ public class DefaultNativeRuntimeLoader implements NativeRuntimeLoader { return; } Path icuPath = tempDirectory.create("icu"); - Path icuDatPath = tempDirectory.getBasePath().resolve("icu/icudt68l.dat"); + Path icuDatPath = icuPath.resolve("icudt68l.dat"); Resources.asByteSource(icuDatUrl).copyTo(Files.asByteSink(icuDatPath.toFile())); - System.setProperty("icu.dir", icuPath.toAbsolutePath().toString()); + System.setProperty("icu.data.path", icuDatPath.toAbsolutePath().toString()); } /** @@ -148,8 +147,6 @@ public class DefaultNativeRuntimeLoader implements NativeRuntimeLoader { private void loadLibrary(TempDirectory tempDirectory) throws IOException { String libraryName = System.mapLibraryName("robolectric-nativeruntime"); - System.setProperty( - "robolectric.nativeruntime.languageTag", Locale.getDefault().toLanguageTag()); Path libraryPath = tempDirectory.getBasePath().resolve(libraryName); URL libraryResource = Resources.getResource(nativeLibraryPath()); Resources.asByteSource(libraryResource).copyTo(Files.asByteSink(libraryPath.toFile())); diff --git a/nativeruntime/src/main/java/org/robolectric/nativeruntime/ImageReaderNatives.java b/nativeruntime/src/main/java/org/robolectric/nativeruntime/ImageReaderNatives.java index 2b7035b86..5ea61ac9f 100644 --- a/nativeruntime/src/main/java/org/robolectric/nativeruntime/ImageReaderNatives.java +++ b/nativeruntime/src/main/java/org/robolectric/nativeruntime/ImageReaderNatives.java @@ -63,6 +63,8 @@ public final class ImageReaderNatives { public synchronized native void nativeDiscardFreeBuffers(); // Q+ /** + * Setup image in native level. + * * @return A return code {@code ACQUIRE_*} * @see #ACQUIRE_SUCCESS * @see #ACQUIRE_NO_BUFS diff --git a/nativeruntime/src/main/java/org/robolectric/nativeruntime/RenderNodeNatives.java b/nativeruntime/src/main/java/org/robolectric/nativeruntime/RenderNodeNatives.java index adda69e61..52c6b7203 100644 --- a/nativeruntime/src/main/java/org/robolectric/nativeruntime/RenderNodeNatives.java +++ b/nativeruntime/src/main/java/org/robolectric/nativeruntime/RenderNodeNatives.java @@ -43,6 +43,8 @@ public final class RenderNodeNatives { public static native void nEndAllAnimators(long renderNode); + public static native void nForceEndAnimators(long renderNode); + public static native void nDiscardDisplayList(long renderNode); public static native boolean nIsValid(long renderNode); diff --git a/nativeruntime/src/main/resources/icu/icudt.dat b/nativeruntime/src/main/resources/icu/icudt.dat Binary files differnew file mode 100644 index 000000000..e25a15ab9 --- /dev/null +++ b/nativeruntime/src/main/resources/icu/icudt.dat diff --git a/nativeruntime/src/test/java/org/robolectric/nativeruntime/DefaultNativeRuntimeLazyLoadTest.java b/nativeruntime/src/test/java/org/robolectric/nativeruntime/DefaultNativeRuntimeLazyLoadTest.java index 5fa5ad364..d432b9497 100644 --- a/nativeruntime/src/test/java/org/robolectric/nativeruntime/DefaultNativeRuntimeLazyLoadTest.java +++ b/nativeruntime/src/test/java/org/robolectric/nativeruntime/DefaultNativeRuntimeLazyLoadTest.java @@ -1,7 +1,7 @@ package org.robolectric.nativeruntime; +import static android.os.Build.VERSION_CODES.KITKAT; import static com.google.common.truth.Truth.assertThat; -import static org.robolectric.annotation.Config.ALL_SDKS; import android.app.Application; import android.database.CursorWindow; @@ -10,15 +10,18 @@ import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; +import org.robolectric.versioning.AndroidVersions.U; @RunWith(RobolectricTestRunner.class) -@Config(sdk = ALL_SDKS) +@Config(minSdk = KITKAT, maxSdk = U.SDK_INT) public final class DefaultNativeRuntimeLazyLoadTest { /** * Checks to see that RNR is not loaded by default when an empty application is created. RNR load * times are typically 0.5-1s, so it is desirable to have it lazy loaded when native code is * called. + * + * <p>Note that lazy loading is disabled for V and above. */ @SuppressWarnings("UnusedVariable") @Test diff --git a/preinstrumented/build.gradle b/preinstrumented/build.gradle index dc1e5d0d5..0de49e0bf 100644 --- a/preinstrumented/build.gradle +++ b/preinstrumented/build.gradle @@ -46,11 +46,11 @@ tasks.register('instrumentAll') { } } -tasks.register('sourcesJar', Jar) { +tasks.register('emptySourcesJar', Jar) { archiveClassifier = "sources" } -tasks.register('javadocJar', Jar) { +tasks.register('emptyJavadocJar', Jar) { archiveClassifier = "javadoc" } @@ -68,8 +68,8 @@ if (System.getenv('PUBLISH_PREINSTRUMENTED_JARS') == "true") { "sdk${androidSdk.apiLevel}"(MavenPublication) { artifact "${buildDir}/${androidSdk.preinstrumentedJarFileName}" artifactId 'android-all-instrumented' - artifact sourcesJar - artifact javadocJar + artifact emptySourcesJar + artifact emptyJavadocJar version androidSdk.preinstrumentedVersion pom { @@ -118,6 +118,20 @@ if (System.getenv('PUBLISH_PREINSTRUMENTED_JARS') == "true") { sign publishing.publications."sdk${androidSdk.apiLevel}" } } + + + // Workaround for https://github.com/gradle/gradle/issues/26132 + // For some reason, Gradle has inferred that all publishing tasks depend on all signing tasks, + // so we must explicitly declare this here. + afterEvaluate { + tasks.all { + if (name.startsWith("publishSdk")) { + sdksToInstrument().each { androidSdk -> + dependsOn(tasks.named("signSdk${androidSdk.apiLevel}Publication")) + } + } + } + } } static def sdksToInstrument() { diff --git a/preinstrumented/src/main/java/org/robolectric/preinstrumented/JarInstrumentor.java b/preinstrumented/src/main/java/org/robolectric/preinstrumented/JarInstrumentor.java index 1410c33d5..d0665562b 100644 --- a/preinstrumented/src/main/java/org/robolectric/preinstrumented/JarInstrumentor.java +++ b/preinstrumented/src/main/java/org/robolectric/preinstrumented/JarInstrumentor.java @@ -20,7 +20,6 @@ import org.robolectric.internal.bytecode.ClassInstrumentor; import org.robolectric.internal.bytecode.ClassNodeProvider; import org.robolectric.internal.bytecode.InstrumentationConfiguration; import org.robolectric.internal.bytecode.Interceptors; -import org.robolectric.internal.bytecode.NativeCallHandler; import org.robolectric.util.inject.Injector; import org.robolectric.versioning.AndroidVersionInitTools; import org.robolectric.versioning.AndroidVersions.AndroidRelease; @@ -51,35 +50,15 @@ public class JarInstrumentor { @VisibleForTesting void processCommandLine(String[] args) throws IOException, ClassNotFoundException { - if (args.length >= 2) { + if (args.length == 2) { File sourceFile = new File(args[0]); File destJarFile = new File(args[1]); - File destNativesFile = null; - boolean throwOnNatives = false; - boolean parseError = false; - for (int i = 2; i < args.length; i++) { - if (args[i].startsWith("--write-natives=")) { - destNativesFile = new File(args[i].substring("--write-natives=".length())); - } else if (args[i].equals("--throw-on-natives")) { - throwOnNatives = true; - } else { - System.err.println("Unknown argument: " + args[i]); - parseError = true; - break; - } - } - - if (!parseError) { - instrumentJar(sourceFile, destJarFile, destNativesFile, throwOnNatives); - return; - } + instrumentJar(sourceFile, destJarFile); + return; } - System.err.println( - "Usage: JarInstrumentor <source jar> <dest jar> " - + "[--write-natives=<file>] " - + "[--throw-on-natives]"); + System.err.println("Usage: JarInstrumentor <source jar> <dest jar> "); exit(1); } @@ -94,13 +73,9 @@ public class JarInstrumentor { * * @param sourceJarFile The source JAR to process. * @param destJarFile The destination JAR with the instrumented method calls. - * @param destNativesFile Optional file to write native calls signature. Null to disable. - * @param throwOnNatives Whether native calls should be instrumented as throwing a dedicated - * exception (true) or no-op (false). */ @VisibleForTesting - protected void instrumentJar( - File sourceJarFile, File destJarFile, File destNativesFile, boolean throwOnNatives) + protected void instrumentJar(File sourceJarFile, File destJarFile) throws IOException, ClassNotFoundException { long startNs = System.nanoTime(); JarFile jarFile = new JarFile(sourceJarFile); @@ -112,23 +87,6 @@ public class JarInstrumentor { } }; - NativeCallHandler nativeCallHandler; - final boolean writeNativesFile = destNativesFile != null; - - if (destNativesFile == null) { - destNativesFile = - new File( - sourceJarFile.getParentFile(), - sourceJarFile.getName().replace(".jar", "-natives.txt")); - } - - try { - nativeCallHandler = new NativeCallHandler(destNativesFile, writeNativesFile, throwOnNatives); - classInstrumentor.setNativeCallHandler(nativeCallHandler); - } catch (IOException e) { - throw new AssertionError("Unable to load native exemptions file", e); - } - int nonClassCount = 0; int classCount = 0; @@ -176,10 +134,6 @@ public class JarInstrumentor { } } - if (writeNativesFile) { - nativeCallHandler.writeExemptionsList(); - } - long elapsedNs = System.nanoTime() - startNs; System.out.println( String.format( diff --git a/preinstrumented/src/test/java/org/robolectric/preinstrumented/JarInstrumentorTest.java b/preinstrumented/src/test/java/org/robolectric/preinstrumented/JarInstrumentorTest.java index 6b6d169e1..3e401f37d 100644 --- a/preinstrumented/src/test/java/org/robolectric/preinstrumented/JarInstrumentorTest.java +++ b/preinstrumented/src/test/java/org/robolectric/preinstrumented/JarInstrumentorTest.java @@ -1,7 +1,6 @@ package org.robolectric.preinstrumented; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; @@ -23,8 +22,7 @@ public class JarInstrumentorTest { JarInstrumentor dummyInstrumentor = new JarInstrumentor() { @Override - protected void instrumentJar( - File sourceJarFile, File destJarFile, File destNativesFile, boolean throwOnNatives) { + protected void instrumentJar(File sourceJarFile, File destJarFile) { // No-op. We only want to test the command line processing. Stub the actual // instrumention. } @@ -40,32 +38,13 @@ public class JarInstrumentorTest { @Test public void processCommandLine_legacyUsage() throws Exception { spyDummyInstrumentor.processCommandLine(new String[] {"source.jar", "dest.jar"}); - verify(spyDummyInstrumentor) - .instrumentJar(new File("source.jar"), new File("dest.jar"), null, false); - } - - @Test - public void processCommandLine_throwOnNatives() throws Exception { - spyDummyInstrumentor.processCommandLine( - new String[] {"source.jar", "dest.jar", "--throw-on-natives"}); - verify(spyDummyInstrumentor) - .instrumentJar(new File("source.jar"), new File("dest.jar"), null, true); - } - - @Test - public void processCommandLine_writeNativesExemptionFile() throws Exception { - spyDummyInstrumentor.processCommandLine( - new String[] {"source.jar", "dest.jar", "--write-natives=natives.txt"}); - verify(spyDummyInstrumentor) - .instrumentJar( - new File("source.jar"), new File("dest.jar"), new File("natives.txt"), false); + verify(spyDummyInstrumentor).instrumentJar(new File("source.jar"), new File("dest.jar")); } @Test public void processCommandLine_unknownArguments() throws Exception { spyDummyInstrumentor.processCommandLine(new String[] {"source.jar", "dest.jar", "--some-flag"}); - verify(spyDummyInstrumentor, never()) - .instrumentJar(any(File.class), any(File.class), any(File.class), anyBoolean()); + verify(spyDummyInstrumentor, never()).instrumentJar(any(File.class), any(File.class)); verify(spyDummyInstrumentor).exit(1); } } diff --git a/processor/src/main/java/org/robolectric/annotation/processing/validator/ImplementsValidator.java b/processor/src/main/java/org/robolectric/annotation/processing/validator/ImplementsValidator.java index e1186b9c4..ee4de7ae5 100644 --- a/processor/src/main/java/org/robolectric/annotation/processing/validator/ImplementsValidator.java +++ b/processor/src/main/java/org/robolectric/annotation/processing/validator/ImplementsValidator.java @@ -173,19 +173,23 @@ public class ImplementsValidator extends Validator { AnnotationValue classNameAttr, TypeElement shadowPickerTypeElement) { - String sdkClassName; + String sdkClassNameFq; if (valueAttr == null) { - sdkClassName = Helpers.getAnnotationStringValue(classNameAttr).replace('$', '.'); + sdkClassNameFq = Helpers.getAnnotationStringValue(classNameAttr); } else { - sdkClassName = Helpers.getAnnotationTypeMirrorValue(valueAttr).toString(); + TypeMirror typeMirror = Helpers.getAnnotationTypeMirrorValue(valueAttr); + TypeElement typeElement = MoreElements.asType(types.asElement(typeMirror)); + sdkClassNameFq = elements.getBinaryName(typeElement).toString(); } // there's no such type at the current SDK level, so just use strings... // getQualifiedName() uses Outer.Inner and we want Outer$Inner, so: String name = getClassFQName(shadowType); - modelBuilder.addExtraShadow(sdkClassName, name); + // SHADOW_MAP currently uses class dot syntax for keys, but SHADOW_PICKER_MAP uses + // FQ syntax for keys. + modelBuilder.addExtraShadow(sdkClassNameFq.replace('$', '.'), name); if (shadowPickerTypeElement != null) { - modelBuilder.addExtraShadowPicker(sdkClassName, shadowPickerTypeElement); + modelBuilder.addExtraShadowPicker(sdkClassNameFq, shadowPickerTypeElement); } } diff --git a/processor/src/test/java/org/robolectric/annotation/processing/RobolectricProcessorTest.java b/processor/src/test/java/org/robolectric/annotation/processing/RobolectricProcessorTest.java index 46f5bc4ea..4f5a57dc9 100644 --- a/processor/src/test/java/org/robolectric/annotation/processing/RobolectricProcessorTest.java +++ b/processor/src/test/java/org/robolectric/annotation/processing/RobolectricProcessorTest.java @@ -235,4 +235,22 @@ public class RobolectricProcessorTest { .and() .generatesSources(forResource("org/robolectric/Robolectric_MinimalPackages.java")); } + + @Test + public void shouldEmitShadowPickerMapForShadowedInnerClasses() { + Map<String, String> options = new HashMap<>(DEFAULT_OPTS); + options.put(SHOULD_INSTRUMENT_PKG_OPT, "true"); + + assertAbout(javaSources()) + .that( + ImmutableList.of( + SHADOW_PROVIDER_SOURCE, + SHADOW_EXTRACTOR_SOURCE, + forResource( + "org/robolectric/annotation/processing/shadows/ShadowInnerDummyWithPicker.java"))) + .processedWith(new RobolectricProcessor(options)) + .compilesWithoutError() + .and() + .generatesSources(forResource("org/robolectric/Robolectric_ShadowPickers.java")); + } } diff --git a/processor/src/test/resources/mock-source/org/robolectric/internal/ShadowProvider.java b/processor/src/test/resources/mock-source/org/robolectric/internal/ShadowProvider.java index 22b858a46..1a4e0a39b 100644 --- a/processor/src/test/resources/mock-source/org/robolectric/internal/ShadowProvider.java +++ b/processor/src/test/resources/mock-source/org/robolectric/internal/ShadowProvider.java @@ -1,6 +1,7 @@ package org.robolectric.internal; import java.util.Collection; +import java.util.Collections; import java.util.Map; public interface ShadowProvider { @@ -10,4 +11,8 @@ public interface ShadowProvider { String[] getProvidedPackageNames(); Collection<Map.Entry<String, String>> getShadows(); + + default Map<String, String> getShadowPickerMap() { + return Collections.emptyMap(); + } } diff --git a/processor/src/test/resources/org/robolectric/Robolectric_ShadowPickers.java b/processor/src/test/resources/org/robolectric/Robolectric_ShadowPickers.java new file mode 100644 index 000000000..e08db1f34 --- /dev/null +++ b/processor/src/test/resources/org/robolectric/Robolectric_ShadowPickers.java @@ -0,0 +1,68 @@ +package org.robolectric; +import com.example.objects.OuterDummy.InnerDummy; +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import javax.annotation.Generated; +import org.robolectric.annotation.processing.shadows.ShadowInnerDummyWithPicker; +import org.robolectric.internal.ShadowProvider; +import org.robolectric.shadow.api.Shadow; + +/** Shadow mapper. Automatically generated by the Robolectric Annotation Processor. */ +@Generated("org.robolectric.annotation.processing.RobolectricProcessor") +@SuppressWarnings({"unchecked", "deprecation"}) +public class Shadows implements ShadowProvider { + private static final List<Map.Entry<String, String>> SHADOWS = new ArrayList<>(3); + + static { + SHADOWS.add( + new AbstractMap.SimpleImmutableEntry<>( + "com.example.objects.OuterDummy.InnerDummy", + "org.robolectric.annotation.processing.shadows.ShadowInnerDummyWithPicker$ShadowInnerDummyWithPicker2")); + SHADOWS.add( + new AbstractMap.SimpleImmutableEntry<>( + "com.example.objects.OuterDummy.InnerDummy2", + "org.robolectric.annotation.processing.shadows.ShadowInnerDummyWithPicker$ShadowInnerDummyWithPicker3")); + } + + public static org.robolectric.annotation.processing.shadows.ShadowInnerDummyWithPicker shadowOf( + InnerDummy actual) { + return (org.robolectric.annotation.processing.shadows.ShadowInnerDummyWithPicker) + Shadow.extract(actual); + } + + @Override + public void reset() {} + + @Override + public Collection<Map.Entry<String, String>> getShadows() { + return SHADOWS; + } + + @Override + public String[] getProvidedPackageNames() { + return new String[] {"com.example.objects"}; + } + + private static final Map<String, String> SHADOW_PICKER_MAP = new HashMap<>(12); + + static { + SHADOW_PICKER_MAP.put( + "com.example.objects.OuterDummy$InnerDummy", + "org.robolectric.annotation.processing.shadows.ShadowInnerDummyWithPicker$Picker"); + SHADOW_PICKER_MAP.put( + "com.example.objects.OuterDummy$InnerDummy", + "org.robolectric.annotation.processing.shadows.ShadowInnerDummyWithPicker$Picker"); + SHADOW_PICKER_MAP.put( + "com.example.objects.OuterDummy$InnerDummy2", + "org.robolectric.annotation.processing.shadows.ShadowInnerDummyWithPicker$Picker"); + } + + @Override + public Map<String, String> getShadowPickerMap() { + return SHADOW_PICKER_MAP; + } +}
\ No newline at end of file diff --git a/processor/src/test/resources/org/robolectric/annotation/processing/shadows/ShadowInnerDummyWithPicker.java b/processor/src/test/resources/org/robolectric/annotation/processing/shadows/ShadowInnerDummyWithPicker.java new file mode 100644 index 000000000..e9d3a53b6 --- /dev/null +++ b/processor/src/test/resources/org/robolectric/annotation/processing/shadows/ShadowInnerDummyWithPicker.java @@ -0,0 +1,26 @@ +package org.robolectric.annotation.processing.shadows; + +import com.example.objects.OuterDummy; +import org.robolectric.annotation.Implements; +import org.robolectric.annotation.processing.shadows.ShadowInnerDummyWithPicker.Picker; +import org.robolectric.shadow.api.ShadowPicker; + +@Implements(value = OuterDummy.InnerDummy.class, shadowPicker = Picker.class) +public class ShadowInnerDummyWithPicker { + + @Implements(value = OuterDummy.InnerDummy.class, maxSdk = 21, shadowPicker = Picker.class) + public static class ShadowInnerDummyWithPicker2 extends ShadowInnerDummyWithPicker {} + + @Implements( + className = "com.example.objects.OuterDummy$InnerDummy2", + maxSdk = 21, + shadowPicker = Picker.class) + public static class ShadowInnerDummyWithPicker3 extends ShadowInnerDummyWithPicker {} + + public static class Picker implements ShadowPicker<ShadowInnerDummyWithPicker> { + @Override + public Class<? extends ShadowInnerDummyWithPicker> pickShadowClass() { + return ShadowInnerDummyWithPicker.class; + } + } +} diff --git a/resources/src/main/java/org/robolectric/RoboSettings.java b/resources/src/main/java/org/robolectric/RoboSettings.java index a76d90d2c..80dbe21bb 100644 --- a/resources/src/main/java/org/robolectric/RoboSettings.java +++ b/resources/src/main/java/org/robolectric/RoboSettings.java @@ -14,10 +14,18 @@ public class RoboSettings { useGlobalScheduler = Boolean.getBoolean("robolectric.scheduling.global"); } + /** + * @deprecated Use PAUSED looper mode. + */ + @Deprecated public static boolean isUseGlobalScheduler() { return useGlobalScheduler; } + /** + * @deprecated Use PAUSED looper mode. + */ + @Deprecated public static void setUseGlobalScheduler(boolean useGlobalScheduler) { RoboSettings.useGlobalScheduler = useGlobalScheduler; } diff --git a/resources/src/main/java/org/robolectric/manifest/AndroidManifest.java b/resources/src/main/java/org/robolectric/manifest/AndroidManifest.java index 00a11f5ff..d5c0850a7 100644 --- a/resources/src/main/java/org/robolectric/manifest/AndroidManifest.java +++ b/resources/src/main/java/org/robolectric/manifest/AndroidManifest.java @@ -620,7 +620,7 @@ public class AndroidManifest implements UsesSdk { @Override public int getMinSdkVersion() { parseAndroidManifest(); - return minSdkVersion == null ? 16 : minSdkVersion; + return minSdkVersion == null ? 19 : minSdkVersion; } /** diff --git a/resources/src/main/java/org/robolectric/res/android/Asset.java b/resources/src/main/java/org/robolectric/res/android/Asset.java index a659a6cd9..85e3629a0 100644 --- a/resources/src/main/java/org/robolectric/res/android/Asset.java +++ b/resources/src/main/java/org/robolectric/res/android/Asset.java @@ -555,14 +555,13 @@ static Asset createFromCompressedMap(FileMap dataMap, default: ALOGW("unexpected whence %d\n", whence); // this was happening due to an long size mismatch - assert(false); - return (long) -1; + assert false; + return -1; } if (newOffset < 0 || newOffset > maxPosn) { - ALOGW("seek out of range: want %d, end=%d\n", - (long) newOffset, (long) maxPosn); - return (long) -1; + ALOGW("seek out of range: want %d, end=%d\n", newOffset, maxPosn); + return -1; } return newOffset; @@ -786,7 +785,7 @@ static Asset createFromCompressedMap(FileMap dataMap, if (mFp.getFilePointer() != mStart + mOffset) { ALOGE("Hosed: %d != %d+%d\n", mFp.getFilePointer(), (long) mStart, (long) mOffset); - assert(false); + assert false; } /* diff --git a/resources/src/main/java/org/robolectric/res/android/CppApkAssets.java b/resources/src/main/java/org/robolectric/res/android/CppApkAssets.java index 656661d03..58d436b69 100644 --- a/resources/src/main/java/org/robolectric/res/android/CppApkAssets.java +++ b/resources/src/main/java/org/robolectric/res/android/CppApkAssets.java @@ -9,6 +9,10 @@ import static org.robolectric.res.android.Util.CHECK; import static org.robolectric.res.android.ZipFileRO.OpenArchive; import static org.robolectric.res.android.ZipFileRO.kCompressDeflated; +import com.google.common.io.ByteStreams; +import java.io.FileDescriptor; +import java.io.FileInputStream; +import java.io.IOException; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.Enumeration; @@ -41,17 +45,21 @@ import org.robolectric.util.PerfStatsCollector; @SuppressWarnings("NewApi") public class CppApkAssets { private static final String kResourcesArsc = "resources.arsc"; -// public: -// static std::unique_ptr<const ApkAssets> Load(const String& path, bool system = false); -// static std::unique_ptr<const ApkAssets> LoadAsSharedLibrary(const String& path, -// bool system = false); -// -// std::unique_ptr<Asset> Open(const String& path, -// Asset::AccessMode mode = Asset::AccessMode::ACCESS_RANDOM) const; -// -// bool ForEachFile(const String& path, -// const std::function<void(const StringPiece&, FileType)>& f) const; + // public: + // static std::unique_ptr<const ApkAssets> Load(const String& path, bool system = false); + // static std::unique_ptr<const ApkAssets> LoadAsSharedLibrary(const String& path, + // bool system = false); + // + // std::unique_ptr<Asset> Open(const String& path, + // Asset::AccessMode mode = Asset::AccessMode::ACCESS_RANDOM) const; + // + // bool ForEachFile(const String& path, + // const std::function<void(const StringPiece&, FileType)>& f) const; + + private CppApkAssets() { + this.zipFileRO = null; + } public CppApkAssets(ZipArchiveHandle zip_handle_, String path_) { this.zip_handle_ = zip_handle_; @@ -169,6 +177,23 @@ public class CppApkAssets { // system, force_shared_lib); // } + // Creates an ApkAssets of the format ARSC from the given file descriptor, and takes ownership of + // the file descriptor. + public static CppApkAssets loadArscFromFd(FileDescriptor fd) { + CppApkAssets loadedApk = new CppApkAssets(); + try { + byte[] bytes = ByteStreams.toByteArray(new FileInputStream(fd)); + + StringPiece data = new StringPiece(ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN), 0); + loadedApk.loaded_arsc_ = LoadedArsc.Load(data, null, false, false); + + } catch (IOException e) { + // logError("Error loading assets from fd: " + e.getLocalizedMessage()); + return null; + } + return loadedApk; + } + // std::unique_ptr<Asset> ApkAssets::CreateAssetFromFile(const std::string& path) { @SuppressWarnings("DoNotCallSuggester") static Asset CreateAssetFromFile(String path) { @@ -275,7 +300,10 @@ public class CppApkAssets { } public Asset Open(String path, AccessMode mode) { - CHECK(zip_handle_ != null); + if (zip_handle_ == null || zipFileRO == null) { + // In this case, the ApkAssets was loaded from a pure ARSC, and does not have assets. + return null; + } String name = path; ZipEntryRO entry; diff --git a/resources/src/main/java/org/robolectric/res/android/CppAssetManager.java b/resources/src/main/java/org/robolectric/res/android/CppAssetManager.java index b796f230d..4f95df813 100644 --- a/resources/src/main/java/org/robolectric/res/android/CppAssetManager.java +++ b/resources/src/main/java/org/robolectric/res/android/CppAssetManager.java @@ -1431,8 +1431,8 @@ public class CppAssetManager { pNewSorted.add(pMergedInfo.itemAt(mergeIdx)); mergeIdx++; } else { - /* "cont" is lower, add that one */ - assert (pContents.itemAt(contIdx).isLessThan(pMergedInfo.itemAt(mergeIdx))); + /* "cont" is lower, add that one */ + assert pContents.itemAt(contIdx).isLessThan(pMergedInfo.itemAt(mergeIdx)); pNewSorted.add(pContents.itemAt(contIdx)); contIdx++; } diff --git a/resources/src/main/java/org/robolectric/res/android/CppAssetManager2.java b/resources/src/main/java/org/robolectric/res/android/CppAssetManager2.java index 4bb34f41b..803f82363 100644 --- a/resources/src/main/java/org/robolectric/res/android/CppAssetManager2.java +++ b/resources/src/main/java/org/robolectric/res/android/CppAssetManager2.java @@ -39,6 +39,7 @@ import org.robolectric.res.android.ResourceTypes.ResTable_map; import org.robolectric.res.android.ResourceTypes.ResTable_map_entry; import org.robolectric.res.android.ResourceTypes.ResTable_type; import org.robolectric.res.android.ResourceTypes.Res_value; +import org.robolectric.util.PerfStatsCollector; // transliterated from https://android.googlesource.com/platform/frameworks/base/+/android-9.0.0_r12/libs/androidfw/include/androidfw/AssetManager2.h // and https://android.googlesource.com/platform/frameworks/base/+/android-9.0.0_r12/libs/androidfw/AssetManager2.cpp @@ -667,7 +668,9 @@ public class CppAssetManager2 { // and we don't need to match the configurations, since they already matched. boolean use_fast_path = desired_config == configuration_; - for (int pi = 0; pi < package_count; pi++) { + // Search the entry in reverse order. This favors the newly added package in case neither + // configuration is considered "better than" the other. + for (int pi = package_count - 1; pi >= 0; pi--) { ConfiguredPackage loaded_package_impl = package_group.packages_.get(pi); LoadedPackage loaded_package = loaded_package_impl.loaded_package_; ApkAssetsCookie cookie = package_group.cookies_.get(pi); @@ -1293,36 +1296,43 @@ public class CppAssetManager2 { // Triggers the re-construction of lists of types that match the set configuration. // This should always be called when mutating the AssetManager's configuration or ApkAssets set. void RebuildFilterList() { - for (PackageGroup group : package_groups_) { - for (ConfiguredPackage impl : group.packages_) { - // // Destroy it. - // impl.filtered_configs_.~ByteBucketArray(); - // - // // Re-create it. - // new (impl.filtered_configs_) ByteBucketArray<FilteredConfigGroup>(); - impl.filtered_configs_ = - new ByteBucketArray<FilteredConfigGroup>(new FilteredConfigGroup()) { - @Override - FilteredConfigGroup newInstance() { - return new FilteredConfigGroup(); + PerfStatsCollector.getInstance() + .measure( + "RebuildFilterList", + () -> { + for (PackageGroup group : package_groups_) { + for (ConfiguredPackage impl : group.packages_) { + // // Destroy it. + // impl.filtered_configs_.~ByteBucketArray(); + // + // // Re-create it. + // new (impl.filtered_configs_) ByteBucketArray<FilteredConfigGroup>(); + impl.filtered_configs_ = + new ByteBucketArray<FilteredConfigGroup>(new FilteredConfigGroup()) { + @Override + FilteredConfigGroup newInstance() { + return new FilteredConfigGroup(); + } + }; + + // Create the filters here. + impl.loaded_package_.ForEachTypeSpec( + (TypeSpec spec, byte type_index) -> { + FilteredConfigGroup configGroup = + impl.filtered_configs_.editItemAt(type_index); + // const auto iter_end = spec->types + spec->type_count; + // for (auto iter = spec->types; iter != iter_end; ++iter) { + for (ResTable_type iter : spec.types) { + if (iter.config.match(configuration_)) { + ResTable_config this_config = ResTable_config.fromDtoH(iter.config); + configGroup.configurations.add(this_config); + configGroup.types.add(iter); + } + } + }); + } } - }; - - // Create the filters here. - impl.loaded_package_.ForEachTypeSpec((TypeSpec spec, byte type_index) -> { - FilteredConfigGroup configGroup = impl.filtered_configs_.editItemAt(type_index); - // const auto iter_end = spec->types + spec->type_count; - // for (auto iter = spec->types; iter != iter_end; ++iter) { - for (ResTable_type iter : spec.types) { - ResTable_config this_config = ResTable_config.fromDtoH(iter.config); - if (this_config.match(configuration_)) { - configGroup.configurations.add(this_config); - configGroup.types.add(iter); - } - } - }); - } - } + }); } // Purge all resources that are cached and vary by the configuration axis denoted by the diff --git a/resources/src/main/java/org/robolectric/res/android/LocaleData.java b/resources/src/main/java/org/robolectric/res/android/LocaleData.java index 0fe00f5da..359b6434a 100644 --- a/resources/src/main/java/org/robolectric/res/android/LocaleData.java +++ b/resources/src/main/java/org/robolectric/res/android/LocaleData.java @@ -103,7 +103,7 @@ public class LocaleData { (((long) script.charAt(1) & 0xff) << 16) | (((long) script.charAt(2) & 0xff) << 8) | ((long) script.charAt(3) & 0xff)); - return (REPRESENTATIVE_LOCALES.contains(packed_locale)); + return REPRESENTATIVE_LOCALES.contains(packed_locale); } private static final int US_SPANISH = 0x65735553; // es-US diff --git a/resources/src/main/java/org/robolectric/res/android/ResTable_config.java b/resources/src/main/java/org/robolectric/res/android/ResTable_config.java index ba164bd1e..3c4a8402d 100644 --- a/resources/src/main/java/org/robolectric/res/android/ResTable_config.java +++ b/resources/src/main/java/org/robolectric/res/android/ResTable_config.java @@ -1210,11 +1210,11 @@ public class ResTable_config { if (isTruthy(requested)) { if (isTruthy(imsi()) || isTruthy(o.imsi())) { if ((mcc != o.mcc) && isTruthy(requested.mcc)) { - return (isTruthy(mcc)); + return isTruthy(mcc); } if ((mnc != o.mnc) && isTruthy(requested.mnc)) { - return (isTruthy(mnc)); + return isTruthy(mnc); } } diff --git a/resources/src/main/java/org/robolectric/res/android/ZipFileRO.java b/resources/src/main/java/org/robolectric/res/android/ZipFileRO.java index c1d21e070..8f884e5fd 100644 --- a/resources/src/main/java/org/robolectric/res/android/ZipFileRO.java +++ b/resources/src/main/java/org/robolectric/res/android/ZipFileRO.java @@ -145,7 +145,7 @@ public class ZipFileRO { final Ref<Long> pUncompLen, Ref<Long> pCompLen, Ref<Long> pOffset, final Ref<Long> pModWhen, Ref<Long> pCrc32) { - final ZipEntryRO zipEntry = /*reinterpret_cast<ZipEntryRO*>*/(entry); + final ZipEntryRO zipEntry = /*reinterpret_cast<ZipEntryRO*>*/ entry; final ZipEntry ze = zipEntry.entry; if (pMethod != null) { diff --git a/robolectric/Android.bp b/robolectric/Android.bp index b6720e96f..954a9e99f 100644 --- a/robolectric/Android.bp +++ b/robolectric/Android.bp @@ -17,6 +17,7 @@ java_library_host { "Robolectric_shadows_framework_upstream", "Robolectric_shadows_versioning_upstream", "Robolectric_annotations_upstream", + "Robolectric_nativeruntime_upstream", "Robolectric_shadowapi_upstream", "Robolectric_resources_upstream", "Robolectric_sandbox_upstream", @@ -86,8 +87,7 @@ java_test_host { "asm-tree-9.2", "junit", "icu4j", - "truth", - "truth-java8-extension", + "truth-1.4.0-prebuilt", "robolectric-ant-1.8.0", "asm-9.2", "jsr305", diff --git a/robolectric/build.gradle b/robolectric/build.gradle index b826e9232..859504e6d 100644 --- a/robolectric/build.gradle +++ b/robolectric/build.gradle @@ -30,10 +30,12 @@ dependencies { compileOnly AndroidSdk.MAX_SDK.coordinates compileOnly libs.junit4 + compileOnly libs.androidx.annotation api "androidx.test:monitor:$axtMonitorVersion@aar" implementation "androidx.test.espresso:espresso-idling-resource:$espressoVersion@aar" + testImplementation libs.androidx.annotation testImplementation libs.junit4 testImplementation libs.truth testImplementation libs.truth.java8.extension @@ -46,40 +48,4 @@ dependencies { testImplementation libs.guava testCompileOnly AndroidSdk.MAX_SDK.coordinates // compile against latest Android SDK testRuntimeOnly AndroidSdk.MAX_SDK.coordinates // run against whatever this JDK supports -} - -project.apply plugin: CheckApiChangesPlugin - -checkApiChanges { - from = [ - "org.robolectric:robolectric:${apiCompatVersion}@jar", - "org.robolectric:annotations:${apiCompatVersion}@jar", - "org.robolectric:junit:${apiCompatVersion}@jar", - "org.robolectric:resources:${apiCompatVersion}@jar", - "org.robolectric:sandbox:${apiCompatVersion}@jar", - "org.robolectric:utils:${apiCompatVersion}@jar", - "org.robolectric:shadowapi:${apiCompatVersion}@jar", - "org.robolectric:shadows-framework:${apiCompatVersion}@jar", - ] - - to = [ - project(":robolectric"), - project(":annotations"), - project(":junit"), - project(":resources"), - project(":sandbox"), - project(":shadows:framework"), - project(":utils"), - project(":shadowapi"), - ] - - entryPoints += "org.robolectric.RobolectricTestRunner" - expectedChanges = [ - "^org.robolectric.util.ActivityController#", - "^org.robolectric.util.ComponentController#", - "^org.robolectric.util.ContentProviderController#", - "^org.robolectric.util.FragmentController#", - "^org.robolectric.util.IntentServiceController#", - "^org.robolectric.util.ServiceController#", - ] -} +}
\ No newline at end of file diff --git a/robolectric/src/main/java/org/robolectric/Robolectric.java b/robolectric/src/main/java/org/robolectric/Robolectric.java index 3ce637e76..7b694ec82 100644 --- a/robolectric/src/main/java/org/robolectric/Robolectric.java +++ b/robolectric/src/main/java/org/robolectric/Robolectric.java @@ -1,15 +1,21 @@ package org.robolectric; +import static android.os.Build.VERSION_CODES.P; import static com.google.common.base.Preconditions.checkState; import static org.robolectric.shadows.ShadowAssetManager.useLegacy; import android.annotation.IdRes; +import android.annotation.RequiresApi; import android.app.Activity; +import android.app.ActivityThread; +import android.app.AppComponentFactory; import android.app.Fragment; import android.app.IntentService; +import android.app.LoadedApk; import android.app.Service; import android.app.backup.BackupAgent; import android.content.ContentProvider; +import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.os.Looper; @@ -26,6 +32,7 @@ import org.robolectric.android.controller.FragmentController; import org.robolectric.android.controller.IntentServiceController; import org.robolectric.android.controller.ServiceController; import org.robolectric.shadows.ShadowApplication; +import org.robolectric.util.Logger; import org.robolectric.util.ReflectionHelpers; import org.robolectric.util.Scheduler; @@ -36,7 +43,7 @@ public class Robolectric { } public static <T extends Service> ServiceController<T> buildService(Class<T> serviceClass, Intent intent) { - return ServiceController.of(ReflectionHelpers.callConstructor(serviceClass), intent); + return ServiceController.of(instantiateService(serviceClass, intent), intent); } public static <T extends Service> T setupService(Class<T> serviceClass) { @@ -48,7 +55,7 @@ public class Robolectric { } public static <T extends IntentService> IntentServiceController<T> buildIntentService(Class<T> serviceClass, Intent intent) { - return IntentServiceController.of(ReflectionHelpers.callConstructor(serviceClass), intent); + return IntentServiceController.of(instantiateService(serviceClass, intent), intent); } public static <T extends IntentService> T setupIntentService(Class<T> serviceClass) { @@ -56,7 +63,7 @@ public class Robolectric { } public static <T extends ContentProvider> ContentProviderController<T> buildContentProvider(Class<T> contentProviderClass) { - return ContentProviderController.of(ReflectionHelpers.callConstructor(contentProviderClass)); + return ContentProviderController.of(instantiateContentProvider(contentProviderClass)); } public static <T extends ContentProvider> T setupContentProvider(Class<T> contentProviderClass) { @@ -110,7 +117,7 @@ public class Robolectric { Thread.currentThread() == Looper.getMainLooper().getThread(), "buildActivity must be called on main Looper thread"); return ActivityController.of( - ReflectionHelpers.callConstructor(activityClass), intent, activityOptions); + instantiateActivity(activityClass, intent), intent, activityOptions); } /** @@ -378,4 +385,84 @@ public class Robolectric { public static void flushBackgroundThreadScheduler() { getBackgroundThreadScheduler().advanceToLastPostedRunnable(); } + + @SuppressWarnings({"NewApi", "unchecked"}) + private static <T extends Service> T instantiateService(Class<T> serviceClass, Intent intent) { + if (RuntimeEnvironment.getApiLevel() >= P) { + final LoadedApk loadedApk = getLoadedApk(); + AppComponentFactory factory = getAppComponentFactory(loadedApk); + if (factory != null) { + try { + Service instance = + factory.instantiateService( + loadedApk.getClassLoader(), serviceClass.getName(), intent); + if (instance != null && serviceClass.isAssignableFrom(instance.getClass())) { + return (T) instance; + } + } catch (ReflectiveOperationException e) { + Logger.debug("Failed to instantiate Service using AppComponentFactory", e); + } + } + } + return ReflectionHelpers.callConstructor(serviceClass); + } + + @SuppressWarnings({"NewApi", "unchecked"}) + private static <T extends ContentProvider> T instantiateContentProvider(Class<T> providerClass) { + if (RuntimeEnvironment.getApiLevel() >= P) { + final LoadedApk loadedApk = getLoadedApk(); + AppComponentFactory factory = getAppComponentFactory(loadedApk); + if (factory != null) { + try { + ContentProvider instance = + factory.instantiateProvider(loadedApk.getClassLoader(), providerClass.getName()); + if (instance != null && providerClass.isAssignableFrom(instance.getClass())) { + return (T) instance; + } + } catch (ReflectiveOperationException e) { + Logger.debug("Failed to instantiate ContentProvider using AppComponentFactory", e); + } + } + } + return ReflectionHelpers.callConstructor(providerClass); + } + + @SuppressWarnings({"NewApi", "unchecked"}) + private static <T extends Activity> T instantiateActivity(Class<T> activityClass, Intent intent) { + if (RuntimeEnvironment.getApiLevel() >= P) { + final LoadedApk loadedApk = getLoadedApk(); + AppComponentFactory factory = getAppComponentFactory(loadedApk); + if (factory != null) { + try { + Activity instance = + factory.instantiateActivity( + loadedApk.getClassLoader(), activityClass.getName(), intent); + if (instance != null && activityClass.isAssignableFrom(instance.getClass())) { + return (T) instance; + } + } catch (ReflectiveOperationException e) { + Logger.debug("Failed to instantiate Activity using AppComponentFactory", e); + } + } + } + return ReflectionHelpers.callConstructor(activityClass); + } + + @Nullable + @RequiresApi(api = P) + private static AppComponentFactory getAppComponentFactory(final LoadedApk loadedApk) { + if (RuntimeEnvironment.getApiLevel() < P) { + return null; + } + if (loadedApk == null || loadedApk.getApplicationInfo().appComponentFactory == null) { + return null; + } + return loadedApk.getAppFactory(); + } + + private static LoadedApk getLoadedApk() { + final ActivityThread activityThread = (ActivityThread) RuntimeEnvironment.getActivityThread(); + return activityThread.getPackageInfo( + activityThread.getApplication().getApplicationInfo(), null, Context.CONTEXT_INCLUDE_CODE); + } } diff --git a/robolectric/src/main/java/org/robolectric/android/internal/AndroidTestEnvironment.java b/robolectric/src/main/java/org/robolectric/android/internal/AndroidTestEnvironment.java index 8776bc660..976edc52c 100644 --- a/robolectric/src/main/java/org/robolectric/android/internal/AndroidTestEnvironment.java +++ b/robolectric/src/main/java/org/robolectric/android/internal/AndroidTestEnvironment.java @@ -39,20 +39,29 @@ import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.Path; import java.security.Security; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; +import java.util.Arrays; import java.util.Locale; import java.util.Objects; import javax.annotation.Nonnull; import javax.inject.Named; +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLException; +import javax.net.ssl.SSLSession; import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.conscrypt.OkHostnameVerifier; import org.conscrypt.OpenSSLProvider; import org.robolectric.ApkLoader; import org.robolectric.RuntimeEnvironment; import org.robolectric.android.Bootstrap; import org.robolectric.annotation.Config; import org.robolectric.annotation.ConscryptMode; -import org.robolectric.annotation.LooperMode; import org.robolectric.annotation.GraphicsMode; import org.robolectric.annotation.GraphicsMode.Mode; +import org.robolectric.annotation.LooperMode; +import org.robolectric.annotation.SQLiteMode; import org.robolectric.annotation.experimental.LazyApplication.LazyLoad; import org.robolectric.config.ConfigurationRegistry; import org.robolectric.internal.ResourcesMode; @@ -61,6 +70,7 @@ import org.robolectric.internal.TestEnvironment; import org.robolectric.manifest.AndroidManifest; import org.robolectric.manifest.BroadcastReceiverData; import org.robolectric.manifest.RoboNotFoundException; +import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader; import org.robolectric.pluginapi.Sdk; import org.robolectric.pluginapi.TestEnvironmentLifecyclePlugin; import org.robolectric.pluginapi.config.ConfigurationStrategy.Configuration; @@ -147,6 +157,13 @@ public class AndroidTestEnvironment implements TestEnvironment { } clearEnvironment(); + + // Starting in Android V and above, the native runtime does not support begin lazy-loaded, it + // must be loaded upfront. + if (shouldLoadNativeRuntime() && RuntimeEnvironment.getApiLevel() >= AndroidVersions.V.SDK_INT) { + DefaultNativeRuntimeLoader.injectAndLoad(); + } + RuntimeEnvironment.setTempDirectory(new TempDirectory(createTestDataDirRootPath(method))); if (ShadowLooper.looperMode() == LooperMode.Mode.LEGACY) { RuntimeEnvironment.setMasterScheduler(new Scheduler()); @@ -169,6 +186,23 @@ public class AndroidTestEnvironment implements TestEnvironment { if (Security.getProvider(CONSCRYPT_PROVIDER) == null) { Security.insertProviderAt(new OpenSSLProvider(), 1); } + + HttpsURLConnection.setDefaultHostnameVerifier( + new HostnameVerifier() { + private final OkHostnameVerifier conscryptVerifier = OkHostnameVerifier.INSTANCE; + + @Override + public boolean verify(String hostname, SSLSession session) { + try { + Certificate[] certificates = session.getPeerCertificates(); + X509Certificate[] x509Certificates = + Arrays.copyOf(certificates, certificates.length, X509Certificate[].class); + return conscryptVerifier.verify(x509Certificates, hostname, session); + } catch (SSLException e) { + return false; + } + } + }); } if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) { @@ -209,8 +243,7 @@ public class AndroidTestEnvironment implements TestEnvironment { Instrumentation instrumentation = createInstrumentation(); InstrumentationRegistry.registerInstance(instrumentation, new Bundle()); - Supplier<Application> applicationSupplier = - createApplicationSupplier(appManifest, config, androidConfiguration, displayMetrics); + Supplier<Application> applicationSupplier = createApplicationSupplier(appManifest, config); RuntimeEnvironment.setApplicationSupplier(applicationSupplier); if (configuration.get(LazyLoad.class) == LazyLoad.ON) { @@ -220,6 +253,7 @@ public class AndroidTestEnvironment implements TestEnvironment { // force eager load of the application RuntimeEnvironment.getApplication(); } + } // If certain Android classes are required to be loaded in a particular order, do so here. @@ -239,10 +273,7 @@ public class AndroidTestEnvironment implements TestEnvironment { // TODO Move synchronization logic into its own class for better readability private Supplier<Application> createApplicationSupplier( - AndroidManifest appManifest, - Config config, - android.content.res.Configuration androidConfiguration, - DisplayMetrics displayMetrics) { + AndroidManifest appManifest, Config config) { final ActivityThread activityThread = (ActivityThread) RuntimeEnvironment.getActivityThread(); final _ActivityThread_ _activityThread_ = reflector(_ActivityThread_.class, activityThread); final ShadowActivityThread shadowActivityThread = Shadow.extract(activityThread); @@ -256,8 +287,6 @@ public class AndroidTestEnvironment implements TestEnvironment { installAndCreateApplication( appManifest, config, - androidConfiguration, - displayMetrics, shadowActivityThread, _activityThread_, activityThread.getInstrumentation()))); @@ -266,8 +295,6 @@ public class AndroidTestEnvironment implements TestEnvironment { private Application installAndCreateApplication( AndroidManifest appManifest, Config config, - android.content.res.Configuration androidConfiguration, - DisplayMetrics displayMetrics, ShadowActivityThread shadowActivityThread, _ActivityThread_ activityThreadReflector, Instrumentation androidInstrumentation) { @@ -309,6 +336,9 @@ public class AndroidTestEnvironment implements TestEnvironment { // code in there that can be reusable, e.g: the XxxxIntentResolver code. ShadowActivityThread.setApplicationInfo(applicationInfo); + // Bootstrap.getConfiguration gets any potential updates to configuration via + // RuntimeEnvironment.setQualifiers. + android.content.res.Configuration androidConfiguration = Bootstrap.getConfiguration(); shadowActivityThread.setCompatConfiguration(androidConfiguration); Bootstrap.setUpDisplay(); @@ -368,9 +398,7 @@ public class AndroidTestEnvironment implements TestEnvironment { } registerBroadcastReceivers(application, appManifest, loadedApk); - appResources.updateConfiguration(androidConfiguration, displayMetrics); - // propagate any updates to configuration via RuntimeEnvironment.setQualifiers - Bootstrap.updateConfiguration(appResources); + appResources.updateConfiguration(androidConfiguration, Bootstrap.getDisplayMetrics()); if (ShadowAssetManager.useLegacy()) { populateAssetPaths(appResources.getAssets(), appManifest); @@ -585,25 +613,14 @@ public class AndroidTestEnvironment implements TestEnvironment { Application dummyInitialApplication = new Application(); final ComponentName dummyInitialComponent = new ComponentName("", androidInstrumentation.getClass().getSimpleName()); - // TODO Move the API check into a helper method inside ShadowInstrumentation - if (RuntimeEnvironment.getApiLevel() <= VERSION_CODES.JELLY_BEAN_MR1) { - reflector(_Instrumentation_.class, androidInstrumentation) - .init( - activityThread, - dummyInitialApplication, - dummyInitialApplication, - dummyInitialComponent, - null); - } else { - reflector(_Instrumentation_.class, androidInstrumentation) - .init( - activityThread, - dummyInitialApplication, - dummyInitialApplication, - dummyInitialComponent, - null, - null); - } + reflector(_Instrumentation_.class, androidInstrumentation) + .init( + activityThread, + dummyInitialApplication, + dummyInitialApplication, + dummyInitialComponent, + null, + null); }); androidInstrumentation.onCreate(new Bundle()); @@ -697,7 +714,7 @@ public class AndroidTestEnvironment implements TestEnvironment { applicationInfo.publicSourceDir = createTempDir(applicationInfo.packageName + "-publicSourceDir"); } else { - if (apiLevel <= VERSION_CODES.KITKAT) { + if (apiLevel == VERSION_CODES.KITKAT) { String sourcePath = reflector(_Package_.class, parsedPackage).getPath(); if (sourcePath == null) { sourcePath = createTempDir("sourceDir"); @@ -742,6 +759,12 @@ public class AndroidTestEnvironment implements TestEnvironment { return null; } + private static boolean shouldLoadNativeRuntime() { + GraphicsMode.Mode graphicsMode = ConfigurationRegistry.get(GraphicsMode.Mode.class); + SQLiteMode.Mode sqliteMode = ConfigurationRegistry.get(SQLiteMode.Mode.class); + return graphicsMode == GraphicsMode.Mode.NATIVE || sqliteMode == SQLiteMode.Mode.NATIVE; + } + // TODO move/replace this with packageManager @VisibleForTesting static void registerBroadcastReceivers( diff --git a/robolectric/src/main/java/org/robolectric/android/internal/RoboMonitoringInstrumentation.java b/robolectric/src/main/java/org/robolectric/android/internal/RoboMonitoringInstrumentation.java index bfd3104af..a9665b706 100644 --- a/robolectric/src/main/java/org/robolectric/android/internal/RoboMonitoringInstrumentation.java +++ b/robolectric/src/main/java/org/robolectric/android/internal/RoboMonitoringInstrumentation.java @@ -35,6 +35,7 @@ import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import javax.annotation.Nullable; import org.robolectric.Robolectric; @@ -62,6 +63,8 @@ public class RoboMonitoringInstrumentation extends Instrumentation { private final IntentMonitorImpl intentMonitor = new IntentMonitorImpl(); private final List<ActivityController<?>> createdActivities = new ArrayList<>(); + private final AtomicBoolean attachedConfigListener = new AtomicBoolean(); + /** * Sets up lifecycle monitoring, and argument registry. * @@ -74,13 +77,6 @@ public class RoboMonitoringInstrumentation extends Instrumentation { ActivityLifecycleMonitorRegistry.registerInstance(lifecycleMonitor); ApplicationLifecycleMonitorRegistry.registerInstance(applicationMonitor); IntentMonitorRegistry.registerInstance(intentMonitor); - if (!Boolean.getBoolean("robolectric.createActivityContexts")) { - // To avoid infinite recursion listen to the system resources, this will be updated before - // the application resources but because activities use the application resources they will - // get updated by the first activity (via updateConfiguration). - shadowOf(Resources.getSystem()).addConfigurationChangeListener(this::updateConfiguration); - } - super.onCreate(arguments); } @@ -115,6 +111,15 @@ public class RoboMonitoringInstrumentation extends Instrumentation { } catch (ClassNotFoundException e) { throw new RuntimeException("Could not load activity " + ai.name, e); } + + if (attachedConfigListener.compareAndSet(false, true) + && !Boolean.getBoolean("robolectric.createActivityContexts")) { + // To avoid infinite recursion listen to the system resources, this will be updated before + // the application resources but because activities use the application resources they will + // get updated by the first activity (via updateConfiguration). + shadowOf(Resources.getSystem()).addConfigurationChangeListener(this::updateConfiguration); + } + AtomicReference<ActivityController<? extends Activity>> activityControllerReference = new AtomicReference<>(); ShadowInstrumentation.runOnMainSyncNoIdle( diff --git a/robolectric/src/main/java/org/robolectric/internal/BuckManifestFactory.java b/robolectric/src/main/java/org/robolectric/internal/BuckManifestFactory.java index 0178ac8ea..9efb53c51 100644 --- a/robolectric/src/main/java/org/robolectric/internal/BuckManifestFactory.java +++ b/robolectric/src/main/java/org/robolectric/internal/BuckManifestFactory.java @@ -20,7 +20,8 @@ import org.robolectric.util.Util; public class BuckManifestFactory implements ManifestFactory { private static final String BUCK_ROBOLECTRIC_RES_DIRECTORIES = "buck.robolectric_res_directories"; - private static final String BUCK_ROBOLECTRIC_ASSETS_DIRECTORIES = "buck.robolectric_assets_directories"; + private static final String BUCK_ROBOLECTRIC_ASSETS_DIRECTORIES = + "buck.robolectric_assets_directories"; private static final String BUCK_ROBOLECTRIC_MANIFEST = "buck.robolectric_manifest"; @Override @@ -83,6 +84,6 @@ public class BuckManifestFactory implements ManifestFactory { for (String dir : dirs) { files.add(Fs.fromUrl(dir)); } - return files; + return Collections.unmodifiableList(files); } } diff --git a/robolectric/src/main/java/org/robolectric/plugins/DefaultSdkProvider.java b/robolectric/src/main/java/org/robolectric/plugins/DefaultSdkProvider.java index f4cf49f26..3b6f76da9 100644 --- a/robolectric/src/main/java/org/robolectric/plugins/DefaultSdkProvider.java +++ b/robolectric/src/main/java/org/robolectric/plugins/DefaultSdkProvider.java @@ -46,7 +46,7 @@ public class DefaultSdkProvider implements SdkProvider { private static final int RUNNING_JAVA_VERSION = Util.getJavaVersion(); - private static final int PREINSTRUMENTED_VERSION = 4; + private static final int PREINSTRUMENTED_VERSION = 5; private final DependencyResolver dependencyResolver; diff --git a/robolectric/src/test/java/org/robolectric/CustomAppComponentFactory.java b/robolectric/src/test/java/org/robolectric/CustomAppComponentFactory.java index 22df750a9..0cf46eae2 100644 --- a/robolectric/src/test/java/org/robolectric/CustomAppComponentFactory.java +++ b/robolectric/src/test/java/org/robolectric/CustomAppComponentFactory.java @@ -1,10 +1,16 @@ package org.robolectric; +import android.app.Activity; import android.app.AppComponentFactory; +import android.app.Service; import android.content.BroadcastReceiver; +import android.content.ContentProvider; import android.content.Intent; import org.robolectric.CustomConstructorReceiverWrapper.CustomConstructorWithEmptyActionReceiver; import org.robolectric.CustomConstructorReceiverWrapper.CustomConstructorWithOneActionReceiver; +import org.robolectric.CustomConstructorServices.CustomConstructorIntentService; +import org.robolectric.CustomConstructorServices.CustomConstructorJobService; +import org.robolectric.CustomConstructorServices.CustomConstructorService; public final class CustomAppComponentFactory extends AppComponentFactory { @Override @@ -19,4 +25,41 @@ public final class CustomAppComponentFactory extends AppComponentFactory { } return super.instantiateReceiver(cl, className, intent); } + + @Override + public Service instantiateService(ClassLoader cl, String className, Intent intent) + throws InstantiationException, IllegalAccessException, ClassNotFoundException { + if (className != null) { + if (className.contains(CustomConstructorService.class.getName())) { + return new CustomConstructorService(100); + } else if (className.contains(CustomConstructorIntentService.class.getName())) { + return new CustomConstructorIntentService(100); + } else if (className.contains(CustomConstructorJobService.class.getName())) { + return new CustomConstructorJobService(100); + } + } + return super.instantiateService(cl, className, intent); + } + + @Override + public ContentProvider instantiateProvider(ClassLoader cl, String className) + throws InstantiationException, IllegalAccessException, ClassNotFoundException { + if (className != null) { + if (className.contains(CustomConstructorContentProvider.class.getName())) { + return new CustomConstructorContentProvider(100); + } + } + return super.instantiateProvider(cl, className); + } + + @Override + public Activity instantiateActivity(ClassLoader cl, String className, Intent intent) + throws InstantiationException, IllegalAccessException, ClassNotFoundException { + if (className != null) { + if (className.contains(CustomConstructorActivity.class.getName())) { + return new CustomConstructorActivity(100); + } + } + return super.instantiateActivity(cl, className, intent); + } } diff --git a/robolectric/src/test/java/org/robolectric/CustomAppComponentFactoryTest.java b/robolectric/src/test/java/org/robolectric/CustomAppComponentFactoryTest.java new file mode 100644 index 000000000..da7f4eb6c --- /dev/null +++ b/robolectric/src/test/java/org/robolectric/CustomAppComponentFactoryTest.java @@ -0,0 +1,161 @@ +package org.robolectric; + +import static com.google.common.truth.Truth.assertThat; + +import android.app.Activity; +import android.app.Service; +import android.content.ContentProvider; +import android.content.ContentValues; +import android.content.Intent; +import android.database.Cursor; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.os.IBinder; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.CustomConstructorServices.CustomConstructorIntentService; +import org.robolectric.CustomConstructorServices.CustomConstructorJobService; +import org.robolectric.CustomConstructorServices.CustomConstructorService; +import org.robolectric.android.controller.ActivityController; +import org.robolectric.annotation.Config; + +@RunWith(AndroidJUnit4.class) +@Config(manifest = "TestAndroidManifestWithAppComponentFactory.xml", minSdk = Build.VERSION_CODES.P) +public class CustomAppComponentFactoryTest { + + @Test + public void instantiateServiceWithCustomConstructor() { + CustomConstructorService service = Robolectric.setupService(CustomConstructorService.class); + assertThat(service.getIntValue()).isEqualTo(100); + } + + @Test + public void instantiateIntentServiceWithCustomConstructor() { + CustomConstructorIntentService service = + Robolectric.setupService(CustomConstructorIntentService.class); + assertThat(service.getIntValue()).isEqualTo(100); + } + + @Test + public void instantiateJobServiceWithCustomConstructor() { + CustomConstructorJobService service = + Robolectric.setupService(CustomConstructorJobService.class); + assertThat(service.getIntValue()).isEqualTo(100); + } + + @Test + public void instantiatePrivateServiceClass() { + PrivateService service = Robolectric.setupService(PrivateService.class); + assertThat(service.isCreated).isTrue(); + } + + @Test + public void instantiateContentProviderWithCustomConstructor() { + CustomConstructorContentProvider provider = + Robolectric.setupContentProvider(CustomConstructorContentProvider.class); + assertThat(provider.getIntValue()).isEqualTo(100); + } + + @Test + public void instantiateContentProviderWithCustomConstructorAndAuthority() { + CustomConstructorContentProvider provider = + Robolectric.setupContentProvider( + CustomConstructorContentProvider.class, "org.robolectric.authority"); + assertThat(provider.getIntValue()).isEqualTo(100); + } + + @Test + public void instantiatePrivateContentProviderClass() { + PrivateContentProvider provider = + Robolectric.setupContentProvider(PrivateContentProvider.class); + assertThat(provider.isCreated).isTrue(); + } + + @Test + @SuppressWarnings("deprecation") + public void instantiateActivityWithCustomConstructor() { + CustomConstructorActivity activity = Robolectric.setupActivity(CustomConstructorActivity.class); + assertThat(activity.getIntValue()).isEqualTo(100); + } + + @Test + public void instantiateActivityWithCustomConstructorAndIntent() { + Uri intentUri = Uri.parse("https://robolectric.org"); + Intent intent = new Intent(Intent.ACTION_VIEW, intentUri); + try (ActivityController<CustomConstructorActivity> activity = + Robolectric.buildActivity(CustomConstructorActivity.class, intent)) { + assertThat(activity.get().getIntValue()).isEqualTo(100); + assertThat(activity.get().getIntent().getData()).isEqualTo(intentUri); + assertThat(activity.get().getIntent().getAction()).isEqualTo(Intent.ACTION_VIEW); + } + } + + @Test + @SuppressWarnings("deprecation") + public void instantiatePrivateActivityClass() { + PrivateActivity activity = Robolectric.setupActivity(PrivateActivity.class); + assertThat(activity.isCreated).isTrue(); + } + + private static class PrivateService extends Service { + public boolean isCreated = false; + + @Override + public void onCreate() { + super.onCreate(); + isCreated = true; + } + + @Override + public IBinder onBind(Intent intent) { + return null; + } + } + + private static class PrivateContentProvider extends ContentProvider { + public boolean isCreated = false; + + @Override + public boolean onCreate() { + isCreated = true; + return false; + } + + @Override + public Cursor query(Uri uri, String[] strings, String s, String[] strings1, String s1) { + return null; + } + + @Override + public String getType(Uri uri) { + return null; + } + + @Override + public Uri insert(Uri uri, ContentValues contentValues) { + return null; + } + + @Override + public int delete(Uri uri, String s, String[] strings) { + return 0; + } + + @Override + public int update(Uri uri, ContentValues contentValues, String s, String[] strings) { + return 0; + } + } + + private static class PrivateActivity extends Activity { + public boolean isCreated = false; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + isCreated = true; + } + } +} diff --git a/robolectric/src/test/java/org/robolectric/CustomConstructorActivity.java b/robolectric/src/test/java/org/robolectric/CustomConstructorActivity.java new file mode 100644 index 000000000..2dae3b1f2 --- /dev/null +++ b/robolectric/src/test/java/org/robolectric/CustomConstructorActivity.java @@ -0,0 +1,15 @@ +package org.robolectric; + +import android.app.Activity; + +public class CustomConstructorActivity extends Activity { + private final int intValue; + + public CustomConstructorActivity(int intValue) { + this.intValue = intValue; + } + + public int getIntValue() { + return intValue; + } +} diff --git a/robolectric/src/test/java/org/robolectric/CustomConstructorContentProvider.java b/robolectric/src/test/java/org/robolectric/CustomConstructorContentProvider.java new file mode 100644 index 000000000..8848f7d9f --- /dev/null +++ b/robolectric/src/test/java/org/robolectric/CustomConstructorContentProvider.java @@ -0,0 +1,48 @@ +package org.robolectric; + +import android.content.ContentProvider; +import android.content.ContentValues; +import android.database.Cursor; +import android.net.Uri; + +public class CustomConstructorContentProvider extends ContentProvider { + private final int intValue; + + public CustomConstructorContentProvider(int intValue) { + this.intValue = intValue; + } + + public int getIntValue() { + return intValue; + } + + @Override + public boolean onCreate() { + return true; + } + + @Override + public Cursor query(Uri uri, String[] strings, String s, String[] strings1, String s1) { + return null; + } + + @Override + public String getType(Uri uri) { + return null; + } + + @Override + public Uri insert(Uri uri, ContentValues contentValues) { + return null; + } + + @Override + public int delete(Uri uri, String s, String[] strings) { + return 0; + } + + @Override + public int update(Uri uri, ContentValues contentValues, String s, String[] strings) { + return 0; + } +} diff --git a/robolectric/src/test/java/org/robolectric/CustomConstructorServices.java b/robolectric/src/test/java/org/robolectric/CustomConstructorServices.java new file mode 100644 index 000000000..31d1eb20f --- /dev/null +++ b/robolectric/src/test/java/org/robolectric/CustomConstructorServices.java @@ -0,0 +1,67 @@ +package org.robolectric; + +import android.app.IntentService; +import android.app.Service; +import android.app.job.JobParameters; +import android.app.job.JobService; +import android.content.Intent; +import android.os.IBinder; + +public class CustomConstructorServices { + + public static class CustomConstructorService extends Service { + private final int intValue; + + public CustomConstructorService(int intValue) { + this.intValue = intValue; + } + + @Override + public IBinder onBind(Intent intent) { + return null; + } + + public int getIntValue() { + return intValue; + } + } + + @SuppressWarnings("deprecation") + public static class CustomConstructorIntentService extends IntentService { + private final int intValue; + + public CustomConstructorIntentService(int intValue) { + super("test"); + this.intValue = intValue; + } + + @Override + protected void onHandleIntent(Intent intent) {} + + public int getIntValue() { + return intValue; + } + } + + public static class CustomConstructorJobService extends JobService { + private final int intValue; + + public CustomConstructorJobService(int intValue) { + this.intValue = intValue; + } + + @Override + public boolean onStartJob(JobParameters jobParameters) { + return true; + } + + @Override + public boolean onStopJob(JobParameters jobParameters) { + return true; + } + + public int getIntValue() { + return intValue; + } + } +} diff --git a/robolectric/src/test/java/org/robolectric/ManifestFactoryTest.java b/robolectric/src/test/java/org/robolectric/ManifestFactoryTest.java index 107252b47..35b9a9694 100644 --- a/robolectric/src/test/java/org/robolectric/ManifestFactoryTest.java +++ b/robolectric/src/test/java/org/robolectric/ManifestFactoryTest.java @@ -1,7 +1,6 @@ package org.robolectric; import static com.google.common.truth.Truth.assertThat; -import static com.google.common.truth.Truth8.assertThat; import java.net.URL; import java.nio.file.Paths; @@ -20,7 +19,8 @@ import org.robolectric.res.Fs; public class ManifestFactoryTest { @Test - public void whenBuildSystemApiPropertiesFileIsPresent_shouldUseDefaultManifestFactory() throws Exception { + public void whenBuildSystemApiPropertiesFileIsPresent_shouldUseDefaultManifestFactory() + throws Exception { final Properties properties = new Properties(); properties.setProperty("android_sdk_home", ""); properties.setProperty("android_merged_manifest", "/path/to/MergedManifest.xml"); @@ -46,8 +46,8 @@ public class ManifestFactoryTest { assertThat(manifestIdentifier.getLibraries()).isEmpty(); assertThat(manifestIdentifier.getPackageName()).isNull(); - AndroidManifest androidManifest = RobolectricTestRunner - .createAndroidManifest(manifestIdentifier); + AndroidManifest androidManifest = + RobolectricTestRunner.createAndroidManifest(manifestIdentifier); assertThat(androidManifest.getAndroidManifestFile()) .isEqualTo(Paths.get("/path/to/MergedManifest.xml")); assertThat(androidManifest.getResDirectory()).isEqualTo(Paths.get("/path/to/merged-resources")); @@ -70,10 +70,11 @@ public class ManifestFactoryTest { } }; - Config.Implementation config = Config.Builder.defaults() - .setManifest("TestAndroidManifest.xml") - .setPackageName("another.package") - .build(); + Config.Implementation config = + Config.Builder.defaults() + .setManifest("TestAndroidManifest.xml") + .setPackageName("another.package") + .build(); ManifestFactory manifestFactory = testRunner.getManifestFactory(config); assertThat(manifestFactory).isInstanceOf(DefaultManifestFactory.class); ManifestIdentifier manifestIdentifier = manifestFactory.identify(config); diff --git a/robolectric/src/test/java/org/robolectric/RuntimeEnvironmentTest.java b/robolectric/src/test/java/org/robolectric/RuntimeEnvironmentTest.java index 26a197003..bdbf97278 100644 --- a/robolectric/src/test/java/org/robolectric/RuntimeEnvironmentTest.java +++ b/robolectric/src/test/java/org/robolectric/RuntimeEnvironmentTest.java @@ -1,7 +1,5 @@ package org.robolectric; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; -import static android.os.Build.VERSION_CODES.KITKAT; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; import static java.util.concurrent.TimeUnit.MILLISECONDS; @@ -19,7 +17,6 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicBoolean; import org.junit.Test; import org.junit.runner.RunWith; -import org.robolectric.annotation.Config; import org.robolectric.annotation.LooperMode; import org.robolectric.shadows.ShadowDisplay; import org.robolectric.util.Scheduler; @@ -133,7 +130,6 @@ public class RuntimeEnvironmentTest { } @Test - @Config(minSdk = JELLY_BEAN_MR1) public void testGetRotation() { RuntimeEnvironment.setQualifiers("+land"); int screenRotation = ShadowDisplay.getDefaultDisplay().getRotation(); @@ -141,7 +137,6 @@ public class RuntimeEnvironmentTest { } @Test - @Config(minSdk = KITKAT) public void setQualifiers_resetsDateUtilsFormatCache() { RuntimeEnvironment.setQualifiers("ar-rXB"); // Populate the DateUtils static format cache. diff --git a/robolectric/src/test/java/org/robolectric/android/BootstrapTest.java b/robolectric/src/test/java/org/robolectric/android/BootstrapTest.java index 2164ac6e1..3e9eb6a93 100644 --- a/robolectric/src/test/java/org/robolectric/android/BootstrapTest.java +++ b/robolectric/src/test/java/org/robolectric/android/BootstrapTest.java @@ -15,7 +15,6 @@ import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.content.res.Configuration.ORIENTATION_PORTRAIT; import static android.content.res.Configuration.SCREENLAYOUT_LAYOUTDIR_LTR; import static android.content.res.Configuration.SCREENLAYOUT_LAYOUTDIR_MASK; -import static android.content.res.Configuration.SCREENLAYOUT_LAYOUTDIR_RTL; import static android.content.res.Configuration.SCREENLAYOUT_LONG_MASK; import static android.content.res.Configuration.SCREENLAYOUT_LONG_NO; import static android.content.res.Configuration.SCREENLAYOUT_LONG_YES; @@ -33,7 +32,6 @@ import static android.content.res.Configuration.UI_MODE_NIGHT_YES; import static android.content.res.Configuration.UI_MODE_TYPE_APPLIANCE; import static android.content.res.Configuration.UI_MODE_TYPE_MASK; import static android.content.res.Configuration.UI_MODE_TYPE_NORMAL; -import static android.os.Build.VERSION_CODES.JELLY_BEAN; import static android.os.Build.VERSION_CODES.KITKAT; import static android.os.Build.VERSION_CODES.N; import static android.os.Build.VERSION_CODES.O; @@ -81,30 +79,28 @@ public class BootstrapTest { @Test @Config(qualifiers = "w480dp-h640dp") public void shouldSetUpRealisticDisplay() throws Exception { - if (Build.VERSION.SDK_INT > JELLY_BEAN) { - DisplayManager displayManager = - (DisplayManager) - ApplicationProvider.getApplicationContext().getSystemService(Context.DISPLAY_SERVICE); - DisplayInfo displayInfo = new DisplayInfo(); - Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY); - display.getDisplayInfo(displayInfo); - - assertThat(displayInfo.name).isEqualTo("Built-in screen"); - assertThat(displayInfo.appWidth).isEqualTo(480); - assertThat(displayInfo.appHeight).isEqualTo(640); - assertThat(displayInfo.smallestNominalAppWidth).isEqualTo(480); - assertThat(displayInfo.smallestNominalAppHeight).isEqualTo(480); - assertThat(displayInfo.largestNominalAppWidth).isEqualTo(640); - assertThat(displayInfo.largestNominalAppHeight).isEqualTo(640); - assertThat(displayInfo.logicalWidth).isEqualTo(480); - assertThat(displayInfo.logicalHeight).isEqualTo(640); - assertThat(displayInfo.rotation).isEqualTo(ROTATION_0); - assertThat(displayInfo.logicalDensityDpi).isEqualTo(160); - assertThat(displayInfo.physicalXDpi).isEqualTo(160f); - assertThat(displayInfo.physicalYDpi).isEqualTo(160f); - if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) { - assertThat(displayInfo.state).isEqualTo(Display.STATE_ON); - } + DisplayManager displayManager = + (DisplayManager) + ApplicationProvider.getApplicationContext().getSystemService(Context.DISPLAY_SERVICE); + DisplayInfo displayInfo = new DisplayInfo(); + Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY); + display.getDisplayInfo(displayInfo); + + assertThat(displayInfo.name).isEqualTo("Built-in screen"); + assertThat(displayInfo.appWidth).isEqualTo(480); + assertThat(displayInfo.appHeight).isEqualTo(640); + assertThat(displayInfo.smallestNominalAppWidth).isEqualTo(480); + assertThat(displayInfo.smallestNominalAppHeight).isEqualTo(480); + assertThat(displayInfo.largestNominalAppWidth).isEqualTo(640); + assertThat(displayInfo.largestNominalAppHeight).isEqualTo(640); + assertThat(displayInfo.logicalWidth).isEqualTo(480); + assertThat(displayInfo.logicalHeight).isEqualTo(640); + assertThat(displayInfo.rotation).isEqualTo(ROTATION_0); + assertThat(displayInfo.logicalDensityDpi).isEqualTo(160); + assertThat(displayInfo.physicalXDpi).isEqualTo(160f); + assertThat(displayInfo.physicalYDpi).isEqualTo(160f); + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) { + assertThat(displayInfo.state).isEqualTo(Display.STATE_ON); } DisplayMetrics displayMetrics = @@ -116,30 +112,28 @@ public class BootstrapTest { @Test @Config(qualifiers = "w480dp-h640dp-land-hdpi") public void shouldSetUpRealisticDisplay_landscapeHighDensity() throws Exception { - if (Build.VERSION.SDK_INT > JELLY_BEAN) { - DisplayManager displayManager = - (DisplayManager) - ApplicationProvider.getApplicationContext().getSystemService(Context.DISPLAY_SERVICE); - DisplayInfo displayInfo = new DisplayInfo(); - Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY); - display.getDisplayInfo(displayInfo); - - assertThat(displayInfo.name).isEqualTo("Built-in screen"); - assertThat(displayInfo.appWidth).isEqualTo(960); - assertThat(displayInfo.appHeight).isEqualTo(720); - assertThat(displayInfo.smallestNominalAppWidth).isEqualTo(720); - assertThat(displayInfo.smallestNominalAppHeight).isEqualTo(720); - assertThat(displayInfo.largestNominalAppWidth).isEqualTo(960); - assertThat(displayInfo.largestNominalAppHeight).isEqualTo(960); - assertThat(displayInfo.logicalWidth).isEqualTo(960); - assertThat(displayInfo.logicalHeight).isEqualTo(720); - assertThat(displayInfo.rotation).isEqualTo(ROTATION_90); - assertThat(displayInfo.logicalDensityDpi).isEqualTo(240); - assertThat(displayInfo.physicalXDpi).isEqualTo(240f); - assertThat(displayInfo.physicalYDpi).isEqualTo(240f); - if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) { - assertThat(displayInfo.state).isEqualTo(Display.STATE_ON); - } + DisplayManager displayManager = + (DisplayManager) + ApplicationProvider.getApplicationContext().getSystemService(Context.DISPLAY_SERVICE); + DisplayInfo displayInfo = new DisplayInfo(); + Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY); + display.getDisplayInfo(displayInfo); + + assertThat(displayInfo.name).isEqualTo("Built-in screen"); + assertThat(displayInfo.appWidth).isEqualTo(960); + assertThat(displayInfo.appHeight).isEqualTo(720); + assertThat(displayInfo.smallestNominalAppWidth).isEqualTo(720); + assertThat(displayInfo.smallestNominalAppHeight).isEqualTo(720); + assertThat(displayInfo.largestNominalAppWidth).isEqualTo(960); + assertThat(displayInfo.largestNominalAppHeight).isEqualTo(960); + assertThat(displayInfo.logicalWidth).isEqualTo(960); + assertThat(displayInfo.logicalHeight).isEqualTo(720); + assertThat(displayInfo.rotation).isEqualTo(ROTATION_90); + assertThat(displayInfo.logicalDensityDpi).isEqualTo(240); + assertThat(displayInfo.physicalXDpi).isEqualTo(240f); + assertThat(displayInfo.physicalYDpi).isEqualTo(240f); + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) { + assertThat(displayInfo.state).isEqualTo(Display.STATE_ON); } DisplayMetrics displayMetrics = @@ -174,13 +168,7 @@ public class BootstrapTest { assertThat(configuration.orientation).isEqualTo(ORIENTATION_PORTRAIT); assertThat(configuration.uiMode & UI_MODE_TYPE_MASK).isEqualTo(UI_MODE_TYPE_NORMAL); assertThat(configuration.uiMode & UI_MODE_NIGHT_MASK).isEqualTo(UI_MODE_NIGHT_NO); - - if (RuntimeEnvironment.getApiLevel() > JELLY_BEAN) { - assertThat(configuration.densityDpi).isEqualTo(DisplayMetrics.DENSITY_DEFAULT); - } else { - assertThat(displayMetrics.densityDpi).isEqualTo(DisplayMetrics.DENSITY_DEFAULT); - assertThat(displayMetrics.density).isEqualTo(1.0f); - } + assertThat(configuration.densityDpi).isEqualTo(DisplayMetrics.DENSITY_DEFAULT); assertThat(configuration.touchscreen).isEqualTo(TOUCHSCREEN_FINGER); assertThat(configuration.keyboardHidden).isEqualTo(KEYBOARDHIDDEN_SOFT); @@ -211,29 +199,21 @@ public class BootstrapTest { displayMetrics); String outQualifiers = ConfigurationV25.resourceQualifierString(configuration, displayMetrics); - if (RuntimeEnvironment.getApiLevel() > JELLY_BEAN) { - // Setting Locale in > JB results in forcing layout direction to match locale - assertThat(outQualifiers).isEqualTo("mcc310-mnc4-fr-rFR-ldltr-sw400dp-w480dp-h456dp" - + "-xlarge-long-round" + altOptsForO + "-land-appliance-night-hdpi-notouch-" - + "keyshidden-12key-navhidden-dpad-v" + Build.VERSION.RESOURCES_SDK_INT); - } else { - assertThat(outQualifiers).isEqualTo("mcc310-mnc4-fr-rFR-ldrtl-sw400dp-w480dp-h456dp" - + "-xlarge-long-round-land-appliance-night-hdpi-notouch-" - + "keyshidden-12key-navhidden-dpad-v" + Build.VERSION.RESOURCES_SDK_INT); - } + // Setting Locale results in forcing layout direction to match locale + assertThat(outQualifiers) + .isEqualTo( + "mcc310-mnc4-fr-rFR-ldltr-sw400dp-w480dp-h456dp" + + "-xlarge-long-round" + + altOptsForO + + "-land-appliance-night-hdpi-notouch-" + + "keyshidden-12key-navhidden-dpad-v" + + Build.VERSION.RESOURCES_SDK_INT); assertThat(configuration.mcc).isEqualTo(310); assertThat(configuration.mnc).isEqualTo(4); assertThat(configuration.locale).isEqualTo(new Locale("fr", "FR")); - if (RuntimeEnvironment.getApiLevel() > JELLY_BEAN) { - // note that locale overrides ltr/rtl - assertThat(configuration.screenLayout & SCREENLAYOUT_LAYOUTDIR_MASK) - .isEqualTo(SCREENLAYOUT_LAYOUTDIR_LTR); - } else { - // but not on Jelly Bean... - assertThat(configuration.screenLayout & SCREENLAYOUT_LAYOUTDIR_MASK) - .isEqualTo(SCREENLAYOUT_LAYOUTDIR_RTL); - } + assertThat(configuration.screenLayout & SCREENLAYOUT_LAYOUTDIR_MASK) + .isEqualTo(SCREENLAYOUT_LAYOUTDIR_LTR); assertThat(configuration.smallestScreenWidthDp).isEqualTo(400); assertThat(configuration.screenWidthDp).isEqualTo(480); assertThat(configuration.screenHeightDp).isEqualTo(456); @@ -243,12 +223,7 @@ public class BootstrapTest { assertThat(configuration.orientation).isEqualTo(ORIENTATION_LANDSCAPE); assertThat(configuration.uiMode & UI_MODE_TYPE_MASK).isEqualTo(UI_MODE_TYPE_APPLIANCE); assertThat(configuration.uiMode & UI_MODE_NIGHT_MASK).isEqualTo(UI_MODE_NIGHT_YES); - if (RuntimeEnvironment.getApiLevel() > JELLY_BEAN) { - assertThat(configuration.densityDpi).isEqualTo(DisplayMetrics.DENSITY_HIGH); - } else { - assertThat(displayMetrics.densityDpi).isEqualTo(DisplayMetrics.DENSITY_HIGH); - assertThat(displayMetrics.density).isEqualTo(1.5f); - } + assertThat(configuration.densityDpi).isEqualTo(DisplayMetrics.DENSITY_HIGH); assertThat(configuration.touchscreen).isEqualTo(TOUCHSCREEN_NOTOUCH); assertThat(configuration.keyboardHidden).isEqualTo(KEYBOARDHIDDEN_YES); assertThat(configuration.keyboard).isEqualTo(KEYBOARD_12KEY); diff --git a/robolectric/src/test/java/org/robolectric/android/DeviceConfigTest.java b/robolectric/src/test/java/org/robolectric/android/DeviceConfigTest.java index f2395b788..d71101765 100644 --- a/robolectric/src/test/java/org/robolectric/android/DeviceConfigTest.java +++ b/robolectric/src/test/java/org/robolectric/android/DeviceConfigTest.java @@ -36,7 +36,6 @@ public class DeviceConfigTest { } @Test - @Config(minSdk = VERSION_CODES.JELLY_BEAN_MR1) public void applyToConfiguration() { applyQualifiers("en-rUS-w400dp-h800dp-notround"); assertThat(asQualifierString()) @@ -95,7 +94,7 @@ public class DeviceConfigTest { + "port-notnight-mdpi-finger-keyssoft-nokeys-navhidden-nonav"); } - // todo: this fails on JELLY_BEAN and LOLLIPOP through M... why? + // todo: this fails on LOLLIPOP through M... why? @Test @Config(minSdk = VERSION_CODES.N) public void applyRules_rtlScript() { diff --git a/robolectric/src/test/java/org/robolectric/android/ResourceLoaderTest.java b/robolectric/src/test/java/org/robolectric/android/ResourceLoaderTest.java index 15ca52c68..b2c1edf64 100644 --- a/robolectric/src/test/java/org/robolectric/android/ResourceLoaderTest.java +++ b/robolectric/src/test/java/org/robolectric/android/ResourceLoaderTest.java @@ -8,7 +8,6 @@ import static org.robolectric.shadows.ShadowAssetManager.useLegacy; import android.content.res.Configuration; import android.content.res.Resources; import android.os.Build; -import android.os.Build.VERSION_CODES; import android.view.LayoutInflater; import android.view.View; import android.widget.TextView; @@ -84,11 +83,7 @@ public class ResourceLoaderTest { assertThat(textView.getText().toString()).isEqualTo("default"); RuntimeEnvironment.setQualifiers("fr-land"); // testing if this pollutes the other test Configuration configuration = Resources.getSystem().getConfiguration(); - if (RuntimeEnvironment.getApiLevel() <= VERSION_CODES.JELLY_BEAN) { - configuration.locale = new Locale("fr", "FR"); - } else { - configuration.setLocale(new Locale("fr", "FR")); - } + configuration.setLocale(new Locale("fr", "FR")); configuration.orientation = Configuration.ORIENTATION_LANDSCAPE; Resources.getSystem().updateConfiguration(configuration, null); } diff --git a/robolectric/src/test/java/org/robolectric/android/controller/ActivityControllerTest.java b/robolectric/src/test/java/org/robolectric/android/controller/ActivityControllerTest.java index 181a8013a..09aec11c9 100644 --- a/robolectric/src/test/java/org/robolectric/android/controller/ActivityControllerTest.java +++ b/robolectric/src/test/java/org/robolectric/android/controller/ActivityControllerTest.java @@ -185,7 +185,6 @@ public class ActivityControllerTest { } @Test - @Config(minSdk = VERSION_CODES.JELLY_BEAN_MR1) public void destroy_cleansUpWindowManagerState() { WindowManager windowManager = controller.get().getWindowManager(); ShadowWindowManagerImpl shadowWindowManager = diff --git a/robolectric/src/test/java/org/robolectric/internal/BuckManifestFactoryTest.java b/robolectric/src/test/java/org/robolectric/internal/BuckManifestFactoryTest.java index f7cd3edf3..10ac36359 100644 --- a/robolectric/src/test/java/org/robolectric/internal/BuckManifestFactoryTest.java +++ b/robolectric/src/test/java/org/robolectric/internal/BuckManifestFactoryTest.java @@ -1,7 +1,6 @@ package org.robolectric.internal; import static com.google.common.truth.Truth.assertThat; -import static com.google.common.truth.Truth8.assertThat; import static java.nio.charset.StandardCharsets.UTF_8; import com.google.common.io.Files; @@ -23,8 +22,7 @@ import org.robolectric.res.ResourcePath; @RunWith(JUnit4.class) public class BuckManifestFactoryTest { - @Rule - public TemporaryFolder tempFolder = new TemporaryFolder(); + @Rule public TemporaryFolder tempFolder = new TemporaryFolder(); private Config.Builder configBuilder; private BuckManifestFactory buckManifestFactory; @@ -43,18 +41,20 @@ public class BuckManifestFactoryTest { System.clearProperty("buck.robolectric_assets_directories"); } - @Test public void identify() throws Exception { + @Test + public void identify() throws Exception { ManifestIdentifier manifestIdentifier = buckManifestFactory.identify(configBuilder.build()); assertThat(manifestIdentifier.getManifestFile()) .isEqualTo(Paths.get("buck/AndroidManifest.xml")); - assertThat(manifestIdentifier.getPackageName()) - .isEqualTo("com.robolectric.buck"); + assertThat(manifestIdentifier.getPackageName()).isEqualTo("com.robolectric.buck"); } - @Test public void multiple_res_dirs() throws Exception { - System.setProperty("buck.robolectric_res_directories", - "buck/res1" + File.pathSeparator + "buck/res2"); - System.setProperty("buck.robolectric_assets_directories", + @Test + public void multiple_res_dirs() throws Exception { + System.setProperty( + "buck.robolectric_res_directories", "buck/res1" + File.pathSeparator + "buck/res2"); + System.setProperty( + "buck.robolectric_assets_directories", "buck/assets1" + File.pathSeparator + "buck/assets2"); ManifestIdentifier manifestIdentifier = buckManifestFactory.identify(configBuilder.build()); @@ -72,7 +72,8 @@ public class BuckManifestFactoryTest { new ResourcePath(manifest.getRClass(), null, Paths.get("buck/assets1"))); } - @Test public void pass_multiple_res_dirs_in_file() throws Exception { + @Test + public void pass_multiple_res_dirs_in_file() throws Exception { String resDirectoriesFileName = "res-directories"; File resDirectoriesFile = tempFolder.newFile(resDirectoriesFileName); Files.asCharSink(resDirectoriesFile, UTF_8).write("buck/res1\nbuck/res2"); diff --git a/robolectric/src/test/java/org/robolectric/internal/DefaultManifestFactoryTest.java b/robolectric/src/test/java/org/robolectric/internal/DefaultManifestFactoryTest.java index c32dc19cd..026d675b4 100644 --- a/robolectric/src/test/java/org/robolectric/internal/DefaultManifestFactoryTest.java +++ b/robolectric/src/test/java/org/robolectric/internal/DefaultManifestFactoryTest.java @@ -1,7 +1,6 @@ package org.robolectric.internal; import static com.google.common.truth.Truth.assertThat; -import static com.google.common.truth.Truth8.assertThat; import java.nio.file.Paths; import java.util.Properties; @@ -100,7 +99,6 @@ public class DefaultManifestFactoryTest { .isEqualTo(Paths.get("gradle/AndroidManifest.xml")); assertThat(manifest.getResDirectory()).isEqualTo(Paths.get("gradle/res")); assertThat(manifest.getAssetsDirectory()).isEqualTo(Paths.get("gradle/assets")); - assertThat(manifest.getRClassName()) - .isEqualTo("overridden.package.R"); + assertThat(manifest.getRClassName()).isEqualTo("overridden.package.R"); } } diff --git a/robolectric/src/test/java/org/robolectric/internal/MavenManifestFactoryTest.java b/robolectric/src/test/java/org/robolectric/internal/MavenManifestFactoryTest.java index 414fdb09d..60b064894 100644 --- a/robolectric/src/test/java/org/robolectric/internal/MavenManifestFactoryTest.java +++ b/robolectric/src/test/java/org/robolectric/internal/MavenManifestFactoryTest.java @@ -1,7 +1,6 @@ package org.robolectric.internal; import static com.google.common.truth.Truth.assertThat; -import static com.google.common.truth.Truth8.assertThat; import java.nio.file.Path; import java.nio.file.Paths; @@ -23,14 +22,16 @@ public class MavenManifestFactoryTest { myMavenManifestFactory = new MyMavenManifestFactory(); } - @Test public void identify() throws Exception { + @Test + public void identify() throws Exception { ManifestIdentifier manifestIdentifier = myMavenManifestFactory.identify(configBuilder.build()); assertThat(manifestIdentifier.getManifestFile()) .isEqualTo(Paths.get("_fakefs_path").resolve("to").resolve("DifferentManifest.xml")); assertThat(manifestIdentifier.getResDir()).isEqualTo(Paths.get("_fakefs_path/to/res")); } - @Test public void withDotSlashManifest_identify() throws Exception { + @Test + public void withDotSlashManifest_identify() throws Exception { configBuilder.setManifest("./DifferentManifest.xml"); ManifestIdentifier manifestIdentifier = myMavenManifestFactory.identify(configBuilder.build()); @@ -40,7 +41,8 @@ public class MavenManifestFactoryTest { .isEqualTo(Paths.get("_fakefs_path/to/res")); } - @Test public void withDotDotSlashManifest_identify() throws Exception { + @Test + public void withDotDotSlashManifest_identify() throws Exception { configBuilder.setManifest("../DifferentManifest.xml"); ManifestIdentifier manifestIdentifier = myMavenManifestFactory.identify(configBuilder.build()); @@ -55,4 +57,4 @@ public class MavenManifestFactoryTest { return Paths.get("_fakefs_path").resolve("to"); } } -}
\ No newline at end of file +} diff --git a/robolectric/src/test/java/org/robolectric/internal/bytecode/ShadowMapTest.java b/robolectric/src/test/java/org/robolectric/internal/bytecode/ShadowMapTest.java index 2e201197f..653d40564 100644 --- a/robolectric/src/test/java/org/robolectric/internal/bytecode/ShadowMapTest.java +++ b/robolectric/src/test/java/org/robolectric/internal/bytecode/ShadowMapTest.java @@ -69,8 +69,10 @@ public class ShadowMapTest { } @Test public void getInvalidatedClasses_disjoin() { - ShadowMap current = baseShadowMap.newBuilder().addShadowClass(A1, A2, true, false).build(); - ShadowMap previous = baseShadowMap.newBuilder().addShadowClass(B1, B2, true, false).build(); + ShadowMap current = + baseShadowMap.newBuilder().addShadowClass(A1, A2, true, false, false).build(); + ShadowMap previous = + baseShadowMap.newBuilder().addShadowClass(B1, B2, true, false, false).build(); assertThat(current.getInvalidatedClasses(previous)).containsExactly(A1, B1); } @@ -79,22 +81,22 @@ public class ShadowMapTest { ShadowMap current = baseShadowMap .newBuilder() - .addShadowClass(A1, A2, true, false) - .addShadowClass(C1, C2, true, false) + .addShadowClass(A1, A2, true, false, false) + .addShadowClass(C1, C2, true, false, false) .build(); ShadowMap previous = baseShadowMap .newBuilder() - .addShadowClass(A1, A2, true, false) - .addShadowClass(C1, C3, true, false) + .addShadowClass(A1, A2, true, false, false) + .addShadowClass(C1, C3, true, false, false) .build(); assertThat(current.getInvalidatedClasses(previous)).containsExactly(C1); } @Test public void equalsHashCode() throws Exception { - ShadowMap a = baseShadowMap.newBuilder().addShadowClass(A, B, true, false).build(); - ShadowMap b = baseShadowMap.newBuilder().addShadowClass(A, B, true, false).build(); + ShadowMap a = baseShadowMap.newBuilder().addShadowClass(A, B, true, false, false).build(); + ShadowMap b = baseShadowMap.newBuilder().addShadowClass(A, B, true, false, false).build(); assertThat(a).isEqualTo(b); assertThat(a.hashCode()).isEqualTo(b.hashCode()); @@ -102,7 +104,7 @@ public class ShadowMapTest { assertThat(c).isEqualTo(b); assertThat(c.hashCode()).isEqualTo(b.hashCode()); - ShadowMap d = baseShadowMap.newBuilder().addShadowClass(A, X, true, false).build(); + ShadowMap d = baseShadowMap.newBuilder().addShadowClass(A, X, true, false, false).build(); assertThat(d).isNotEqualTo(a); assertThat(d.hashCode()).isNotEqualTo(b.hashCode()); } diff --git a/robolectric/src/test/java/org/robolectric/manifest/AndroidManifestTest.java b/robolectric/src/test/java/org/robolectric/manifest/AndroidManifestTest.java index 51b809b69..e64cacd04 100644 --- a/robolectric/src/test/java/org/robolectric/manifest/AndroidManifestTest.java +++ b/robolectric/src/test/java/org/robolectric/manifest/AndroidManifestTest.java @@ -28,7 +28,7 @@ public class AndroidManifestTest { @Rule public TemporaryFolder temporaryFolder = new TemporaryFolder(); @Test - public void parseManifest_shouldReadContentProviders() throws Exception { + public void parseManifest_shouldReadContentProviders() { AndroidManifest config = newConfig("TestAndroidManifestWithContentProviders.xml"); assertThat(config.getContentProviders().get(0).getName()) @@ -45,7 +45,7 @@ public class AndroidManifestTest { } @Test - public void parseManifest_shouldReadPermissions() throws Exception { + public void parseManifest_shouldReadPermissions() { AndroidManifest config = newConfig("TestAndroidManifestWithPermissions.xml"); assertThat(config.getPermissions().keySet()) @@ -63,7 +63,7 @@ public class AndroidManifestTest { } @Test - public void parseManifest_shouldReadPermissionGroups() throws Exception { + public void parseManifest_shouldReadPermissionGroups() { AndroidManifest config = newConfig("TestAndroidManifestWithPermissions.xml"); assertThat(config.getPermissionGroups().keySet()) @@ -76,7 +76,7 @@ public class AndroidManifestTest { } @Test - public void parseManifest_shouldReadBroadcastReceivers() throws Exception { + public void parseManifest_shouldReadBroadcastReceivers() { AndroidManifest config = newConfig("TestAndroidManifestWithReceivers.xml"); assertThat(config.getBroadcastReceivers()).hasSize(8); @@ -124,7 +124,7 @@ public class AndroidManifestTest { } @Test - public void parseManifest_shouldReadServices() throws Exception { + public void parseManifest_shouldReadServices() { AndroidManifest config = newConfig("TestAndroidManifestWithServices.xml"); assertThat(config.getServices()).hasSize(2); @@ -152,13 +152,13 @@ public class AndroidManifestTest { } @Test - public void testManifestWithNoApplicationElement() throws Exception { + public void testManifestWithNoApplicationElement() { AndroidManifest config = newConfig("TestAndroidManifestNoApplicationElement.xml"); assertThat(config.getPackageName()).isEqualTo("org.robolectric"); } @Test - public void parseManifest_shouldReadBroadcastReceiversWithMetaData() throws Exception { + public void parseManifest_shouldReadBroadcastReceiversWithMetaData() { AndroidManifest config = newConfig("TestAndroidManifestWithReceivers.xml"); assertThat(config.getBroadcastReceivers().get(4).getName()) @@ -208,7 +208,7 @@ public class AndroidManifestTest { } @Test - public void shouldReadBroadcastReceiverPermissions() throws Exception { + public void shouldReadBroadcastReceiverPermissions() { AndroidManifest config = newConfig("TestAndroidManifestWithReceivers.xml"); assertThat(config.getBroadcastReceivers().get(7).getName()) @@ -231,19 +231,19 @@ public class AndroidManifestTest { assertThat(newConfigWith("minsdk7.xml", "android:minSdkVersion=\"7\"").getTargetSdkVersion()) .isEqualTo(7); assertThat(newConfigWith("noattributes.xml", "").getTargetSdkVersion()) - .isEqualTo(VERSION_CODES.JELLY_BEAN); + .isEqualTo(VERSION_CODES.KITKAT); } @Test - public void shouldReadMinSdkVersionFromAndroidManifestOrDefaultToJellyBean() throws Exception { + public void shouldReadMinSdkVersionFromAndroidManifestOrDefaultToKitKat() throws Exception { assertThat(newConfigWith("minsdk17.xml", "android:minSdkVersion=\"17\"").getMinSdkVersion()) .isEqualTo(17); assertThat(newConfigWith("noattributes.xml", "").getMinSdkVersion()) - .isEqualTo(VERSION_CODES.JELLY_BEAN); + .isEqualTo(VERSION_CODES.KITKAT); } @Test - public void shouldReadProcessFromAndroidManifest() throws Exception { + public void shouldReadProcessFromAndroidManifest() { assertThat(newConfig("TestAndroidManifestWithProcess.xml").getProcessName()) .isEqualTo("robolectricprocess"); } @@ -256,7 +256,7 @@ public class AndroidManifestTest { @Test @Config(manifest = "TestAndroidManifestWithAppMetaData.xml") - public void shouldReturnApplicationMetaData() throws Exception { + public void shouldReturnApplicationMetaData() { Map<String, Object> meta = newConfig("TestAndroidManifestWithAppMetaData.xml").getApplicationMetaData(); @@ -301,7 +301,7 @@ public class AndroidManifestTest { } @Test - public void shouldTolerateMissingRFile() throws Exception { + public void shouldTolerateMissingRFile() { AndroidManifest appManifest = new AndroidManifest( resourceFile("TestAndroidManifestWithNoRFile.xml"), @@ -312,7 +312,7 @@ public class AndroidManifestTest { } @Test - public void whenNullManifestFile_getRClass_shouldComeFromPackageName() throws Exception { + public void whenNullManifestFile_getRClass_shouldComeFromPackageName() { AndroidManifest appManifest = new AndroidManifest(null, resourceFile("res"), resourceFile("assets"), "org.robolectric"); assertThat(appManifest.getRClass()).isEqualTo(org.robolectric.R.class); @@ -320,7 +320,7 @@ public class AndroidManifestTest { } @Test - public void whenMissingManifestFile_getRClass_shouldComeFromPackageName() throws Exception { + public void whenMissingManifestFile_getRClass_shouldComeFromPackageName() { AndroidManifest appManifest = new AndroidManifest( resourceFile("none.xml"), @@ -332,7 +332,7 @@ public class AndroidManifestTest { } @Test - public void whenMissingManifestFile_getPackageName_shouldBeDefault() throws Exception { + public void whenMissingManifestFile_getPackageName_shouldBeDefault() { AndroidManifest appManifest = new AndroidManifest(null, resourceFile("res"), resourceFile("assets"), null); assertThat(appManifest.getPackageName()).isEqualTo("org.robolectric.default"); @@ -398,7 +398,7 @@ public class AndroidManifestTest { } @Test - public void shouldReadPermissions() throws Exception { + public void shouldReadPermissions() { AndroidManifest config = newConfig("TestAndroidManifestWithPermissions.xml"); assertThat(config.getUsedPermissions()).hasSize(3); @@ -408,7 +408,7 @@ public class AndroidManifestTest { } @Test - public void shouldReadPartiallyQualifiedActivities() throws Exception { + public void shouldReadPartiallyQualifiedActivities() { AndroidManifest config = newConfig("TestAndroidManifestForActivities.xml"); assertThat(config.getActivityDatas()).hasSize(2); assertThat(config.getActivityDatas()).containsKey("org.robolectric.shadows.TestActivity"); @@ -416,7 +416,7 @@ public class AndroidManifestTest { } @Test - public void shouldReadActivityAliases() throws Exception { + public void shouldReadActivityAliases() { AndroidManifest config = newConfig("TestAndroidManifestForActivityAliases.xml"); assertThat(config.getActivityDatas()).hasSize(2); assertThat(config.getActivityDatas()).containsKey("org.robolectric.shadows.TestActivity"); @@ -468,7 +468,7 @@ public class AndroidManifestTest { } @Test - public void shouldHaveStableHashCode() throws Exception { + public void shouldHaveStableHashCode() { AndroidManifest manifest = newConfig("TestAndroidManifestWithContentProviders.xml"); int hashCode1 = manifest.hashCode(); manifest.getServices(); @@ -477,13 +477,13 @@ public class AndroidManifestTest { } @Test - public void shouldReadApplicationAttrsFromAndroidManifest() throws Exception { + public void shouldReadApplicationAttrsFromAndroidManifest() { AndroidManifest config = newConfig("TestAndroidManifestWithFlags.xml"); assertThat(config.getApplicationAttributes().get("android:allowBackup")).isEqualTo("true"); } @Test - public void allFieldsShouldBePrimitivesOrJavaLangOrRobolectric() throws Exception { + public void allFieldsShouldBePrimitivesOrJavaLangOrRobolectric() { List<Field> wrongFields = new ArrayList<>(); for (Field field : AndroidManifest.class.getDeclaredFields()) { Class<?> type = field.getType(); @@ -502,35 +502,35 @@ public class AndroidManifestTest { } @Test - public void activitiesWithoutIntentFiltersNotExportedByDefault() throws Exception { + public void activitiesWithoutIntentFiltersNotExportedByDefault() { AndroidManifest config = newConfig("TestAndroidManifestForActivities.xml"); ActivityData activityData = config.getActivityData("org.robolectric.shadows.TestActivity"); assertThat(activityData.isExported()).isFalse(); } @Test - public void activitiesWithIntentFiltersExportedByDefault() throws Exception { + public void activitiesWithIntentFiltersExportedByDefault() { AndroidManifest config = newConfig("TestAndroidManifestForActivitiesWithIntentFilter.xml"); ActivityData activityData = config.getActivityData("org.robolectric.shadows.TestActivity"); assertThat(activityData.isExported()).isTrue(); } @Test - public void servicesWithoutIntentFiltersNotExportedByDefault() throws Exception { + public void servicesWithoutIntentFiltersNotExportedByDefault() { AndroidManifest config = newConfig("TestAndroidManifestWithServices.xml"); ServiceData serviceData = config.getServiceData("com.bar.ServiceWithoutIntentFilter"); assertThat(serviceData.isExported()).isFalse(); } @Test - public void servicesWithIntentFiltersExportedByDefault() throws Exception { + public void servicesWithIntentFiltersExportedByDefault() { AndroidManifest config = newConfig("TestAndroidManifestWithServices.xml"); ServiceData serviceData = config.getServiceData("com.foo.Service"); assertThat(serviceData.isExported()).isTrue(); } @Test - public void receiversWithoutIntentFiltersNotExportedByDefault() throws Exception { + public void receiversWithoutIntentFiltersNotExportedByDefault() { AndroidManifest config = newConfig("TestAndroidManifestWithReceivers.xml"); BroadcastReceiverData receiverData = config.getBroadcastReceiver("com.bar.ReceiverWithoutIntentFilter"); @@ -539,7 +539,7 @@ public class AndroidManifestTest { } @Test - public void receiversWithIntentFiltersExportedByDefault() throws Exception { + public void receiversWithIntentFiltersExportedByDefault() { AndroidManifest config = newConfig("TestAndroidManifestWithReceivers.xml"); BroadcastReceiverData receiverData = config.getBroadcastReceiver("com.foo.Receiver"); assertThat(receiverData).isNotNull(); @@ -547,7 +547,7 @@ public class AndroidManifestTest { } @Test - public void getTransitiveManifests() throws Exception { + public void getTransitiveManifests() { AndroidManifest lib1 = new AndroidManifest(resourceFile("lib1/AndroidManifest.xml"), null, null); AndroidManifest lib2 = new AndroidManifest(resourceFile("lib2/AndroidManifest.xml"), null, null, diff --git a/robolectric/src/test/java/org/robolectric/plugins/LegacyDependencyResolverTest.java b/robolectric/src/test/java/org/robolectric/plugins/LegacyDependencyResolverTest.java index 4cb13a0c8..e628be5cd 100644 --- a/robolectric/src/test/java/org/robolectric/plugins/LegacyDependencyResolverTest.java +++ b/robolectric/src/test/java/org/robolectric/plugins/LegacyDependencyResolverTest.java @@ -1,7 +1,6 @@ package org.robolectric.plugins; import static com.google.common.truth.Truth.assertThat; -import static com.google.common.truth.Truth8.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -46,9 +45,9 @@ public class LegacyDependencyResolverTest { @Test public void whenRobolectricDepsPropertiesProperty() throws Exception { - Path depsPath = tempDirectory - .createFile("deps.properties", - "org.robolectric\\:android-all\\:" + VERSION + ": file-123.jar"); + Path depsPath = + tempDirectory.createFile( + "deps.properties", "org.robolectric\\:android-all\\:" + VERSION + ": file-123.jar"); Path jarPath = tempDirectory.createFile("file-123.jar", "..."); properties.setProperty("robolectric-deps.properties", depsPath.toString()); @@ -61,9 +60,9 @@ public class LegacyDependencyResolverTest { @Test public void whenRobolectricDepsPropertiesPropertyAndOfflineProperty() throws Exception { - Path depsPath = tempDirectory - .createFile("deps.properties", - "org.robolectric\\:android-all\\:" + VERSION + ": file-123.jar"); + Path depsPath = + tempDirectory.createFile( + "deps.properties", "org.robolectric\\:android-all\\:" + VERSION + ": file-123.jar"); Path jarPath = tempDirectory.createFile("file-123.jar", "..."); properties.setProperty("robolectric-deps.properties", depsPath.toString()); @@ -77,9 +76,9 @@ public class LegacyDependencyResolverTest { @Test public void whenRobolectricDepsPropertiesResource() throws Exception { - Path depsPath = tempDirectory - .createFile("deps.properties", - "org.robolectric\\:android-all\\:" + VERSION + ": file-123.jar"); + Path depsPath = + tempDirectory.createFile( + "deps.properties", "org.robolectric\\:android-all\\:" + VERSION + ": file-123.jar"); when(mockClassLoader.getResource("robolectric-deps.properties")).thenReturn(meh(depsPath)); DependencyResolver resolver = new LegacyDependencyResolver(properties, mockClassLoader); @@ -110,8 +109,7 @@ public class LegacyDependencyResolverTest { DependencyResolver resolver = new LegacyDependencyResolver(properties, mockClassLoader); URL jarUrl = resolver.getLocalArtifactUrl(DEPENDENCY_COORDS); - assertThat(Fs.fromUrl(jarUrl)) - .isEqualTo(Paths.get("/some/fake/file.jar").toAbsolutePath()); + assertThat(Fs.fromUrl(jarUrl)).isEqualTo(Paths.get("/some/fake/file.jar").toAbsolutePath()); } public static class FakeMavenDependencyResolver implements DependencyResolver { diff --git a/robolectric/src/test/java/org/robolectric/shadows/AppWidgetProviderInfoBuilderTest.java b/robolectric/src/test/java/org/robolectric/shadows/AppWidgetProviderInfoBuilderTest.java index dfb47e40a..0496529f0 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/AppWidgetProviderInfoBuilderTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/AppWidgetProviderInfoBuilderTest.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.JELLY_BEAN; import static android.os.Build.VERSION_CODES.L; import static com.google.common.truth.Truth.assertThat; @@ -21,7 +20,6 @@ import org.robolectric.annotation.Config; /** Tests for {@link AppWidgetProviderInfoBuilder} */ @RunWith(AndroidJUnit4.class) -@Config(minSdk = JELLY_BEAN) public class AppWidgetProviderInfoBuilderTest { private Context context; private PackageManager packageManager; diff --git a/robolectric/src/test/java/org/robolectric/shadows/AssetManagerCachingTest.java b/robolectric/src/test/java/org/robolectric/shadows/AssetManagerCachingTest.java new file mode 100644 index 000000000..d0171913f --- /dev/null +++ b/robolectric/src/test/java/org/robolectric/shadows/AssetManagerCachingTest.java @@ -0,0 +1,53 @@ +package org.robolectric.shadows; + +import static com.google.common.truth.Truth.assertThat; + +import android.app.Application; +import android.content.res.AssetManager; +import android.content.res.Resources; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import java.util.concurrent.atomic.AtomicLong; +import org.junit.FixMethodOrder; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.MethodSorters; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; +import org.robolectric.shadow.api.Shadow; +import org.robolectric.versioning.AndroidVersions.P; + +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +@RunWith(AndroidJUnit4.class) +@Config(minSdk = P.SDK_INT) +public class AssetManagerCachingTest { + private static final AtomicLong systemNativePtr = new AtomicLong(); + + @Test + public void test1_getAssetManagerPtr() { + AssetManager systemAssetManager = Resources.getSystem().getAssets(); + systemNativePtr.set(getNativePtr(systemAssetManager)); + assertThat(systemNativePtr.get()).isNotEqualTo(0); + } + + @Test + public void test2_verifySamePtr() { + AssetManager systemAssetManager = Resources.getSystem().getAssets(); + long nativePtr = getNativePtr(systemAssetManager); + assertThat(nativePtr).isEqualTo(systemNativePtr.get()); + } + + @Test + public void test3_createApplicationAssets() { + AssetManager systemAssetManager = Resources.getSystem().getAssets(); + Application application = RuntimeEnvironment.getApplication(); + AssetManager assetManager = application.getAssets(); + long nativePtr = getNativePtr(systemAssetManager); + long appNativePtr = getNativePtr(assetManager); + assertThat(nativePtr).isEqualTo(systemNativePtr.get()); + assertThat(nativePtr).isNotEqualTo(appNativePtr); + } + + private static long getNativePtr(AssetManager assetManager) { + return ((ShadowAssetManager) Shadow.extract(assetManager)).getNativePtr(); + } +} diff --git a/robolectric/src/test/java/org/robolectric/shadows/AudioDeviceInfoBuilderTest.java b/robolectric/src/test/java/org/robolectric/shadows/AudioDeviceInfoBuilderTest.java index e1961877a..6e05e7d15 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/AudioDeviceInfoBuilderTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/AudioDeviceInfoBuilderTest.java @@ -1,11 +1,15 @@ package org.robolectric.shadows; -import static android.media.AudioDeviceInfo.TYPE_BLUETOOTH_A2DP; import static android.os.Build.VERSION_CODES.M; +import static android.os.Build.VERSION_CODES.R; +import static android.os.Build.VERSION_CODES.S; import static com.google.common.truth.Truth.assertThat; import android.media.AudioDeviceInfo; +import android.media.AudioFormat; +import android.media.AudioProfile; import androidx.test.ext.junit.runners.AndroidJUnit4; +import com.google.common.collect.ImmutableList; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.annotation.Config; @@ -15,11 +19,45 @@ import org.robolectric.annotation.Config; @Config(minSdk = M) public class AudioDeviceInfoBuilderTest { + @Config(minSdk = M, maxSdk = R) @Test - public void canCreateAudioDeviceInfoWithDesiredType() { + public void buildAudioDeviceInfo_apiM_withDefaultValues_buildsExpectedObject() { + AudioDeviceInfo audioDeviceInfo = AudioDeviceInfoBuilder.newBuilder().build(); + + assertThat(audioDeviceInfo.getType()).isEqualTo(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER); + } + + @Config(minSdk = S) + @Test + public void buildAudioDeviceInfo_apiS_withDefaultValues_buildsExpectedObject() { + AudioDeviceInfo audioDeviceInfo = AudioDeviceInfoBuilder.newBuilder().build(); + + assertThat(audioDeviceInfo.getType()).isEqualTo(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER); + assertThat(audioDeviceInfo.getAudioProfiles()).isEmpty(); + } + + @Config(minSdk = M, maxSdk = R) + @Test + public void buildAudioDeviceInfo_apiM_witSetValues_buildsExpectedObject() { + AudioDeviceInfo audioDeviceInfo = + AudioDeviceInfoBuilder.newBuilder().setType(AudioDeviceInfo.TYPE_BLUETOOTH_A2DP).build(); + + assertThat(audioDeviceInfo.getType()).isEqualTo(AudioDeviceInfo.TYPE_BLUETOOTH_A2DP); + } + + @Test + @Config(minSdk = S) + public void buildAudioDeviceInfo_apiS_witSetValues_buildsExpectedObject() { + ImmutableList<AudioProfile> audioProfiles = + ImmutableList.of( + AudioProfileBuilder.newBuilder().setFormat(AudioFormat.ENCODING_PCM_32BIT).build()); AudioDeviceInfo audioDeviceInfo = - AudioDeviceInfoBuilder.newBuilder().setType(TYPE_BLUETOOTH_A2DP).build(); + AudioDeviceInfoBuilder.newBuilder() + .setType(AudioDeviceInfo.TYPE_BLUETOOTH_A2DP) + .setProfiles(audioProfiles) + .build(); - assertThat(audioDeviceInfo.getType()).isEqualTo(TYPE_BLUETOOTH_A2DP); + assertThat(audioDeviceInfo.getType()).isEqualTo(AudioDeviceInfo.TYPE_BLUETOOTH_A2DP); + assertThat(audioDeviceInfo.getAudioProfiles()).isEqualTo(audioProfiles); } } diff --git a/robolectric/src/test/java/org/robolectric/shadows/AudioProfileBuilderTest.java b/robolectric/src/test/java/org/robolectric/shadows/AudioProfileBuilderTest.java new file mode 100644 index 000000000..30980a532 --- /dev/null +++ b/robolectric/src/test/java/org/robolectric/shadows/AudioProfileBuilderTest.java @@ -0,0 +1,77 @@ +package org.robolectric.shadows; + +import static android.os.Build.VERSION_CODES.S; +import static com.google.common.truth.Truth.assertThat; + +import android.media.AudioFormat; +import android.media.AudioProfile; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.annotation.Config; + +/** Tests for {@link AudioProfileBuilder}. */ +@RunWith(AndroidJUnit4.class) +@Config(minSdk = S) +public class AudioProfileBuilderTest { + + @Test + public void canCreateAudioProfile() { + AudioProfile audioProfile = + AudioProfileBuilder.newBuilder() + .setFormat(AudioFormat.ENCODING_AC3) + .setSamplingRates(new int[] {48_000}) + .setChannelMasks(new int[] {AudioFormat.CHANNEL_OUT_5POINT1}) + // The canonical channel index masks by channel count are given by the formula + // (1 << channelCount) - 1. See: + // https://developer.android.com/reference/android/media/AudioFormat#channelMask + .setChannelIndexMasks(new int[] {0x3F}) + .setEncapsulationType(AudioProfile.AUDIO_ENCAPSULATION_TYPE_NONE) + .build(); + + assertThat(audioProfile.getFormat()).isEqualTo(AudioFormat.ENCODING_AC3); + int[] sampleRates = audioProfile.getSampleRates(); + assertThat(sampleRates).hasLength(1); + assertThat(sampleRates[0]).isEqualTo(48_000); + int[] channelMasks = audioProfile.getChannelMasks(); + assertThat(channelMasks).hasLength(1); + assertThat(channelMasks[0]).isEqualTo(AudioFormat.CHANNEL_OUT_5POINT1); + int[] channelIndexMasks = audioProfile.getChannelIndexMasks(); + assertThat(channelIndexMasks).hasLength(1); + assertThat(channelIndexMasks[0]).isEqualTo(0x3F); + assertThat(audioProfile.getEncapsulationType()) + .isEqualTo(AudioProfile.AUDIO_ENCAPSULATION_TYPE_NONE); + } + + @Test + public void buildAudioProfile_withDefaultValues_buildsExpectedObject() { + AudioProfile audioProfile = AudioProfileBuilder.newBuilder().build(); + + assertThat(audioProfile.getFormat()).isEqualTo(AudioFormat.ENCODING_PCM_16BIT); + assertThat(audioProfile.getSampleRates()).isEqualTo(new int[] {48000}); + assertThat(audioProfile.getChannelMasks()) + .isEqualTo(new int[] {AudioFormat.CHANNEL_OUT_STEREO}); + assertThat(audioProfile.getChannelIndexMasks()).isEqualTo(new int[0]); + assertThat(audioProfile.getEncapsulationType()) + .isEqualTo(AudioProfile.AUDIO_ENCAPSULATION_TYPE_NONE); + } + + @Test + public void buildAudioProfile_withSetValues_buildsExpectedObject() { + AudioProfile audioProfile = + AudioProfileBuilder.newBuilder() + .setFormat(AudioFormat.ENCODING_PCM_32BIT) + .setSamplingRates(new int[] {96000}) + .setChannelMasks(new int[] {AudioFormat.CHANNEL_OUT_QUAD}) + .setChannelIndexMasks(new int[] {0x5}) + .setEncapsulationType(AudioProfile.AUDIO_ENCAPSULATION_TYPE_IEC61937) + .build(); + + assertThat(audioProfile.getFormat()).isEqualTo(AudioFormat.ENCODING_PCM_32BIT); + assertThat(audioProfile.getSampleRates()).isEqualTo(new int[] {96000}); + assertThat(audioProfile.getChannelMasks()).isEqualTo(new int[] {AudioFormat.CHANNEL_OUT_QUAD}); + assertThat(audioProfile.getChannelIndexMasks()).isEqualTo(new int[] {0x5}); + assertThat(audioProfile.getEncapsulationType()) + .isEqualTo(AudioProfile.AUDIO_ENCAPSULATION_TYPE_IEC61937); + } +} diff --git a/robolectric/src/test/java/org/robolectric/shadows/CellIdentityLteBuilderTest.java b/robolectric/src/test/java/org/robolectric/shadows/CellIdentityLteBuilderTest.java index 72887ddb5..0819f2b70 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/CellIdentityLteBuilderTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/CellIdentityLteBuilderTest.java @@ -13,7 +13,6 @@ import org.robolectric.annotation.Config; /** Test for {@link CellIdentityLteBuilder} */ @RunWith(AndroidJUnit4.class) -@Config(minSdk = Build.VERSION_CODES.JELLY_BEAN_MR1) public class CellIdentityLteBuilderTest { private static final String MCC = "310"; @@ -38,7 +37,7 @@ public class CellIdentityLteBuilderTest { } @Test - @Config(minSdk = Build.VERSION_CODES.JELLY_BEAN_MR1, maxSdk = Build.VERSION_CODES.M) + @Config(maxSdk = Build.VERSION_CODES.M) public void build_sdkJtoM() { CellIdentityLte cellIdentity = getCellIdentityLte(); diff --git a/robolectric/src/test/java/org/robolectric/shadows/CellInfoLteBuilderTest.java b/robolectric/src/test/java/org/robolectric/shadows/CellInfoLteBuilderTest.java index f61abad90..55abc5cbb 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/CellInfoLteBuilderTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/CellInfoLteBuilderTest.java @@ -14,7 +14,6 @@ import org.robolectric.annotation.Config; /** Test for {@link CellInfoLteBuilder} */ @RunWith(AndroidJUnit4.class) -@Config(minSdk = Build.VERSION_CODES.JELLY_BEAN_MR1) public class CellInfoLteBuilderTest { private static final boolean REGISTERED = false; @@ -37,7 +36,7 @@ public class CellInfoLteBuilderTest { } @Test - @Config(minSdk = Build.VERSION_CODES.JELLY_BEAN_MR1, maxSdk = Build.VERSION_CODES.N_MR1) + @Config(maxSdk = Build.VERSION_CODES.N_MR1) public void build_sdkJtoN() { CellInfoLte cellInfo = getCellInfoLte(); diff --git a/robolectric/src/test/java/org/robolectric/shadows/CellSignalStrengthLteBuilderTest.java b/robolectric/src/test/java/org/robolectric/shadows/CellSignalStrengthLteBuilderTest.java index cfd3bbeeb..5637b9336 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/CellSignalStrengthLteBuilderTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/CellSignalStrengthLteBuilderTest.java @@ -12,7 +12,6 @@ import org.robolectric.annotation.Config; /** Test for {@link CellSignalStrengthLteBuilder} */ @RunWith(AndroidJUnit4.class) -@Config(minSdk = Build.VERSION_CODES.JELLY_BEAN_MR1) public class CellSignalStrengthLteBuilderTest { // The platform enforces that some of these values are within a certain range - otherwise, it will @@ -35,7 +34,7 @@ public class CellSignalStrengthLteBuilderTest { } @Test - @Config(minSdk = Build.VERSION_CODES.JELLY_BEAN_MR1, maxSdk = Build.VERSION_CODES.N_MR1) + @Config(maxSdk = Build.VERSION_CODES.N_MR1) public void build_sdkJtoN() { CellSignalStrengthLte cellSignalStrength = getCellSignalStrength(); diff --git a/robolectric/src/test/java/org/robolectric/shadows/HealthStatsBuilderTest.java b/robolectric/src/test/java/org/robolectric/shadows/HealthStatsBuilderTest.java new file mode 100644 index 000000000..c860357c8 --- /dev/null +++ b/robolectric/src/test/java/org/robolectric/shadows/HealthStatsBuilderTest.java @@ -0,0 +1,175 @@ +package org.robolectric.shadows; + +import static android.os.Build.VERSION_CODES.N; +import static com.google.common.truth.Truth.assertThat; + +import android.os.health.HealthStats; +import android.os.health.TimerStat; +import android.util.ArrayMap; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import com.google.common.primitives.Ints; +import java.util.HashSet; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.annotation.Config; + +@RunWith(AndroidJUnit4.class) +@Config(minSdk = N) +public class HealthStatsBuilderTest { + + private static final int KEY_1 = 867; + private static final int KEY_2 = 5309; + + private static final TimerStat TIMER_STAT_1 = new TimerStat(42, 64); + private static final TimerStat TIMER_STAT_2 = new TimerStat(5, 7); + + private static final long MEASUREMENT_1 = 13; + private static final long MEASUREMENT_2 = 17; + + private static final String MAP_STRING_1 = "map_string_1"; + private static final String MAP_STRING_2 = "map_string_2"; + + private static final ArrayMap<String, Long> MEASUREMENTS_MAP = new ArrayMap<>(); + private static final long MEASUREMENTS_VALUE_1 = 19; + private static final long MEASUREMENTS_VALUE_2 = 21; + + private static final ArrayMap<String, HealthStats> STATS_MAP = new ArrayMap<>(); + private static final HealthStats STATS_VALUE_1 = + HealthStatsBuilder.newBuilder().setDataType("23").build(); + private static final HealthStats STATS_VALUE_2 = + HealthStatsBuilder.newBuilder().setDataType("27").build(); + + private static final ArrayMap<String, TimerStat> TIMERS_MAP = new ArrayMap<>(); + private static final TimerStat TIMERS_VALUE_1 = new TimerStat(29, 31); + private static final TimerStat TIMERS_VALUE_2 = new TimerStat(37, 41); + + static { + MEASUREMENTS_MAP.put(MAP_STRING_1, MEASUREMENTS_VALUE_1); + MEASUREMENTS_MAP.put(MAP_STRING_2, MEASUREMENTS_VALUE_2); + + STATS_MAP.put(MAP_STRING_1, STATS_VALUE_1); + STATS_MAP.put(MAP_STRING_2, STATS_VALUE_2); + + TIMERS_MAP.put(MAP_STRING_1, TIMERS_VALUE_1); + TIMERS_MAP.put(MAP_STRING_2, TIMERS_VALUE_2); + } + + @Test + public void emptyBuilder_isEmpty() { + HealthStats stats = HealthStatsBuilder.newBuilder().build(); + + assertThat(stats.getDataType()).isNull(); + assertThat(stats.getMeasurementKeyCount()).isEqualTo(0); + assertThat(stats.getMeasurementsKeyCount()).isEqualTo(0); + assertThat(stats.getStatsKeyCount()).isEqualTo(0); + assertThat(stats.getTimerKeyCount()).isEqualTo(0); + assertThat(stats.getTimersKeyCount()).isEqualTo(0); + } + + @Test + public void setEverything_everythingSetsCorrectly() { + HealthStats stats = + HealthStatsBuilder.newBuilder() + .setDataType("arbitrary_data_type") + .addTimerStat(KEY_1, TIMER_STAT_1) + .addTimerStat(KEY_2, TIMER_STAT_2) + .addMeasurement(KEY_1, MEASUREMENT_1) + .addMeasurement(KEY_2, MEASUREMENT_2) + .addStats(KEY_1, STATS_MAP) + .addTimers(KEY_1, TIMERS_MAP) + .addMeasurements(KEY_1, MEASUREMENTS_MAP) + .build(); + + assertThat(stats.getDataType()).isEqualTo("arbitrary_data_type"); + + assertThat(stats.getTimerKeyCount()).isEqualTo(2); + assertThat(stats.hasTimer(KEY_1)).isTrue(); + assertThat(stats.getTimerCount(KEY_1)).isEqualTo(TIMER_STAT_1.getCount()); + assertThat(stats.getTimerTime(KEY_1)).isEqualTo(TIMER_STAT_1.getTime()); + compareTimers(stats.getTimer(KEY_1), TIMER_STAT_1); + assertThat(stats.hasTimer(KEY_2)).isTrue(); + assertThat(stats.getTimerCount(KEY_2)).isEqualTo(TIMER_STAT_2.getCount()); + assertThat(stats.getTimerTime(KEY_2)).isEqualTo(TIMER_STAT_2.getTime()); + compareTimers(stats.getTimer(KEY_2), TIMER_STAT_2); + + assertThat(stats.getMeasurementKeyCount()).isEqualTo(2); + assertThat(stats.hasMeasurement(KEY_1)).isTrue(); + assertThat(stats.getMeasurement(KEY_1)).isEqualTo(MEASUREMENT_1); + assertThat(stats.hasMeasurement(KEY_2)).isTrue(); + assertThat(stats.getMeasurement(KEY_2)).isEqualTo(MEASUREMENT_2); + + assertThat(stats.getMeasurementsKeyCount()).isEqualTo(1); + assertThat(stats.hasMeasurements(KEY_1)).isTrue(); + assertThat(stats.getMeasurements(KEY_1)).isEqualTo(MEASUREMENTS_MAP); + + assertThat(stats.getStatsKeyCount()).isEqualTo(1); + assertThat(stats.hasStats(KEY_1)).isTrue(); + assertThat(stats.getStats(KEY_1)).isEqualTo(STATS_MAP); + + assertThat(stats.getTimersKeyCount()).isEqualTo(1); + assertThat(stats.hasTimers(KEY_1)).isTrue(); + assertThat(stats.getTimers(KEY_1)).isEqualTo(TIMERS_MAP); + } + + @Test + public void healthStats_keysAreIterable() { + HealthStats stats = + HealthStatsBuilder.newBuilder() + .setDataType("arbitrary_data_type") + .addMeasurement(KEY_1, MEASUREMENT_1) + .addMeasurement(KEY_2, MEASUREMENT_2) + .build(); + + assertThat(stats.getMeasurementKeyCount()).isEqualTo(2); + for (int i = 0; i < stats.getMeasurementKeyCount(); i++) { + int key = stats.getMeasurementKeyAt(i); + switch (key) { + case KEY_1: + assertThat(stats.getMeasurement(key)).isEqualTo(MEASUREMENT_1); + break; + case KEY_2: + assertThat(stats.getMeasurement(key)).isEqualTo(MEASUREMENT_2); + break; + default: + throw new IllegalStateException("Unexpected HealthStats key"); + } + } + } + + @Test + public void toSortedIntArray_resultIsSorted() { + HashSet<Integer> set = new HashSet<>(); + set.add(1); + set.add(2); + set.add(3); + ReverseIteratingSet reversedSet = new ReverseIteratingSet(set); + + int[] setAsSortedArray = HealthStatsBuilder.toSortedIntArray(set); + int[] reversedSetAsSortedArray = HealthStatsBuilder.toSortedIntArray(reversedSet); + + assertThat(Ints.asList(setAsSortedArray)).isInStrictOrder(); + assertThat(Ints.asList(reversedSetAsSortedArray)).isInStrictOrder(); + } + + private final void compareTimers(TimerStat timer1, TimerStat timer2) { + assertThat(timer1.getCount()).isEqualTo(timer2.getCount()); + assertThat(timer1.getTime()).isEqualTo(timer2.getTime()); + } + + // Identical to HashSet<Integer>, except that the result of toArray() is reversed. + private static final class ReverseIteratingSet extends HashSet<Integer> { + public ReverseIteratingSet(HashSet<Integer> c) { + super(c); + } + + @Override + public Object[] toArray() { + Object[] forward = super.toArray(); + Object[] backward = new Object[forward.length]; + for (int i = 0; i < forward.length; i++) { + backward[i] = forward[forward.length - 1 - i]; + } + return backward; + } + } +} diff --git a/robolectric/src/test/java/org/robolectric/shadows/NetworkSpecifierFactoryTest.java b/robolectric/src/test/java/org/robolectric/shadows/NetworkSpecifierFactoryTest.java new file mode 100644 index 000000000..319d3763c --- /dev/null +++ b/robolectric/src/test/java/org/robolectric/shadows/NetworkSpecifierFactoryTest.java @@ -0,0 +1,26 @@ +package org.robolectric.shadows; + +import static com.google.common.truth.Truth.assertThat; + +import android.net.StringNetworkSpecifier; +import android.os.Build; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.annotation.Config; + +@RunWith(AndroidJUnit4.class) +@Config(minSdk = Build.VERSION_CODES.O) +public final class NetworkSpecifierFactoryTest { + + private static final String SUBSCRIPTION_ID_SPECIFIER = "1"; + private static final String ETHERNET_SPECIFIER = "eth0"; + + @Test + public void newStringNetworkSpecifier() { + assertThat(NetworkSpecifierFactory.newStringNetworkSpecifier(SUBSCRIPTION_ID_SPECIFIER)) + .isInstanceOf(StringNetworkSpecifier.class); + assertThat(NetworkSpecifierFactory.newStringNetworkSpecifier(ETHERNET_SPECIFIER)) + .isInstanceOf(StringNetworkSpecifier.class); + } +} diff --git a/robolectric/src/test/java/org/robolectric/shadows/ResourceHelperTest.java b/robolectric/src/test/java/org/robolectric/shadows/ResourceHelperTest.java index 9edaac17a..66762daca 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ResourceHelperTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ResourceHelperTest.java @@ -10,10 +10,10 @@ import org.robolectric.annotation.Config; /** Unit tests for {@link ResourceHelper}. */ @RunWith(AndroidJUnit4.class) +@Config(sdk = Config.NEWEST_SDK) public class ResourceHelperTest { @Test - @Config(sdk = Config.NEWEST_SDK) public void parseFloatAttribute() { TypedValue out = new TypedValue(); ResourceHelper.parseFloatAttribute(null, "0.16", out, false); @@ -23,4 +23,42 @@ public class ResourceHelperTest { ResourceHelper.parseFloatAttribute(null, ".16", out, false); assertThat(out.getFloat()).isEqualTo(0.16f); } + + @Test + public void parseFloatAttribute_lengthEquals1000_parseSucceed() { + TypedValue out = new TypedValue(); + boolean parseResult = + ResourceHelper.parseFloatAttribute( + null, generateTestFloatAttribute("0.16", 1000), out, false); + assertThat(parseResult).isTrue(); + assertThat(out.getFloat()).isEqualTo(0.16f); + } + + @Test + public void parseFloatAttribute_lengthLargerThan1000_returnsFalse() { + TypedValue out = new TypedValue(); + boolean parseResult = + ResourceHelper.parseFloatAttribute( + null, generateTestFloatAttribute("0.17", 1001), out, false); + assertThat(parseResult).isFalse(); + } + + @Test + public void parseFloatAttribute_lengthLessThan1000_parseSucceed() { + TypedValue out = new TypedValue(); + boolean parseResult = + ResourceHelper.parseFloatAttribute( + null, generateTestFloatAttribute("0.18", 999), out, false); + assertThat(parseResult).isTrue(); + assertThat(out.getFloat()).isEqualTo(0.18f); + } + + private static String generateTestFloatAttribute(String prefixAttribute, int length) { + StringBuilder builder = new StringBuilder(prefixAttribute); + int usedLength = builder.length(); + for (int i = 0; i < length - usedLength; i++) { + builder.append("0"); + } + return builder.toString(); + } } diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowAccessibilityManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowAccessibilityManagerTest.java index a3c471788..27f3fabd2 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowAccessibilityManagerTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowAccessibilityManagerTest.java @@ -1,7 +1,6 @@ package org.robolectric.shadows; import static android.content.Context.ACCESSIBILITY_SERVICE; -import static android.os.Build.VERSION_CODES.KITKAT; import static android.os.Build.VERSION_CODES.O; import static android.os.Build.VERSION_CODES.O_MR1; import static android.os.Build.VERSION_CODES.Q; @@ -189,7 +188,6 @@ public class ShadowAccessibilityManagerTest { assertThat(accessibilityManager.removeAccessibilityStateChangeListener(null)).isFalse(); } - @Config(minSdk = KITKAT) @Test public void setTouchExplorationEnabled_invokesCallbacks() { AtomicBoolean enabled = new AtomicBoolean(false); diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowAccessibilityNodeInfoTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowAccessibilityNodeInfoTest.java index 14c91f404..74bf81c9c 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowAccessibilityNodeInfoTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowAccessibilityNodeInfoTest.java @@ -1,11 +1,12 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.KITKAT; import static android.os.Build.VERSION_CODES.LOLLIPOP; import static android.os.Build.VERSION_CODES.N; import static android.os.Build.VERSION_CODES.O; import static android.os.Build.VERSION_CODES.P; import static android.os.Build.VERSION_CODES.Q; +import static android.os.Build.VERSION_CODES.R; +import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE; import static com.google.common.truth.Truth.assertThat; import static org.robolectric.Shadows.shadowOf; @@ -202,7 +203,6 @@ public class ShadowAccessibilityNodeInfoTest { assertThat(node).isEqualTo(clone); } - @Config(minSdk = KITKAT) @Test public void shouldCloneExtrasCorrectly() { node.getExtras().putString("key", "value"); @@ -257,6 +257,30 @@ public class ShadowAccessibilityNodeInfoTest { } @Test + @Config(minSdk = R) + public void clone_preservesStateDescription() { + String description = "description"; + AccessibilityNodeInfo node = AccessibilityNodeInfo.obtain(); + node.setStateDescription(description); + + AccessibilityNodeInfo clone = AccessibilityNodeInfo.obtain(node); + + assertThat(clone.getStateDescription().toString()).isEqualTo(description); + } + + @Test + @Config(minSdk = UPSIDE_DOWN_CAKE) + public void clone_preservesContainerTitle() { + String title = "container title"; + AccessibilityNodeInfo node = AccessibilityNodeInfo.obtain(); + node.setContainerTitle(title); + + AccessibilityNodeInfo clone = AccessibilityNodeInfo.obtain(node); + + assertThat(clone.getContainerTitle().toString()).isEqualTo(title); + } + + @Test public void testGetBoundsInScreen() { AccessibilityNodeInfo root = AccessibilityNodeInfo.obtain(); Rect expected = new Rect(0, 0, 100, 100); diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowAccountManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowAccountManagerTest.java index 4f9f0b2ef..ffbc9c760 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowAccountManagerTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowAccountManagerTest.java @@ -1,10 +1,10 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2; import static android.os.Build.VERSION_CODES.LOLLIPOP; import static android.os.Build.VERSION_CODES.LOLLIPOP_MR1; import static android.os.Build.VERSION_CODES.O; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; import static org.junit.Assert.fail; import static org.robolectric.Shadows.shadowOf; import static org.robolectric.shadows.ShadowLooper.shadowMainLooper; @@ -1051,7 +1051,6 @@ public class ShadowAccountManagerTest { } @Test - @Config(minSdk = JELLY_BEAN_MR2) public void getAccountsByTypeForPackage() { Account[] accountsByTypeForPackage = am.getAccountsByTypeForPackage(null, "org.somepackage"); @@ -1103,14 +1102,33 @@ public class ShadowAccountManagerTest { shadowOf(am).setAuthenticationErrorOnNextResponse(true); - try { - am.getAccountsByTypeAndFeatures(null, null, null, null).getResult(); - fail("should have thrown"); - } catch (AuthenticatorException expected) { - // Expected - } + assertThrows( + AuthenticatorException.class, + () -> + am.getAccountsByTypeAndFeatures( + /* type= */ null, + /* features= */ null, + /* callback= */ null, + /* handler= */ null) + .getResult()); + + assertThat( + am.getAccountsByTypeAndFeatures( + /* type= */ null, + /* features= */ null, + /* callback= */ null, + /* handler= */ null) + .getResult()) + .isEmpty(); + } + + @Test + public void setSecurityErrorOnNextGetAccountsByTypeCall() { + shadowOf(am).setSecurityErrorOnNextGetAccountsByTypeCall(true); + + assertThrows(SecurityException.class, () -> am.getAccountsByType(null)); - am.getAccountsByTypeAndFeatures(null, null, null, null).getResult(); + assertThat(am.getAccountsByType(null)).isEmpty(); } private static class TestAccountManagerCallback<T> implements AccountManagerCallback<T> { diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowActivityManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowActivityManagerTest.java index 420123969..e2212f443 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowActivityManagerTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowActivityManagerTest.java @@ -4,8 +4,6 @@ import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREG import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE; import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE; import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; -import static android.os.Build.VERSION_CODES.KITKAT; import static android.os.Build.VERSION_CODES.LOLLIPOP; import static android.os.Build.VERSION_CODES.M; import static android.os.Build.VERSION_CODES.O; @@ -161,7 +159,6 @@ public class ShadowActivityManagerTest { } @Test - @Config(minSdk = KITKAT) public void setIsLowRamDevice() { shadowActivityManager.setIsLowRamDevice(true); assertThat(activityManager.isLowRamDevice()).isTrue(); @@ -198,7 +195,6 @@ public class ShadowActivityManagerTest { } @Test - @Config(minSdk = JELLY_BEAN_MR1) public void switchUser() { shadowOf(application).setSystemService(Context.USER_SERVICE, userManager); shadowOf(userManager).addUser(10, "secondary_user", 0); @@ -215,13 +211,11 @@ public class ShadowActivityManagerTest { } @Test - @Config(minSdk = JELLY_BEAN_MR1) public void getCurrentUser_default_returnZero() { assertThat(ActivityManager.getCurrentUser()).isEqualTo(0); } @Test - @Config(minSdk = JELLY_BEAN_MR1) public void getCurrentUser_nonDefault_returnValueSet() { shadowOf(application).setSystemService(Context.USER_SERVICE, userManager); shadowOf(userManager).addUser(10, "secondary_user", 0); @@ -424,13 +418,11 @@ public class ShadowActivityManagerTest { assertThat(activityManager.getDeviceConfigurationInfo()).isEqualTo(configurationInfo); } - @Config(minSdk = KITKAT) @Test public void isApplicationUserDataCleared_returnsDefaultFalse() { assertThat(shadowActivityManager.isApplicationUserDataCleared()).isFalse(); } - @Config(minSdk = KITKAT) @Test public void isApplicationUserDataCleared_returnsTrue() { activityManager.clearApplicationUserData(); diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowActivityTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowActivityTest.java index 08b2a2060..387f0cfcc 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowActivityTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowActivityTest.java @@ -1,8 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.JELLY_BEAN; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; -import static android.os.Build.VERSION_CODES.KITKAT; import static android.os.Build.VERSION_CODES.LOLLIPOP; import static android.os.Build.VERSION_CODES.M; import static android.os.Build.VERSION_CODES.N; @@ -10,6 +7,7 @@ import static android.os.Build.VERSION_CODES.O; import static android.os.Build.VERSION_CODES.O_MR1; import static android.os.Build.VERSION_CODES.Q; import static android.os.Build.VERSION_CODES.S; +import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE; import static android.os.Looper.getMainLooper; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; @@ -50,6 +48,7 @@ import android.content.SharedPreferences; import android.content.pm.ActivityInfo; import android.database.Cursor; import android.database.MatrixCursor; +import android.graphics.Color; import android.media.AudioManager; import android.net.Uri; import android.os.Build.VERSION_CODES; @@ -178,7 +177,6 @@ public class ShadowActivityTest { } @Test - @Config(minSdk = JELLY_BEAN_MR1) public void shouldReportDestroyedStatus() { try (ActivityController<DialogCreatingActivity> controller = Robolectric.buildActivity(DialogCreatingActivity.class)) { @@ -514,7 +512,6 @@ public class ShadowActivityTest { } @Test - @Config(minSdk = JELLY_BEAN) public void shouldCallFinishOnFinishAffinity() { Activity activity = new Activity(); activity.finishAffinity(); @@ -1018,6 +1015,110 @@ public class ShadowActivityTest { } @Test + @Config(minSdk = UPSIDE_DOWN_CAKE) + public void getOverriddenActivityTransitionOpen_withoutBackgroundColor() { + try (ActivityController<Activity> controller = buildActivity(Activity.class).setup()) { + Activity activity = controller.get(); + activity.overrideActivityTransition(Activity.OVERRIDE_TRANSITION_OPEN, 15, 2); + ShadowActivity.OverriddenActivityTransition overriddenActivityTransition = + shadowOf(activity).getOverriddenActivityTransition(Activity.OVERRIDE_TRANSITION_OPEN); + + assertThat(overriddenActivityTransition).isNotNull(); + assertThat(overriddenActivityTransition.enterAnim).isEqualTo(15); + assertThat(overriddenActivityTransition.exitAnim).isEqualTo(2); + assertThat(overriddenActivityTransition.backgroundColor).isEqualTo(Color.TRANSPARENT); + } + } + + @Test + @Config(minSdk = UPSIDE_DOWN_CAKE) + public void getOverriddenActivityTransitionClose_withoutBackgroundColor() { + try (ActivityController<Activity> controller = buildActivity(Activity.class).setup()) { + Activity activity = controller.get(); + ShadowActivity shadowActivity = shadowOf(activity); + activity.overrideActivityTransition(Activity.OVERRIDE_TRANSITION_CLOSE, 15, 2); + ShadowActivity.OverriddenActivityTransition overriddenActivityTransition = + shadowActivity.getOverriddenActivityTransition(Activity.OVERRIDE_TRANSITION_CLOSE); + + assertThat(overriddenActivityTransition).isNotNull(); + assertThat(overriddenActivityTransition.enterAnim).isEqualTo(15); + assertThat(overriddenActivityTransition.exitAnim).isEqualTo(2); + assertThat(overriddenActivityTransition.backgroundColor).isEqualTo(Color.TRANSPARENT); + } + } + + @Test + @Config(minSdk = UPSIDE_DOWN_CAKE) + public void getOverriddenActivityTransitionOpen_withBackgroundColor() { + try (ActivityController<Activity> controller = buildActivity(Activity.class).setup()) { + Activity activity = controller.get(); + activity.overrideActivityTransition(Activity.OVERRIDE_TRANSITION_OPEN, 33, 12, Color.RED); + ShadowActivity.OverriddenActivityTransition overriddenActivityTransition = + shadowOf(activity).getOverriddenActivityTransition(Activity.OVERRIDE_TRANSITION_OPEN); + + assertThat(overriddenActivityTransition).isNotNull(); + assertThat(overriddenActivityTransition.enterAnim).isEqualTo(33); + assertThat(overriddenActivityTransition.exitAnim).isEqualTo(12); + assertThat(overriddenActivityTransition.backgroundColor).isEqualTo(Color.RED); + } + } + + @Test + @Config(minSdk = UPSIDE_DOWN_CAKE) + public void getOverriddenActivityTransitionClose_withBackgroundColor() { + try (ActivityController<Activity> controller = buildActivity(Activity.class).setup()) { + Activity activity = controller.get(); + ShadowActivity shadowActivity = shadowOf(activity); + activity.overrideActivityTransition(Activity.OVERRIDE_TRANSITION_CLOSE, 33, 12, Color.RED); + ShadowActivity.OverriddenActivityTransition overriddenActivityTransition = + shadowActivity.getOverriddenActivityTransition(Activity.OVERRIDE_TRANSITION_CLOSE); + + assertThat(overriddenActivityTransition).isNotNull(); + assertThat(overriddenActivityTransition.enterAnim).isEqualTo(33); + assertThat(overriddenActivityTransition.exitAnim).isEqualTo(12); + assertThat(overriddenActivityTransition.backgroundColor).isEqualTo(Color.RED); + } + } + + @Test + @Config(minSdk = UPSIDE_DOWN_CAKE) + public void getOverriddenActivityTransition_invalidType() { + try (ActivityController<Activity> controller = buildActivity(Activity.class).setup()) { + Activity activity = controller.get(); + activity.overrideActivityTransition(Activity.OVERRIDE_TRANSITION_CLOSE, 33, 12, Color.RED); + ShadowActivity.OverriddenActivityTransition overriddenActivityTransition = + shadowOf(activity).getOverriddenActivityTransition(-1); + assertThat(overriddenActivityTransition).isNull(); + } + } + + @Test + @Config(minSdk = UPSIDE_DOWN_CAKE) + public void getOverriddenActivityTransition_beforeOverridingOrAfterClearing() { + try (ActivityController<Activity> controller = buildActivity(Activity.class).setup()) { + Activity activity = controller.get(); + ShadowActivity shadowActivity = shadowOf(activity); + + assertThat(shadowActivity.getOverriddenActivityTransition(Activity.OVERRIDE_TRANSITION_OPEN)) + .isNull(); + + assertThat(shadowActivity.getOverriddenActivityTransition(Activity.OVERRIDE_TRANSITION_CLOSE)) + .isNull(); + + activity.overrideActivityTransition(Activity.OVERRIDE_TRANSITION_OPEN, 12, 33); + activity.overrideActivityTransition(Activity.OVERRIDE_TRANSITION_CLOSE, 33, 12); + activity.clearOverrideActivityTransition(Activity.OVERRIDE_TRANSITION_OPEN); + activity.clearOverrideActivityTransition(Activity.OVERRIDE_TRANSITION_CLOSE); + + assertThat(shadowActivity.getOverriddenActivityTransition(Activity.OVERRIDE_TRANSITION_OPEN)) + .isNull(); + + assertThat(shadowActivity.getOverriddenActivityTransition(Activity.OVERRIDE_TRANSITION_CLOSE)) + .isNull(); + } + } + + @Test public void getActionBar_shouldWorkIfActivityHasAnAppropriateTheme() { try (ActivityController<ActionBarThemedActivity> controller = Robolectric.buildActivity(ActionBarThemedActivity.class)) { @@ -1302,7 +1403,6 @@ public class ShadowActivityTest { } @Test - @Config(minSdk = KITKAT) public void reportFullyDrawn_reported() { Activity activity = Robolectric.setupActivity(Activity.class); activity.reportFullyDrawn(); diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowAlarmManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowAlarmManagerTest.java index 7a6ab89af..a2ae9222b 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowAlarmManagerTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowAlarmManagerTest.java @@ -154,7 +154,6 @@ public class ShadowAlarmManagerTest { verify(onFire, times(2)).run(); } - @Config(minSdk = VERSION_CODES.KITKAT) @Test public void setWindow_pendingIntent() { Runnable onFire = mock(Runnable.class); @@ -222,7 +221,6 @@ public class ShadowAlarmManagerTest { verify(onFire).onAlarm(); } - @Config(minSdk = VERSION_CODES.KITKAT) @Test public void setExact_pendingIntent() { Runnable onFire = mock(Runnable.class); @@ -282,7 +280,6 @@ public class ShadowAlarmManagerTest { } } - @Config(minSdk = VERSION_CODES.KITKAT) @Test public void set_pendingIntent_workSource() { Runnable onFire = mock(Runnable.class); diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowAmbientDisplayConfigurationTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowAmbientDisplayConfigurationTest.java new file mode 100644 index 000000000..20632d2d7 --- /dev/null +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowAmbientDisplayConfigurationTest.java @@ -0,0 +1,215 @@ +package org.robolectric.shadows; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.Context; +import android.os.Build.VERSION_CODES; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; +import org.robolectric.util.ReflectionHelpers; +import org.robolectric.util.ReflectionHelpers.ClassParameter; + +/** Tests for {@link ShadowAmbientDisplayConfiguration}. */ +@RunWith(RobolectricTestRunner.class) +@Config(minSdk = VERSION_CODES.R) +public final class ShadowAmbientDisplayConfigurationTest { + + private Object instanceAmbientDisplayConfiguration; + + @Before + public void setUp() throws Exception { + instanceAmbientDisplayConfiguration = + ReflectionHelpers.callConstructor( + Class.forName("android.hardware.display.AmbientDisplayConfiguration"), + ClassParameter.from(Context.class, RuntimeEnvironment.getApplication())); + } + + @Test + public void ambientDisplayComponent_shouldReturnNullByDefault() throws Exception { + String component = + ReflectionHelpers.callInstanceMethod( + instanceAmbientDisplayConfiguration, "ambientDisplayComponent"); + assertThat(component).isNull(); + } + + @Test + public void ambientDisplayComponent_whenValidDozeComponentIsSet_shouldReturnDozeComponent() + throws Exception { + ShadowAmbientDisplayConfiguration.setDozeComponent( + "com.google.android.aod/.TestDozeAlwaysOnDisplay"); + + String component = + ReflectionHelpers.callInstanceMethod( + instanceAmbientDisplayConfiguration, "ambientDisplayComponent"); + assertThat(component).isEqualTo("com.google.android.aod/.TestDozeAlwaysOnDisplay"); + } + + @Test + public void ambientDisplayAvailable_whenValidDozeComponentIsSet_shouldReturnTrue() + throws Exception { + ShadowAmbientDisplayConfiguration.setDozeComponent( + "com.google.android.aod/.TestDozeAlwaysOnDisplay"); + + assertThat( + (Boolean) + ReflectionHelpers.callInstanceMethod( + instanceAmbientDisplayConfiguration, "ambientDisplayAvailable")) + .isTrue(); + } + + @Test + public void ambientDisplayAvailable_whenInvalidDozeComponentIsSet_shouldReturnFalse() + throws Exception { + ShadowAmbientDisplayConfiguration.setDozeComponent(""); + + assertThat( + (Boolean) + ReflectionHelpers.callInstanceMethod( + instanceAmbientDisplayConfiguration, "ambientDisplayAvailable")) + .isFalse(); + } + + @Test + public void + alwaysOnDisplayAvailable_whenOverrideDozeAlwaysOnDisplayAvailableStateToTrue_shouldReturnTrue() + throws Exception { + ShadowAmbientDisplayConfiguration.setDozeAlwaysOnDisplayAvailable( + /* dozeAlwaysOnDisplayAvailable= */ true); + + assertThat( + (Boolean) + ReflectionHelpers.callInstanceMethod( + instanceAmbientDisplayConfiguration, "alwaysOnDisplayAvailable")) + .isTrue(); + } + + @Test + public void + alwaysOnDisplayAvailable_whenOverrideDozeAlwaysOnDisplayAvailableStateToFalse_shouldReturnFalse() + throws Exception { + ShadowAmbientDisplayConfiguration.setDozeAlwaysOnDisplayAvailable( + /* dozeAlwaysOnDisplayAvailable= */ false); + + assertThat( + (Boolean) + ReflectionHelpers.callInstanceMethod( + instanceAmbientDisplayConfiguration, "alwaysOnDisplayAvailable")) + .isFalse(); + } + + @Test + public void + alwaysOnDisplayDebuggingEnabled_whenBothDebuggableAndAodSystemPropertyAreSet_shouldReturnTrue() + throws Exception { + ShadowSystemProperties.override("ro.debuggable", "1"); + ShadowSystemProperties.override("debug.doze.aod", "true"); + + assertThat( + (Boolean) + ReflectionHelpers.callInstanceMethod( + instanceAmbientDisplayConfiguration, "alwaysOnDisplayDebuggingEnabled")) + .isTrue(); + } + + @Test + public void + alwaysOnDisplayDebuggingEnabled_whenOnlyDebuggableSystemPropertyIsSet_shouldReturnFalse() + throws Exception { + ShadowSystemProperties.override("ro.debuggable", "1"); + + assertThat( + (Boolean) + ReflectionHelpers.callInstanceMethod( + instanceAmbientDisplayConfiguration, "alwaysOnDisplayDebuggingEnabled")) + .isFalse(); + } + + @Test + public void alwaysOnDisplayDebuggingEnabled_whenOnlyAodSystemPropertyIsSet_shouldReturnFalse() + throws Exception { + ShadowSystemProperties.override("debug.doze.aod", "true"); + + assertThat( + (Boolean) + ReflectionHelpers.callInstanceMethod( + instanceAmbientDisplayConfiguration, "alwaysOnDisplayDebuggingEnabled")) + .isFalse(); + } + + @Test + public void + alwaysOnAvailable_whenValidSystemPropertiesAndValidDozeComponentAreSet_shouldReturnTrue() + throws Exception { + ShadowSystemProperties.override("ro.debuggable", "1"); + ShadowSystemProperties.override("debug.doze.aod", "true"); + + ShadowAmbientDisplayConfiguration.setDozeAlwaysOnDisplayAvailable( + /* dozeAlwaysOnDisplayAvailable= */ false); + ShadowAmbientDisplayConfiguration.setDozeComponent( + "com.google.android.aod/.TestDozeAlwaysOnDisplay"); + + assertThat( + (Boolean) + ReflectionHelpers.callInstanceMethod( + instanceAmbientDisplayConfiguration, "alwaysOnAvailable")) + .isTrue(); + } + + @Test + public void + alwaysOnAvailable_whenOverrideDozeAlwaysOnDisplayAvailableStateAndValidDozeComponentAreSet_shouldReturnTrue() + throws Exception { + ShadowSystemProperties.override("ro.debuggable", "0"); + ShadowSystemProperties.override("debug.doze.aod", "false"); + + ShadowAmbientDisplayConfiguration.setDozeAlwaysOnDisplayAvailable( + /* dozeAlwaysOnDisplayAvailable= */ true); + ShadowAmbientDisplayConfiguration.setDozeComponent( + "com.google.android.aod/.TestDozeAlwaysOnDisplay"); + + assertThat( + (Boolean) + ReflectionHelpers.callInstanceMethod( + instanceAmbientDisplayConfiguration, "alwaysOnAvailable")) + .isTrue(); + } + + @Test + public void alwaysOnAvailable_whenInvalidDozeComponentIsSet_shouldReturnFalse() throws Exception { + ShadowSystemProperties.override("ro.debuggable", "1"); + ShadowSystemProperties.override("debug.doze.aod", "true"); + + ShadowAmbientDisplayConfiguration.setDozeAlwaysOnDisplayAvailable( + /* dozeAlwaysOnDisplayAvailable= */ true); + ShadowAmbientDisplayConfiguration.setDozeComponent(""); + + assertThat( + (Boolean) + ReflectionHelpers.callInstanceMethod( + instanceAmbientDisplayConfiguration, "alwaysOnAvailable")) + .isFalse(); + } + + @Test + public void + alwaysOnAvailable_whenInvalidSystemPropertiesAreSetAndOverrideDozeAlwaysOnDisplayAvailableStateToFalse_shouldReturnFalse() + throws Exception { + ShadowSystemProperties.override("ro.debuggable", "0"); + ShadowSystemProperties.override("debug.doze.aod", "false"); + + ShadowAmbientDisplayConfiguration.setDozeAlwaysOnDisplayAvailable( + /* dozeAlwaysOnDisplayAvailable= */ false); + ShadowAmbientDisplayConfiguration.setDozeComponent( + "com.google.android.aod/.TestDozeAlwaysOnDisplay"); + + assertThat( + (Boolean) + ReflectionHelpers.callInstanceMethod( + instanceAmbientDisplayConfiguration, "alwaysOnAvailable")) + .isFalse(); + } +} diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowAppOpsManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowAppOpsManagerTest.java index d94c27568..168e03fa1 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowAppOpsManagerTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowAppOpsManagerTest.java @@ -15,7 +15,6 @@ import static android.app.AppOpsManager.OP_FINE_LOCATION; import static android.app.AppOpsManager.OP_GPS; import static android.app.AppOpsManager.OP_SEND_SMS; import static android.app.AppOpsManager.OP_VIBRATE; -import static android.os.Build.VERSION_CODES.KITKAT; import static android.os.Build.VERSION_CODES.Q; import static android.os.Build.VERSION_CODES.R; import static com.google.common.truth.Truth.assertThat; @@ -53,7 +52,6 @@ import org.robolectric.util.ReflectionHelpers.ClassParameter; /** Unit tests for {@link ShadowAppOpsManager}. */ @RunWith(AndroidJUnit4.class) -@Config(minSdk = KITKAT) public class ShadowAppOpsManagerTest { private static final String PACKAGE_NAME1 = "com.company1.pkg1"; @@ -94,14 +92,12 @@ public class ShadowAppOpsManagerTest { } @Test - @Config(minSdk = VERSION_CODES.KITKAT) - public void checkOpNoThrow_noModeSet_atLeastKitKat_shouldReturnModeAllowed() { + public void checkOpNoThrow_noModeSet_shouldReturnModeAllowed() { assertThat(appOps.checkOpNoThrow(/* op= */ 2, UID_1, PACKAGE_NAME1)).isEqualTo(MODE_ALLOWED); } @Test - @Config(minSdk = VERSION_CODES.KITKAT) - public void setMode_withModeDefault_atLeastKitKat_checkOpNoThrow_shouldReturnModeDefault() { + public void setMode_withModeDefault_checkOpNoThrow_shouldReturnModeDefault() { appOps.setMode(/* op= */ 2, UID_1, PACKAGE_NAME1, MODE_DEFAULT); assertThat(appOps.checkOpNoThrow(/* op= */ 2, UID_1, PACKAGE_NAME1)).isEqualTo(MODE_DEFAULT); } @@ -217,7 +213,6 @@ public class ShadowAppOpsManagerTest { } @Test - @Config(minSdk = VERSION_CODES.KITKAT) public void startStopWatchingMode() { OnOpChangedListener callback = mock(OnOpChangedListener.class); appOps.startWatchingMode(OPSTR_FINE_LOCATION, PACKAGE_NAME1, callback); @@ -429,7 +424,7 @@ public class ShadowAppOpsManagerTest { } @Test - @Config(minSdk = VERSION_CODES.KITKAT, maxSdk = VERSION_CODES.Q) + @Config(maxSdk = VERSION_CODES.Q) public void startOpNoThrow_setModeAllowed() { appOps.setMode(OP_FINE_LOCATION, UID_1, PACKAGE_NAME1, MODE_ALLOWED); @@ -438,7 +433,7 @@ public class ShadowAppOpsManagerTest { } @Test - @Config(minSdk = VERSION_CODES.KITKAT, maxSdk = VERSION_CODES.Q) + @Config(maxSdk = VERSION_CODES.Q) public void startOpNoThrow_setModeErrored() { appOps.setMode(OP_FINE_LOCATION, UID_1, PACKAGE_NAME1, MODE_ERRORED); @@ -513,7 +508,6 @@ public class ShadowAppOpsManagerTest { // check passes without exception } - @Config(minSdk = KITKAT) @Test public void getPackageForOps_setNone_getNull() { int[] intNull = null; @@ -521,7 +515,6 @@ public class ShadowAppOpsManagerTest { assertThat(packageOps).isNull(); } - @Config(minSdk = KITKAT) @Test public void getPackageForOps_setOne_getOne() { String packageName = "com.android.package"; @@ -533,7 +526,6 @@ public class ShadowAppOpsManagerTest { assertThat(containsPackageOpPair(packageOps, packageName, 0, MODE_ALLOWED)).isTrue(); } - @Config(minSdk = KITKAT) @Test public void getPackageForOps_setMultiple_getMultiple() { String packageName1 = "com.android.package"; @@ -557,7 +549,6 @@ public class ShadowAppOpsManagerTest { assertThat(containsPackageOpPair(packageOps, packageName2, 0, MODE_ALLOWED)).isTrue(); } - @Config(minSdk = KITKAT) @Test public void getPackageForOps_setMultiple_onlyGetThoseAskedFor() { String packageName1 = "com.android.package"; diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowAppWidgetManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowAppWidgetManagerTest.java index 3774edb89..10e11912c 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowAppWidgetManagerTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowAppWidgetManagerTest.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; import static android.os.Build.VERSION_CODES.L; import static android.os.Build.VERSION_CODES.O; import static android.os.Looper.getMainLooper; @@ -168,7 +167,6 @@ public class ShadowAppWidgetManagerTest { } @Test - @Config(minSdk = JELLY_BEAN_MR1) public void bindAppWidgetIdIfAllowed_shouldSetOptionsBundle() { ComponentName provider = new ComponentName("A", "B"); Bundle options = new Bundle(); diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowApplicationTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowApplicationTest.java index dd367590c..83e57227e 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowApplicationTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowApplicationTest.java @@ -1,7 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; -import static android.os.Build.VERSION_CODES.KITKAT; import static android.os.Build.VERSION_CODES.LOLLIPOP; import static android.os.Build.VERSION_CODES.LOLLIPOP_MR1; import static android.os.Build.VERSION_CODES.M; @@ -128,7 +126,6 @@ public class ShadowApplicationTest { } @Test - @Config(minSdk = JELLY_BEAN_MR1) public void shouldProvideServicesIntroducedInJellyBeanMr1() throws Exception { assertThat(context.getSystemService(Context.DISPLAY_SERVICE)) .isInstanceOf(android.hardware.display.DisplayManager.class); @@ -136,7 +133,6 @@ public class ShadowApplicationTest { } @Test - @Config(minSdk = KITKAT) public void shouldProvideServicesIntroducedInKitKat() throws Exception { assertThat(context.getSystemService(Context.PRINT_SERVICE)).isInstanceOf(PrintManager.class); assertThat(context.getSystemService(Context.CAPTIONING_SERVICE)) @@ -187,7 +183,6 @@ public class ShadowApplicationTest { } @Test - @Config(minSdk = KITKAT) public void shouldCorrectlyInstantiatedAccessibilityService() throws Exception { AccessibilityManager accessibilityManager = (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE); diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowAudioManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowAudioManagerTest.java index 51757ceec..f35e38783 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowAudioManagerTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowAudioManagerTest.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.KITKAT; import static android.os.Build.VERSION_CODES.LOLLIPOP; import static android.os.Build.VERSION_CODES.M; import static android.os.Build.VERSION_CODES.N; @@ -10,8 +9,8 @@ import static android.os.Build.VERSION_CODES.Q; import static android.os.Build.VERSION_CODES.R; import static android.os.Build.VERSION_CODES.S; import static android.os.Build.VERSION_CODES.TIRAMISU; +import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE; import static com.google.common.truth.Truth.assertThat; -import static com.google.common.truth.Truth8.assertThat; import static com.google.common.util.concurrent.MoreExecutors.directExecutor; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; @@ -27,6 +26,7 @@ import android.media.AudioFormat; import android.media.AudioManager; import android.media.AudioManager.OnModeChangedListener; import android.media.AudioPlaybackConfiguration; +import android.media.AudioProfile; import android.media.AudioRecordingConfiguration; import android.media.AudioSystem; import android.media.MediaRecorder.AudioSource; @@ -173,6 +173,7 @@ public class ShadowAudioManagerTest { case AudioManager.STREAM_RING: case AudioManager.STREAM_SYSTEM: case AudioManager.STREAM_VOICE_CALL: + case AudioManager.STREAM_ACCESSIBILITY: assertThat(audioManager.getStreamMaxVolume(stream)) .isEqualTo(ShadowAudioManager.DEFAULT_MAX_VOLUME); break; @@ -250,6 +251,7 @@ public class ShadowAudioManagerTest { case AudioManager.STREAM_RING: case AudioManager.STREAM_SYSTEM: case AudioManager.STREAM_VOICE_CALL: + case AudioManager.STREAM_ACCESSIBILITY: assertThat(audioManager.getStreamVolume(stream)) .isEqualTo(ShadowAudioManager.DEFAULT_MAX_VOLUME); break; @@ -377,6 +379,24 @@ public class ShadowAudioManagerTest { } @Test + public void lockMode_locked_modeRemainsTheSame() { + shadowOf(audioManager).lockMode(true); + + audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION); + + assertThat(audioManager.getMode()).isEqualTo(AudioManager.MODE_NORMAL); + } + + @Test + public void lockMode_notLocked_modeIsSet() { + shadowOf(audioManager).lockMode(false); + + audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION); + + assertThat(audioManager.getMode()).isEqualTo(AudioManager.MODE_IN_COMMUNICATION); + } + + @Test public void isSpeakerphoneOn_shouldReturnSpeakerphoneState() { assertThat(audioManager.isSpeakerphoneOn()).isFalse(); audioManager.setSpeakerphoneOn(true); @@ -462,6 +482,49 @@ public class ShadowAudioManagerTest { } @Test + @Config(minSdk = TIRAMISU) + public void getAudioDevicesForAttributes_returnsEmptyListByDefault() { + AudioAttributes movieAttribute = + new AudioAttributes.Builder().setContentType(AudioAttributes.CONTENT_TYPE_MOVIE).build(); + + assertThat(audioManager.getAudioDevicesForAttributes(movieAttribute)).isEmpty(); + } + + @Test + @Config(minSdk = TIRAMISU) + public void setAudioDevicesForAttributes_updatesAudioDevicesForAttributes() { + AudioAttributes movieAttribute = + new AudioAttributes.Builder().setContentType(AudioAttributes.CONTENT_TYPE_MOVIE).build(); + ImmutableList<AudioDeviceInfo> newDevices = + ImmutableList.of( + AudioDeviceInfoBuilder.newBuilder() + .setType(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER) + .build()); + + shadowOf(audioManager).setAudioDevicesForAttributes(movieAttribute, newDevices); + + assertThat(audioManager.getAudioDevicesForAttributes(movieAttribute)).isEqualTo(newDevices); + } + + @Test + @Config(minSdk = TIRAMISU) + public void setAudioDevicesForAttributes_returnsEmptyListForOtherAttributes() { + AudioAttributes movieAttribute = + new AudioAttributes.Builder().setContentType(AudioAttributes.CONTENT_TYPE_MOVIE).build(); + AudioAttributes otherAttribute = + new AudioAttributes.Builder().setContentType(AudioAttributes.CONTENT_TYPE_MUSIC).build(); + ImmutableList<AudioDeviceInfo> newDevices = + ImmutableList.of( + AudioDeviceInfoBuilder.newBuilder() + .setType(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER) + .build()); + + shadowOf(audioManager).setAudioDevicesForAttributes(movieAttribute, newDevices); + + assertThat(audioManager.getAudioDevicesForAttributes(otherAttribute)).isEmpty(); + } + + @Test @Config(minSdk = M) public void registerAudioDeviceCallback_availableDevices_onAudioDevicesAddedCallback() throws Exception { @@ -848,7 +911,29 @@ public class ShadowAudioManagerTest { @Config(minSdk = S) public void setCommunicationDevice_updatesCommunicationDevice() throws Exception { AudioDeviceInfo scoDevice = createAudioDevice(DEVICE_OUT_BLUETOOTH_SCO); - shadowOf(audioManager).setCommunicationDevice(scoDevice); + audioManager.setCommunicationDevice(scoDevice); + + assertThat(audioManager.getCommunicationDevice()).isEqualTo(scoDevice); + } + + @Test + @Config(minSdk = S) + public void lockCommunicationDevice_locked_deviceIsNotSet() throws Exception { + AudioDeviceInfo scoDevice = createAudioDevice(DEVICE_OUT_BLUETOOTH_SCO); + shadowOf(audioManager).lockCommunicationDevice(true); + + audioManager.setCommunicationDevice(scoDevice); + + assertThat(audioManager.getCommunicationDevice()).isNull(); + } + + @Test + @Config(minSdk = S) + public void lockCommunicationDevice_notLocked_deviceIsSet() throws Exception { + AudioDeviceInfo scoDevice = createAudioDevice(DEVICE_OUT_BLUETOOTH_SCO); + shadowOf(audioManager).lockCommunicationDevice(false); + + audioManager.setCommunicationDevice(scoDevice); assertThat(audioManager.getCommunicationDevice()).isEqualTo(scoDevice); } @@ -857,10 +942,10 @@ public class ShadowAudioManagerTest { @Config(minSdk = S) public void clearCommunicationDevice_clearsCommunicationDevice() throws Exception { AudioDeviceInfo scoDevice = createAudioDevice(DEVICE_OUT_BLUETOOTH_SCO); - shadowOf(audioManager).setCommunicationDevice(scoDevice); + audioManager.setCommunicationDevice(scoDevice); assertThat(audioManager.getCommunicationDevice()).isEqualTo(scoDevice); - shadowOf(audioManager).clearCommunicationDevice(); + audioManager.clearCommunicationDevice(); assertThat(audioManager.getCommunicationDevice()).isNull(); } @@ -1351,7 +1436,6 @@ public class ShadowAudioManagerTest { } @Test - @Config(minSdk = KITKAT) public void dispatchMediaKeyEvent_recordsEvent() { KeyEvent keyEvent = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PLAY); @@ -1361,7 +1445,6 @@ public class ShadowAudioManagerTest { } @Test - @Config(minSdk = KITKAT) public void clearDispatchedMediaKeyEvents_clearsDispatchedEvents() { audioManager.dispatchMediaKeyEvent( new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PLAY)); @@ -1371,6 +1454,93 @@ public class ShadowAudioManagerTest { assertThat(shadowOf(audioManager).getDispatchedMediaKeyEvents()).isEmpty(); } + @Test + @Config(minSdk = UPSIDE_DOWN_CAKE) + public void setHotwordStreamSupportedWithLookbackAudio_updatesIsHotwordStreamSupported() { + assertThat(shadowOf(audioManager).isHotwordStreamSupported(/* lookbackAudio= */ true)) + .isFalse(); + assertThat(shadowOf(audioManager).isHotwordStreamSupported(/* lookbackAudio= */ false)) + .isFalse(); + + shadowOf(audioManager) + .setHotwordStreamSupported(/* lookbackAudio= */ true, /* isSupported= */ true); + + // isHotwordStreamSupported with lookbackAudio=true is set. + assertThat(shadowOf(audioManager).isHotwordStreamSupported(/* lookbackAudio= */ true)).isTrue(); + // isHotwordStreamSupported with lookbackAudio=false is not set. + assertThat(shadowOf(audioManager).isHotwordStreamSupported(/* lookbackAudio= */ false)) + .isFalse(); + } + + @Test + @Config(minSdk = UPSIDE_DOWN_CAKE) + public void setHotwordStreamSupportedWithoutLookbackAudio_updatesIsHotwordStreamSupported() { + assertThat(shadowOf(audioManager).isHotwordStreamSupported(/* lookbackAudio= */ false)) + .isFalse(); + assertThat(shadowOf(audioManager).isHotwordStreamSupported(/* lookbackAudio= */ true)) + .isFalse(); + + shadowOf(audioManager) + .setHotwordStreamSupported(/* lookbackAudio= */ false, /* isSupported= */ true); + + // isHotwordStreamSupported with lookbackAudio=false is set. + assertThat(shadowOf(audioManager).isHotwordStreamSupported(/* lookbackAudio= */ false)) + .isTrue(); + // isHotwordStreamSupported with lookbackAudio=true is not set. + assertThat(shadowOf(audioManager).isHotwordStreamSupported(/* lookbackAudio= */ true)) + .isFalse(); + } + + @Test + @Config(minSdk = TIRAMISU) + public void getDirectProfilesForAttributes_returnsEmptyListByDefault() { + AudioAttributes audioAttributes = + new AudioAttributes.Builder() + .setUsage(AudioAttributes.USAGE_MEDIA) + .setContentType(AudioAttributes.CONTENT_TYPE_MOVIE) + .build(); + + assertThat(shadowOf(audioManager).getDirectProfilesForAttributes(audioAttributes)).isEmpty(); + } + + @Test + @Config(minSdk = TIRAMISU) + public void + addAndRemoveOutputDeviceWithDirectProfiles_updatesDirectProfilesForAttributes_notifiesCallback() { + AudioDeviceCallback callback = mock(AudioDeviceCallback.class); + audioManager.registerAudioDeviceCallback(callback, /* handler= */ null); + verify(callback).onAudioDevicesAdded(new AudioDeviceInfo[] {}); // initial registration + ImmutableList<AudioProfile> expectedProfiles = + ImmutableList.of( + AudioProfileBuilder.newBuilder() + .setFormat(AudioFormat.ENCODING_AC3) + .setSamplingRates(new int[] {48_000}) + .setChannelMasks(new int[] {AudioFormat.CHANNEL_OUT_5POINT1}) + .setEncapsulationType(AudioProfile.AUDIO_ENCAPSULATION_TYPE_NONE) + .build()); + AudioDeviceInfo outputDevice = + AudioDeviceInfoBuilder.newBuilder() + .setType(AudioDeviceInfo.TYPE_HDMI) + .setProfiles(expectedProfiles) + .build(); + AudioAttributes audioAttributes = + new AudioAttributes.Builder() + .setUsage(AudioAttributes.USAGE_MEDIA) + .setContentType(AudioAttributes.CONTENT_TYPE_MOVIE) + .build(); + + shadowOf(audioManager).addOutputDeviceWithDirectProfiles(outputDevice); + + assertThat(shadowOf(audioManager).getDirectProfilesForAttributes(audioAttributes)) + .isEqualTo(expectedProfiles); + verify(callback).onAudioDevicesAdded(new AudioDeviceInfo[] {outputDevice}); + + shadowOf(audioManager).removeOutputDeviceWithDirectProfiles(outputDevice); + + assertThat(shadowOf(audioManager).getDirectProfilesForAttributes(audioAttributes)).isEmpty(); + verify(callback).onAudioDevicesAdded(new AudioDeviceInfo[] {outputDevice}); + } + private static AudioDeviceInfo createAudioDevice(int type) throws ReflectiveOperationException { AudioDeviceInfo info = Shadow.newInstanceOf(AudioDeviceInfo.class); Field portField = AudioDeviceInfo.class.getDeclaredField("mPort"); diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowAudioTrackTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowAudioTrackTest.java index e8869bd49..10dc3e33a 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowAudioTrackTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowAudioTrackTest.java @@ -5,6 +5,7 @@ import static android.media.AudioTrack.WRITE_BLOCKING; import static android.media.AudioTrack.WRITE_NON_BLOCKING; import static android.os.Build.VERSION_CODES.LOLLIPOP; import static android.os.Build.VERSION_CODES.M; +import static android.os.Build.VERSION_CODES.N; import static android.os.Build.VERSION_CODES.Q; import static android.os.Build.VERSION_CODES.R; import static android.os.Build.VERSION_CODES.S; @@ -13,13 +14,20 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertThrows; import android.media.AudioAttributes; +import android.media.AudioDeviceInfo; import android.media.AudioFormat; import android.media.AudioManager; +import android.media.AudioRouting; +import android.media.AudioRouting.OnRoutingChangedListener; import android.media.AudioSystem; import android.media.AudioTrack; import android.media.PlaybackParams; +import android.os.Handler; +import android.os.Looper; import androidx.test.ext.junit.runners.AndroidJUnit4; import java.nio.ByteBuffer; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.annotation.Config; @@ -533,6 +541,117 @@ public class ShadowAudioTrackTest implements ShadowAudioTrack.OnAudioDataWritten .isEqualTo(AudioTrack.ERROR_DEAD_OBJECT); } + @Test + @Config(minSdk = N) + public void getRoutedDevice_withoutSetRoutedDevice_returnsNull() { + AudioTrack audioTrack = new AudioTrack.Builder().build(); + + assertThat(audioTrack.getRoutedDevice()).isNull(); + } + + @Test + @Config(minSdk = N) + public void getRoutedDevice_afterSetRoutedDevice_returnsRoutedDevice() { + AudioTrack audioTrack = new AudioTrack.Builder().build(); + AudioDeviceInfo audioDeviceInfo = + AudioDeviceInfoBuilder.newBuilder().setType(AudioDeviceInfo.TYPE_HDMI).build(); + + ShadowAudioTrack.setRoutedDevice(audioDeviceInfo); + + assertThat(audioTrack.getRoutedDevice()).isEqualTo(audioDeviceInfo); + } + + @Test + @Config(minSdk = N) + public void addOnRoutingChangedListener_beforeSetRoutedDevice_listenerCalledOnceDeviceSet() { + AudioTrack audioTrack = new AudioTrack.Builder().build(); + AudioDeviceInfo audioDeviceInfo = + AudioDeviceInfoBuilder.newBuilder().setType(AudioDeviceInfo.TYPE_HDMI).build(); + AtomicReference<AudioRouting> listenerRouting = new AtomicReference<>(); + + audioTrack.addOnRoutingChangedListener( + (OnRoutingChangedListener) listenerRouting::set, new Handler(Looper.getMainLooper())); + ShadowLooper.idleMainLooper(); + + assertThat(listenerRouting.get()).isNull(); + + ShadowAudioTrack.setRoutedDevice(audioDeviceInfo); + ShadowLooper.idleMainLooper(); + + assertThat(listenerRouting.get()).isEqualTo(audioTrack); + assertThat(listenerRouting.get().getRoutedDevice()).isEqualTo(audioDeviceInfo); + } + + @Test + @Config(minSdk = N) + public void + addOnRoutingChangedListener_afterSetRoutedDevice_listenerCalledImmediatelyAndWhenNewDeviceSet() { + AudioTrack audioTrack = new AudioTrack.Builder().build(); + AudioDeviceInfo audioDeviceInfo1 = + AudioDeviceInfoBuilder.newBuilder().setType(AudioDeviceInfo.TYPE_HDMI).build(); + AudioDeviceInfo audioDeviceInfo2 = + AudioDeviceInfoBuilder.newBuilder().setType(AudioDeviceInfo.TYPE_BLUETOOTH_A2DP).build(); + AtomicReference<AudioRouting> listenerRouting = new AtomicReference<>(); + + ShadowAudioTrack.setRoutedDevice(audioDeviceInfo1); + audioTrack.addOnRoutingChangedListener( + (OnRoutingChangedListener) listenerRouting::set, new Handler(Looper.getMainLooper())); + ShadowLooper.idleMainLooper(); + + assertThat(listenerRouting.get()).isEqualTo(audioTrack); + assertThat(listenerRouting.get().getRoutedDevice()).isEqualTo(audioDeviceInfo1); + + ShadowAudioTrack.setRoutedDevice(audioDeviceInfo2); + ShadowLooper.idleMainLooper(); + + assertThat(listenerRouting.get()).isEqualTo(audioTrack); + assertThat(listenerRouting.get().getRoutedDevice()).isEqualTo(audioDeviceInfo2); + } + + @Test + @Config(minSdk = N) + public void setRoutedDevice_toNull_listenerCalled() { + AudioTrack audioTrack = new AudioTrack.Builder().build(); + AudioDeviceInfo audioDeviceInfo = + AudioDeviceInfoBuilder.newBuilder().setType(AudioDeviceInfo.TYPE_HDMI).build(); + AtomicReference<AudioRouting> listenerRouting = new AtomicReference<>(); + ShadowAudioTrack.setRoutedDevice(audioDeviceInfo); + audioTrack.addOnRoutingChangedListener( + (OnRoutingChangedListener) listenerRouting::set, new Handler(Looper.getMainLooper())); + ShadowLooper.idleMainLooper(); + + ShadowAudioTrack.setRoutedDevice(null); + ShadowLooper.idleMainLooper(); + + assertThat(listenerRouting.get()).isEqualTo(audioTrack); + assertThat(listenerRouting.get().getRoutedDevice()).isEqualTo(null); + } + + @Test + @Config(minSdk = N) + public void removeOnRoutingChangedListener_noFurtherUpdatesSent() { + AudioTrack audioTrack = new AudioTrack.Builder().build(); + AudioDeviceInfo audioDeviceInfo1 = + AudioDeviceInfoBuilder.newBuilder().setType(AudioDeviceInfo.TYPE_HDMI).build(); + AudioDeviceInfo audioDeviceInfo2 = + AudioDeviceInfoBuilder.newBuilder().setType(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER).build(); + ShadowAudioTrack.setRoutedDevice(audioDeviceInfo1); + AtomicInteger listenerCounter1 = new AtomicInteger(); + AtomicInteger listenerCounter2 = new AtomicInteger(); + OnRoutingChangedListener listener1 = routing -> listenerCounter1.incrementAndGet(); + OnRoutingChangedListener listener2 = routing -> listenerCounter2.incrementAndGet(); + audioTrack.addOnRoutingChangedListener(listener1, new Handler(Looper.getMainLooper())); + audioTrack.addOnRoutingChangedListener(listener2, new Handler(Looper.getMainLooper())); + ShadowLooper.idleMainLooper(); + + audioTrack.removeOnRoutingChangedListener(listener1); + ShadowAudioTrack.setRoutedDevice(audioDeviceInfo2); + ShadowLooper.idleMainLooper(); + + assertThat(listenerCounter1.get()).isEqualTo(1); + assertThat(listenerCounter2.get()).isEqualTo(2); + } + @Override @Config(minSdk = Q) public void onAudioDataWritten( diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowBackupManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowBackupManagerTest.java index b7c8cfa9d..259173fbb 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowBackupManagerTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowBackupManagerTest.java @@ -2,31 +2,30 @@ package org.robolectric.shadows; import static android.os.Build.VERSION_CODES.LOLLIPOP; import static android.os.Build.VERSION_CODES.M; +import static android.os.Build.VERSION_CODES.Q; import static com.google.common.truth.Truth.assertThat; -import static org.junit.Assert.fail; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.verify; +import static org.junit.Assert.assertThrows; import static org.robolectric.Shadows.shadowOf; import static org.robolectric.shadows.ShadowLooper.shadowMainLooper; import android.app.Application; import android.app.backup.BackupManager; +import android.app.backup.BackupTransport; import android.app.backup.RestoreObserver; import android.app.backup.RestoreSession; import android.app.backup.RestoreSet; +import androidx.annotation.Nullable; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; import com.google.common.truth.Correspondence; -import java.util.Arrays; -import java.util.Collections; +import java.util.ArrayList; +import java.util.List; import java.util.Objects; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.mockito.MockitoAnnotations; import org.robolectric.annotation.Config; import org.robolectric.util.ReflectionHelpers; @@ -34,20 +33,19 @@ import org.robolectric.util.ReflectionHelpers; @RunWith(AndroidJUnit4.class) public class ShadowBackupManagerTest { private BackupManager backupManager; - @Mock private TestRestoreObserver restoreObserver; + private TestRestoreObserver restoreObserver; @Before public void setUp() { - MockitoAnnotations.initMocks(this); - shadowMainLooper().pause(); shadowOf((Application) ApplicationProvider.getApplicationContext()) .grantPermissions(android.Manifest.permission.BACKUP); backupManager = new BackupManager(ApplicationProvider.getApplicationContext()); + restoreObserver = new TestRestoreObserver(); - shadowOf(backupManager).addAvailableRestoreSets(123L, Arrays.asList("foo.bar", "bar.baz")); - shadowOf(backupManager).addAvailableRestoreSets(456L, Collections.singletonList("hello.world")); + shadowOf(backupManager).addAvailableRestoreSets(123L, ImmutableList.of("foo.bar", "bar.baz")); + shadowOf(backupManager).addAvailableRestoreSets(456L, ImmutableList.of("hello.world")); } @Test @@ -96,12 +94,8 @@ public class ShadowBackupManagerTest { public void isBackupEnabled_noPermission_shouldThrowSecurityException() { shadowOf((Application) ApplicationProvider.getApplicationContext()) .denyPermissions(android.Manifest.permission.BACKUP); - try { - backupManager.isBackupEnabled(); - fail("SecurityException should be thrown"); - } catch (SecurityException e) { - // pass - } + + assertThrows(SecurityException.class, () -> backupManager.isBackupEnabled()); } @Test @@ -111,10 +105,8 @@ public class ShadowBackupManagerTest { assertThat(result).isEqualTo(BackupManager.SUCCESS); shadowMainLooper().idle(); - ArgumentCaptor<RestoreSet[]> restoreSetArg = ArgumentCaptor.forClass(RestoreSet[].class); - verify(restoreObserver).restoreSetsAvailable(restoreSetArg.capture()); - RestoreSet[] restoreSets = restoreSetArg.getValue(); + RestoreSet[] restoreSets = restoreObserver.getRestoreSets(); assertThat(restoreSets).hasLength(2); assertThat(restoreSets) .asList() @@ -130,12 +122,14 @@ public class ShadowBackupManagerTest { assertThat(result).isEqualTo(BackupManager.SUCCESS); shadowMainLooper().idle(); - verify(restoreObserver).restoreStarting(eq(2)); - verify(restoreObserver).restoreFinished(eq(BackupManager.SUCCESS)); - + assertThat(restoreObserver.getRestoreStartingNumPackages()).isEqualTo(2); + assertThat(restoreObserver.getRestoreFinishedResult()).isEqualTo(BackupManager.SUCCESS); assertThat(shadowOf(backupManager).getPackageRestoreToken("foo.bar")).isEqualTo(123L); + assertThat(shadowOf(backupManager).getPackageRestoreCount("foo.bar")).isEqualTo(1); assertThat(shadowOf(backupManager).getPackageRestoreToken("bar.baz")).isEqualTo(123L); + assertThat(shadowOf(backupManager).getPackageRestoreCount("bar.baz")).isEqualTo(1); assertThat(shadowOf(backupManager).getPackageRestoreToken("hello.world")).isEqualTo(0L); + assertThat(shadowOf(backupManager).getPackageRestoreCount("hello.world")).isEqualTo(0); } @Test @@ -146,11 +140,90 @@ public class ShadowBackupManagerTest { assertThat(result).isEqualTo(BackupManager.SUCCESS); shadowMainLooper().idle(); - verify(restoreObserver).restoreStarting(eq(1)); - verify(restoreObserver).restoreFinished(eq(BackupManager.SUCCESS)); + assertThat(restoreObserver.getRestoreStartingNumPackages()).isEqualTo(1); + assertThat(restoreObserver.getRestoreFinishedResult()).isEqualTo(BackupManager.SUCCESS); assertThat(shadowOf(backupManager).getPackageRestoreToken("foo.bar")).isEqualTo(0L); + assertThat(shadowOf(backupManager).getPackageRestoreCount("foo.bar")).isEqualTo(0); assertThat(shadowOf(backupManager).getPackageRestoreToken("bar.baz")).isEqualTo(123L); + assertThat(shadowOf(backupManager).getPackageRestoreCount("bar.baz")).isEqualTo(1); + } + + @Test + @Config(minSdk = Q) + public void restorePackages_multipleRestores_countsRestores() { + RestoreSession restoreSession = backupManager.beginRestoreSession(); + + int result = restoreSession.restorePackages(123L, restoreObserver, ImmutableSet.of("foo.bar")); + assertThat(result).isEqualTo(BackupManager.SUCCESS); + restoreSession.endRestoreSession(); + + restoreSession = backupManager.beginRestoreSession(); + result = restoreSession.restorePackages(123L, restoreObserver, ImmutableSet.of("foo.bar")); + assertThat(result).isEqualTo(BackupManager.SUCCESS); + + assertThat(shadowOf(backupManager).getPackageRestoreToken("foo.bar")).isEqualTo(123L); + assertThat(shadowOf(backupManager).getPackageRestoreCount("foo.bar")).isEqualTo(2); + } + + @Test + @Config(minSdk = Q) + public void restorePackages_transportError_returnsErrorInRestoreFinishedCallback() { + shadowOf(backupManager) + .addAvailableRestoreSets( + 789L, ImmutableList.of("foo.bar"), BackupTransport.TRANSPORT_ERROR); + RestoreSession restoreSession = backupManager.beginRestoreSession(); + + int result = restoreSession.restorePackages(789L, restoreObserver, ImmutableSet.of("foo.bar")); + assertThat(result).isEqualTo(BackupManager.SUCCESS); + shadowMainLooper().idle(); + + assertThat(restoreObserver.getRestoreFinishedResult()) + .isEqualTo(BackupTransport.TRANSPORT_ERROR); + } + + @Test + @Config(minSdk = Q) + public void restorePackages_multipleRestoreSets_returnsEachResult() { + // Add two restore set for token 789 + shadowOf(backupManager) + .addAvailableRestoreSets( + 789L, ImmutableList.of("foo.bar"), BackupTransport.TRANSPORT_ERROR); + shadowOf(backupManager) + .addAvailableRestoreSets(789L, ImmutableList.of("foo.bar"), BackupManager.SUCCESS); + RestoreSession restoreSession = backupManager.beginRestoreSession(); + + List<Integer> restoreResults = new ArrayList<>(); + for (int attempt = 1; attempt <= 3; attempt++) { + if (restoreSession != null) { + restoreSession.endRestoreSession(); + } + restoreObserver = new TestRestoreObserver(); + restoreSession = backupManager.beginRestoreSession(); + int result = + restoreSession.restorePackages(789L, restoreObserver, ImmutableSet.of("foo.bar")); + assertThat(result).isEqualTo(BackupManager.SUCCESS); + shadowMainLooper().idle(); + restoreResults.add(restoreObserver.getRestoreFinishedResult()); + } + + // The first attempt is expected to fail, any subsequent attempt is expected to succeed + assertThat(restoreResults) + .containsExactly( + BackupTransport.TRANSPORT_ERROR, BackupManager.SUCCESS, BackupManager.SUCCESS); + } + + @Test + @Config(minSdk = Q) + public void restorePackages_unknownToken_returnsError() { + RestoreSession restoreSession = backupManager.beginRestoreSession(); + + int result = restoreSession.restorePackages(1L, restoreObserver, ImmutableSet.of("foo.bar")); + assertThat(result).isEqualTo(BackupManager.SUCCESS); + shadowMainLooper().idle(); + + assertThat(restoreObserver.getRestoreStartingNumPackages()).isEqualTo(0); + assertThat(restoreObserver.getRestoreFinishedResult()).isEqualTo(-1); } @Test @@ -159,9 +232,10 @@ public class ShadowBackupManagerTest { restoreSession.restoreSome(123L, restoreObserver, new String[0]); assertThat(shadowOf(backupManager).getPackageRestoreToken("bar.baz")).isEqualTo(0L); + assertThat(shadowOf(backupManager).getPackageRestoreCount("bar.baz")).isEqualTo(0); restoreSession.endRestoreSession(); shadowMainLooper().idle(); - Mockito.reset(restoreObserver); + restoreObserver = new TestRestoreObserver(); restoreSession = backupManager.beginRestoreSession(); int result = restoreSession.restorePackage("bar.baz", restoreObserver); @@ -169,9 +243,10 @@ public class ShadowBackupManagerTest { assertThat(result).isEqualTo(BackupManager.SUCCESS); shadowMainLooper().idle(); - verify(restoreObserver).restoreStarting(eq(1)); - verify(restoreObserver).restoreFinished(eq(BackupManager.SUCCESS)); + assertThat(restoreObserver.getRestoreStartingNumPackages()).isEqualTo(1); + assertThat(restoreObserver.getRestoreFinishedResult()).isEqualTo(BackupManager.SUCCESS); assertThat(shadowOf(backupManager).getPackageRestoreToken("bar.baz")).isEqualTo(123L); + assertThat(shadowOf(backupManager).getPackageRestoreCount("bar.baz")).isEqualTo(1); } @Test @@ -195,15 +270,14 @@ public class ShadowBackupManagerTest { long defaultVal = 0L; long restoreToken = 123L; RestoreSession restoreSession = backupManager.beginRestoreSession(); + int result = restoreSession.restoreSome(restoreToken, restoreObserver, new String[] {"bar.baz"}); - assertThat(result).isEqualTo(BackupManager.SUCCESS); - shadowMainLooper().idle(); - verify(restoreObserver).restoreStarting(eq(1)); - verify(restoreObserver).restoreFinished(eq(BackupManager.SUCCESS)); + assertThat(restoreObserver.getRestoreStartingNumPackages()).isEqualTo(1); + assertThat(restoreObserver.getRestoreFinishedResult()).isEqualTo(BackupManager.SUCCESS); assertThat(backupManager.getAvailableRestoreToken("foo.bar")).isEqualTo(defaultVal); assertThat(backupManager.getAvailableRestoreToken("bar.baz")).isEqualTo(restoreToken); } @@ -215,5 +289,39 @@ public class ShadowBackupManagerTest { "field \"" + fieldName + "\" matches"); } - private static class TestRestoreObserver extends RestoreObserver {} + private static class TestRestoreObserver extends RestoreObserver { + @Nullable private RestoreSet[] restoreSets; + @Nullable private Integer restoreStartingNumPackages; + @Nullable private Integer restoreFinishedResult; + + @Override + public void restoreSetsAvailable(RestoreSet[] restoreSets) { + this.restoreSets = restoreSets; + } + + @Override + public void restoreStarting(int numPackages) { + this.restoreStartingNumPackages = numPackages; + } + + @Override + public void restoreFinished(int result) { + this.restoreFinishedResult = result; + } + + @Nullable + public RestoreSet[] getRestoreSets() { + return restoreSets; + } + + @Nullable + public Integer getRestoreStartingNumPackages() { + return restoreStartingNumPackages; + } + + @Nullable + public Integer getRestoreFinishedResult() { + return restoreFinishedResult; + } + } } diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowBasicTagTechnologyTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowBasicTagTechnologyTest.java index d3918cc38..313066b77 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowBasicTagTechnologyTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowBasicTagTechnologyTest.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.KITKAT; import static com.google.common.truth.Truth.assertThat; import android.nfc.tech.IsoDep; @@ -8,11 +7,9 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.robolectric.annotation.Config; /** Unit tests for {@link ShadowBasicTagTechnology}. */ @RunWith(AndroidJUnit4.class) -@Config(minSdk = KITKAT) public final class ShadowBasicTagTechnologyTest { // IsoDep extends BasicTagTechnology, which is otherwise a package-protected class. diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowBinderTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowBinderTest.java index b02e06691..0acc2b87a 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowBinderTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowBinderTest.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; import static android.os.Build.VERSION_CODES.Q; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertThrows; @@ -100,7 +99,6 @@ public class ShadowBinderTest { } @Test - @Config(minSdk = JELLY_BEAN_MR1) public void testSetCallingUserHandle() { UserHandle newUser = shadowOf(userManager).addUser(10, "secondary_user", 0); ShadowBinder.setCallingUserHandle(newUser); @@ -136,7 +134,6 @@ public class ShadowBinderTest { } @Test - @Config(minSdk = JELLY_BEAN_MR1) public void testGetCallingUserHandleShouldUseThatOfProcessByDefault() { assertThat(Binder.getCallingUserHandle()).isEqualTo(android.os.Process.myUserHandle()); } @@ -160,7 +157,6 @@ public class ShadowBinderTest { } @Test - @Config(minSdk = JELLY_BEAN_MR1) public void testResetUpdatesCallingUserHandle() { UserHandle newUser = shadowOf(userManager).addUser(10, "secondary_user", 0); ShadowBinder.setCallingUserHandle(newUser); diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowBitmapTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowBitmapTest.java index dfe336b6c..face8d842 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowBitmapTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowBitmapTest.java @@ -1,7 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; -import static android.os.Build.VERSION_CODES.KITKAT; import static android.os.Build.VERSION_CODES.O; import static android.os.Build.VERSION_CODES.S; import static com.google.common.truth.Truth.assertThat; @@ -113,7 +111,6 @@ public class ShadowBitmapTest { } @Test - @Config(minSdk = JELLY_BEAN_MR1) public void hasMipmap() { Bitmap bitmap = Bitmap.createBitmap(100, 200, Bitmap.Config.ARGB_8888); assertThat(bitmap.hasMipMap()).isFalse(); @@ -122,7 +119,6 @@ public class ShadowBitmapTest { } @Test - @Config(minSdk = KITKAT) public void getAllocationByteCount() { Bitmap bitmap = Bitmap.createBitmap(100, 200, Bitmap.Config.ARGB_8888); assertThat(bitmap.getAllocationByteCount()).isGreaterThan(0); @@ -178,7 +174,6 @@ public class ShadowBitmapTest { } @Test - @Config(minSdk = JELLY_BEAN_MR1) public void shouldCreateMutableBitmapWithDisplayMetrics() { final DisplayMetrics metrics = new DisplayMetrics(); metrics.densityDpi = 1000; @@ -263,7 +258,6 @@ public class ShadowBitmapTest { } @Test(expected = NullPointerException.class) - @Config(minSdk = JELLY_BEAN_MR1) public void byteCountIsAccurate() { Bitmap b1 = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888); assertThat(b1.getByteCount()).isEqualTo(400); @@ -276,7 +270,6 @@ public class ShadowBitmapTest { } @Test - @Config(minSdk = JELLY_BEAN_MR1) public void shouldSetDensity() { final Bitmap bitmap = Bitmap.createBitmap(new DisplayMetrics(), 100, 100, Bitmap.Config.ARGB_8888); bitmap.setDensity(1000); @@ -646,7 +639,6 @@ public class ShadowBitmapTest { original.reconfigure(100, 100, Bitmap.Config.ARGB_8888); } - @Config(minSdk = Build.VERSION_CODES.KITKAT) @Test public void isPremultiplied_argb888_defaultsTrue() { Bitmap original = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888); @@ -654,7 +646,6 @@ public class ShadowBitmapTest { assertThat(original.isPremultiplied()).isTrue(); } - @Config(minSdk = Build.VERSION_CODES.KITKAT) @Test public void isPremultiplied_argb888_noAlpha_defaultsFalse() { Bitmap original = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888); @@ -663,7 +654,6 @@ public class ShadowBitmapTest { assertThat(original.isPremultiplied()).isFalse(); } - @Config(minSdk = Build.VERSION_CODES.KITKAT) @Test public void isPremultiplied_rgb565_defaultsFalse() { Bitmap original = Bitmap.createBitmap(100, 100, Bitmap.Config.RGB_565); @@ -671,7 +661,6 @@ public class ShadowBitmapTest { assertThat(original.isPremultiplied()).isFalse(); } - @Config(minSdk = Build.VERSION_CODES.KITKAT) @Test public void setPremultiplied_argb888_isFalse() { Bitmap original = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888); diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothA2dpTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothA2dpTest.java index c5995991f..b1c195b20 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothA2dpTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothA2dpTest.java @@ -1,13 +1,18 @@ package org.robolectric.shadows; import static android.os.Build.VERSION_CODES.P; +import static android.os.Build.VERSION_CODES.R; import static android.os.Build.VERSION_CODES.S; +import static android.os.Build.VERSION_CODES.TIRAMISU; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; import static org.robolectric.Shadows.shadowOf; import android.app.Application; import android.bluetooth.BluetoothA2dp; import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothCodecConfig; +import android.bluetooth.BluetoothCodecStatus; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothProfile; import android.content.Intent; @@ -168,4 +173,90 @@ public class ShadowBluetoothA2dpTest { assertThat((BluetoothDevice) intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)) .isEqualTo(connectedBluetoothDevice); } + + @Test + @Config(minSdk = TIRAMISU) + public void getCodecStatus_returnValueFromSetter() { + BluetoothCodecStatus status = + new BluetoothCodecStatus.Builder() + .setCodecConfig( + new BluetoothCodecConfig.Builder() + .setCodecType(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC) + .setBitsPerSample(BluetoothCodecConfig.BITS_PER_SAMPLE_32) + .build()) + .build(); + + shadowBluetoothA2dp.setCodecStatus(connectedBluetoothDevice, status); + + assertThat(bluetoothA2dp.getCodecStatus(connectedBluetoothDevice)).isEqualTo(status); + } + + @Test + @Config(minSdk = TIRAMISU) + public void getCodecConfigPreference_returnValueFromSetter() { + BluetoothCodecConfig codecConfig = + new BluetoothCodecConfig.Builder() + .setCodecType(BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC) + .setBitsPerSample(BluetoothCodecConfig.BITS_PER_SAMPLE_16) + .build(); + + bluetoothA2dp.setCodecConfigPreference(connectedBluetoothDevice, codecConfig); + + assertThat(shadowBluetoothA2dp.getCodecConfigPreference(connectedBluetoothDevice)) + .isEqualTo(codecConfig); + } + + @Test + @Config(minSdk = R) + public void isOptionalCodecsEnabled_deviceIsNull_throwException() { + assertThrows( + IllegalArgumentException.class, + () -> bluetoothA2dp.isOptionalCodecsEnabled(/* device= */ null)); + } + + @Test + @Config(minSdk = R) + public void isOptionalCodecsEnabled_defaultUnknown() { + assertThat(bluetoothA2dp.isOptionalCodecsEnabled(connectedBluetoothDevice)) + .isEqualTo(BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN); + } + + @Test + @Config(minSdk = R) + public void isOptionalCodecsEnabled_returnValueFromSetOptionalCodecsEnabled() { + bluetoothA2dp.setOptionalCodecsEnabled( + connectedBluetoothDevice, BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED); + + assertThat(shadowBluetoothA2dp.isOptionalCodecsEnabled(connectedBluetoothDevice)) + .isEqualTo(BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED); + } + + @Test + @Config(minSdk = R) + public void isOptionalCodecsEnabled_returnValueFromSetOptionalCodecsDisabled() { + bluetoothA2dp.setOptionalCodecsEnabled( + connectedBluetoothDevice, BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED); + + assertThat(shadowBluetoothA2dp.isOptionalCodecsEnabled(connectedBluetoothDevice)) + .isEqualTo(BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED); + } + + @Test + @Config(minSdk = R) + public void setOptionalCodecsEnabled_invalidValue_ignore() { + bluetoothA2dp.setOptionalCodecsEnabled(connectedBluetoothDevice, 100); + + assertThat(shadowBluetoothA2dp.isOptionalCodecsEnabled(connectedBluetoothDevice)) + .isEqualTo(BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN); + } + + @Test + @Config(minSdk = R) + public void setOptionalCodecsEnabled_deviceIsNull_throwException() { + assertThrows( + IllegalArgumentException.class, + () -> + bluetoothA2dp.setOptionalCodecsEnabled( + /* device= */ null, BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED)); + } } diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothAdapterTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothAdapterTest.java index a9c3e8d81..ae6eb3bdc 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothAdapterTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothAdapterTest.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2; import static android.os.Build.VERSION_CODES.LOLLIPOP; import static android.os.Build.VERSION_CODES.M; import static android.os.Build.VERSION_CODES.O; @@ -9,6 +8,7 @@ import static android.os.Build.VERSION_CODES.R; import static android.os.Build.VERSION_CODES.S; import static android.os.Build.VERSION_CODES.S_V2; import static android.os.Build.VERSION_CODES.TIRAMISU; +import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE; import static androidx.test.core.app.ApplicationProvider.getApplicationContext; import static com.google.common.truth.Truth.assertThat; import static java.nio.charset.StandardCharsets.UTF_8; @@ -95,6 +95,26 @@ public class ShadowBluetoothAdapterTest { } @Test + @Config(minSdk = UPSIDE_DOWN_CAKE) + public void canSetAndGetIsLeCodedPhySupported() { + assertThat(bluetoothAdapter.isLeCodedPhySupported()).isTrue(); + + shadowOf(bluetoothAdapter).setIsLeCodedPhySupported(false); + + assertThat(bluetoothAdapter.isLeCodedPhySupported()).isFalse(); + } + + @Test + @Config(minSdk = UPSIDE_DOWN_CAKE) + public void canSetAndGetIsLe2MPhySupported() { + assertThat(bluetoothAdapter.isLe2MPhySupported()).isTrue(); + + shadowOf(bluetoothAdapter).setIsLe2MPhySupported(false); + + assertThat(bluetoothAdapter.isLe2MPhySupported()).isFalse(); + } + + @Test public void testAdapterDefaultsDisabled() { assertThat(bluetoothAdapter.isEnabled()).isFalse(); } @@ -370,7 +390,6 @@ public class ShadowBluetoothAdapterTest { } @Test - @Config(minSdk = JELLY_BEAN_MR2) public void testLeScan() { BluetoothAdapter.LeScanCallback callback1 = newLeScanCallback(); BluetoothAdapter.LeScanCallback callback2 = newLeScanCallback(); @@ -388,7 +407,6 @@ public class ShadowBluetoothAdapterTest { } @Test - @Config(minSdk = JELLY_BEAN_MR2) public void testGetSingleLeScanCallback() { BluetoothAdapter.LeScanCallback callback1 = newLeScanCallback(); BluetoothAdapter.LeScanCallback callback2 = newLeScanCallback(); @@ -430,9 +448,8 @@ public class ShadowBluetoothAdapterTest { @Test public void secureRfcomm_notNull() throws Exception { assertThat( - bluetoothAdapter.listenUsingRfcommWithServiceRecord( - "serviceName", UUID.randomUUID())) - .isNotNull(); + bluetoothAdapter.listenUsingRfcommWithServiceRecord("serviceName", UUID.randomUUID())) + .isNotNull(); } @Test @@ -904,4 +921,36 @@ public class ShadowBluetoothAdapterTest { shadowOf(adapter).setLeAudioSupported(BluetoothStatusCodes.FEATURE_SUPPORTED); assertThat(adapter.isLeAudioSupported()).isEqualTo(BluetoothStatusCodes.FEATURE_SUPPORTED); } + + @Config(minSdk = UPSIDE_DOWN_CAKE) + @Test + public void canGetAndSetDistanceMeasurementSupport() { + // By default distance measurement is not supported + assertThat(bluetoothAdapter.isDistanceMeasurementSupported()) + .isEqualTo(BluetoothStatusCodes.FEATURE_NOT_SUPPORTED); + + // set distance measurement feature to supported. + shadowOf(bluetoothAdapter) + .setDistanceMeasurementSupported(BluetoothStatusCodes.FEATURE_SUPPORTED); + + assertThat(bluetoothAdapter.isDistanceMeasurementSupported()) + .isEqualTo(BluetoothStatusCodes.FEATURE_SUPPORTED); + } + + @Test + @SuppressWarnings("JdkImmutableCollections") + public void getBondedDevices_whenSeededWithSet_returnsThatSet() { + BluetoothDevice device1 = bluetoothAdapter.getRemoteDevice("AB:CD:EF:12:34:56"); + BluetoothDevice device2 = bluetoothAdapter.getRemoteDevice("12:34:56:AB:CD:EF"); + shadowOf(bluetoothAdapter).setBondedDevices(Set.of(device1, device2)); + + assertThat(bluetoothAdapter.getBondedDevices()).containsExactly(device1, device2); + } + + @Test + public void getBondedDevices_whenSeededWithNull_returnsNull() { + shadowOf(bluetoothAdapter).setBondedDevices(null); + + assertThat(bluetoothAdapter.getBondedDevices()).isNull(); + } } diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothDeviceTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothDeviceTest.java index d63ea83ef..93a5d5dfc 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothDeviceTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothDeviceTest.java @@ -5,7 +5,6 @@ import static android.bluetooth.BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES; import static android.bluetooth.BluetoothDevice.BOND_BONDED; import static android.bluetooth.BluetoothDevice.BOND_NONE; import static android.bluetooth.BluetoothDevice.DEVICE_TYPE_CLASSIC; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2; import static android.os.Build.VERSION_CODES.M; import static android.os.Build.VERSION_CODES.O; import static android.os.Build.VERSION_CODES.Q; @@ -31,6 +30,7 @@ import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; import java.util.UUID; +import javax.annotation.Nullable; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.annotation.Config; @@ -167,7 +167,6 @@ public class ShadowBluetoothDeviceTest { } @Test - @Config(minSdk = JELLY_BEAN_MR2) public void connectGatt_doesntCrash() { shadowOf(application).grantPermissions(BLUETOOTH_CONNECT); BluetoothDevice bluetoothDevice = ShadowBluetoothDevice.newInstance(MOCK_MAC_ADDRESS); @@ -221,7 +220,6 @@ public class ShadowBluetoothDeviceTest { } @Test - @Config(minSdk = JELLY_BEAN_MR2) public void canSetAndGetType() { shadowOf(application).grantPermissions(BLUETOOTH_CONNECT); BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(MOCK_MAC_ADDRESS); @@ -231,7 +229,6 @@ public class ShadowBluetoothDeviceTest { } @Test - @Config(minSdk = JELLY_BEAN_MR2) public void canGetBluetoothGatts() { shadowOf(application).grantPermissions(BLUETOOTH_CONNECT); BluetoothDevice device = ShadowBluetoothDevice.newInstance(MOCK_MAC_ADDRESS); @@ -248,7 +245,6 @@ public class ShadowBluetoothDeviceTest { } @Test - @Config(minSdk = JELLY_BEAN_MR2) public void connectGatt_setsBluetoothGattCallback() { shadowOf(application).grantPermissions(BLUETOOTH_CONNECT); BluetoothDevice device = ShadowBluetoothDevice.newInstance(MOCK_MAC_ADDRESS); @@ -262,7 +258,6 @@ public class ShadowBluetoothDeviceTest { } @Test - @Config(minSdk = JELLY_BEAN_MR2) public void canSimulateGattConnectionChange() { shadowOf(application).grantPermissions(BLUETOOTH_CONNECT); BluetoothDevice device = ShadowBluetoothDevice.newInstance(MOCK_MAC_ADDRESS); @@ -278,6 +273,38 @@ public class ShadowBluetoothDeviceTest { } @Test + @Config(minSdk = O) + public void connectGatt_withInterceptor() { + shadowOf(application).grantPermissions(BLUETOOTH_CONNECT); + BluetoothDevice bluetoothDevice = ShadowBluetoothDevice.newInstance(MOCK_MAC_ADDRESS); + + final class FakeGattConnectionInterceptor + implements ShadowBluetoothDevice.BluetoothGattConnectionInterceptor { + @Nullable private BluetoothGatt interceptedGatt = null; + + public BluetoothGatt getInterceptedGatt() { + return interceptedGatt; + } + + @Override + public void onNewGattConnection(BluetoothGatt gatt) { + interceptedGatt = gatt; + } + } + + FakeGattConnectionInterceptor interceptor = new FakeGattConnectionInterceptor(); + shadowOf(bluetoothDevice).setGattConnectionInterceptor(interceptor); + + BluetoothGatt gatt = + bluetoothDevice.connectGatt( + ApplicationProvider.getApplicationContext(), + /* autoConnect= */ false, + new BluetoothGattCallback() {}); + assertThat(gatt).isNotNull(); + assertThat(gatt).isEqualTo(interceptor.getInterceptedGatt()); + } + + @Test public void createRfcommSocketToServiceRecord_returnsSocket() throws Exception { BluetoothDevice device = ShadowBluetoothDevice.newInstance(MOCK_MAC_ADDRESS); diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothGattServerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothGattServerTest.java index cdac21f20..c31cb92d3 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothGattServerTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothGattServerTest.java @@ -7,11 +7,14 @@ import static org.robolectric.Shadows.shadowOf; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothGatt; +import android.bluetooth.BluetoothGattCharacteristic; import android.bluetooth.BluetoothGattServer; import android.bluetooth.BluetoothGattServerCallback; +import android.bluetooth.BluetoothGattService; import android.bluetooth.BluetoothManager; import android.content.Context; import androidx.test.core.app.ApplicationProvider; +import java.util.UUID; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -25,10 +28,35 @@ import org.robolectric.annotation.Config; public class ShadowBluetoothGattServerTest { private static final int INITIAL_VALUE = -99; + private static final int REQUEST_ID = 1; + private static final int OFFSET = 0; private static final String ACTION_CONNECTION = "CONNECT/DISCONNECT"; + private static final String ACTION_CHARACTERISTIC_WRITE = "WRITE"; + private static final String ACTION_CHARACTERISTIC_WRITE_NO_RESPONSE = "WRITE_NO_RESPONSE"; private static final String MOCK_MAC_ADDRESS = "00:11:22:33:AA:BB"; + private static final byte[] PAYLOAD = new byte[] {'m', 'm', 'e'}; + private static final byte[] PAYLOAD2 = new byte[] {'c', 'd', 'i'}; private static final byte[] RESPONSE_VALUE1 = new byte[] {'a', 'b', 'c'}; private static final byte[] RESPONSE_VALUE2 = new byte[] {'1', '2', '3'}; + private static final BluetoothGattCharacteristic characteristicWithWriteProperty = + new BluetoothGattCharacteristic( + UUID.fromString("00000000-0000-0000-0000-0000000000A1"), + BluetoothGattCharacteristic.PROPERTY_WRITE, + BluetoothGattCharacteristic.PERMISSION_WRITE); + private static final BluetoothGattCharacteristic characteristicWithWriteNoResponseProperty = + new BluetoothGattCharacteristic( + UUID.fromString("00000000-0000-0000-0000-0000000000A2"), + BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE, + BluetoothGattCharacteristic.PERMISSION_WRITE); + private static final BluetoothGattCharacteristic characteristicWithReadProperty = + new BluetoothGattCharacteristic( + UUID.fromString("00000000-0000-0000-0000-0000000000A3"), + BluetoothGattCharacteristic.PROPERTY_READ, + BluetoothGattCharacteristic.PERMISSION_READ); + private static final BluetoothGattService service = + new BluetoothGattService( + UUID.fromString("00000000-0000-0000-0001-0000000000A1"), + BluetoothGattService.SERVICE_TYPE_PRIMARY); private BluetoothManager manager; private Context context; @@ -47,6 +75,23 @@ public class ShadowBluetoothGattServerTest { resultState = newState; resultAction = ACTION_CONNECTION; } + + @Override + public void onCharacteristicWriteRequest( + BluetoothDevice device, + int requestId, + BluetoothGattCharacteristic characteristic, + boolean isWrite, + boolean isRead, + int offset, + byte[] payload) { + if (characteristic.getWriteType() == BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT) { + resultAction = ACTION_CHARACTERISTIC_WRITE; + } else if (characteristic.getWriteType() + == BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE) { + resultAction = ACTION_CHARACTERISTIC_WRITE_NO_RESPONSE; + } + } }; @Before @@ -119,6 +164,89 @@ public class ShadowBluetoothGattServerTest { } @Test + public void test_getWrittenBytes_initially() { + assertThat(shadowOf(server).getWrittenBytes()).isEmpty(); + } + + @Test + public void test_getWrittenBytes_afterCharacteristicWriteRequest() { + shadowOf(server).setGattServerCallback(callback); + assertThat( + shadowOf(server) + .notifyOnCharacteristicWriteRequest( + device, + REQUEST_ID, + characteristicWithWriteProperty, + false, + false, + OFFSET, + PAYLOAD)) + .isTrue(); + assertThat(shadowOf(server).getWrittenBytes()).hasSize(1); + assertThat( + shadowOf(server) + .notifyOnCharacteristicWriteRequest( + device, + REQUEST_ID, + characteristicWithWriteProperty, + false, + false, + OFFSET, + PAYLOAD2)) + .isTrue(); + assertThat(shadowOf(server).getWrittenBytes()).hasSize(2); + assertThat(shadowOf(server).getWrittenBytes().get(0)).isEqualTo(PAYLOAD); + assertThat(shadowOf(server).getWrittenBytes().get(1)).isEqualTo(PAYLOAD2); + } + + @Test + public void test_getWrittenBytes_afterClearWrittenBytes() { + assertThat( + shadowOf(server) + .notifyOnCharacteristicWriteRequest( + device, + REQUEST_ID, + characteristicWithWriteProperty, + false, + false, + OFFSET, + PAYLOAD)) + .isTrue(); + shadowOf(server).clearWrittenBytes(); + assertThat(shadowOf(server).getWrittenBytes()).isEmpty(); + } + + @Test + public void test_getWrittenBytes_acceptsNull() { + assertThat( + shadowOf(server) + .notifyOnCharacteristicWriteRequest( + device, + REQUEST_ID, + characteristicWithWriteProperty, + false, + false, + OFFSET, + PAYLOAD)) + .isTrue(); + assertThat(shadowOf(server).getWrittenBytes()).hasSize(1); + assertThat( + shadowOf(server) + .notifyOnCharacteristicWriteRequest( + device, + REQUEST_ID, + characteristicWithWriteProperty, + false, + false, + OFFSET, + null)) + .isTrue(); + assertThat(shadowOf(server).getWrittenBytes()).hasSize(2); + assertThat(shadowOf(server).getWrittenBytes().get(0)).isEqualTo(PAYLOAD); + assertThat(shadowOf(server).getWrittenBytes().get(1)).isEqualTo(null); + } + + @Test public void test_isConnectedToDevice_initially() { assertThat(shadowOf(server).isConnectedToDevice(device)).isFalse(); } @@ -179,6 +307,73 @@ public class ShadowBluetoothGattServerTest { } @Test + public void test_notifyOnCharacteristicWriteRequest_withoutCallback() { + shadowOf(server).setGattServerCallback(null); + assertThat( + shadowOf(server) + .notifyOnCharacteristicWriteRequest( + device, + REQUEST_ID, + characteristicWithWriteProperty, + false, + false, + OFFSET, + PAYLOAD)) + .isFalse(); + } + + @Test + public void test_notifyOnCharacteristicWriteRequest_withCallback_wrongProperty() { + shadowOf(server).setGattServerCallback(callback); + assertThat( + shadowOf(server) + .notifyOnCharacteristicWriteRequest( + device, + REQUEST_ID, + characteristicWithReadProperty, + false, + false, + OFFSET, + PAYLOAD)) + .isFalse(); + assertThat(resultAction).isNull(); + } + + @Test + public void test_notifyOnCharacteristicWriteRequest_correctSetup_writeProperty() { + shadowOf(server).setGattServerCallback(callback); + assertThat( + shadowOf(server) + .notifyOnCharacteristicWriteRequest( + device, + REQUEST_ID, + characteristicWithWriteProperty, + false, + false, + OFFSET, + PAYLOAD)) + .isTrue(); + assertThat(resultAction).isEqualTo(ACTION_CHARACTERISTIC_WRITE); + } + + @Test + public void test_notifyOnCharacteristicWriteRequest_correctSetup_writeNoResponseProperty() { + shadowOf(server).setGattServerCallback(callback); + assertThat( + shadowOf(server) + .notifyOnCharacteristicWriteRequest( + device, + REQUEST_ID, + characteristicWithWriteNoResponseProperty, + false, + false, + OFFSET, + PAYLOAD)) + .isTrue(); + assertThat(resultAction).isEqualTo(ACTION_CHARACTERISTIC_WRITE_NO_RESPONSE); + } + + @Test public void test_isConnectionCancelled_afterCancelConnection() { server.cancelConnection(device); assertThat(shadowOf(server).isConnectionCancelled(device)).isTrue(); @@ -206,4 +401,54 @@ public class ShadowBluetoothGattServerTest { shadowOf(server).notifyConnection(device); assertThat(shadowOf(server).isConnectionCancelled(device)).isFalse(); } + + @Test + public void test_getServices_beforeAddService() { + assertThat(shadowOf(server).getServices()).isEmpty(); + } + + @Test + public void test_getServices_afterAddService() { + server.addService(service); + assertThat(shadowOf(server).getServices()).containsExactly(service); + } + + @Test + public void test_getServices_afterAddService_afterRemoveService() { + server.addService(service); + server.removeService(service); + assertThat(shadowOf(server).getServices()).isEmpty(); + } + + @Test + public void test_getServices_afterAddService_afterClearService() { + server.addService(service); + server.clearServices(); + assertThat(shadowOf(server).getServices()).isEmpty(); + } + + @Test + public void test_getService_beforeAddService() { + assertThat(shadowOf(server).getService(service.getUuid())).isNull(); + } + + @Test + public void test_getService_afterAddService() { + server.addService(service); + assertThat(shadowOf(server).getService(service.getUuid())).isEqualTo(service); + } + + @Test + public void test_getService_afterAddService_afterRemoveService() { + server.addService(service); + server.removeService(service); + assertThat(shadowOf(server).getService(service.getUuid())).isNull(); + } + + @Test + public void test_getService_afterAddService_afterClearService() { + server.addService(service); + server.clearServices(); + assertThat(shadowOf(server).getService(service.getUuid())).isNull(); + } } diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothGattTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothGattTest.java index 874ded718..f405847c8 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothGattTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothGattTest.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2; import static android.os.Build.VERSION_CODES.O; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertThrows; @@ -23,7 +22,6 @@ import org.robolectric.annotation.Config; /** Tests for {@link ShadowBluetoothGatt}. */ @RunWith(AndroidJUnit4.class) -@Config(minSdk = JELLY_BEAN_MR2) public class ShadowBluetoothGattTest { private static final byte[] CHARACTERISTIC_VALUE = new byte[] {'a', 'b', 'c'}; @@ -33,10 +31,12 @@ public class ShadowBluetoothGattTest { private static final String ACTION_DISCOVER = "DISCOVER"; private static final String ACTION_READ = "READ"; private static final String ACTION_WRITE = "WRITE"; + private static final String ACTION_MTU = "MTU"; private static final String REMOTE_ADDRESS = "R-A"; private int resultStatus = INITIAL_VALUE; private int resultState = INITIAL_VALUE; + private int resultMtu = INITIAL_VALUE; private String resultAction; private BluetoothGattCharacteristic resultCharacteristic; private BluetoothGattDescriptor resultDescriptor; @@ -89,6 +89,13 @@ public class ShadowBluetoothGattTest { resultDescriptor = descriptor; resultAction = ACTION_WRITE; } + + @Override + public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) { + resultStatus = status; + resultMtu = mtu; + resultAction = ACTION_MTU; + } }; private final BluetoothGattCharacteristic characteristicWithReadProperty = @@ -236,6 +243,24 @@ public class ShadowBluetoothGattTest { @Test @Config(minSdk = O) + public void requestMtu_failsBeforeCallbackSet() { + assertThat(bluetoothGatt.requestMtu(1)).isFalse(); + } + + @Test + @Config(minSdk = O) + public void requestMtu_success() { + shadowOf(bluetoothGatt).setGattCallback(callback); + + int mtu = 1; + assertThat(bluetoothGatt.requestMtu(mtu)).isTrue(); + assertThat(resultAction).isEqualTo(ACTION_MTU); + assertThat(resultStatus).isEqualTo(BluetoothGatt.GATT_SUCCESS); + assertThat(resultMtu).isEqualTo(mtu); + } + + @Test + @Config(minSdk = O) public void discoverServices_noDiscoverableServices_returnsFalse() { assertThat(bluetoothGatt.discoverServices()).isFalse(); assertThat(bluetoothGatt.getServices()).isEmpty(); @@ -532,6 +557,98 @@ public class ShadowBluetoothGattTest { } @Test + @Config + public void writeIncomingCharacteristic_updated_withoutCallback() { + service1.addCharacteristic(characteristicWithWriteProperties); + assertThrows( + IllegalStateException.class, + () -> + shadowOf(bluetoothGatt).writeIncomingCharacteristic(characteristicWithWriteProperties)); + } + + @Test + @Config + public void writeIncomingCharacteristic_updated_withCallbackOnly() { + shadowOf(bluetoothGatt).setGattCallback(callback); + assertThat( + shadowOf(bluetoothGatt).writeIncomingCharacteristic(characteristicWithWriteProperties)) + .isFalse(); + assertThat(resultStatus).isEqualTo(INITIAL_VALUE); + assertThat(resultAction).isNull(); + assertThat(resultCharacteristic).isNull(); + assertThat(shadowOf(bluetoothGatt).getLatestWrittenBytes()).isNull(); + } + + @Test + @Config + public void writeIncomingCharacteristic_updated_correctlySetup() { + shadowOf(bluetoothGatt).setGattCallback(callback); + characteristicWithWriteProperties.setValue(CHARACTERISTIC_VALUE); + service2.addCharacteristic(characteristicWithWriteProperties); + assertThat(characteristicWithWriteProperties.getService()).isNotNull(); + assertThat( + shadowOf(bluetoothGatt).writeIncomingCharacteristic(characteristicWithWriteProperties)) + .isTrue(); + assertThat(resultStatus).isEqualTo(BluetoothGatt.GATT_SUCCESS); + assertThat(resultAction).isEqualTo(ACTION_WRITE); + assertThat(resultCharacteristic).isEqualTo(characteristicWithWriteProperties); + assertThat(shadowOf(bluetoothGatt).getLatestWrittenBytes()).isEqualTo(CHARACTERISTIC_VALUE); + } + + @Test + @Config + public void writeIncomingCharacteristic_updated_withCallbackAndServiceSet_wrongProperty() { + shadowOf(bluetoothGatt).setGattCallback(callback); + service1.addCharacteristic(characteristicWithReadProperty); + assertThat(characteristicWithReadProperty.getService()).isNotNull(); + + assertThat(shadowOf(bluetoothGatt).writeIncomingCharacteristic(characteristicWithReadProperty)) + .isFalse(); + assertThat(resultStatus).isEqualTo(INITIAL_VALUE); + assertThat(resultAction).isNull(); + assertThat(resultCharacteristic).isNull(); + assertThat(shadowOf(bluetoothGatt).getLatestWrittenBytes()).isNull(); + } + + @Test + @Config + public void writeIncomingCharacteristic_updated_onlyWriteProperty() { + BluetoothGattCharacteristic characteristic = + new BluetoothGattCharacteristic( + UUID.fromString("00000000-0000-0000-0000-0000000000A6"), + BluetoothGattCharacteristic.PROPERTY_WRITE, + BluetoothGattCharacteristic.PERMISSION_WRITE); + + shadowOf(bluetoothGatt).setGattCallback(callback); + service1.addCharacteristic(characteristic); + characteristic.setValue(CHARACTERISTIC_VALUE); + assertThat(shadowOf(bluetoothGatt).writeIncomingCharacteristic(characteristic)).isTrue(); + assertThat(resultStatus).isEqualTo(BluetoothGatt.GATT_SUCCESS); + assertThat(resultAction).isEqualTo(ACTION_WRITE); + assertThat(resultCharacteristic).isEqualTo(characteristic); + assertThat(shadowOf(bluetoothGatt).getLatestWrittenBytes()).isEqualTo(CHARACTERISTIC_VALUE); + } + + @Test + @Config + public void writeIncomingCharacteristic_updated_onlyWriteNoResponseProperty() { + BluetoothGattCharacteristic characteristic = + new BluetoothGattCharacteristic( + UUID.fromString("00000000-0000-0000-0000-0000000000A7"), + BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE, + BluetoothGattCharacteristic.PERMISSION_WRITE); + + shadowOf(bluetoothGatt).setGattCallback(callback); + service1.addCharacteristic(characteristic); + characteristic.setValue(CHARACTERISTIC_VALUE); + assertThat(shadowOf(bluetoothGatt).writeIncomingCharacteristic(characteristic)).isTrue(); + assertThat(resultStatus).isEqualTo(BluetoothGatt.GATT_SUCCESS); + assertThat(resultAction).isEqualTo(ACTION_WRITE); + assertThat(resultCharacteristic).isEqualTo(characteristic); + assertThat(shadowOf(bluetoothGatt).getLatestWrittenBytes()).isEqualTo(CHARACTERISTIC_VALUE); + } + + @Test public void test_getBluetoothConnectionManager() { assertThat(shadowOf(bluetoothGatt).getBluetoothConnectionManager()).isNotNull(); } @@ -616,8 +733,12 @@ public class ShadowBluetoothGattTest { service1.addCharacteristic(characteristicWithReadProperty); shadowOf(bluetoothGatt).addDiscoverableService(service1); shadowOf(bluetoothGatt).allowCharacteristicNotification(characteristicWithReadProperty); + + // All setCharacteristicNotification calls are allowed on characteristicWithReadProperty. assertThat(bluetoothGatt.setCharacteristicNotification(characteristicWithReadProperty, true)) .isTrue(); + assertThat(bluetoothGatt.setCharacteristicNotification(characteristicWithReadProperty, false)) + .isTrue(); } @Test @@ -626,7 +747,43 @@ public class ShadowBluetoothGattTest { service1.addCharacteristic(characteristicWithReadProperty); shadowOf(bluetoothGatt).addDiscoverableService(service1); shadowOf(bluetoothGatt).disallowCharacteristicNotification(characteristicWithReadProperty); + + // No setCharacteristicNotification calls are allowed on characteristicWithReadProperty. + assertThat(bluetoothGatt.setCharacteristicNotification(characteristicWithReadProperty, true)) + .isFalse(); + assertThat(bluetoothGatt.setCharacteristicNotification(characteristicWithReadProperty, false)) + .isFalse(); + } + + @Test + @Config(minSdk = O) + public void disallowCharacteristicNotification_byDefault() { + service1.addCharacteristic(characteristicWithReadProperty); + shadowOf(bluetoothGatt).addDiscoverableService(service1); + + // setCharacteristicNotification calls are disallowed by default when neither + // allowCharacteristicNotification nor disallowCharacteristicNotification is called. + + // No setCharacteristicNotification calls are allowed on characteristicWithReadProperty. + assertThat(bluetoothGatt.setCharacteristicNotification(characteristicWithReadProperty, true)) + .isFalse(); + assertThat(bluetoothGatt.setCharacteristicNotification(characteristicWithReadProperty, false)) + .isFalse(); + } + + @Test + @Config(minSdk = O) + public void disallowCharacteristicNotification_overridesPreviousAllow() { + service1.addCharacteristic(characteristicWithReadProperty); + shadowOf(bluetoothGatt).addDiscoverableService(service1); + + shadowOf(bluetoothGatt).allowCharacteristicNotification(characteristicWithReadProperty); + shadowOf(bluetoothGatt).disallowCharacteristicNotification(characteristicWithReadProperty); + + // No setCharacteristicNotification calls are allowed on characteristicWithReadProperty. assertThat(bluetoothGatt.setCharacteristicNotification(characteristicWithReadProperty, true)) .isFalse(); + assertThat(bluetoothGatt.setCharacteristicNotification(characteristicWithReadProperty, false)) + .isFalse(); } } diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothHeadsetTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothHeadsetTest.java index 545677640..97065ac33 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothHeadsetTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothHeadsetTest.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.KITKAT; import static android.os.Build.VERSION_CODES.P; import static android.os.Build.VERSION_CODES.S; import static android.os.Looper.getMainLooper; @@ -109,6 +108,28 @@ public class ShadowBluetoothHeadsetTest { } @Test + public void getDevicesMatchingConnectionStates_returnsMatchingDevices() { + shadowOf(bluetoothHeadset).addDevice(device1, BluetoothProfile.STATE_CONNECTING); + shadowOf(bluetoothHeadset).addDevice(device2, BluetoothProfile.STATE_DISCONNECTED); + + assertThat( + bluetoothHeadset.getDevicesMatchingConnectionStates( + new int[] {BluetoothProfile.STATE_CONNECTING, BluetoothProfile.STATE_DISCONNECTED})) + .containsExactly(device1, device2); + } + + @Test + public void getDevicesMatchingConnectionStates_subsetOfAvailableStates_returnsMatchingDevices() { + shadowOf(bluetoothHeadset).addDevice(device1, BluetoothProfile.STATE_CONNECTING); + shadowOf(bluetoothHeadset).addDevice(device2, BluetoothProfile.STATE_DISCONNECTED); + + assertThat( + bluetoothHeadset.getDevicesMatchingConnectionStates( + new int[] {BluetoothProfile.STATE_CONNECTING})) + .containsExactly(device1); + } + + @Test public void connect_addsDeviceToConnectedListAndReturnsTrue() { boolean result = bluetoothHeadset.connect(device1); @@ -256,7 +277,6 @@ public class ShadowBluetoothHeadsetTest { } @Test - @Config(minSdk = KITKAT) public void sendVendorSpecificResultCode_defaultsToTrueForConnectedDevice() { shadowOf(bluetoothHeadset).addConnectedDevice(device1); @@ -264,13 +284,11 @@ public class ShadowBluetoothHeadsetTest { } @Test - @Config(minSdk = KITKAT) public void sendVendorSpecificResultCode_alwaysFalseForDisconnectedDevice() { assertThat(bluetoothHeadset.sendVendorSpecificResultCode(device1, "command", "arg")).isFalse(); } @Test - @Config(minSdk = KITKAT) public void sendVendorSpecificResultCode_canBeForcedToFalseForConnectedDevice() { shadowOf(bluetoothHeadset).addConnectedDevice(device1); shadowOf(bluetoothHeadset).setAllowsSendVendorSpecificResultCode(false); @@ -279,7 +297,6 @@ public class ShadowBluetoothHeadsetTest { } @Test - @Config(minSdk = KITKAT) public void sendVendorSpecificResultCode_throwsOnNullCommand() { try { bluetoothHeadset.sendVendorSpecificResultCode(device1, null, "arg"); diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothLeAdvertiserTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothLeAdvertiserTest.java index cd8759cff..76358dc76 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothLeAdvertiserTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothLeAdvertiserTest.java @@ -1,16 +1,29 @@ package org.robolectric.shadows; import static android.os.Build.VERSION_CODES.O; +import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertThrows; import static org.robolectric.Shadows.shadowOf; import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothGattServer; +import android.bluetooth.BluetoothManager; import android.bluetooth.le.AdvertiseCallback; import android.bluetooth.le.AdvertiseData; import android.bluetooth.le.AdvertiseSettings; +import android.bluetooth.le.AdvertisingSet; +import android.bluetooth.le.AdvertisingSetCallback; +import android.bluetooth.le.AdvertisingSetParameters; import android.bluetooth.le.BluetoothLeAdvertiser; +import android.bluetooth.le.PeriodicAdvertisingParameters; +import android.content.Context; +import android.os.Handler; +import android.os.Looper; import android.os.ParcelUuid; +import androidx.test.core.app.ApplicationProvider; +import java.util.Optional; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -31,6 +44,14 @@ public class ShadowBluetoothLeAdvertiserTest { private static final String CALLBACK2_SUCCESS_RESULT = "c2s"; private static final String CALLBACK2_FAILURE_RESULT = "c2f"; + private final Context context = ApplicationProvider.getApplicationContext(); + private final BluetoothManager bluetoothManager = + context.getSystemService(BluetoothManager.class); + private final BluetoothAdapter bluetoothAdapter = bluetoothManager.getAdapter(); + private final ShadowBluetoothAdapter shadowBluetoothAdapter = shadowOf(bluetoothAdapter); + private final BluetoothGattServer gattServer = bluetoothManager.openGattServer(context, null); + private final Handler mainLooperHandler = new Handler(Looper.getMainLooper()); + private BluetoothLeAdvertiser bluetoothLeAdvertiser; private BluetoothLeAdvertiser bluetoothLeAdvertiserNameSet; private AdvertiseSettings advertiseSettings1; @@ -42,6 +63,32 @@ public class ShadowBluetoothLeAdvertiserTest { private AdvertiseCallback advertiseCallback1; private AdvertiseCallback advertiseCallback2; + private Optional<Integer> advertisingSetStartStatusOptional; + private boolean advertisingSetStopped; + private AdvertisingSetCallback advertisingSetCallback = + new AdvertisingSetCallback() { + @Override + public void onAdvertisingSetStarted( + AdvertisingSet advertisingSet, int txPower, int status) { + advertisingSetStartStatusOptional = Optional.of(status); + /* switch (status) { + case AdvertisingSetCallback.ADVERTISE_SUCCESS: + advertisingSetStartStatusOptional. + break; + case AdvertisingSetCallback.ADVERTISE_FAILED_DATA_TOO_LARGE: + break; + case AdvertisingSetCallback.ADVERTISE_FAILED_ALREADY_STARTED: + break; + default: + break; */ + } + + @Override + public void onAdvertisingSetStopped(AdvertisingSet advertisingSet) { + advertisingSetStopped = true; + } + }; + private String result; private int error; private AdvertiseSettings settings; @@ -123,6 +170,9 @@ public class ShadowBluetoothLeAdvertiserTest { .setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_LOW) .setConnectable(false) .build(); + + advertisingSetStartStatusOptional = Optional.empty(); + advertisingSetStopped = false; } @Test @@ -450,4 +500,346 @@ public class ShadowBluetoothLeAdvertiserTest { bluetoothLeAdvertiser.stopAdvertising(advertiseCallback2); assertThat(shadowOf(bluetoothLeAdvertiser).getAdvertisementRequestCount()).isEqualTo(1); } + + @Test + @Config(minSdk = UPSIDE_DOWN_CAKE) + public void startAdvertisingSet() { + bluetoothLeAdvertiser.startAdvertisingSet( + buildAdvertisingSetParams( + true, true, true, BluetoothDevice.PHY_LE_CODED, BluetoothDevice.PHY_LE_CODED), + advertiseData1, + null, + null, + null, + 0, + 0, + gattServer, + advertisingSetCallback, + mainLooperHandler); + + assertThat(advertisingSetStartStatusOptional.get()) + .isEqualTo(AdvertisingSetCallback.ADVERTISE_SUCCESS); + } + + @Test + @Config(minSdk = UPSIDE_DOWN_CAKE) + public void + startAdvertisingSet_advertisingAlreadyStarted_invokeOnAdvertisingSetStartedWithAlreadyStartedStatusCode() { + bluetoothLeAdvertiser.startAdvertisingSet( + buildAdvertisingSetParams( + true, true, true, BluetoothDevice.PHY_LE_CODED, BluetoothDevice.PHY_LE_CODED), + advertiseData1, + null, + null, + null, + 0, + 0, + gattServer, + advertisingSetCallback, + mainLooperHandler); + + bluetoothLeAdvertiser.startAdvertisingSet( + buildAdvertisingSetParams( + true, true, true, BluetoothDevice.PHY_LE_CODED, BluetoothDevice.PHY_LE_CODED), + advertiseData1, + null, + null, + null, + 0, + 0, + gattServer, + advertisingSetCallback, + mainLooperHandler); + + assertThat(advertisingSetStartStatusOptional.get()) + .isEqualTo(AdvertisingSetCallback.ADVERTISE_FAILED_ALREADY_STARTED); + } + + @Test + @Config(minSdk = UPSIDE_DOWN_CAKE) + public void startAdvertisingSet_nullCallback_throwsIllegalArgumentException() { + assertThrows( + IllegalArgumentException.class, + () -> + bluetoothLeAdvertiser.startAdvertisingSet( + buildAdvertisingSetParams( + true, true, true, BluetoothDevice.PHY_LE_CODED, BluetoothDevice.PHY_LE_CODED), + advertiseData1, + null, + null, + null, + 0, + 0, + gattServer, + null, + mainLooperHandler)); + } + + @Test + @Config(minSdk = UPSIDE_DOWN_CAKE) + public void + startAdvertisingSet_legacyModeWithTooBigAdvertiseData_throwsIllegalArgumentException() { + AdvertiseData oversizedData = + new AdvertiseData.Builder() + .addServiceUuid(ParcelUuid.fromString("FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF")) + .addServiceUuid(ParcelUuid.fromString("EEEEEEEE-FFFF-FFFF-FFFF-FFFFFFFFFFFF")) + .build(); + + assertThrows( + IllegalArgumentException.class, + () -> + bluetoothLeAdvertiser.startAdvertisingSet( + buildAdvertisingSetParams( + true, true, true, BluetoothDevice.PHY_LE_CODED, BluetoothDevice.PHY_LE_CODED), + oversizedData, + /* scanResponse= */ null, + /* periodicParameters= */ null, + /* periodicData= */ null, + /* duration= */ 0, + /* maxExtendedAdvertisingEvents= */ 0, + gattServer, + advertisingSetCallback, + mainLooperHandler)); + } + + @Test + @Config(minSdk = UPSIDE_DOWN_CAKE) + public void + startAdvertisingSet_legacyModeWithTooBigScanResponse_throwsIllegalArgumentException() { + AdvertiseData oversizedData = + new AdvertiseData.Builder() + .addServiceUuid(ParcelUuid.fromString("FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF")) + .addServiceUuid(ParcelUuid.fromString("EEEEEEEE-FFFF-FFFF-FFFF-FFFFFFFFFFFF")) + .build(); + + assertThrows( + IllegalArgumentException.class, + () -> + bluetoothLeAdvertiser.startAdvertisingSet( + buildAdvertisingSetParams( + true, true, true, BluetoothDevice.PHY_LE_CODED, BluetoothDevice.PHY_LE_CODED), + advertiseData1, + oversizedData, + /* periodicParameters= */ null, + /* periodicData= */ null, + /* duration= */ 0, + /* maxExtendedAdvertisingEvents= */ 0, + gattServer, + advertisingSetCallback, + mainLooperHandler)); + } + + @Test + @Config(minSdk = UPSIDE_DOWN_CAKE) + public void + startAdvertisingSet_nonLegacyModePrimaryPhySetButNotSupported_throwsIllegalArgumentException() { + shadowBluetoothAdapter.setIsLeCodedPhySupported(false); + + assertThrows( + IllegalArgumentException.class, + () -> + bluetoothLeAdvertiser.startAdvertisingSet( + buildAdvertisingSetParams( + false, true, false, BluetoothDevice.PHY_LE_CODED, BluetoothDevice.PHY_LE_1M), + advertiseData1, + /* scanResponse= */ null, + /* periodicParameters= */ null, + /* periodicData= */ null, + /* duration= */ 0, + /* maxExtendedAdvertisingEvents= */ 0, + gattServer, + advertisingSetCallback, + mainLooperHandler)); + } + + @Test + @Config(minSdk = UPSIDE_DOWN_CAKE) + public void + startAdvertisingSet_nonLegacyModeSecondaryPhySetButNotSupported_throwsIllegalArgumentException() { + shadowBluetoothAdapter.setIsLeCodedPhySupported(false); + + assertThrows( + IllegalArgumentException.class, + () -> + bluetoothLeAdvertiser.startAdvertisingSet( + buildAdvertisingSetParams( + false, true, false, BluetoothDevice.PHY_LE_1M, BluetoothDevice.PHY_LE_CODED), + advertiseData1, + /* scanResponse= */ null, + /* periodicParameters= */ null, + /* periodicData= */ null, + /* duration= */ 0, + /* maxExtendedAdvertisingEvents= */ 0, + gattServer, + advertisingSetCallback, + mainLooperHandler)); + } + + @Test + @Config(minSdk = UPSIDE_DOWN_CAKE) + public void + startAdvertisingSet_nonLegacyModeWithTooBigAdvertiseData_throwsIllegalArgumentException() { + shadowBluetoothAdapter.setIsLeExtendedAdvertisingSupported(false); + AdvertiseData oversizedData = + new AdvertiseData.Builder() + .addServiceUuid(ParcelUuid.fromString("FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF")) + .addServiceUuid(ParcelUuid.fromString("EEEEEEEE-FFFF-FFFF-FFFF-FFFFFFFFFFFF")) + .build(); + + assertThrows( + IllegalArgumentException.class, + () -> + bluetoothLeAdvertiser.startAdvertisingSet( + buildAdvertisingSetParams( + false, true, false, BluetoothDevice.PHY_LE_CODED, BluetoothDevice.PHY_LE_CODED), + oversizedData, + /* scanResponse= */ null, + /* periodicParameters= */ null, + /* periodicData= */ null, + /* duration= */ 0, + /* maxExtendedAdvertisingEvents= */ 0, + gattServer, + advertisingSetCallback, + mainLooperHandler)); + } + + @Test + @Config(minSdk = UPSIDE_DOWN_CAKE) + public void + startAdvertisingSet_nonLegacyModeWithTooBigScanResponse_throwsIllegalArgumentException() { + shadowBluetoothAdapter.setIsLeExtendedAdvertisingSupported(false); + AdvertiseData oversizedData = + new AdvertiseData.Builder() + .addServiceUuid(ParcelUuid.fromString("FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF")) + .addServiceUuid(ParcelUuid.fromString("EEEEEEEE-FFFF-FFFF-FFFF-FFFFFFFFFFFF")) + .build(); + + assertThrows( + IllegalArgumentException.class, + () -> + bluetoothLeAdvertiser.startAdvertisingSet( + buildAdvertisingSetParams( + false, true, false, BluetoothDevice.PHY_LE_CODED, BluetoothDevice.PHY_LE_CODED), + advertiseData1, + oversizedData, + /* periodicParameters= */ null, + /* periodicData= */ null, + /* duration= */ 0, + /* maxExtendedAdvertisingEvents= */ 0, + gattServer, + advertisingSetCallback, + mainLooperHandler)); + } + + @Test + @Config(minSdk = UPSIDE_DOWN_CAKE) + public void + startAdvertisingSet_nonLegacyModeWithTooBigPeriodicData_throwsIllegalArgumentException() { + shadowBluetoothAdapter.setIsLeExtendedAdvertisingSupported(false); + AdvertiseData oversizedData = + new AdvertiseData.Builder() + .addServiceUuid(ParcelUuid.fromString("FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF")) + .addServiceUuid(ParcelUuid.fromString("EEEEEEEE-FFFF-FFFF-FFFF-FFFFFFFFFFFF")) + .build(); + + assertThrows( + IllegalArgumentException.class, + () -> + bluetoothLeAdvertiser.startAdvertisingSet( + buildAdvertisingSetParams( + false, true, false, BluetoothDevice.PHY_LE_CODED, BluetoothDevice.PHY_LE_CODED), + advertiseData1, + /* scanResponse= */ null, + new PeriodicAdvertisingParameters.Builder().setInterval(1).build(), + oversizedData, + /* duration= */ 0, + /* maxExtendedAdvertisingEvents= */ 0, + gattServer, + advertisingSetCallback, + mainLooperHandler)); + } + + @Test + @Config(minSdk = UPSIDE_DOWN_CAKE) + public void + startAdvertisingSet_maxExtendedAdvertisingEventsOutOfRange_throwsIllegalArgumentException() { + assertThrows( + IllegalArgumentException.class, + () -> + bluetoothLeAdvertiser.startAdvertisingSet( + buildAdvertisingSetParams( + false, true, false, BluetoothDevice.PHY_LE_CODED, BluetoothDevice.PHY_LE_CODED), + advertiseData1, + /* scanResponse= */ null, + /* periodicParameters= */ null, + /* periodicData= */ null, + /* duration= */ 0, + -1, + gattServer, + advertisingSetCallback, + mainLooperHandler)); + } + + @Test + @Config(minSdk = UPSIDE_DOWN_CAKE) + public void startAdvertisingSet_durationOutOfRange_throwsIllegalArgumentException() { + assertThrows( + IllegalArgumentException.class, + () -> + bluetoothLeAdvertiser.startAdvertisingSet( + buildAdvertisingSetParams( + false, true, false, BluetoothDevice.PHY_LE_CODED, BluetoothDevice.PHY_LE_CODED), + advertiseData1, + /* scanResponse= */ null, + /* periodicParameters= */ null, + /* periodicData= */ null, + -1, + /* maxExtendedAdvertisingEvents= */ 0, + gattServer, + advertisingSetCallback, + mainLooperHandler)); + } + + @Test + @Config(minSdk = UPSIDE_DOWN_CAKE) + public void stopAdvertisingSet() { + bluetoothLeAdvertiser.startAdvertisingSet( + buildAdvertisingSetParams( + true, true, true, BluetoothDevice.PHY_LE_CODED, BluetoothDevice.PHY_LE_CODED), + advertiseData1, + null, + null, + null, + 0, + 0, + gattServer, + advertisingSetCallback, + mainLooperHandler); + + bluetoothLeAdvertiser.stopAdvertisingSet(advertisingSetCallback); + + assertThat(advertisingSetStopped).isTrue(); + } + + @Test + @Config(minSdk = UPSIDE_DOWN_CAKE) + public void stopAdvertisingSet_nullCallback_throwsIllegalArgumentException() { + assertThrows( + IllegalArgumentException.class, () -> bluetoothLeAdvertiser.stopAdvertisingSet(null)); + } + + private AdvertisingSetParameters buildAdvertisingSetParams( + boolean isLegacy, + boolean isConnectable, + boolean isScannable, + int primaryPhy, + int secondaryPhy) { + return new AdvertisingSetParameters.Builder() + .setLegacyMode(isLegacy) + .setConnectable(isConnectable) + .setScannable(isScannable) + .setPrimaryPhy(primaryPhy) + .setSecondaryPhy(secondaryPhy) + .build(); + } } diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothLeScannerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothLeScannerTest.java index f4a4d71cc..51b12b86f 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothLeScannerTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothLeScannerTest.java @@ -11,11 +11,14 @@ import android.bluetooth.BluetoothAdapter; import android.bluetooth.le.BluetoothLeScanner; import android.bluetooth.le.ScanCallback; import android.bluetooth.le.ScanFilter; +import android.bluetooth.le.ScanRecord; import android.bluetooth.le.ScanResult; import android.bluetooth.le.ScanSettings; import android.content.Intent; import android.os.ParcelUuid; import androidx.test.core.app.ApplicationProvider; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.UUID; @@ -30,15 +33,27 @@ import org.robolectric.annotation.Config; @RunWith(RobolectricTestRunner.class) @Config(minSdk = LOLLIPOP) public class ShadowBluetoothLeScannerTest { + private BluetoothAdapter adapter; private BluetoothLeScanner bluetoothLeScanner; private List<ScanFilter> scanFilters; private ScanSettings scanSettings; - private ScanCallback scanCallback; private PendingIntent pendingIntent; + private static final class FakeScanCallback extends ScanCallback { + List<ScanResult> scanResults = new ArrayList<>(); + + @Override + public void onScanResult(int callbackType, ScanResult scanResult) { + assertThat(callbackType).isEqualTo(ScanSettings.CALLBACK_TYPE_ALL_MATCHES); + scanResults.add(scanResult); + } + } + + private FakeScanCallback scanCallback; + @Before public void setUp() throws Exception { - BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + adapter = BluetoothAdapter.getDefaultAdapter(); if (RuntimeEnvironment.getApiLevel() < M) { // On SDK < 23, bluetooth has to be in STATE_ON in order to get a BluetoothLeScanner. shadowOf(adapter).setState(BluetoothAdapter.STATE_ON); @@ -61,14 +76,7 @@ public class ShadowBluetoothLeScannerTest { .setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES) .setReportDelay(0) .build(); - scanCallback = - new ScanCallback() { - @Override - public void onScanResult(int callbackType, ScanResult scanResult) {} - - @Override - public void onScanFailed(int errorCode) {} - }; + scanCallback = new FakeScanCallback(); pendingIntent = PendingIntent.getBroadcast( ApplicationProvider.getApplicationContext(), 0, new Intent("SCAN_CALLBACK"), 0); @@ -159,4 +167,80 @@ public class ShadowBluetoothLeScannerTest { assertThat(shadowOf(bluetoothLeScanner).getActiveScans().get(0).scanSettings()) .isEqualTo(scanSettings); } + + @Test + @Config(minSdk = O) + public void startScan_withScanResult_andNullFilters() { + ScanResult scanResultOne = + new ScanResult( + adapter.getRemoteDevice("AA:BB:CC:DD:EE:FF"), + /* eventType= */ 1, + /* primaryPhy= */ 1, + /* secondaryPhy= */ 1, + /* advertisingSid= */ 1, + /* txPower= */ 1, + /* rssi= */ 1, + /* periodicActivitySignal= */ 1, + /* scanRecord= */ ScanRecord.parseFromBytes(new byte[] {}), + /* timestamp= */ 1); + ScanResult scanResultTwo = + new ScanResult( + adapter.getRemoteDevice("BB:BB:CC:DD:EE:FF"), + /* eventType= */ 2, + /* primaryPhy= */ 2, + /* secondaryPhy= */ 2, + /* advertisingSid= */ 2, + /* txPower= */ 2, + /* rssi= */ 2, + /* periodicActivitySignal= */ 2, + /* scanRecord= */ ScanRecord.parseFromBytes(new byte[] {}), + /* timestamp= */ 2); + + ShadowBluetoothLeScanner shadowBluetoothLeScanner = shadowOf(bluetoothLeScanner); + shadowBluetoothLeScanner.addScanResult(scanResultOne); + shadowBluetoothLeScanner.addScanResult(scanResultTwo); + + bluetoothLeScanner.startScan(/* filters= */ null, /* settings= */ null, scanCallback); + + assertThat(scanCallback.scanResults).containsExactly(scanResultOne, scanResultTwo); + } + + @Test + @Config(minSdk = O) + public void startScan_withScanResult_andFilter() { + String addressOne = "AA:BB:CC:DD:EE:FF"; + ScanResult scanResultOne = + new ScanResult( + adapter.getRemoteDevice(addressOne), + /* eventType= */ 1, + /* primaryPhy= */ 1, + /* secondaryPhy= */ 1, + /* advertisingSid= */ 1, + /* txPower= */ 1, + /* rssi= */ 1, + /* periodicActivitySignal= */ 1, + /* scanRecord= */ ScanRecord.parseFromBytes(new byte[] {}), + /* timestamp= */ 1); + ScanResult scanResultTwo = + new ScanResult( + adapter.getRemoteDevice("BB:BB:CC:DD:EE:FF"), + /* eventType= */ 2, + /* primaryPhy= */ 2, + /* secondaryPhy= */ 2, + /* advertisingSid= */ 2, + /* txPower= */ 2, + /* rssi= */ 2, + /* periodicActivitySignal= */ 2, + /* scanRecord= */ ScanRecord.parseFromBytes(new byte[] {}), + /* timestamp= */ 2); + + ShadowBluetoothLeScanner shadowBluetoothLeScanner = shadowOf(bluetoothLeScanner); + shadowBluetoothLeScanner.addScanResult(scanResultOne); + shadowBluetoothLeScanner.addScanResult(scanResultTwo); + + ScanFilter filter = new ScanFilter.Builder().setDeviceAddress(addressOne).build(); + bluetoothLeScanner.startScan(Arrays.asList(filter), /* settings= */ null, scanCallback); + + assertThat(scanCallback.scanResults).containsExactly(scanResultOne); + } } diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothManagerTest.java index 5e9848ae2..4fb52e0e3 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothManagerTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothManagerTest.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2; import static android.os.Build.VERSION_CODES.O; import static android.os.Build.VERSION_CODES.R; import static android.os.Build.VERSION_CODES.S; @@ -25,7 +24,6 @@ import org.junit.runner.RunWith; import org.robolectric.annotation.Config; @RunWith(AndroidJUnit4.class) -@Config(minSdk = JELLY_BEAN_MR2) public class ShadowBluetoothManagerTest { private static final String DEVICE_ADDRESS_1 = "00:11:22:AA:BB:CC"; private static final String DEVICE_ADDRESS_2 = "11:22:33:BB:CC:DD"; diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowBuildTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowBuildTest.java index 88ee76029..62e25aba5 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowBuildTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowBuildTest.java @@ -1,5 +1,6 @@ package org.robolectric.shadows; +import static android.os.Build.VERSION_CODES.L; import static android.os.Build.VERSION_CODES.M; import static android.os.Build.VERSION_CODES.O; import static android.os.Build.VERSION_CODES.R; @@ -132,6 +133,30 @@ public class ShadowBuildTest { assertThat(Build.getSerial()).isEqualTo("robo_serial"); } + @Test + @Config(minSdk = L) + public void supported32BitAbis() { + assertThat(Build.SUPPORTED_32_BIT_ABIS).isEqualTo(new String[] {"armeabi-v7a", "armeabi"}); + ShadowBuild.setSupported32BitAbis(new String[] {"x86"}); + assertThat(Build.SUPPORTED_32_BIT_ABIS).isEqualTo(new String[] {"x86"}); + } + + @Test + @Config(minSdk = L) + public void supported64BitAbis() { + assertThat(Build.SUPPORTED_64_BIT_ABIS).isEqualTo(new String[] {"armeabi-v7a", "armeabi"}); + ShadowBuild.setSupported64BitAbis(new String[] {"x86_64"}); + assertThat(Build.SUPPORTED_64_BIT_ABIS).isEqualTo(new String[] {"x86_64"}); + } + + @Test + @Config(minSdk = L) + public void supportedAbis() { + assertThat(Build.SUPPORTED_ABIS).isEqualTo(new String[] {"armeabi-v7a"}); + ShadowBuild.setSupportedAbis(new String[] {"x86"}); + assertThat(Build.SUPPORTED_ABIS).isEqualTo(new String[] {"x86"}); + } + /** Verifies that each test gets a fresh set of Build values. */ private void checkValues() { assertThat(Build.FINGERPRINT).isEqualTo("robolectric"); diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowCameraTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowCameraTest.java index 0c5297088..c79d135a2 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowCameraTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowCameraTest.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; import static com.google.common.truth.Truth.assertThat; import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.Assert.fail; @@ -17,7 +16,6 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.Shadows; -import org.robolectric.annotation.Config; @RunWith(AndroidJUnit4.class) public class ShadowCameraTest { @@ -279,7 +277,6 @@ public class ShadowCameraTest { } @Test - @Config(minSdk = JELLY_BEAN_MR1) public void testCameraInfoShutterSound() { Camera.CameraInfo cameraQueryCannotDisable = new Camera.CameraInfo(); Camera.CameraInfo cameraInfoCannotDisable = new Camera.CameraInfo(); @@ -313,7 +310,6 @@ public class ShadowCameraTest { } @Test - @Config(minSdk = JELLY_BEAN_MR1) public void testShutterEnabled() { Camera.CameraInfo cameraInfo = new Camera.CameraInfo(); cameraInfo.facing = Camera.CameraInfo.CAMERA_FACING_BACK; diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowConfigurationTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowConfigurationTest.java index 79de3de40..9e0ea9e70 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowConfigurationTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowConfigurationTest.java @@ -4,13 +4,11 @@ import static android.content.res.Configuration.SCREENLAYOUT_UNDEFINED; import static com.google.common.truth.Truth.assertThat; import android.content.res.Configuration; -import android.os.Build; import androidx.test.ext.junit.runners.AndroidJUnit4; import java.util.Locale; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.robolectric.annotation.Config; @RunWith(AndroidJUnit4.class) public class ShadowConfigurationTest { @@ -30,7 +28,6 @@ public class ShadowConfigurationTest { } @Test - @Config(minSdk = Build.VERSION_CODES.JELLY_BEAN_MR1) public void testSetLocale() { configuration.setLocale( Locale.US ); assertThat(configuration.locale).isEqualTo(Locale.US); diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowConnectivityManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowConnectivityManagerTest.java index b224e4dfb..5fbe161d6 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowConnectivityManagerTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowConnectivityManagerTest.java @@ -3,7 +3,6 @@ package org.robolectric.shadows; import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_DISABLED; import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED; import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_WHITELISTED; -import static android.os.Build.VERSION_CODES.KITKAT; import static android.os.Build.VERSION_CODES.LOLLIPOP; import static android.os.Build.VERSION_CODES.M; import static android.os.Build.VERSION_CODES.N; @@ -72,7 +71,7 @@ public class ShadowConnectivityManagerTest { } @Test - public void getNetworkInfo_shouldReturnDefaultNetworks() throws Exception { + public void getNetworkInfo_shouldReturnDefaultNetworks() { NetworkInfo wifi = connectivityManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI); assertThat(wifi.getDetailedState()).isEqualTo(NetworkInfo.DetailedState.DISCONNECTED); @@ -80,8 +79,9 @@ public class ShadowConnectivityManagerTest { assertThat(mobile.getDetailedState()).isEqualTo(NetworkInfo.DetailedState.CONNECTED); } - @Test @Config(minSdk = LOLLIPOP) - public void getNetworkInfo_shouldReturnSomeForAllNetworks() throws Exception { + @Test + @Config(minSdk = LOLLIPOP) + public void getNetworkInfo_shouldReturnSomeForAllNetworks() { Network[] allNetworks = connectivityManager.getAllNetworks(); for (Network network: allNetworks) { NetworkInfo networkInfo = connectivityManager.getNetworkInfo(network); @@ -89,8 +89,9 @@ public class ShadowConnectivityManagerTest { } } - @Test @Config(minSdk = LOLLIPOP) - public void getNetworkInfo_shouldReturnAddedNetwork() throws Exception { + @Test + @Config(minSdk = LOLLIPOP) + public void getNetworkInfo_shouldReturnAddedNetwork() { Network vpnNetwork = ShadowNetwork.newInstance(123); NetworkInfo vpnNetworkInfo = ShadowNetworkInfo.newInstance( @@ -105,8 +106,9 @@ public class ShadowConnectivityManagerTest { assertThat(returnedNetworkInfo).isSameInstanceAs(vpnNetworkInfo); } - @Test @Config(minSdk = LOLLIPOP) - public void getNetworkInfo_shouldNotReturnRemovedNetwork() throws Exception { + @Test + @Config(minSdk = LOLLIPOP) + public void getNetworkInfo_shouldNotReturnRemovedNetwork() { Network wifiNetwork = ShadowNetwork.newInstance(ShadowConnectivityManager.NET_ID_WIFI); shadowOf(connectivityManager).removeNetwork(wifiNetwork); @@ -124,14 +126,14 @@ public class ShadowConnectivityManagerTest { } @Test - public void shouldGetAndSetBackgroundDataSetting() throws Exception { + public void shouldGetAndSetBackgroundDataSetting() { assertThat(connectivityManager.getBackgroundDataSetting()).isFalse(); shadowOf(connectivityManager).setBackgroundDataSetting(true); assertThat(connectivityManager.getBackgroundDataSetting()).isTrue(); } @Test - public void setActiveNetworkInfo_shouldSetActiveNetworkInfo() throws Exception { + public void setActiveNetworkInfo_shouldSetActiveNetworkInfo() { shadowOf(connectivityManager).setActiveNetworkInfo(null); assertThat(connectivityManager.getActiveNetworkInfo()).isNull(); shadowOf(connectivityManager) @@ -166,7 +168,7 @@ public class ShadowConnectivityManagerTest { @Test @Config(minSdk = M) - public void setActiveNetworkInfo_shouldSetActiveNetwork() throws Exception { + public void setActiveNetworkInfo_shouldSetActiveNetwork() { shadowOf(connectivityManager).setActiveNetworkInfo(null); assertThat(connectivityManager.getActiveNetworkInfo()).isNull(); shadowOf(connectivityManager) @@ -188,7 +190,7 @@ public class ShadowConnectivityManagerTest { } @Test - public void getAllNetworkInfo_shouldReturnAllNetworkInterfaces() throws Exception { + public void getAllNetworkInfo_shouldReturnAllNetworkInterfaces() { NetworkInfo[] infos = connectivityManager.getAllNetworkInfo(); assertThat(infos).asList().hasSize(2); assertThat(infos).asList().contains(connectivityManager.getActiveNetworkInfo()); @@ -199,7 +201,7 @@ public class ShadowConnectivityManagerTest { @Test @Config(minSdk = LOLLIPOP) - public void getAllNetworkInfo_shouldEqualGetAllNetworks() throws Exception { + public void getAllNetworkInfo_shouldEqualGetAllNetworks() { // Update the active network so that we're no longer in the default state. NetworkInfo networkInfo = ShadowNetworkInfo.newInstance( @@ -227,21 +229,24 @@ public class ShadowConnectivityManagerTest { assertThat(connectivityManager.getAllNetworkInfo()).isNull(); } - @Test @Config(minSdk = LOLLIPOP) - public void getAllNetworks_shouldReturnAllNetworks() throws Exception { + @Test + @Config(minSdk = LOLLIPOP) + public void getAllNetworks_shouldReturnAllNetworks() { Network[] networks = connectivityManager.getAllNetworks(); assertThat(networks).asList().hasSize(2); } - @Test @Config(minSdk = LOLLIPOP) - public void getAllNetworks_shouldReturnNoNetworksWhenCleared() throws Exception { + @Test + @Config(minSdk = LOLLIPOP) + public void getAllNetworks_shouldReturnNoNetworksWhenCleared() { shadowOf(connectivityManager).clearAllNetworks(); Network[] networks = connectivityManager.getAllNetworks(); assertThat(networks).isEmpty(); } - @Test @Config(minSdk = LOLLIPOP) - public void getAllNetworks_shouldReturnAddedNetworks() throws Exception { + @Test + @Config(minSdk = LOLLIPOP) + public void getAllNetworks_shouldReturnAddedNetworks() { // Let's start clear. shadowOf(connectivityManager).clearAllNetworks(); @@ -266,8 +271,9 @@ public class ShadowConnectivityManagerTest { assertThat(returnedNetworkInfo).isSameInstanceAs(vpnNetworkInfo); } - @Test @Config(minSdk = LOLLIPOP) - public void getAllNetworks_shouldNotReturnRemovedNetworks() throws Exception { + @Test + @Config(minSdk = LOLLIPOP) + public void getAllNetworks_shouldNotReturnRemovedNetworks() { Network wifiNetwork = ShadowNetwork.newInstance(ShadowConnectivityManager.NET_ID_WIFI); shadowOf(connectivityManager).removeNetwork(wifiNetwork); @@ -280,13 +286,13 @@ public class ShadowConnectivityManagerTest { } @Test - public void getNetworkPreference_shouldGetDefaultValue() throws Exception { + public void getNetworkPreference_shouldGetDefaultValue() { assertThat(connectivityManager.getNetworkPreference()).isEqualTo(ConnectivityManager.DEFAULT_NETWORK_PREFERENCE); } @Test @Config(minSdk = M) - public void getReportedNetworkConnectivity() throws Exception { + public void getReportedNetworkConnectivity() { Network wifiNetwork = ShadowNetwork.newInstance(ShadowConnectivityManager.NET_ID_WIFI); connectivityManager.reportNetworkConnectivity(wifiNetwork, true); @@ -303,15 +309,16 @@ public class ShadowConnectivityManagerTest { } @Test - public void setNetworkPreference_shouldSetDefaultValue() throws Exception { + public void setNetworkPreference_shouldSetDefaultValue() { connectivityManager.setNetworkPreference(ConnectivityManager.TYPE_MOBILE); assertThat(connectivityManager.getNetworkPreference()).isEqualTo(connectivityManager.getNetworkPreference()); connectivityManager.setNetworkPreference(ConnectivityManager.TYPE_WIFI); assertThat(connectivityManager.getNetworkPreference()).isEqualTo(ConnectivityManager.TYPE_WIFI); } - @Test @Config(minSdk = LOLLIPOP) - public void getNetworkCallbacks_shouldHaveEmptyDefault() throws Exception { + @Test + @Config(minSdk = LOLLIPOP) + public void getNetworkCallbacks_shouldHaveEmptyDefault() { assertThat(shadowOf(connectivityManager).getNetworkCallbacks()).isEmpty(); } @@ -330,15 +337,16 @@ public class ShadowConnectivityManagerTest { @Test @Config(minSdk = LOLLIPOP) - public void requestNetwork_shouldAddCallback() throws Exception { + public void requestNetwork_shouldAddCallback() { NetworkRequest.Builder builder = new NetworkRequest.Builder(); ConnectivityManager.NetworkCallback callback = createSimpleCallback(); connectivityManager.requestNetwork(builder.build(), callback); assertThat(shadowOf(connectivityManager).getNetworkCallbacks()).hasSize(1); } - @Test @Config(minSdk = LOLLIPOP) - public void registerCallback_shouldAddCallback() throws Exception { + @Test + @Config(minSdk = LOLLIPOP) + public void registerCallback_shouldAddCallback() { NetworkRequest.Builder builder = new NetworkRequest.Builder(); ConnectivityManager.NetworkCallback callback = createSimpleCallback(); connectivityManager.registerNetworkCallback(builder.build(), callback); @@ -347,7 +355,7 @@ public class ShadowConnectivityManagerTest { @Test @Config(minSdk = M) - public void registerCallback_withPendingIntent_shouldAddCallback() throws Exception { + public void registerCallback_withPendingIntent_shouldAddCallback() { NetworkRequest.Builder builder = new NetworkRequest.Builder(); PendingIntent pendingIntent = createSimplePendingIntent(); connectivityManager.registerNetworkCallback(builder.build(), pendingIntent); @@ -356,7 +364,7 @@ public class ShadowConnectivityManagerTest { @Test @Config(minSdk = O) - public void requestNetwork_withTimeout_shouldAddCallback() throws Exception { + public void requestNetwork_withTimeout_shouldAddCallback() { NetworkRequest.Builder builder = new NetworkRequest.Builder(); ConnectivityManager.NetworkCallback callback = createSimpleCallback(); connectivityManager.requestNetwork(builder.build(), callback, 0); @@ -365,7 +373,7 @@ public class ShadowConnectivityManagerTest { @Test @Config(minSdk = O) - public void requestNetwork_withHandler_shouldAddCallback() throws Exception { + public void requestNetwork_withHandler_shouldAddCallback() { NetworkRequest.Builder builder = new NetworkRequest.Builder(); ConnectivityManager.NetworkCallback callback = createSimpleCallback(); connectivityManager.requestNetwork(builder.build(), callback, new Handler()); @@ -374,7 +382,7 @@ public class ShadowConnectivityManagerTest { @Test @Config(minSdk = O) - public void requestNetwork_withHandlerAndTimer_shouldAddCallback() throws Exception { + public void requestNetwork_withHandlerAndTimer_shouldAddCallback() { NetworkRequest.Builder builder = new NetworkRequest.Builder(); ConnectivityManager.NetworkCallback callback = createSimpleCallback(); connectivityManager.requestNetwork(builder.build(), callback, new Handler(), 0); @@ -383,14 +391,15 @@ public class ShadowConnectivityManagerTest { @Test @Config(minSdk = N) - public void registerDefaultCallback_shouldAddCallback() throws Exception { + public void registerDefaultCallback_shouldAddCallback() { ConnectivityManager.NetworkCallback callback = createSimpleCallback(); connectivityManager.registerDefaultNetworkCallback(callback); assertThat(shadowOf(connectivityManager).getNetworkCallbacks()).hasSize(1); } - @Test @Config(minSdk = LOLLIPOP) - public void unregisterCallback_shouldRemoveCallbacks() throws Exception { + @Test + @Config(minSdk = LOLLIPOP) + public void unregisterCallback_shouldRemoveCallbacks() { NetworkRequest.Builder builder = new NetworkRequest.Builder(); // Add two different callbacks. ConnectivityManager.NetworkCallback callback1 = createSimpleCallback(); @@ -407,7 +416,7 @@ public class ShadowConnectivityManagerTest { @Test @Config(minSdk = M) - public void unregisterCallback_withPendingIntent_shouldRemoveCallbacks() throws Exception { + public void unregisterCallback_withPendingIntent_shouldRemoveCallbacks() { NetworkRequest.Builder builder = new NetworkRequest.Builder(); // Add two pendingIntents, should treat them as equal based on Intent#filterEquals PendingIntent pendingIntent1 = createSimplePendingIntent(); @@ -420,15 +429,20 @@ public class ShadowConnectivityManagerTest { assertThat(shadowOf(connectivityManager).getNetworkCallbackPendingIntents()).isEmpty(); } - @Test(expected=IllegalArgumentException.class) @Config(minSdk = LOLLIPOP) - public void unregisterCallback_shouldNotAllowNullCallback() throws Exception { + @Config(minSdk = LOLLIPOP) + @Test + public void unregisterCallback_shouldNotAllowNullCallback() { // Verify that exception is thrown. - connectivityManager.unregisterNetworkCallback((ConnectivityManager.NetworkCallback) null); + assertThrows( + IllegalArgumentException.class, + () -> + connectivityManager.unregisterNetworkCallback( + (ConnectivityManager.NetworkCallback) null)); } - @Test @Config(minSdk = M) - public void unregisterCallback_withPendingIntent_shouldNotAllowNullCallback() throws Exception { + @Test + public void unregisterCallback_withPendingIntent_shouldNotAllowNullCallback() { // Verify that exception is thrown. assertThrows( IllegalArgumentException.class, @@ -496,7 +510,7 @@ public class ShadowConnectivityManagerTest { @Test @Config(minSdk = LOLLIPOP) - public void addDefaultNetworkActiveListener_shouldAddListener() throws Exception { + public void addDefaultNetworkActiveListener_shouldAddListener() { ConnectivityManager.OnNetworkActiveListener listener1 = spy(createSimpleOnNetworkActiveListener()); ConnectivityManager.OnNetworkActiveListener listener2 = @@ -512,7 +526,7 @@ public class ShadowConnectivityManagerTest { @Test @Config(minSdk = LOLLIPOP) - public void removeDefaultNetworkActiveListener_shouldRemoveListeners() throws Exception { + public void removeDefaultNetworkActiveListener_shouldRemoveListeners() { // Add two different callbacks. ConnectivityManager.OnNetworkActiveListener listener1 = spy(createSimpleOnNetworkActiveListener()); @@ -541,16 +555,18 @@ public class ShadowConnectivityManagerTest { verify(listener2).onNetworkActive(); } - @Test(expected = IllegalArgumentException.class) @Config(minSdk = LOLLIPOP) - public void removeDefaultNetworkActiveListener_shouldNotAllowNullListener() throws Exception { + @Test + public void removeDefaultNetworkActiveListener_shouldNotAllowNullListener() { // Verify that exception is thrown. - connectivityManager.removeDefaultNetworkActiveListener(null); + assertThrows( + IllegalArgumentException.class, + () -> connectivityManager.removeDefaultNetworkActiveListener(null)); } @Test @Config(minSdk = LOLLIPOP) - public void getNetworkCapabilities() throws Exception { + public void getNetworkCapabilities() { NetworkCapabilities nc = ShadowNetworkCapabilities.newInstance(); shadowOf(nc).addCapability(NetworkCapabilities.NET_CAPABILITY_MMS); @@ -566,7 +582,7 @@ public class ShadowConnectivityManagerTest { @Test @Config(minSdk = LOLLIPOP) - public void getNetworkCapabilities_shouldReturnDefaultCapabilities() throws Exception { + public void getNetworkCapabilities_shouldReturnDefaultCapabilities() { for (Network network : connectivityManager.getAllNetworks()) { NetworkCapabilities nc = connectivityManager.getNetworkCapabilities(network); assertThat(nc).isNotNull(); @@ -594,7 +610,6 @@ public class ShadowConnectivityManagerTest { } @Test - @Config(minSdk = KITKAT) public void setAirplaneMode() { connectivityManager.setAirplaneMode(false); assertThat( @@ -650,20 +665,24 @@ public class ShadowConnectivityManagerTest { .isEqualTo(RESTRICT_BACKGROUND_STATUS_DISABLED); } - @Test(expected = IllegalArgumentException.class) @Config(minSdk = N) - public void setRestrictBackgroundStatus_throwsExceptionOnIncorrectStatus0() throws Exception{ - shadowOf(connectivityManager).setRestrictBackgroundStatus(0); + @Test + public void setRestrictBackgroundStatus_throwsExceptionOnIncorrectStatus0() { + assertThrows( + IllegalArgumentException.class, + () -> shadowOf(connectivityManager).setRestrictBackgroundStatus(0)); } - @Test(expected = IllegalArgumentException.class) @Config(minSdk = N) - public void setRestrictBackgroundStatus_throwsExceptionOnIncorrectStatus4() throws Exception{ - shadowOf(connectivityManager).setRestrictBackgroundStatus(4); + @Test + public void setRestrictBackgroundStatus_throwsExceptionOnIncorrectStatus4() { + assertThrows( + IllegalArgumentException.class, + () -> shadowOf(connectivityManager).setRestrictBackgroundStatus(4)); } @Test - public void checkPollingTetherThreadNotCreated() throws Exception { + public void checkPollingTetherThreadNotCreated() { for (StackTraceElement[] elements : Thread.getAllStackTraces().values()) { for (StackTraceElement element : elements) { if (element.toString().contains("android.net.TetheringManager")) { diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowContentProviderTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowContentProviderTest.java index ebfaf5735..daa36e5b2 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowContentProviderTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowContentProviderTest.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.KITKAT; import static com.google.common.truth.Truth.assertThat; import static org.robolectric.Shadows.shadowOf; @@ -8,12 +7,10 @@ import android.content.ContentProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; -import org.robolectric.annotation.Config; import org.robolectric.shadows.testing.TestContentProvider1; @RunWith(AndroidJUnit4.class) public class ShadowContentProviderTest { - @Config(minSdk = KITKAT) @Test public void testSetCallingPackage() { ContentProvider provider = new TestContentProvider1(); diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowContentResolverTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowContentResolverTest.java index 6e2e2d03f..94a19debb 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowContentResolverTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowContentResolverTest.java @@ -3,7 +3,6 @@ package org.robolectric.shadows; import static android.content.ContentResolver.QUERY_ARG_SQL_SELECTION; import static android.content.ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS; import static android.content.ContentResolver.QUERY_ARG_SQL_SORT_ORDER; -import static android.os.Build.VERSION_CODES.KITKAT; import static android.os.Build.VERSION_CODES.O; import static android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI; import static com.google.common.truth.Truth.assertThat; @@ -764,11 +763,6 @@ public class ShadowContentResolverTest { assertThat(resultOperations).isNotNull(); assertThat(resultOperations.size()).isEqualTo(0); - ContentProviderResult[] contentProviderResults = - new ContentProviderResult[] { - new ContentProviderResult(1), new ContentProviderResult(1), - }; - shadowContentResolver.setContentProviderResult(contentProviderResults); Uri uri = Uri.parse("content://org.robolectric"); ArrayList<ContentProviderOperation> operations = new ArrayList<>(); operations.add( @@ -789,7 +783,7 @@ public class ShadowContentResolverTest { resultOperations = shadowContentResolver.getContentProviderOperations(AUTHORITY); assertThat(resultOperations).isEqualTo(operations); - assertThat(result).isEqualTo(contentProviderResults); + assertThat(result).isNotNull(); } @Test @@ -1167,7 +1161,6 @@ public class ShadowContentResolverTest { } @Test - @Config(minSdk = KITKAT) public void takeAndReleasePersistableUriPermissions() { List<UriPermission> permissions = contentResolver.getPersistedUriPermissions(); assertThat(permissions).isEmpty(); diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowContextImplTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowContextImplTest.java index 2ff62ac04..647307155 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowContextImplTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowContextImplTest.java @@ -1,7 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2; import static android.os.Build.VERSION_CODES.KITKAT; import static android.os.Build.VERSION_CODES.LOLLIPOP; import static android.os.Build.VERSION_CODES.N; @@ -87,26 +85,6 @@ public class ShadowContextImplTest { .isFalse(); } - @Config(maxSdk = JELLY_BEAN_MR2) - @Test - public void getExternalFilesDir_withType_returnFolderWithGivenTypeName() { - File file = context.getExternalFilesDir("something"); - assertThat(file.isDirectory()).isTrue(); - assertThat(file.canWrite()).isTrue(); - assertThat(file.getName()).isEqualTo("something"); - assertThat(file.getParentFile().getName()).isEqualTo(context.getPackageName()); - } - - @Config(maxSdk = JELLY_BEAN_MR2) - @Test - public void getExternalFilesDir_withNullType_returnFolderWithPackageName() { - File file = context.getExternalFilesDir(null); - assertThat(file.isDirectory()).isTrue(); - assertThat(file.canWrite()).isTrue(); - assertThat(file.getName()).isEqualTo(context.getPackageName()); - } - - @Config(minSdk = KITKAT) @Test public void getExternalFilesDirs_withType_returnFolderWithGivenTypeName() { File[] dirs = context.getExternalFilesDirs("something"); @@ -117,7 +95,6 @@ public class ShadowContextImplTest { assertThat(dirs[0].getParentFile().getName()).isEqualTo(context.getPackageName()); } - @Config(minSdk = KITKAT) @Test public void getExternalFilesDirs_withNullType_returnFolderWithPackageName() { File[] dirs = context.getExternalFilesDirs(null); @@ -128,7 +105,6 @@ public class ShadowContextImplTest { } @Test - @Config(minSdk = JELLY_BEAN_MR2) public void getSystemService_shouldReturnBluetoothAdapter() { assertThat(context.getSystemService(Context.BLUETOOTH_SERVICE)) .isInstanceOf(BluetoothManager.class); @@ -352,7 +328,6 @@ public class ShadowContextImplTest { } @Test - @Config(minSdk = JELLY_BEAN_MR1) public void sendBroadcastAsUser_sendBroadcast() { UserHandle userHandle = Process.myUserHandle(); String action = "foo-action"; @@ -365,7 +340,6 @@ public class ShadowContextImplTest { } @Test - @Config(minSdk = JELLY_BEAN_MR1) public void sendOrderedBroadcastAsUser_sendsBroadcast() { UserHandle userHandle = Process.myUserHandle(); String action = "foo-action"; @@ -409,13 +383,11 @@ public class ShadowContextImplTest { } @Test - @Config(minSdk = JELLY_BEAN_MR2) public void getUserId_returns0() { assertThat(context.getUserId()).isEqualTo(0); } @Test - @Config(minSdk = JELLY_BEAN_MR2) public void getUserId_userIdHasBeenSet_returnsCorrectUserId() { int userId = 10; shadowContext.setUserId(userId); diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowContextTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowContextTest.java index b93dc589d..51f71d358 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowContextTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowContextTest.java @@ -1,7 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; -import static android.os.Build.VERSION_CODES.KITKAT; import static android.os.Build.VERSION_CODES.P; import static com.google.common.truth.Truth.assertThat; import static java.nio.charset.StandardCharsets.UTF_8; @@ -34,7 +32,6 @@ public class ShadowContextTest { private final Context context = ApplicationProvider.getApplicationContext(); @Test - @Config(minSdk = JELLY_BEAN_MR1) public void createConfigurationContext() { Configuration configuration = new Configuration(context.getResources().getConfiguration()); configuration.mcc = 234; @@ -121,13 +118,11 @@ public class ShadowContextTest { } @Test - @Config(minSdk = KITKAT) public void getExternalCacheDirs_nonEmpty() { assertThat(context.getExternalCacheDirs()).isNotEmpty(); } @Test - @Config(minSdk = KITKAT) public void getExternalCacheDirs_createsDirectories() { File[] externalCacheDirs = context.getExternalCacheDirs(); for (File d : externalCacheDirs) { diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowContextWrapperTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowContextWrapperTest.java index 03d9d732c..502e1a4be 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowContextWrapperTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowContextWrapperTest.java @@ -2,7 +2,6 @@ package org.robolectric.shadows; import static android.content.pm.PackageManager.PERMISSION_DENIED; import static android.content.pm.PackageManager.PERMISSION_GRANTED; -import static android.os.Build.VERSION_CODES.KITKAT; import static android.os.Build.VERSION_CODES.M; import static android.os.Build.VERSION_CODES.P; import static com.google.common.truth.Truth.assertThat; @@ -317,7 +316,6 @@ public class ShadowContextWrapperTest { } @Test - @Config(minSdk = KITKAT) public void sendOrderedBroadcastAsUser_shouldReturnValues() throws Exception { String action = "test"; diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowDateUtilsTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowDateUtilsTest.java index 5cc9e8bff..77826d059 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowDateUtilsTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowDateUtilsTest.java @@ -1,7 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2; -import static android.os.Build.VERSION_CODES.KITKAT; import static android.os.Build.VERSION_CODES.LOLLIPOP_MR1; import static android.os.Build.VERSION_CODES.M; import static com.google.common.truth.Truth.assertThat; @@ -31,8 +29,8 @@ public class ShadowDateUtilsTest { } @Test - @Config(minSdk = KITKAT, maxSdk = LOLLIPOP_MR1) - public void formatDateTime_withCurrentYear_worksSinceKitKat() { + @Config(maxSdk = LOLLIPOP_MR1) + public void formatDateTime_withCurrentYear() { final long millisAtStartOfYear = getMillisAtStartOfYear(); String actual = @@ -57,18 +55,6 @@ public class ShadowDateUtilsTest { } @Test - @Config(maxSdk = JELLY_BEAN_MR2) - public void formatDateTime_withCurrentYear_worksPreKitKat() { - Calendar calendar = Calendar.getInstance(); - final int currentYear = calendar.get(Calendar.YEAR); - final long millisAtStartOfYear = getMillisAtStartOfYear(); - - String actual = - DateUtils.formatDateTime(context, millisAtStartOfYear, DateUtils.FORMAT_NUMERIC_DATE); - assertThat(actual).isEqualTo("1/1/" + currentYear); - } - - @Test public void formatDateTime_withPastYear() { String actual = DateUtils.formatDateTime(context, 1420099200000L, DateUtils.FORMAT_NUMERIC_DATE); diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowDevicePolicyManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowDevicePolicyManagerTest.java index af4194a89..0767e8b3d 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowDevicePolicyManagerTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowDevicePolicyManagerTest.java @@ -18,7 +18,6 @@ import static android.app.admin.DevicePolicyManager.STATE_USER_SETUP_COMPLETE; import static android.app.admin.DevicePolicyManager.STATE_USER_SETUP_FINALIZED; import static android.app.admin.DevicePolicyManager.STATE_USER_SETUP_INCOMPLETE; import static android.app.admin.DevicePolicyManager.STATE_USER_UNMANAGED; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2; import static android.os.Build.VERSION_CODES.LOLLIPOP; import static android.os.Build.VERSION_CODES.LOLLIPOP_MR1; import static android.os.Build.VERSION_CODES.M; @@ -98,7 +97,6 @@ public final class ShadowDevicePolicyManagerTest { } @Test - @Config(minSdk = JELLY_BEAN_MR2) public void isDeviceOwnerAppShouldReturnFalseForNonDeviceOwnerApp() { // GIVEN an test package which is not the device owner app of the device String testPackage = testComponent.getPackageName(); @@ -121,7 +119,6 @@ public final class ShadowDevicePolicyManagerTest { } @Test - @Config(minSdk = JELLY_BEAN_MR2) public void isDeviceOwnerShouldReturnTrueForDeviceOwner() { // GIVEN an test package which is the device owner app of the device String testPackage = testComponent.getPackageName(); @@ -133,7 +130,6 @@ public final class ShadowDevicePolicyManagerTest { } @Test - @Config(minSdk = JELLY_BEAN_MR2) public void getDeviceOwnerShouldReturnDeviceOwnerPackageName() { // GIVEN an test package which is the device owner app of the device String testPackage = testComponent.getPackageName(); @@ -145,7 +141,6 @@ public final class ShadowDevicePolicyManagerTest { } @Test - @Config(minSdk = JELLY_BEAN_MR2) public void getDeviceOwnerShouldReturnNullWhenThereIsNoDeviceOwner() { // WHEN DevicePolicyManager#getProfileOwner is called without a device owner // THEN the method should return null @@ -291,7 +286,6 @@ public final class ShadowDevicePolicyManagerTest { } @Test - @Config(minSdk = JELLY_BEAN_MR2) public void getActiveAdminsShouldReturnDeviceOwner() { // GIVEN an test package which is the device owner app of the device shadowOf(devicePolicyManager).setDeviceOwner(testComponent); diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowDialogTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowDialogTest.java index f1270f3d8..e5561e9f7 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowDialogTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowDialogTest.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.KITKAT; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -25,7 +24,6 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.R; -import org.robolectric.annotation.Config; @RunWith(AndroidJUnit4.class) public class ShadowDialogTest { @@ -186,7 +184,6 @@ public class ShadowDialogTest { } @Test - @Config(minSdk = KITKAT) public void show_shouldWorkWithAPI19() { Dialog dialog = new Dialog(context); dialog.show(); diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowDisplayEventReceiverTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowDisplayEventReceiverTest.java index d3bc403af..9a5f4538f 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowDisplayEventReceiverTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowDisplayEventReceiverTest.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.JELLY_BEAN; import static com.google.common.truth.Truth.assertThat; import android.os.Looper; @@ -10,7 +9,6 @@ import dalvik.system.CloseGuard; import java.util.concurrent.atomic.AtomicBoolean; import org.junit.Test; import org.junit.runner.RunWith; -import org.robolectric.annotation.Config; /** Tests for {@link ShadowDisplayEventReceiver}. */ @RunWith(AndroidJUnit4.class) @@ -29,7 +27,6 @@ public class ShadowDisplayEventReceiverTest { } @Test - @Config(minSdk = JELLY_BEAN) public void closeGuard_autoCloses() throws Throwable { final AtomicBoolean closeGuardWarned = new AtomicBoolean(false); CloseGuard.Reporter originalReporter = CloseGuard.getReporter(); diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowDisplayManagerGlobalTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowDisplayManagerGlobalTest.java index 530dccfbe..8c509d0b7 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowDisplayManagerGlobalTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowDisplayManagerGlobalTest.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; import static com.google.common.truth.Truth.assertThat; import android.hardware.display.DisplayManagerGlobal; @@ -8,7 +7,6 @@ import android.view.Display; import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; -import org.robolectric.annotation.Config; import org.robolectric.annotation.experimental.LazyApplication; import org.robolectric.annotation.experimental.LazyApplication.LazyLoad; @@ -18,7 +16,6 @@ public class ShadowDisplayManagerGlobalTest { @LazyApplication(LazyLoad.ON) @Test - @Config(minSdk = JELLY_BEAN_MR1) public void testDisplayManagerGlobalIsLazyLoaded() { assertThat(ShadowDisplayManagerGlobal.getGlobalInstance()).isNull(); assertThat(DisplayManagerGlobal.getInstance().getRealDisplay(Display.DEFAULT_DISPLAY)) diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowDisplayManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowDisplayManagerTest.java index c92b389f6..5c9228e89 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowDisplayManagerTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowDisplayManagerTest.java @@ -1,7 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.JELLY_BEAN; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; import static android.os.Build.VERSION_CODES.P; import static android.os.Build.VERSION_CODES.Q; import static com.google.common.truth.Truth.assertThat; @@ -23,6 +21,7 @@ import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import java.util.ArrayList; import java.util.List; +import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -42,18 +41,6 @@ public class ShadowDisplayManagerTest { } @Test - @Config(maxSdk = JELLY_BEAN) - public void notSupportedInJellyBean() { - try { - ShadowDisplayManager.removeDisplay(0); - fail("Expected Exception thrown"); - } catch (UnsupportedOperationException e) { - assertThat(e).hasMessageThat().contains("displays not supported in Jelly Bean"); - } - } - - @Test - @Config(minSdk = JELLY_BEAN_MR1) public void getDisplayInfo_shouldReturnCopy() { DisplayInfo displayInfo = getGlobal().getDisplayInfo(Display.DEFAULT_DISPLAY); int origAppWidth = displayInfo.appWidth; @@ -63,13 +50,11 @@ public class ShadowDisplayManagerTest { } @Test - @Config(minSdk = JELLY_BEAN_MR1) public void forNonexistentDisplay_getDisplayInfo_shouldReturnNull() { assertThat(getGlobal().getDisplayInfo(3)).isEqualTo(null); } @Test - @Config(minSdk = JELLY_BEAN_MR1) public void forNonexistentDisplay_changeDisplay_shouldThrow() { try { ShadowDisplayManager.changeDisplay(3, ""); @@ -80,7 +65,6 @@ public class ShadowDisplayManagerTest { } @Test - @Config(minSdk = JELLY_BEAN_MR1) public void forNonexistentDisplay_removeDisplay_shouldThrow() { try { ShadowDisplayManager.removeDisplay(3); @@ -91,7 +75,6 @@ public class ShadowDisplayManagerTest { } @Test - @Config(minSdk = JELLY_BEAN_MR1) public void addDisplay() { int displayId = ShadowDisplayManager.addDisplay("w100dp-h200dp"); assertThat(displayId).isGreaterThan(0); @@ -106,7 +89,6 @@ public class ShadowDisplayManagerTest { } @Test - @Config(minSdk = JELLY_BEAN_MR1) public void addDisplay_withName_shouldReflectInAddedDisplay() { int displayId = ShadowDisplayManager.addDisplay("w100dp-h200dp", "VirtualDevice_1"); assertThat(displayId).isGreaterThan(0); @@ -121,7 +103,6 @@ public class ShadowDisplayManagerTest { } @Test - @Config(minSdk = JELLY_BEAN_MR1) public void addDisplay_shouldNotifyListeners() { List<String> events = new ArrayList<>(); instance.registerDisplayListener(new MyDisplayListener(events), null); @@ -130,7 +111,6 @@ public class ShadowDisplayManagerTest { } @Test - @Config(minSdk = JELLY_BEAN_MR1) public void changeDisplay_shouldUpdateSmallestAndLargestNominalWidthAndHeight() { Point smallest = new Point(); Point largest = new Point(); @@ -150,7 +130,6 @@ public class ShadowDisplayManagerTest { } @Test - @Config(minSdk = JELLY_BEAN_MR1) public void withQualifiers_changeDisplay_shouldUpdateSmallestAndLargestNominalWidthAndHeight() { Point smallest = new Point(); Point largest = new Point(); @@ -168,7 +147,6 @@ public class ShadowDisplayManagerTest { } @Test - @Config(minSdk = JELLY_BEAN_MR1) public void changeAndRemoveDisplay_shouldNotifyListeners() { List<String> events = new ArrayList<>(); instance.registerDisplayListener(new MyDisplayListener(events), null); @@ -188,7 +166,6 @@ public class ShadowDisplayManagerTest { } @Test - @Config(minSdk = JELLY_BEAN_MR1) public void changeDisplay_shouldAllowPartialChanges() { List<String> events = new ArrayList<>(); instance.registerDisplayListener(new MyDisplayListener(events), null); @@ -471,7 +448,6 @@ public class ShadowDisplayManagerTest { } @Test - @Config(minSdk = JELLY_BEAN_MR1) public void setNaturallyPortrait_setPortrait_isRotatedWhenLandscape() { ShadowDisplayManager.setNaturallyPortrait(Display.DEFAULT_DISPLAY, true); @@ -481,7 +457,6 @@ public class ShadowDisplayManagerTest { } @Test - @Config(minSdk = JELLY_BEAN_MR1) public void setNaturallyPortrait_setPortraitWhenLandscape_isRotated() { ShadowDisplayManager.changeDisplay(Display.DEFAULT_DISPLAY, "land"); @@ -491,7 +466,6 @@ public class ShadowDisplayManagerTest { } @Test - @Config(minSdk = JELLY_BEAN_MR1) public void setNaturallyPortrait_setLandscape_isNotRotatedWhenLandscape() { ShadowDisplayManager.setNaturallyPortrait(Display.DEFAULT_DISPLAY, false); @@ -501,7 +475,6 @@ public class ShadowDisplayManagerTest { } @Test - @Config(minSdk = JELLY_BEAN_MR1) public void setNaturallyPortrait_setLandscape_isRotatedWhenPortrait() { ShadowDisplayManager.setNaturallyPortrait(Display.DEFAULT_DISPLAY, false); @@ -511,7 +484,6 @@ public class ShadowDisplayManagerTest { } @Test - @Config(minSdk = JELLY_BEAN_MR1) public void setNaturallyPortrait_setLandscapeWhenLandscape_isNotRotated() { ShadowDisplayManager.changeDisplay(Display.DEFAULT_DISPLAY, "land"); @@ -520,6 +492,20 @@ public class ShadowDisplayManagerTest { assertThat(ShadowDisplay.getDefaultDisplay().getRotation()).isEqualTo(Surface.ROTATION_0); } + @Test + public void configureDefaultDisplay_calledTwice_showsReasonableException() { + IllegalStateException e = + Assert.assertThrows( + IllegalStateException.class, + () -> ShadowDisplayManager.configureDefaultDisplay(null, null)); + + assertThat(e).hasMessageThat().contains("configureDefaultDisplay should only be called once"); + assertThat(e) + .hasCauseThat() + .hasMessageThat() + .contains("configureDefaultDisplay was called a second time"); + } + // because DisplayManagerGlobal don't exist in Jelly Bean, // and we don't want them resolved as part of the test class. static class HideFromJB { diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowDisplayTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowDisplayTest.java index 5149161ed..361312110 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowDisplayTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowDisplayTest.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; import static android.os.Build.VERSION_CODES.LOLLIPOP; import static android.os.Build.VERSION_CODES.M; import static android.os.Build.VERSION_CODES.N; @@ -28,7 +27,6 @@ import org.robolectric.Shadows; import org.robolectric.annotation.Config; @RunWith(AndroidJUnit4.class) -@Config(minSdk = JELLY_BEAN_MR1) public class ShadowDisplayTest { private Display display; diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowDistanceMeasurementManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowDistanceMeasurementManagerTest.java new file mode 100644 index 000000000..74459fda3 --- /dev/null +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowDistanceMeasurementManagerTest.java @@ -0,0 +1,164 @@ +package org.robolectric.shadows; + +import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE; +import static com.google.common.truth.Truth.assertThat; +import static org.robolectric.Shadows.shadowOf; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothManager; +import android.bluetooth.BluetoothStatusCodes; +import android.bluetooth.le.DistanceMeasurementManager; +import android.bluetooth.le.DistanceMeasurementMethod; +import android.bluetooth.le.DistanceMeasurementParams; +import android.bluetooth.le.DistanceMeasurementResult; +import android.bluetooth.le.DistanceMeasurementSession; +import android.content.Context; +import androidx.annotation.NonNull; +import androidx.test.core.app.ApplicationProvider; +import com.google.common.collect.ImmutableList; +import com.google.common.util.concurrent.MoreExecutors; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Executor; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; +import org.robolectric.shadow.api.Shadow; + +/** Unit tests for {@link ShadowDistanceMeasurementManager}. */ +@RunWith(RobolectricTestRunner.class) +@Config(minSdk = UPSIDE_DOWN_CAKE) +public class ShadowDistanceMeasurementManagerTest { + + private static final String REMOTE_ADDRESS = "11:22:33:AA:BB:CC"; + + private int onStartFailReason; + private int onStoppedReason; + + private final Context context = ApplicationProvider.getApplicationContext(); + private final BluetoothAdapter adapter = + context.getSystemService(BluetoothManager.class).getAdapter(); + private final BluetoothDevice remoteDevice = adapter.getRemoteDevice(REMOTE_ADDRESS); + private final Executor executor = MoreExecutors.directExecutor(); + + private Object distanceMeasurementManager; + private ShadowDistanceMeasurementManager shadowDistanceMeasurementManager; + + private final Object params = new DistanceMeasurementParams.Builder(remoteDevice).build(); + private Object startedDistanceMeasurementSession; + private final List<DistanceMeasurementResult> distanceMeasurementResults = new ArrayList<>(); + private final Object distanceMeasurementSessionCallback = + new DistanceMeasurementSession.Callback() { + @Override + public void onStarted(@NonNull DistanceMeasurementSession session) { + startedDistanceMeasurementSession = session; + } + + @Override + public void onStartFail(int reason) { + onStartFailReason = reason; + } + + @Override + public void onStopped(@NonNull DistanceMeasurementSession session, int reason) { + onStoppedReason = reason; + } + + @Override + public void onResult( + @NonNull BluetoothDevice device, @NonNull DistanceMeasurementResult result) { + distanceMeasurementResults.add(result); + } + }; + + @Before + public void setUp() { + shadowOf(adapter).setDistanceMeasurementSupported(BluetoothStatusCodes.FEATURE_SUPPORTED); + distanceMeasurementManager = adapter.getDistanceMeasurementManager(); + shadowDistanceMeasurementManager = Shadow.extract(distanceMeasurementManager); + } + + @Test + public void getSupportedMethods_whenMethodsSet_returnsSetMethods() { + DistanceMeasurementMethod methodAuto = + new DistanceMeasurementMethod.Builder( + DistanceMeasurementMethod.DISTANCE_MEASUREMENT_METHOD_AUTO) + .build(); + DistanceMeasurementMethod methodRssi = + new DistanceMeasurementMethod.Builder( + DistanceMeasurementMethod.DISTANCE_MEASUREMENT_METHOD_RSSI) + .build(); + + shadowDistanceMeasurementManager.setSupportedMethods(ImmutableList.of(methodAuto, methodRssi)); + + assertThat(((DistanceMeasurementManager) distanceMeasurementManager).getSupportedMethods()) + .containsExactly(methodAuto, methodRssi); + } + + @Test + public void startMeasurementSession_onSessionSuccess_invokesOnResult() { + ((DistanceMeasurementManager) distanceMeasurementManager) + .startMeasurementSession( + (DistanceMeasurementParams) params, + executor, + (DistanceMeasurementSession.Callback) distanceMeasurementSessionCallback); + shadowDistanceMeasurementManager.simulateOnResult( + remoteDevice, new DistanceMeasurementResult.Builder(123, 12).build()); + shadowDistanceMeasurementManager.simulateOnResult( + remoteDevice, new DistanceMeasurementResult.Builder(321, 21).build()); + shadowDistanceMeasurementManager.simulateSuccessfulTermination(remoteDevice); + + assertThat(startedDistanceMeasurementSession).isNotNull(); + assertThat(distanceMeasurementResults.get(0).getResultMeters()).isEqualTo(123); + assertThat(distanceMeasurementResults.get(0).getErrorMeters()).isEqualTo(12); + assertThat(distanceMeasurementResults.get(1).getResultMeters()).isEqualTo(321); + assertThat(distanceMeasurementResults.get(1).getErrorMeters()).isEqualTo(21); + } + + @Test + public void startMeasurementSession_onSessionStartFailed_invokesOnStartFail() { + ((DistanceMeasurementManager) distanceMeasurementManager) + .startMeasurementSession( + (DistanceMeasurementParams) params, + executor, + (DistanceMeasurementSession.Callback) distanceMeasurementSessionCallback); + shadowDistanceMeasurementManager.simulateOnStartFailError( + remoteDevice, BluetoothStatusCodes.ERROR_UNKNOWN); + + assertThat(startedDistanceMeasurementSession).isNull(); + assertThat(onStartFailReason).isEqualTo(BluetoothStatusCodes.ERROR_UNKNOWN); + assertThat(distanceMeasurementResults).isEmpty(); + } + + @Test + public void startMeasurementSession_onSessionStopped_invokesOnStopped() { + ((DistanceMeasurementManager) distanceMeasurementManager) + .startMeasurementSession( + (DistanceMeasurementParams) params, + executor, + (DistanceMeasurementSession.Callback) distanceMeasurementSessionCallback); + shadowDistanceMeasurementManager.simulateOnStoppedError( + remoteDevice, BluetoothStatusCodes.ERROR_UNKNOWN); + + assertThat(startedDistanceMeasurementSession).isNotNull(); + assertThat(onStoppedReason).isEqualTo(BluetoothStatusCodes.ERROR_UNKNOWN); + assertThat(distanceMeasurementResults).isEmpty(); + } + + @Test + public void startMeasurementSession_onTimeout_invokeOnStoppedWithTimeoutError() { + ((DistanceMeasurementManager) distanceMeasurementManager) + .startMeasurementSession( + (DistanceMeasurementParams) params, + executor, + (DistanceMeasurementSession.Callback) distanceMeasurementSessionCallback); + shadowDistanceMeasurementManager.simulateTimeout(remoteDevice); + + assertThat(startedDistanceMeasurementSession).isNotNull(); + assertThat(onStoppedReason).isEqualTo(BluetoothStatusCodes.ERROR_TIMEOUT); + assertThat(distanceMeasurementResults).isEmpty(); + } +} diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowDownloadManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowDownloadManagerTest.java index 914c8eb07..dd0be08e6 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowDownloadManagerTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowDownloadManagerTest.java @@ -129,6 +129,7 @@ public class ShadowDownloadManagerTest { assertThat(cursor.getColumnIndex(DownloadManager.COLUMN_TITLE)).isAtLeast(0); assertThat(cursor.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES)).isAtLeast(0); assertThat(cursor.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR)).isAtLeast(0); + assertThat(cursor.getColumnIndex(DownloadManager.COLUMN_ID)).isAtLeast(0); } @Test @@ -192,6 +193,23 @@ public class ShadowDownloadManagerTest { } @Test + public void query_shouldGetColumnId() { + ShadowDownloadManager manager = new ShadowDownloadManager(); + long firstId = manager.enqueue(request); + Request secondRequest = new Request(Uri.parse("http://example.com/foo2.mp4")); + long secondId = manager.enqueue(secondRequest); + + try (Cursor cursor = manager.query(new DownloadManager.Query())) { + cursor.moveToFirst(); + assertThat(cursor.getLong(cursor.getColumnIndex(DownloadManager.COLUMN_ID))) + .isEqualTo(firstId); + cursor.moveToNext(); + assertThat(cursor.getLong(cursor.getColumnIndex(DownloadManager.COLUMN_ID))) + .isEqualTo(secondId); + } + } + + @Test public void request_shouldSetDestinationInExternalPublicDir_publicDirectories() throws Exception { shadow.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, "foo.mp4"); diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowEnvironmentTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowEnvironmentTest.java index 7fdf54c6e..3f5d8ea78 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowEnvironmentTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowEnvironmentTest.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.KITKAT; import static android.os.Build.VERSION_CODES.LOLLIPOP; import static android.os.Build.VERSION_CODES.LOLLIPOP_MR1; import static android.os.Build.VERSION_CODES.M; @@ -241,7 +240,6 @@ public class ShadowEnvironmentTest { // TODO: failing test @Ignore @Test - @Config(minSdk = KITKAT) public void getExternalFilesDirs() throws Exception { ShadowEnvironment.addExternalDir("external_dir_1"); ShadowEnvironment.addExternalDir("external_dir_2"); @@ -261,7 +259,7 @@ public class ShadowEnvironmentTest { } @Test - @Config(minSdk = KITKAT, maxSdk = LOLLIPOP) + @Config(maxSdk = LOLLIPOP) public void getExternalStorageStatePreLollipopMR1() { File storageDir1 = ShadowEnvironment.addExternalDir("dir1"); File storageDir2 = ShadowEnvironment.addExternalDir("dir2"); diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowFloatMathTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowFloatMathTest.java index 65afb9c00..1470b0241 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowFloatMathTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowFloatMathTest.java @@ -1,19 +1,16 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.JELLY_BEAN; import static com.google.common.truth.Truth.assertThat; import android.util.FloatMath; import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; -import org.robolectric.annotation.Config; /** * Tests for {@link FloatMath}. On SDKs < 23, {@link FloatMath} was implemented using native * methods. */ -@Config(minSdk = JELLY_BEAN) @RunWith(AndroidJUnit4.class) public class ShadowFloatMathTest { diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowICUTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowICUTest.java index 4f32159e2..28a990362 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowICUTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowICUTest.java @@ -65,6 +65,25 @@ public class ShadowICUTest { @Test @Config(minSdk = LOLLIPOP) + public void getBestDateTimePattern_returns_yMMMd_ptBR() { + assertThat(ICU.getBestDateTimePattern("yMMMd", new Locale("pt", "BR"))).isEqualTo("MMM d, y"); + } + + @Test + @Config(minSdk = LOLLIPOP) + public void getBestDateTimePattern_returns_yMMMMEEEEd_ptBR() { + assertThat(ICU.getBestDateTimePattern("yMMMMEEEEd", new Locale("pt", "BR"))) + .isEqualTo("EEEE, MMMM d, y"); + } + + @Test + @Config(minSdk = LOLLIPOP) + public void getBestDateTimePattern_returns_yMMMM_ptBR() { + assertThat(ICU.getBestDateTimePattern("yMMMM", new Locale("pt", "BR"))).isEqualTo("MMMM y"); + } + + @Test + @Config(minSdk = LOLLIPOP) public void datePickerShouldNotCrashWhenAskingForBestDateTimePattern() { ActivityController<DatePickerActivity> activityController = Robolectric.buildActivity(DatePickerActivity.class); diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowImageReaderTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowImageReaderTest.java index acc8d8804..b3aee4ca8 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowImageReaderTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowImageReaderTest.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.KITKAT; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertThrows; @@ -16,11 +15,9 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicBoolean; import org.junit.Test; import org.junit.runner.RunWith; -import org.robolectric.annotation.Config; /** Tests for {@link ShadowImageReader}. */ @RunWith(AndroidJUnit4.class) -@Config(minSdk = KITKAT) public class ShadowImageReaderTest { private static final int WIDTH = 640; private static final int HEIGHT = 480; diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowInputDeviceTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowInputDeviceTest.java index 7ddb3f760..557f1c7ae 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowInputDeviceTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowInputDeviceTest.java @@ -1,13 +1,11 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.KITKAT; import static com.google.common.truth.Truth.assertThat; import android.view.InputDevice; import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; -import org.robolectric.annotation.Config; import org.robolectric.shadow.api.Shadow; @RunWith(AndroidJUnit4.class) @@ -19,7 +17,6 @@ public class ShadowInputDeviceTest { } @Test - @Config(minSdk = KITKAT) public void canChangeProductId() { InputDevice inputDevice = ShadowInputDevice.makeInputDeviceNamed("foo"); ShadowInputDevice shadowInputDevice = Shadow.extract(inputDevice); @@ -29,7 +26,6 @@ public class ShadowInputDeviceTest { } @Test - @Config(minSdk = KITKAT) public void canChangeVendorId() { InputDevice inputDevice = ShadowInputDevice.makeInputDeviceNamed("foo"); ShadowInputDevice shadowInputDevice = Shadow.extract(inputDevice); diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowInputEventReceiverTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowInputEventReceiverTest.java index 2a4219b35..f7d97df31 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowInputEventReceiverTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowInputEventReceiverTest.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.JELLY_BEAN; import static com.google.common.truth.Truth.assertThat; import android.os.Looper; @@ -11,7 +10,6 @@ import dalvik.system.CloseGuard; import java.util.concurrent.atomic.AtomicBoolean; import org.junit.Test; import org.junit.runner.RunWith; -import org.robolectric.annotation.Config; /** Tests for {@link ShadowInputEventReceiver}. */ @RunWith(AndroidJUnit4.class) @@ -30,7 +28,6 @@ public class ShadowInputEventReceiverTest { } @Test - @Config(minSdk = JELLY_BEAN) public void closeGuard_autoCloses() throws Throwable { final AtomicBoolean closeGuardWarned = new AtomicBoolean(false); CloseGuard.Reporter originalReporter = CloseGuard.getReporter(); diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowInputMethodManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowInputMethodManagerTest.java index 69a0e2f6c..11ccfda69 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowInputMethodManagerTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowInputMethodManagerTest.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.KITKAT; import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @@ -22,7 +21,6 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.Shadows; -import org.robolectric.annotation.Config; @RunWith(AndroidJUnit4.class) public class ShadowInputMethodManagerTest { @@ -125,8 +123,6 @@ public class ShadowInputMethodManagerTest { assertThat(shadow.getCurrentInputMethodSubtype()).isNull(); } - /** The builder is only available for 19+. */ - @Config(minSdk = KITKAT) @Test public void setCurrentInputMethodSubtype_isReturned() { InputMethodSubtype inputMethodSubtype = new InputMethodSubtypeBuilder().build(); diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowInstrumentationTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowInstrumentationTest.java index c611a403a..daafcdd19 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowInstrumentationTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowInstrumentationTest.java @@ -1,7 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2; import static android.os.Build.VERSION_CODES.N; import static android.os.Build.VERSION_CODES.N_MR1; import static com.google.common.truth.Truth.assertThat; @@ -36,7 +34,7 @@ import org.robolectric.annotation.Config; public class ShadowInstrumentationTest { @Test - @Config(minSdk = JELLY_BEAN_MR1, maxSdk = N_MR1) + @Config(maxSdk = N_MR1) public void testExecStartActivity_handledProperlyForSDK17to25() throws Exception { Instrumentation instrumentation = ((ActivityThread) RuntimeEnvironment.getActivityThread()).getInstrumentation(); @@ -145,7 +143,6 @@ public class ShadowInstrumentationTest { assertThat(decorView.isInTouchMode()).isTrue(); } - @Config(minSdk = JELLY_BEAN_MR2) @Test public void getUiAutomation() { assertThat(InstrumentationRegistry.getInstrumentation().getUiAutomation()).isNotNull(); diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowIsoDepTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowIsoDepTest.java index 3d7636fd0..ed6f37155 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowIsoDepTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowIsoDepTest.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.KITKAT; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.fail; import static org.robolectric.Shadows.shadowOf; @@ -12,11 +11,9 @@ import java.util.concurrent.Callable; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.robolectric.annotation.Config; /** Unit tests for {@link ShadowIsoDep}. */ @RunWith(AndroidJUnit4.class) -@Config(minSdk = KITKAT) public final class ShadowIsoDepTest { private IsoDep isoDep; diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowLegacyMessageQueueTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowLegacyMessageQueueTest.java index 89892fa86..c2c41b07a 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowLegacyMessageQueueTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowLegacyMessageQueueTest.java @@ -9,7 +9,6 @@ import static org.robolectric.util.ReflectionHelpers.callInstanceMethod; import static org.robolectric.util.ReflectionHelpers.setField; import static org.robolectric.util.reflector.Reflector.reflector; -import android.os.Build; import android.os.Handler; import android.os.Looper; import android.os.Message; @@ -74,7 +73,7 @@ public class ShadowLegacyMessageQueueTest { scheduler = shadowQueue.getScheduler(); scheduler.pause(); testMessage = handler.obtainMessage(); - quitField = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT ? "mQuitting" : "mQuiting"; + quitField = "mQuitting"; } @Test diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowLegacySystemClockTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowLegacySystemClockTest.java index 529c65689..2ae90cd60 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowLegacySystemClockTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowLegacySystemClockTest.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; import static android.os.Build.VERSION_CODES.P; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertFalse; @@ -56,7 +55,7 @@ public class ShadowLegacySystemClockTest { assertThat(SystemClock.elapsedRealtime()).isEqualTo(1034); } - @Test @Config(minSdk = JELLY_BEAN_MR1) + @Test public void testElapsedRealtimeNanos() { Robolectric.getForegroundThreadScheduler().advanceTo(1000); assertThat(SystemClock.elapsedRealtimeNanos()).isEqualTo(1000000000); diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowLinuxTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowLinuxTest.java index 366daf956..aa75f2195 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowLinuxTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowLinuxTest.java @@ -1,6 +1,7 @@ package org.robolectric.shadows; import static android.os.Build.VERSION_CODES.O; +import static android.os.Build.VERSION_CODES.R; import static com.google.common.truth.Truth.assertThat; import static java.nio.charset.StandardCharsets.UTF_8; @@ -58,6 +59,14 @@ public final class ShadowLinuxTest { } @Test + @Config(minSdk = R) + public void memfdCreate_returnNoneNullFileDescriptor() throws Exception { + FileDescriptor arscFile = + shadowLinux.memfd_create("remote_views_theme_colors.arsc", /* flags= */ 0); + assertThat(arscFile).isNotNull(); + } + + @Test public void pread_validateExtractsContentWithOffset() throws Exception { try (FileInputStream fis = new FileInputStream(file)) { FileDescriptor fd = fis.getFD(); diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowLocaleDataTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowLocaleDataTest.java index 862d2a623..f039fd76e 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowLocaleDataTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowLocaleDataTest.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; import static android.os.Build.VERSION_CODES.LOLLIPOP; import static android.os.Build.VERSION_CODES.LOLLIPOP_MR1; import static android.os.Build.VERSION_CODES.M; @@ -127,9 +126,7 @@ public class ShadowLocaleDataTest { } @Test - @Config(minSdk = JELLY_BEAN_MR1) - public void shouldSupportLocaleEn_US_since_jelly_bean_mr1() - throws NoSuchFieldException, IllegalAccessException { + public void shouldSupportLocaleEn_US_since() throws NoSuchFieldException, IllegalAccessException { LocaleData localeData = LocaleData.get(Locale.US); LocaleDataReflector localeDataReflector = reflector(LocaleDataReflector.class, localeData); diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowLocationManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowLocationManagerTest.java index 5add1c81c..e9e51f909 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowLocationManagerTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowLocationManagerTest.java @@ -116,7 +116,6 @@ public class ShadowLocationManagerTest { } @Test - @Config(minSdk = VERSION_CODES.KITKAT) public void testGetProvider() { LocationProvider p; diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowLogTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowLogTest.java index 6e351319e..01054fd75 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowLogTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowLogTest.java @@ -5,10 +5,12 @@ import static com.google.common.truth.Truth.assertThat; import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; import android.util.Log; +import android.util.Log.TerribleFailure; +import android.util.Log.TerribleFailureHandler; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.common.collect.Iterables; import java.io.ByteArrayOutputStream; @@ -16,7 +18,9 @@ import java.io.PrintStream; import java.util.List; import org.junit.Test; import org.junit.runner.RunWith; +import org.robolectric.annotation.Config; import org.robolectric.shadows.ShadowLog.LogItem; +import org.robolectric.versioning.AndroidVersions.L; @RunWith(AndroidJUnit4.class) public class ShadowLogTest { @@ -129,12 +133,32 @@ public class ShadowLogTest { ShadowLog.setWtfIsFatal(true); Throwable throwable = new Throwable(); - try { - Log.wtf("tag", "msg", throwable); - fail("TerribleFailure should be thrown"); - } catch (ShadowLog.TerribleFailure e) { - // pass - } + assertThrows(TerribleFailure.class, () -> Log.wtf("tag", "msg", throwable)); + assertLogged(Log.ASSERT, "tag", "msg", throwable); + } + + @Test + @Config(minSdk = L.SDK_INT) + public void wtf_shouldLogAppropriately_withNewWtfHandler() { + Throwable throwable = new Throwable(); + + String[] captured = new String[1]; + + TerribleFailureHandler prevWtfHandler = + Log.setWtfHandler( + new TerribleFailureHandler() { + @Override + public void onTerribleFailure(String tag, TerribleFailure what, boolean system) { + captured[0] = what.getMessage(); + } + }); + + Log.wtf("tag", "msg", throwable); + // assert that the new handler captures the message + assertEquals("msg", captured[0]); + // reset the handler + Log.setWtfHandler(prevWtfHandler); + assertLogged(Log.ASSERT, "tag", "msg", throwable); } diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowLooperResetterTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowLooperResetterTest.java index 92e1643c5..494882a4a 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowLooperResetterTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowLooperResetterTest.java @@ -8,8 +8,14 @@ import static org.robolectric.Shadows.shadowOf; import android.os.Handler; import android.os.HandlerThread; +import android.os.SystemClock; +import android.view.Choreographer; import java.time.Duration; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; +import org.junit.After; import org.junit.AfterClass; import org.junit.Before; import org.junit.Test; @@ -21,6 +27,7 @@ import org.junit.runner.notification.RunNotifier; import org.junit.runners.JUnit4; import org.junit.runners.model.InitializationError; import org.robolectric.RobolectricTestRunner; +import org.robolectric.util.TimeUtils; /** A specialized test for verifying that looper state is cleared properly between tests. */ @RunWith(JUnit4.class) @@ -188,4 +195,142 @@ public class ShadowLooperResetterTest { // run and assert no failures runner.run(runNotifier); } + + /** Test that uses Choreographer. ShadowLooper should clear Choreographer state between tests */ + public static class ChoreographerResetTest { + + // use a static thread so both tests share the same Looper + Choreographer + static HandlerThread handlerThread; + + @Before + public void init() { + if (handlerThread == null) { + handlerThread = new HandlerThread("ChoreographerResetTest"); + handlerThread.start(); + } + } + + @AfterClass + public static void shutDown() throws InterruptedException { + handlerThread.quit(); + handlerThread.join(); + } + + private void doPostToChoreographerTest() { + checkNotNull(handlerThread.getLooper()); + Handler handler = new Handler(handlerThread.getLooper()); + + AtomicLong frameTimeNanosResult = new AtomicLong(-1); + // you can only access Choreographer from Looper thread + handler.post(() -> Choreographer.getInstance().postFrameCallback(frameTimeNanosResult::set)); + shadowOf(handlerThread.getLooper()).idle(); + + // ensure callback happened and that clock is consistent with Choreographer's frame time + // tracking + assertThat(frameTimeNanosResult.get()) + .isEqualTo(SystemClock.uptimeMillis() * TimeUtils.NANOS_PER_MS); + + // Now set Choreographer so it expects there is a pending vsync+frame callback. + // Choreographer will ignore vsync+frame requests if there is already one pending. + // If Choreographer state isn't clearly properly between tests the next test will fail. + + // Setting Choreographer into paused mode makes this test deterministic, as vsync callbacks + // won't occur until clock has been incremented. + ShadowChoreographer.setPaused(true); + ShadowChoreographer.setFrameDelay(Duration.ofMillis(16)); + + handler.post(() -> Choreographer.getInstance().postFrameCallback(frameTimeNanos -> {})); + shadowOf(handlerThread.getLooper()).idle(); + } + + @Test + public void postToChoreographerTest() { + doPostToChoreographerTest(); + } + + @Test + public void anotherPostToChoreographerTest() { + doPostToChoreographerTest(); + } + } + + @Test + public void choreographerPost() throws InitializationError { + Runner runner = new RobolectricTestRunner(ChoreographerResetTest.class); + + // run and assert no failures + runner.run(runNotifier); + } + + /** Test that uses Choreographer and asynchronously kills thread between tests */ + public static class ChoreographerResetQuitTest { + + // use a static thread so both tests share the same Looper + Choreographer + private HandlerThread handlerThread; + private final Executor executor = Executors.newSingleThreadExecutor(); + + @Before + public void init() { + handlerThread = new HandlerThread("ChoreographerResetQuitTest"); + handlerThread.start(); + } + + @After + public void shutDown() throws InterruptedException { + // asynchronously quit handler thread to try to expose race conditions + executor.execute( + new Runnable() { + @Override + public void run() { + handlerThread.quit(); + } + }); + } + + private void doPostToChoreographerTest() { + checkNotNull(handlerThread.getLooper()); + Handler handler = new Handler(handlerThread.getLooper()); + + AtomicLong frameTimeNanosResult = new AtomicLong(-1); + // you can only access Choreographer from Looper thread + handler.post(() -> Choreographer.getInstance().postFrameCallback(frameTimeNanosResult::set)); + shadowOf(handlerThread.getLooper()).idle(); + + // ensure callback happened and that clock is consistent with Choreographer's frame time + // tracking + assertThat(frameTimeNanosResult.get()) + .isEqualTo(SystemClock.uptimeMillis() * TimeUtils.NANOS_PER_MS); + + // Now set Choreographer so it expects there is a pending vsync+frame callback. + // Choreographer will ignore vsync+frame requests if there is already one pending. + // If Choreographer state isn't clearly properly between tests the next test will fail. + + // Setting Choreographer into paused mode makes this test deterministic, as vsync callbacks + // won't occur until clock has been incremented. + ShadowChoreographer.setPaused(true); + ShadowChoreographer.setFrameDelay(Duration.ofMillis(16)); + + handler.post(() -> Choreographer.getInstance().postFrameCallback(frameTimeNanos -> {})); + shadowOf(handlerThread.getLooper()).idle(); + } + + @Test + public void postToChoreographerTest() { + doPostToChoreographerTest(); + } + + @Test + public void anotherPostToChoreographerTest() { + doPostToChoreographerTest(); + } + } + + /** Tests for potentially race conditions where Looper is quit asynchrounously at end of test */ + @Test + public void choreographerQuitPost() throws InitializationError { + Runner runner = new RobolectricTestRunner(ChoreographerResetQuitTest.class); + + // run and assert no failures + runner.run(runNotifier); + } } diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowMediaActionSoundTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowMediaActionSoundTest.java index ad6aeebdb..d58130f14 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowMediaActionSoundTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowMediaActionSoundTest.java @@ -3,7 +3,6 @@ package org.robolectric.shadows; import static com.google.common.truth.Truth.assertThat; import android.media.MediaActionSound; -import android.os.Build; import android.os.Build.VERSION_CODES; import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Test; @@ -12,7 +11,6 @@ import org.robolectric.annotation.Config; /** Unit tests for {@link org.robolectric.shadows.ShadowMediaActionSound}. */ @RunWith(AndroidJUnit4.class) -@Config(minSdk = Build.VERSION_CODES.JELLY_BEAN) public final class ShadowMediaActionSoundTest { @Test public void getPlayCount_noShutterClickPlayed_zero() { diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowMediaRouterTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowMediaRouterTest.java index 15f1be229..44b2ee8f6 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowMediaRouterTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowMediaRouterTest.java @@ -2,8 +2,6 @@ package org.robolectric.shadows; import static android.media.MediaRouter.ROUTE_TYPE_LIVE_AUDIO; import static android.media.MediaRouter.ROUTE_TYPE_LIVE_VIDEO; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2; import static android.os.Build.VERSION_CODES.N; import static com.google.common.truth.Truth.assertThat; import static org.robolectric.Shadows.shadowOf; @@ -16,7 +14,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; /** Tests for {@link ShadowMediaRouter}. */ @@ -54,7 +51,6 @@ public final class ShadowMediaRouterTest { } @Test - @Config(minSdk = JELLY_BEAN_MR2) public void testAddBluetoothRoute_checkBluetoothRouteProperties_apiJbMr2() { shadowOf(mediaRouter).addBluetoothRoute(); RouteInfo bluetoothRoute = mediaRouter.getRouteAt(1); @@ -88,7 +84,8 @@ public final class ShadowMediaRouterTest { shadowOf(mediaRouter).removeBluetoothRoute(); assertThat(mediaRouter.getRouteCount()).isEqualTo(1); - assertThat(mediaRouter.getSelectedRoute(ROUTE_TYPE_LIVE_AUDIO)).isEqualTo(getDefaultRoute()); + assertThat(mediaRouter.getSelectedRoute(ROUTE_TYPE_LIVE_AUDIO)) + .isEqualTo(mediaRouter.getDefaultRoute()); } @Test @@ -100,7 +97,8 @@ public final class ShadowMediaRouterTest { shadowOf(mediaRouter).removeBluetoothRoute(); assertThat(mediaRouter.getRouteCount()).isEqualTo(1); - assertThat(mediaRouter.getSelectedRoute(ROUTE_TYPE_LIVE_AUDIO)).isEqualTo(getDefaultRoute()); + assertThat(mediaRouter.getSelectedRoute(ROUTE_TYPE_LIVE_AUDIO)) + .isEqualTo(mediaRouter.getDefaultRoute()); } @Test @@ -108,24 +106,21 @@ public final class ShadowMediaRouterTest { assertThat(shadowOf(mediaRouter).isBluetoothRouteSelected(ROUTE_TYPE_LIVE_AUDIO)).isFalse(); } - // Pre-API 18, non-user routes weren't able to be selected. @Test - @Config(minSdk = JELLY_BEAN_MR2) public void testIsBluetoothRouteSelected_bluetoothRouteAddedButNotSelected_returnsFalse() { shadowOf(mediaRouter).addBluetoothRoute(); - mediaRouter.selectRoute(ROUTE_TYPE_LIVE_AUDIO, getDefaultRoute()); + mediaRouter.selectRoute(ROUTE_TYPE_LIVE_AUDIO, mediaRouter.getDefaultRoute()); assertThat(shadowOf(mediaRouter).isBluetoothRouteSelected(ROUTE_TYPE_LIVE_AUDIO)).isFalse(); } @Test - @Config(minSdk = JELLY_BEAN_MR1) public void testIsBluetoothRouteSelected_bluetoothRouteSelectedForDifferentType_returnsFalse() { shadowOf(mediaRouter).addBluetoothRoute(); RouteInfo bluetoothRoute = mediaRouter.getRouteAt(1); // Select the Bluetooth route for AUDIO and the default route for AUDIO. mediaRouter.selectRoute(ROUTE_TYPE_LIVE_AUDIO, bluetoothRoute); - mediaRouter.selectRoute(ROUTE_TYPE_LIVE_VIDEO, getDefaultRoute()); + mediaRouter.selectRoute(ROUTE_TYPE_LIVE_VIDEO, mediaRouter.getDefaultRoute()); assertThat(shadowOf(mediaRouter).isBluetoothRouteSelected(ROUTE_TYPE_LIVE_VIDEO)).isFalse(); } @@ -137,11 +132,4 @@ public final class ShadowMediaRouterTest { mediaRouter.selectRoute(ROUTE_TYPE_LIVE_AUDIO, bluetoothRoute); assertThat(shadowOf(mediaRouter).isBluetoothRouteSelected(ROUTE_TYPE_LIVE_AUDIO)).isTrue(); } - - private RouteInfo getDefaultRoute() { - if (RuntimeEnvironment.getApiLevel() >= JELLY_BEAN_MR2) { - return mediaRouter.getDefaultRoute(); - } - return mediaRouter.getRouteAt(0); - } } diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowNfcAdapterTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowNfcAdapterTest.java index bd64a107f..cf25dc858 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowNfcAdapterTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowNfcAdapterTest.java @@ -1,6 +1,7 @@ package org.robolectric.shadows; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; import static org.mockito.ArgumentMatchers.same; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @@ -16,9 +17,7 @@ import android.nfc.Tag; import android.os.Build; import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Before; -import org.junit.Rule; import org.junit.Test; -import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; import org.robolectric.Robolectric; import org.robolectric.RuntimeEnvironment; @@ -27,12 +26,10 @@ import org.robolectric.annotation.Config; @RunWith(AndroidJUnit4.class) public class ShadowNfcAdapterTest { - @Rule public ExpectedException expectedException = ExpectedException.none(); - private Application context; + private final Application context = RuntimeEnvironment.getApplication(); @Before public void setUp() throws Exception { - context = RuntimeEnvironment.getApplication(); shadowOf(context.getPackageManager()) .setSystemFeature(PackageManager.FEATURE_NFC, /* supported= */ true); } @@ -67,9 +64,11 @@ public class ShadowNfcAdapterTest { final Activity nullActivity = null; final NfcAdapter adapter = NfcAdapter.getDefaultAdapter(activity); - expectedException.expect(NullPointerException.class); - expectedException.expectMessage("activity cannot be null"); - adapter.setOnNdefPushCompleteCallback(callback, nullActivity); + NullPointerException exception = + assertThrows( + NullPointerException.class, + () -> adapter.setOnNdefPushCompleteCallback(callback, nullActivity)); + assertThat(exception).hasMessageThat().contains("activity cannot be null"); } @Test @@ -80,10 +79,11 @@ public class ShadowNfcAdapterTest { final Activity nullActivity = null; final NfcAdapter adapter = NfcAdapter.getDefaultAdapter(activity); - expectedException.expect(NullPointerException.class); - expectedException.expectMessage("activities cannot contain null"); - - adapter.setOnNdefPushCompleteCallback(callback, activity, nullActivity); + NullPointerException exception = + assertThrows( + NullPointerException.class, + () -> adapter.setOnNdefPushCompleteCallback(callback, activity, nullActivity)); + assertThat(exception).hasMessageThat().contains("activities cannot contain null"); } @Test @@ -99,22 +99,59 @@ public class ShadowNfcAdapterTest { } @Test + @Config(minSdk = Build.VERSION_CODES.Q) + public void isSecureNfcSupported_shouldReturnSupportedState() { + NfcAdapter adapter = NfcAdapter.getDefaultAdapter(context); + assertThat(adapter.isSecureNfcSupported()).isFalse(); + + shadowOf(adapter).setSecureNfcSupported(true); + assertThat(adapter.isSecureNfcSupported()).isTrue(); + + shadowOf(adapter).setSecureNfcSupported(false); + assertThat(adapter.isSecureNfcSupported()).isFalse(); + } + + @Test + @Config(minSdk = Build.VERSION_CODES.Q) + public void isSecureNfcEnabled_shouldReturnEnabledState() { + NfcAdapter adapter = NfcAdapter.getDefaultAdapter(context); + assertThat(adapter.isSecureNfcEnabled()).isFalse(); + + adapter.enableSecureNfc(true); + assertThat(adapter.isSecureNfcEnabled()).isTrue(); + + adapter.enableSecureNfc(false); + assertThat(adapter.isSecureNfcEnabled()).isFalse(); + } + + @Test public void getNfcAdapter_returnsNonNull() { NfcAdapter adapter = NfcAdapter.getDefaultAdapter(context); + assertThat(adapter).isNotNull(); + + // This is checked twice to prevent a regression where attempting to acquire the + // `getDefaultAdapter` twice in a test would cause a `null` value to be returned on UDC+. + NfcAdapter adapterAgain = NfcAdapter.getDefaultAdapter(context); + + assertThat(adapterAgain).isNotNull(); } @Test public void getNfcAdapter_hardwareExists_returnsNonNull() { ShadowNfcAdapter.setNfcHardwareExists(true); + NfcAdapter adapter = NfcAdapter.getDefaultAdapter(context); + assertThat(adapter).isNotNull(); } @Test public void getNfcAdapter_hardwareDoesNotExist_returnsNull() { ShadowNfcAdapter.setNfcHardwareExists(false); + NfcAdapter adapter = NfcAdapter.getDefaultAdapter(context); + assertThat(adapter).isNull(); } @@ -145,13 +182,10 @@ public class ShadowNfcAdapterTest { final Activity activity = Robolectric.setupActivity(Activity.class); final NfcAdapter adapter = NfcAdapter.getDefaultAdapter(activity); - expectedException.expect(IllegalStateException.class); - - shadowOf(adapter).getNdefPushMessage(); + assertThrows(IllegalStateException.class, () -> shadowOf(adapter).getNdefPushMessage()); } @Test - @Config(minSdk = Build.VERSION_CODES.KITKAT) public void isInReaderMode_beforeEnableReaderMode_shouldReturnFalse() { final Activity activity = Robolectric.setupActivity(Activity.class); @@ -160,7 +194,6 @@ public class ShadowNfcAdapterTest { } @Test - @Config(minSdk = Build.VERSION_CODES.KITKAT) public void isInReaderMode_afterEnableReaderMode_shouldReturnTrue() { final Activity activity = Robolectric.setupActivity(Activity.class); NfcAdapter adapter = NfcAdapter.getDefaultAdapter(activity); @@ -175,7 +208,6 @@ public class ShadowNfcAdapterTest { } @Test - @Config(minSdk = Build.VERSION_CODES.KITKAT) public void isInReaderMode_afterDisableReaderMode_shouldReturnFalse() { final Activity activity = Robolectric.setupActivity(Activity.class); NfcAdapter adapter = NfcAdapter.getDefaultAdapter(activity); @@ -191,7 +223,6 @@ public class ShadowNfcAdapterTest { } @Test - @Config(minSdk = Build.VERSION_CODES.KITKAT) public void dispatchTagDiscovered_shouldDispatchTagToCallback() { final Activity activity = Robolectric.setupActivity(Activity.class); NfcAdapter adapter = NfcAdapter.getDefaultAdapter(activity); diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowNotificationBuilderTestBase.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowNotificationBuilderTestBase.java index 6274b67ac..fe3483e2c 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowNotificationBuilderTestBase.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowNotificationBuilderTestBase.java @@ -1,7 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2; import static android.os.Build.VERSION_CODES.M; import static android.os.Build.VERSION_CODES.N; import static com.google.common.truth.Truth.assertThat; @@ -44,7 +42,6 @@ public abstract class ShadowNotificationBuilderTestBase { } @Test - @Config(minSdk = JELLY_BEAN_MR1) public void build_whenShowWhenNotSet_setsShowWhenOnNotificationToTrue() { Notification notification = builder.setWhen(100).setShowWhen(true).build(); @@ -52,7 +49,6 @@ public abstract class ShadowNotificationBuilderTestBase { } @Test - @Config(minSdk = JELLY_BEAN_MR1) public void build_setShowWhenOnNotification() { Notification notification = builder.setShowWhen(false).build(); @@ -112,7 +108,6 @@ public abstract class ShadowNotificationBuilderTestBase { } @Test - @Config(minSdk = JELLY_BEAN_MR1) public void build_setsUsesChronometerOnNotification_true() { Notification notification = builder.setUsesChronometer(true).setWhen(10).setShowWhen(true).build(); @@ -120,7 +115,6 @@ public abstract class ShadowNotificationBuilderTestBase { } @Test - @Config(minSdk = JELLY_BEAN_MR1) public void build_setsUsesChronometerOnNotification_false() { Notification notification = builder.setUsesChronometer(false).setWhen(10).setShowWhen(true).build(); @@ -209,7 +203,6 @@ public abstract class ShadowNotificationBuilderTestBase { } @Test - @Config(minSdk = JELLY_BEAN_MR2) public void build_addsActionToNotification() throws Exception { PendingIntent action = PendingIntent.getBroadcast(ApplicationProvider.getApplicationContext(), 0, null, 0); @@ -247,4 +240,22 @@ public abstract class ShadowNotificationBuilderTestBase { assertThat(shadowOf(notification).getBigPicture().sameAs(bigPicture)).isTrue(); } + + @Test + public void withInboxStyle() { + Notification notification = + builder + .setStyle( + new Notification.InboxStyle(builder) + .addLine("Line1") + .addLine("Line2") + .setBigContentTitle("Title") + .setSummaryText("Summary")) + .build(); + + assertThat(shadowOf(notification).getBigContentTitle().toString()).isEqualTo("Title"); + assertThat(shadowOf(notification).getBigContentText().toString()).isEqualTo("Summary"); + assertThat(shadowOf(notification).getBigPicture()).isNull(); + assertThat(shadowOf(notification).getTextLines()).containsExactly("Line1", "Line2"); + } } diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowOpenGLMatrixTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowOpenGLMatrixTest.java index a129547a9..8784162c0 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowOpenGLMatrixTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowOpenGLMatrixTest.java @@ -1,13 +1,11 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; import static com.google.common.truth.Truth.assertThat; import android.opengl.Matrix; import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; -import org.robolectric.annotation.Config; @RunWith(AndroidJUnit4.class) public class ShadowOpenGLMatrixTest { @@ -356,8 +354,7 @@ public class ShadowOpenGLMatrixTest { } @Test - @Config(minSdk = JELLY_BEAN_MR1) - public void testFrustumJB_MR1() { + public void testFrustum() { float[] expected = new float[]{ 0.005f, 0, 0, 0, 0, 0.02f, 0, 0, diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowPackageManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowPackageManagerTest.java index e2ac826e4..720a01d2f 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowPackageManagerTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowPackageManagerTest.java @@ -32,8 +32,6 @@ import static android.content.pm.PackageManager.SIGNATURE_SECOND_NOT_SIGNED; import static android.content.pm.PackageManager.SIGNATURE_UNKNOWN_PACKAGE; import static android.content.pm.PackageManager.VERIFICATION_ALLOW; import static android.content.pm.PackageManager.VERIFICATION_REJECT; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2; import static android.os.Build.VERSION_CODES.LOLLIPOP; import static android.os.Build.VERSION_CODES.LOLLIPOP_MR1; import static android.os.Build.VERSION_CODES.M; @@ -1179,7 +1177,6 @@ public class ShadowPackageManagerTest { } @Test - @Config(minSdk = JELLY_BEAN_MR1) public void queryIntentActivitiesAsUser_EmptyResult() { Intent i = new Intent(Intent.ACTION_APP_ERROR, null); i.addCategory(Intent.CATEGORY_APP_BROWSER); @@ -1209,7 +1206,6 @@ public class ShadowPackageManagerTest { } @Test - @Config(minSdk = JELLY_BEAN_MR1) public void queryIntentActivitiesAsUser_Match() { Intent i = new Intent(Intent.ACTION_MAIN, null); i.addCategory(Intent.CATEGORY_LAUNCHER); @@ -1509,7 +1505,6 @@ public class ShadowPackageManagerTest { } @Test - @Config(minSdk = JELLY_BEAN_MR1) public void resolveActivityAsUser_Match() { Intent i = new Intent(Intent.ACTION_MAIN, null).addCategory(Intent.CATEGORY_LAUNCHER); ResolveInfo info = new ResolveInfo(); @@ -1533,7 +1528,6 @@ public class ShadowPackageManagerTest { } @Test - @Config(minSdk = JELLY_BEAN_MR1) public void resolveActivityAsUser_NoMatch() { Intent i = new Intent(); i.setComponent(new ComponentName("foo.bar", "No Activity")); @@ -1706,7 +1700,6 @@ public class ShadowPackageManagerTest { } @Test - @Config(minSdk = JELLY_BEAN_MR1) public void queryIntentServicesAsUser() { Intent i = new Intent("org.robolectric.ACTION_DIFFERENT_PACKAGE"); i.addCategory(Intent.CATEGORY_LAUNCHER); @@ -2112,6 +2105,16 @@ public class ShadowPackageManagerTest { } @Test + @Config(minSdk = TIRAMISU) + public void testReceiverInfo_withComponentInfoFlags() throws Exception { + ActivityInfo info = + packageManager.getReceiverInfo( + new ComponentName(context, "org.robolectric.test.ConfigTestReceiver"), + PackageManager.ComponentInfoFlags.of(PackageManager.GET_META_DATA)); + assertThat(info.metaData.getInt("numberOfSheep")).isEqualTo(42); + } + + @Test public void testGetPackageInfo_ForReceiversIncorrectPackage() { try { packageManager.getPackageInfo("unknown_package", PackageManager.GET_RECEIVERS); @@ -2598,6 +2601,74 @@ public class ShadowPackageManagerTest { } @Test + @Config(minSdk = TIRAMISU) + public void getServiceInfo_withComponentInfoFlags_shouldReturnServiceInfoIfExists() + throws Exception { + ServiceInfo serviceInfo = + packageManager.getServiceInfo( + new ComponentName("org.robolectric", "com.foo.Service"), + PackageManager.ComponentInfoFlags.of(0)); + assertThat(serviceInfo.packageName).isEqualTo("org.robolectric"); + assertThat(serviceInfo.name).isEqualTo("com.foo.Service"); + assertThat(serviceInfo.permission).isEqualTo("com.foo.MY_PERMISSION"); + assertThat(serviceInfo.applicationInfo).isNotNull(); + } + + @Test + @Config(minSdk = TIRAMISU) + public void + getServiceInfo_withComponentInfoFlags_shouldReturnServiceInfoWithMetaDataWhenFlagsSet() + throws Exception { + ServiceInfo serviceInfo = + packageManager.getServiceInfo( + new ComponentName("org.robolectric", "com.foo.Service"), + PackageManager.ComponentInfoFlags.of(PackageManager.GET_META_DATA)); + assertThat(serviceInfo.metaData).isNotNull(); + } + + @Test + @Config(minSdk = TIRAMISU) + public void + getServiceInfo_withComponentInfoFlags_shouldReturnServiceInfoWithoutMetaDataWhenFlagsNotSet() + throws Exception { + ComponentName component = new ComponentName("org.robolectric", "com.foo.Service"); + ServiceInfo serviceInfo = + packageManager.getServiceInfo(component, PackageManager.ComponentInfoFlags.of(0)); + assertThat(serviceInfo.metaData).isNull(); + } + + @Test + @Config(minSdk = TIRAMISU) + public void getServiceInfo_withComponentInfoFlags_shouldThrowNameNotFoundExceptionIfNotExist() { + ComponentName nonExistComponent = + new ComponentName("org.robolectric", "com.foo.NonExistService"); + try { + packageManager.getServiceInfo( + nonExistComponent, PackageManager.ComponentInfoFlags.of(PackageManager.GET_SERVICES)); + fail("should have thrown NameNotFoundException"); + } catch (PackageManager.NameNotFoundException e) { + assertThat(e.getMessage()).contains("com.foo.NonExistService"); + } + } + + @Test + @Config(minSdk = TIRAMISU) + public void getServiceInfo_withComponentInfoFlags_shouldFindServiceIfAddedInResolveInfo() + throws Exception { + ComponentName componentName = new ComponentName("com.test", "com.test.ServiceName"); + ResolveInfo resolveInfo = new ResolveInfo(); + resolveInfo.serviceInfo = new ServiceInfo(); + resolveInfo.serviceInfo.name = componentName.getClassName(); + resolveInfo.serviceInfo.applicationInfo = new ApplicationInfo(); + resolveInfo.serviceInfo.applicationInfo.packageName = componentName.getPackageName(); + shadowOf(packageManager).addResolveInfoForIntent(new Intent("RANDOM_ACTION"), resolveInfo); + + ServiceInfo serviceInfo = + packageManager.getServiceInfo(componentName, PackageManager.ComponentInfoFlags.of(0)); + assertThat(serviceInfo).isNotNull(); + } + + @Test public void getNameForUid() { assertThat(packageManager.getNameForUid(10)).isNull(); @@ -2770,7 +2841,6 @@ public class ShadowPackageManagerTest { } @Test - @Config(minSdk = JELLY_BEAN_MR1) public void extendPendingInstallTimeout_verificationRejectAtTimeout_extendsPendingInstallTimeoutAndsetsCodeAtTimeoutToReject() { packageManager.extendVerificationTimeout( @@ -2786,7 +2856,6 @@ public class ShadowPackageManagerTest { } @Test - @Config(minSdk = JELLY_BEAN_MR1) public void extendPendingInstallTimeout_verificationAllowAtTimeout_extendsPendingInstallTimeoutAndsetsCodeAtTimeoutToAllow() { packageManager.extendVerificationTimeout( @@ -2802,7 +2871,6 @@ public class ShadowPackageManagerTest { } @Test - @Config(minSdk = JELLY_BEAN_MR1) public void whenVerificationTimeOutNotExtended_verificationCodeAtTimeoutIsAllow() { assertThat(shadowOf(packageManager).getVerificationExtendedTimeout(INSTALL_VERIFICATION_ID)) .isEqualTo(0); @@ -2813,7 +2881,6 @@ public class ShadowPackageManagerTest { } @Test - @Config(minSdk = JELLY_BEAN_MR1) public void triggerInstallVerificationTimeout_broadcastsPackageVerifiedIntent() { ShadowPackageManager shadowPackageManagerMock = mock(ShadowPackageManager.class, Mockito.CALLS_REAL_METHODS); @@ -3288,6 +3355,14 @@ public class ShadowPackageManagerTest { } @Test + @Config(minSdk = VERSION_CODES.R) + public void getInstallerSourceInfo_notExists_throwsException() throws Exception { + assertThrows( + NameNotFoundException.class, + () -> packageManager.getInstallSourceInfo("nonExistTarget.package")); + } + + @Test public void getXml() { XmlResourceParser in = packageManager.getXml( @@ -3380,6 +3455,21 @@ public class ShadowPackageManagerTest { } @Test + @Config(minSdk = TIRAMISU) + public void addService_withComponentInfoFlags() throws Exception { + ServiceInfo serviceInfo = new ServiceInfo(); + serviceInfo.name = "name"; + serviceInfo.packageName = "package"; + + shadowOf(packageManager).addOrUpdateService(serviceInfo); + + assertThat( + packageManager.getServiceInfo( + new ComponentName("package", "name"), PackageManager.ComponentInfoFlags.of(0))) + .isNotNull(); + } + + @Test public void addProvider() throws Exception { ProviderInfo providerInfo = new ProviderInfo(); providerInfo.name = "name"; @@ -3402,6 +3492,21 @@ public class ShadowPackageManagerTest { } @Test + @Config(minSdk = TIRAMISU) + public void addReceiver_withComponentInfoFlags() throws Exception { + ActivityInfo receiverInfo = new ActivityInfo(); + receiverInfo.name = "name"; + receiverInfo.packageName = "package"; + + shadowOf(packageManager).addOrUpdateReceiver(receiverInfo); + + assertThat( + packageManager.getReceiverInfo( + new ComponentName("package", "name"), PackageManager.ComponentInfoFlags.of(0))) + .isNotNull(); + } + + @Test public void addActivity_addsNewPackage() throws Exception { ActivityInfo activityInfo = new ActivityInfo(); activityInfo.name = "name"; @@ -3476,6 +3581,22 @@ public class ShadowPackageManagerTest { } @Test + @Config(minSdk = TIRAMISU) + public void removeService_withComponentInfoFlags() { + ComponentName componentName = new ComponentName(context, "com.foo.Service"); + + ServiceInfo removed = shadowOf(packageManager).removeService(componentName); + + assertThat(removed).isNotNull(); + try { + packageManager.getServiceInfo(componentName, PackageManager.ComponentInfoFlags.of(0)); + fail(); + } catch (NameNotFoundException e) { + // expected + } + } + + @Test public void removeProvider() { ComponentName componentName = new ComponentName(context, "org.robolectric.shadows.testing.TestContentProvider1"); @@ -3508,6 +3629,23 @@ public class ShadowPackageManagerTest { } @Test + @Config(minSdk = TIRAMISU) + public void removeReceiver_withComponentInfoFlags() { + ComponentName componentName = + new ComponentName(context, "org.robolectric.fakes.ConfigTestReceiver"); + + ActivityInfo removed = shadowOf(packageManager).removeReceiver(componentName); + + assertThat(removed).isNotNull(); + try { + packageManager.getReceiverInfo(componentName, PackageManager.ComponentInfoFlags.of(0)); + fail(); + } catch (NameNotFoundException e) { + // expected + } + } + + @Test public void removeNonExistingComponent() { ComponentName componentName = new ComponentName(context, "org.robolectric.DoesnExist"); @@ -4392,7 +4530,6 @@ public class ShadowPackageManagerTest { } @Test - @Config(minSdk = JELLY_BEAN_MR2) public void getPackagesHoldingPermissions_returnPackages() { String permissionA = "com.android.providers.permission.test.a"; String permissionB = "com.android.providers.permission.test.b"; @@ -4419,7 +4556,6 @@ public class ShadowPackageManagerTest { } @Test - @Config(minSdk = JELLY_BEAN_MR2) public void getPackagesHoldingPermissions_returnsEmpty() { String permissionA = "com.android.providers.permission.test.a"; diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowPaintDrawableTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowPaintDrawableTest.java new file mode 100644 index 000000000..b8041a8b6 --- /dev/null +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowPaintDrawableTest.java @@ -0,0 +1,28 @@ +package org.robolectric.shadows; + +import static com.google.common.truth.Truth.assertThat; +import static org.robolectric.Shadows.shadowOf; + +import android.graphics.drawable.PaintDrawable; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +public final class ShadowPaintDrawableTest { + @Test + public void noCornerRadii_returnsNull() { + PaintDrawable paintDrawable = new PaintDrawable(); + assertThat(shadowOf(paintDrawable).getCornerRadii()).isEqualTo(null); + } + + @Test + public void getCornerRadii_returnsCornerRadii() { + PaintDrawable paintDrawable = new PaintDrawable(); + + float[] radii = {10f, 20f, 30f, 40f, 50f, 60f, 70f, 80f}; + paintDrawable.setCornerRadii(radii); + + assertThat(shadowOf(paintDrawable).getCornerRadii()).isEqualTo(radii); + } +} diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowParcelFileDescriptorTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowParcelFileDescriptorTest.java index 4cb7c06c9..c14709fde 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowParcelFileDescriptorTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowParcelFileDescriptorTest.java @@ -1,9 +1,9 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.KITKAT; import static android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE; import static com.google.common.truth.Truth.assertThat; import static java.nio.charset.Charset.defaultCharset; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.Assert.assertThrows; import static org.junit.Assume.assumeThat; import static org.robolectric.Shadows.shadowOf; @@ -22,13 +22,14 @@ import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.RandomAccessFile; import java.util.concurrent.atomic.AtomicBoolean; import org.hamcrest.Matchers; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.robolectric.annotation.Config; import org.robolectric.shadows.ShadowParcelFileDescriptor.FileDescriptorFromParcelUnavailableException; import org.robolectric.util.ReflectionHelpers; @@ -88,7 +89,6 @@ public class ShadowParcelFileDescriptorTest { } @Test - @Config(minSdk = KITKAT) public void testOpenWithOnCloseListener_nullHandler() throws Exception { final AtomicBoolean onCloseCalled = new AtomicBoolean(false); ParcelFileDescriptor.OnCloseListener onCloseListener = @@ -106,7 +106,6 @@ public class ShadowParcelFileDescriptorTest { } @Test - @Config(minSdk = KITKAT) public void testOpenWithOnCloseListener_nullOnCloseListener() throws Exception { HandlerThread handlerThread = new HandlerThread("test"); handlerThread.start(); @@ -118,7 +117,6 @@ public class ShadowParcelFileDescriptorTest { } @Test - @Config(minSdk = KITKAT) public void testOpenWithOnCloseListener_callsListenerOnClose() throws Exception { HandlerThread handlerThread = new HandlerThread("test"); handlerThread.start(); @@ -428,6 +426,92 @@ public class ShadowParcelFileDescriptorTest { assertThat(dupFile.valid()).isTrue(); } + @Test + public void testStaticDup_returnsFd() throws Exception { + RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw"); + FileDescriptor fd = randomAccessFile.getFD(); + ParcelFileDescriptor dupFd = ParcelFileDescriptor.dup(fd); + + assertThat(fd.valid()).isTrue(); + assertThat(dupFd.getFileDescriptor().valid()).isTrue(); + } + + @Test + public void testStaticDup_sameContent() throws Exception { + ParcelFileDescriptor pfd = null; + File tempFile = File.createTempFile("testFile", ".txt"); + String content = "abc123"; + Files.asCharSink(tempFile, UTF_8).write(content); + + try (FileInputStream fis = new FileInputStream(tempFile)) { + FileDescriptor fd = fis.getFD(); + pfd = ParcelFileDescriptor.dup(fd); + assertThat(readLine(pfd.getFileDescriptor())).isEqualTo(content); + assertThat(readLine(fd)).isEqualTo(content); + } finally { + if (pfd != null) { + pfd.close(); + } + } + + tempFile.delete(); + } + + @Test + public void testStaticDup_oldFilePositionDoesNotChange() throws Exception { + ParcelFileDescriptor pfd = null; + File tempFile = File.createTempFile("testFile", ".txt"); + String content = "abc123"; + Files.asCharSink(tempFile, UTF_8).write(content); + + try (FileInputStream fis = new FileInputStream(tempFile)) { + FileDescriptor fd = fis.getFD(); + long oldFilePosition = getCurrentFilePosition(fd); + pfd = ParcelFileDescriptor.dup(fd); + long newFilePosition = getCurrentFilePosition(fd); + + assertThat(newFilePosition).isEqualTo(oldFilePosition); + } finally { + if (pfd != null) { + pfd.close(); + } + } + + tempFile.delete(); + } + + @Test + public void testStaticDup_afterWrite() throws Exception { + File tempFile = File.createTempFile("testFile", ".txt"); + RandomAccessFile randomAccessFile = new RandomAccessFile(tempFile, "rw"); + FileDescriptor fd = randomAccessFile.getFD(); + String content = "abc123"; + OutputStream writer = new FileOutputStream(fd); + + writer.write(content.getBytes(UTF_8)); + try (ParcelFileDescriptor pfd = ParcelFileDescriptor.dup(fd)) { + assertThat(readLine(pfd.getFileDescriptor())).isEqualTo(content); + } finally { + writer.close(); + } + } + + @Test + public void testClose_afterDup_doesNotCloseOriginalFd() throws Exception { + ParcelFileDescriptor pfd = null; + File tempFile = File.createTempFile("testFile", ".txt"); + String content = "abc123"; + Files.asCharSink(tempFile, UTF_8).write(content); + + try (FileInputStream fis = new FileInputStream(tempFile)) { + FileDescriptor fd = fis.getFD(); + pfd = ParcelFileDescriptor.dup(fd); + pfd.close(); + assertThat(fd.valid()).isTrue(); + } + tempFile.delete(); + } + private static String readLine(FileDescriptor fd) throws IOException { try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(fd), defaultCharset()))) { @@ -445,4 +529,9 @@ public class ShadowParcelFileDescriptorTest { parcel.recycle(); } } + + private static long getCurrentFilePosition(FileDescriptor fd) throws IOException { + FileInputStream is = new FileInputStream(fd); + return is.getChannel().position(); + } } diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowPausedAsyncTaskLoaderTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowPausedAsyncTaskLoaderTest.java index f8d1de1df..b524f382d 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowPausedAsyncTaskLoaderTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowPausedAsyncTaskLoaderTest.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.KITKAT; import static com.google.common.truth.Truth.assertThat; import android.content.AsyncTaskLoader; @@ -12,12 +11,10 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.android.util.concurrent.PausedExecutorService; -import org.robolectric.annotation.Config; import org.robolectric.shadow.api.Shadow; /** Tests for {@link ShadowPausedAsyncTaskLoader}. */ @RunWith(AndroidJUnit4.class) -@Config(minSdk = KITKAT) public class ShadowPausedAsyncTaskLoaderTest { private final List<String> taskRecord = new ArrayList<>(); private TestLoader testLoader; diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowPausedLooperTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowPausedLooperTest.java index 71e85c9b6..ebb9e02d8 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowPausedLooperTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowPausedLooperTest.java @@ -13,6 +13,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.robolectric.Shadows.shadowOf; import static org.robolectric.shadows.ShadowLooper.shadowMainLooper; +import static org.robolectric.util.reflector.Reflector.reflector; import android.os.Build.VERSION_CODES; import android.os.Handler; @@ -21,6 +22,7 @@ import android.os.Looper; import android.os.MessageQueue.IdleHandler; import android.os.SystemClock; import androidx.test.ext.junit.runners.AndroidJUnit4; +import com.google.common.util.concurrent.SettableFuture; import java.time.Duration; import java.util.concurrent.Callable; import java.util.concurrent.CountDownLatch; @@ -28,16 +30,20 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TestName; import org.junit.runner.RunWith; +import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; import org.robolectric.annotation.LooperMode; import org.robolectric.res.android.Ref; import org.robolectric.shadow.api.Shadow; +import org.robolectric.util.reflector.Direct; +import org.robolectric.util.reflector.ForType; @RunWith(AndroidJUnit4.class) @LooperMode(LooperMode.Mode.PAUSED) @@ -643,6 +649,42 @@ public class ShadowPausedLooperTest { assertThat(wasRun.get()).isTrue(); } + /** + * Tests a race condition that could occur if a paused background Looper was quit but the thread + * was still alive. The resetter would attempt to unpause it, but the message would never run + * because the looper was quit. This caused a deadlock. + */ + @Test + public void looper_customThread_unPauseAfterQuit() throws Exception { + for (int i = 0; i < 100; i++) { + final SettableFuture<Looper> future = SettableFuture.create(); + final CountDownLatch countDownLatch = new CountDownLatch(1); + + Thread t = + new Thread( + () -> { + try { + Looper.prepare(); + } finally { + future.set(Looper.myLooper()); + } + Looper.loop(); + try { + countDownLatch.await(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + }); + t.start(); + Looper looper = future.get(); + shadowOf(looper).pause(); + new Handler(looper).post(() -> looper.quitSafely()); + shadowOf(looper).idle(); + ((ShadowPausedLooper) shadowOf(looper)).resetLooperToInitialState(); + countDownLatch.countDown(); + } + } + @Test @Config(minSdk = VERSION_CODES.P) public void runOneTask_ignoreSyncBarrier_with_async() { @@ -660,6 +702,73 @@ public class ShadowPausedLooperTest { Looper.getMainLooper().getQueue().removeSyncBarrier(barrier); } + /** + * Verify that calling a control operation like idle while a sync barrier is being held doesn't + * deadlock the looper + */ + @Test + public void idle_paused_onSyncBarrier() { + + Handler handler = new Handler(handlerThread.getLooper()); + Handler asyncHandler = ShadowPausedLooper.createAsyncHandler(handlerThread.getLooper()); + ShadowPausedLooper shadowLooper = Shadow.extract(handlerThread.getLooper()); + shadowLooper.pause(); + + AtomicInteger token = new AtomicInteger(-1); + AtomicBoolean wasRun = new AtomicBoolean(false); + handler.post( + () -> { + token.set(postSyncBarrierCompat(handlerThread.getLooper())); + handler.post( + () -> { + wasRun.set(true); + }); + }); + shadowLooper.idle(); + assertThat(token.get()).isNotEqualTo(-1); + assertThat(wasRun.get()).isEqualTo(false); + // should be effectively a no-op and not deadlock + shadowLooper.idle(); + // remove sync barriers messages need to get posted as async + asyncHandler.post( + () -> { + removeSyncBarrierCompat(handlerThread.getLooper(), token.get()); + }); + shadowLooper.idle(); + assertThat(wasRun.get()).isEqualTo(true); + } + + /** Similar to previous test but with a running aka unpaused looper. */ + @Test + public void idle_running_onSyncBarrier() { + Handler handler = new Handler(handlerThread.getLooper()); + Handler asyncHandler = ShadowPausedLooper.createAsyncHandler(handlerThread.getLooper()); + ShadowPausedLooper shadowLooper = Shadow.extract(handlerThread.getLooper()); + + AtomicInteger token = new AtomicInteger(-1); + AtomicBoolean wasRun = new AtomicBoolean(false); + handler.post( + () -> { + token.set(postSyncBarrierCompat(handlerThread.getLooper())); + handler.post( + () -> { + wasRun.set(true); + }); + }); + shadowLooper.idle(); + assertThat(token.get()).isNotEqualTo(-1); + assertThat(wasRun.get()).isEqualTo(false); + // should be effectively a no-op and not deadlock + shadowLooper.idle(); + // remove sync barriers messages need to get posted as async + asyncHandler.post( + () -> { + removeSyncBarrierCompat(handlerThread.getLooper(), token.get()); + }); + shadowLooper.idle(); + assertThat(wasRun.get()).isEqualTo(true); + } + private static class BlockingRunnable implements Runnable { CountDownLatch latch = new CountDownLatch(1); @@ -675,7 +784,31 @@ public class ShadowPausedLooperTest { private void postToMainLooper() { // just post a runnable and rely on setUp to check Handler handler = new Handler(getMainLooper()); - Runnable mockRunnable = mock(Runnable.class); - handler.post(mockRunnable); + handler.post(() -> {}); + } + + private static int postSyncBarrierCompat(Looper looper) { + if (RuntimeEnvironment.getApiLevel() >= 23) { + return looper.getQueue().postSyncBarrier(); + } else { + return reflector(LooperReflector.class, looper).postSyncBarrier(); + } + } + + private static void removeSyncBarrierCompat(Looper looper, int token) { + if (RuntimeEnvironment.getApiLevel() >= 23) { + looper.getQueue().removeSyncBarrier(token); + } else { + reflector(LooperReflector.class, looper).removeSyncBarrier(token); + } + } + + @ForType(Looper.class) + private interface LooperReflector { + @Direct + int postSyncBarrier(); + + @Direct + void removeSyncBarrier(int token); } } diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowPausedSystemClockTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowPausedSystemClockTest.java index b0261527d..7eaa98896 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowPausedSystemClockTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowPausedSystemClockTest.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; import static android.os.Build.VERSION_CODES.P; import static android.os.Build.VERSION_CODES.Q; import static com.google.common.truth.Truth.assertThat; @@ -12,6 +11,7 @@ import static org.robolectric.annotation.LooperMode.Mode.PAUSED; import android.os.SystemClock; import androidx.test.ext.junit.runners.AndroidJUnit4; import java.time.DateTimeException; +import java.time.Duration; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -32,6 +32,7 @@ public class ShadowPausedSystemClockTest { assertTrue(SystemClock.setCurrentTimeMillis(1000)); SystemClock.sleep(34); assertThat(SystemClock.uptimeMillis()).isEqualTo(1034); + assertThat(SystemClock.elapsedRealtime()).isEqualTo(1034); } @Test @@ -69,11 +70,58 @@ public class ShadowPausedSystemClockTest { } @Test + public void deepSleep_advancesOnlyRealtime() { + assertTrue(SystemClock.setCurrentTimeMillis(1000)); + + ShadowPausedSystemClock.deepSleep(34); + + assertThat(SystemClock.uptimeMillis()).isEqualTo(1000); + assertThat(SystemClock.elapsedRealtime()).isEqualTo(1034); + } + + @Test + public void deepSleep_notifiesListener() { + AtomicBoolean listenerCalled = new AtomicBoolean(); + ShadowPausedSystemClock.addListener(() -> listenerCalled.set(true)); + + ShadowPausedSystemClock.deepSleep(100); + + assertThat(listenerCalled.get()).isTrue(); + } + + @SuppressWarnings("FutureReturnValueIgnored") + @Test + public void deepSleep_concurrentAccess_doesNotCorruptData() throws Exception { + SystemClock.setCurrentTimeMillis(100); + ExecutorService executor = Executors.newFixedThreadPool(2); + try { + int numToExecute = 10000; + CountDownLatch latch = new CountDownLatch(numToExecute); + + for (int i = 0; i < numToExecute; i++) { + executor.submit( + () -> { + ShadowPausedSystemClock.deepSleep(100); + latch.countDown(); + }); + } + latch.await(); + + assertThat(SystemClock.uptimeMillis()).isEqualTo(100); + assertThat(SystemClock.elapsedRealtime()).isEqualTo(100 + numToExecute * 100); + } finally { + executor.shutdown(); + } + } + + @Test public void testSetCurrentTime() { assertTrue(SystemClock.setCurrentTimeMillis(1034)); + assertThat(SystemClock.elapsedRealtime()).isEqualTo(1034); assertThat(SystemClock.uptimeMillis()).isEqualTo(1034); assertThat(SystemClock.currentThreadTimeMillis()).isEqualTo(1034); assertFalse(SystemClock.setCurrentTimeMillis(1000)); + assertThat(SystemClock.elapsedRealtime()).isEqualTo(1034); assertThat(SystemClock.uptimeMillis()).isEqualTo(1034); } @@ -104,7 +152,7 @@ public class ShadowPausedSystemClockTest { latch.countDown(); }); latch.await(); - + assertThat(SystemClock.elapsedRealtime()).isEqualTo(300); assertThat(SystemClock.uptimeMillis()).isEqualTo(300); } finally { executor.shutdown(); @@ -112,13 +160,34 @@ public class ShadowPausedSystemClockTest { } @Test + public void advanceTimeBy_shouldAdvanceBothElapsedRealtimeAndUptimeMillis() { + SystemClock.setCurrentTimeMillis(1000); + + ShadowPausedSystemClock.advanceBy(Duration.ofMillis(100)); + + assertThat(SystemClock.uptimeMillis()).isEqualTo(1100); + assertThat(SystemClock.elapsedRealtime()).isEqualTo(1100); + assertThat(SystemClock.currentThreadTimeMillis()).isEqualTo(1100); + } + + @Test + public void simulateDeepSleep_shouldOnlyAdvanceElapsedRealtime() { + SystemClock.setCurrentTimeMillis(1000); + + ShadowPausedSystemClock.simulateDeepSleep(Duration.ofMillis(100)); + + assertThat(SystemClock.uptimeMillis()).isEqualTo(1000); + assertThat(SystemClock.currentThreadTimeMillis()).isEqualTo(1000); + assertThat(SystemClock.elapsedRealtime()).isEqualTo(1100); + } + + @Test public void testElapsedRealtime() { SystemClock.setCurrentTimeMillis(1000); assertThat(SystemClock.elapsedRealtime()).isEqualTo(1000); } @Test - @Config(minSdk = JELLY_BEAN_MR1) public void testElapsedRealtimeNanos() { SystemClock.setCurrentTimeMillis(1000); assertThat(SystemClock.elapsedRealtimeNanos()).isEqualTo(1000000000); diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowPendingIntentTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowPendingIntentTest.java index 58d219cf7..61cbb18c1 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowPendingIntentTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowPendingIntentTest.java @@ -894,7 +894,6 @@ public class ShadowPendingIntentTest { } @Test - @Config(minSdk = Build.VERSION_CODES.JELLY_BEAN_MR1) public void testGetCreatorPackage_nothingSet() { PendingIntent pendingIntent = PendingIntent.getActivity(context, 99, new Intent("activity"), 0); assertThat(pendingIntent.getCreatorPackage()).isEqualTo(context.getPackageName()); @@ -902,7 +901,6 @@ public class ShadowPendingIntentTest { } @Test - @Config(minSdk = Build.VERSION_CODES.JELLY_BEAN_MR1) public void testGetCreatorPackage_explicitlySetPackage() { String fakePackage = "some.fake.package"; PendingIntent pendingIntent = PendingIntent.getActivity(context, 99, new Intent("activity"), 0); @@ -912,7 +910,6 @@ public class ShadowPendingIntentTest { } @Test - @Config(minSdk = Build.VERSION_CODES.JELLY_BEAN_MR1) public void testGetCreatorUid() { int fakeUid = 123; PendingIntent pendingIntent = PendingIntent.getActivity(context, 99, new Intent("activity"), 0); @@ -972,7 +969,6 @@ public class ShadowPendingIntentTest { } @Test - @Config(minSdk = Build.VERSION_CODES.JELLY_BEAN) public void toString_doesNotNPE() { assertThat( PendingIntent.getBroadcast( diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowPosixTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowPosixTest.java index 05fe13a16..437c313b7 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowPosixTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowPosixTest.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.KITKAT; import static android.os.Build.VERSION_CODES.LOLLIPOP; import static com.google.common.truth.Truth.assertThat; @@ -52,7 +51,6 @@ public final class ShadowPosixTest { } @Test - @Config(maxSdk = KITKAT) public void getStatBelowLollipop_returnCorrectMode() throws Exception { Object stat = ShadowPosix.stat(path); int mode = ReflectionHelpers.getField(stat, "st_mode"); @@ -60,7 +58,6 @@ public final class ShadowPosixTest { } @Test - @Config(minSdk = KITKAT) public void getStatBelowLollipop_returnCorrectSize() throws Exception { Object stat = ShadowPosix.stat(path); long size = ReflectionHelpers.getField(stat, "st_size"); @@ -68,7 +65,6 @@ public final class ShadowPosixTest { } @Test - @Config(minSdk = KITKAT) public void getStatBelowtLollipop_returnCorrectModifiedTime() throws Exception { Object stat = ShadowPosix.stat(path); long modifiedTime = ReflectionHelpers.getField(stat, "st_mtime"); diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowPreferenceGroupTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowPreferenceGroupTest.java index f70bcd251..f21c21fda 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowPreferenceGroupTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowPreferenceGroupTest.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.KITKAT; import static com.google.common.truth.Truth.assertThat; import static org.robolectric.Robolectric.buildActivity; import static org.robolectric.Shadows.shadowOf; @@ -16,10 +15,6 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.Robolectric; -import org.robolectric.RuntimeEnvironment; -import org.robolectric.annotation.Config; -import org.robolectric.util.ReflectionHelpers; -import org.robolectric.util.ReflectionHelpers.ClassParameter; @RunWith(AndroidJUnit4.class) public class ShadowPreferenceGroupTest { @@ -37,7 +32,7 @@ public class ShadowPreferenceGroupTest { group = new TestPreferenceGroup(activity, attrs); shadow = shadowOf(group); - shadow.callOnAttachedToHierarchy(newPreferenceManager(activity, 0)); + shadow.callOnAttachedToHierarchy(new PreferenceManager(activity, 0)); pref1 = new Preference(activity); pref1.setKey("pref1"); @@ -46,18 +41,6 @@ public class ShadowPreferenceGroupTest { pref2.setKey("pref2"); } - private static PreferenceManager newPreferenceManager(Activity activity, int firstRequestCode) { - if (RuntimeEnvironment.getApiLevel() >= KITKAT) { - return new PreferenceManager(activity, 0); - } else { - // Constructor was not public before KITKAT. - return ReflectionHelpers.callConstructor( - PreferenceManager.class, - ClassParameter.from(Activity.class, activity), - ClassParameter.from(int.class, 0)); - } - } - @Test public void shouldInheritFromPreference() { assertThat(shadow).isInstanceOf(ShadowPreference.class); @@ -153,7 +136,6 @@ public class ShadowPreferenceGroupTest { assertThat(group.findPreference(pref2.getKey())).isSameInstanceAs(pref2); } - @Config(minSdk = KITKAT) @Test public void shouldFindPreferenceRecursively() { TestPreferenceGroup group2 = new TestPreferenceGroup(activity, attrs); @@ -166,7 +148,6 @@ public class ShadowPreferenceGroupTest { assertThat(group.findPreference(pref2.getKey())).isSameInstanceAs(pref2); } - @Config(minSdk = KITKAT) @Test public void shouldSetEnabledRecursively() { boolean[] values = {false, true}; diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowRelativeLayoutTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowRelativeLayoutTest.java index f741b638b..6cc90a3b9 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowRelativeLayoutTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowRelativeLayoutTest.java @@ -1,7 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.JELLY_BEAN; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; import static com.google.common.truth.Truth.assertThat; import android.view.ViewGroup; @@ -11,13 +9,11 @@ import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; -import org.robolectric.annotation.Config; @RunWith(AndroidJUnit4.class) public class ShadowRelativeLayoutTest { @Test - @Config(minSdk = JELLY_BEAN_MR1) public void getRules_shouldShowAddRuleData_sinceApiLevel17() { ImageView imageView = new ImageView(ApplicationProvider.getApplicationContext()); RelativeLayout layout = new RelativeLayout(ApplicationProvider.getApplicationContext()); @@ -28,17 +24,4 @@ public class ShadowRelativeLayoutTest { int[] rules = layoutParams.getRules(); assertThat(rules).isEqualTo(new int[]{0, 0, 0, 0, 0, 0, 1234, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}); } - - @Test - @Config(maxSdk = JELLY_BEAN) - public void getRules_shouldShowAddRuleData_uptoApiLevel16() { - ImageView imageView = new ImageView(ApplicationProvider.getApplicationContext()); - RelativeLayout layout = new RelativeLayout(ApplicationProvider.getApplicationContext()); - layout.addView(imageView, new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); - RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) imageView.getLayoutParams(); - layoutParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT); - layoutParams.addRule(RelativeLayout.ALIGN_TOP, 1234); - int[] rules = layoutParams.getRules(); - assertThat(rules).isEqualTo(new int[]{0, 0, 0, 0, 0, 0, 1234, 0, 0, 0, 0, -1, 0, 0, 0, 0}); - } } diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowResourcesTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowResourcesTest.java index a4732b920..9a71b9f87 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowResourcesTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowResourcesTest.java @@ -1,11 +1,14 @@ package org.robolectric.shadows; import static android.os.Build.VERSION_CODES.N_MR1; +import static android.os.Build.VERSION_CODES.Q; +import static android.os.Build.VERSION_CODES.S; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.TruthJUnit.assume; import static org.robolectric.Shadows.shadowOf; import static org.robolectric.shadows.ShadowAssetManager.useLegacy; +import android.content.Context; import android.content.res.AssetFileDescriptor; import android.content.res.Configuration; import android.content.res.Resources; @@ -15,13 +18,18 @@ import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.os.Build; import android.util.AttributeSet; +import android.util.SparseIntArray; import android.util.TypedValue; +import android.widget.RemoteViews; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.common.collect.Range; import java.io.InputStream; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.stream.IntStream; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.R; @@ -31,6 +39,23 @@ import org.robolectric.annotation.Config; @RunWith(AndroidJUnit4.class) public class ShadowResourcesTest { + private static final int FIRST_RESOURCE_COLOR_ID = android.R.color.system_neutral1_0; + private static final int LAST_RESOURCE_COLOR_ID = android.R.color.system_accent3_1000; + + /** + * Some green/blue colors, from system_neutral1_0 to system_accent3_1000, extracted using 0xB1EBFF + * as seed color and "FRUIT_SALAD" as theme style. + */ + private static final int[] greenBlueColorBase = { + -1, -393729, -1641480, -2562838, -4405043, -6181454, -7892073, -9668483, -11181979, -12760755, + -14208458, -15590111, -16777216, -1, -393729, -2296322, -3217680, -4994349, -6770760, -8547171, + -10257790, -11836822, -13350318, -14863301, -16376283, -16777216, -1, -720905, -4456478, + -8128307, -10036302, -12075112, -14638210, -16742810, -16749487, -16756420, -16762839, + -16768746, -16777216, -1, -720905, -4456478, -5901613, -7678281, -9454947, -11231613, -13139095, + -15111342, -16756420, -16762839, -16768746, -16777216, -1, -393729, -2361857, -5051393, + -7941655, -9783603, -11625551, -13729642, -16750723, -16757153, -16763326, -16769241, -16777216 + }; + private Resources resources; @Before @@ -295,4 +320,43 @@ public class ShadowResourcesTest { assertThat(resourcesSubclass).isNotNull(); } + + @Test + @Config(minSdk = S) + public void getColor_shouldReturnCorrectMaterialYouColor() throws Exception { + SparseIntArray sparseArray = + new SparseIntArray(LAST_RESOURCE_COLOR_ID - FIRST_RESOURCE_COLOR_ID + 1); + IntStream.range(0, greenBlueColorBase.length) + .forEach(i -> sparseArray.put(FIRST_RESOURCE_COLOR_ID + i, greenBlueColorBase[i])); + int basicColor = android.R.color.system_neutral1_10; + Context context = ApplicationProvider.getApplicationContext(); + RemoteViews.ColorResources colorResources = + RemoteViews.ColorResources.create(context, sparseArray); + assertThat(colorResources).isNotNull(); + + colorResources.apply(context); + + assertThat(basicColor).isNotEqualTo(0); + assertThat(resources.getColor(basicColor, /* theme= */ null)).isEqualTo(-393729); + } + + @Ignore("Re-enable when performing benchmarks") + @Test + @Config(sdk = Q) + public void benchmarkUpdateConfiguration() { + long startTime = System.nanoTime(); + Resources systemResources = Resources.getSystem(); + for (int i = 0; i < 10_000; i++) { + Configuration oldConfig = resources.getConfiguration(); + Configuration newConfig = new Configuration(oldConfig); + // This change triggers RebuildFilterList in CppAssetManager2 + newConfig.colorMode = 3; + systemResources.updateConfiguration(newConfig, resources.getDisplayMetrics()); + systemResources.updateConfiguration(oldConfig, resources.getDisplayMetrics()); + } + long endTime = System.nanoTime(); + long elapsedNs = endTime - startTime; + System.err.println( + "updateConfiguration benchmark took " + TimeUnit.NANOSECONDS.toMillis(elapsedNs) + " ms"); + } } diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowRoleManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowRoleManagerTest.java index 86109b095..df472eb36 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowRoleManagerTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowRoleManagerTest.java @@ -1,6 +1,7 @@ package org.robolectric.shadows; import static com.google.common.truth.Truth.assertThat; +import static com.google.common.util.concurrent.MoreExecutors.directExecutor; import static org.junit.Assert.assertThrows; import static org.robolectric.RuntimeEnvironment.getApplication; import static org.robolectric.Shadows.shadowOf; @@ -8,7 +9,9 @@ import static org.robolectric.Shadows.shadowOf; import android.app.role.RoleManager; import android.content.Context; import android.os.Build; +import androidx.test.core.content.pm.PackageInfoBuilder; import androidx.test.ext.junit.runners.AndroidJUnit4; +import java.util.concurrent.atomic.AtomicBoolean; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -25,25 +28,25 @@ public final class ShadowRoleManagerTest { roleManager = (RoleManager) getApplication().getSystemService(Context.ROLE_SERVICE); } - @Test(expected = IllegalArgumentException.class) + @Test public void isRoleHeld_shouldThrowWithNullArgument() { - shadowOf(roleManager).isRoleHeld(null); + assertThrows(IllegalArgumentException.class, () -> shadowOf(roleManager).isRoleHeld(null)); } - @Test() + @Test public void addHeldRole_isPresentInIsRoleHeld() { shadowOf(roleManager).addHeldRole(RoleManager.ROLE_SMS); assertThat(roleManager.isRoleHeld(RoleManager.ROLE_SMS)).isTrue(); } - @Test() + @Test public void removeHeldRole_notPresentInIsRoleHeld() { shadowOf(roleManager).addHeldRole(RoleManager.ROLE_SMS); shadowOf(roleManager).removeHeldRole(RoleManager.ROLE_SMS); assertThat(roleManager.isRoleHeld(RoleManager.ROLE_SMS)).isFalse(); } - @Test() + @Test public void isRoleHeld_noValueByDefault() { assertThat(roleManager.isRoleHeld(RoleManager.ROLE_SMS)).isFalse(); } @@ -53,26 +56,75 @@ public final class ShadowRoleManagerTest { assertThrows(IllegalArgumentException.class, () -> shadowOf(roleManager).isRoleAvailable(null)); } - @Test() + @Test public void addAvailableRole_isPresentInIsRoleAvailable() { - shadowOf(roleManager).addAvailableRole(RoleManager.ROLE_SMS); - assertThat(roleManager.isRoleAvailable(RoleManager.ROLE_SMS)).isTrue(); + shadowOf(roleManager).addAvailableRole("some.weird.role"); + assertThat(roleManager.isRoleAvailable("some.weird.role")).isTrue(); } - @Test() + @Test public void addAvailableRole_shouldThrowWithEmptyArgument() { assertThrows(IllegalArgumentException.class, () -> shadowOf(roleManager).addAvailableRole("")); } - @Test() + @Test public void removeAvailableRole_notPresentInIsRoleAvailable() { shadowOf(roleManager).addAvailableRole(RoleManager.ROLE_SMS); shadowOf(roleManager).removeAvailableRole(RoleManager.ROLE_SMS); assertThat(roleManager.isRoleAvailable(RoleManager.ROLE_SMS)).isFalse(); } - @Test() - public void isRoleAvailable_noValueByDefault() { - assertThat(roleManager.isRoleAvailable(RoleManager.ROLE_SMS)).isFalse(); + @Test + @Config(minSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + public void getDefaultApplication_shouldReturnRoleOwner() { + shadowOf(roleManager).addHeldRole(RoleManager.ROLE_SMS); + assertThat(roleManager.getDefaultApplication(RoleManager.ROLE_SMS)) + .isEqualTo(getApplication().getPackageName()); + } + + @Test + @Config(minSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + public void getDefaultApplication_shouldReturnPackageSet() { + shadowOf(roleManager).addAvailableRole(RoleManager.ROLE_SMS); + shadowOf(getApplication().getPackageManager()) + .installPackage(PackageInfoBuilder.newBuilder().setPackageName("test.app").build()); + AtomicBoolean resultHolder = new AtomicBoolean(false); + shadowOf(roleManager) + .setDefaultApplication( + RoleManager.ROLE_SMS, + "test.app", + 0, + directExecutor(), + result -> resultHolder.set(result)); + assertThat(roleManager.getDefaultApplication(RoleManager.ROLE_SMS)).isEqualTo("test.app"); + assertThat(resultHolder.get()).isTrue(); + } + + @Test + @Config(minSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + public void setDefaultApplication_checksAppInstalled() { + AtomicBoolean resultHolder = new AtomicBoolean(true); + shadowOf(roleManager) + .setDefaultApplication( + RoleManager.ROLE_SMS, + "test.app", + 0, + directExecutor(), + result -> resultHolder.set(result)); + assertThat(resultHolder.get()).isFalse(); + assertThat(roleManager.getDefaultApplication(RoleManager.ROLE_SMS)).isNull(); + } + + @Test + @Config(minSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + public void setDefaultApplication_checksRoleAllowed() { + shadowOf(getApplication().getPackageManager()) + .installPackage(PackageInfoBuilder.newBuilder().setPackageName("test.app").build()); + assertThrows( + IllegalArgumentException.class, + () -> + shadowOf(roleManager) + .setDefaultApplication( + "bogus.role", "test.app", 0, directExecutor(), result -> {})); } } diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowSafetyCenterManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowSafetyCenterManagerTest.java index b9977262b..d0e3e61bb 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowSafetyCenterManagerTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowSafetyCenterManagerTest.java @@ -2,6 +2,7 @@ package org.robolectric.shadows; import static androidx.test.core.app.ApplicationProvider.getApplicationContext; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; import android.os.Build.VERSION_CODES; import android.safetycenter.SafetyCenterManager; @@ -10,6 +11,7 @@ import android.safetycenter.SafetySourceData; import android.safetycenter.SafetySourceErrorDetails; import org.junit.Before; import org.junit.Test; +import org.junit.function.ThrowingRunnable; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; @@ -385,4 +387,107 @@ public final class ShadowSafetyCenterManagerTest { assertThat(shadowSafetyCenterManager.getLastSafetySourceError("id2")) .isSameInstanceAs(errorDetails2); } + + @Test + public void throwOnSafetySourceId_safetyCenterDisabled_doesntThrowForAllIds() { + SafetyCenterManager safetyCenterManager = + getApplicationContext().getSystemService(SafetyCenterManager.class); + ShadowSafetyCenterManager shadowSafetyCenterManager = + Shadow.extract(getApplicationContext().getSystemService(SafetyCenterManager.class)); + + shadowSafetyCenterManager.throwOnSafetySourceId("id"); + + shadowSafetyCenterManager.setSafetyCenterEnabled(false); + safetyCenterManager.setSafetySourceData( + "id", + new SafetySourceData.Builder().build(), + new SafetyEvent.Builder(SafetyEvent.SAFETY_EVENT_TYPE_REFRESH_REQUESTED) + .setRefreshBroadcastId("id") + .build()); + SafetySourceData unused = safetyCenterManager.getSafetySourceData("id"); + safetyCenterManager.reportSafetySourceError( + "id", + new SafetySourceErrorDetails( + new SafetyEvent.Builder(SafetyEvent.SAFETY_EVENT_TYPE_REFRESH_REQUESTED) + .setRefreshBroadcastId("id") + .build())); + } + + @Test + public void throwOnSafetySourceId_safetyCenterEnabled_doesntThrowForOtherIds() { + SafetyCenterManager safetyCenterManager = + getApplicationContext().getSystemService(SafetyCenterManager.class); + ShadowSafetyCenterManager shadowSafetyCenterManager = + Shadow.extract(getApplicationContext().getSystemService(SafetyCenterManager.class)); + + shadowSafetyCenterManager.throwOnSafetySourceId("unrelated_id"); + + shadowSafetyCenterManager.setSafetyCenterEnabled(true); + safetyCenterManager.setSafetySourceData( + "id", + new SafetySourceData.Builder().build(), + new SafetyEvent.Builder(SafetyEvent.SAFETY_EVENT_TYPE_REFRESH_REQUESTED) + .setRefreshBroadcastId("id") + .build()); + SafetySourceData unused = safetyCenterManager.getSafetySourceData("id"); + safetyCenterManager.reportSafetySourceError( + "id", + new SafetySourceErrorDetails( + new SafetyEvent.Builder(SafetyEvent.SAFETY_EVENT_TYPE_REFRESH_REQUESTED) + .setRefreshBroadcastId("id") + .build())); + } + + @Test + public void throwOnSafetySourceId_safetyCenterEnabled_throwsForGivenIds() { + SafetyCenterManager safetyCenterManager = + getApplicationContext().getSystemService(SafetyCenterManager.class); + ShadowSafetyCenterManager shadowSafetyCenterManager = + Shadow.extract(getApplicationContext().getSystemService(SafetyCenterManager.class)); + + shadowSafetyCenterManager.throwOnSafetySourceId("id1"); + shadowSafetyCenterManager.throwOnSafetySourceId("id2"); + + shadowSafetyCenterManager.setSafetyCenterEnabled(true); + assertThrowsIllegalArgumentExceptionForSource( + "id1", + new ThrowingRunnable() { + @Override + public void run() { + safetyCenterManager.setSafetySourceData( + "id1", + new SafetySourceData.Builder().build(), + new SafetyEvent.Builder(SafetyEvent.SAFETY_EVENT_TYPE_REFRESH_REQUESTED) + .setRefreshBroadcastId("id") + .build()); + } + }); + assertThrowsIllegalArgumentExceptionForSource( + "id2", + new ThrowingRunnable() { + @Override + public void run() { + SafetySourceData unused = safetyCenterManager.getSafetySourceData("id2"); + } + }); + assertThrowsIllegalArgumentExceptionForSource( + "id1", + new ThrowingRunnable() { + @Override + public void run() { + safetyCenterManager.reportSafetySourceError( + "id1", + new SafetySourceErrorDetails( + new SafetyEvent.Builder(SafetyEvent.SAFETY_EVENT_TYPE_REFRESH_REQUESTED) + .setRefreshBroadcastId("id") + .build())); + } + }); + } + + private static void assertThrowsIllegalArgumentExceptionForSource( + String safetySourceId, ThrowingRunnable runnable) { + IllegalArgumentException e = assertThrows(IllegalArgumentException.class, runnable); + assertThat(e).hasMessageThat().contains(safetySourceId); + } } diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowScrollViewTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowScrollViewTest.java index 536e6dc21..6c5c6ac2d 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowScrollViewTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowScrollViewTest.java @@ -16,21 +16,32 @@ import org.robolectric.Robolectric; public class ShadowScrollViewTest { @Test public void shouldSmoothScrollTo() { - ScrollView scrollView = new ScrollView(ApplicationProvider.getApplicationContext()); - scrollView.smoothScrollTo(7, 6); + // This test depends on broken scrolling behavior. + System.setProperty("robolectric.useRealScrolling", "false"); + try { + ScrollView scrollView = new ScrollView(ApplicationProvider.getApplicationContext()); + scrollView.smoothScrollTo(7, 6); - assertEquals(7, scrollView.getScrollX()); - assertEquals(6, scrollView.getScrollY()); + assertEquals(7, scrollView.getScrollX()); + assertEquals(6, scrollView.getScrollY()); + } finally { + System.clearProperty("robolectric.useRealScrolling"); + } } @Test public void shouldSmoothScrollBy() { - ScrollView scrollView = new ScrollView(ApplicationProvider.getApplicationContext()); - scrollView.smoothScrollTo(7, 6); - scrollView.smoothScrollBy(10, 20); - - assertEquals(17, scrollView.getScrollX()); - assertEquals(26, scrollView.getScrollY()); + // This test depends on broken scrolling behavior. + System.setProperty("robolectric.useRealScrolling", "false"); + try { + ScrollView scrollView = new ScrollView(ApplicationProvider.getApplicationContext()); + scrollView.smoothScrollTo(7, 6); + scrollView.smoothScrollBy(10, 20); + assertEquals(17, scrollView.getScrollX()); + assertEquals(26, scrollView.getScrollY()); + } finally { + System.clearProperty("robolectric.useRealScrolling"); + } } @Test diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowSensorManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowSensorManagerTest.java index 784bbcd3f..a1c949aa6 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowSensorManagerTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowSensorManagerTest.java @@ -260,7 +260,6 @@ public class ShadowSensorManagerTest { } @Test - @Config(minSdk = Build.VERSION_CODES.KITKAT) public void flush_shouldCallOnFlushCompleted() { Sensor accelSensor = ShadowSensor.newInstance(TYPE_ACCELEROMETER); Sensor gyroSensor = ShadowSensor.newInstance(TYPE_GYROSCOPE); diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowServiceManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowServiceManagerTest.java index 68778c584..8c172775c 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowServiceManagerTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowServiceManagerTest.java @@ -19,11 +19,6 @@ import org.robolectric.annotation.Config; /** Tests for {@link ShadowServiceManager}. */ @RunWith(AndroidJUnit4.class) public final class ShadowServiceManagerTest { - - @Test - public void getService_available_shouldReturnNonNull() { - assertThat(ServiceManager.getService(Context.INPUT_METHOD_SERVICE)).isNotNull(); - } @Test @Config(sdk = VERSION_CODES.S) @@ -32,12 +27,30 @@ public final class ShadowServiceManagerTest { } @Test - public void getService_unavailableService_shouldReturnNull() { + public void getService_available_shouldReturnNonNull() { + assertThat(ServiceManager.getService(Context.INPUT_METHOD_SERVICE)).isNotNull(); + } + + @Test + public void getService_unavailable_shouldReturnNull() { ShadowServiceManager.setServiceAvailability(Context.INPUT_METHOD_SERVICE, false); + assertThat(ServiceManager.getService(Context.INPUT_METHOD_SERVICE)).isNull(); } @Test + public void checkService_available_shouldReturnNonNull() { + assertThat(ServiceManager.checkService(Context.INPUT_METHOD_SERVICE)).isNotNull(); + } + + @Test + public void checkService_unavailable_shouldReturnNull() { + ShadowServiceManager.setServiceAvailability(Context.INPUT_METHOD_SERVICE, false); + + assertThat(ServiceManager.checkService(Context.INPUT_METHOD_SERVICE)).isNull(); + } + + @Test public void getService_multipleThreads_binderRace() throws Exception { ExecutorService e = Executors.newFixedThreadPool(4); final AtomicReference<Exception> thrownException = new AtomicReference<>(); diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowSettingsTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowSettingsTest.java index 44da6104c..ae2737ccb 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowSettingsTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowSettingsTest.java @@ -2,7 +2,6 @@ package org.robolectric.shadows; import static android.location.LocationManager.GPS_PROVIDER; import static android.location.LocationManager.NETWORK_PROVIDER; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2; import static android.os.Build.VERSION_CODES.LOLLIPOP; import static android.os.Build.VERSION_CODES.O; @@ -62,7 +61,6 @@ public class ShadowSettingsTest { } @Test - @Config(minSdk = JELLY_BEAN_MR1) public void testGlobalGetInt() { assertThat(Settings.Global.getInt(contentResolver, "property", 0)).isEqualTo(0); assertThat(Settings.Global.getInt(contentResolver, "property", 2)).isEqualTo(2); @@ -135,15 +133,13 @@ public class ShadowSettingsTest { } @Test - @Config(minSdk = JELLY_BEAN_MR1) - public void testSetAdbEnabled_sinceJBMR1_settingsGlobal_true() { + public void testSetAdbEnabled_settingsGlobal_true() { ShadowSettings.setAdbEnabled(true); assertThat(Global.getInt(context.getContentResolver(), Global.ADB_ENABLED, 0)).isEqualTo(1); } @Test - @Config(minSdk = JELLY_BEAN_MR1) - public void testSetAdbEnabled_sinceJBMR1_settingsGlobal_false() { + public void testSetAdbEnabled_settingsGlobal_false() { ShadowSettings.setAdbEnabled(false); assertThat(Global.getInt(context.getContentResolver(), Global.ADB_ENABLED, 1)).isEqualTo(0); } @@ -163,16 +159,14 @@ public class ShadowSettingsTest { } @Test - @Config(minSdk = JELLY_BEAN_MR1) - public void testSetInstallNonMarketApps_sinceJBMR1_settingsGlobal_true() { + public void testSetInstallNonMarketApps_settingsGlobal_true() { ShadowSettings.setInstallNonMarketApps(true); assertThat(Global.getInt(context.getContentResolver(), Global.INSTALL_NON_MARKET_APPS, 0)) .isEqualTo(1); } @Test - @Config(minSdk = JELLY_BEAN_MR1) - public void testSetInstallNonMarketApps_sinceJBMR1_settingsGlobal_false() { + public void testSetInstallNonMarketApps_settingsGlobal_false() { ShadowSettings.setInstallNonMarketApps(false); assertThat(Global.getInt(context.getContentResolver(), Global.INSTALL_NON_MARKET_APPS, 1)) .isEqualTo(0); @@ -266,7 +260,6 @@ public class ShadowSettingsTest { } @Test - @Config(minSdk = JELLY_BEAN_MR1) public void testGlobalGetFloat() { float durationScale = Global.getFloat( @@ -281,7 +274,6 @@ public class ShadowSettingsTest { } @Test - @Config(minSdk = JELLY_BEAN_MR1) public void differentContentResolver() { Context context = ApplicationProvider.getApplicationContext(); ContentResolver contentResolver1 = @@ -300,7 +292,6 @@ public class ShadowSettingsTest { } @Test - @Config(minSdk = JELLY_BEAN_MR1) public void global_animatorDurationScale() { long startTime = SystemClock.uptimeMillis(); ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 1); diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowSmsManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowSmsManagerTest.java index 9f9b521cc..29d5dfd60 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowSmsManagerTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowSmsManagerTest.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2; import static android.os.Build.VERSION_CODES.LOLLIPOP; import static android.os.Build.VERSION_CODES.LOLLIPOP_MR1; import static android.os.Build.VERSION_CODES.R; @@ -26,7 +25,6 @@ import org.robolectric.shadows.ShadowSmsManager.TextSmsParams; import org.robolectric.util.ReflectionHelpers; @RunWith(AndroidJUnit4.class) -@Config(minSdk = JELLY_BEAN_MR2) public class ShadowSmsManagerTest { private SmsManager smsManager = SmsManager.getDefault(); private final String scAddress = "serviceCenterAddress"; diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowSoundPoolTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowSoundPoolTest.java index 184fadf88..855b782fd 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowSoundPoolTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowSoundPoolTest.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2; import static android.os.Build.VERSION_CODES.LOLLIPOP; import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.mock; @@ -32,13 +31,6 @@ public class ShadowSoundPoolTest { } @Test - @Config(maxSdk = JELLY_BEAN_MR2) - public void shouldCreateSoundPool_JellyBean() { - SoundPool soundPool = new SoundPool(1, AudioManager.STREAM_MUSIC, 0); - assertThat(soundPool).isNotNull(); - } - - @Test public void playedSoundsFromResourcesAreRecorded() { SoundPool soundPool = createSoundPool(); diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowStatFsTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowStatFsTest.java index 6ae22e0e2..6f793e36a 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowStatFsTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowStatFsTest.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2; import static com.google.common.truth.Truth.assertThat; import android.os.StatFs; @@ -8,7 +7,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import java.io.File; import org.junit.Test; import org.junit.runner.RunWith; -import org.robolectric.annotation.Config; @RunWith(AndroidJUnit4.class) public class ShadowStatFsTest { @@ -77,7 +75,6 @@ public class ShadowStatFsTest { } @Test - @Config(minSdk = JELLY_BEAN_MR2) public void withApi18_shouldRegisterStats() { ShadowStatFs.registerStats("/tmp", 100, 20, 10); StatFs statsFs = new StatFs("/tmp"); @@ -91,7 +88,6 @@ public class ShadowStatFsTest { } @Test - @Config(minSdk = JELLY_BEAN_MR2) public void withApi18_shouldRegisterStatsWithFile() { ShadowStatFs.registerStats(new File("/tmp"), 100, 20, 10); StatFs statsFs = new StatFs(new File("/tmp").getAbsolutePath()); @@ -105,7 +101,6 @@ public class ShadowStatFsTest { } @Test - @Config(minSdk = JELLY_BEAN_MR2) public void withApi18_shouldResetStateBetweenTests() { StatFs statsFs = new StatFs("/tmp"); assertThat(statsFs.getBlockCountLong()).isEqualTo(0L); @@ -134,7 +129,6 @@ public class ShadowStatFsTest { } @Test - @Config(minSdk = JELLY_BEAN_MR2) public void withApi18_shouldRestat() { ShadowStatFs.registerStats("/tmp", 100, 20, 10); StatFs statsFs = new StatFs("/tmp"); diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowStorageManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowStorageManagerTest.java index ad410660e..cd7841598 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowStorageManagerTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowStorageManagerTest.java @@ -105,6 +105,24 @@ public class ShadowStorageManagerTest { .isTrue(); } + @Test + @Config(minSdk = N) + public void getStorageVolumeFromAnUserContext() { + File file1 = new File(internalStorage); + shadowOf(storageManager).addStorageVolume(buildAndGetStorageVolume(file1, "internal")); + Context userContext = getApplication(); + + try { + userContext = + getApplication().createPackageContextAsUser("system", 0, UserHandle.of(0 /* userId */)); + } catch (Exception e) { + throw new RuntimeException(e); + } + + StorageManager anotherStorageManager = userContext.getSystemService(StorageManager.class); + assertThat(shadowOf(anotherStorageManager).getStorageVolume(file1)).isNotNull(); + } + private StorageVolume buildAndGetStorageVolume(File file, String description) { Parcel parcel = Parcel.obtain(); parcel.writeInt(0); diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowSubscriptionManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowSubscriptionManagerTest.java index e3fd11c48..8ebde3424 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowSubscriptionManagerTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowSubscriptionManagerTest.java @@ -2,6 +2,7 @@ package org.robolectric.shadows; import static android.content.Context.TELEPHONY_SUBSCRIPTION_SERVICE; import static android.os.Build.VERSION_CODES.N; +import static android.os.Build.VERSION_CODES.O_MR1; import static android.os.Build.VERSION_CODES.P; import static android.os.Build.VERSION_CODES.Q; import static android.os.Build.VERSION_CODES.R; @@ -286,6 +287,43 @@ public class ShadowSubscriptionManagerTest { } @Test + @Config(minSdk = O_MR1) + public void getAccessibleSubscriptionInfoList() { + SubscriptionInfo expectedSubscriptionInfo = + SubscriptionInfoBuilder.newBuilder().setId(123).setIsEmbedded(true).buildSubscriptionInfo(); + + // Default + assertThat(shadowOf(subscriptionManager).getAccessibleSubscriptionInfoList()).isEmpty(); + + // Null vararg + shadowOf(subscriptionManager).setAccessibleSubscriptionInfos(); + assertThat(shadowOf(subscriptionManager).getAccessibleSubscriptionInfoList()).isEmpty(); + + // A specific subscription + shadowOf(subscriptionManager).setAccessibleSubscriptionInfos(expectedSubscriptionInfo); + assertThat(shadowOf(subscriptionManager).getAccessibleSubscriptionInfoList()) + .containsExactly(expectedSubscriptionInfo); + } + + @Test + @Config(minSdk = O_MR1) + public void setAccessibleSubscriptionInfoList_triggersSubscriptionsChanged() { + DummySubscriptionsChangedListener listener = new DummySubscriptionsChangedListener(); + subscriptionManager.addOnSubscriptionsChangedListener(listener); + // Invoked upon registration, but that's not important for this test + int initialInvocationCount = listener.subscriptionChangedCount; + + shadowOf(subscriptionManager) + .setAccessibleSubscriptionInfos( + SubscriptionInfoBuilder.newBuilder() + .setId(123) + .setIsEmbedded(true) + .buildSubscriptionInfo()); + + assertThat(listener.subscriptionChangedCount - initialInvocationCount).isEqualTo(1); + } + + @Test public void getAvailableSubscriptionInfoList() { SubscriptionInfo expectedSubscriptionInfo = SubscriptionInfoBuilder.newBuilder().setId(123).buildSubscriptionInfo(); @@ -305,6 +343,20 @@ public class ShadowSubscriptionManagerTest { } @Test + public void setAvailableSubscriptionInfoList_triggersSubscriptionsChanged() { + DummySubscriptionsChangedListener listener = new DummySubscriptionsChangedListener(); + subscriptionManager.addOnSubscriptionsChangedListener(listener); + // Invoked upon registration, but that's not important for this test + int initialInvocationCount = listener.subscriptionChangedCount; + + shadowOf(subscriptionManager) + .setAvailableSubscriptionInfos( + SubscriptionInfoBuilder.newBuilder().setId(123).buildSubscriptionInfo()); + + assertThat(listener.subscriptionChangedCount - initialInvocationCount).isEqualTo(1); + } + + @Test @Config(maxSdk = P) public void getPhoneId_shouldReturnPhoneIdIfSet() { ShadowSubscriptionManager.putPhoneId(123, 456); diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowSurfaceControlTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowSurfaceControlTest.java index 6bf42f48a..8754395ed 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowSurfaceControlTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowSurfaceControlTest.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2; import static android.os.Build.VERSION_CODES.O; import static android.os.Build.VERSION_CODES.P; import static android.os.Build.VERSION_CODES.Q; @@ -18,7 +17,6 @@ import org.robolectric.util.ReflectionHelpers; import org.robolectric.util.ReflectionHelpers.ClassParameter; /** Tests for {@link org.robolectric.shadows.ShadowSurfaceControl} */ -@Config(minSdk = JELLY_BEAN_MR2) @RunWith(AndroidJUnit4.class) public class ShadowSurfaceControlTest { // The spurious CloseGuard warnings happens in Q, where the CloseGuard is always opened. diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowSystemHealthManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowSystemHealthManagerTest.java new file mode 100644 index 000000000..6a95c9229 --- /dev/null +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowSystemHealthManagerTest.java @@ -0,0 +1,75 @@ +package org.robolectric.shadows; + +import static android.os.Build.VERSION_CODES.N; +import static com.google.common.truth.Truth.assertThat; + +import android.content.Context; +import android.os.Process; +import android.os.health.HealthStats; +import android.os.health.SystemHealthManager; +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.annotation.Config; +import org.robolectric.shadow.api.Shadow; + +@RunWith(AndroidJUnit4.class) +@Config(minSdk = N) +public final class ShadowSystemHealthManagerTest { + + private static final int MY_UID = Process.myUid(); + private static final int OTHER_UID_1 = MY_UID + 1; + private static final int OTHER_UID_2 = MY_UID + 2; + + private static final HealthStats MY_UID_HEALTH_STATS = + HealthStatsBuilder.newBuilder().setDataType("my_uid_stats").build(); + private static final HealthStats OTHER_UID_1_HEALTH_STATS = + HealthStatsBuilder.newBuilder().setDataType("other_uid_1_stats").build(); + private static final HealthStats OTHER_UID_2_HEALTH_STATS = + HealthStatsBuilder.newBuilder().setDataType("other_uid_2_stats").build(); + + private SystemHealthManager systemHealthManager; + private ShadowSystemHealthManager shadowSystemHealthManager; + + @Before + public void setUp() { + systemHealthManager = + (SystemHealthManager) + ApplicationProvider.getApplicationContext() + .getSystemService(Context.SYSTEM_HEALTH_SERVICE); + shadowSystemHealthManager = Shadow.extract(systemHealthManager); + + shadowSystemHealthManager.addHealthStats(MY_UID_HEALTH_STATS); + shadowSystemHealthManager.addHealthStatsForUid(OTHER_UID_1, OTHER_UID_1_HEALTH_STATS); + shadowSystemHealthManager.addHealthStatsForUid(OTHER_UID_2, OTHER_UID_2_HEALTH_STATS); + } + + @Test + public void snapshotForMyUid_expectedResult() throws Exception { + HealthStats stats = systemHealthManager.takeMyUidSnapshot(); + + assertThat(stats).isEqualTo(MY_UID_HEALTH_STATS); + } + + @Test + public void snapshotForOtherUids_expectedResult() throws Exception { + HealthStats stats1 = systemHealthManager.takeUidSnapshot(OTHER_UID_1); + HealthStats stats2 = systemHealthManager.takeUidSnapshot(OTHER_UID_2); + + assertThat(stats1).isEqualTo(OTHER_UID_1_HEALTH_STATS); + assertThat(stats2).isEqualTo(OTHER_UID_2_HEALTH_STATS); + } + + @Test + public void snapshotForAllUids_expectedResult() throws Exception { + int[] uids = {OTHER_UID_1, MY_UID, OTHER_UID_2}; + + HealthStats[] stats = systemHealthManager.takeUidSnapshots(uids); + + assertThat(stats[0]).isEqualTo(OTHER_UID_1_HEALTH_STATS); + assertThat(stats[1]).isEqualTo(MY_UID_HEALTH_STATS); + assertThat(stats[2]).isEqualTo(OTHER_UID_2_HEALTH_STATS); + } +} diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowTelecomManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowTelecomManagerTest.java index 826d36391..04e7b6e01 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowTelecomManagerTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowTelecomManagerTest.java @@ -7,6 +7,7 @@ import static android.os.Build.VERSION_CODES.N; import static android.os.Build.VERSION_CODES.O; import static android.os.Build.VERSION_CODES.Q; import static android.os.Build.VERSION_CODES.R; +import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertThrows; import static org.mockito.ArgumentMatchers.eq; @@ -96,6 +97,20 @@ public class ShadowTelecomManagerTest { } @Test + @Config(minSdk = UPSIDE_DOWN_CAKE) + public void registerWithTransactionalCapabilites_addsSelfManagedCapability() { + PhoneAccountHandle handle = createHandle("id"); + PhoneAccount phoneAccount = + PhoneAccount.builder(handle, "main_account") + // Transactional, but not explicitly self-managed. + .setCapabilities(PhoneAccount.CAPABILITY_SUPPORTS_TRANSACTIONAL_OPERATIONS) + .build(); + telecomService.registerPhoneAccount(phoneAccount); + + assertThat(telecomService.getSelfManagedPhoneAccounts()).contains(handle); + } + + @Test public void getPhoneAccount_noPermission_throwsSecurityException() { shadowOf(telecomService).setReadPhoneStatePermission(false); diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowTelephonyManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowTelephonyManagerTest.java index 90c74d105..afabf1db0 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowTelephonyManagerTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowTelephonyManagerTest.java @@ -1,9 +1,6 @@ package org.robolectric.shadows; import static android.content.Context.TELEPHONY_SERVICE; -import static android.os.Build.VERSION_CODES.JELLY_BEAN; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2; import static android.os.Build.VERSION_CODES.LOLLIPOP_MR1; import static android.os.Build.VERSION_CODES.M; import static android.os.Build.VERSION_CODES.N; @@ -53,7 +50,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.net.Uri; -import android.os.Build.VERSION; +import android.os.Build; import android.os.PersistableBundle; import android.telecom.PhoneAccountHandle; import android.telephony.CellInfo; @@ -98,11 +95,11 @@ import org.robolectric.util.ReflectionHelpers; import org.robolectric.util.ReflectionHelpers.ClassParameter; @RunWith(AndroidJUnit4.class) -@Config(minSdk = JELLY_BEAN) public class ShadowTelephonyManagerTest { private TelephonyManager telephonyManager; private ShadowTelephonyManager shadowTelephonyManager; + private TelephonyManager tmForSub5; @Before public void setUp() throws Exception { @@ -110,6 +107,25 @@ public class ShadowTelephonyManagerTest { shadowTelephonyManager = Shadow.extract(telephonyManager); shadowOf((Application) ApplicationProvider.getApplicationContext()) .grantPermissions(permission.READ_PRIVILEGED_PHONE_STATE); + tmForSub5 = newTelephonyManager(5); + } + + private TelephonyManager newTelephonyManager(Integer subId) { + Class<?>[] parameters; + Object[] arguments; + if (subId == null || Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { + parameters = new Class<?>[] {Context.class}; + arguments = new Object[] {ApplicationProvider.getApplicationContext()}; + } else { + parameters = new Class<?>[] {Context.class, int.class}; + arguments = new Object[] {ApplicationProvider.getApplicationContext(), subId}; + } + TelephonyManager newInstance = + Shadow.newInstance(TelephonyManager.class, parameters, arguments); + if (subId != null) { + shadowTelephonyManager.setTelephonyManagerForSubscriptionId(subId, newInstance); + } + return newInstance; } @Test @@ -119,9 +135,7 @@ public class ShadowTelephonyManagerTest { verify(listener).onCallStateChanged(CALL_STATE_IDLE, null); verify(listener).onCellLocationChanged(null); - if (VERSION.SDK_INT >= JELLY_BEAN_MR1) { - verify(listener).onCellInfoChanged(Collections.emptyList()); - } + verify(listener).onCellInfoChanged(Collections.emptyList()); } @Test @@ -164,6 +178,15 @@ public class ShadowTelephonyManagerTest { @Test @Config(minSdk = M) + public void setDeviceId_withSlot_doesNotAffectCallingInstance() { + String testId = "TESTING123"; + shadowOf(telephonyManager).setDeviceId(123, testId); + assertThat(telephonyManager.getDeviceId()).isNull(); + assertThat(telephonyManager.getDeviceId(123)).isEqualTo(testId); + } + + @Test + @Config(minSdk = M) public void shouldGiveDeviceIdForSlot() { shadowOf(telephonyManager).setDeviceId(1, "device in slot 1"); shadowOf(telephonyManager).setDeviceId(2, "device in slot 2"); @@ -199,6 +222,14 @@ public class ShadowTelephonyManagerTest { @Test @Config(minSdk = O) + public void setImei_withSlotId_acceptsNull() { + shadowOf(telephonyManager).setImei(0, "imei0"); + shadowOf(telephonyManager).setImei(0, null); + assertEquals(null, telephonyManager.getImei(0)); + } + + @Test + @Config(minSdk = O) public void getMeid() { String testMeid = "4test meid"; shadowOf(telephonyManager).setMeid(testMeid); @@ -213,6 +244,7 @@ public class ShadowTelephonyManagerTest { shadowOf(telephonyManager).setMeid(1, "meid1"); assertEquals("meid0", telephonyManager.getMeid(0)); assertEquals("meid1", telephonyManager.getMeid(1)); + assertEquals("defaultMeid", telephonyManager.getMeid()); } @Test @@ -263,7 +295,6 @@ public class ShadowTelephonyManagerTest { } @Test - @Config(minSdk = JELLY_BEAN_MR1) public void shouldGiveAllCellInfo() { PhoneStateListener listener = mock(PhoneStateListener.class); telephonyManager.listen(listener, LISTEN_CELL_INFO); @@ -347,7 +378,6 @@ public class ShadowTelephonyManagerTest { } @Test - @Config(minSdk = JELLY_BEAN_MR2) public void shouldGiveGroupIdLevel1() { shadowOf(telephonyManager).setGroupIdLevel1("SomeGroupId"); assertEquals("SomeGroupId", telephonyManager.getGroupIdLevel1()); @@ -386,6 +416,14 @@ public class ShadowTelephonyManagerTest { } @Test + @Config(minSdk = M) + public void setCurrentPhoneType_fromPhoneId_canBeReadFromAnyInstance() { + shadowOf(telephonyManager).setCurrentPhoneType(123, TelephonyManager.PHONE_TYPE_CDMA); + + assertEquals(TelephonyManager.PHONE_TYPE_CDMA, tmForSub5.getCurrentPhoneType(123)); + } + + @Test public void shouldGiveCellLocation() { PhoneStateListener listener = mock(PhoneStateListener.class); telephonyManager.listen(listener, LISTEN_CELL_LOCATION); @@ -462,6 +500,14 @@ public class ShadowTelephonyManagerTest { } @Test + @Config(minSdk = R) + public void setSmsCapable_modifiesAllInstances() { + shadowOf(telephonyManager).setIsSmsCapable(false); + newTelephonyManager(123); + assertThat(telephonyManager.createForSubscriptionId(123).isSmsCapable()).isFalse(); + } + + @Test @Config(minSdk = O) public void shouldGiveCarrierConfigIfSet() { PersistableBundle bundle = new PersistableBundle(); @@ -543,6 +589,18 @@ public class ShadowTelephonyManagerTest { @Test @Config(minSdk = N) + public void setVoicemailVibrationEnabled_accessibleFromAllTelephonyManagers() { + PhoneAccountHandle phoneAccountHandle = + new PhoneAccountHandle( + new ComponentName(ApplicationProvider.getApplicationContext(), Object.class), "handle"); + + shadowOf(telephonyManager).setVoicemailVibrationEnabled(phoneAccountHandle, true); + + assertTrue(telephonyManager.isVoicemailVibrationEnabled(phoneAccountHandle)); + } + + @Test + @Config(minSdk = N) public void shouldGiveVoicemailRingtoneUri() { PhoneAccountHandle phoneAccountHandle = new PhoneAccountHandle( @@ -569,6 +627,19 @@ public class ShadowTelephonyManagerTest { } @Test + @Config(minSdk = N) + public void setVoicemailRingtoneUri_accessibleFromAllTelephonyManagers() { + PhoneAccountHandle phoneAccountHandle = + new PhoneAccountHandle( + new ComponentName(ApplicationProvider.getApplicationContext(), Object.class), "handle"); + Uri ringtoneUri = Uri.fromParts("file", "ringtone.mp3", /* fragment= */ null); + + shadowOf(telephonyManager).setVoicemailRingtoneUri(phoneAccountHandle, ringtoneUri); + + assertEquals(ringtoneUri, telephonyManager.getVoicemailRingtoneUri(phoneAccountHandle)); + } + + @Test @Config(minSdk = O) public void shouldCreateForPhoneAccountHandle() { PhoneAccountHandle phoneAccountHandle = @@ -584,6 +655,20 @@ public class ShadowTelephonyManagerTest { } @Test + @Config(minSdk = O) + public void shouldCreateForPhoneAccountHandle_fromAllInstances() { + PhoneAccountHandle phoneAccountHandle = + new PhoneAccountHandle( + new ComponentName(ApplicationProvider.getApplicationContext(), Object.class), "handle"); + TelephonyManager mockTelephonyManager = mock(TelephonyManager.class); + + shadowOf(telephonyManager) + .setTelephonyManagerForHandle(phoneAccountHandle, mockTelephonyManager); + + assertEquals(mockTelephonyManager, tmForSub5.createForPhoneAccountHandle(phoneAccountHandle)); + } + + @Test @Config(minSdk = N) public void shouldCreateForSubscriptionId() { int subscriptionId = 42; @@ -596,6 +681,18 @@ public class ShadowTelephonyManagerTest { } @Test + @Config(minSdk = N) + public void shouldCreateForSubscriptionId_fromAllInstances() { + int subscriptionId = 42; + TelephonyManager mockTelephonyManager = mock(TelephonyManager.class); + + shadowOf(telephonyManager) + .setTelephonyManagerForSubscriptionId(subscriptionId, mockTelephonyManager); + + assertEquals(mockTelephonyManager, tmForSub5.createForSubscriptionId(subscriptionId)); + } + + @Test @Config(minSdk = O) public void shouldSetServiceState() { PhoneStateListener listener = mock(PhoneStateListener.class); @@ -686,6 +783,13 @@ public class ShadowTelephonyManagerTest { @Test @Config(minSdk = O) + public void getSimState_defaultForZeroSpecial() { + assertThat(telephonyManager.getSimState(1)).isEqualTo(TelephonyManager.SIM_STATE_UNKNOWN); + assertThat(telephonyManager.getSimState(0)).isEqualTo(TelephonyManager.SIM_STATE_READY); + } + + @Test + @Config(minSdk = O) public void shouldGetSimStateUsingSlotNumber() { int expectedSimState = TelephonyManager.SIM_STATE_ABSENT; int slotNumber = 3; @@ -695,22 +799,62 @@ public class ShadowTelephonyManagerTest { } @Test + @Config(minSdk = O) + public void setSimState_withSlotParameter_doesNotAffectCaller() { + int expectedSimState = TelephonyManager.SIM_STATE_ABSENT; + int slotNumber = 3; + shadowOf(telephonyManager).setSimState(slotNumber, expectedSimState); + + assertThat(telephonyManager.getSimState()).isEqualTo(TelephonyManager.SIM_STATE_READY); + } + + @Test public void shouldGetSimIso() { assertThat(telephonyManager.getSimCountryIso()).isEmpty(); } @Test @Config(minSdk = N, maxSdk = Q) + public void shouldGetSimIso_resetsZeroSpecial() { + assertThat(callGetSimCountryIso(telephonyManager, 1)).isNull(); + assertThat(callGetSimCountryIso(telephonyManager, 0)).isEmpty(); + } + + private String callGetSimCountryIso(TelephonyManager telephonyManager, int subId) { + return (String) + ReflectionHelpers.callInstanceMethod( + telephonyManager, "getSimCountryIso", ClassParameter.from(int.class, subId)); + } + + @Test + @Config(minSdk = N, maxSdk = Q) public void shouldGetSimIsoWhenSetUsingSlotNumber() { String expectedSimIso = "usa"; int subId = 2; shadowOf(telephonyManager).setSimCountryIso(subId, expectedSimIso); - assertThat( - (String) - ReflectionHelpers.callInstanceMethod( - telephonyManager, "getSimCountryIso", ClassParameter.from(int.class, subId))) - .isEqualTo(expectedSimIso); + assertThat(callGetSimCountryIso(telephonyManager, subId)).isEqualTo(expectedSimIso); + } + + @Test + @Config(minSdk = N, maxSdk = Q) + public void setSimIso_withSlotParameter_doesNotAffectCaller() { + String expectedSimIso = "usa"; + int subId = 2; + shadowOf(telephonyManager).setSimCountryIso(subId, expectedSimIso); + + assertThat(telephonyManager.getSimCountryIso()).isEqualTo(""); + } + + @Test + @Config(minSdk = N, maxSdk = Q) + public void setSimIso_withSlotParameter_acceptsNull() { + String expectedSimIso = "usa"; + int subId = 2; + shadowOf(telephonyManager).setSimCountryIso(subId, expectedSimIso); + shadowOf(telephonyManager).setSimCountryIso(subId, null); + + assertThat(callGetSimCountryIso(telephonyManager, subId)).isEqualTo(null); } @Test @@ -743,6 +887,24 @@ public class ShadowTelephonyManagerTest { @Test @Config(minSdk = M) + public void shouldGetCurrentPhoneTypeGivenSubId_fromAllInstances() { + int subId = 1; + int expectedPhoneType = TelephonyManager.PHONE_TYPE_GSM; + shadowOf(telephonyManager).setCurrentPhoneType(subId, expectedPhoneType); + + assertThat(tmForSub5.getCurrentPhoneType(subId)).isEqualTo(expectedPhoneType); + } + + @Test + @Config(minSdk = M) + public void clearPhoneTypes_setsStartingState() { + ShadowTelephonyManager.clearPhoneTypes(); + assertEquals(TelephonyManager.PHONE_TYPE_NONE, telephonyManager.getCurrentPhoneType(0)); + assertEquals(TelephonyManager.PHONE_TYPE_NONE, telephonyManager.getCurrentPhoneType(1)); + } + + @Test + @Config(minSdk = M) public void shouldGetCarrierPackageNamesForIntentAndPhone() { List<String> packages = Collections.singletonList("package1"); int phoneId = 123; @@ -754,6 +916,39 @@ public class ShadowTelephonyManagerTest { @Test @Config(minSdk = M) + public void setCarrierPackageNamesForPhone_acceptsNull() { + List<String> packages = Collections.singletonList("package1"); + int phoneId = 123; + shadowOf(telephonyManager).setCarrierPackageNamesForPhone(phoneId, packages); + shadowOf(telephonyManager).setCarrierPackageNamesForPhone(phoneId, null); + + assertThat(telephonyManager.getCarrierPackageNamesForIntentAndPhone(new Intent(), phoneId)) + .isEqualTo(null); + } + + @Test + @Config(minSdk = M) + public void shouldGetCarrierPackageNamesForIntentAndPhone_fromAllInstances() { + List<String> packages = Collections.singletonList("package1"); + int phoneId = 123; + shadowOf(telephonyManager).setCarrierPackageNamesForPhone(phoneId, packages); + + assertThat(tmForSub5.getCarrierPackageNamesForIntentAndPhone(new Intent(), phoneId)) + .isEqualTo(packages); + } + + @Test + @Config(minSdk = M) + public void shouldGetCarrierPackageNamesForIntentAndPhone_doesNotAffectCaller() { + List<String> packages = Collections.singletonList("package1"); + int phoneId = 123; + shadowOf(telephonyManager).setCarrierPackageNamesForPhone(phoneId, packages); + + assertThat(telephonyManager.getCarrierPackageNamesForIntent(new Intent())).isNull(); + } + + @Test + @Config(minSdk = M) public void shouldGetCarrierPackageNamesForIntent() { List<String> packages = Collections.singletonList("package1"); shadowOf(telephonyManager) @@ -763,10 +958,13 @@ public class ShadowTelephonyManagerTest { } @Test + @Config(minSdk = O) public void resetSimStates_shouldRetainDefaultState() { shadowOf(telephonyManager).resetSimStates(); assertThat(telephonyManager.getSimState()).isEqualTo(TelephonyManager.SIM_STATE_READY); + assertThat(telephonyManager.getSimState(1)).isEqualTo(TelephonyManager.SIM_STATE_UNKNOWN); + assertThat(telephonyManager.getSimState(0)).isEqualTo(TelephonyManager.SIM_STATE_READY); } @Test @@ -778,6 +976,14 @@ public class ShadowTelephonyManagerTest { } @Test + @Config(minSdk = N, maxSdk = Q) + public void resetSimCountryIsos_resetZeroSpecial() { + shadowOf(telephonyManager).resetSimCountryIsos(); + assertThat(callGetSimCountryIso(telephonyManager, 1)).isNull(); + assertThat(callGetSimCountryIso(telephonyManager, 0)).isEmpty(); + } + + @Test public void shouldSetSubscriberId() { String subscriberId = "123451234512345"; shadowOf(telephonyManager).setSubscriberId(subscriberId); @@ -803,6 +1009,23 @@ public class ShadowTelephonyManagerTest { } @Test + @Config(minSdk = Q) + public void getUiccCardsInfo() { + Object /*UiccCardInfo*/ cardsInfo1 = "new UiccCardInfo(true, true, null, 0, 0, true)"; + Object /*UiccCardInfo*/ cardsInfo2 = "new UiccCardInfo(true, true, null, 0, 1, true)"; + List<Object /*UiccCardInfo*/> cardInfos = ImmutableList.of(cardsInfo1, cardsInfo2); + shadowOf(telephonyManager).setUiccCardsInfo(cardInfos); + + assertThat(telephonyManager.getUiccCardsInfo()).isEqualTo(cardInfos); + } + + @Test + @Config(minSdk = Q) + public void getUiccCardsInfo_returnsListAfterReset() { + assertThat(telephonyManager.getUiccCardsInfo()).isEmpty(); + } + + @Test @Config(minSdk = O) public void shouldSetVisualVoicemailPackage() { shadowOf(telephonyManager).setVisualVoicemailPackageName("org.foo"); @@ -1227,4 +1450,39 @@ public class ShadowTelephonyManagerTest { .setPhoneAccountHandleSubscriptionId(phoneAccountHandle, subscriptionId); assertEquals(subscriptionId, telephonyManager.getSubscriptionId(phoneAccountHandle)); } + + @Test + @Config(minSdk = R) + public void getSubscriptionIdForPhoneAccountHandle_affectsAllInstances() { + int subscriptionId = 123; + PhoneAccountHandle phoneAccountHandle = + new PhoneAccountHandle( + new ComponentName(ApplicationProvider.getApplicationContext(), Object.class), "handle"); + shadowOf(telephonyManager) + .setPhoneAccountHandleSubscriptionId(phoneAccountHandle, subscriptionId); + assertEquals(subscriptionId, tmForSub5.getSubscriptionId(phoneAccountHandle)); + } + + @Test + @Config(minSdk = R) + public void getSubscriptionIdForPhoneAccountHandle_doesNotModifyCaller() { + int subscriptionId = 123; + PhoneAccountHandle phoneAccountHandle = + new PhoneAccountHandle( + new ComponentName(ApplicationProvider.getApplicationContext(), Object.class), "handle"); + shadowOf(telephonyManager) + .setPhoneAccountHandleSubscriptionId(phoneAccountHandle, subscriptionId); + assertEquals(5, tmForSub5.getSubscriptionId()); + } + + @Test + @Config(minSdk = R) + public void newInstance_alreadyKnowsSubId() { + Context context = ApplicationProvider.getApplicationContext(); + Class<?>[] parameters = new Class<?>[] {Context.class, int.class}; + Object[] arguments = new Object[] {context, 123}; + TelephonyManager tm = Shadow.newInstance(TelephonyManager.class, parameters, arguments); + + assertThat(tm.getSubscriptionId()).isEqualTo(123); + } } diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowTelephonyTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowTelephonyTest.java index 7846f0307..d0ed68329 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowTelephonyTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowTelephonyTest.java @@ -3,19 +3,16 @@ package org.robolectric.shadows; import static com.google.common.truth.Truth.assertThat; import android.content.Context; -import android.os.Build.VERSION_CODES; import android.provider.Telephony.Sms; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.robolectric.annotation.Config; import org.robolectric.shadows.ShadowTelephony.ShadowSms; /** Unit tests for {@link ShadowTelephony}. */ @RunWith(AndroidJUnit4.class) -@Config(minSdk = VERSION_CODES.KITKAT) public class ShadowTelephonyTest { private static final String TEST_PACKAGE_NAME = "test.package.name"; diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowTimeTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowTimeTest.java index 809437af7..239d1c665 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowTimeTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowTimeTest.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2; import static android.os.Build.VERSION_CODES.KITKAT_WATCH; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; @@ -13,7 +12,6 @@ import org.junit.runner.RunWith; import org.robolectric.annotation.Config; @RunWith(AndroidJUnit4.class) -@Config(minSdk = JELLY_BEAN_MR2) public class ShadowTimeTest { @Test diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowTraceTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowTraceTest.java index 07b57f9cb..a0d5b35de 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowTraceTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowTraceTest.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2; import static android.os.Build.VERSION_CODES.Q; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertThrows; @@ -21,7 +20,6 @@ import org.robolectric.shadows.ShadowTrace.Counter; /** Test for {@link ShadowTrace}. */ @RunWith(AndroidJUnit4.class) -@Config(minSdk = JELLY_BEAN_MR2) public class ShadowTraceTest { private static final String VERY_LONG_TAG_NAME = String.format(String.format("%%%ds", 128), "A"); diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowTransportControlsTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowTransportControlsTest.java new file mode 100644 index 000000000..243428b7f --- /dev/null +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowTransportControlsTest.java @@ -0,0 +1,163 @@ +package org.robolectric.shadows; + +import static android.media.session.PlaybackState.ACTION_PAUSE; +import static android.media.session.PlaybackState.ACTION_PLAY; +import static android.media.session.PlaybackState.ACTION_PLAY_FROM_SEARCH; +import static android.media.session.PlaybackState.ACTION_PLAY_FROM_URI; +import static android.media.session.PlaybackState.ACTION_PREPARE_FROM_SEARCH; +import static android.media.session.PlaybackState.ACTION_PREPARE_FROM_URI; +import static android.media.session.PlaybackState.ACTION_SEEK_TO; +import static android.media.session.PlaybackState.ACTION_SET_RATING; +import static android.media.session.PlaybackState.ACTION_SKIP_TO_NEXT; +import static android.media.session.PlaybackState.ACTION_SKIP_TO_PREVIOUS; +import static android.media.session.PlaybackState.ACTION_SKIP_TO_QUEUE_ITEM; +import static android.media.session.PlaybackState.ACTION_STOP; +import static android.os.Build.VERSION_CODES.LOLLIPOP; +import static android.os.Build.VERSION_CODES.M; +import static android.os.Build.VERSION_CODES.N; +import static com.google.common.truth.Truth.assertThat; +import static org.robolectric.shadow.api.Shadow.extract; + +import android.media.Rating; +import android.media.session.MediaController.TransportControls; +import android.media.session.MediaSession; +import android.net.Uri; +import android.os.Bundle; +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.annotation.Config; + +/** Tests for {@link ShadowTransportControls}. */ +@RunWith(AndroidJUnit4.class) +public class ShadowTransportControlsTest { + TransportControls transportControls; + private ShadowTransportControls shadowTransportControls; + + @Before + public void setup() { + MediaSession mediaSession = + new MediaSession(ApplicationProvider.getApplicationContext(), "TestMediaSession"); + transportControls = mediaSession.getController().getTransportControls(); + shadowTransportControls = extract(transportControls); + } + + @Test + @Config(minSdk = LOLLIPOP) + public void testPause_lastPerformedActionIsPause() { + transportControls.pause(); + + assertThat(shadowTransportControls.getLastPerformedAction()).isEqualTo(ACTION_PAUSE); + } + + @Test + @Config(minSdk = LOLLIPOP) + public void testPlay_lastPerformedActionIsPlay() { + transportControls.play(); + + assertThat(shadowTransportControls.getLastPerformedAction()).isEqualTo(ACTION_PLAY); + } + + @Test + @Config(minSdk = LOLLIPOP) + public void testPlayFromSearch_lastPerformedActionIsPlayFromSearch() { + transportControls.playFromSearch("query", new Bundle()); + + assertThat(shadowTransportControls.getLastPerformedAction()).isEqualTo(ACTION_PLAY_FROM_SEARCH); + } + + @Test + @Config(minSdk = M) + public void testPlayFromUri_lastPerformedActionIsPlayFromUri() { + Uri uri = Uri.parse("test://address"); + transportControls.playFromUri(uri, new Bundle()); + + assertThat(shadowTransportControls.getLastPerformedAction()).isEqualTo(ACTION_PLAY_FROM_URI); + assertThat(shadowTransportControls.getUri()).isEqualTo(uri); + } + + @Test + @Config(minSdk = N) + public void testPrepareFromSearch_lastPerformedActionIsPrepareFromSearch() { + transportControls.prepareFromSearch("query", new Bundle()); + + assertThat(shadowTransportControls.getLastPerformedAction()) + .isEqualTo(ACTION_PREPARE_FROM_SEARCH); + } + + @Test + @Config(minSdk = N) + public void testPrepareFromUri_lastPerformedActionIsPrepareFromUri() { + Uri uri = Uri.parse("test://address"); + transportControls.prepareFromUri(uri, new Bundle()); + + assertThat(shadowTransportControls.getLastPerformedAction()).isEqualTo(ACTION_PREPARE_FROM_URI); + assertThat(shadowTransportControls.getUri()).isEqualTo(uri); + } + + @Test + @Config(minSdk = LOLLIPOP) + public void testSeekTo_lastPerformedActionIsSeekTo() { + transportControls.seekTo(50); + + assertThat(shadowTransportControls.getLastPerformedAction()).isEqualTo(ACTION_SEEK_TO); + assertThat(shadowTransportControls.getSeekToPositionMs()).isEqualTo(50); + } + + @Test + @Config(minSdk = LOLLIPOP) + public void testSendCustomAction_customActionAndArgsAreRecorded() { + Bundle customActionArgs = new Bundle(); + customActionArgs.putInt("test", 5); + transportControls.sendCustomAction("action", customActionArgs); + + assertThat(shadowTransportControls.getCustomAction()).isEqualTo("action"); + assertThat(shadowTransportControls.getCustomActionArgs()).isEqualTo(customActionArgs); + } + + @Test + @Config(minSdk = LOLLIPOP) + public void testSetRating_lastPerformedActionIsSetRating() { + Rating rating = Rating.newPercentageRating(30F); + transportControls.setRating(rating); + + assertThat(shadowTransportControls.getLastPerformedAction()).isEqualTo(ACTION_SET_RATING); + assertThat(shadowTransportControls.getRating()).isEqualTo(rating); + } + + @Test + @Config(minSdk = LOLLIPOP) + public void testSkipToNext_lastPerformedActionIsSkipToNext() { + transportControls.skipToNext(); + + assertThat(shadowTransportControls.getLastPerformedAction()).isEqualTo(ACTION_SKIP_TO_NEXT); + } + + @Test + @Config(minSdk = LOLLIPOP) + public void testSkipToPrevious_lastPerformedActionIsSkipToPrevious() { + transportControls.skipToPrevious(); + + assertThat(shadowTransportControls.getLastPerformedAction()).isEqualTo(ACTION_SKIP_TO_PREVIOUS); + } + + @Test + @Config(minSdk = LOLLIPOP) + public void testSkipToPrevious_lastPerformedActionIsSkipToQueueItem() { + transportControls.skipToQueueItem(5); + + assertThat(shadowTransportControls.getLastPerformedAction()) + .isEqualTo(ACTION_SKIP_TO_QUEUE_ITEM); + assertThat(shadowTransportControls.getQueueItemId()).isEqualTo(5); + } + + @Test + @Config(minSdk = LOLLIPOP) + public void testStop_lastPerformedActionIsStop() { + transportControls.stop(); + + assertThat(shadowTransportControls.getLastPerformedAction()).isEqualTo(ACTION_STOP); + } +} diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowUIModeManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowUIModeManagerTest.java index 0fb9bc605..72c5e52e7 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowUIModeManagerTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowUIModeManagerTest.java @@ -19,6 +19,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; import org.robolectric.shadow.api.Shadow; @@ -275,6 +276,32 @@ public class ShadowUIModeManagerTest { .isEqualTo(UiModeManager.MODE_NIGHT_CUSTOM_TYPE_SCHEDULE); } + @Test + @Config(minSdk = S) + public void getProjectingPackages_noProjectingPackages_returnsEmpty() { + assertThat(uiModeManager.getProjectingPackages(UiModeManager.PROJECTION_TYPE_ALL)).isEmpty(); + } + + @Test + @Config(minSdk = S) + public void getProjectingPackages_projecting_returnsNotEmpty() { + setPermissions(android.Manifest.permission.TOGGLE_AUTOMOTIVE_PROJECTION); + uiModeManager.requestProjection(UiModeManager.PROJECTION_TYPE_AUTOMOTIVE); + + assertThat(uiModeManager.getProjectingPackages(UiModeManager.PROJECTION_TYPE_AUTOMOTIVE)) + .contains(RuntimeEnvironment.getApplication().getPackageName()); + } + + @Test + @Config(minSdk = S) + public void getProjectingPackages_projecting_allTypes_returnsNotEmpty() { + setPermissions(android.Manifest.permission.TOGGLE_AUTOMOTIVE_PROJECTION); + uiModeManager.requestProjection(UiModeManager.PROJECTION_TYPE_AUTOMOTIVE); + + assertThat(uiModeManager.getProjectingPackages(UiModeManager.PROJECTION_TYPE_ALL)) + .contains(RuntimeEnvironment.getApplication().getPackageName()); + } + private void setPermissions(String... permissions) { PackageInfo pi = new PackageInfo(); pi.packageName = context.getPackageName(); diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowUiAutomationTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowUiAutomationTest.java index 79a9c68a9..4a3d5d527 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowUiAutomationTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowUiAutomationTest.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2; import static android.os.Build.VERSION_CODES.KITKAT; import static com.google.common.truth.Truth.assertThat; @@ -19,7 +18,6 @@ import org.robolectric.annotation.Config; import org.robolectric.annotation.LooperMode; /** Test for {@link ShadowUiAutomation}. */ -@Config(minSdk = JELLY_BEAN_MR2) @RunWith(AndroidJUnit4.class) public class ShadowUiAutomationTest { @Config(sdk = KITKAT) diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowUserManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowUserManagerTest.java index 2e81f2203..9da42710a 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowUserManagerTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowUserManagerTest.java @@ -1,7 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2; import static android.os.Build.VERSION_CODES.KITKAT_WATCH; import static android.os.Build.VERSION_CODES.LOLLIPOP; import static android.os.Build.VERSION_CODES.M; @@ -95,7 +93,6 @@ public class ShadowUserManagerTest { } @Test - @Config(minSdk = JELLY_BEAN_MR2) public void testGetApplicationRestrictions() { String packageName = context.getPackageName(); assertThat(userManager.getApplicationRestrictions(packageName).size()).isEqualTo(0); @@ -132,7 +129,6 @@ public class ShadowUserManagerTest { } @Test - @Config(minSdk = JELLY_BEAN_MR2) public void setUserRestriction_forGivenUserHandle_setsTheRestriction() { assertThat(userManager.hasUserRestriction(UserManager.ENSURE_VERIFY_APPS)).isFalse(); @@ -143,7 +139,6 @@ public class ShadowUserManagerTest { } @Test - @Config(minSdk = JELLY_BEAN_MR2) public void setUserRestriction_forCurrentUser_setsTheRestriction() { assertThat(userManager.hasUserRestriction(UserManager.ENSURE_VERIFY_APPS)).isFalse(); @@ -153,7 +148,6 @@ public class ShadowUserManagerTest { } @Test - @Config(minSdk = JELLY_BEAN_MR2) public void getUserRestrictions() { assertThat(userManager.getUserRestrictions().size()).isEqualTo(0); @@ -175,7 +169,6 @@ public class ShadowUserManagerTest { } @Test - @Config(minSdk = JELLY_BEAN_MR2) public void clearUserRestrictions() { assertThat(userManager.getUserRestrictions().size()).isEqualTo(0); shadowOf(userManager) @@ -282,7 +275,6 @@ public class ShadowUserManagerTest { } @Test - @Config(minSdk = JELLY_BEAN_MR1) public void shouldGetSerialNumberForUser() { long serialNumberInvalid = -1L; @@ -299,7 +291,6 @@ public class ShadowUserManagerTest { } @Test - @Config(minSdk = JELLY_BEAN_MR1) public void getUserForNonExistSerialNumber() { long nonExistSerialNumber = 121; assertThat(userManager.getUserForSerialNumber(nonExistSerialNumber)).isNull(); @@ -307,7 +298,6 @@ public class ShadowUserManagerTest { } @Test - @Config(minSdk = JELLY_BEAN_MR1) public void shouldGetSerialNumberForProfile() { long serialNumberInvalid = -1L; @@ -317,7 +307,6 @@ public class ShadowUserManagerTest { } @Test - @Config(minSdk = JELLY_BEAN_MR1) public void shouldGetUserHandleFromSerialNumberForProfile() { long serialNumberInvalid = -1L; @@ -328,7 +317,6 @@ public class ShadowUserManagerTest { } @Test - @Config(minSdk = JELLY_BEAN_MR1) public void getSerialNumberForUser_returnsSetSerialNumberForUser() { UserHandle userHandle = newUserHandle(0); shadowOf(userManager).setSerialNumberForUser(userHandle, 123L); @@ -336,7 +324,6 @@ public class ShadowUserManagerTest { } @Test - @Config(minSdk = JELLY_BEAN_MR1) public void getUserHandle() { UserHandle expectedUserHandle = shadowOf(userManager).addUser(10, "secondary_user", 0); @@ -396,7 +383,6 @@ public class ShadowUserManagerTest { } @Test - @Config(minSdk = JELLY_BEAN_MR2) public void isLinkedUser() { assertThat(userManager.isLinkedUser()).isFalse(); @@ -478,7 +464,6 @@ public class ShadowUserManagerTest { } @Test - @Config(minSdk = JELLY_BEAN_MR1) public void isUserRunning() { UserHandle userHandle = newUserHandle(0); @@ -504,7 +489,6 @@ public class ShadowUserManagerTest { } @Test - @Config(minSdk = JELLY_BEAN_MR1) public void isUserRunningOrStopping() { UserHandle userHandle = newUserHandle(0); @@ -582,7 +566,6 @@ public class ShadowUserManagerTest { } @Test - @Config(minSdk = JELLY_BEAN_MR1) public void addSecondaryUser() { assertThat(userManager.getUserCount()).isEqualTo(1); UserHandle userHandle = shadowOf(userManager).addUser(10, "secondary_user", 0); @@ -591,7 +574,6 @@ public class ShadowUserManagerTest { } @Test - @Config(minSdk = JELLY_BEAN_MR1) public void removeSecondaryUser() { shadowOf(userManager).addUser(10, "secondary_user", 0); assertThat(shadowOf(userManager).removeUser(10)).isTrue(); @@ -635,7 +617,6 @@ public class ShadowUserManagerTest { } @Test - @Config(minSdk = JELLY_BEAN_MR1) public void switchToSecondaryUser() { shadowOf(userManager).addUser(10, "secondary_user", 0); shadowOf(userManager).switchUser(10); @@ -677,7 +658,6 @@ public class ShadowUserManagerTest { } @Test - @Config(minSdk = JELLY_BEAN_MR1) public void getUsers() { assertThat(userManager.getUsers()).hasSize(1); shadowOf(userManager).addUser(10, "secondary_user", 0); @@ -687,7 +667,6 @@ public class ShadowUserManagerTest { } @Test - @Config(minSdk = JELLY_BEAN_MR1) public void getUserInfo() { shadowOf(userManager).addUser(10, "secondary_user", 0); assertThat(userManager.getUserInfo(10)).isNotNull(); @@ -695,7 +674,6 @@ public class ShadowUserManagerTest { } @Test - @Config(minSdk = JELLY_BEAN_MR1) public void getUserInfoOfProfile() { shadowOf(userManager).addProfile(10, 11, "profile_user", 0); shadowOf(userManager).addProfile(10, 12, "profile_user_2", 0); @@ -897,7 +875,6 @@ public class ShadowUserManagerTest { } @Test - @Config(minSdk = JELLY_BEAN_MR1) public void getMaxSupportedUsers() { assertThat(UserManager.getMaxSupportedUsers()).isEqualTo(1); shadowOf(userManager).setMaxSupportedUsers(5); diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowUwbManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowUwbManagerTest.java index ecc5237db..bbea33710 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowUwbManagerTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowUwbManagerTest.java @@ -6,14 +6,17 @@ import static androidx.test.core.app.ApplicationProvider.getApplicationContext; import static com.google.common.truth.Truth.assertThat; import static com.google.common.util.concurrent.MoreExecutors.directExecutor; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import android.os.PersistableBundle; import android.uwb.RangingSession; import android.uwb.UwbManager; +import android.uwb.UwbManager.AdapterStateCallback; import com.google.common.collect.ImmutableList; import java.util.List; import org.junit.Before; @@ -29,14 +32,46 @@ import org.robolectric.shadow.api.Shadow; @Config(minSdk = S) public class ShadowUwbManagerTest { private /* RangingSession.Callback */ Object callbackObject; + private /* AdapterStateCallback */ Object adapterStateCallbackObject; private /* UwbManager */ Object uwbManagerObject; private ShadowRangingSession.Adapter adapter; @Before public void setUp() { callbackObject = mock(RangingSession.Callback.class); + adapterStateCallbackObject = mock(AdapterStateCallback.class); adapter = mock(ShadowRangingSession.Adapter.class); uwbManagerObject = getApplicationContext().getSystemService(UwbManager.class); + ((UwbManager) uwbManagerObject) + .unregisterAdapterStateCallback((AdapterStateCallback) adapterStateCallbackObject); + } + + @Test + public void registerAdapterStateCallback_invokesCallbackOnceInitially() { + UwbManager manager = (UwbManager) uwbManagerObject; + AdapterStateCallback adapterStateCallback = (AdapterStateCallback) adapterStateCallbackObject; + + Shadow.<ShadowUwbManager>extract(manager) + .registerAdapterStateCallback(directExecutor(), adapterStateCallback); + + verify(adapterStateCallback).onStateChanged(anyInt(), anyInt()); + } + + @Test + public void simulateAdapterStateChange_invokesCallbackWithGivenStateAndReason() { + UwbManager manager = (UwbManager) uwbManagerObject; + AdapterStateCallback adapterStateCallback = (AdapterStateCallback) adapterStateCallbackObject; + manager.registerAdapterStateCallback(directExecutor(), adapterStateCallback); + + Shadow.<ShadowUwbManager>extract(manager) + .simulateAdapterStateChange( + AdapterStateCallback.STATE_CHANGED_REASON_SYSTEM_REGULATION, + AdapterStateCallback.STATE_DISABLED); + + verify(adapterStateCallback) + .onStateChanged( + AdapterStateCallback.STATE_CHANGED_REASON_SYSTEM_REGULATION, + AdapterStateCallback.STATE_DISABLED); } @Test @@ -73,6 +108,72 @@ public class ShadowUwbManagerTest { @Config(minSdk = TIRAMISU) @Test + public void setUwbEnabled_setToTrue_enablesUwbAndInvokesCallback() { + UwbManager manager = (UwbManager) uwbManagerObject; + AdapterStateCallback adapterStateCallback = (AdapterStateCallback) adapterStateCallbackObject; + manager.registerAdapterStateCallback(directExecutor(), adapterStateCallback); + + Shadow.<ShadowUwbManager>extract(manager).setUwbEnabled(false); + Shadow.<ShadowUwbManager>extract(manager).setUwbEnabled(true); + + assertThat(manager.isUwbEnabled()).isTrue(); + // Invoked once when the callback is initially registered + verify(adapterStateCallback, times(2)) + .onStateChanged( + AdapterStateCallback.STATE_ENABLED_INACTIVE, + AdapterStateCallback.STATE_CHANGED_REASON_SYSTEM_POLICY); + } + + @Config(minSdk = TIRAMISU) + @Test + public void setUwbEnabled_setToFalse_disablesUwbAndInvokesCallback() { + UwbManager manager = (UwbManager) uwbManagerObject; + AdapterStateCallback adapterStateCallback = (AdapterStateCallback) adapterStateCallbackObject; + manager.registerAdapterStateCallback(directExecutor(), adapterStateCallback); + + Shadow.<ShadowUwbManager>extract(manager).setUwbEnabled(false); + + assertThat(manager.isUwbEnabled()).isFalse(); + verify(adapterStateCallback) + .onStateChanged( + AdapterStateCallback.STATE_DISABLED, + AdapterStateCallback.STATE_CHANGED_REASON_SYSTEM_POLICY); + } + + @Config(minSdk = TIRAMISU) + @Test + public void setUwbEnabled_enabledStateNotChanged_doesNothing() { + UwbManager manager = (UwbManager) uwbManagerObject; + AdapterStateCallback adapterStateCallback = (AdapterStateCallback) adapterStateCallbackObject; + manager.registerAdapterStateCallback(directExecutor(), adapterStateCallback); + + Shadow.<ShadowUwbManager>extract(manager).setUwbEnabled(true); + + assertThat(manager.isUwbEnabled()).isTrue(); + // Invoked once when the callback is initially registered + verify(adapterStateCallback).onStateChanged(anyInt(), anyInt()); + } + + @Config(minSdk = TIRAMISU) + @Test + public void setUwbEnabled_disabledStateNotChanged_doesNothing() { + UwbManager manager = (UwbManager) uwbManagerObject; + AdapterStateCallback adapterStateCallback = (AdapterStateCallback) adapterStateCallbackObject; + manager.registerAdapterStateCallback(directExecutor(), adapterStateCallback); + + Shadow.<ShadowUwbManager>extract(manager).setUwbEnabled(false); + Shadow.<ShadowUwbManager>extract(manager).setUwbEnabled(false); + + assertThat(manager.isUwbEnabled()).isFalse(); + // Invoked only once when UWB is initially disabled + verify(adapterStateCallback) + .onStateChanged( + AdapterStateCallback.STATE_DISABLED, + AdapterStateCallback.STATE_CHANGED_REASON_SYSTEM_POLICY); + } + + @Config(minSdk = TIRAMISU) + @Test public void getChipInfos_expectedValue() { UwbManager manager = (UwbManager) uwbManagerObject; Shadow.<ShadowUwbManager>extract(manager).setChipInfos(ImmutableList.of(genParams("chipInfo"))); diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowValueAnimatorTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowValueAnimatorTest.java index b6f7fe616..64d3f4c9c 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowValueAnimatorTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowValueAnimatorTest.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.JELLY_BEAN; import static com.google.common.truth.Truth.assertThat; import static org.robolectric.shadows.ShadowLooper.shadowMainLooper; @@ -13,10 +12,8 @@ import java.util.List; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.Shadows; -import org.robolectric.annotation.Config; @RunWith(AndroidJUnit4.class) -@Config(minSdk = JELLY_BEAN) public class ShadowValueAnimatorTest { @Test diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowViewTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowViewTest.java index 432eec66c..336342ed9 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowViewTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowViewTest.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2; import static android.os.Build.VERSION_CODES.LOLLIPOP; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; @@ -426,8 +425,14 @@ public class ShadowViewTest { @Test public void scrollTo_shouldStoreTheScrolledCoordinates() throws Exception { - view.scrollTo(1, 2); - assertThat(shadowOf(view).scrollToCoordinates).isEqualTo(new Point(1, 2)); + // This test depends on broken scrolling behavior. + System.setProperty("robolectric.useRealScrolling", "false"); + try { + view.scrollTo(1, 2); + assertThat(shadowOf(view).scrollToCoordinates).isEqualTo(new Point(1, 2)); + } finally { + System.clearProperty("robolectric.useRealScrolling"); + } } @Test @@ -440,12 +445,18 @@ public class ShadowViewTest { @Test public void scrollBy_shouldStoreTheScrolledCoordinates() throws Exception { - view.scrollTo(4, 5); - view.scrollBy(10, 20); - assertThat(shadowOf(view).scrollToCoordinates).isEqualTo(new Point(14, 25)); - - assertThat(view.getScrollX()).isEqualTo(14); - assertThat(view.getScrollY()).isEqualTo(25); + // This test depends on broken scrolling behavior. + System.setProperty("robolectric.useRealScrolling", "false"); + try { + view.scrollTo(4, 5); + view.scrollBy(10, 20); + assertThat(shadowOf(view).scrollToCoordinates).isEqualTo(new Point(14, 25)); + + assertThat(view.getScrollX()).isEqualTo(14); + assertThat(view.getScrollY()).isEqualTo(25); + } finally { + System.clearProperty("robolectric.useRealScrolling"); + } } @Test @@ -866,7 +877,7 @@ public class ShadowViewTest { assertFalse(shadowOf(temporaryChild).isAttachedToWindow()); } - @Test @Config(minSdk = JELLY_BEAN_MR2) + @Test public void getWindowId_shouldReturnValidObjectWhenAttached() throws Exception { MyView parent = new MyView("parent", transcript); MyView child = new MyView("child", transcript); diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowVirtualDeviceManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowVirtualDeviceManagerTest.java index f11cf763e..dcc5c4a1c 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowVirtualDeviceManagerTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowVirtualDeviceManagerTest.java @@ -1,6 +1,7 @@ package org.robolectric.shadows; import static android.hardware.Sensor.TYPE_ACCELEROMETER; +import static android.hardware.input.VirtualKeyEvent.ACTION_DOWN; import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE; import static androidx.test.core.app.ApplicationProvider.getApplicationContext; import static com.google.common.truth.Truth.assertThat; @@ -17,6 +18,19 @@ import android.companion.virtual.sensor.VirtualSensorCallback; import android.companion.virtual.sensor.VirtualSensorConfig; import android.content.Context; import android.content.Intent; +import android.hardware.input.VirtualKeyEvent; +import android.hardware.input.VirtualKeyboard; +import android.hardware.input.VirtualKeyboardConfig; +import android.hardware.input.VirtualMouse; +import android.hardware.input.VirtualMouseButtonEvent; +import android.hardware.input.VirtualMouseConfig; +import android.hardware.input.VirtualMouseRelativeEvent; +import android.hardware.input.VirtualMouseScrollEvent; +import android.hardware.input.VirtualTouchEvent; +import android.hardware.input.VirtualTouchscreen; +import android.hardware.input.VirtualTouchscreenConfig; +import android.view.KeyEvent; +import android.view.MotionEvent; import java.time.Duration; import java.util.function.IntConsumer; import org.junit.Before; @@ -43,6 +57,9 @@ public class ShadowVirtualDeviceManagerTest { private VirtualDeviceManager virtualDeviceManager; @Mock private IntConsumer mockCallback; + private static final int DISPLAY_WIDTH = 720; + private static final int DISPLAY_HEIGHT = 1280; + @Before public void setUp() throws Exception { virtualDeviceManager = @@ -167,4 +184,111 @@ public class ShadowVirtualDeviceManagerTest { assertThat(retrievedCallback).isNotNull(); verify(mockVirtualSensorCallback).onConfigurationChanged(any(), eq(true), any(), any()); } + + @Test + public void testCreateVirtualMouse() { + VirtualDevice virtualDevice = + virtualDeviceManager.createVirtualDevice( + 0, new VirtualDeviceParams.Builder().setName("foo").build()); + VirtualMouseButtonEvent buttonDownEvent = + new VirtualMouseButtonEvent.Builder() + .setButtonCode(VirtualMouseButtonEvent.BUTTON_PRIMARY) + .setAction(VirtualMouseButtonEvent.ACTION_BUTTON_PRESS) + .build(); + VirtualMouseButtonEvent buttonUpEvent = + new VirtualMouseButtonEvent.Builder() + .setButtonCode(VirtualMouseButtonEvent.BUTTON_PRIMARY) + .setAction(VirtualMouseButtonEvent.ACTION_BUTTON_RELEASE) + .build(); + VirtualMouseScrollEvent scrollEvent = + new VirtualMouseScrollEvent.Builder().setXAxisMovement(0.5f).setYAxisMovement(0.5f).build(); + VirtualMouseRelativeEvent relativeEvent = + new VirtualMouseRelativeEvent.Builder().setRelativeX(0.1f).setRelativeY(0.1f).build(); + + VirtualMouse virtualMouse = + virtualDevice.createVirtualMouse(new VirtualMouseConfig.Builder().build()); + virtualMouse.sendButtonEvent(buttonDownEvent); + virtualMouse.sendButtonEvent(buttonUpEvent); + virtualMouse.sendScrollEvent(scrollEvent); + virtualMouse.sendRelativeEvent(relativeEvent); + + assertThat(virtualMouse).isNotNull(); + ShadowVirtualMouse shadowVirtualMouse = Shadow.extract(virtualMouse); + assertThat(shadowVirtualMouse.getSentButtonEvents()) + .containsExactly(buttonDownEvent, buttonUpEvent); + assertThat(shadowVirtualMouse.getSentScrollEvents()).containsExactly(scrollEvent); + assertThat(shadowVirtualMouse.getSentRelativeEvents()).containsExactly(relativeEvent); + } + + @Test + public void testCreateVirtualTouchscreen() { + VirtualDevice virtualDevice = + virtualDeviceManager.createVirtualDevice( + 0, new VirtualDeviceParams.Builder().setName("foo").build()); + VirtualTouchEvent virtualTouchEvent = + new VirtualTouchEvent.Builder() + .setToolType(MotionEvent.TOOL_TYPE_FINGER) + .setAction(MotionEvent.ACTION_DOWN) + .setPointerId(1) + .setX(1.0f) + .setY(1.0f) + .build(); + + VirtualTouchscreen virtualTouchscreen = + virtualDevice.createVirtualTouchscreen( + new VirtualTouchscreenConfig.Builder(DISPLAY_WIDTH, DISPLAY_HEIGHT).build()); + virtualTouchscreen.sendTouchEvent(virtualTouchEvent); + + assertThat(virtualTouchscreen).isNotNull(); + ShadowVirtualTouchscreen shadowVirtualTouchscreen = Shadow.extract(virtualTouchscreen); + assertThat(shadowVirtualTouchscreen.getSentEvents()).containsExactly(virtualTouchEvent); + } + + @Test + public void testCreateVirtualKeyboard() { + VirtualDevice virtualDevice = + virtualDeviceManager.createVirtualDevice( + 0, new VirtualDeviceParams.Builder().setName("foo").build()); + VirtualKeyEvent keyEvent1 = + new VirtualKeyEvent.Builder().setAction(ACTION_DOWN).setKeyCode(KeyEvent.KEYCODE_A).build(); + VirtualKeyEvent keyEvent2 = + new VirtualKeyEvent.Builder() + .setAction(ACTION_DOWN) + .setKeyCode(KeyEvent.KEYCODE_ENTER) + .build(); + + VirtualKeyboard virtualKeyboard = + virtualDevice.createVirtualKeyboard(new VirtualKeyboardConfig.Builder().build()); + virtualKeyboard.sendKeyEvent(keyEvent1); + virtualKeyboard.sendKeyEvent(keyEvent2); + + assertThat(virtualKeyboard).isNotNull(); + ShadowVirtualKeyboard shadowVirtualKeyboard = Shadow.extract(virtualKeyboard); + assertThat(shadowVirtualKeyboard.getSentEvents()).containsExactly(keyEvent1, keyEvent2); + } + + @Test + public void testCloseVirtualInputDevices() { + VirtualDevice virtualDevice = + virtualDeviceManager.createVirtualDevice( + 0, new VirtualDeviceParams.Builder().setName("foo").build()); + VirtualKeyboard virtualKeyboard = + virtualDevice.createVirtualKeyboard(new VirtualKeyboardConfig.Builder().build()); + VirtualTouchscreen virtualTouchscreen = + virtualDevice.createVirtualTouchscreen( + new VirtualTouchscreenConfig.Builder(DISPLAY_WIDTH, DISPLAY_HEIGHT).build()); + VirtualMouse virtualMouse = + virtualDevice.createVirtualMouse(new VirtualMouseConfig.Builder().build()); + + virtualKeyboard.close(); + virtualTouchscreen.close(); + virtualMouse.close(); + + ShadowVirtualKeyboard shadowVirtualKeyboard = Shadow.extract(virtualKeyboard); + ShadowVirtualTouchscreen shadowVirtualTouchscreen = Shadow.extract(virtualTouchscreen); + ShadowVirtualMouse shadowVirtualMouse = Shadow.extract(virtualMouse); + assertThat(shadowVirtualKeyboard.isClosed()).isTrue(); + assertThat(shadowVirtualTouchscreen.isClosed()).isTrue(); + assertThat(shadowVirtualMouse.isClosed()).isTrue(); + } } diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowVisualizerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowVisualizerTest.java index f62941f42..d4393b33f 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowVisualizerTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowVisualizerTest.java @@ -1,7 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.GINGERBREAD; -import static android.os.Build.VERSION_CODES.KITKAT; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertThrows; import static org.robolectric.Shadows.shadowOf; @@ -16,12 +14,10 @@ import java.util.concurrent.atomic.AtomicBoolean; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.robolectric.annotation.Config; import org.robolectric.shadows.ShadowVisualizer.VisualizerSource; /** Tests for {@link ShadowVisualizer}. */ @RunWith(AndroidJUnit4.class) -@Config(minSdk = GINGERBREAD) public class ShadowVisualizerTest { private Visualizer visualizer; @@ -98,7 +94,6 @@ public class ShadowVisualizerTest { assertThat(visualizer.getCaptureSize()).isEqualTo(2000); } - @Config(minSdk = KITKAT) @Test public void getMeasurementPeakRms_returnsRmsFromSource() { int peak = -500; diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowWallpaperManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowWallpaperManagerTest.java index 675612cc0..7bafc91b3 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowWallpaperManagerTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowWallpaperManagerTest.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; import static android.os.Build.VERSION_CODES.M; import static android.os.Build.VERSION_CODES.N; import static android.os.Build.VERSION_CODES.TIRAMISU; @@ -96,14 +95,12 @@ public class ShadowWallpaperManagerTest { } @Test - @Config(minSdk = JELLY_BEAN_MR1) public void hasResourceWallpaper_wallpaperResourceNotSet_returnsFalse() { assertThat(manager.hasResourceWallpaper(1)).isFalse(); assertThat(manager.hasResourceWallpaper(5)).isFalse(); } @Test - @Config(minSdk = JELLY_BEAN_MR1) public void hasResourceWallpaper_wallpaperResourceSet_returnsTrue() throws IOException { int resid = 5; manager.setResource(resid); @@ -113,7 +110,6 @@ public class ShadowWallpaperManagerTest { } @Test - @Config(minSdk = JELLY_BEAN_MR1) public void setResource_multipleTimes_hasResourceWallpaperReturnsTrueForLastValue() throws IOException { manager.setResource(1); @@ -557,6 +553,16 @@ public class ShadowWallpaperManagerTest { .isEqualTo(testImageBytes); } + @Test + @Config(minSdk = TIRAMISU) + public void getAllWallpaperDimAmounts_returnsFullListOfAllDimAmountsSet() { + assertThat(shadowOf(manager).getAllWallpaperDimAmounts()).isEmpty(); + manager.setWallpaperDimAmount(0.5f); + assertThat(shadowOf(manager).getAllWallpaperDimAmounts()).containsExactly(0.5f); + manager.setWallpaperDimAmount(0f); + assertThat(shadowOf(manager).getAllWallpaperDimAmounts()).containsExactly(0.5f, 0f); + } + private static byte[] getBytesFromFileDescriptor(FileDescriptor fileDescriptor) throws IOException { InputStream inputStream = new FileInputStream(fileDescriptor); diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowWebSettingsTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowWebSettingsTest.java index 33280417c..aeda81d2b 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowWebSettingsTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowWebSettingsTest.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2; import static com.google.common.truth.Truth.assertThat; import android.content.Context; @@ -10,7 +9,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.robolectric.annotation.Config; /** Tests for {@link ShadowWebSettings} */ @RunWith(AndroidJUnit4.class) @@ -24,7 +22,6 @@ public final class ShadowWebSettingsTest { } @Test - @Config(minSdk = JELLY_BEAN_MR2) public void setDefaultUserAgent() { ShadowWebSettings.setDefaultUserAgent("Chrome/71.0.143.1"); diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowWifiConfigurationTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowWifiConfigurationTest.java index 0b30d7e03..1576c8564 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowWifiConfigurationTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowWifiConfigurationTest.java @@ -63,7 +63,6 @@ public class ShadowWifiConfigurationTest { assertThat(copy.wepKeys[3]).isEqualTo("3"); } - @Config(minSdk = Build.VERSION_CODES.JELLY_BEAN_MR2) @Test public void shouldCopy_sdk18() { WifiConfiguration wifiConfiguration = new WifiConfiguration(); diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowWifiManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowWifiManagerTest.java index 6563db768..bfcd80e50 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowWifiManagerTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowWifiManagerTest.java @@ -1,7 +1,6 @@ package org.robolectric.shadows; import static android.net.wifi.WifiManager.SCAN_RESULTS_AVAILABLE_ACTION; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2; import static android.os.Build.VERSION_CODES.LOLLIPOP; import static android.os.Build.VERSION_CODES.Q; import static android.os.Build.VERSION_CODES.R; @@ -156,7 +155,6 @@ public class ShadowWifiManagerTest { } @Test - @Config(minSdk = JELLY_BEAN_MR2) public void getIsScanAlwaysAvailable() { shadowOf(wifiManager).setIsScanAlwaysAvailable(true); assertThat(wifiManager.isScanAlwaysAvailable()).isEqualTo(true); @@ -607,7 +605,6 @@ public class ShadowWifiManagerTest { } @Test - @Config(minSdk = Build.VERSION_CODES.KITKAT) public void connect_setsNetworkId_shouldHasNetworkId() { // WHEN wifiManager.connect(123, null); @@ -617,7 +614,6 @@ public class ShadowWifiManagerTest { } @Test - @Config(minSdk = Build.VERSION_CODES.KITKAT) public void connect_setsConnectionInfo() { // GIVEN WifiConfiguration wifiConfiguration = new WifiConfiguration(); diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowWindowManagerGlobalTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowWindowManagerGlobalTest.java index 2ffab72e8..8bc4a049d 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowWindowManagerGlobalTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowWindowManagerGlobalTest.java @@ -29,7 +29,6 @@ public class ShadowWindowManagerGlobalTest { } @Test - @Config(minSdk = JELLY_BEAN_MR1) public void getWindowSession_shouldReturnSession() { assertThat(ShadowWindowManagerGlobal.getWindowSession()).isNotNull(); } @@ -54,7 +53,6 @@ public class ShadowWindowManagerGlobalTest { } @Test - @Config(minSdk = JELLY_BEAN_MR1) public void windowIsVisible() { View decorView = Robolectric.buildActivity(DragActivity.class).setup().get().getWindow().getDecorView(); diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowWindowTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowWindowTest.java index 2a6ab49ad..924cf82a9 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowWindowTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowWindowTest.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.KITKAT; import static android.os.Build.VERSION_CODES.LOLLIPOP_MR1; import static android.os.Build.VERSION_CODES.M; import static android.os.Build.VERSION_CODES.N; @@ -81,7 +80,7 @@ public class ShadowWindowTest { } @Test - @Config(minSdk = KITKAT, maxSdk = VERSION_CODES.R) + @Config(maxSdk = VERSION_CODES.R) public void getSystemFlag_shouldReturnFlagsSetViaAddPrivateFlags() throws Exception { Activity activity = Robolectric.buildActivity(Activity.class).create().get(); Window window = activity.getWindow(); @@ -93,7 +92,7 @@ public class ShadowWindowTest { } @Test - @Config(minSdk = KITKAT, maxSdk = VERSION_CODES.R) + @Config(maxSdk = VERSION_CODES.R) public void getSystemFlag_callingAddPrivateFlagsShouldNotOverrideExistingFlags() throws Exception { Activity activity = Robolectric.buildActivity(Activity.class).create().get(); diff --git a/robolectric/src/test/resources/AndroidManifest.xml b/robolectric/src/test/resources/AndroidManifest.xml index f5e755d73..b677954f2 100644 --- a/robolectric/src/test/resources/AndroidManifest.xml +++ b/robolectric/src/test/resources/AndroidManifest.xml @@ -39,7 +39,6 @@ android:allowBackup="true" android:allowClearUserData="true" android:allowTaskReparenting="true" - android:debuggable="true" android:hasCode="true" android:killAfterRestore="true" android:persistent="true" @@ -91,7 +90,7 @@ <activity android:name=".android.controller.ActivityControllerTest$ConfigAwareActivity" android:configChanges="fontScale|smallestScreenSize" /> - <activity android:name="org.robolectric.shadows.TestActivity"> + <activity android:name="org.robolectric.shadows.TestActivity" android:exported="true"> <intent-filter> <action android:name="android.intent.action.VIEW"/> <category android:name="android.intent.category.DEFAULT"/> @@ -198,32 +197,32 @@ </intent-filter> </receiver> - <receiver android:name="org.robolectric.fakes.ConfigTestReceiver"> + <receiver android:name="org.robolectric.fakes.ConfigTestReceiver" android:exported="true"> <intent-filter> <action android:name="org.robolectric.ACTION_SUPERSET_PACKAGE"/> </intent-filter> </receiver> - <receiver android:name="org.robolectric.ConfigTestReceiver"> + <receiver android:name="org.robolectric.ConfigTestReceiver" android:exported="true"> <intent-filter> <action android:name="org.robolectric.ACTION_SUBSET_PACKAGE"/> </intent-filter> </receiver> - <receiver android:name=".DotConfigTestReceiver"> + <receiver android:name=".DotConfigTestReceiver" android:exported="true"> <intent-filter> <action android:name="org.robolectric.ACTION_DOT_PACKAGE"/> </intent-filter> </receiver> - <receiver android:name=".test.ConfigTestReceiver"> + <receiver android:name=".test.ConfigTestReceiver" android:exported="true"> <intent-filter> <action android:name="org.robolectric.ACTION_DOT_SUBPACKAGE"/> </intent-filter> <meta-data android:name="numberOfSheep" android:value="42" /> </receiver> - <receiver android:name="com.foo.Receiver"> + <receiver android:name="com.foo.Receiver" android:exported="true"> <intent-filter> <action android:name="org.robolectric.ACTION_DIFFERENT_PACKAGE"/> </intent-filter> diff --git a/robolectric/src/test/resources/TestAndroidManifestWithAppComponentFactory.xml b/robolectric/src/test/resources/TestAndroidManifestWithAppComponentFactory.xml index 8741436a8..2bc4c0d74 100644 --- a/robolectric/src/test/resources/TestAndroidManifestWithAppComponentFactory.xml +++ b/robolectric/src/test/resources/TestAndroidManifestWithAppComponentFactory.xml @@ -12,5 +12,19 @@ </receiver> <receiver android:name=".CustomConstructorReceiverWrapper$CustomConstructorWithEmptyActionReceiver" /> + + <service + android:name=".CustomConstructorServices$CustomConstructorService" /> + <service + android:name=".CustomConstructorServices$CustomConstructorIntentService" /> + <service + android:name=".CustomConstructorServices$CustomConstructorJobService" + android:permission="android.permission.BIND_JOB_SERVICE"/> + <provider + android:name=".CustomConstructorContentProvider" + android:authorities="org.robolectric.authority" /> + <activity + android:name=".CustomConstructorContentProvider" + android:authorities="org.robolectric.authority" /> </application> </manifest> diff --git a/robolectric/src/test/resources/TestAndroidManifestWithFlags.xml b/robolectric/src/test/resources/TestAndroidManifestWithFlags.xml index 540d337a3..95aae7374 100644 --- a/robolectric/src/test/resources/TestAndroidManifestWithFlags.xml +++ b/robolectric/src/test/resources/TestAndroidManifestWithFlags.xml @@ -5,7 +5,6 @@ android:allowBackup="true" android:allowClearUserData="true" android:allowTaskReparenting="true" - android:debuggable="true" android:hasCode="true" android:killAfterRestore="true" android:persistent="true" diff --git a/robolectric/src/test/resources/TestAndroidManifestWithReceivers.xml b/robolectric/src/test/resources/TestAndroidManifestWithReceivers.xml index d5b5103c6..be7ff8f76 100644 --- a/robolectric/src/test/resources/TestAndroidManifestWithReceivers.xml +++ b/robolectric/src/test/resources/TestAndroidManifestWithReceivers.xml @@ -21,7 +21,7 @@ </intent-filter> </receiver> - <receiver android:name="org.robolectric.ConfigTestReceiver"> + <receiver android:name="org.robolectric.ConfigTestReceiver" android:exported="true"> <intent-filter> <action android:name="org.robolectric.ACTION_SUBSET_PACKAGE"/> </intent-filter> diff --git a/sandbox/src/main/java/org/robolectric/internal/bytecode/ClassHandler.java b/sandbox/src/main/java/org/robolectric/internal/bytecode/ClassHandler.java index 73fc9e876..5fae33362 100644 --- a/sandbox/src/main/java/org/robolectric/internal/bytecode/ClassHandler.java +++ b/sandbox/src/main/java/org/robolectric/internal/bytecode/ClassHandler.java @@ -69,7 +69,7 @@ public interface ClassHandler { * @see ShadowInvalidator for invalidating the returned {@link MethodHandle} */ MethodHandle findShadowMethodHandle( - Class<?> theClass, String name, MethodType methodType, boolean isStatic) + Class<?> theClass, String name, MethodType methodType, boolean isStatic, boolean isNative) throws IllegalAccessException; /** diff --git a/sandbox/src/main/java/org/robolectric/internal/bytecode/ClassInstrumentor.java b/sandbox/src/main/java/org/robolectric/internal/bytecode/ClassInstrumentor.java index 5c77b9964..a9b532a26 100644 --- a/sandbox/src/main/java/org/robolectric/internal/bytecode/ClassInstrumentor.java +++ b/sandbox/src/main/java/org/robolectric/internal/bytecode/ClassInstrumentor.java @@ -38,7 +38,6 @@ import org.objectweb.asm.tree.MethodInsnNode; import org.objectweb.asm.tree.MethodNode; import org.objectweb.asm.tree.TypeInsnNode; import org.objectweb.asm.tree.VarInsnNode; -import org.robolectric.sandbox.NativeMethodNotFoundException; import org.robolectric.util.PerfStatsCollector; /** @@ -54,15 +53,23 @@ public class ClassInstrumentor { protected static final Type OBJECT_TYPE = Type.getType(Object.class); private static final ShadowImpl SHADOW_IMPL = new ShadowImpl(); final Decorator decorator; - private NativeCallHandler nativeCallHandler; static { String className = Type.getInternalName(InvokeDynamicSupport.class); MethodType bootstrap = methodType(CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class); + + /* + * There is an additional int.class argument to the invokedynamic bootstrap method. This conveys + * whether or not the method invocation represents a native method. A one means the original + * method was a native method, and a zero means it was not. It should be boolean.class, but + * that is nt possible due to https://bugs.java.com/bugdatabase/view_bug?bug_id=JDK-8322510. + */ String bootstrapMethod = - bootstrap.appendParameterTypes(MethodHandle.class).toMethodDescriptorString(); + bootstrap + .appendParameterTypes(MethodHandle.class, /* isNative */ int.class) + .toMethodDescriptorString(); String bootstrapIntrinsic = bootstrap.appendParameterTypes(String.class).toMethodDescriptorString(); @@ -397,7 +404,7 @@ public class ClassInstrumentor { generator.loadThis(); generator.invokeVirtual(mutableClass.classType, new Method(ROBO_INIT_METHOD_NAME, "()V")); generateClassHandlerCall( - mutableClass, method, ShadowConstants.CONSTRUCTOR_METHOD_NAME, generator); + mutableClass, method, ShadowConstants.CONSTRUCTOR_METHOD_NAME, generator, false); generator.endMethod(); @@ -526,7 +533,7 @@ public class ClassInstrumentor { makeMethodPrivate(method); RobolectricGeneratorAdapter generator = new RobolectricGeneratorAdapter(delegatorMethodNode); - generateClassHandlerCall(mutableClass, method, originalName, generator); + generateClassHandlerCall(mutableClass, method, originalName, generator, isNativeMethod); generator.endMethod(); mutableClass.addMethod(delegatorMethodNode); } @@ -537,21 +544,26 @@ public class ClassInstrumentor { * @param method Method to be instrumented, must be native */ protected void instrumentNativeMethod(MutableClass mutableClass, MethodNode method) { + + String nativeBindingMethodName = + SHADOW_IMPL.directNativeMethodName(mutableClass.getName(), method.name); + + // Generate native binding method + MethodNode nativeBindingMethod = + new MethodNode( + Opcodes.ASM4, + nativeBindingMethodName, + method.desc, + method.signature, + exceptionArray(method)); + nativeBindingMethod.access = method.access | Opcodes.ACC_SYNTHETIC; + makeMethodPrivate(nativeBindingMethod); + mutableClass.addMethod(nativeBindingMethod); + method.access = method.access & ~Opcodes.ACC_NATIVE; RobolectricGeneratorAdapter generator = new RobolectricGeneratorAdapter(method); - if (nativeCallHandler != null) { - String descriptor = - String.format("%s#%s%s", mutableClass.getName(), method.name, method.desc); - nativeCallHandler.logNativeCall(descriptor); - if (nativeCallHandler.shouldThrow(descriptor)) { - String message = - nativeCallHandler.getExceptionMessage(descriptor, mutableClass.getName(), method.name); - generator.throwException(Type.getType(NativeMethodNotFoundException.class), message); - } - } - Type returnType = generator.getReturnType(); generator.pushDefaultReturnValueToStack(returnType); generator.returnValue(); @@ -719,7 +731,8 @@ public class ClassInstrumentor { MutableClass mutableClass, MethodNode originalMethod, String originalMethodName, - RobolectricGeneratorAdapter generator) { + RobolectricGeneratorAdapter generator, + boolean isNativeMethod) { Handle original = new Handle( getTag(originalMethod), @@ -730,12 +743,13 @@ public class ClassInstrumentor { if (generator.isStatic()) { generator.loadArgs(); - generator.invokeDynamic(originalMethodName, originalMethod.desc, BOOTSTRAP_STATIC, original); + generator.invokeDynamic( + originalMethodName, originalMethod.desc, BOOTSTRAP_STATIC, original, isNativeMethod); } else { String desc = "(" + mutableClass.classType.getDescriptor() + originalMethod.desc.substring(1); generator.loadThis(); generator.loadArgs(); - generator.invokeDynamic(originalMethodName, desc, BOOTSTRAP, original); + generator.invokeDynamic(originalMethodName, desc, BOOTSTRAP, original, isNativeMethod); } generator.returnValue(); @@ -753,10 +767,6 @@ public class ClassInstrumentor { return -1; } - public void setNativeCallHandler(NativeCallHandler nativeCallHandler) { - this.nativeCallHandler = nativeCallHandler; - } - public interface Decorator { void decorate(MutableClass mutableClass); } diff --git a/sandbox/src/main/java/org/robolectric/internal/bytecode/InvokeDynamicSupport.java b/sandbox/src/main/java/org/robolectric/internal/bytecode/InvokeDynamicSupport.java index d7b8b7937..fa53caec0 100644 --- a/sandbox/src/main/java/org/robolectric/internal/bytecode/InvokeDynamicSupport.java +++ b/sandbox/src/main/java/org/robolectric/internal/bytecode/InvokeDynamicSupport.java @@ -31,6 +31,15 @@ public class InvokeDynamicSupport { private static final MethodHandle EXCEPTION_HANDLER; private static final MethodHandle GET_SHADOW; + /** + * Represents the boolean 'true' as an integer. Due to a JVM bug, invokedynamic bootstrap methods + * currently do not support extra primitive boolean parameters. Integers are required to convey + * booleans. + * + * <p>See https://bugs.java.com/bugdatabase/view_bug?bug_id=JDK-8322510 + */ + private static final int BOOLEAN_TRUE = 1; + static { try { MethodHandles.Lookup lookup = MethodHandles.lookup(); @@ -75,14 +84,24 @@ public class InvokeDynamicSupport { @SuppressWarnings("UnusedDeclaration") public static CallSite bootstrap( - MethodHandles.Lookup caller, String name, MethodType type, MethodHandle original) + MethodHandles.Lookup caller, + String name, + MethodType type, + MethodHandle original, + int isNative /* 1 == originally native, 0 == not originally native */) throws IllegalAccessException { return PerfStatsCollector.getInstance() .measure( "invokedynamic bootstrap", () -> { MethodCallSite site = - new MethodCallSite(caller.lookupClass(), type, name, original, REGULAR); + new MethodCallSite( + caller.lookupClass(), + type, + name, + original, + REGULAR, + isNative == BOOLEAN_TRUE); bindCallSite(site); @@ -92,14 +111,19 @@ public class InvokeDynamicSupport { @SuppressWarnings("UnusedDeclaration") public static CallSite bootstrapStatic( - MethodHandles.Lookup caller, String name, MethodType type, MethodHandle original) + MethodHandles.Lookup caller, + String name, + MethodType type, + MethodHandle original, + int isNative /* 1 == originally native, 0 == not originally native */) throws IllegalAccessException { return PerfStatsCollector.getInstance() .measure( "invokedynamic bootstrap static", () -> { MethodCallSite site = - new MethodCallSite(caller.lookupClass(), type, name, original, STATIC); + new MethodCallSite( + caller.lookupClass(), type, name, original, STATIC, isNative == BOOLEAN_TRUE); bindCallSite(site); @@ -159,7 +183,7 @@ public class InvokeDynamicSupport { private static MethodHandle bindCallSite(MethodCallSite site) throws IllegalAccessException { MethodHandle mh = RobolectricInternals.findShadowMethodHandle( - site.getTheClass(), site.getName(), site.type(), site.isStatic()); + site.getTheClass(), site.getName(), site.type(), site.isStatic(), site.isNative()); if (mh == null) { // call original code @@ -169,8 +193,13 @@ public class InvokeDynamicSupport { mh = dropArguments(mh, 0, site.type().parameterList()); } else if (!site.isStatic()) { // drop arg 0 (this) for static methods - Class<?> shadowType = mh.type().parameterType(0); - mh = filterArguments(mh, 0, GET_SHADOW.asType(methodType(shadowType, site.thisType()))); + Class<?> mhType = mh.type().parameterType(0); + // At this point, thisType is either a shadow type, or in the case of native method + // invocations, it can be equivalent to the original type. + if (!mhType.equals(site.getTheClass())) { + // Only invoke getShadow if the method is on class that is decoupled from the original. + mh = filterArguments(mh, 0, GET_SHADOW.asType(methodType(mhType, site.thisType()))); + } } try { diff --git a/sandbox/src/main/java/org/robolectric/internal/bytecode/MethodCallSite.java b/sandbox/src/main/java/org/robolectric/internal/bytecode/MethodCallSite.java index bcb2ae089..df6f3f847 100644 --- a/sandbox/src/main/java/org/robolectric/internal/bytecode/MethodCallSite.java +++ b/sandbox/src/main/java/org/robolectric/internal/bytecode/MethodCallSite.java @@ -10,12 +10,20 @@ public class MethodCallSite extends RoboCallSite { private final MethodHandle original; private final Kind kind; - public MethodCallSite(Class<?> theClass, MethodType type, String name, MethodHandle original, - Kind kind) { + private final boolean isNative; + + public MethodCallSite( + Class<?> theClass, + MethodType type, + String name, + MethodHandle original, + Kind kind, + boolean isNative) { super(type, theClass); this.name = name; this.original = original; this.kind = kind; + this.isNative = isNative; } public String getName() { @@ -34,6 +42,10 @@ public class MethodCallSite extends RoboCallSite { return kind == STATIC; } + public boolean isNative() { + return isNative; + } + @Override public String toString() { return "RoboCallSite{" + "theClass=" + getTheClass() + diff --git a/sandbox/src/main/java/org/robolectric/internal/bytecode/NativeCallHandler.java b/sandbox/src/main/java/org/robolectric/internal/bytecode/NativeCallHandler.java deleted file mode 100644 index 89034d63f..000000000 --- a/sandbox/src/main/java/org/robolectric/internal/bytecode/NativeCallHandler.java +++ /dev/null @@ -1,137 +0,0 @@ -package org.robolectric.internal.bytecode; - -import static java.nio.charset.StandardCharsets.UTF_8; - -import java.io.BufferedReader; -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileReader; -import java.io.FileWriter; -import java.io.IOException; -import java.util.Set; -import java.util.TreeSet; -import javax.annotation.Nonnull; - -/** - * Handler for native calls instrumented by ClassInstrumentor. - * - * <p>Native Calls can either be instrumented as no-op calls (returning a default value or 0 or - * null) or throw an exception. This helper class helps maintain a list of exemptions to indicates - * which native calls should be no-op and never throw. - */ -public class NativeCallHandler { - - private final File exemptionsFile; - private final boolean writeExemptions; - private final boolean throwOnNatives; - private final Set<String> descriptors = new TreeSet<>(); - - /** - * Initializes the native calls handler. - * - * @param exemptionsFile The exemptions file to read from and/or to generate. - * @param writeExemptions When true, native calls are added to the exemption list. - * @param throwOnNatives Whether native calls should throw by default unless their signature is - * listed in the exemption list. When false, all native calls become no-op. - * @throws IOException if there's an issue reading an existing exemption list. - */ - public NativeCallHandler( - @Nonnull File exemptionsFile, boolean writeExemptions, boolean throwOnNatives) - throws IOException { - this.exemptionsFile = exemptionsFile; - this.writeExemptions = writeExemptions; - this.throwOnNatives = throwOnNatives; - - if (exemptionsFile.exists()) { - readExemptionsList(exemptionsFile); - } - } - - private String getExemptionFileName() { - return exemptionsFile.getName(); - } - - private void readExemptionsList(File exemptionsFile) throws IOException { - try (BufferedReader reader = - new BufferedReader(new FileReader(exemptionsFile.getPath(), UTF_8))) { - String line; - while ((line = reader.readLine()) != null) { - // Sanitize input. Ignore empty lines and commented lines starting with #. - line = sanitize(line.trim()); - if (line.isEmpty() || line.charAt(0) == '#') { - continue; - } - descriptors.add(line); - } - } - System.out.println( - "Loaded " + descriptors.size() + " exemptions from " + exemptionsFile.getPath()); - } - - public void writeExemptionsList() throws IOException { - try (BufferedWriter writer = - new BufferedWriter(new FileWriter(exemptionsFile.getPath(), UTF_8))) { - for (String descriptor : descriptors) { - writer.write(descriptor); - writer.write('\n'); - } - } - System.out.println( - "Wrote " + descriptors.size() + " exemptions to " + exemptionsFile.getPath()); - } - - /** - * Adds the method description to the native call exemption list if {@link #writeExemptions} is - * set. - */ - public void logNativeCall(@Nonnull String descriptor) { - if (!writeExemptions) { - return; - } - descriptors.add(sanitize(descriptor)); - } - - /** Returns whether the ClassInstrumentor should generate an exception or a no-op bytecode. */ - public boolean shouldThrow(@Nonnull String descriptor) { - return throwOnNatives && !descriptors.contains(sanitize(descriptor)); - } - - private String sanitize(String descriptor) { - // Post-processing of the exemptions files is made complicated by the presence of $ signs - // in the FQCN. Instead of escaping them, just replace them by another unused character - // that is not so sensitive to shell or make mangling. - return descriptor.replace('$', '^'); - } - - /** - * Returns the detailed message to be used by the ClassInstrumentor in the generated bytecode. - * - * @param descriptor The ASM descriptor as it should be written in the exemption file. - * @param className The fully qualified class name, used for the user description. - * @param methodName The method name, used for the user description. - */ - public String getExceptionMessage( - @Nonnull String descriptor, @Nonnull String className, @Nonnull String methodName) { - // The shadow message is merely a hint based on the last component of the FQCN, which is - // typically the pattern used for shadow classes. - String shadowHint = - "Shadow" + className.replaceAll("[^.]+\\.", "").replaceAll("\\$.*", "") + ".java"; - // The message below tries to educate the user that shadow overrides are not necessarily - // needed nor desired for trivial cases that are better covered by a no-op return operation. - return "Unexpected Robolectric native method call to '" - + className - + "#" - + methodName - + "()'.\n" - + "Option 1: If customizing this method is useful, add an implementation in " - + shadowHint - + ".\n" - + "Option 2: If this method just needs to trivially return 0 or null, please add an" - + " exemption entry for\n" - + " " - + sanitize(descriptor) - + "\n" - + "to exemption file " - + getExemptionFileName(); - } -} diff --git a/sandbox/src/main/java/org/robolectric/internal/bytecode/RobolectricInternals.java b/sandbox/src/main/java/org/robolectric/internal/bytecode/RobolectricInternals.java index 673f5c1f8..bfecf8f92 100644 --- a/sandbox/src/main/java/org/robolectric/internal/bytecode/RobolectricInternals.java +++ b/sandbox/src/main/java/org/robolectric/internal/bytecode/RobolectricInternals.java @@ -31,9 +31,9 @@ public class RobolectricInternals { } public static MethodHandle findShadowMethodHandle( - Class<?> theClass, String name, MethodType methodType, boolean isStatic) + Class<?> theClass, String name, MethodType methodType, boolean isStatic, boolean isNative) throws IllegalAccessException { - return classHandler.findShadowMethodHandle(theClass, name, methodType, isStatic); + return classHandler.findShadowMethodHandle(theClass, name, methodType, isStatic, isNative); } @SuppressWarnings("UnusedDeclaration") diff --git a/sandbox/src/main/java/org/robolectric/internal/bytecode/Sandbox.java b/sandbox/src/main/java/org/robolectric/internal/bytecode/Sandbox.java index 263e91bc3..92f897f8b 100644 --- a/sandbox/src/main/java/org/robolectric/internal/bytecode/Sandbox.java +++ b/sandbox/src/main/java/org/robolectric/internal/bytecode/Sandbox.java @@ -1,7 +1,6 @@ package org.robolectric.internal.bytecode; import static java.util.concurrent.TimeUnit.SECONDS; -import static org.robolectric.util.ReflectionHelpers.newInstance; import static org.robolectric.util.ReflectionHelpers.setStaticField; import java.io.IOException; @@ -16,6 +15,7 @@ import java.util.concurrent.Future; import java.util.concurrent.ThreadFactory; import javax.inject.Inject; import org.robolectric.shadow.api.Shadow; +import org.robolectric.util.ReflectionHelpers; import org.robolectric.util.Util; public class Sandbox { @@ -92,7 +92,10 @@ public class Sandbox { setStaticField(invokeDynamicSupportClass, "INTERCEPTORS", interceptors); Class<?> shadowClass = bootstrappedClass(Shadow.class); - setStaticField(shadowClass, "SHADOW_IMPL", newInstance(bootstrappedClass(ShadowImpl.class))); + setStaticField( + shadowClass, + "SHADOW_IMPL", + ReflectionHelpers.newInstance(bootstrappedClass(ShadowImpl.class))); } public void runOnMainThread(Runnable runnable) { diff --git a/sandbox/src/main/java/org/robolectric/internal/bytecode/ShadowDecorator.java b/sandbox/src/main/java/org/robolectric/internal/bytecode/ShadowDecorator.java index a83009d63..6a42c2063 100644 --- a/sandbox/src/main/java/org/robolectric/internal/bytecode/ShadowDecorator.java +++ b/sandbox/src/main/java/org/robolectric/internal/bytecode/ShadowDecorator.java @@ -22,7 +22,7 @@ public class ShadowDecorator implements ClassInstrumentor.Decorator { mutableClass.addField( 0, new FieldNode( - Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNTHETIC, + Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNTHETIC | Opcodes.ACC_TRANSIENT, ShadowConstants.CLASS_HANDLER_DATA_FIELD_NAME, OBJECT_DESC, OBJECT_DESC, diff --git a/sandbox/src/main/java/org/robolectric/internal/bytecode/ShadowImpl.java b/sandbox/src/main/java/org/robolectric/internal/bytecode/ShadowImpl.java index a55bf5f42..5a5bb32b2 100644 --- a/sandbox/src/main/java/org/robolectric/internal/bytecode/ShadowImpl.java +++ b/sandbox/src/main/java/org/robolectric/internal/bytecode/ShadowImpl.java @@ -18,7 +18,8 @@ public class ShadowImpl implements IShadow { return ReflectionHelpers.callConstructor(clazz); } - @Override public <T> T newInstance(Class<T> clazz, Class[] parameterTypes, Object[] params) { + @Override + public <T> T newInstance(Class<T> clazz, Class<?>[] parameterTypes, Object[] params) { return ReflectionHelpers.callConstructor(clazz, ReflectionHelpers.ClassParameter.fromComponentLists(parameterTypes, params)); } @@ -36,38 +37,50 @@ public class ShadowImpl implements IShadow { return createProxy(shadowedObject, clazz); } - private <T> T createProxy(T shadowedObject, Class<T> clazz) { - try { - return proxyMaker.createProxy(clazz, shadowedObject); - } catch (Exception e) { - throw new RuntimeException("error creating direct call proxy for " + clazz, e); - } - } - - @Override @SuppressWarnings(value = {"unchecked", "TypeParameterUnusedInFormals"}) - public <R> R directlyOn(Object shadowedObject, String clazzName, String methodName, ReflectionHelpers.ClassParameter... paramValues) { + @Override + @SuppressWarnings(value = {"unchecked", "TypeParameterUnusedInFormals"}) + public <R> R directlyOn( + Object shadowedObject, + String clazzName, + String methodName, + ReflectionHelpers.ClassParameter<?>... paramValues) { try { - Class<Object> aClass = (Class<Object>) shadowedObject.getClass().getClassLoader().loadClass(clazzName); + Class<Object> aClass = + (Class<Object>) shadowedObject.getClass().getClassLoader().loadClass(clazzName); return directlyOn(shadowedObject, aClass, methodName, paramValues); } catch (ClassNotFoundException e) { throw new RuntimeException(e); } } - @Override @SuppressWarnings(value = {"unchecked", "TypeParameterUnusedInFormals"}) - public <R, T> R directlyOn(T shadowedObject, Class<T> clazz, String methodName, ReflectionHelpers.ClassParameter... paramValues) { + @Override + @SuppressWarnings(value = {"unchecked", "TypeParameterUnusedInFormals"}) + public <R, T> R directlyOn( + T shadowedObject, + Class<T> clazz, + String methodName, + ReflectionHelpers.ClassParameter<?>... paramValues) { String directMethodName = directMethodName(clazz.getName(), methodName); - return (R) ReflectionHelpers.callInstanceMethod(clazz, shadowedObject, directMethodName, paramValues); + return (R) + ReflectionHelpers.callInstanceMethod(clazz, shadowedObject, directMethodName, paramValues); } - @Override @SuppressWarnings(value = {"unchecked", "TypeParameterUnusedInFormals"}) - public <R, T> R directlyOn(Class<T> clazz, String methodName, ReflectionHelpers.ClassParameter... paramValues) { + @Override + @SuppressWarnings(value = {"unchecked", "TypeParameterUnusedInFormals"}) + public <R, T> R directlyOn( + Class<T> clazz, String methodName, ReflectionHelpers.ClassParameter<?>... paramValues) { String directMethodName = directMethodName(clazz.getName(), methodName); return (R) ReflectionHelpers.callStaticMethod(clazz, directMethodName, paramValues); } - @Override @SuppressWarnings(value = {"unchecked", "TypeParameterUnusedInFormals"}) - public <R> R invokeConstructor(Class<? extends R> clazz, R instance, ReflectionHelpers.ClassParameter... paramValues) { + private <T> T createProxy(T shadowedObject, Class<T> clazz) { + return proxyMaker.createProxy(clazz, shadowedObject); + } + + @Override + @SuppressWarnings(value = {"unchecked", "TypeParameterUnusedInFormals"}) + public <R> R invokeConstructor( + Class<? extends R> clazz, R instance, ReflectionHelpers.ClassParameter<?>... paramValues) { String directMethodName = directMethodName(clazz.getName(), ShadowConstants.CONSTRUCTOR_METHOD_NAME); return (R) ReflectionHelpers.callInstanceMethod(clazz, instance, directMethodName, paramValues); @@ -81,6 +94,11 @@ public class ShadowImpl implements IShadow { } @Override + public String directNativeMethodName(String className, String methodName) { + return ShadowConstants.ROBO_PREFIX + methodName + "$nativeBinding"; + } + + @Override public void directInitialize(Class<?> clazz) { try { RobolectricInternals.performStaticInitialization(clazz); diff --git a/sandbox/src/main/java/org/robolectric/internal/bytecode/ShadowInfo.java b/sandbox/src/main/java/org/robolectric/internal/bytecode/ShadowInfo.java index 1276c3722..b82c25b33 100644 --- a/sandbox/src/main/java/org/robolectric/internal/bytecode/ShadowInfo.java +++ b/sandbox/src/main/java/org/robolectric/internal/bytecode/ShadowInfo.java @@ -11,6 +11,8 @@ public class ShadowInfo { public final String shadowedClassName; public final String shadowClassName; public final boolean callThroughByDefault; + public final boolean callNativeMethodsByDefault; + public final boolean looseSignatures; private final int minSdk; private final int maxSdk; @@ -20,6 +22,7 @@ public class ShadowInfo { String shadowedClassName, String shadowClassName, boolean callThroughByDefault, + boolean callNativeMethodsByDefault, boolean looseSignatures, int minSdk, int maxSdk, @@ -27,6 +30,7 @@ public class ShadowInfo { this.shadowedClassName = shadowedClassName; this.shadowClassName = shadowClassName; this.callThroughByDefault = callThroughByDefault; + this.callNativeMethodsByDefault = callNativeMethodsByDefault; this.looseSignatures = looseSignatures; this.minSdk = minSdk; this.maxSdk = maxSdk; @@ -37,9 +41,11 @@ public class ShadowInfo { } ShadowInfo(String shadowedClassName, String shadowClassName, Implements annotation) { - this(shadowedClassName, + this( + shadowedClassName, shadowClassName, annotation.callThroughByDefault(), + annotation.callNativeMethodsByDefault(), annotation.looseSignatures(), annotation.minSdk(), annotation.maxSdk(), diff --git a/sandbox/src/main/java/org/robolectric/internal/bytecode/ShadowMap.java b/sandbox/src/main/java/org/robolectric/internal/bytecode/ShadowMap.java index cb77a1f74..5fb531d4e 100644 --- a/sandbox/src/main/java/org/robolectric/internal/bytecode/ShadowMap.java +++ b/sandbox/src/main/java/org/robolectric/internal/bytecode/ShadowMap.java @@ -70,7 +70,7 @@ public class ShadowMap { } public boolean hasShadowPicker(MutableClass mutableClass) { - return shadowPickers.containsKey(mutableClass.getName().replace('$', '.')); + return shadowPickers.containsKey(mutableClass.getName()); } public ShadowInfo getShadowInfo(Class<?> clazz, ShadowMatcher shadowMatcher) { @@ -265,10 +265,18 @@ public class ShadowMap { String realClassName, String shadowClassName, boolean callThroughByDefault, + boolean callNativeMethodsByDefault, boolean looseSignatures) { addShadowInfo( new ShadowInfo( - realClassName, shadowClassName, callThroughByDefault, looseSignatures, -1, -1, null)); + realClassName, + shadowClassName, + callThroughByDefault, + callNativeMethodsByDefault, + looseSignatures, + -1, + -1, + null)); return this; } diff --git a/sandbox/src/main/java/org/robolectric/internal/bytecode/ShadowWrangler.java b/sandbox/src/main/java/org/robolectric/internal/bytecode/ShadowWrangler.java index 30ee4f563..1c6d8c19c 100644 --- a/sandbox/src/main/java/org/robolectric/internal/bytecode/ShadowWrangler.java +++ b/sandbox/src/main/java/org/robolectric/internal/bytecode/ShadowWrangler.java @@ -23,7 +23,6 @@ import javax.annotation.Nonnull; import javax.annotation.Priority; import org.robolectric.annotation.RealObject; import org.robolectric.annotation.ReflectorObject; -import org.robolectric.sandbox.NativeMethodNotFoundException; import org.robolectric.sandbox.ShadowMatcher; import org.robolectric.util.Function; import org.robolectric.util.PerfStatsCollector; @@ -183,21 +182,7 @@ public class ShadowWrangler implements ClassHandler { } else { RobolectricInternals.performStaticInitialization(clazz); } - } catch (InvocationTargetException e) { - // Note: target exception originates from the sandbox classloader. - // "instanceof" does not check class equality across classloaders (since they differ). - // A simple workaround is to check the class FQCN instead. - String nativeMethodNotFoundException = NativeMethodNotFoundException.class.getName(); - - for (Throwable t = e.getTargetException(); t != null; ) { - if (nativeMethodNotFoundException.equals(t.getClass().getName())) { - throw (RuntimeException) t; - } - - t = t.getCause(); - } - throw new RuntimeException(e); - } catch (IllegalAccessException e) { + } catch (InvocationTargetException | IllegalAccessException e) { throw new RuntimeException(e); } } @@ -210,7 +195,11 @@ public class ShadowWrangler implements ClassHandler { @SuppressWarnings({"ReferenceEquality"}) @Override public MethodHandle findShadowMethodHandle( - Class<?> definingClass, String name, MethodType methodType, boolean isStatic) + Class<?> definingClass, + String name, + MethodType methodType, + boolean isStatic, + boolean isNative) throws IllegalAccessException { return PerfStatsCollector.getInstance() .measure( @@ -222,6 +211,18 @@ public class ShadowWrangler implements ClassHandler { Method shadowMethod = pickShadowMethod(definingClass, name, paramTypes); if (shadowMethod == CALL_REAL_CODE) { + ShadowInfo shadowInfo = getExactShadowInfo(definingClass); + if (isNative && shadowInfo != null && shadowInfo.callNativeMethodsByDefault) { + try { + Method method = + definingClass.getDeclaredMethod( + ShadowConstants.ROBO_PREFIX + name + "$nativeBinding", paramTypes); + method.setAccessible(true); + return LOOKUP.unreflect(method); + } catch (NoSuchMethodException e) { + throw new LinkageError("Missing native binding method", e); + } + } return null; } else if (shadowMethod == DO_NOTHING_METHOD) { return DO_NOTHING; diff --git a/sandbox/src/main/java/org/robolectric/sandbox/NativeMethodNotFoundException.java b/sandbox/src/main/java/org/robolectric/sandbox/NativeMethodNotFoundException.java deleted file mode 100644 index ad04d959f..000000000 --- a/sandbox/src/main/java/org/robolectric/sandbox/NativeMethodNotFoundException.java +++ /dev/null @@ -1,18 +0,0 @@ -package org.robolectric.sandbox; - -/** - * Thrown when a particular Robolectric native method cannot be found. - * - * <p>Instrumented native methods throw this exception when the NativeCallHandler is set to - * throw-on-native and that the dedicated method signature has not been exempted. - */ -public class NativeMethodNotFoundException extends RuntimeException { - - public NativeMethodNotFoundException() { - super(); - } - - public NativeMethodNotFoundException(String message) { - super(message); - } -} diff --git a/sandbox/src/test/java/org/robolectric/NativeMethodInvocationTest.java b/sandbox/src/test/java/org/robolectric/NativeMethodInvocationTest.java new file mode 100644 index 000000000..2dcda2ae5 --- /dev/null +++ b/sandbox/src/test/java/org/robolectric/NativeMethodInvocationTest.java @@ -0,0 +1,33 @@ +package org.robolectric; + +import static org.junit.Assert.assertThrows; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.annotation.Implements; +import org.robolectric.annotation.internal.Instrument; +import org.robolectric.internal.SandboxTestRunner; +import org.robolectric.internal.bytecode.SandboxConfig; + +/* Tests for native method instrumentation. */ +@RunWith(SandboxTestRunner.class) +public class NativeMethodInvocationTest { + @SandboxConfig(shadows = ShadowClassWithNativeMethods.class) + @Test + public void callNativeMethodsByDefault_unsatisfiedLinkError() { + ClassWithNativeMethods classWithNativeMethods = new ClassWithNativeMethods(); + assertThrows(UnsatisfiedLinkError.class, ClassWithNativeMethods::staticNativeMethod); + assertThrows(UnsatisfiedLinkError.class, classWithNativeMethods::instanceNativeMethod); + } + + @Instrument + static class ClassWithNativeMethods { + static native void staticNativeMethod(); + + native void instanceNativeMethod(); + } + + /** Shadow for {@link NativeMethodInvocationTest.ClassWithNativeMethods} */ + @Implements(value = ClassWithNativeMethods.class, callNativeMethodsByDefault = true) + public static class ShadowClassWithNativeMethods {} +} diff --git a/sandbox/src/test/java/org/robolectric/internal/bytecode/ClassInstrumentorTest.java b/sandbox/src/test/java/org/robolectric/internal/bytecode/ClassInstrumentorTest.java index fa208d95d..f0f0c22a9 100644 --- a/sandbox/src/test/java/org/robolectric/internal/bytecode/ClassInstrumentorTest.java +++ b/sandbox/src/test/java/org/robolectric/internal/bytecode/ClassInstrumentorTest.java @@ -1,13 +1,8 @@ package org.robolectric.internal.bytecode; import static com.google.common.truth.Truth.assertThat; -import static java.nio.charset.StandardCharsets.UTF_8; -import com.google.common.collect.ImmutableList; -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; +import com.google.common.collect.Iterables; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -15,8 +10,11 @@ import org.junit.rules.TemporaryFolder; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.FieldNode; import org.objectweb.asm.tree.MethodNode; +import org.robolectric.shadow.api.Shadow; /** Test for {@link ClassInstrumentor}. */ @RunWith(JUnit4.class) @@ -39,29 +37,41 @@ public class ClassInstrumentorTest { } @Test - public void instrumentNativeMethod_legacy() { - ClassNode classNode = new ClassNode(); - classNode.name = "org/example/MyClass"; + public void instrumentRegularMethod() { + ClassNode classNode = createClassWithRegularMethod(); + MutableClass clazz = + new MutableClass( + classNode, InstrumentationConfiguration.newBuilder().build(), classNodeProvider); + instrumentor.instrument(clazz); + + String someFunctionName = Shadow.directMethodName("org.example.MyClass", "someFunction"); + MethodNode methodNode = findMethodNode(classNode, someFunctionName); - MethodNode methodNode = new MethodNode(); - methodNode.access = Opcodes.ACC_PUBLIC + Opcodes.ACC_NATIVE; - methodNode.name = "someFunction"; - methodNode.desc = "()I"; - methodNode.signature = "()"; - methodNode.exceptions = ImmutableList.of(); - methodNode.visibleAnnotations = ImmutableList.of(); + assertThat(clazz.classNode.interfaces).contains(Type.getInternalName(ShadowedObject.class)); + assertRoboDataField(clazz.getFields().get(0)); - classNode.methods.add(methodNode); + // Side effect: original method has been made private. + assertThat(methodNode.access & Opcodes.ACC_PRIVATE).isNotEqualTo(0); + // Side effect: instructions have been rewritten to return 0. + assertThat(methodNode.instructions).isEmpty(); + } + @Test + public void instrumentNativeMethod_legacy() { + ClassNode classNode = createClassWithNativeMethod(); MutableClass clazz = new MutableClass( classNode, InstrumentationConfiguration.newBuilder().build(), classNodeProvider); instrumentor.instrument(clazz); + String someFunctionName = Shadow.directMethodName("org.example.MyClass", "someFunction"); + MethodNode methodNode = findMethodNode(classNode, someFunctionName); + + assertThat(clazz.classNode.interfaces).contains(Type.getInternalName(ShadowedObject.class)); + assertRoboDataField(clazz.getFields().get(0)); + // Side effect: original method has been made private. assertThat(methodNode.access & Opcodes.ACC_PRIVATE).isNotEqualTo(0); - // Side effect: original method has been renamed to a robolectric delegate - assertThat(methodNode.name).isEqualTo("$$robo$$org_example_MyClass$someFunction"); // Side effect: instructions have been rewritten to return 0. assertThat(methodNode.instructions.size()).isEqualTo(2); assertThat(methodNode.instructions.get(0).getOpcode()).isEqualTo(Opcodes.ICONST_0); @@ -69,90 +79,49 @@ public class ClassInstrumentorTest { } @Test - public void instrumentNativeMethod_withoutExemption_generatesThrowException() throws IOException { - File exemptionsFile = tempFolder.newFile("natives.txt"); - try (BufferedWriter writer = - new BufferedWriter(new FileWriter(exemptionsFile.getPath(), UTF_8))) { - writer.write("org.example.MyClass#someOtherMethod()V\n"); - } - - NativeCallHandler nativeCallHandler = - new NativeCallHandler( - exemptionsFile, /* writeExemptions= */ false, /* throwOnNatives= */ true); - instrumentor.setNativeCallHandler(nativeCallHandler); - - ClassNode classNode = new ClassNode(); - classNode.name = "org/example/MyClass"; - - MethodNode methodNode = new MethodNode(); - methodNode.access = Opcodes.ACC_PUBLIC + Opcodes.ACC_NATIVE; - methodNode.name = "someFunction"; - methodNode.desc = "()I"; - methodNode.signature = "()"; - methodNode.exceptions = ImmutableList.of(); - methodNode.visibleAnnotations = ImmutableList.of(); - - classNode.methods.add(methodNode); - + public void instrumentNativeMethod_generatesNativeBindingMethod() { + ClassNode classNode = createClassWithNativeMethod(); MutableClass clazz = new MutableClass( classNode, InstrumentationConfiguration.newBuilder().build(), classNodeProvider); instrumentor.instrument(clazz); - // Side effect: original method has been made private. + String nativeMethodName = Shadow.directNativeMethodName("org.example.MyClass", "someFunction"); + MethodNode methodNode = findMethodNode(classNode, nativeMethodName); + + assertThat(clazz.classNode.interfaces).contains(Type.getInternalName(ShadowedObject.class)); + assertRoboDataField(clazz.getFields().get(0)); + + assertThat(methodNode.access & Opcodes.ACC_NATIVE).isNotEqualTo(0); assertThat(methodNode.access & Opcodes.ACC_PRIVATE).isNotEqualTo(0); - // Side effect: original method has been renamed to a robolectric delegate - assertThat(methodNode.name).isEqualTo("$$robo$$org_example_MyClass$someFunction"); - // Side effect: instructions have been rewritten to throw and return. - assertThat(methodNode.instructions.size()).isEqualTo(7); - assertThat(methodNode.instructions.get(0).getOpcode()).isEqualTo(Opcodes.NEW); - assertThat(methodNode.instructions.get(1).getOpcode()).isEqualTo(Opcodes.DUP); - assertThat(methodNode.instructions.get(2).getOpcode()).isEqualTo(Opcodes.LDC); - assertThat(methodNode.instructions.get(3).getOpcode()).isEqualTo(Opcodes.INVOKESPECIAL); - assertThat(methodNode.instructions.get(4).getOpcode()).isEqualTo(Opcodes.ATHROW); - assertThat(methodNode.instructions.get(5).getOpcode()).isEqualTo(Opcodes.ICONST_0); - assertThat(methodNode.instructions.get(6).getOpcode()).isEqualTo(Opcodes.IRETURN); + assertThat(methodNode.access & Opcodes.ACC_SYNTHETIC).isNotEqualTo(0); } - @Test - public void instrumentNativeMethod_withExemption_generatesNoOpReturn() throws IOException { - File exemptionsFile = tempFolder.newFile("natives.txt"); - try (BufferedWriter writer = - new BufferedWriter(new FileWriter(exemptionsFile.getPath(), UTF_8))) { - writer.write("org.example.MyClass#someOtherMethod()V\n"); - writer.write("org.example.MyClass#someFunction()I\n"); - } - - NativeCallHandler nativeCallHandler = - new NativeCallHandler( - exemptionsFile, /* writeExemptions= */ false, /* throwOnNatives= */ true); - instrumentor.setNativeCallHandler(nativeCallHandler); - + private static ClassNode createClassWithRegularMethod() { ClassNode classNode = new ClassNode(); classNode.name = "org/example/MyClass"; + classNode.methods.add(new MethodNode(Opcodes.ACC_PUBLIC, "someFunction", "()I", null, null)); + return classNode; + } - MethodNode methodNode = new MethodNode(); - methodNode.access = Opcodes.ACC_PUBLIC + Opcodes.ACC_NATIVE; - methodNode.name = "someFunction"; - methodNode.desc = "()I"; - methodNode.signature = "()"; - methodNode.exceptions = ImmutableList.of(); - methodNode.visibleAnnotations = ImmutableList.of(); - - classNode.methods.add(methodNode); + private static ClassNode createClassWithNativeMethod() { + ClassNode classNode = new ClassNode(); + classNode.name = "org/example/MyClass"; + classNode.methods.add( + new MethodNode(Opcodes.ACC_PUBLIC + Opcodes.ACC_NATIVE, "someFunction", "()I", null, null)); + return classNode; + } - MutableClass clazz = - new MutableClass( - classNode, InstrumentationConfiguration.newBuilder().build(), classNodeProvider); - instrumentor.instrument(clazz); + private static MethodNode findMethodNode(ClassNode classNode, String name) { + return Iterables.find(classNode.methods, input -> input.name.equals(name)); + } - // Side effect: original method has been made private. - assertThat(methodNode.access & Opcodes.ACC_PRIVATE).isNotEqualTo(0); - // Side effect: original method has been renamed to a robolectric delegate - assertThat(methodNode.name).isEqualTo("$$robo$$org_example_MyClass$someFunction"); - // Side effect: instructions have been rewritten to return 0. - assertThat(methodNode.instructions.size()).isEqualTo(2); - assertThat(methodNode.instructions.get(0).getOpcode()).isEqualTo(Opcodes.ICONST_0); - assertThat(methodNode.instructions.get(1).getOpcode()).isEqualTo(Opcodes.IRETURN); + private static void assertRoboDataField(FieldNode fieldNode) { + assertThat(fieldNode.access) + .isEqualTo(Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNTHETIC | Opcodes.ACC_TRANSIENT); + assertThat(fieldNode.name).isEqualTo(ShadowConstants.CLASS_HANDLER_DATA_FIELD_NAME); + assertThat(fieldNode.desc).isEqualTo(Type.getDescriptor(Object.class)); + assertThat(fieldNode.signature).isEqualTo(Type.getDescriptor(Object.class)); + assertThat(fieldNode.value).isNull(); } } diff --git a/sandbox/src/test/java/org/robolectric/internal/bytecode/NativeCallHandlerTest.java b/sandbox/src/test/java/org/robolectric/internal/bytecode/NativeCallHandlerTest.java deleted file mode 100644 index 04035346e..000000000 --- a/sandbox/src/test/java/org/robolectric/internal/bytecode/NativeCallHandlerTest.java +++ /dev/null @@ -1,218 +0,0 @@ -package org.robolectric.internal.bytecode; - -import static com.google.common.truth.Truth.assertThat; -import static java.nio.charset.StandardCharsets.UTF_8; - -import com.google.common.io.Files; -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -/** Test for {@link NativeCallHandler}. */ -@RunWith(JUnit4.class) -public class NativeCallHandlerTest { - @Rule public TemporaryFolder tempFolder = new TemporaryFolder(); - - @Test - public void jarInstrumentorLegacyUsage() throws IOException { - // CUJ: Legacy jarInstrumentor usage; there is no exemption file, native methods do not throw. - - File exemptionsFile = tempFolder.newFile("natives.txt"); - assertThat(exemptionsFile.delete()).isTrue(); - - // Create handler, which loads exemptions from file. It's fine for the file to be missing. - NativeCallHandler handler = - new NativeCallHandler( - exemptionsFile, /* writeExemptions= */ false, /* throwOnNatives= */ false); - - // No method descriptor should throw. - assertThat(handler.shouldThrow("org.example.MyClass#someOtherMethod()V")).isFalse(); - assertThat(handler.shouldThrow("org.example.MyClass#someOtherMethod(II)V")).isFalse(); - } - - @Test - public void jarInstrumentorUsage_throwOnNativesEnabled() throws IOException { - // CUJ: jarInstrumentor usage with an exemption list and non-exempted native methods should - // throw. - - File exemptionsFile = tempFolder.newFile("natives.txt"); - try (BufferedWriter writer = - new BufferedWriter(new FileWriter(exemptionsFile.getPath(), UTF_8))) { - writer.write("android.app.ActivityThread#dumpGraphicsInfo(Ljava/io/FileDescriptor;)V\n"); - writer.write("libcore.io.Linux#chmod(Ljava/lang/String;I)V\n"); - writer.write("libcore.io.Linux#fchmod(Ljava/io/FileDescriptor;I)V\n"); - writer.write("android.graphics.fonts.Font^Builder#nAddAxis(JIF)V\n"); - writer.write("org.example.MyClass#someOtherMethod()V\n"); - // empty or white-space lines are ignored - writer.write("\n"); - writer.write(" \t \n"); - // A # prefix denotes a comment and is ignored too - writer.write("# org.example.Ignored#comment()V\n"); - writer.write(" # org.example.Ignored#thisIsACommentToo()V \n"); - } - - // Create handler, which loads exemptions from file. ThrowOnNatives is enabled. - NativeCallHandler handler = - new NativeCallHandler( - exemptionsFile, /* writeExemptions= */ false, /* throwOnNatives= */ true); - - // Test exempted methods - assertThat(handler.shouldThrow("org.example.MyClass#someOtherMethod()V")).isFalse(); - - // Test non-exempted methods - assertThat(handler.shouldThrow("org.example.MyClass#someOtherMethod(II)V")).isTrue(); - - // Empty lines and comments are ignored and not present in the exemption list. - assertThat(handler.shouldThrow("")).isTrue(); - assertThat(handler.shouldThrow(" \t ")).isTrue(); - assertThat(handler.shouldThrow("# org.example.Ignored#comment()V")).isTrue(); - assertThat(handler.shouldThrow(" # org.example.Ignored#thisIsACommentToo()V ")).isTrue(); - } - - @Test - public void jarInstrumentorUsage_throwOnNativesDisabled() throws IOException { - // CUJ: jarInstrumentor usage with an exemption list and non-exempted native methods should - // throw. - - File exemptionsFile = tempFolder.newFile("natives.txt"); - try (BufferedWriter writer = - new BufferedWriter(new FileWriter(exemptionsFile.getPath(), UTF_8))) { - writer.write("android.app.ActivityThread#dumpGraphicsInfo(Ljava/io/FileDescriptor;)V\n"); - writer.write("libcore.io.Linux#chmod(Ljava/lang/String;I)V\n"); - writer.write("libcore.io.Linux#fchmod(Ljava/io/FileDescriptor;I)V\n"); - writer.write("android.graphics.fonts.Font^Builder#nAddAxis(JIF)V\n"); - writer.write("org.example.MyClass#someOtherMethod()V\n"); - } - - // Create handler, which loads exemptions from file. ThrowOnNatives is disabled. - NativeCallHandler handler = - new NativeCallHandler( - exemptionsFile, /* writeExemptions= */ false, /* throwOnNatives= */ false); - - // Test exempted methods - assertThat(handler.shouldThrow("org.example.MyClass#someOtherMethod()V")).isFalse(); - - // Test non-exempted methods - assertThat(handler.shouldThrow("org.example.MyClass#someOtherMethod(II)V")).isFalse(); - } - - @Test - public void jarInstrumentorUsage_logNativeCall_ignored() throws IOException { - // When not writing the exemption list, logNativeCall calls are no-op. - - File exemptionsFile = tempFolder.newFile("natives.txt"); - - // Create handler, which loads exemptions from file. ThrowOnNatives is enabled. - NativeCallHandler handler = - new NativeCallHandler( - exemptionsFile, /* writeExemptions= */ false, /* throwOnNatives= */ true); - - // No methods are exempted -- initial list is empty. - assertThat(handler.shouldThrow("org.example.MyClass#someOtherMethod()V")).isTrue(); - assertThat(handler.shouldThrow("android.graphics.fonts.Font$Builder#nAddAxis(JIF)V")).isTrue(); - - handler.logNativeCall("org.example.MyClass#someOtherMethod()V"); - handler.logNativeCall("android.graphics.fonts.Font$Builder#nAddAxis(JIF)V"); - - // LogNativeCall did not capture. These methods are still not exempted. - assertThat(handler.shouldThrow("org.example.MyClass#someOtherMethod()V")).isTrue(); - assertThat(handler.shouldThrow("android.graphics.fonts.Font$Builder#nAddAxis(JIF)V")).isTrue(); - } - - @Test - public void exemptionListGeneratorUsage_logNativeCall_capturesCalls() throws IOException { - // CUJ: jarInstrumentor called to generate the exemption list. - - File exemptionsFile = tempFolder.newFile("natives.txt"); - - // Create handler, which loads exemptions from file. ThrowOnNatives is enabled. - NativeCallHandler handler = - new NativeCallHandler( - exemptionsFile, /* writeExemptions= */ true, /* throwOnNatives= */ true); - - // No methods are exempted -- initial list is empty. - assertThat(handler.shouldThrow("org.example.MyClass#someOtherMethod()V")).isTrue(); - assertThat(handler.shouldThrow("android.graphics.fonts.Font$Builder#nAddAxis(JIF)V")).isTrue(); - - handler.logNativeCall("org.example.MyClass#someOtherMethod()V"); - handler.logNativeCall("android.graphics.fonts.Font$Builder#nAddAxis(JIF)V"); - - // These methods are now exempted. - assertThat(handler.shouldThrow("org.example.MyClass#someOtherMethod()V")).isFalse(); - assertThat(handler.shouldThrow("android.graphics.fonts.Font$Builder#nAddAxis(JIF)V")).isFalse(); - } - - @Test - public void exemptionListGeneratorUsage_writeExemptionFile() throws IOException { - // CUJ: jarInstrumentor called to generate the exemption list. - - File exemptionsFile = tempFolder.newFile("natives.txt"); - try (BufferedWriter writer = - new BufferedWriter(new FileWriter(exemptionsFile.getPath(), UTF_8))) { - writer.write("android.app.ActivityThread#dumpGraphicsInfo(Ljava/io/FileDescriptor;)V\n"); - writer.write("libcore.io.Linux#fchmod(Ljava/io/FileDescriptor;I)V\n"); - } - - // Create handler, which loads exemptions from file. ThrowOnNatives is disabled. - NativeCallHandler handler = - new NativeCallHandler( - exemptionsFile, /* writeExemptions= */ true, /* throwOnNatives= */ false); - - handler.logNativeCall("org.example.MyClass#someOtherMethod()V"); - // Multiple calls with same value are idempotent. - handler.logNativeCall("org.example.MyClass#someOtherMethod(I)V"); - handler.logNativeCall("org.example.MyClass#someOtherMethod(I)V"); - handler.logNativeCall("org.example.MyClass#someOtherMethod(I)V"); - handler.logNativeCall("org.example.MyClass#someOtherMethod(II)V"); - handler.logNativeCall("libcore.io.Linux#chmod(Ljava/lang/String;I)V"); - // Case of a nested class with $ in the FQCN. - handler.logNativeCall("android.graphics.fonts.Font$Builder#nAddAxis(JIF)V"); - - handler.writeExemptionsList(); - - // Note: due to how the generated files are manipulated in the shell/makefile build system, - // '$' characters are a problem and would need to be escaped (and potentially differently for - // shell vs makefiles). The workaround is to have '$' rewritten as '^'. - - assertThat(Files.asCharSource(exemptionsFile, UTF_8).read()) - .isEqualTo( - "android.app.ActivityThread#dumpGraphicsInfo(Ljava/io/FileDescriptor;)V\n" - // Font$Builder gets written as Font^Builder. - + "android.graphics.fonts.Font^Builder#nAddAxis(JIF)V\n" - + "libcore.io.Linux#chmod(Ljava/lang/String;I)V\n" - + "libcore.io.Linux#fchmod(Ljava/io/FileDescriptor;I)V\n" - + "org.example.MyClass#someOtherMethod()V\n" - + "org.example.MyClass#someOtherMethod(I)V\n" - + "org.example.MyClass#someOtherMethod(II)V\n"); - } - - @Test - public void getExceptionMessage() throws IOException { - File exemptionsFile = tempFolder.newFile("natives.txt"); - NativeCallHandler handler = - new NativeCallHandler( - exemptionsFile, /* writeExemptions= */ false, /* throwOnNatives= */ true); - - // Test generated exception message for non-exempted methods. - assertThat( - handler.getExceptionMessage( - "org.example.MyClass$1#someOtherMethod(II)V", - "org.example.MyClass$1", - "someOtherMethod")) - .isEqualTo( - "Unexpected Robolectric native method call to" - + " 'org.example.MyClass$1#someOtherMethod()'.\n" - + "Option 1: If customizing this method is useful, add an implementation in" - + " ShadowMyClass.java.\n" - + "Option 2: If this method just needs to trivially return 0 or null, please add an" - + " exemption entry for\n" - + " org.example.MyClass^1#someOtherMethod(II)V\n" - + "to exemption file natives.txt"); - } -} diff --git a/sandbox/src/test/java/org/robolectric/internal/bytecode/SandboxClassLoaderTest.java b/sandbox/src/test/java/org/robolectric/internal/bytecode/SandboxClassLoaderTest.java index 5c9411ce0..84b97ce8a 100644 --- a/sandbox/src/test/java/org/robolectric/internal/bytecode/SandboxClassLoaderTest.java +++ b/sandbox/src/test/java/org/robolectric/internal/bytecode/SandboxClassLoaderTest.java @@ -147,6 +147,7 @@ public class SandboxClassLoaderTest { Field roboDataField = exampleClass.getField(ShadowConstants.CLASS_HANDLER_DATA_FIELD_NAME); assertNotNull(roboDataField); assertThat(Modifier.isPublic(roboDataField.getModifiers())).isTrue(); + assertThat(Modifier.isTransient(roboDataField.getModifiers())).isTrue(); // Java 9 doesn't allow updates to final fields from outside <init> or <clinit>: // https://bugs.openjdk.java.net/browse/JDK-8157181 @@ -625,7 +626,7 @@ public class SandboxClassLoaderTest { @Override public MethodHandle findShadowMethodHandle( - Class<?> theClass, String name, MethodType type, boolean isStatic) + Class<?> theClass, String name, MethodType type, boolean isStatic, boolean isNative) throws IllegalAccessException { String signature = getSignature(theClass, name, type, isStatic); InvocationProfile invocationProfile = diff --git a/scripts/install-android-prebuilt.sh b/scripts/install-android-prebuilt.sh index 443688137..555ef054d 100755 --- a/scripts/install-android-prebuilt.sh +++ b/scripts/install-android-prebuilt.sh @@ -1,4 +1,3 @@ -@@ -0,0 1,85 @@ #!/bin/bash # # This script signs already built AOSP Android jars, and installs them in your local @@ -20,11 +19,6 @@ if [[ $# -ne 3 ]]; then exit 1 fi -read -p "Please set the GPG passphrase: " -s signingPassphrase -if [[ -z "${signingPassphrase}" ]]; then - exit 1 -fi - JAR_DIR=$(readlink -e "$1") ANDROID_VERSION="$2" ROBOLECTRIC_SUB_VERSION="$3" @@ -53,7 +47,7 @@ build_signed_packages() { echo "Robolectric: Signing files with gpg..." for ext in ".jar" "-javadoc.jar" "-sources.jar" ".pom"; do - ( cd ${JAR_DIR} && gpg -ab --passphrase ${signingPassphrase} android-all-${ROBOLECTRIC_VERSION}$ext ) + ( cd ${JAR_DIR} && gpg -ab android-all-${ROBOLECTRIC_VERSION}$ext ) done echo "Robolectric: Creating bundle for Sonatype upload..." @@ -90,4 +84,4 @@ generate_empty_javadoc build_signed_packages mavenize -echo "DONE!!"
\ No newline at end of file +echo "DONE!!" diff --git a/settings.gradle b/settings.gradle index 37692707e..1d2e82c32 100644 --- a/settings.gradle +++ b/settings.gradle @@ -6,7 +6,6 @@ include ":junit" include ":utils" include ":utils:reflector" include ":pluginapi" -include ":plugins:accessibility-deprecated" include ":plugins:maven-dependency-resolver" include ":preinstrumented" include ":processor" @@ -30,6 +29,7 @@ include ":integration_tests:mockito" include ":integration_tests:mockito-kotlin" include ":integration_tests:mockito-experimental" include ":integration_tests:powermock" +include ":integration_tests:roborazzi" include ':integration_tests:androidx' include ':integration_tests:androidx_test' include ':integration_tests:ctesque' diff --git a/shadowapi/src/main/java/org/robolectric/internal/IShadow.java b/shadowapi/src/main/java/org/robolectric/internal/IShadow.java index d46cbe742..3783164c0 100644 --- a/shadowapi/src/main/java/org/robolectric/internal/IShadow.java +++ b/shadowapi/src/main/java/org/robolectric/internal/IShadow.java @@ -8,7 +8,7 @@ public interface IShadow { <T> T newInstanceOf(Class<T> clazz); - <T> T newInstance(Class<T> clazz, Class[] parameterTypes, Object[] params); + <T> T newInstance(Class<T> clazz, Class<?>[] parameterTypes, Object[] params); /** * Returns a proxy object that invokes the original $$robo$$-prefixed methods for {@code @@ -21,16 +21,27 @@ public interface IShadow { @Deprecated <T> T directlyOn(T shadowedObject, Class<T> clazz); - @SuppressWarnings("unchecked") - <R> R directlyOn(Object shadowedObject, String clazzName, String methodName, ReflectionHelpers.ClassParameter... paramValues); + <R> R directlyOn( + Object shadowedObject, + String clazzName, + String methodName, + ReflectionHelpers.ClassParameter<?>... paramValues); - <R, T> R directlyOn(T shadowedObject, Class<T> clazz, String methodName, ReflectionHelpers.ClassParameter... paramValues); + <R, T> R directlyOn( + T shadowedObject, + Class<T> clazz, + String methodName, + ReflectionHelpers.ClassParameter<?>... paramValues); - <R, T> R directlyOn(Class<T> clazz, String methodName, ReflectionHelpers.ClassParameter... paramValues); + <R, T> R directlyOn( + Class<T> clazz, String methodName, ReflectionHelpers.ClassParameter<?>... paramValues); - <R> R invokeConstructor(Class<? extends R> clazz, R instance, ReflectionHelpers.ClassParameter... paramValues); + <R> R invokeConstructor( + Class<? extends R> clazz, R instance, ReflectionHelpers.ClassParameter<?>... paramValues); String directMethodName(String className, String methodName); + String directNativeMethodName(String className, String methodName); + void directInitialize(Class<?> clazz); } diff --git a/shadowapi/src/main/java/org/robolectric/shadow/api/Shadow.java b/shadowapi/src/main/java/org/robolectric/shadow/api/Shadow.java index 7c01b898b..02ba581a1 100644 --- a/shadowapi/src/main/java/org/robolectric/shadow/api/Shadow.java +++ b/shadowapi/src/main/java/org/robolectric/shadow/api/Shadow.java @@ -79,6 +79,10 @@ public class Shadow { return SHADOW_IMPL.directMethodName(className, methodName); } + public static String directNativeMethodName(String className, String methodName) { + return SHADOW_IMPL.directNativeMethodName(className, methodName); + } + public static void directInitialize(Class<?> clazz) { SHADOW_IMPL.directInitialize(clazz); } diff --git a/shadowapi/src/main/java/org/robolectric/util/ReflectionHelpers.java b/shadowapi/src/main/java/org/robolectric/util/ReflectionHelpers.java index 8ae639971..d32920217 100644 --- a/shadowapi/src/main/java/org/robolectric/util/ReflectionHelpers.java +++ b/shadowapi/src/main/java/org/robolectric/util/ReflectionHelpers.java @@ -522,23 +522,4 @@ public class ReflectionHelpers { return values; } } - - /** - * String parameter used with reflective method calls. - * - * @param <V> The value of the method parameter. - */ - public static class StringParameter<V> { - public final String className; - public final V value; - - public StringParameter(String className, V value) { - this.className = className; - this.value = value; - } - - public static <V> StringParameter<V> from(String className, V value) { - return new StringParameter<>(className, value); - } - } } diff --git a/shadows/framework/build.gradle b/shadows/framework/build.gradle index 57d2679d6..74409a64f 100644 --- a/shadows/framework/build.gradle +++ b/shadows/framework/build.gradle @@ -62,7 +62,6 @@ dependencies { api libs.sqlite4java compileOnly(AndroidSdk.MAX_SDK.coordinates) api libs.icu4j - api libs.androidx.annotation api libs.auto.value.annotations annotationProcessor libs.auto.value diff --git a/shadows/framework/src/main/java/android/webkit/RoboCookieManager.java b/shadows/framework/src/main/java/android/webkit/RoboCookieManager.java index b9626e882..e867a2fac 100644 --- a/shadows/framework/src/main/java/android/webkit/RoboCookieManager.java +++ b/shadows/framework/src/main/java/android/webkit/RoboCookieManager.java @@ -1,8 +1,8 @@ package android.webkit; import java.io.UnsupportedEncodingException; -import java.net.URI; -import java.net.URISyntaxException; +import java.net.MalformedURLException; +import java.net.URL; import java.net.URLDecoder; import java.text.DateFormat; import java.text.ParseException; @@ -242,8 +242,8 @@ public class RoboCookieManager extends CookieManager { } try { - return new URI(url).getHost(); - } catch (URISyntaxException e) { + return new URL(url).getHost(); + } catch (MalformedURLException e) { throw new IllegalArgumentException("wrong URL : " + url, e); } } diff --git a/shadows/framework/src/main/java/org/robolectric/RuntimeEnvironment.java b/shadows/framework/src/main/java/org/robolectric/RuntimeEnvironment.java index cc7c1d457..a728c859b 100644 --- a/shadows/framework/src/main/java/org/robolectric/RuntimeEnvironment.java +++ b/shadows/framework/src/main/java/org/robolectric/RuntimeEnvironment.java @@ -1,7 +1,5 @@ package org.robolectric; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; -import static android.os.Build.VERSION_CODES.KITKAT; import static android.os.Build.VERSION_CODES.LOLLIPOP; import static org.robolectric.annotation.LooperMode.Mode.LEGACY; import static org.robolectric.shadows.ShadowLooper.assertLooperMode; @@ -12,7 +10,6 @@ import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Bitmap; -import android.os.Build; import android.util.DisplayMetrics; import android.view.Display; import com.google.common.base.Supplier; @@ -197,9 +194,7 @@ public class RuntimeEnvironment { * @param newQualifiers the qualifiers to apply */ public static void setQualifiers(String newQualifiers) { - if (getApiLevel() >= JELLY_BEAN_MR1) { - ShadowDisplayManager.changeDisplay(Display.DEFAULT_DISPLAY, newQualifiers); - } + ShadowDisplayManager.changeDisplay(Display.DEFAULT_DISPLAY, newQualifiers); Configuration configuration; DisplayMetrics displayMetrics = new DisplayMetrics(); @@ -238,8 +233,7 @@ public class RuntimeEnvironment { Configuration configuration, DisplayMetrics displayMetrics) { // Update the resources last so that listeners will have a consistent environment. // TODO(paulsowden): Can we call ResourcesManager.getInstance().applyConfigurationToResources()? - if (Build.VERSION.SDK_INT >= KITKAT - && ResourcesManager.getInstance().getConfiguration() != null) { + if (ResourcesManager.getInstance().getConfiguration() != null) { ResourcesManager.getInstance().getConfiguration().updateFrom(configuration); } Resources.getSystem().updateConfiguration(configuration, displayMetrics); diff --git a/shadows/framework/src/main/java/org/robolectric/android/Bootstrap.java b/shadows/framework/src/main/java/org/robolectric/android/Bootstrap.java index 1ed8d2172..97d9ce294 100644 --- a/shadows/framework/src/main/java/org/robolectric/android/Bootstrap.java +++ b/shadows/framework/src/main/java/org/robolectric/android/Bootstrap.java @@ -3,14 +3,10 @@ package org.robolectric.android; import android.content.res.AssetManager; import android.content.res.Configuration; import android.content.res.Resources; -import android.os.Build; -import android.os.Build.VERSION_CODES; import android.util.DisplayMetrics; -import org.robolectric.RuntimeEnvironment; import org.robolectric.res.Qualifiers; import org.robolectric.shadows.ShadowDateUtils; import org.robolectric.shadows.ShadowDisplayManager; -import org.robolectric.shadows.ShadowWindowManagerImpl; public class Bootstrap { @@ -20,6 +16,20 @@ public class Bootstrap { /** internal only */ public static boolean displaySet = false; + public static Configuration getConfiguration() { + if (displayResources != null) { + return displayResources.getConfiguration(); + } + return Bootstrap.configuration; + } + + public static DisplayMetrics getDisplayMetrics() { + if (displayResources != null) { + return displayResources.getDisplayMetrics(); + } + return Bootstrap.displayMetrics; + } + /** internal only */ public static void setDisplayConfiguration( Configuration configuration, DisplayMetrics displayMetrics) { @@ -47,24 +57,10 @@ public class Bootstrap { } /** internal only */ - public static void updateConfiguration(Resources resources) { - if (displayResources == null) { - resources.updateConfiguration(Bootstrap.configuration, Bootstrap.displayMetrics); - } else { - resources.updateConfiguration( - displayResources.getConfiguration(), displayResources.getDisplayMetrics()); - } - } - - /** internal only */ public static void setUpDisplay() { if (!displaySet) { displaySet = true; - if (Build.VERSION.SDK_INT == VERSION_CODES.JELLY_BEAN) { - ShadowWindowManagerImpl.configureDefaultDisplayForJBOnly(configuration, displayMetrics); - } else { - ShadowDisplayManager.configureDefaultDisplay(configuration, displayMetrics); - } + ShadowDisplayManager.configureDefaultDisplay(configuration, displayMetrics); } } @@ -101,20 +97,7 @@ public class Bootstrap { DeviceConfig.applyRules(configuration, displayMetrics, apiLevel); - fixJellyBean(configuration, displayMetrics); - // DateUtils has a static cache of the last Configuration, so it may need to be reset. ShadowDateUtils.resetLastConfig(); } - - private static void fixJellyBean(Configuration configuration, DisplayMetrics displayMetrics) { - if (RuntimeEnvironment.getApiLevel() < Build.VERSION_CODES.KITKAT) { - int widthPx = (int) (configuration.screenWidthDp * displayMetrics.density); - int heightPx = (int) (configuration.screenHeightDp * displayMetrics.density); - displayMetrics.widthPixels = displayMetrics.noncompatWidthPixels = widthPx; - displayMetrics.heightPixels = displayMetrics.noncompatHeightPixels = heightPx; - displayMetrics.xdpi = displayMetrics.noncompatXdpi = displayMetrics.densityDpi; - displayMetrics.ydpi = displayMetrics.noncompatYdpi = displayMetrics.densityDpi; - } - } } diff --git a/shadows/framework/src/main/java/org/robolectric/android/ConfigurationV25.java b/shadows/framework/src/main/java/org/robolectric/android/ConfigurationV25.java index d92298a8e..da0c0f8ea 100644 --- a/shadows/framework/src/main/java/org/robolectric/android/ConfigurationV25.java +++ b/shadows/framework/src/main/java/org/robolectric/android/ConfigurationV25.java @@ -221,12 +221,7 @@ public class ConfigurationV25 { break; } - int densityDpi; - if (RuntimeEnvironment.getApiLevel() > VERSION_CODES.JELLY_BEAN) { - densityDpi = config.densityDpi; - } else { - densityDpi = displayMetrics.densityDpi; - } + int densityDpi = config.densityDpi; switch (densityDpi) { case DENSITY_DPI_UNDEFINED: diff --git a/shadows/framework/src/main/java/org/robolectric/android/DeviceConfig.java b/shadows/framework/src/main/java/org/robolectric/android/DeviceConfig.java index 6ca8a43f7..2b1bd365b 100644 --- a/shadows/framework/src/main/java/org/robolectric/android/DeviceConfig.java +++ b/shadows/framework/src/main/java/org/robolectric/android/DeviceConfig.java @@ -10,7 +10,6 @@ import android.os.Build.VERSION_CODES; import android.util.DisplayMetrics; import java.util.Locale; import org.robolectric.res.Qualifiers; -import org.robolectric.res.android.ConfigDescription; import org.robolectric.res.android.ResTable_config; import org.robolectric.util.ReflectionHelpers; @@ -165,7 +164,7 @@ public class DeviceConfig { .build(); } if (locale != null) { - setLocale(apiLevel, configuration, locale); + configuration.setLocale(locale); } if (resTab.smallestScreenWidthDp != 0) { @@ -236,9 +235,7 @@ public class DeviceConfig { private static void setDensity(int densityDpi, int apiLevel, Configuration configuration, DisplayMetrics displayMetrics) { - if (apiLevel >= VERSION_CODES.JELLY_BEAN_MR1) { - configuration.densityDpi = densityDpi; - } + configuration.densityDpi = densityDpi; displayMetrics.densityDpi = densityDpi; displayMetrics.density = displayMetrics.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE; @@ -278,12 +275,7 @@ public class DeviceConfig { } locale = new Locale(language, country); - setLocale(apiLevel, configuration, locale); - } - - if (apiLevel <= ConfigDescription.SDK_JELLY_BEAN && - getScreenLayoutLayoutDir(configuration) == Configuration.SCREENLAYOUT_LAYOUTDIR_UNDEFINED) { - setScreenLayoutLayoutDir(configuration, Configuration.SCREENLAYOUT_LAYOUTDIR_LTR); + configuration.setLocale(locale); } ScreenSize requestedScreenSize = getScreenSize(configuration); @@ -409,14 +401,6 @@ public class DeviceConfig { configuration.screenHeightDp = oldWidth; } - private static void setLocale(int apiLevel, Configuration configuration, Locale locale) { - if (apiLevel >= VERSION_CODES.JELLY_BEAN_MR1) { - configuration.setLocale(locale); - } else { - configuration.locale = locale; - } - } - private static Locale getLocale(Configuration configuration, int apiLevel) { Locale locale; if (apiLevel > Build.VERSION_CODES.M) { diff --git a/shadows/framework/src/main/java/org/robolectric/android/internal/DisplayConfig.java b/shadows/framework/src/main/java/org/robolectric/android/internal/DisplayConfig.java index 7d07118d6..22368c241 100644 --- a/shadows/framework/src/main/java/org/robolectric/android/internal/DisplayConfig.java +++ b/shadows/framework/src/main/java/org/robolectric/android/internal/DisplayConfig.java @@ -1,6 +1,5 @@ package org.robolectric.android.internal; -import static android.os.Build.VERSION_CODES.KITKAT; import static android.os.Build.VERSION_CODES.LOLLIPOP; import static android.os.Build.VERSION_CODES.LOLLIPOP_MR1; import static android.os.Build.VERSION_CODES.M; @@ -238,10 +237,8 @@ public final class DisplayConfig { presentationDeadlineNanos = other.presentationDeadlineNanos; state = other.state; } - if (RuntimeEnvironment.getApiLevel() >= KITKAT) { - ownerUid = other.ownerUid; - ownerPackageName = other.ownerPackageName; - } + ownerUid = other.ownerUid; + ownerPackageName = other.ownerPackageName; if (RuntimeEnvironment.getApiLevel() >= O) { removeMode = other.removeMode; } @@ -372,10 +369,8 @@ public final class DisplayConfig { other.presentationDeadlineNanos = presentationDeadlineNanos; other.state = state; } - if (RuntimeEnvironment.getApiLevel() >= KITKAT) { - other.ownerUid = ownerUid; - other.ownerPackageName = ownerPackageName; - } + other.ownerUid = ownerUid; + other.ownerPackageName = ownerPackageName; if (RuntimeEnvironment.getApiLevel() >= O) { other.removeMode = removeMode; } diff --git a/shadows/framework/src/main/java/org/robolectric/android/util/concurrent/InlineExecutorService.java b/shadows/framework/src/main/java/org/robolectric/android/util/concurrent/InlineExecutorService.java index 255130368..5294c4523 100644 --- a/shadows/framework/src/main/java/org/robolectric/android/util/concurrent/InlineExecutorService.java +++ b/shadows/framework/src/main/java/org/robolectric/android/util/concurrent/InlineExecutorService.java @@ -1,6 +1,6 @@ package org.robolectric.android.util.concurrent; -import androidx.annotation.NonNull; +import android.annotation.NonNull; import com.google.common.annotations.Beta; import com.google.common.util.concurrent.MoreExecutors; import java.util.Collection; diff --git a/shadows/framework/src/main/java/org/robolectric/android/util/concurrent/PausedExecutorService.java b/shadows/framework/src/main/java/org/robolectric/android/util/concurrent/PausedExecutorService.java index cd16c11d9..bb540577d 100644 --- a/shadows/framework/src/main/java/org/robolectric/android/util/concurrent/PausedExecutorService.java +++ b/shadows/framework/src/main/java/org/robolectric/android/util/concurrent/PausedExecutorService.java @@ -1,6 +1,6 @@ package org.robolectric.android.util.concurrent; -import androidx.annotation.NonNull; +import android.annotation.NonNull; import com.google.common.annotations.Beta; import com.google.common.collect.ImmutableList; import com.google.common.util.concurrent.AbstractFuture; diff --git a/shadows/framework/src/main/java/org/robolectric/fakes/RoboSplashScreen.java b/shadows/framework/src/main/java/org/robolectric/fakes/RoboSplashScreen.java index 915834d59..93b30dbbd 100644 --- a/shadows/framework/src/main/java/org/robolectric/fakes/RoboSplashScreen.java +++ b/shadows/framework/src/main/java/org/robolectric/fakes/RoboSplashScreen.java @@ -1,9 +1,9 @@ package org.robolectric.fakes; +import android.annotation.RequiresApi; import android.annotation.StyleRes; import android.os.Build; import android.window.SplashScreen; -import androidx.annotation.RequiresApi; /** Robolectric implementation of {@link android.window.SplashScreen}. */ @RequiresApi(api = Build.VERSION_CODES.S) diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/AssociationInfoBuilder.java b/shadows/framework/src/main/java/org/robolectric/shadows/AssociationInfoBuilder.java index 2c56e50ab..5750126f3 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/AssociationInfoBuilder.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/AssociationInfoBuilder.java @@ -25,6 +25,7 @@ public class AssociationInfoBuilder { // We have two different constructors for AssociationInfo across // T branches. aosp has the constructor that takes a new "revoked" parameter. private boolean revoked; + private boolean pending; private long lastTimeConnectedMs; private int systemDataSyncFlags; @@ -93,7 +94,7 @@ public class AssociationInfoBuilder { this.revoked = revoked; return this; } - + public AssociationInfoBuilder setLastTimeConnectedMs(long lastTimeConnectedMs) { this.lastTimeConnectedMs = lastTimeConnectedMs; return this; @@ -192,6 +193,7 @@ public class AssociationInfoBuilder { ClassParameter.from(boolean.class, selfManaged), ClassParameter.from(boolean.class, notifyOnDeviceNearby), ClassParameter.from(boolean.class, revoked), + ClassParameter.from(boolean.class, pending), ClassParameter.from(long.class, approvedMs), ClassParameter.from(long.class, lastTimeConnectedMs), ClassParameter.from(int.class, systemDataSyncFlags)); diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/AudioDeviceInfoBuilder.java b/shadows/framework/src/main/java/org/robolectric/shadows/AudioDeviceInfoBuilder.java index c13b68678..b092d1d64 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/AudioDeviceInfoBuilder.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/AudioDeviceInfoBuilder.java @@ -2,10 +2,15 @@ package org.robolectric.shadows; import static org.robolectric.util.reflector.Reflector.reflector; +import android.annotation.RequiresApi; import android.media.AudioDeviceInfo; +import android.media.AudioProfile; +import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; import android.util.SparseIntArray; -import androidx.annotation.RequiresApi; +import com.google.common.collect.ImmutableList; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import java.util.List; import java.util.Optional; import org.robolectric.shadow.api.Shadow; import org.robolectric.util.ReflectionHelpers; @@ -18,7 +23,8 @@ import org.robolectric.util.reflector.Static; @RequiresApi(VERSION_CODES.M) public class AudioDeviceInfoBuilder { - private int type; + private int type = AudioDeviceInfo.TYPE_BUILTIN_SPEAKER; + private ImmutableList<AudioProfile> profiles = ImmutableList.of(); private AudioDeviceInfoBuilder() {} @@ -29,18 +35,39 @@ public class AudioDeviceInfoBuilder { /** * Sets the device type. * + * <p>The default is {@link AudioDeviceInfo#TYPE_BUILTIN_SPEAKER}. + * * @param type The device type. The possible values are the constants defined as <a * href="https://cs.android.com/android/platform/superproject/+/master:frameworks/base/media/java/android/media/AudioDeviceInfo.java?q=AudioDeviceType">AudioDeviceInfo.AudioDeviceType</a> */ + @CanIgnoreReturnValue public AudioDeviceInfoBuilder setType(int type) { this.type = type; return this; } + /** + * Sets the {@link AudioProfile profiles}. + * + * @param profiles The list of {@link AudioProfile profiles}. + */ + @RequiresApi(VERSION_CODES.S) + @CanIgnoreReturnValue + public AudioDeviceInfoBuilder setProfiles(List<AudioProfile> profiles) { + this.profiles = ImmutableList.copyOf(profiles); + return this; + } + public AudioDeviceInfo build() { Object port = Shadow.newInstanceOf("android.media.AudioDevicePort"); ReflectionHelpers.setField(port, "mType", externalToInternalType(type)); - + ReflectionHelpers.setField(port, "mAddress", ""); + Object handle = Shadow.newInstanceOf("android.media.AudioHandle"); + ReflectionHelpers.setField(handle, "mId", 0); + ReflectionHelpers.setField(port, "mHandle", handle); + if (VERSION.SDK_INT >= 31) { + ReflectionHelpers.setField(port, "mProfiles", profiles); + } return ReflectionHelpers.callConstructor( AudioDeviceInfo.class, ClassParameter.from(port.getClass(), port)); } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/AudioProfileBuilder.java b/shadows/framework/src/main/java/org/robolectric/shadows/AudioProfileBuilder.java new file mode 100644 index 000000000..afbbf4b8d --- /dev/null +++ b/shadows/framework/src/main/java/org/robolectric/shadows/AudioProfileBuilder.java @@ -0,0 +1,104 @@ +package org.robolectric.shadows; + +import android.annotation.RequiresApi; +import android.media.AudioFormat; +import android.media.AudioProfile; +import android.os.Build.VERSION_CODES; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import org.robolectric.util.ReflectionHelpers; +import org.robolectric.util.ReflectionHelpers.ClassParameter; + +/** Builder for {@link AudioProfile}. */ +@RequiresApi(VERSION_CODES.S) +public class AudioProfileBuilder { + + private int format = AudioFormat.ENCODING_PCM_16BIT; + private int[] samplingRates = new int[] {48000}; + private int[] channelMasks = new int[] {AudioFormat.CHANNEL_OUT_STEREO}; + private int[] channelIndexMasks = new int[] {}; + private int encapsulationType = AudioProfile.AUDIO_ENCAPSULATION_TYPE_NONE; + + private AudioProfileBuilder() {} + + public static AudioProfileBuilder newBuilder() { + return new AudioProfileBuilder(); + } + + /** + * Sets the audio format. + * + * <p>The default is {@link AudioFormat#ENCODING_PCM_16BIT}. + * + * @param format The audio format. The possible values are the {@code ENCODING_} constants defined + * in {@link AudioFormat}. + */ + @CanIgnoreReturnValue + public AudioProfileBuilder setFormat(int format) { + this.format = format; + return this; + } + + /** + * Sets the sampling rates. + * + * <p>The default is a single-item array with 48000. + * + * @param samplingRates The array of supported sampling rates. + */ + @CanIgnoreReturnValue + public AudioProfileBuilder setSamplingRates(int[] samplingRates) { + this.samplingRates = samplingRates; + return this; + } + + /** + * Sets the channel masks. + * + * <p>The default is a single-item array with {@link AudioFormat#CHANNEL_OUT_STEREO}. + * + * @param channelMasks The array of supported channel masks. The possible values are the {@code + * CHANNEL_OUT_} constants defined in {@link AudioFormat}. + */ + @CanIgnoreReturnValue + public AudioProfileBuilder setChannelMasks(int[] channelMasks) { + this.channelMasks = channelMasks; + return this; + } + + /** + * Sets the channel index masks. + * + * <p>The default is an empty array. + * + * @param channelIndexMasks The array of supported channel index masks. + */ + @CanIgnoreReturnValue + public AudioProfileBuilder setChannelIndexMasks(int[] channelIndexMasks) { + this.channelIndexMasks = channelIndexMasks; + return this; + } + + /** + * Sets the encapsulation type. + * + * <p>The default is {@link AudioProfile#AUDIO_ENCAPSULATION_TYPE_NONE}. + * + * @param encapsulationType The encapsulation type. The possible values are the {@code + * AUDIO_ENCAPSULATION_TYPE_} constants defined in {@link AudioProfile}. + */ + @CanIgnoreReturnValue + public AudioProfileBuilder setEncapsulationType(int encapsulationType) { + this.encapsulationType = encapsulationType; + return this; + } + + public AudioProfile build() { + return ReflectionHelpers.callConstructor( + AudioProfile.class, + ClassParameter.from(Integer.TYPE, format), + ClassParameter.from(int[].class, samplingRates), + ClassParameter.from(int[].class, channelMasks), + ClassParameter.from(int[].class, channelIndexMasks), + ClassParameter.from(Integer.TYPE, encapsulationType)); + } +} diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/BackupDataOutputFactory.java b/shadows/framework/src/main/java/org/robolectric/shadows/BackupDataOutputFactory.java index eb8c888a0..863a60298 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/BackupDataOutputFactory.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/BackupDataOutputFactory.java @@ -2,9 +2,9 @@ package org.robolectric.shadows; import static org.robolectric.util.reflector.Reflector.reflector; +import android.annotation.RequiresApi; import android.app.backup.BackupDataOutput; import android.os.Build.VERSION_CODES; -import androidx.annotation.RequiresApi; import java.io.FileDescriptor; import org.robolectric.util.reflector.Constructor; import org.robolectric.util.reflector.ForType; diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/BarringInfoBuilder.java b/shadows/framework/src/main/java/org/robolectric/shadows/BarringInfoBuilder.java index d30219824..1edfebe59 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/BarringInfoBuilder.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/BarringInfoBuilder.java @@ -47,12 +47,12 @@ import static android.telephony.BarringInfo.BarringServiceInfo.BARRING_TYPE_NONE import static android.telephony.BarringInfo.BarringServiceInfo.BARRING_TYPE_UNCONDITIONAL; import static android.telephony.BarringInfo.BarringServiceInfo.BARRING_TYPE_UNKNOWN; +import android.annotation.RequiresApi; import android.os.Build.VERSION_CODES; import android.telephony.BarringInfo; import android.telephony.BarringInfo.BarringServiceInfo; import android.telephony.CellIdentity; import android.util.SparseArray; -import androidx.annotation.RequiresApi; import com.google.errorprone.annotations.CanIgnoreReturnValue; import org.robolectric.util.ReflectionHelpers; import org.robolectric.util.ReflectionHelpers.ClassParameter; diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/CellIdentityLteBuilder.java b/shadows/framework/src/main/java/org/robolectric/shadows/CellIdentityLteBuilder.java index 597aef246..1aa19cdaf 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/CellIdentityLteBuilder.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/CellIdentityLteBuilder.java @@ -6,7 +6,6 @@ import android.os.Build; import android.telephony.CellIdentityLte; import android.telephony.CellInfo; import android.telephony.ClosedSubscriberGroupInfo; -import androidx.annotation.RequiresApi; import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -16,7 +15,6 @@ import org.robolectric.util.reflector.Constructor; import org.robolectric.util.reflector.ForType; /** Builder for {@link android.telephony.CellIdentityLte}. */ -@RequiresApi(Build.VERSION_CODES.JELLY_BEAN_MR1) public class CellIdentityLteBuilder { @Nullable private String mcc = null; diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/CellIdentityNrBuilder.java b/shadows/framework/src/main/java/org/robolectric/shadows/CellIdentityNrBuilder.java index 22a0e75c0..f103b2bb4 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/CellIdentityNrBuilder.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/CellIdentityNrBuilder.java @@ -2,10 +2,10 @@ package org.robolectric.shadows; import static org.robolectric.util.reflector.Reflector.reflector; +import android.annotation.RequiresApi; import android.os.Build; import android.telephony.CellIdentityNr; import android.telephony.CellInfo; -import androidx.annotation.RequiresApi; import java.util.ArrayList; import java.util.Collection; import java.util.List; diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/CellInfoLteBuilder.java b/shadows/framework/src/main/java/org/robolectric/shadows/CellInfoLteBuilder.java index 6f3f93420..06396e050 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/CellInfoLteBuilder.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/CellInfoLteBuilder.java @@ -7,7 +7,6 @@ import android.telephony.CellIdentityLte; import android.telephony.CellInfo; import android.telephony.CellInfoLte; import android.telephony.CellSignalStrengthLte; -import androidx.annotation.RequiresApi; import org.robolectric.RuntimeEnvironment; import org.robolectric.util.ReflectionHelpers; import org.robolectric.util.reflector.Accessor; @@ -16,7 +15,6 @@ import org.robolectric.util.reflector.ForType; import org.robolectric.util.reflector.WithType; /** Builder for {@link android.telephony.CellInfoLte}. */ -@RequiresApi(Build.VERSION_CODES.JELLY_BEAN_MR1) public class CellInfoLteBuilder { private boolean isRegistered = false; @@ -76,7 +74,7 @@ public class CellInfoLteBuilder { cellInfoLteReflector.setCellSignalStrength(cellSignalStrength); CellInfoReflector cellInfoReflector = reflector(CellInfoReflector.class, cellInfo); cellInfoReflector.setTimeStamp(timeStamp); - if (apiLevel <= Build.VERSION_CODES.KITKAT) { + if (apiLevel == Build.VERSION_CODES.KITKAT) { cellInfoReflector.setRegisterd(isRegistered); } else { cellInfoReflector.setRegistered(isRegistered); diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/CellInfoNrBuilder.java b/shadows/framework/src/main/java/org/robolectric/shadows/CellInfoNrBuilder.java index 78195246e..f99a01377 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/CellInfoNrBuilder.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/CellInfoNrBuilder.java @@ -2,12 +2,12 @@ package org.robolectric.shadows; import static org.robolectric.util.reflector.Reflector.reflector; +import android.annotation.RequiresApi; import android.os.Build; import android.os.Parcel; import android.telephony.CellIdentityNr; import android.telephony.CellInfoNr; import android.telephony.CellSignalStrengthNr; -import androidx.annotation.RequiresApi; import org.robolectric.RuntimeEnvironment; import org.robolectric.util.reflector.Constructor; import org.robolectric.util.reflector.ForType; diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/CellSignalStrengthLteBuilder.java b/shadows/framework/src/main/java/org/robolectric/shadows/CellSignalStrengthLteBuilder.java index 9b5d1a1ac..fc44c3a65 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/CellSignalStrengthLteBuilder.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/CellSignalStrengthLteBuilder.java @@ -5,13 +5,11 @@ import static org.robolectric.util.reflector.Reflector.reflector; import android.os.Build; import android.telephony.CellInfo; import android.telephony.CellSignalStrengthLte; -import androidx.annotation.RequiresApi; import org.robolectric.RuntimeEnvironment; import org.robolectric.util.reflector.Constructor; import org.robolectric.util.reflector.ForType; /** Builder for {@link android.telephony.CellSignalStrengthLte} */ -@RequiresApi(Build.VERSION_CODES.JELLY_BEAN_MR1) public class CellSignalStrengthLteBuilder { private int rssi = CellInfo.UNAVAILABLE; diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/CellSignalStrengthNrBuilder.java b/shadows/framework/src/main/java/org/robolectric/shadows/CellSignalStrengthNrBuilder.java index 4f3f859bc..5f23b3adb 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/CellSignalStrengthNrBuilder.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/CellSignalStrengthNrBuilder.java @@ -2,10 +2,10 @@ package org.robolectric.shadows; import static org.robolectric.util.reflector.Reflector.reflector; +import android.annotation.RequiresApi; import android.os.Build; import android.telephony.CellInfo; import android.telephony.CellSignalStrengthNr; -import androidx.annotation.RequiresApi; import java.util.ArrayList; import java.util.List; import org.robolectric.RuntimeEnvironment; diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/DeviceStateSensorOrientationBuilder.java b/shadows/framework/src/main/java/org/robolectric/shadows/DeviceStateSensorOrientationBuilder.java index ae97119e4..2b4a24451 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/DeviceStateSensorOrientationBuilder.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/DeviceStateSensorOrientationBuilder.java @@ -1,8 +1,8 @@ package org.robolectric.shadows; +import android.annotation.RequiresApi; import android.hardware.camera2.params.DeviceStateSensorOrientationMap; import android.os.Build.VERSION_CODES; -import androidx.annotation.RequiresApi; import com.google.errorprone.annotations.CanIgnoreReturnValue; /** Builder for {@link DeviceStateSensorOrientationMap} which was introduced in Android T. */ diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/DragEventBuilder.java b/shadows/framework/src/main/java/org/robolectric/shadows/DragEventBuilder.java index 6b10fac15..ba00e3882 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/DragEventBuilder.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/DragEventBuilder.java @@ -3,11 +3,11 @@ package org.robolectric.shadows; import static android.os.Build.VERSION_CODES.M; import static android.os.Build.VERSION_CODES.R; +import android.annotation.Nullable; import android.content.ClipData; import android.content.ClipDescription; import android.view.DragEvent; import android.view.SurfaceControl; -import androidx.annotation.Nullable; import com.android.internal.view.IDragAndDropPermissions; import org.robolectric.RuntimeEnvironment; import org.robolectric.util.ReflectionHelpers; diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/GnssStatusBuilder.java b/shadows/framework/src/main/java/org/robolectric/shadows/GnssStatusBuilder.java index df5a5c344..29b05b48e 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/GnssStatusBuilder.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/GnssStatusBuilder.java @@ -1,8 +1,8 @@ package org.robolectric.shadows; +import android.annotation.Nullable; import android.location.GnssStatus; import android.os.Build; -import androidx.annotation.Nullable; import com.google.auto.value.AutoValue; import java.util.ArrayList; import java.util.Arrays; diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/HealthStatsBuilder.java b/shadows/framework/src/main/java/org/robolectric/shadows/HealthStatsBuilder.java new file mode 100644 index 000000000..300a21b79 --- /dev/null +++ b/shadows/framework/src/main/java/org/robolectric/shadows/HealthStatsBuilder.java @@ -0,0 +1,214 @@ +package org.robolectric.shadows; + +import static org.robolectric.util.reflector.Reflector.reflector; + +import android.os.Parcel; +import android.os.health.HealthStats; +import android.os.health.TimerStat; +import android.util.ArrayMap; +import com.google.common.annotations.VisibleForTesting; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Set; +import org.robolectric.util.reflector.Accessor; +import org.robolectric.util.reflector.ForType; + +/** Test helper class to build {@link HealthStats} */ +final class HealthStatsBuilder { + + // Header fields + private String dataType = null; + + // TimerStat fields + private final HashMap<Integer, TimerStat> timerMap = new HashMap<>(); + + // Measurement fields + private final HashMap<Integer, Long> measurementMap = new HashMap<>(); + + // Stats fields + private final HashMap<Integer, ArrayMap<String, HealthStats>> statsMap = new HashMap<>(); + + // Timers fields + private final HashMap<Integer, ArrayMap<String, TimerStat>> timersMap = new HashMap<>(); + + // Measurements fields + private final HashMap<Integer, ArrayMap<String, Long>> measurementsMap = new HashMap<>(); + + public static HealthStatsBuilder newBuilder() { + return new HealthStatsBuilder(); + } + + /** Sets the DataType. Defaults to null. */ + @CanIgnoreReturnValue + public HealthStatsBuilder setDataType(String dataType) { + this.dataType = dataType; + return this; + } + + /** + * Adds a TimerStat for the given key. If the same key is used multiple times, the last provided + * TimerStat is used. + */ + @CanIgnoreReturnValue + public HealthStatsBuilder addTimerStat(int key, TimerStat value) { + timerMap.put(key, value); + return this; + } + + /** + * Adds a measurement for the given key. If the same key is used multiple times, the last provided + * measurement is used. + */ + @CanIgnoreReturnValue + public HealthStatsBuilder addMeasurement(int key, long value) { + measurementMap.put(key, value); + return this; + } + + /** + * Adds a map of HealthStats for the given key. If the same key is used multiple times, the last + * provided map is used. + */ + @CanIgnoreReturnValue + public HealthStatsBuilder addStats(int key, ArrayMap<String, HealthStats> value) { + statsMap.put(key, new ArrayMap<String, HealthStats>(value)); + return this; + } + + /** + * Adds a map of TimerStats for the given key. If the same key is used multiple times, the last + * provided map is used. + */ + @CanIgnoreReturnValue + public HealthStatsBuilder addTimers(int key, ArrayMap<String, TimerStat> value) { + timersMap.put(key, new ArrayMap<String, TimerStat>(value)); + return this; + } + + /** + * Adds a map of measurements for the given key. If the same key is used multiple times, the last + * provided map is used. + */ + @CanIgnoreReturnValue + public HealthStatsBuilder addMeasurements(int key, ArrayMap<String, Long> value) { + measurementsMap.put(key, new ArrayMap<String, Long>(value)); + return this; + } + + // HealthStats has internal fields that are generic arrays, so this is unavoidable. + @SuppressWarnings("unchecked") + public HealthStats build() { + // HealthStats' default constructor throws an exception + HealthStats result = new HealthStats(Parcel.obtain()); + + reflector(HealthStatsReflector.class, result).setDataType(dataType); + + int[] mTimerKeys = toSortedIntArray(timerMap.keySet()); + reflector(HealthStatsReflector.class, result).setTimerKeys(mTimerKeys); + int[] mTimerCounts = new int[mTimerKeys.length]; + long[] mTimerTimes = new long[mTimerKeys.length]; + for (int i = 0; i < mTimerKeys.length; i++) { + TimerStat timerStat = timerMap.get(mTimerKeys[i]); + mTimerCounts[i] = timerStat.getCount(); + mTimerTimes[i] = timerStat.getTime(); + } + reflector(HealthStatsReflector.class, result).setTimerCounts(mTimerCounts); + reflector(HealthStatsReflector.class, result).setTimerTimes(mTimerTimes); + + int[] mMeasurementKeys = toSortedIntArray(measurementMap.keySet()); + reflector(HealthStatsReflector.class, result).setMeasurementKeys(mMeasurementKeys); + long[] mMeasurementValues = new long[mMeasurementKeys.length]; + for (int i = 0; i < mMeasurementKeys.length; i++) { + long measurementValue = measurementMap.get(mMeasurementKeys[i]); + mMeasurementValues[i] = measurementValue; + } + reflector(HealthStatsReflector.class, result).setMeasurementValues(mMeasurementValues); + + int[] mStatsKeys = toSortedIntArray(statsMap.keySet()); + reflector(HealthStatsReflector.class, result).setStatsKeys(mStatsKeys); + ArrayMap<String, HealthStats>[] mStatsValues = + (ArrayMap<String, HealthStats>[]) new ArrayMap<?, ?>[mStatsKeys.length]; + for (int i = 0; i < mStatsKeys.length; i++) { + ArrayMap<String, HealthStats> stats = statsMap.get(mStatsKeys[i]); + mStatsValues[i] = stats; + } + reflector(HealthStatsReflector.class, result).setStatsValues(mStatsValues); + + int[] mTimersKeys = toSortedIntArray(timersMap.keySet()); + reflector(HealthStatsReflector.class, result).setTimersKeys(mTimersKeys); + ArrayMap<String, TimerStat>[] mTimersValues = + (ArrayMap<String, TimerStat>[]) new ArrayMap<?, ?>[mTimersKeys.length]; + for (int i = 0; i < mTimersKeys.length; i++) { + ArrayMap<String, TimerStat> timers = timersMap.get(mTimersKeys[i]); + mTimersValues[i] = timers; + } + reflector(HealthStatsReflector.class, result).setTimersValues(mTimersValues); + + int[] mMeasurementsKeys = toSortedIntArray(measurementsMap.keySet()); + reflector(HealthStatsReflector.class, result).setMeasurementsKeys(mMeasurementsKeys); + ArrayMap<String, Long>[] mMeasurementsValues = + (ArrayMap<String, Long>[]) new ArrayMap<?, ?>[mMeasurementsKeys.length]; + for (int i = 0; i < mMeasurementsKeys.length; i++) { + ArrayMap<String, Long> measurements = measurementsMap.get(mMeasurementsKeys[i]); + mMeasurementsValues[i] = measurements; + } + reflector(HealthStatsReflector.class, result).setMeasurementsValues(mMeasurementsValues); + + return result; + } + + private HealthStatsBuilder() {} + + @ForType(HealthStats.class) + private interface HealthStatsReflector { + + @Accessor("mDataType") + void setDataType(String dataType); + + @Accessor("mTimerKeys") + void setTimerKeys(int[] timerKeys); + + @Accessor("mTimerCounts") + void setTimerCounts(int[] timerCounts); + + @Accessor("mTimerTimes") + void setTimerTimes(long[] timerTimes); + + @Accessor("mMeasurementKeys") + void setMeasurementKeys(int[] measurementKeys); + + @Accessor("mMeasurementValues") + void setMeasurementValues(long[] measurementValues); + + @Accessor("mStatsKeys") + void setStatsKeys(int[] statsKeys); + + @Accessor("mStatsValues") + void setStatsValues(ArrayMap<String, HealthStats>[] statsValues); + + @Accessor("mTimersKeys") + void setTimersKeys(int[] timersKeys); + + @Accessor("mTimersValues") + void setTimersValues(ArrayMap<String, TimerStat>[] timersValues); + + @Accessor("mMeasurementsKeys") + void setMeasurementsKeys(int[] measurementsKeys); + + @Accessor("mMeasurementsValues") + void setMeasurementsValues(ArrayMap<String, Long>[] measurementsValues); + } + + @VisibleForTesting + static final int[] toSortedIntArray(Set<Integer> set) { + int[] result = new int[set.size()]; + Object[] inputObjArray = set.toArray(); + for (int i = 0; i < inputObjArray.length; i++) { + result[i] = (int) inputObjArray[i]; + } + // mFooKeys fields of HealthStats are used in Arrays.binarySearch, so they have to be sorted. + Arrays.sort(result); + return result; + } +} diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ModuleInfoBuilder.java b/shadows/framework/src/main/java/org/robolectric/shadows/ModuleInfoBuilder.java index 39063caf0..ba5511aca 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ModuleInfoBuilder.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ModuleInfoBuilder.java @@ -2,8 +2,8 @@ package org.robolectric.shadows; import static com.google.common.base.Preconditions.checkNotNull; +import android.annotation.Nullable; import android.content.pm.ModuleInfo; -import androidx.annotation.Nullable; /** * Builder for {@link ModuleInfo} as ModuleInfo has hidden constructors, this builder class has been diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/NetworkRegistrationInfoTestBuilder.java b/shadows/framework/src/main/java/org/robolectric/shadows/NetworkRegistrationInfoTestBuilder.java index 44706fba2..c3e1eac10 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/NetworkRegistrationInfoTestBuilder.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/NetworkRegistrationInfoTestBuilder.java @@ -4,12 +4,12 @@ import static android.os.Build.VERSION_CODES.Q; import static android.os.Build.VERSION_CODES.TIRAMISU; import static org.robolectric.util.reflector.Reflector.reflector; +import android.annotation.RequiresApi; import android.os.Build.VERSION; import android.telephony.CellIdentity; import android.telephony.DataSpecificRegistrationInfo; import android.telephony.NetworkRegistrationInfo; import android.telephony.VoiceSpecificRegistrationInfo; -import androidx.annotation.RequiresApi; import java.util.List; import org.robolectric.RuntimeEnvironment; import org.robolectric.util.reflector.Accessor; diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/NetworkSpecifierFactory.java b/shadows/framework/src/main/java/org/robolectric/shadows/NetworkSpecifierFactory.java new file mode 100644 index 000000000..93b11acd1 --- /dev/null +++ b/shadows/framework/src/main/java/org/robolectric/shadows/NetworkSpecifierFactory.java @@ -0,0 +1,29 @@ +package org.robolectric.shadows; + +import android.annotation.RequiresApi; +import android.net.NetworkSpecifier; +import android.net.StringNetworkSpecifier; +import android.os.Build; + +/** Factory to create {@link NetworkSpecifier} types that are hidden on certain SDK levels. */ +@RequiresApi(Build.VERSION_CODES.O) +public final class NetworkSpecifierFactory { + + /** + * Constructs a new {@link StringNetworkSpecifier} instance, which has remained hidden on all SDKs + * (as of U), but has existed since the {@link NetworkSpecifier} hierarchy was created in O to + * represent a few more niche specifier types without defining a full-blown subclass of {@link + * NetworkSpecifier} for each of their particular use cases. These meanings typically stabilize + * over time and then gain a concrete {@link NetworkSpecifier} subtype in the public SDK, which + * tests should prefer when available. + * + * <p>Depending on the {@code specifier} string's content, the returned instance will have one of + * several different meanings. See {@link + * android.net.NetworkRequest.Builder#setNetworkSpecifier(String)} documentation for more detail. + */ + public static NetworkSpecifier newStringNetworkSpecifier(String specifier) { + return new StringNetworkSpecifier(specifier); + } + + private NetworkSpecifierFactory() {} +} diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/PackageRollbackInfoBuilder.java b/shadows/framework/src/main/java/org/robolectric/shadows/PackageRollbackInfoBuilder.java index ac71cb5cd..393a32d72 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/PackageRollbackInfoBuilder.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/PackageRollbackInfoBuilder.java @@ -3,13 +3,13 @@ package org.robolectric.shadows; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; +import android.annotation.Nullable; import android.content.pm.VersionedPackage; import android.content.rollback.PackageRollbackInfo; import android.content.rollback.PackageRollbackInfo.RestoreInfo; import android.os.Build.VERSION_CODES; import android.util.IntArray; import android.util.SparseLongArray; -import androidx.annotation.Nullable; import java.util.ArrayList; import java.util.List; import org.robolectric.RuntimeEnvironment; diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ResourceHelper.java b/shadows/framework/src/main/java/org/robolectric/shadows/ResourceHelper.java index 7fe54d94b..f031681b0 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ResourceHelper.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ResourceHelper.java @@ -19,6 +19,7 @@ package org.robolectric.shadows; import android.util.TypedValue; import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.robolectric.util.Logger; /** * Helper class to provide various conversion method used in handling android resources. @@ -141,17 +142,19 @@ public final class ResourceHelper { } } - private final static UnitEntry[] sUnitNames = new UnitEntry[] { - new UnitEntry("px", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_PX, 1.0f), - new UnitEntry("dip", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_DIP, 1.0f), - new UnitEntry("dp", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_DIP, 1.0f), - new UnitEntry("sp", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_SP, 1.0f), - new UnitEntry("pt", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_PT, 1.0f), - new UnitEntry("in", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_IN, 1.0f), - new UnitEntry("mm", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_MM, 1.0f), - new UnitEntry("%", TypedValue.TYPE_FRACTION, TypedValue.COMPLEX_UNIT_FRACTION, 1.0f/100), - new UnitEntry("%p", TypedValue.TYPE_FRACTION, TypedValue.COMPLEX_UNIT_FRACTION_PARENT, 1.0f/100), - }; + private static final UnitEntry[] sUnitNames = + new UnitEntry[] { + new UnitEntry("px", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_PX, 1.0f), + new UnitEntry("dip", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_DIP, 1.0f), + new UnitEntry("dp", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_DIP, 1.0f), + new UnitEntry("sp", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_SP, 1.0f), + new UnitEntry("pt", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_PT, 1.0f), + new UnitEntry("in", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_IN, 1.0f), + new UnitEntry("mm", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_MM, 1.0f), + new UnitEntry("%", TypedValue.TYPE_FRACTION, TypedValue.COMPLEX_UNIT_FRACTION, 1.0f / 100), + new UnitEntry( + "%p", TypedValue.TYPE_FRACTION, TypedValue.COMPLEX_UNIT_FRACTION_PARENT, 1.0f / 100), + }; /** * Returns the raw value from the given attribute float-type value string. @@ -190,6 +193,17 @@ public final class ResourceHelper { return false; } + // Limit the maximum float attribute string length to mitigate potential + // "Polynomial regular expression used on uncontrolled data" check severity + // from GitHub CodeQL. + if (len > 1000) { + Logger.info( + "Skip to parse attribute for float attribute, because it is too long. The attribute is " + + attribute + + "."); + return false; + } + // check that there's no non ascii characters. char[] buf = value.toCharArray(); for (int i = 0 ; i < len ; i++) { diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ServiceStateBuilder.java b/shadows/framework/src/main/java/org/robolectric/shadows/ServiceStateBuilder.java index 2417bf8a7..736b698a5 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ServiceStateBuilder.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ServiceStateBuilder.java @@ -5,10 +5,10 @@ import static android.os.Build.VERSION_CODES.Q; import static android.os.Build.VERSION_CODES.R; import static org.robolectric.util.reflector.Reflector.reflector; +import android.annotation.RequiresApi; import android.os.Build.VERSION; import android.telephony.NetworkRegistrationInfo; import android.telephony.ServiceState; -import androidx.annotation.RequiresApi; import java.util.List; import org.robolectric.RuntimeEnvironment; import org.robolectric.util.reflector.Accessor; diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAccessibilityManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAccessibilityManager.java index 31815eebb..f2f53a073 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAccessibilityManager.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAccessibilityManager.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.KITKAT; import static android.os.Build.VERSION_CODES.O; import static android.os.Build.VERSION_CODES.O_MR1; import static org.robolectric.RuntimeEnvironment.getApiLevel; @@ -75,32 +74,18 @@ public class ShadowAccessibilityManager { } private static AccessibilityManager createInstance(Context context) { - if (getApiLevel() >= KITKAT) { - AccessibilityManager accessibilityManager = - Shadow.newInstance( - AccessibilityManager.class, - new Class[] {Context.class, IAccessibilityManager.class, int.class}, - new Object[] { - context, ReflectionHelpers.createNullProxy(IAccessibilityManager.class), 0 - }); - ReflectionHelpers.setField( - accessibilityManager, - "mHandler", - new MyHandler(context.getMainLooper(), accessibilityManager)); - return accessibilityManager; - } else { - AccessibilityManager accessibilityManager = - Shadow.newInstance(AccessibilityManager.class, new Class[0], new Object[0]); - ReflectionHelpers.setField( - accessibilityManager, - "mHandler", - new MyHandler(context.getMainLooper(), accessibilityManager)); - ReflectionHelpers.setField( - accessibilityManager, - "mService", - ReflectionHelpers.createNullProxy(IAccessibilityManager.class)); - return accessibilityManager; - } + AccessibilityManager accessibilityManager = + Shadow.newInstance( + AccessibilityManager.class, + new Class[] {Context.class, IAccessibilityManager.class, int.class}, + new Object[] { + context, ReflectionHelpers.createNullProxy(IAccessibilityManager.class), 0 + }); + ReflectionHelpers.setField( + accessibilityManager, + "mHandler", + new MyHandler(context.getMainLooper(), accessibilityManager)); + return accessibilityManager; } @Implementation @@ -215,7 +200,7 @@ public class ShadowAccessibilityManager { reflector(AccessibilityManagerReflector.class, realAccessibilityManager) .getTouchExplorationStateChangeListeners() .keySet()); - } else if (getApiLevel() >= KITKAT) { + } else { listeners = new ArrayList<>( reflector(AccessibilityManagerReflectorN.class, realAccessibilityManager) diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAccessibilityNodeInfo.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAccessibilityNodeInfo.java index a27d01c98..905baf661 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAccessibilityNodeInfo.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAccessibilityNodeInfo.java @@ -1,13 +1,12 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2; -import static android.os.Build.VERSION_CODES.KITKAT; import static android.os.Build.VERSION_CODES.LOLLIPOP; import static android.os.Build.VERSION_CODES.LOLLIPOP_MR1; import static android.os.Build.VERSION_CODES.N; import static android.os.Build.VERSION_CODES.O; import static android.os.Build.VERSION_CODES.P; +import static android.os.Build.VERSION_CODES.R; +import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE; import static org.robolectric.RuntimeEnvironment.getApiLevel; import static org.robolectric.util.reflector.Reflector.reflector; @@ -279,7 +278,7 @@ public class ShadowAccessibilityNodeInfo { return obtain(parent); } - @Implementation(minSdk = JELLY_BEAN_MR2) + @Implementation protected boolean refresh() { return refreshReturnValue; } @@ -316,7 +315,7 @@ public class ShadowAccessibilityNodeInfo { return text; } - @Implementation(minSdk = JELLY_BEAN_MR2) + @Implementation protected AccessibilityNodeInfo getLabelFor() { if (labelFor == null) { return null; @@ -333,7 +332,7 @@ public class ShadowAccessibilityNodeInfo { labelFor = obtain(info); } - @Implementation(minSdk = JELLY_BEAN_MR1) + @Implementation protected AccessibilityNodeInfo getLabeledBy() { if (labeledBy == null) { return null; @@ -593,20 +592,16 @@ public class ShadowAccessibilityNodeInfo { newShadow.refreshReturnValue = refreshReturnValue; newInfo.setMovementGranularities(realAccessibilityNodeInfo.getMovementGranularities()); newInfo.setPackageName(realAccessibilityNodeInfo.getPackageName()); - if (getApiLevel() >= JELLY_BEAN_MR2) { - newInfo.setViewIdResourceName(realAccessibilityNodeInfo.getViewIdResourceName()); - newInfo.setTextSelection( - realAccessibilityNodeInfo.getTextSelectionStart(), - realAccessibilityNodeInfo.getTextSelectionEnd()); - } - if (getApiLevel() >= KITKAT) { - newInfo.setCollectionInfo(realAccessibilityNodeInfo.getCollectionInfo()); - newInfo.setCollectionItemInfo(realAccessibilityNodeInfo.getCollectionItemInfo()); - newInfo.setInputType(realAccessibilityNodeInfo.getInputType()); - newInfo.setLiveRegion(realAccessibilityNodeInfo.getLiveRegion()); - newInfo.setRangeInfo(realAccessibilityNodeInfo.getRangeInfo()); - newShadow.realAccessibilityNodeInfo.getExtras().putAll(realAccessibilityNodeInfo.getExtras()); - } + newInfo.setViewIdResourceName(realAccessibilityNodeInfo.getViewIdResourceName()); + newInfo.setTextSelection( + realAccessibilityNodeInfo.getTextSelectionStart(), + realAccessibilityNodeInfo.getTextSelectionEnd()); + newInfo.setCollectionInfo(realAccessibilityNodeInfo.getCollectionInfo()); + newInfo.setCollectionItemInfo(realAccessibilityNodeInfo.getCollectionItemInfo()); + newInfo.setInputType(realAccessibilityNodeInfo.getInputType()); + newInfo.setLiveRegion(realAccessibilityNodeInfo.getLiveRegion()); + newInfo.setRangeInfo(realAccessibilityNodeInfo.getRangeInfo()); + newShadow.realAccessibilityNodeInfo.getExtras().putAll(realAccessibilityNodeInfo.getExtras()); if (getApiLevel() >= LOLLIPOP) { newInfo.setMaxTextLength(realAccessibilityNodeInfo.getMaxTextLength()); newInfo.setError(realAccessibilityNodeInfo.getError()); @@ -629,6 +624,12 @@ public class ShadowAccessibilityNodeInfo { newInfo.setTooltipText(realAccessibilityNodeInfo.getTooltipText()); newInfo.setPaneTitle(realAccessibilityNodeInfo.getPaneTitle()); } + if (getApiLevel() >= R) { + newInfo.setStateDescription(realAccessibilityNodeInfo.getStateDescription()); + } + if (getApiLevel() >= UPSIDE_DOWN_CAKE) { + newInfo.setContainerTitle(realAccessibilityNodeInfo.getContainerTitle()); + } return newInfo; } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAccessibilityRecord.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAccessibilityRecord.java index 7470e4dbb..8b99abe66 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAccessibilityRecord.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAccessibilityRecord.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; import static org.robolectric.util.reflector.Reflector.reflector; import android.view.View; @@ -80,7 +79,7 @@ public class ShadowAccessibilityRecord { * * @param id The id to set */ - @Implementation(minSdk = JELLY_BEAN_MR1) + @Implementation public void setWindowId(int id) { windowId = id; } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAccountManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAccountManager.java index 81b70d1c0..a51d205d1 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAccountManager.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAccountManager.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2; import static android.os.Build.VERSION_CODES.LOLLIPOP; import static android.os.Build.VERSION_CODES.LOLLIPOP_MR1; import static android.os.Build.VERSION_CODES.O; @@ -14,12 +13,12 @@ import android.accounts.AuthenticatorException; import android.accounts.IAccountManager; import android.accounts.OnAccountsUpdateListener; import android.accounts.OperationCanceledException; +import android.annotation.Nullable; import android.app.Activity; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.os.Handler; -import androidx.annotation.Nullable; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; @@ -59,6 +58,7 @@ public class ShadowAccountManager { private Handler mainHandler; private RoboAccountManagerFuture pendingAddFuture; private boolean authenticationErrorOnNextResponse = false; + private boolean securityErrorOnNextGetAccountsByTypeCall = false; private Intent removeAccountIntent; @Implementation @@ -78,6 +78,11 @@ public class ShadowAccountManager { @Implementation protected Account[] getAccountsByType(String type) { + if (securityErrorOnNextGetAccountsByTypeCall) { + securityErrorOnNextGetAccountsByTypeCall = false; + throw new SecurityException(); + } + if (type == null) { return getAccounts(); } @@ -724,7 +729,7 @@ public class ShadowAccountManager { return future; } - @Implementation(minSdk = JELLY_BEAN_MR2) + @Implementation protected Account[] getAccountsByTypeForPackage(String type, String packageName) { List<Account> result = new ArrayList<>(); @@ -750,6 +755,16 @@ public class ShadowAccountManager { } /** + * Sets flag which if {@code true} will cause an exception to be thrown by {@link + * #getAccountsByType}. + * + * @param shouldThrowException should an exception be thrown or not on the next method call. + */ + public void setSecurityErrorOnNextGetAccountsByTypeCall(boolean shouldThrowException) { + this.securityErrorOnNextGetAccountsByTypeCall = shouldThrowException; + } + + /** * Sets the intent to include in Bundle result from {@link #removeAccount} if Activity is given. * * @param removeAccountIntent the intent to surface as {@link AccountManager#KEY_INTENT}. diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowActivity.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowActivity.java index 1d575a4b9..2c6520d3b 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowActivity.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowActivity.java @@ -1,7 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.JELLY_BEAN; -import static android.os.Build.VERSION_CODES.KITKAT; import static android.os.Build.VERSION_CODES.LOLLIPOP; import static android.os.Build.VERSION_CODES.M; import static android.os.Build.VERSION_CODES.N; @@ -9,8 +7,12 @@ import static android.os.Build.VERSION_CODES.O; import static android.os.Build.VERSION_CODES.O_MR1; import static android.os.Build.VERSION_CODES.Q; import static android.os.Build.VERSION_CODES.S; +import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE; import static org.robolectric.util.reflector.Reflector.reflector; +import android.annotation.AnimRes; +import android.annotation.ColorInt; +import android.annotation.RequiresApi; import android.app.Activity; import android.app.ActivityManager; import android.app.ActivityOptions; @@ -32,6 +34,7 @@ import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Configuration; import android.database.Cursor; import android.os.Binder; +import android.os.Build; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; import android.os.Bundle; @@ -42,6 +45,7 @@ import android.os.Looper; import android.os.Parcel; import android.text.Selection; import android.text.SpannableStringBuilder; +import android.util.SparseArray; import android.view.Display; import android.view.LayoutInflater; import android.view.Menu; @@ -49,7 +53,6 @@ import android.view.MenuInflater; import android.view.View; import android.view.ViewGroup; import android.view.Window; -import androidx.annotation.RequiresApi; import com.android.internal.app.IVoiceInteractor; import java.util.ArrayList; import java.util.HashMap; @@ -89,6 +92,8 @@ public class ShadowActivity extends ShadowContextThemeWrapper { private Integer lastShownDialogId = null; private int pendingTransitionEnterAnimResId = -1; private int pendingTransitionExitAnimResId = -1; + private SparseArray<OverriddenActivityTransition> overriddenActivityTransitions = + new SparseArray<>(); private Object lastNonConfigurationInstance; private Map<Integer, Dialog> dialogForId = new HashMap<>(); private ArrayList<Cursor> managedCursors = new ArrayList<>(); @@ -382,7 +387,7 @@ public class ShadowActivity extends ShadowContextThemeWrapper { * is because `finish` modifies the members of {@link ShadowActivity#realActivity}, so * `isFinishing` should refer to those same members. */ - @Implementation(minSdk = JELLY_BEAN) + @Implementation protected boolean isFinishing() { return reflector(DirectActivityReflector.class, realActivity).isFinishing(); } @@ -475,7 +480,7 @@ public class ShadowActivity extends ShadowContextThemeWrapper { lastIntentSenderRequest.send(); } - @Implementation(minSdk = KITKAT) + @Implementation protected void reportFullyDrawn() { hasReportedFullyDrawn = true; } @@ -571,6 +576,24 @@ public class ShadowActivity extends ShadowContextThemeWrapper { return pendingTransitionExitAnimResId; } + /** + * Get the overridden {@link Activity} transition, set by {@link + * Activity#overrideActivityTransition}. + * + * @param overrideType Use {@link Activity#OVERRIDE_TRANSITION_OPEN} to get the overridden + * activity transition animation details when starting/entering an activity. Use {@link + * Activity#OVERRIDE_TRANSITION_CLOSE} to get the overridden activity transition animation + * details when finishing/closing an activity. + * @return overridden activity transition details after calling {@link + * Activity#overrideActivityTransition(int, int, int, int)} or null if was not overridden. + * @see #clearOverrideActivityTransition(int) + */ + @Nullable + @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + public OverriddenActivityTransition getOverriddenActivityTransition(int overrideType) { + return overriddenActivityTransitions.get(overrideType, null); + } + @Implementation protected boolean onCreateOptionsMenu(Menu menu) { optionsMenu = menu; @@ -739,6 +762,27 @@ public class ShadowActivity extends ShadowContextThemeWrapper { pendingTransitionExitAnimResId = exitAnim; } + @Implementation(minSdk = UPSIDE_DOWN_CAKE) + protected void overrideActivityTransition( + int overrideType, + @AnimRes int enterAnim, + @AnimRes int exitAnim, + @ColorInt int backgroundColor) { + overriddenActivityTransitions.put( + overrideType, new OverriddenActivityTransition(enterAnim, exitAnim, backgroundColor)); + + reflector(DirectActivityReflector.class, realActivity) + .overrideActivityTransition(overrideType, enterAnim, exitAnim, backgroundColor); + } + + @Implementation(minSdk = UPSIDE_DOWN_CAKE) + protected void clearOverrideActivityTransition(int overrideType) { + overriddenActivityTransitions.remove(overrideType); + + reflector(DirectActivityReflector.class, realActivity) + .clearOverrideActivityTransition(overrideType); + } + public Dialog getDialogById(int dialogId) { return dialogForId.get(dialogId); } @@ -916,6 +960,22 @@ public class ShadowActivity extends ShadowContextThemeWrapper { }); } + /** + * Class to hold overridden activity transition details after calling {@link + * Activity#overrideActivityTransition(int, int, int, int)} + */ + public static class OverriddenActivityTransition { + @AnimRes public final int enterAnim; + @AnimRes public final int exitAnim; + @ColorInt public final int backgroundColor; + + public OverriddenActivityTransition(int enterAnim, int exitAnim, int backgroundColor) { + this.enterAnim = enterAnim; + this.exitAnim = exitAnim; + this.backgroundColor = backgroundColor; + } + } + /** Class to hold a permissions request, including its request code. */ public static class PermissionsRequest { public final int requestCode; @@ -987,6 +1047,11 @@ public class ShadowActivity extends ShadowContextThemeWrapper { boolean isFinishing(); + void overrideActivityTransition( + int overrideType, int enterAnim, int exitAnim, int backgroundColor); + + void clearOverrideActivityTransition(int overrideType); + Window getWindow(); Object getLastNonConfigurationInstance(); diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowActivityManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowActivityManager.java index bd588217a..b3607ac0e 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowActivityManager.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowActivityManager.java @@ -1,8 +1,6 @@ package org.robolectric.shadows; import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; -import static android.os.Build.VERSION_CODES.KITKAT; import static android.os.Build.VERSION_CODES.LOLLIPOP; import static android.os.Build.VERSION_CODES.M; import static android.os.Build.VERSION_CODES.O; @@ -11,6 +9,7 @@ import static android.os.Build.VERSION_CODES.R; import static java.util.stream.Collectors.toCollection; import static org.robolectric.util.reflector.Reflector.reflector; +import android.annotation.RequiresApi; import android.annotation.RequiresPermission; import android.app.ActivityManager; import android.app.ApplicationExitInfo; @@ -26,7 +25,6 @@ import android.os.Process; import android.os.UserHandle; import android.util.ArrayMap; import android.util.SparseIntArray; -import androidx.annotation.RequiresApi; import com.google.common.base.Preconditions; import java.util.ArrayDeque; import java.util.ArrayList; @@ -91,7 +89,7 @@ public class ShadowActivityManager { return false; } - @Implementation(minSdk = JELLY_BEAN_MR1) + @Implementation @HiddenApi @RequiresPermission( anyOf = { @@ -156,7 +154,7 @@ public class ShadowActivityManager { } @HiddenApi - @Implementation(minSdk = JELLY_BEAN_MR1) + @Implementation protected boolean switchUser(int userid) { ShadowUserManager shadowUserManager = Shadow.extract(context.getSystemService(Context.USER_SERVICE)); @@ -246,7 +244,7 @@ public class ShadowActivityManager { return ReflectionHelpers.createNullProxy(IActivityManager.class); } - @Implementation(minSdk = KITKAT) + @Implementation protected boolean isLowRamDevice() { if (isLowRamDeviceOverride != null) { return isLowRamDeviceOverride; diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAlarmManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAlarmManager.java index ea551d8c1..2971b8e57 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAlarmManager.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAlarmManager.java @@ -3,6 +3,8 @@ package org.robolectric.shadows; import static android.app.AlarmManager.RTC_WAKEUP; import static org.robolectric.util.reflector.Reflector.reflector; +import android.annotation.Nullable; +import android.annotation.RequiresApi; import android.app.AlarmManager; import android.app.AlarmManager.AlarmClockInfo; import android.app.AlarmManager.OnAlarmListener; @@ -14,9 +16,7 @@ import android.os.Handler; import android.os.Looper; import android.os.SystemClock; import android.os.WorkSource; -import androidx.annotation.GuardedBy; -import androidx.annotation.Nullable; -import androidx.annotation.RequiresApi; +import com.android.internal.annotations.GuardedBy; import com.google.common.collect.Iterables; import java.util.ArrayList; import java.util.List; @@ -117,7 +117,7 @@ public class ShadowAlarmManager { setImpl(type, triggerAtMs, WINDOW_HEURISTIC, intervalMs, operation, null, null, false); } - @Implementation(minSdk = VERSION_CODES.KITKAT) + @Implementation protected void setWindow( int type, long windowStartMs, long windowLengthMs, PendingIntent operation) { setImpl(type, windowStartMs, windowLengthMs, 0L, operation, null, null, false); @@ -179,7 +179,7 @@ public class ShadowAlarmManager { setImpl(type, windowStartMs, windowLengthMs, 0L, tag, listener, executor, null, true); } - @Implementation(minSdk = VERSION_CODES.KITKAT) + @Implementation protected void setExact(int type, long triggerAtMs, PendingIntent operation) { setImpl(type, triggerAtMs, WINDOW_EXACT, 0L, operation, null, null, false); } @@ -209,7 +209,7 @@ public class ShadowAlarmManager { setImpl(RTC_WAKEUP, info.getTriggerTime(), WINDOW_EXACT, 0L, operation, null, info, true); } - @Implementation(minSdk = VERSION_CODES.KITKAT) + @Implementation protected void set( int type, long triggerAtMs, diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAmbientContextManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAmbientContextManager.java index 9e9dda56e..aac2e77f8 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAmbientContextManager.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAmbientContextManager.java @@ -1,11 +1,11 @@ package org.robolectric.shadows; +import android.annotation.Nullable; import android.app.PendingIntent; import android.app.ambientcontext.AmbientContextEventRequest; import android.app.ambientcontext.AmbientContextManager; import android.os.Build.VERSION_CODES; -import androidx.annotation.GuardedBy; -import androidx.annotation.Nullable; +import com.android.internal.annotations.GuardedBy; import java.util.Set; import java.util.concurrent.Executor; import java.util.function.Consumer; diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAmbientDisplayConfiguration.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAmbientDisplayConfiguration.java new file mode 100644 index 000000000..95888f800 --- /dev/null +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAmbientDisplayConfiguration.java @@ -0,0 +1,69 @@ +package org.robolectric.shadows; + +import android.os.Build.VERSION_CODES; +import android.os.SystemProperties; +import android.text.TextUtils; +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; +import org.robolectric.annotation.Resetter; + +/** Shadow for {@code AmbientDisplayConfiguration} class. */ +@Implements( + className = "android.hardware.display.AmbientDisplayConfiguration", + minSdk = VERSION_CODES.Q, + isInAndroidSdk = false) +public class ShadowAmbientDisplayConfiguration { + + @SuppressWarnings("NonFinalStaticField") + private static String dozeComponent; + + @SuppressWarnings("NonFinalStaticField") + private static boolean dozeAlwaysOnDisplayAvailable; + + @Implementation + protected String ambientDisplayComponent() { + return dozeComponent; + } + + @Implementation + protected boolean alwaysOnAvailable() { + return (alwaysOnDisplayDebuggingEnabled() || alwaysOnDisplayAvailable()) + && ambientDisplayAvailable(); + } + + @Implementation + protected boolean ambientDisplayAvailable() { + return !TextUtils.isEmpty(ambientDisplayComponent()); + } + + @Implementation + protected boolean alwaysOnDisplayDebuggingEnabled() { + return SystemProperties.getBoolean("debug.doze.aod", false) + && (SystemProperties.getInt("ro.debuggable", 0) == 1); + } + + @Implementation + protected boolean alwaysOnDisplayAvailable() { + return dozeAlwaysOnDisplayAvailable; + } + + /** + * Overrides the string format of component for doze mode. See {@link #ambientDisplayComponent()}. + */ + public static void setDozeComponent(String dozeComponent) { + ShadowAmbientDisplayConfiguration.dozeComponent = dozeComponent; + } + + /** + * Overrides the available state for always on display. See {@link #alwaysOnDisplayAvailable()}. + */ + public static void setDozeAlwaysOnDisplayAvailable(boolean dozeAlwaysOnDisplayAvailable) { + ShadowAmbientDisplayConfiguration.dozeAlwaysOnDisplayAvailable = dozeAlwaysOnDisplayAvailable; + } + + @Resetter + public static void reset() { + dozeComponent = null; + dozeAlwaysOnDisplayAvailable = false; + } +} diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAppIntegrityManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAppIntegrityManager.java index 1065554d3..4d7eaa507 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAppIntegrityManager.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAppIntegrityManager.java @@ -13,7 +13,6 @@ import org.robolectric.annotation.Implements; @Implements( value = AppIntegrityManager.class, minSdk = R, - looseSignatures = true, isInAndroidSdk = false) public class ShadowAppIntegrityManager { diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAppOpsManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAppOpsManager.java index 0d36bfc06..814e96a76 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAppOpsManager.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAppOpsManager.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.KITKAT; import static android.os.Build.VERSION_CODES.LOLLIPOP; import static android.os.Build.VERSION_CODES.M; import static android.os.Build.VERSION_CODES.P; @@ -14,6 +13,7 @@ import static org.robolectric.util.reflector.Reflector.reflector; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.RequiresApi; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.app.AppOpsManager; @@ -33,7 +33,6 @@ import android.os.Build; import android.util.ArrayMap; import android.util.LongSparseArray; import android.util.LongSparseLongArray; -import androidx.annotation.RequiresApi; import com.android.internal.app.IAppOpsService; import com.google.auto.value.AutoValue; import com.google.common.base.Preconditions; @@ -65,7 +64,7 @@ import org.robolectric.util.reflector.Accessor; import org.robolectric.util.reflector.ForType; /** Shadow for {@link AppOpsManager}. */ -@Implements(value = AppOpsManager.class, minSdk = KITKAT, looseSignatures = true) +@Implements(value = AppOpsManager.class, looseSignatures = true) public class ShadowAppOpsManager { // OpEntry fields that the shadow doesn't currently allow the test to configure. @@ -277,7 +276,7 @@ public class ShadowAppOpsManager { } /** Stores a fake long-running operation. It does not throw if a wrong uid is passed. */ - @Implementation(minSdk = KITKAT, maxSdk = Q) + @Implementation(maxSdk = Q) protected int startOpNoThrow(int op, int uid, String packageName) { int mode = unsafeCheckOpRawNoThrow(op, uid, packageName); if (mode == AppOpsManager.MODE_ALLOWED) { diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAppWidgetManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAppWidgetManager.java index a8a0847a0..b1d78d1f4 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAppWidgetManager.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAppWidgetManager.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; import static android.os.Build.VERSION_CODES.KITKAT; import static android.os.Build.VERSION_CODES.L; import static android.os.Build.VERSION_CODES.LOLLIPOP; @@ -219,7 +218,7 @@ public class ShadowAppWidgetManager { } @HiddenApi - @Implementation(minSdk = JELLY_BEAN_MR1) + @Implementation protected void bindAppWidgetId(int appWidgetId, ComponentName provider, Bundle options) { WidgetInfo widgetInfo = new WidgetInfo(provider); widgetInfos.put(appWidgetId, widgetInfo); @@ -246,7 +245,7 @@ public class ShadowAppWidgetManager { * Create an internal presentation of the widget locally and store the options {@link Bundle} with * it. This implementation doesn't trigger {@code AppWidgetProvider.onUpdate} */ - @Implementation(minSdk = JELLY_BEAN_MR1) + @Implementation protected boolean bindAppWidgetIdIfAllowed( int appWidgetId, ComponentName provider, Bundle options) { if (validWidgetProviderComponentName) { diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowApplicationPackageManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowApplicationPackageManager.java index 6b20e2d62..77fc0c11a 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowApplicationPackageManager.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowApplicationPackageManager.java @@ -22,9 +22,6 @@ import static android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS; import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES; import static android.content.pm.PackageManager.SIGNATURE_UNKNOWN_PACKAGE; import static android.os.Build.VERSION_CODES.JELLY_BEAN; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2; -import static android.os.Build.VERSION_CODES.KITKAT; import static android.os.Build.VERSION_CODES.LOLLIPOP; import static android.os.Build.VERSION_CODES.LOLLIPOP_MR1; import static android.os.Build.VERSION_CODES.M; @@ -63,7 +60,6 @@ import android.content.pm.FeatureInfo; import android.content.pm.IPackageDataObserver; import android.content.pm.IPackageDeleteObserver; import android.content.pm.IPackageStatsObserver; -import android.content.pm.InstallSourceInfo; import android.content.pm.InstrumentationInfo; import android.content.pm.IntentFilterVerificationInfo; import android.content.pm.ModuleInfo; @@ -71,6 +67,7 @@ import android.content.pm.PackageInfo; import android.content.pm.PackageItemInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.ComponentEnabledSetting; +import android.content.pm.PackageManager.ComponentInfoFlags; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.PackageManager.OnPermissionsChangedListener; import android.content.pm.PackageManager.PackageInfoFlags; @@ -588,7 +585,7 @@ public class ShadowApplicationPackageManager extends ShadowPackageManager { return resolveInfo.activityInfo != null || resolveInfo.serviceInfo != null - || (VERSION.SDK_INT >= VERSION_CODES.KITKAT && resolveInfo.providerInfo != null); + || resolveInfo.providerInfo != null; } private static boolean isFlagSet(long flags, long matchFlag) { @@ -603,7 +600,7 @@ public class ShadowApplicationPackageManager extends ShadowPackageManager { } /** Behaves as {@link #queryIntentServices(Intent, int)} and currently ignores userId. */ - @Implementation(minSdk = JELLY_BEAN_MR1) + @Implementation protected List<ResolveInfo> queryIntentServicesAsUser(Intent intent, int flags, int userId) { return queryIntentServices(intent, flags); } @@ -787,7 +784,7 @@ public class ShadowApplicationPackageManager extends ShadowPackageManager { } /** Behaves as {@link #queryIntentActivities(Intent, int)} and currently ignores userId. */ - @Implementation(minSdk = JELLY_BEAN_MR1) + @Implementation protected List<ResolveInfo> queryIntentActivitiesAsUser(Intent intent, int flags, int userId) { return queryIntentActivities(intent, flags); } @@ -909,6 +906,14 @@ public class ShadowApplicationPackageManager extends ShadowPackageManager { ActivityInfo::new); } + @Implementation(minSdk = TIRAMISU) + protected ActivityInfo getReceiverInfo( + /*ComponentName*/ Object component, /*ComponentInfoFlags*/ Object flags) + throws NameNotFoundException { + return getReceiverInfo( + (ComponentName) component, (int) ((ComponentInfoFlags) flags).getValue()); + } + @Implementation protected List<ResolveInfo> queryBroadcastReceivers(Intent intent, int flags) { return this.queryIntentComponents( @@ -922,7 +927,8 @@ public class ShadowApplicationPackageManager extends ShadowPackageManager { } @Implementation(minSdk = TIRAMISU) - protected List<ResolveInfo> queryBroadcastReceivers(Object intent, @NonNull Object flags) { + protected List<ResolveInfo> queryBroadcastReceivers( + /*Intent*/ Object intent, /*ResolveInfoFlags*/ Object flags) { return queryBroadcastReceivers((Intent) intent, (int) ((ResolveInfoFlags) flags).getValue()); } @@ -953,6 +959,13 @@ public class ShadowApplicationPackageManager extends ShadowPackageManager { ServiceInfo::new); } + @Implementation(minSdk = TIRAMISU) + protected ServiceInfo getServiceInfo( + /*ComponentName*/ Object component, /*ComponentInfoFlags*/ Object flags) + throws NameNotFoundException { + return getServiceInfo((ComponentName) component, (int) ((ComponentInfoFlags) flags).getValue()); + } + /** * Modifies the component in place using. * @@ -1085,8 +1098,12 @@ public class ShadowApplicationPackageManager extends ShadowPackageManager { } @Implementation(minSdk = R) - protected Object getInstallSourceInfo(String packageName) { - return (InstallSourceInfo) packageInstallSourceInfoMap.get(packageName); + protected Object getInstallSourceInfo(String packageName) throws NameNotFoundException { + if (!packageInstallSourceInfoMap.containsKey(packageName)) { + throw new NameNotFoundException("Package is not installed: " + packageName); + } else { + return packageInstallSourceInfoMap.get(packageName); + } } @Implementation @@ -1133,7 +1150,7 @@ public class ShadowApplicationPackageManager extends ShadowPackageManager { verificationResults.put(id, verificationCode); } - @Implementation(minSdk = JELLY_BEAN_MR1) + @Implementation protected void extendVerificationTimeout( int id, int verificationCodeAtTimeout, long millisecondsToDelay) { synchronized (lock) { @@ -1157,7 +1174,7 @@ public class ShadowApplicationPackageManager extends ShadowPackageManager { } } - @Implementation(minSdk = KITKAT) + @Implementation protected List<ResolveInfo> queryIntentContentProviders(Intent intent, int flags) { return this.queryIntentComponents( intent, @@ -1169,7 +1186,7 @@ public class ShadowApplicationPackageManager extends ShadowPackageManager { ProviderInfo::new); } - @Implementation(minSdk = KITKAT) + @Implementation protected List<ResolveInfo> queryIntentContentProvidersAsUser( Intent intent, int flags, int userId) { return Collections.emptyList(); @@ -1195,7 +1212,7 @@ public class ShadowApplicationPackageManager extends ShadowPackageManager { }); } - @Implementation(minSdk = JELLY_BEAN_MR1, maxSdk = M) + @Implementation(maxSdk = M) protected void getPackageSizeInfo(Object pkgName, Object uid, final Object observer) { final PackageStats packageStats = packageStatsMap.get((String) pkgName); new Handler(Looper.getMainLooper()) @@ -1396,7 +1413,7 @@ public class ShadowApplicationPackageManager extends ShadowPackageManager { return null; } - @Implementation(minSdk = JELLY_BEAN_MR2) + @Implementation protected int getPackageUid(String packageName, int flags) throws NameNotFoundException { Integer uid = uidForPackage.get(packageName); if (uid == null) { @@ -1673,7 +1690,7 @@ public class ShadowApplicationPackageManager extends ShadowPackageManager { return null; } - @Implementation(minSdk = JELLY_BEAN_MR2) + @Implementation protected List<PackageInfo> getPackagesHoldingPermissions(String[] permissions, int flags) { synchronized (lock) { List<PackageInfo> packageInfosWithPermissions = new ArrayList<>(); @@ -1706,7 +1723,7 @@ public class ShadowApplicationPackageManager extends ShadowPackageManager { } /** Behaves as {@link #resolveActivity(Intent, int)} and currently ignores userId. */ - @Implementation(minSdk = JELLY_BEAN_MR1) + @Implementation protected ResolveInfo resolveActivityAsUser(Intent intent, int flags, int userId) { return resolveActivity(intent, flags); } @@ -1817,7 +1834,7 @@ public class ShadowApplicationPackageManager extends ShadowPackageManager { } } - @Implementation(minSdk = JELLY_BEAN_MR1) + @Implementation protected Resources getResourcesForApplicationAsUser(String appPackageName, int userId) throws NameNotFoundException { return null; @@ -1837,7 +1854,7 @@ public class ShadowApplicationPackageManager extends ShadowPackageManager { protected void installPackage( Object packageURI, Object observer, Object flags, Object installerPackageName) {} - @Implementation(minSdk = JELLY_BEAN_MR1) + @Implementation protected int installExistingPackage(String packageName) throws NameNotFoundException { return 0; } @@ -1989,7 +2006,7 @@ public class ShadowApplicationPackageManager extends ShadowPackageManager { clearPackagePreferredActivitiesInternal(packageName, preferredActivities); } - @Implementation(minSdk = KITKAT) + @Implementation protected ComponentName getHomeActivities(List<ResolveInfo> outActivities) { return null; } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowArscApkAssets9.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowArscApkAssets9.java index 9ab0887c7..13e95f947 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowArscApkAssets9.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowArscApkAssets9.java @@ -197,6 +197,26 @@ public class ShadowArscApkAssets9 extends ShadowApkAssets { // return ShadowArscAssetManager9.NATIVE_APK_ASSETS_REGISTRY.getNativeObjectId(apk_assets); } + // static jlong NativeLoadFromFd(JNIEnv* env, jclass /*clazz*/, const format_type_t format, + // jobject file_descriptor, jstring friendly_name, + // const jint property_flags, jobject assets_provider) + @Implementation(minSdk = R) + protected static Object nativeLoadFd( + Object format, + Object fileDescriptor, + Object friendlyName, + Object propertyFlags, + Object assetsProvider) + throws IOException { + CppApkAssets apkAssets = CppApkAssets.loadArscFromFd((FileDescriptor) fileDescriptor); + if (apkAssets == null) { + String errorMessage = + String.format("Failed to load from the file descriptor %s", fileDescriptor); + throw new IOException(errorMessage); + } + return Registries.NATIVE_APK_ASSETS_REGISTRY.register(apkAssets); + } + // static jstring NativeGetAssetPath(JNIEnv* env, jclass /*clazz*/, jlong ptr) { @Implementation protected static String nativeGetAssetPath(long ptr) { diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowArscAssetManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowArscAssetManager.java index c0c9c7a8e..6025ee4eb 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowArscAssetManager.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowArscAssetManager.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2; import static android.os.Build.VERSION_CODES.KITKAT_WATCH; import static android.os.Build.VERSION_CODES.LOLLIPOP; import static android.os.Build.VERSION_CODES.M; @@ -265,21 +264,23 @@ public class ShadowArscAssetManager extends ShadowAssetManager.ArscBase { //////////// native method implementations -// public native final String[] list(String path) -// throws IOException; + // public native final String[] list(String path) + // throws IOException; -// @HiddenApi @Implementation(minSdk = VERSION_CODES.P) -// public void setApkAssets(Object apkAssetsObjects, Object invalidateCaches) { -// throw new UnsupportedOperationException("implement me"); -// } -// + // @HiddenApi @Implementation(minSdk = VERSION_CODES.P) + // public void setApkAssets(Object apkAssetsObjects, Object invalidateCaches) { + // throw new UnsupportedOperationException("implement me"); + // } + // - @HiddenApi @Implementation(maxSdk = VERSION_CODES.JELLY_BEAN_MR1) + @HiddenApi + @Implementation(maxSdk = VERSION_CODES.JELLY_BEAN_MR1) public int addAssetPath(String path) { return addAssetPathNative(path); } - @HiddenApi @Implementation(minSdk = JELLY_BEAN_MR2, maxSdk = M) + @HiddenApi + @Implementation(maxSdk = M) final protected int addAssetPathNative(String path) { return addAssetPathNative(path, false); } @@ -1375,6 +1376,12 @@ public class ShadowArscAssetManager extends ShadowAssetManager.ArscBase { return paths; } + @VisibleForTesting + @Override + long getNativePtr() { + return reflector(_AssetManager_.class, realObject).getNativePtr(); + } + @Override List<AssetPath> getAssetPaths() { return assetManagerForJavaObject().getAssetPaths(); diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowArscAssetManager10.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowArscAssetManager10.java index aa0272ab1..bc5200e97 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowArscAssetManager10.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowArscAssetManager10.java @@ -21,7 +21,6 @@ import static org.robolectric.res.android.Util.CHECK; import static org.robolectric.res.android.Util.JNI_FALSE; import static org.robolectric.res.android.Util.JNI_TRUE; import static org.robolectric.res.android.Util.isTruthy; -import static org.robolectric.shadow.api.Shadow.invokeConstructor; import static org.robolectric.util.reflector.Reflector.reflector; import android.annotation.AnyRes; @@ -38,6 +37,7 @@ import android.os.Build; import android.os.ParcelFileDescriptor; import android.util.SparseArray; import android.util.TypedValue; +import com.google.common.annotations.VisibleForTesting; import dalvik.system.VMRuntime; import java.io.File; import java.io.FileDescriptor; @@ -76,10 +76,11 @@ import org.robolectric.res.android.ResourceTypes.Res_value; import org.robolectric.shadow.api.Shadow; import org.robolectric.util.PerfStatsCollector; import org.robolectric.util.ReflectionHelpers; -import org.robolectric.util.ReflectionHelpers.ClassParameter; import org.robolectric.util.reflector.Direct; import org.robolectric.util.reflector.ForType; import org.robolectric.util.reflector.Static; +import org.robolectric.versioning.AndroidVersions.U; + // TODO: update path to released version. // transliterated from // https://android.googlesource.com/platform/frameworks/base/+/android-10.0.0_rXX/core/jni/android_util_AssetManager.cpp @@ -105,7 +106,7 @@ public class ShadowArscAssetManager10 extends ShadowAssetManager.ArscBase { private static CppAssetManager2 systemCppAssetManager2; private static long systemCppAssetManager2Ref; - private static boolean inNonSystemConstructor; + private static boolean inResourcesGetSystem; @RealObject AssetManager realAssetManager; @@ -229,6 +230,12 @@ public class ShadowArscAssetManager10 extends ShadowAssetManager.ArscBase { return ApkAssetsCookie.forInt(cookie > 0 ? (cookie - 1) : kInvalidCookie); } + @VisibleForTesting + @Override + long getNativePtr() { + return reflector(_AssetManager_.class, realAssetManager).getNativePtr(); + } + // This is called by zygote (running as user root) as part of preloadResources. // static void NativeVerifySystemIdmaps(JNIEnv* /*env*/, jclass /*clazz*/) { @Implementation(minSdk = P, maxSdk = Q) @@ -440,16 +447,15 @@ public class ShadowArscAssetManager10 extends ShadowAssetManager.ArscBase { return ParcelFileDescriptor.open(asset.getFile(), ParcelFileDescriptor.MODE_READ_ONLY); } - /** Used for the creation of system assets. */ @Implementation(minSdk = P) - protected void __constructor__(boolean sentinel) { - inNonSystemConstructor = true; + protected static AssetManager getSystem() { + // The Android code of AssetManager.getSystem is locked on a static variable, so there is not + // a concurrency concern here. + inResourcesGetSystem = true; try { - // call real constructor so field initialization happens. - invokeConstructor( - AssetManager.class, realAssetManager, ClassParameter.from(boolean.class, sentinel)); + return reflector(_AssetManager_.class).getSystem(); } finally { - inNonSystemConstructor = false; + inResourcesGetSystem = false; } } @@ -487,17 +493,17 @@ public class ShadowArscAssetManager10 extends ShadowAssetManager.ArscBase { long cppAssetManagerRef; // we want to share a single instance of the system CppAssetManager2 - if (inNonSystemConstructor) { - CppAssetManager2 appAssetManager = new CppAssetManager2(); - cppAssetManagerRef = Registries.NATIVE_ASSET_MANAGER_REGISTRY.register(appAssetManager); - } else { + if (inResourcesGetSystem) { if (systemCppAssetManager2 == null) { systemCppAssetManager2 = new CppAssetManager2(); systemCppAssetManager2Ref = Registries.NATIVE_ASSET_MANAGER_REGISTRY.register(systemCppAssetManager2); } - cppAssetManagerRef = systemCppAssetManager2Ref; + + } else { + CppAssetManager2 appAssetManager = new CppAssetManager2(); + cppAssetManagerRef = Registries.NATIVE_ASSET_MANAGER_REGISTRY.register(appAssetManager); } return cppAssetManagerRef; @@ -517,7 +523,7 @@ public class ShadowArscAssetManager10 extends ShadowAssetManager.ArscBase { // static void NativeSetApkAssets(JNIEnv* env, jclass /*clazz*/, jlong ptr, // jobjectArray apk_assets_array, jboolean invalidate_caches) { - @Implementation(minSdk = P) + @Implementation(minSdk = P, maxSdk = U.SDK_INT) protected static void nativeSetApkAssets( long ptr, @NonNull android.content.res.ApkAssets[] apk_assets_array, diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowArscAssetManager9.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowArscAssetManager9.java index 36d40a8ed..9bbe660c7 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowArscAssetManager9.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowArscAssetManager9.java @@ -20,7 +20,6 @@ import static org.robolectric.res.android.Util.CHECK; import static org.robolectric.res.android.Util.JNI_FALSE; import static org.robolectric.res.android.Util.JNI_TRUE; import static org.robolectric.res.android.Util.isTruthy; -import static org.robolectric.shadow.api.Shadow.invokeConstructor; import static org.robolectric.util.reflector.Reflector.reflector; import android.annotation.AnyRes; @@ -37,6 +36,7 @@ import android.os.Build; import android.os.ParcelFileDescriptor; import android.util.SparseArray; import android.util.TypedValue; +import com.google.common.annotations.VisibleForTesting; import dalvik.system.VMRuntime; import java.io.File; import java.io.FileDescriptor; @@ -75,10 +75,11 @@ import org.robolectric.res.android.ResourceTypes.Res_value; import org.robolectric.shadow.api.Shadow; import org.robolectric.util.PerfStatsCollector; import org.robolectric.util.ReflectionHelpers; -import org.robolectric.util.ReflectionHelpers.ClassParameter; import org.robolectric.util.reflector.Direct; import org.robolectric.util.reflector.ForType; import org.robolectric.util.reflector.Static; +import org.robolectric.versioning.AndroidVersions.U; + // transliterated from // https://android.googlesource.com/platform/frameworks/base/+/android-9.0.0_r12/core/jni/android_util_AssetManager.cpp @@ -99,7 +100,7 @@ public class ShadowArscAssetManager9 extends ShadowAssetManager.ArscBase { private static CppAssetManager2 systemCppAssetManager2; private static long systemCppAssetManager2Ref; - private static boolean inNonSystemConstructor; + private static boolean inResourcesGetSystem; @RealObject AssetManager realAssetManager; @@ -223,6 +224,12 @@ public class ShadowArscAssetManager9 extends ShadowAssetManager.ArscBase { return ApkAssetsCookie.forInt(cookie > 0 ? (cookie - 1) : kInvalidCookie); } + @VisibleForTesting + @Override + long getNativePtr() { + return reflector(_AssetManager_.class, realAssetManager).getNativePtr(); + } + // This is called by zygote (running as user root) as part of preloadResources. // static void NativeVerifySystemIdmaps(JNIEnv* /*env*/, jclass /*clazz*/) { @Implementation(minSdk = P, maxSdk = Q) @@ -434,16 +441,15 @@ public class ShadowArscAssetManager9 extends ShadowAssetManager.ArscBase { return ParcelFileDescriptor.open(asset.getFile(), ParcelFileDescriptor.MODE_READ_ONLY); } - /** Used for the creation of system assets. */ @Implementation(minSdk = P) - protected void __constructor__(boolean sentinel) { - inNonSystemConstructor = true; + protected static AssetManager getSystem() { + // The Android code of AssetManager.getSystem is locked on a static variable, so there is not + // a concurrency concern here. + inResourcesGetSystem = true; try { - // call real constructor so field initialization happens. - invokeConstructor( - AssetManager.class, realAssetManager, ClassParameter.from(boolean.class, sentinel)); + return reflector(_AssetManager_.class).getSystem(); } finally { - inNonSystemConstructor = false; + inResourcesGetSystem = false; } } @@ -481,10 +487,8 @@ public class ShadowArscAssetManager9 extends ShadowAssetManager.ArscBase { long cppAssetManagerRef; // we want to share a single instance of the system CppAssetManager2 - if (inNonSystemConstructor) { - CppAssetManager2 appAssetManager = new CppAssetManager2(); - cppAssetManagerRef = Registries.NATIVE_ASSET_MANAGER_REGISTRY.register(appAssetManager); - } else { + + if (inResourcesGetSystem) { if (systemCppAssetManager2 == null) { systemCppAssetManager2 = new CppAssetManager2(); systemCppAssetManager2Ref = @@ -492,6 +496,9 @@ public class ShadowArscAssetManager9 extends ShadowAssetManager.ArscBase { } cppAssetManagerRef = systemCppAssetManager2Ref; + } else { + CppAssetManager2 appAssetManager = new CppAssetManager2(); + cppAssetManagerRef = Registries.NATIVE_ASSET_MANAGER_REGISTRY.register(appAssetManager); } return cppAssetManagerRef; @@ -511,7 +518,7 @@ public class ShadowArscAssetManager9 extends ShadowAssetManager.ArscBase { // static void NativeSetApkAssets(JNIEnv* env, jclass /*clazz*/, jlong ptr, // jobjectArray apk_assets_array, jboolean invalidate_caches) { - @Implementation(minSdk = P) + @Implementation(minSdk = P, maxSdk = U.SDK_INT) protected static void nativeSetApkAssets( long ptr, @NonNull android.content.res.ApkAssets[] apk_assets_array, diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAssetManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAssetManager.java index 19c5196f0..37ba3ec4f 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAssetManager.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAssetManager.java @@ -1,8 +1,10 @@ package org.robolectric.shadows; + import android.content.res.ApkAssets; import android.content.res.AssetManager; import android.util.ArraySet; +import com.google.common.annotations.VisibleForTesting; import java.nio.file.Path; import java.util.Collection; import java.util.List; @@ -13,6 +15,7 @@ import org.robolectric.res.android.ResTable; import org.robolectric.res.android.String8; import org.robolectric.shadow.api.Shadow; import org.robolectric.util.reflector.Accessor; +import org.robolectric.util.reflector.Direct; import org.robolectric.util.reflector.ForType; import org.robolectric.util.reflector.Static; @@ -48,6 +51,9 @@ abstract public class ShadowAssetManager { abstract Collection<Path> getAllAssetDirs(); + @VisibleForTesting + abstract long getNativePtr(); + public abstract static class ArscBase extends ShadowAssetManager { private ResTable compileTimeResTable; @@ -78,12 +84,16 @@ abstract public class ShadowAssetManager { /** Accessor interface for {@link AssetManager}'s internals. */ @ForType(AssetManager.class) interface _AssetManager_ { - - @Static @Accessor("sSystem") + @Direct + @Static AssetManager getSystem(); - @Static @Accessor("sSystem") + @Static + @Accessor("sSystem") void setSystem(AssetManager o); + + @Accessor("mObject") + long getNativePtr(); } /** Accessor interface for {@link AssetManager}'s internals added in API level 28. */ diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAudioEffect.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAudioEffect.java index a0997fd79..f98a8e875 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAudioEffect.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAudioEffect.java @@ -38,7 +38,7 @@ public class ShadowAudioEffect { private boolean isEnabled = false; private int errorCode = SUCCESS; - @Implementation(minSdk = VERSION_CODES.JELLY_BEAN, maxSdk = VERSION_CODES.LOLLIPOP_MR1) + @Implementation(maxSdk = VERSION_CODES.LOLLIPOP_MR1) protected int native_setup( Object audioEffectThis, String type, diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAudioManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAudioManager.java index 0f4cb5f98..c2e7e21c9 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAudioManager.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAudioManager.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.KITKAT; import static android.os.Build.VERSION_CODES.LOLLIPOP; import static android.os.Build.VERSION_CODES.M; import static android.os.Build.VERSION_CODES.N; @@ -9,6 +8,8 @@ import static android.os.Build.VERSION_CODES.P; import static android.os.Build.VERSION_CODES.Q; import static android.os.Build.VERSION_CODES.R; import static android.os.Build.VERSION_CODES.S; +import static android.os.Build.VERSION_CODES.TIRAMISU; +import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE; import static org.robolectric.util.ReflectionHelpers.ClassParameter.from; import static org.robolectric.util.reflector.Reflector.reflector; @@ -21,6 +22,7 @@ import android.media.AudioDeviceInfo; import android.media.AudioFormat; import android.media.AudioManager; import android.media.AudioPlaybackConfiguration; +import android.media.AudioProfile; import android.media.AudioRecordingConfiguration; import android.media.IPlayer; import android.media.PlayerBase; @@ -31,6 +33,7 @@ import android.os.Parcel; import android.view.KeyEvent; import com.android.internal.util.Preconditions; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -65,7 +68,8 @@ public class ShadowAudioManager { AudioManager.STREAM_RING, AudioManager.STREAM_SYSTEM, AudioManager.STREAM_VOICE_CALL, - AudioManager.STREAM_DTMF); + AudioManager.STREAM_DTMF, + AudioManager.STREAM_ACCESSIBILITY); private static final int INVALID_PATCH_HANDLE = -1; private static final float MAX_VOLUME_DB = 0; @@ -85,6 +89,7 @@ public class ShadowAudioManager { private final HashSet<AudioDeviceCallback> audioDeviceCallbacks = new HashSet<>(); private int ringerMode = AudioManager.RINGER_MODE_NORMAL; private int mode = AudioManager.MODE_NORMAL; + private boolean lockMode = false; private boolean bluetoothA2dpOn; private boolean isBluetoothScoOn; private boolean isSpeakerphoneOn; @@ -97,12 +102,18 @@ public class ShadowAudioManager { private final Map<String, AudioPolicy> registeredAudioPolicies = new HashMap<>(); private int audioSessionIdCounter = 1; private final Map<AudioAttributes, ImmutableList<Object>> devicesForAttributes = new HashMap<>(); + private final List<AudioDeviceInfo> outputDevicesWithDirectProfiles = new ArrayList<>(); private ImmutableList<Object> defaultDevicesForAttributes = ImmutableList.of(); + private final Map<AudioAttributes, ImmutableList<AudioDeviceInfo>> audioDevicesForAttributes = + new HashMap<>(); private List<AudioDeviceInfo> inputDevices = new ArrayList<>(); private List<AudioDeviceInfo> outputDevices = new ArrayList<>(); private List<AudioDeviceInfo> availableCommunicationDevices = new ArrayList<>(); private AudioDeviceInfo communicationDevice = null; + private boolean lockCommunicationDevice = false; private final List<KeyEvent> dispatchedMediaKeyEvents = new ArrayList<>(); + private boolean isHotwordStreamSupportedForLookbackAudio = false; + private boolean isHotwordStreamSupportedWithoutLookbackAudio = false; public ShadowAudioManager() { for (int stream : ALL_STREAMS) { @@ -208,8 +219,12 @@ public class ShadowAudioManager { <= (int) ReflectionHelpers.getStaticField(AudioManager.class, "RINGER_MODE_MAX"); } + /** Note that this method can silently fail. See {@link lockMode}. */ @Implementation protected void setMode(int mode) { + if (lockMode) { + return; + } int previousMode = this.mode; this.mode = mode; if (RuntimeEnvironment.getApiLevel() >= S && mode != previousMode) { @@ -224,6 +239,11 @@ public class ShadowAudioManager { .dispatchAudioModeChanged(newMode); } + /** Sets whether subsequent calls to {@link setMode} will succeed or not. */ + public void lockMode(boolean lockMode) { + this.lockMode = lockMode; + } + @Implementation protected int getMode() { return this.mode; @@ -236,6 +256,7 @@ public class ShadowAudioManager { void dispatchAudioModeChanged(int newMode); } + public void setStreamMaxVolume(int streamMaxVolume) { streamStatus.forEach((key, value) -> value.setMaxVolume(streamMaxVolume)); } @@ -448,6 +469,27 @@ public class ShadowAudioManager { } /** + * Returns the audio devices that would be used for the routing of the given audio attributes. + * + * <p>Devices can be added with {@link #setAudioDevicesForAttributes}. Note that {@link + * #setDevicesForAttributes} and {@link #setDefaultDevicesForAttributes} have no effect on the + * return value of this method. + */ + @Implementation(minSdk = TIRAMISU) + @NonNull + protected List<AudioDeviceInfo> getAudioDevicesForAttributes( + @NonNull AudioAttributes attributes) { + ImmutableList<AudioDeviceInfo> devices = audioDevicesForAttributes.get(attributes); + return devices == null ? ImmutableList.of() : devices; + } + + /** Sets the audio devices returned from {@link #getAudioDevicesForAttributes}. */ + public void setAudioDevicesForAttributes( + @NonNull AudioAttributes attributes, @NonNull ImmutableList<AudioDeviceInfo> devices) { + audioDevicesForAttributes.put(attributes, devices); + } + + /** * Sets the list of connected input devices represented by {@link AudioDeviceInfo}. * * <p>The previous list of input devices is replaced and no notifications of the list of {@link @@ -582,6 +624,8 @@ public class ShadowAudioManager { * @see #removeInputDevice(AudioDeviceInfo, boolean) * @see #removeOutputDevice(AudioDeviceInfo, boolean) * @see #removeAvailableCommunicationDevice(AudioDeviceInfo, boolean) + * @see #addOutputDeviceWithDirectProfiles(AudioDeviceInfo) + * @see #removeOutputDeviceWithDirectProfiles(AudioDeviceInfo) */ @Implementation(minSdk = M) protected void registerAudioDeviceCallback(AudioDeviceCallback callback, Handler handler) { @@ -600,6 +644,8 @@ public class ShadowAudioManager { * @see #removeInputDevice(AudioDeviceInfo, boolean) * @see #removeOutputDevice(AudioDeviceInfo, boolean) * @see #removeAvailableCommunicationDevice(AudioDeviceInfo, boolean) + * @see #addOutputDeviceWithDirectProfiles(AudioDeviceInfo) + * @see #removeOutputDeviceWithDirectProfiles(AudioDeviceInfo) */ @Implementation(minSdk = M) protected void unregisterAudioDeviceCallback(AudioDeviceCallback callback) { @@ -625,10 +671,18 @@ public class ShadowAudioManager { return outputDevices; } + /** Note that this method can silently fail. See {@link lockCommunicationDevice}. */ @Implementation(minSdk = S) protected boolean setCommunicationDevice(AudioDeviceInfo communicationDevice) { - this.communicationDevice = communicationDevice; - return true; + if (!lockCommunicationDevice) { + this.communicationDevice = communicationDevice; + } + return !lockCommunicationDevice; + } + + /** Sets whether subsequent calls to {@link setCommunicationDevice} will succeed. */ + public void lockCommunicationDevice(boolean lockCommunicationDevice) { + this.lockCommunicationDevice = lockCommunicationDevice; } @Implementation(minSdk = S) @@ -646,6 +700,22 @@ public class ShadowAudioManager { return availableCommunicationDevices; } + @Implementation(minSdk = UPSIDE_DOWN_CAKE) + protected boolean isHotwordStreamSupported(boolean lookbackAudio) { + if (lookbackAudio) { + return isHotwordStreamSupportedForLookbackAudio; + } + return isHotwordStreamSupportedWithoutLookbackAudio; + } + + public void setHotwordStreamSupported(boolean lookbackAudio, boolean isSupported) { + if (lookbackAudio) { + isHotwordStreamSupportedForLookbackAudio = isSupported; + } else { + isHotwordStreamSupportedWithoutLookbackAudio = isSupported; + } + } + @Implementation(minSdk = M) public AudioDeviceInfo[] getDevices(int flags) { List<AudioDeviceInfo> result = new ArrayList<>(); @@ -862,6 +932,47 @@ public class ShadowAudioManager { } /** + * Returns the list of profiles supported for direct playback. + * + * <p>In this shadow-implementation the list returned are profiles set through {@link + * #addOutputDeviceWithDirectProfiles(AudioDeviceInfo)}, {@link + * #removeOutputDeviceWithDirectProfiles(AudioDeviceInfo)}. + */ + @Implementation(minSdk = TIRAMISU) + @NonNull + protected List<AudioProfile> getDirectProfilesForAttributes(@NonNull AudioAttributes attributes) { + ImmutableSet.Builder<AudioProfile> audioProfiles = new ImmutableSet.Builder<>(); + for (int i = 0; i < outputDevicesWithDirectProfiles.size(); i++) { + audioProfiles.addAll(outputDevicesWithDirectProfiles.get(i).getAudioProfiles()); + } + return new ArrayList<>(audioProfiles.build()); + } + + /** + * Adds an output {@link AudioDeviceInfo device} with direct profiles and notifies the list of + * {@link AudioDeviceCallback} if the device was not present before. + */ + public void addOutputDeviceWithDirectProfiles(AudioDeviceInfo outputDevice) { + boolean changed = + !this.outputDevicesWithDirectProfiles.contains(outputDevice) + && this.outputDevicesWithDirectProfiles.add(outputDevice); + if (changed) { + notifyAudioDeviceCallbacks(ImmutableList.of(outputDevice), /* added= */ true); + } + } + + /** + * Removes an output {@link AudioDeviceInfo device} with direct profiles and notifies the list of + * {@link AudioDeviceCallback} if the device was present before. + */ + public void removeOutputDeviceWithDirectProfiles(AudioDeviceInfo outputDevice) { + boolean changed = this.outputDevicesWithDirectProfiles.remove(outputDevice); + if (changed) { + notifyAudioDeviceCallbacks(ImmutableList.of(outputDevice), /* added= */ false); + } + } + + /** * Provides a mock like interface for the {@link AudioManager#generateAudioSessionId} method by * returning positive distinct values, or {@link AudioManager#ERROR} if all possible values have * already been returned. @@ -897,7 +1008,7 @@ public class ShadowAudioManager { * routed to a media app, this shadow method only records the events to be verified through {@link * #getDispatchedMediaKeyEvents()}. */ - @Implementation(minSdk = KITKAT) + @Implementation protected void dispatchMediaKeyEvent(KeyEvent keyEvent) { if (keyEvent == null) { throw new NullPointerException("keyEvent is null"); diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAudioTrack.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAudioTrack.java index ecf5f16ea..14be26230 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAudioTrack.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAudioTrack.java @@ -16,12 +16,17 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.RequiresApi; import android.media.AudioAttributes; +import android.media.AudioDeviceInfo; import android.media.AudioFormat; +import android.media.AudioRouting.OnRoutingChangedListener; import android.media.AudioTrack; import android.media.AudioTrack.WriteMode; import android.media.PlaybackParams; import android.os.Build.VERSION; +import android.os.Handler; import android.os.Parcel; import android.util.Log; import com.google.common.collect.HashMultimap; @@ -31,8 +36,10 @@ import java.nio.ByteBuffer; import java.util.Collections; import java.util.HashSet; import java.util.List; +import java.util.Objects; import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.CopyOnWriteArraySet; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; import org.robolectric.annotation.RealObject; @@ -80,6 +87,9 @@ public class ShadowAudioTrack { private static final Set<Integer> allowedNonPcmEncodings = Collections.synchronizedSet(new HashSet<>()); + private static AudioDeviceInfo routedDevice; + private static final Set<OnRoutingChangedListenerInfo> onRoutingChangedListeners = + new CopyOnWriteArraySet<>(); private static final List<OnAudioDataWrittenListener> audioDataWrittenListeners = new CopyOnWriteArrayList<>(); private static int minBufferSize = DEFAULT_MIN_BUFFER_SIZE; @@ -153,6 +163,25 @@ public class ShadowAudioTrack { allowedNonPcmEncodings.clear(); } + /** + * Sets the routed device returned from {@link AudioTrack#getRoutedDevice()} and informs all + * registered {@link OnRoutingChangedListener}. + * + * <p>Note that this affects the routed device for all {@link AudioTrack} instances. + * + * @param routedDevice The route device, or null to reset it to unknown. + */ + @RequiresApi(N) + public static void setRoutedDevice(@Nullable AudioDeviceInfo routedDevice) { + if (Objects.equals(routedDevice, ShadowAudioTrack.routedDevice)) { + return; + } + ShadowAudioTrack.routedDevice = routedDevice; + for (OnRoutingChangedListenerInfo listenerInfo : onRoutingChangedListeners) { + listenerInfo.callListener(); + } + } + @Implementation(minSdk = N, maxSdk = P) protected static int native_get_FCC_8() { // Return the value hard-coded in native code: @@ -289,6 +318,28 @@ public class ShadowAudioTrack { return sizeInBytes; } + @Implementation(minSdk = N) + protected AudioDeviceInfo getRoutedDevice() { + return routedDevice; + } + + @Implementation(minSdk = N) + protected void addOnRoutingChangedListener( + @NonNull OnRoutingChangedListener listener, Handler handler) { + OnRoutingChangedListenerInfo listenerInfo = + new OnRoutingChangedListenerInfo(listener, audioTrack, handler); + onRoutingChangedListeners.add(listenerInfo); + if (routedDevice != null) { + listenerInfo.callListener(); + } + } + + @Implementation(minSdk = N) + protected void removeOnRoutingChangedListener(@NonNull OnRoutingChangedListener listener) { + onRoutingChangedListeners.removeIf( + registeredListener -> registeredListener.listener.equals(listener)); + } + @Implementation(minSdk = M) public void setPlaybackParams(@NonNull PlaybackParams params) { playbackParams = checkNotNull(params, "Illegal null params"); @@ -369,6 +420,7 @@ public class ShadowAudioTrack { audioDataWrittenListeners.clear(); clearDirectPlaybackSupportedFormats(); clearAllowedNonPcmEncodings(); + routedDevice = null; } private static boolean isPcm(int encoding) { @@ -466,4 +518,21 @@ public class ShadowAudioTrack { return result; } } + + private static final class OnRoutingChangedListenerInfo { + private final OnRoutingChangedListener listener; + private final AudioTrack audioTrack; + private final Handler handler; + + public OnRoutingChangedListenerInfo( + OnRoutingChangedListener listener, AudioTrack audioTrack, Handler handler) { + this.listener = listener; + this.audioTrack = audioTrack; + this.handler = handler; + } + + public void callListener() { + handler.post(() -> listener.onRoutingChanged(audioTrack)); + } + } } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAutofillManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAutofillManager.java index fea9f9e26..1a14a1d09 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAutofillManager.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAutofillManager.java @@ -3,10 +3,10 @@ package org.robolectric.shadows; import static android.os.Build.VERSION_CODES.O; import static android.os.Build.VERSION_CODES.P; +import android.annotation.Nullable; import android.content.ComponentName; import android.service.autofill.FillEventHistory; import android.view.autofill.AutofillManager; -import androidx.annotation.Nullable; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBackgroundThread.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBackgroundThread.java index 90e81ace2..2273a03e5 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBackgroundThread.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBackgroundThread.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.KITKAT; import static org.robolectric.util.reflector.Reflector.reflector; import android.os.Handler; @@ -11,7 +10,7 @@ import org.robolectric.util.reflector.Accessor; import org.robolectric.util.reflector.ForType; import org.robolectric.util.reflector.Static; -@Implements(value = BackgroundThread.class, isInAndroidSdk = false, minSdk = KITKAT) +@Implements(value = BackgroundThread.class, isInAndroidSdk = false) public class ShadowBackgroundThread { @Resetter diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBackupDataOutput.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBackupDataOutput.java index 9c48306f2..b82f7c32c 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBackupDataOutput.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBackupDataOutput.java @@ -1,8 +1,8 @@ package org.robolectric.shadows; +import android.annotation.Nullable; import android.app.backup.BackupDataOutput; import android.os.Build.VERSION_CODES; -import androidx.annotation.Nullable; import com.google.common.collect.ImmutableList; import java.io.FileDescriptor; import java.util.ArrayList; diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBackupManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBackupManager.java index 42195f5eb..f0efc26a4 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBackupManager.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBackupManager.java @@ -4,6 +4,7 @@ import static android.os.Build.VERSION_CODES.LOLLIPOP; import static android.os.Build.VERSION_CODES.M; import android.app.backup.BackupManager; +import android.app.backup.BackupTransport; import android.app.backup.IBackupManagerMonitor; import android.app.backup.IRestoreObserver; import android.app.backup.IRestoreSession; @@ -14,6 +15,8 @@ import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.RemoteException; +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.ListMultimap; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; @@ -33,8 +36,9 @@ import org.robolectric.util.ReflectionHelpers.ClassParameter; /** * A stub implementation of {@link BackupManager} that instead of connecting to a real backup - * transport and performing restores, stores which packages are restored from which backup set, and - * can be verified using methods on the shadow like {@link #getPackageRestoreToken(String)}. + * transport and performing restores, stores which packages are restored from which backup set, what + * the final result should be and can be verified using methods on the shadow like {@link + * #getPackageRestoreToken(String)} and {@link #getPackageRestoreCount(String)}. */ @Implements(BackupManager.class) public class ShadowBackupManager { @@ -103,16 +107,27 @@ public class ShadowBackupManager { } /** - * Returns the restore token for the given package, or {@code 0} if the package was not restored. + * Returns the last recorded restore token for the given package, or {@code 0} if the package was + * not restored. */ public long getPackageRestoreToken(String packageName) { - Long token = serviceState.restoredPackages.get(packageName); - return token != null ? token : 0L; + List<Long> result = serviceState.restoredPackages.get(packageName); + return result.isEmpty() ? 0L : result.get(result.size() - 1); } - /** Adds a restore set available to be restored. */ + /** Returns the number of recorded restores for the given package. */ + public int getPackageRestoreCount(String packageName) { + return serviceState.restoredPackages.get(packageName).size(); + } + + /** Adds a restore set available to be restored successfully. */ public void addAvailableRestoreSets(long restoreToken, List<String> packages) { - serviceState.restoreData.put(restoreToken, packages); + addAvailableRestoreSets(restoreToken, packages, BackupTransport.TRANSPORT_OK); + } + + /** Adds a restore set available to be restored and the final result of the restore session. */ + public void addAvailableRestoreSets(long restoreToken, List<String> packages, int result) { + serviceState.restoreData.put(restoreToken, new RestoreData(packages, result)); } private void enforceBackupPermission(String message) { @@ -137,7 +152,7 @@ public class ShadowBackupManager { for (long token : restoreTokens) { restoreSets.add(new RestoreSet("RestoreSet-" + token, "device", token)); } - observer.restoreSetsAvailable(restoreSets.toArray(new RestoreSet[restoreSets.size()])); + observer.restoreSetsAvailable(restoreSets.toArray(new RestoreSet[0])); }); return BackupManager.SUCCESS; } @@ -166,10 +181,12 @@ public class ShadowBackupManager { return restorePackages(token, observer, packages, monitor); } + @Override public int restorePackages( long token, IRestoreObserver observer, String[] packages, IBackupManagerMonitor monitor) throws RemoteException { - List<String> restorePackages = new ArrayList<>(serviceState.restoreData.get(token)); + RestoreData restoreData = serviceState.takeRestoreData(token); + List<String> restorePackages = new ArrayList<>(restoreData.packages); if (packages != null) { restorePackages.retainAll(Arrays.asList(packages)); } @@ -179,7 +196,7 @@ public class ShadowBackupManager { post(() -> observer.onUpdate(index, restorePackages.get(index))); serviceState.restoredPackages.put(restorePackages.get(index), token); } - post(() -> observer.restoreFinished(BackupManager.SUCCESS)); + post(() -> observer.restoreFinished(restoreData.result)); serviceState.lastRestoreToken = token; return BackupManager.SUCCESS; } @@ -197,14 +214,15 @@ public class ShadowBackupManager { if (serviceState.lastRestoreToken == 0L) { return -1; } - List<String> restorePackages = serviceState.restoreData.get(serviceState.lastRestoreToken); + RestoreData restoreData = serviceState.takeRestoreData(serviceState.lastRestoreToken); + List<String> restorePackages = new ArrayList<>(restoreData.packages); if (!restorePackages.contains(packageName)) { return BackupManager.ERROR_PACKAGE_NOT_FOUND; } post(() -> observer.restoreStarting(1)); post(() -> observer.onUpdate(0, packageName)); serviceState.restoredPackages.put(packageName, serviceState.lastRestoreToken); - post(() -> observer.restoreFinished(BackupManager.SUCCESS)); + post(() -> observer.restoreFinished(restoreData.result)); return BackupManager.SUCCESS; } @@ -235,8 +253,34 @@ public class ShadowBackupManager { boolean backupEnabled = true; long lastRestoreToken = 0L; final Map<String, Integer> dataChangedCount = new HashMap<>(); - final Map<Long, List<String>> restoreData = new HashMap<>(); - final Map<String, Long> restoredPackages = new HashMap<>(); + final ListMultimap<Long, RestoreData> restoreData = ArrayListMultimap.create(); + final ListMultimap<String, Long> restoredPackages = ArrayListMultimap.create(); + + /** + * Returns the first {@link RestoreData} matching the given token and removes it from the + * existing restore data if it's not the last one. + */ + RestoreData takeRestoreData(long token) { + List<RestoreData> results = restoreData.get(token); + if (results.isEmpty()) { + return new RestoreData(new ArrayList<>(), -1); + } + RestoreData data = results.get(0); + if (results.size() > 1) { + restoreData.remove(token, data); + } + return data; + } + } + + private static class RestoreData { + final List<String> packages; + final int result; + + public RestoreData(List<String> packages, int result) { + this.packages = packages; + this.result = result; + } } private interface RemoteRunnable { diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBinder.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBinder.java index bb0cd86f3..e62796b32 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBinder.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBinder.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; import static android.os.Build.VERSION_CODES.Q; import android.os.Binder; @@ -80,7 +79,7 @@ public class ShadowBinder { throw new IllegalStateException("Thread is not in a binder transcation"); } - @Implementation(minSdk = JELLY_BEAN_MR1) + @Implementation protected static UserHandle getCallingUserHandle() { if (callingUserHandle != null) { return callingUserHandle; diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBitmap.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBitmap.java index ca55a0657..931be2006 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBitmap.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBitmap.java @@ -20,7 +20,7 @@ import org.robolectric.versioning.AndroidVersions.U; @Implements(value = Bitmap.class, shadowPicker = Picker.class, looseSignatures = true) public abstract class ShadowBitmap { - @RealObject Bitmap realBitmap; + @RealObject protected Bitmap realBitmap; /** * Returns a textual representation of the appearance of the object. diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBitmapFactory.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBitmapFactory.java index fd2c084a5..207228df3 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBitmapFactory.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBitmapFactory.java @@ -12,7 +12,6 @@ import android.graphics.BitmapFactory; import android.graphics.Point; import android.graphics.Rect; import android.net.Uri; -import android.os.Build; import android.util.TypedValue; import java.awt.Graphics2D; import java.awt.image.BufferedImage; @@ -268,10 +267,8 @@ public class ShadowBitmapFactory { p.y = p.y == 0 ? 1 : p.y; } - // Prior to KitKat the density scale will be applied by finishDecode below. float scale = - RuntimeEnvironment.getApiLevel() >= Build.VERSION_CODES.KITKAT - && options != null + options != null && options.inScaled && options.inDensity != 0 && options.inTargetDensity != 0 @@ -301,21 +298,11 @@ public class ShadowBitmapFactory { shadowBitmap.setMutable(options.inMutable); } - if (RuntimeEnvironment.getApiLevel() >= Build.VERSION_CODES.KITKAT) { - ReflectionHelpers.callStaticMethod( - BitmapFactory.class, - "setDensityFromOptions", - ClassParameter.from(Bitmap.class, bitmap), - ClassParameter.from(BitmapFactory.Options.class, options)); - } else { - bitmap = - ReflectionHelpers.callStaticMethod( - BitmapFactory.class, - "finishDecode", - ClassParameter.from(Bitmap.class, bitmap), - ClassParameter.from(Rect.class, outPadding), - ClassParameter.from(BitmapFactory.Options.class, options)); - } + ReflectionHelpers.callStaticMethod( + BitmapFactory.class, + "setDensityFromOptions", + ClassParameter.from(Bitmap.class, bitmap), + ClassParameter.from(BitmapFactory.Options.class, options)); return bitmap; } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothA2dp.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothA2dp.java index 101fee72e..77fd608d8 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothA2dp.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothA2dp.java @@ -1,13 +1,19 @@ package org.robolectric.shadows; import static android.os.Build.VERSION_CODES.P; +import static android.os.Build.VERSION_CODES.R; import static android.os.Build.VERSION_CODES.S; +import static android.os.Build.VERSION_CODES.TIRAMISU; +import static org.robolectric.util.reflector.Reflector.reflector; import android.bluetooth.BluetoothA2dp; +import android.bluetooth.BluetoothA2dp.OptionalCodecsPreferenceStatus; import android.bluetooth.BluetoothCodecConfig; +import android.bluetooth.BluetoothCodecStatus; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothProfile; import android.content.Intent; +import android.util.Log; import com.google.common.collect.ImmutableList; import java.util.ArrayList; import java.util.HashMap; @@ -17,11 +23,22 @@ import javax.annotation.Nullable; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; +import org.robolectric.annotation.RealObject; +import org.robolectric.util.reflector.Direct; +import org.robolectric.util.reflector.ForType; /** Shadow of {@link BluetoothA2dp}. */ @Implements(BluetoothA2dp.class) public class ShadowBluetoothA2dp { + private static final String TAG = "BluetoothA2dp"; + + @RealObject protected BluetoothA2dp realObject; + private final Map<BluetoothDevice, Integer> bluetoothDevices = new HashMap<>(); + private final Map<BluetoothDevice, BluetoothCodecStatus> codecStatusMap = new HashMap<>(); + private final Map<BluetoothDevice, BluetoothCodecConfig> codecConfigPreferenceMap = + new HashMap<>(); + private final Map<BluetoothDevice, Integer> optionalCodecPreferenceStatusMap = new HashMap<>(); private int dynamicBufferSupportType = BluetoothA2dp.DYNAMIC_BUFFER_SUPPORT_NONE; private final int[] bufferLengthMillisArray = new int[6]; private BluetoothDevice activeBluetoothDevice; @@ -129,4 +146,60 @@ public class ShadowBluetoothA2dp { RuntimeEnvironment.getApplication().sendBroadcast(intent); return true; } + + @Implementation(minSdk = TIRAMISU) + @Nullable + protected BluetoothCodecStatus getCodecStatus(BluetoothDevice device) { + return codecStatusMap.get(device); + } + + public void setCodecStatus(BluetoothDevice device, BluetoothCodecStatus codecStatus) { + codecStatusMap.put(device, codecStatus); + } + + @Nullable + public BluetoothCodecConfig getCodecConfigPreference(BluetoothDevice device) { + return codecConfigPreferenceMap.get(device); + } + + @Implementation(minSdk = TIRAMISU) + protected void setCodecConfigPreference( + BluetoothDevice device, BluetoothCodecConfig codecConfig) { + codecConfigPreferenceMap.put(device, codecConfig); + } + + @Implementation(minSdk = R) + @OptionalCodecsPreferenceStatus + protected int isOptionalCodecsEnabled(BluetoothDevice device) { + verifyDeviceNotNull(device, "isOptionalCodecsEnabled"); + if (optionalCodecPreferenceStatusMap.containsKey(device)) { + return optionalCodecPreferenceStatusMap.get(device); + } else { + return BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN; + } + } + + @Implementation(minSdk = R) + protected void setOptionalCodecsEnabled( + BluetoothDevice device, @OptionalCodecsPreferenceStatus int value) { + verifyDeviceNotNull(device, "setOptionalCodecsEnabled"); + if (value != BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN + && value != BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED + && value != BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED) { + Log.e(TAG, "Invalid value passed to setOptionalCodecsEnabled: " + value); + return; + } + optionalCodecPreferenceStatusMap.put(device, value); + } + + @ForType(BluetoothA2dp.class) + private interface BluetoothA2dpReflector { + @Direct + void verifyDeviceNotNull(BluetoothDevice device, String methodName); + } + + @Implementation(minSdk = R) + protected void verifyDeviceNotNull(BluetoothDevice device, String methodName) { + reflector(BluetoothA2dpReflector.class, realObject).verifyDeviceNotNull(device, methodName); + } } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothAdapter.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothAdapter.java index 5e89a77b6..bff4fe2b7 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothAdapter.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothAdapter.java @@ -1,8 +1,6 @@ package org.robolectric.shadows; import static android.bluetooth.BluetoothAdapter.STATE_ON; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2; import static android.os.Build.VERSION_CODES.LOLLIPOP; import static android.os.Build.VERSION_CODES.M; import static android.os.Build.VERSION_CODES.O; @@ -10,6 +8,7 @@ import static android.os.Build.VERSION_CODES.Q; import static android.os.Build.VERSION_CODES.R; import static android.os.Build.VERSION_CODES.S_V2; import static android.os.Build.VERSION_CODES.TIRAMISU; +import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE; import static org.robolectric.Shadows.shadowOf; import static org.robolectric.util.reflector.Reflector.reflector; @@ -101,7 +100,10 @@ public class ShadowBluetoothAdapter { private boolean isBleScanAlwaysAvailable = true; private boolean isMultipleAdvertisementSupported = true; private int isLeAudioSupported = BluetoothStatusCodes.FEATURE_NOT_SUPPORTED; + private int isDistanceMeasurementSupported = BluetoothStatusCodes.FEATURE_NOT_SUPPORTED; private boolean isLeExtendedAdvertisingSupported = true; + private boolean isLeCodedPhySupported = true; + private boolean isLe2MPhySupported = true; private boolean isOverridingProxyBehavior; private final Map<Integer, Integer> profileConnectionStateData = new HashMap<>(); private final Map<Integer, BluetoothProfile> profileProxies = new HashMap<>(); @@ -159,6 +161,19 @@ public class ShadowBluetoothAdapter { } /** + * Sets whether the distance measurement is supported or not. Minimum sdk version required is + * UPSIDE_DOWN_CAKE. + */ + public void setDistanceMeasurementSupported(int supported) { + isDistanceMeasurementSupported = supported; + } + + @Implementation(minSdk = VERSION_CODES.UPSIDE_DOWN_CAKE) + protected int isDistanceMeasurementSupported() { + return isDistanceMeasurementSupported; + } + + /** * @deprecated use real BluetoothLeAdvertiser instead */ @Deprecated @@ -190,11 +205,16 @@ public class ShadowBluetoothAdapter { } @Implementation + @Nullable protected Set<BluetoothDevice> getBondedDevices() { + // real android will return null in error conditions + if (bondedDevices == null) { + return null; + } return Collections.unmodifiableSet(bondedDevices); } - public void setBondedDevices(Set<BluetoothDevice> bluetoothDevices) { + public void setBondedDevices(@Nullable Set<BluetoothDevice> bluetoothDevices) { bondedDevices = bluetoothDevices; } @@ -277,12 +297,12 @@ public class ShadowBluetoothAdapter { != 0; } - @Implementation(minSdk = JELLY_BEAN_MR2) + @Implementation protected boolean startLeScan(LeScanCallback callback) { return startLeScan(null, callback); } - @Implementation(minSdk = JELLY_BEAN_MR2) + @Implementation protected boolean startLeScan(UUID[] serviceUuids, LeScanCallback callback) { if (Build.VERSION.SDK_INT >= M && !realAdapter.isLeEnabled()) { return false; @@ -293,7 +313,7 @@ public class ShadowBluetoothAdapter { return true; } - @Implementation(minSdk = JELLY_BEAN_MR2) + @Implementation protected void stopLeScan(LeScanCallback callback) { leScanCallbacks.remove(callback); } @@ -618,6 +638,28 @@ public class ShadowBluetoothAdapter { isLeExtendedAdvertisingSupported = supported; } + /** Returns the last value of {@link #setIsLeCodedPhySupported}, defaulting to true. */ + @Implementation(minSdk = UPSIDE_DOWN_CAKE) + protected boolean isLeCodedPhySupported() { + return isLeCodedPhySupported; + } + + /** Sets the {@link #isLeCodedPhySupported} to enable/disable LE coded phy supported featured. */ + public void setIsLeCodedPhySupported(boolean supported) { + isLeCodedPhySupported = supported; + } + + /** Returns the last value of {@link #setIsLe2MPhySupported}, defaulting to true. */ + @Implementation(minSdk = UPSIDE_DOWN_CAKE) + protected boolean isLe2MPhySupported() { + return isLe2MPhySupported; + } + + /** Sets the {@link #isLe2MPhySupported} to enable/disable LE 2M phy supported featured. */ + public void setIsLe2MPhySupported(boolean supported) { + isLe2MPhySupported = supported; + } + @Implementation(minSdk = O) protected int getLeMaximumAdvertisingDataLength() { return isLeExtendedAdvertisingSupported @@ -630,7 +672,7 @@ public class ShadowBluetoothAdapter { // PendingIntent#isImmutable throws an NPE if the component does not exist, so verify directly // against the flags for now. if ((shadowOf(pendingIntent).getFlags() & PendingIntent.FLAG_IMMUTABLE) == 0) { - throw new IllegalArgumentException("RFCOMM server PendingIntent must be marked immutable"); + throw new IllegalArgumentException("RFCOMM servers PendingIntent must be marked immutable"); } boolean[] isNewServerSocket = {false}; diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothDevice.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothDevice.java index d193b79b4..56b3be662 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothDevice.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothDevice.java @@ -2,7 +2,6 @@ package org.robolectric.shadows; import static android.bluetooth.BluetoothDevice.BOND_NONE; import static android.content.pm.PackageManager.PERMISSION_GRANTED; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2; import static android.os.Build.VERSION_CODES.KITKAT_WATCH; import static android.os.Build.VERSION_CODES.M; import static android.os.Build.VERSION_CODES.O; @@ -30,6 +29,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; +import javax.annotation.Nullable; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; @@ -44,6 +44,15 @@ import org.robolectric.util.reflector.Static; /** Shadow for {@link BluetoothDevice}. */ @Implements(value = BluetoothDevice.class, looseSignatures = true) public class ShadowBluetoothDevice { + /** + * Interceptor interface for {@link BluetoothGatt} objects. Tests that require configuration of + * their ShadowBluetoothGatt's may inject an interceptor, which will be called with the newly + * constructed BluetoothGatt before {@link ShadowBluetoothGatt#connectGatt} returns. + */ + public static interface BluetoothGattConnectionInterceptor { + public void onNewGattConnection(BluetoothGatt gatt); + } + @Deprecated // Prefer {@link android.bluetooth.BluetoothAdapter#getRemoteDevice} public static BluetoothDevice newInstance(String address) { return ReflectionHelpers.callConstructor( @@ -76,6 +85,7 @@ public class ShadowBluetoothDevice { private int batteryLevel = BluetoothDevice.BATTERY_LEVEL_BLUETOOTH_OFF; private boolean isInSilenceMode = false; private boolean isConnected = false; + @Nullable private BluetoothGattConnectionInterceptor bluetoothGattConnectionInterceptor = null; /** * Implements getService() in the same way the original method does, but ignores any Exceptions @@ -180,7 +190,7 @@ public class ShadowBluetoothDevice { * @return Value set by calling {@link ShadowBluetoothDevice#setType}. If setType has not * previously been called, will return BluetoothDevice.DEVICE_TYPE_UNKNOWN. */ - @Implementation(minSdk = JELLY_BEAN_MR2) + @Implementation protected int getType() { checkForBluetoothConnectPermission(); return type; @@ -319,7 +329,7 @@ public class ShadowBluetoothDevice { return fetchUuidsWithSdpCount; } - @Implementation(minSdk = JELLY_BEAN_MR2) + @Implementation protected BluetoothGatt connectGatt( Context context, boolean autoConnect, BluetoothGattCallback callback) { checkForBluetoothConnectPermission(); @@ -350,6 +360,11 @@ public class ShadowBluetoothDevice { bluetoothGatts.add(bluetoothGatt); ShadowBluetoothGatt shadowBluetoothGatt = Shadow.extract(bluetoothGatt); shadowBluetoothGatt.setGattCallback(callback); + + if (bluetoothGattConnectionInterceptor != null) { + bluetoothGattConnectionInterceptor.onNewGattConnection(bluetoothGatt); + } + return bluetoothGatt; } @@ -436,6 +451,15 @@ public class ShadowBluetoothDevice { return isInSilenceMode; } + /** + * Allows tests to intercept the {@link BluetoothDevice.connectGatt} method and set state on both + * BluetoothDevice and BluetoothGatt objects. This is useful for e2e testing situations where the + * fine-grained execution of Bluetooth connection logic is onerous. + */ + public void setGattConnectionInterceptor(BluetoothGattConnectionInterceptor interceptor) { + bluetoothGattConnectionInterceptor = interceptor; + } + @ForType(BluetoothDevice.class) interface BluetoothDeviceReflector { diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothGatt.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothGatt.java index 1dce5a3da..3935c8ff7 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothGatt.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothGatt.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2; import static android.os.Build.VERSION_CODES.LOLLIPOP; import static android.os.Build.VERSION_CODES.O; import static android.os.Build.VERSION_CODES.O_MR1; @@ -15,6 +14,7 @@ import android.bluetooth.BluetoothGattDescriptor; import android.bluetooth.BluetoothGattService; import android.bluetooth.BluetoothProfile; import android.content.Context; +import android.os.Build; import java.util.ArrayList; import java.util.HashSet; import java.util.List; @@ -32,7 +32,7 @@ import org.robolectric.util.reflector.Direct; import org.robolectric.util.reflector.ForType; /** Shadow implementation of {@link BluetoothGatt}. */ -@Implements(value = BluetoothGatt.class, minSdk = JELLY_BEAN_MR2) +@Implements(value = BluetoothGatt.class) public class ShadowBluetoothGatt { private static final String NULL_CALLBACK_MSG = "BluetoothGattCallback can not be null."; @@ -126,7 +126,7 @@ public class ShadowBluetoothGatt { * @return true, if a {@link BluetoothGattCallback} has been set by {@link * ShadowBluetoothGatt#setGattCallback} */ - @Implementation(minSdk = JELLY_BEAN_MR2) + @Implementation protected boolean connect() { if (this.getGattCallback() != null) { this.isConnected = true; @@ -141,7 +141,7 @@ public class ShadowBluetoothGatt { /** * Disconnects an established connection, or cancels a connection attempt currently in progress. */ - @Implementation(minSdk = JELLY_BEAN_MR2) + @Implementation protected void disconnect() { bluetoothGattReflector.disconnect(); if (this.isCallbackAppropriate()) { @@ -155,7 +155,7 @@ public class ShadowBluetoothGatt { } /** Close this Bluetooth GATT client. */ - @Implementation(minSdk = JELLY_BEAN_MR2) + @Implementation protected void close() { bluetoothGattReflector.close(); this.isClosed = true; @@ -183,6 +183,21 @@ public class ShadowBluetoothGatt { } /** + * Overrides {@link BluetoothGatt#requestMtu} to always fail before {@link + * ShadowBlueoothGatt.setGattCallback} is called, and always succeed after. + */ + @Implementation(minSdk = O) + protected boolean requestMtu(int mtu) { + if (this.bluetoothGattCallback == null) { + return false; + } + + this.bluetoothGattCallback.onMtuChanged( + this.realBluetoothGatt, mtu, BluetoothGatt.GATT_SUCCESS); + return true; + } + + /** * Overrides {@link BluetoothGatt#discoverServices} to always return false unless there are * discoverable services made available by {@link ShadowBluetoothGatt#addDiscoverableService} * @@ -241,7 +256,7 @@ public class ShadowBluetoothGatt { @Implementation(minSdk = O) protected boolean setCharacteristicNotification( BluetoothGattCharacteristic characteristic, boolean enable) { - return characteristicNotificationEnableSet.contains(characteristic) == enable; + return characteristicNotificationEnableSet.contains(characteristic); } @Implementation(minSdk = O) @@ -264,6 +279,17 @@ public class ShadowBluetoothGatt { return writeIncomingCharacteristic(characteristic); } + @Implementation(minSdk = Build.VERSION_CODES.TIRAMISU) + protected int writeCharacteristic( + BluetoothGattCharacteristic characteristic, byte[] value, int writeType) { + characteristic.setValue(value); + boolean writeSuccessCode = writeIncomingCharacteristic(characteristic); + if (writeSuccessCode) { + return BluetoothGatt.GATT_SUCCESS; + } + return BluetoothGatt.GATT_FAILURE; + } + /** * Reads bytes from incoming characteristic if properties are valid and callback is set. Callback * responds with {@link BluetoothGattCallback#onCharacteristicWrite} and returns true when diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothGattServer.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothGattServer.java index 7927da22c..b75885367 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothGattServer.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothGattServer.java @@ -5,12 +5,17 @@ import static android.os.Build.VERSION_CODES.O; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothGatt; +import android.bluetooth.BluetoothGattCharacteristic; import android.bluetooth.BluetoothGattServer; import android.bluetooth.BluetoothGattServerCallback; +import android.bluetooth.BluetoothGattService; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.UUID; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; import org.robolectric.annotation.ReflectorObject; @@ -22,8 +27,10 @@ import org.robolectric.util.reflector.ForType; public class ShadowBluetoothGattServer { private BluetoothGattServerCallback callback; private final List<byte[]> responses = new ArrayList<>(); + private final List<byte[]> writtenBytes = new ArrayList<>(); private final Set<BluetoothDevice> cancelledDevices = new HashSet<>(); private boolean isClosed; + private final Set<BluetoothGattService> services = new HashSet<>(); @ReflectorObject protected BluetoothGattServerReflector bluetoothGattServerReflector; @@ -62,6 +69,53 @@ public class ShadowBluetoothGattServer { } /** + * Add a service to the GATT server. + * + * @param service service to be added to GattServer + */ + @Implementation + protected boolean addService(BluetoothGattService service) { + bluetoothGattServerReflector.addService(service); + this.services.add(service); + return true; + } + + /** + * Remove a service from the GATT server. + * + * @param service service to be removed from GattServer + */ + @Implementation + protected boolean removeService(BluetoothGattService service) { + return this.services.remove(service); + } + + /** Remove all services from the list of provided services. */ + @Implementation + protected void clearServices() { + this.services.clear(); + } + + /** Returns a list of GATT services offered by this device. */ + @Implementation + protected List<BluetoothGattService> getServices() { + return ImmutableList.copyOf(this.services); + } + + /** + * Returns a {@link BluetoothGattService} from the list of services offered by this device. + * + * <p>If multiple instances of the same service (as identified by UUID) exist, the first instance + * of the service is returned. + * + * @param uuid uuid of service + */ + @Implementation + protected BluetoothGattService getService(UUID uuid) { + return this.services.stream().filter(s -> s.getUuid().equals(uuid)).findFirst().orElse(null); + } + + /** * Simulate a successful Gatt Server Connection with {@link BluetoothConnectionManager}. Performs * a {@link BluetoothGattCallback#onConnectionStateChange} if available. * @@ -95,6 +149,39 @@ public class ShadowBluetoothGattServer { } /** + * Simulate a Gatt characteristic write request to the Gatt Server by triggering the server + * callback. + * + * @param device remote device + * @param requestId id of the request + * @param characteristic characteristic to be written to + * @param preparedWrite true, if this write operation should be queued for later execution + * @param responseNeeded true, if the remote device requires a response + * @param offset the offset given for the value + * @param value the value the client wants to assign to the characteristic + */ + public boolean notifyOnCharacteristicWriteRequest( + BluetoothDevice device, + int requestId, + BluetoothGattCharacteristic characteristic, + Boolean preparedWrite, + Boolean responseNeeded, + int offset, + byte[] value) { + if (this.callback == null) { + return false; + } else if ((characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_WRITE) == 0 + && (characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) + == 0) { + return false; + } + writtenBytes.add(value); + this.callback.onCharacteristicWriteRequest( + device, requestId, characteristic, preparedWrite, responseNeeded, offset, value); + return true; + } + + /** * Get whether the device's connection has been cancelled. * * @param device remote device @@ -130,6 +217,16 @@ public class ShadowBluetoothGattServer { this.responses.clear(); } + /** Get a copy of the list of bytes that have been received. */ + public List<byte[]> getWrittenBytes() { + return Lists.transform(this.writtenBytes, bytes -> bytes != null ? bytes.clone() : null); + } + + /** Clear the list of written bytes. */ + public void clearWrittenBytes() { + this.writtenBytes.clear(); + } + /** Get whether server has been closed. */ public boolean isClosed() { return this.isClosed; @@ -159,5 +256,8 @@ public class ShadowBluetoothGattServer { @Direct boolean sendResponse( BluetoothDevice device, int requestId, int status, int offset, byte[] value); + + @Direct + boolean addService(BluetoothGattService service); } } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothHeadset.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothHeadset.java index b612fc4d2..f0158dc70 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothHeadset.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothHeadset.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.KITKAT; import static android.os.Build.VERSION_CODES.P; import static android.os.Build.VERSION_CODES.S; import static java.util.stream.Collectors.toCollection; @@ -9,6 +8,9 @@ import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothHeadset; import android.bluetooth.BluetoothProfile; import android.content.Intent; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.common.primitives.Ints; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -75,6 +77,18 @@ public class ShadowBluetoothHeadset { return bluetoothDevices.getOrDefault(device, BluetoothProfile.STATE_DISCONNECTED); } + @Implementation + protected List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { + ImmutableSet<Integer> statesSet = ImmutableSet.copyOf(Ints.asList(states)); + List<BluetoothDevice> matchingDevices = new ArrayList<>(); + for (Map.Entry<BluetoothDevice, Integer> entry : bluetoothDevices.entrySet()) { + if (statesSet.contains(entry.getValue())) { + matchingDevices.add(entry.getKey()); + } + } + return ImmutableList.copyOf(matchingDevices); + } + /** * Overrides behavior of {@link connect}. Returns {@code true} and adds {@code device} to the * shadow profile's connected device list if {@code device} is currently disconnected, and returns @@ -156,7 +170,7 @@ public class ShadowBluetoothHeadset { * 'false' argument. * @throws IllegalArgumentException if 'command' argument is null, per Android API */ - @Implementation(minSdk = KITKAT) + @Implementation protected boolean sendVendorSpecificResultCode( BluetoothDevice device, String command, String arg) { if (command == null) { diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothLeAdvertiser.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothLeAdvertiser.java index 191eb9e95..10bc66e0a 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothLeAdvertiser.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothLeAdvertiser.java @@ -3,21 +3,35 @@ package org.robolectric.shadows; import static android.os.Build.VERSION_CODES.O; import static android.os.Build.VERSION_CODES.R; import static android.os.Build.VERSION_CODES.S; +import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE; import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothGattServer; import android.bluetooth.BluetoothUuid; import android.bluetooth.IBluetoothManager; import android.bluetooth.le.AdvertiseCallback; import android.bluetooth.le.AdvertiseData; import android.bluetooth.le.AdvertiseSettings; +import android.bluetooth.le.AdvertisingSet; +import android.bluetooth.le.AdvertisingSetCallback; +import android.bluetooth.le.AdvertisingSetParameters; import android.bluetooth.le.BluetoothLeAdvertiser; +import android.bluetooth.le.PeriodicAdvertisingParameters; +import android.content.AttributionSource; +import android.os.Handler; import android.os.ParcelUuid; +import java.util.HashMap; import java.util.HashSet; +import java.util.Map; import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; import org.robolectric.annotation.ReflectorObject; import org.robolectric.util.PerfStatsCollector; +import org.robolectric.util.ReflectionHelpers; +import org.robolectric.util.ReflectionHelpers.ClassParameter; import org.robolectric.util.reflector.Direct; import org.robolectric.util.reflector.ForType; @@ -34,6 +48,8 @@ public class ShadowBluetoothLeAdvertiser { private BluetoothAdapter bluetoothAdapter; private final Set<AdvertiseCallback> advertisements = new HashSet<>(); + private final Map<AdvertisingSetCallback, AdvertisingSet> advertisingSetMap = new HashMap<>(); + private final AtomicInteger advertiserId = new AtomicInteger(0); @ReflectorObject protected BluetoothLeAdvertiserReflector bluetoothLeAdvertiserReflector; @Implementation(maxSdk = R) @@ -118,6 +134,142 @@ public class ShadowBluetoothLeAdvertiser { this.advertisements.remove(callback); } + /** + * Start Bluetooth LE Advertising Set. This method returns immediately, the operation status is + * delivered through {@code callback}. + * + * @param parameters Advertising set parameters. + * @param advertiseData Advertisement data to be broadcasted. + * @param scanResponse Scan response associated with the advertisement data. + * @param periodicParameters Periodic advertisng parameters. + * @param periodicData Periodic advertising data. + * @param duration Advertising duration, in 10ms unit. + * @param maxExtendedAdvertisingEvents Maximum number of extended advertising events the + * controller shall attempt to send prior to terminating the extended advertising, even if the + * duration has not expired. + * @param gattServer GattServer the GATT server that will "own" connections derived from this + * advertising. + * @param callback Callback for advertising set. + * @param handler Thread upon which the callbacks will be invoked. + * @throws IllegalArgumentException When {@code callback} is not present. + */ + @Implementation(minSdk = UPSIDE_DOWN_CAKE) + protected void startAdvertisingSet( + AdvertisingSetParameters parameters, + AdvertiseData advertiseData, + AdvertiseData scanResponse, + PeriodicAdvertisingParameters periodicParameters, + AdvertiseData periodicData, + int duration, + int maxExtendedAdvertisingEvents, + BluetoothGattServer gattServer, + AdvertisingSetCallback callback, + Handler handler) { + if (callback == null) { + throw new IllegalArgumentException("callback cannot be null"); + } + + boolean isConnectable = parameters.isConnectable(); + boolean isDiscoverable = parameters.isDiscoverable(); + boolean hasFlags = isConnectable && isDiscoverable; + if (parameters.isLegacy()) { + if (getTotalBytes(advertiseData, hasFlags) > MAX_LEGACY_ADVERTISING_DATA_BYTES) { + throw new IllegalArgumentException("Legacy advertising data too big"); + } + + if (getTotalBytes(scanResponse, false) > MAX_LEGACY_ADVERTISING_DATA_BYTES) { + throw new IllegalArgumentException("Legacy scan response data too big"); + } + } else { + boolean supportCodedPhy = bluetoothAdapter.isLeCodedPhySupported(); + boolean support2MPhy = bluetoothAdapter.isLe2MPhySupported(); + int pphy = parameters.getPrimaryPhy(); + int sphy = parameters.getSecondaryPhy(); + if (pphy == BluetoothDevice.PHY_LE_CODED && !supportCodedPhy) { + throw new IllegalArgumentException("Unsupported primary PHY selected"); + } + + if ((sphy == BluetoothDevice.PHY_LE_CODED && !supportCodedPhy) + || (sphy == BluetoothDevice.PHY_LE_2M && !support2MPhy)) { + throw new IllegalArgumentException("Unsupported secondary PHY selected"); + } + + int maxData = bluetoothAdapter.getLeMaximumAdvertisingDataLength(); + if (getTotalBytes(advertiseData, hasFlags) > maxData) { + throw new IllegalArgumentException("Advertising data too big"); + } + + if (getTotalBytes(scanResponse, false) > maxData) { + throw new IllegalArgumentException("Scan response data too big"); + } + + if (getTotalBytes(periodicData, false) > maxData) { + throw new IllegalArgumentException("Periodic advertising data too big"); + } + } + + if (maxExtendedAdvertisingEvents < 0 || maxExtendedAdvertisingEvents > 255) { + throw new IllegalArgumentException( + "maxExtendedAdvertisingEvents out of range: " + maxExtendedAdvertisingEvents); + } + + if (maxExtendedAdvertisingEvents != 0 && !bluetoothAdapter.isLePeriodicAdvertisingSupported()) { + throw new IllegalArgumentException( + "Can't use maxExtendedAdvertisingEvents with controller that don't support " + + "LE Extended Advertising"); + } + + if (duration < 0 || duration > 65535) { + throw new IllegalArgumentException("duration out of range: " + duration); + } + + if (advertisingSetMap.containsKey(callback)) { + callback.onAdvertisingSetStarted( + /* advertisingSet= */ null, + parameters.getTxPowerLevel(), + AdvertisingSetCallback.ADVERTISE_FAILED_ALREADY_STARTED); + return; + } + + AdvertisingSet advertisingSet = + ReflectionHelpers.callConstructor( + AdvertisingSet.class, + ClassParameter.from(int.class, advertiserId.getAndAdd(1)), + ClassParameter.from( + IBluetoothManager.class, + ReflectionHelpers.createNullProxy(IBluetoothManager.class)), + ClassParameter.from( + AttributionSource.class, + ReflectionHelpers.callInstanceMethod(bluetoothAdapter, "getAttributionSource"))); + + callback.onAdvertisingSetStarted( + advertisingSet, parameters.getTxPowerLevel(), AdvertisingSetCallback.ADVERTISE_SUCCESS); + + advertisingSetMap.put(callback, advertisingSet); + } + + /** + * Used to dispose of a {@link AdvertisingSet} object, obtained with {@link + * BluetoothLeAdvertiser#startAdvertisingSet}. + * + * @param callback Callback for advertising set. + * @throws IllegalArgumentException When {@code callback} is not present. + */ + @Implementation(minSdk = UPSIDE_DOWN_CAKE) + protected void stopAdvertisingSet(AdvertisingSetCallback callback) { + if (callback == null) { + throw new IllegalArgumentException("callback cannot be null"); + } + + if (!advertisingSetMap.containsKey(callback)) { + throw new IllegalArgumentException("callback not found"); + } + + callback.onAdvertisingSetStopped(advertisingSetMap.get(callback)); + + advertisingSetMap.remove(callback); + } + /** Returns the count of current ongoing Bluetooth LE advertising requests. */ public int getAdvertisementRequestCount() { return this.advertisements.size(); diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothLeScanner.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothLeScanner.java index d65dfb3b4..a27e1096a 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothLeScanner.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothLeScanner.java @@ -2,12 +2,14 @@ package org.robolectric.shadows; import static android.os.Build.VERSION_CODES.LOLLIPOP; import static android.os.Build.VERSION_CODES.O; +import static com.google.common.base.Preconditions.checkNotNull; import static java.util.Collections.unmodifiableList; import android.app.PendingIntent; import android.bluetooth.le.BluetoothLeScanner; import android.bluetooth.le.ScanCallback; import android.bluetooth.le.ScanFilter; +import android.bluetooth.le.ScanResult; import android.bluetooth.le.ScanSettings; import com.google.auto.value.AutoValue; import com.google.common.collect.ImmutableList; @@ -26,9 +28,11 @@ import org.robolectric.annotation.Implements; /** Adds Robolectric support for BLE scanning. */ @Implements(value = BluetoothLeScanner.class, minSdk = LOLLIPOP) public class ShadowBluetoothLeScanner { - private List<ScanParams> activeScanParams = new ArrayList<>(); + // Set of ScanResults that will be immediately returned when startScan is called. + private final Set<ScanResult> scanResults = new HashSet<>(); + /** * Encapsulates scan params passed to {@link android.bluetooth.BluetoothAdapter} startScan * methods. @@ -69,11 +73,19 @@ public class ShadowBluetoothLeScanner { */ @Implementation protected void startScan(List<ScanFilter> filters, ScanSettings settings, ScanCallback callback) { + checkNotNull(callback); + if (filters != null) { filters = unmodifiableList(filters); } activeScanParams.add(ScanParams.create(filters, settings, callback)); + + for (ScanResult scanResult : scanResults) { + if (filters == null || filters.stream().anyMatch(f -> f.matches(scanResult))) { + callback.onScanResult(ScanSettings.CALLBACK_TYPE_ALL_MATCHES, scanResult); + } + } } /** @@ -122,4 +134,9 @@ public class ShadowBluetoothLeScanner { public List<ScanParams> getActiveScans() { return Collections.unmodifiableList(activeScanParams); } + + public void addScanResult(ScanResult scanResult) { + checkNotNull(scanResult); + this.scanResults.add(scanResult); + } } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothManager.java index db9d1bd62..f69bd1d4f 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothManager.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothManager.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2; import static android.os.Build.VERSION_CODES.O; import static android.os.Build.VERSION_CODES.R; import static android.os.Build.VERSION_CODES.S; @@ -28,7 +27,7 @@ import org.robolectric.util.ReflectionHelpers; import org.robolectric.util.ReflectionHelpers.ClassParameter; /** Shadow of {@link BluetoothManager} that makes the testing possible. */ -@Implements(value = BluetoothManager.class, minSdk = JELLY_BEAN_MR2) +@Implements(value = BluetoothManager.class) public class ShadowBluetoothManager { private static final ImmutableIntArray VALID_STATES = ImmutableIntArray.of( diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothServerSocket.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothServerSocket.java index 7d8c28309..70166ddad 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothServerSocket.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothServerSocket.java @@ -1,12 +1,9 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; - import android.annotation.SuppressLint; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothServerSocket; import android.bluetooth.BluetoothSocket; -import android.os.Build; import android.os.ParcelUuid; import java.io.IOException; import java.util.concurrent.BlockingQueue; @@ -23,20 +20,12 @@ public class ShadowBluetoothServerSocket { private boolean closed; @SuppressLint("PrivateApi") - @SuppressWarnings("unchecked") public static BluetoothServerSocket newInstance( int type, boolean auth, boolean encrypt, ParcelUuid uuid) { - if (Build.VERSION.SDK_INT >= JELLY_BEAN_MR1) { - return Shadow.newInstance( - BluetoothServerSocket.class, - new Class<?>[] {Integer.TYPE, Boolean.TYPE, Boolean.TYPE, ParcelUuid.class}, - new Object[] {type, auth, encrypt, uuid}); - } else { - return Shadow.newInstance( - BluetoothServerSocket.class, - new Class<?>[] {Integer.TYPE, Boolean.TYPE, Boolean.TYPE, Integer.TYPE}, - new Object[] {type, auth, encrypt, getPort(uuid)}); - } + return Shadow.newInstance( + BluetoothServerSocket.class, + new Class<?>[] {Integer.TYPE, Boolean.TYPE, Boolean.TYPE, ParcelUuid.class}, + new Object[] {type, auth, encrypt, uuid}); } // Port ranges are valid from 1 to MAX_RFCOMM_CHANNEL. diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBroadcastPendingResult.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBroadcastPendingResult.java index 2cd2058ee..cbd7e3eea 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBroadcastPendingResult.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBroadcastPendingResult.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.JELLY_BEAN; import static android.os.Build.VERSION_CODES.LOLLIPOP_MR1; import static org.robolectric.RuntimeEnvironment.getApiLevel; @@ -21,18 +20,7 @@ public final class ShadowBroadcastPendingResult { static BroadcastReceiver.PendingResult create(int resultCode, String resultData, Bundle resultExtras, boolean ordered) { try { - if (getApiLevel() <= JELLY_BEAN) { - return BroadcastReceiver.PendingResult.class - .getConstructor(int.class, String.class, Bundle.class, int.class, boolean.class, boolean.class, IBinder.class) - .newInstance( - resultCode, - resultData, - resultExtras, - 0 /* type */, - ordered, - false /*sticky*/, - null /* ibinder token */); - } else if (getApiLevel() <= LOLLIPOP_MR1) { + if (getApiLevel() <= LOLLIPOP_MR1) { return BroadcastReceiver.PendingResult.class .getConstructor(int.class, String.class, Bundle.class, int.class, boolean.class, boolean.class, IBinder.class, int.class) .newInstance( @@ -64,25 +52,7 @@ public final class ShadowBroadcastPendingResult { static BroadcastReceiver.PendingResult createSticky(Intent intent) { try { - if (getApiLevel() <= JELLY_BEAN) { - return BroadcastReceiver.PendingResult.class - .getConstructor( - int.class, - String.class, - Bundle.class, - int.class, - boolean.class, - boolean.class, - IBinder.class) - .newInstance( - 0 /*resultCode*/, - intent.getDataString(), - intent.getExtras(), - 0 /* type */, - false /*ordered*/, - true /*sticky*/, - null /* ibinder token */); - } else if (getApiLevel() <= LOLLIPOP_MR1) { + if (getApiLevel() <= LOLLIPOP_MR1) { return BroadcastReceiver.PendingResult.class .getConstructor( int.class, diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBugreportManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBugreportManager.java index cea521c2c..08957d9f1 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBugreportManager.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBugreportManager.java @@ -4,11 +4,11 @@ import static android.os.Build.VERSION_CODES.Q; import static android.os.Build.VERSION_CODES.R; import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE; +import android.annotation.Nullable; import android.os.BugreportManager; import android.os.BugreportManager.BugreportCallback; import android.os.BugreportParams; import android.os.ParcelFileDescriptor; -import androidx.annotation.Nullable; import java.io.IOException; import java.util.concurrent.Executor; import org.robolectric.annotation.Implementation; diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBuild.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBuild.java index b6b1b8309..7b1516152 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBuild.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBuild.java @@ -68,6 +68,15 @@ public class ShadowBuild { } /** + * Sets the value of the {@link Build#IS_DEBUGGABLE} field. + * + * <p>It will be reset for the next test. + */ + public static void setDebuggable(Boolean isDebuggable) { + ReflectionHelpers.setStaticField(Build.class, "IS_DEBUGGABLE", isDebuggable); + } + + /** * Sets the value of the {@link Build#MODEL} field. * * <p>It will be reset for the next test. @@ -176,6 +185,16 @@ public class ShadowBuild { } /** + * Sets the value of the {@link Build#SUPPORTED_32_BIT_ABIS} field. Available in Android L+. + * + * <p>It will be reset for the next test. + */ + @TargetApi(LOLLIPOP) + public static void setSupported32BitAbis(String[] supported32BitAbis) { + ReflectionHelpers.setStaticField(Build.class, "SUPPORTED_32_BIT_ABIS", supported32BitAbis); + } + + /** * Sets the value of the {@link Build#SUPPORTED_64_BIT_ABIS} field. Available in Android L+. * * <p>It will be reset for the next test. @@ -186,6 +205,16 @@ public class ShadowBuild { } /** + * Sets the value of the {@link Build#SUPPORTED_ABIS} field. Available in Android L+. + * + * <p>It will be reset for the next test. + */ + @TargetApi(LOLLIPOP) + public static void setSupportedAbis(String[] supportedAbis) { + ReflectionHelpers.setStaticField(Build.class, "SUPPORTED_ABIS", supportedAbis); + } + + /** * Override return value from {@link Build#getRadioVersion()} * * @param radioVersion diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowCamera.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowCamera.java index 0c2c3a895..dc36d919c 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowCamera.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowCamera.java @@ -1,10 +1,8 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; import static org.robolectric.shadow.api.Shadow.newInstanceOf; import android.hardware.Camera; -import android.os.Build; import android.view.SurfaceHolder; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; @@ -227,10 +225,7 @@ public class ShadowCamera { Camera.CameraInfo foundCam = cameras.get(cameraId); cameraInfo.facing = foundCam.facing; cameraInfo.orientation = foundCam.orientation; - // canDisableShutterSound was added in API 17. - if (Build.VERSION.SDK_INT >= JELLY_BEAN_MR1) { - cameraInfo.canDisableShutterSound = foundCam.canDisableShutterSound; - } + cameraInfo.canDisableShutterSound = foundCam.canDisableShutterSound; } @Implementation @@ -254,7 +249,7 @@ public class ShadowCamera { } } - @Implementation(minSdk = JELLY_BEAN_MR1) + @Implementation protected boolean enableShutterSound(boolean enabled) { if (!enabled && cameras.containsKey(id) && !cameras.get(id).canDisableShutterSound) { return false; diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowCardEmulation.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowCardEmulation.java index 2bd28da36..3f6989a14 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowCardEmulation.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowCardEmulation.java @@ -30,7 +30,7 @@ public class ShadowCardEmulation { @RealObject CardEmulation cardEmulation; - @Implementation(minSdk = Build.VERSION_CODES.KITKAT) + @Implementation public boolean isDefaultServiceForCategory(ComponentName service, String category) { return service.equals(defaultServiceForCategoryMap.get(category)); } @@ -79,14 +79,12 @@ public class ShadowCardEmulation { public static void reset() { defaultServiceForCategoryMap = new HashMap<>(); preferredService = null; - if (RuntimeEnvironment.getApiLevel() >= Build.VERSION_CODES.KITKAT) { - CardEmulationReflector reflector = reflector(CardEmulationReflector.class); - reflector.setIsInitialized(false); - reflector.setService(null); - Map<Context, CardEmulation> cardEmus = reflector.getCardEmus(); - if (cardEmus != null) { - cardEmus.clear(); - } + CardEmulationReflector reflector = reflector(CardEmulationReflector.class); + reflector.setIsInitialized(false); + reflector.setService(null); + Map<Context, CardEmulation> cardEmus = reflector.getCardEmus(); + if (cardEmus != null) { + cardEmus.clear(); } } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowChoreographer.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowChoreographer.java index cff9382ea..5a136ec09 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowChoreographer.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowChoreographer.java @@ -5,6 +5,7 @@ import static com.google.common.base.Preconditions.checkState; import static org.robolectric.shadows.ShadowLooper.looperMode; import static org.robolectric.util.reflector.Reflector.reflector; +import android.os.Looper; import android.view.Choreographer; import android.view.Choreographer.FrameCallback; import android.view.DisplayEventReceiver; @@ -175,5 +176,14 @@ public abstract class ShadowChoreographer { @Accessor("mDisplayEventReceiver") DisplayEventReceiver getReceiver(); + + @Direct + void __constructor__(Looper looper); + + @Direct + void __constructor__(Looper looper, int vsyncSource, long layerHandle); + + @Direct + void __constructor__(Looper looper, int vsyncSource); } } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowClipboardManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowClipboardManager.java index 1ff824105..e90b0a426 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowClipboardManager.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowClipboardManager.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2; import static android.os.Build.VERSION_CODES.N; import static android.os.Build.VERSION_CODES.O; import static android.os.Build.VERSION_CODES.P; @@ -48,7 +47,7 @@ public class ShadowClipboardManager { if (clip != null) { clip.prepareToLeaveProcess(true); } - } else if (getApiLevel() >= JELLY_BEAN_MR2) { + } else { if (clip != null) { ReflectionHelpers.callInstanceMethod(ClipData.class, clip, "prepareToLeaveProcess"); } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowCompanionDeviceManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowCompanionDeviceManager.java index ce52528d9..23449cd08 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowCompanionDeviceManager.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowCompanionDeviceManager.java @@ -5,6 +5,7 @@ import static java.util.stream.Collectors.toCollection; import static java.util.stream.Collectors.toList; import android.Manifest.permission; +import android.annotation.Nullable; import android.app.ActivityThread; import android.companion.AssociationInfo; import android.companion.AssociationRequest; @@ -14,7 +15,6 @@ import android.content.ComponentName; import android.net.MacAddress; import android.os.Build.VERSION_CODES; import android.os.Handler; -import androidx.annotation.Nullable; import com.google.auto.value.AutoValue; import com.google.common.base.Ascii; import com.google.common.collect.ImmutableList; @@ -281,7 +281,8 @@ public class ShadowCompanionDeviceManager { info.isNotifyOnDeviceNearby(), revoked, info.getTimeApprovedMs(), - info.getLastTimeConnectedMs(), + // return value of getLastTimeConnectedMs changed from a long to a Long + (long) ReflectionHelpers.callInstanceMethod(info, "getLastTimeConnectedMs"), systemDataSyncFlags); } @@ -343,7 +344,7 @@ public class ShadowCompanionDeviceManager { .setRevoked(false) .setAssociatedDevice(null) .setTimeApprovedMs(0) - .setLastTimeConnectedMs(0) + .setLastTimeConnectedMs(0L) .setSystemDataSyncFlags(DEFAULT_SYSTEMDATASYNCFLAGS); } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowConnectivityManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowConnectivityManager.java index d60c68aae..a051af131 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowConnectivityManager.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowConnectivityManager.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.KITKAT; import static android.os.Build.VERSION_CODES.LOLLIPOP; import static android.os.Build.VERSION_CODES.M; import static android.os.Build.VERSION_CODES.N; @@ -428,7 +427,7 @@ public class ShadowConnectivityManager { * * @param enable new status for airplane mode */ - @Implementation(minSdk = KITKAT) + @Implementation protected void setAirplaneMode(boolean enable) { ShadowSettings.setAirplaneMode(enable); } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowContentProvider.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowContentProvider.java index a824e9004..a92f53015 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowContentProvider.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowContentProvider.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.KITKAT; import static android.os.Build.VERSION_CODES.Q; import static org.robolectric.util.reflector.Reflector.reflector; @@ -24,7 +23,7 @@ public class ShadowContentProvider { return callingPackage; } - @Implementation(minSdk = KITKAT) + @Implementation protected String getCallingPackage() { if (callingPackage != null) { return callingPackage; diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowContentProviderClient.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowContentProviderClient.java index 4652869c0..cf3e712a2 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowContentProviderClient.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowContentProviderClient.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; import static org.robolectric.util.reflector.Reflector.reflector; import android.content.ContentProvider; @@ -34,7 +33,7 @@ public class ShadowContentProviderClient { private ContentProvider provider; - @Implementation(minSdk = JELLY_BEAN_MR1) + @Implementation protected Bundle call(String method, String arg, Bundle extras) throws RemoteException { return provider.call(method, arg, extras); } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowContentResolver.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowContentResolver.java index bb2672c32..ce1681cf0 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowContentResolver.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowContentResolver.java @@ -6,9 +6,6 @@ import static android.content.ContentResolver.QUERY_ARG_SQL_SORT_ORDER; import static android.content.ContentResolver.SCHEME_ANDROID_RESOURCE; import static android.content.ContentResolver.SCHEME_CONTENT; import static android.content.ContentResolver.SCHEME_FILE; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2; -import static android.os.Build.VERSION_CODES.KITKAT; import static android.os.Build.VERSION_CODES.O; import static android.os.Build.VERSION_CODES.Q; import static org.robolectric.util.reflector.Reflector.reflector; @@ -84,7 +81,6 @@ public class ShadowContentResolver { private static final Map<Uri, Supplier<OutputStream>> outputStreamMap = new HashMap<>(); private static final Map<String, List<ContentProviderOperation>> contentProviderOperations = new HashMap<>(); - private static ContentProviderResult[] contentProviderResults; private static final List<UriPermission> uriPermissions = new ArrayList<>(); private static final CopyOnWriteArrayList<ContentObserverEntry> contentObservers = @@ -108,7 +104,6 @@ public class ShadowContentResolver { inputStreamMap.clear(); outputStreamMap.clear(); contentProviderOperations.clear(); - contentProviderResults = null; uriPermissions.clear(); contentObservers.clear(); syncableAccounts.clear(); @@ -514,7 +509,7 @@ public class ShadowContentResolver { } @Implementation - protected ContentProviderResult[] applyBatch( + protected @NonNull ContentProviderResult[] applyBatch( String authority, ArrayList<ContentProviderOperation> operations) throws OperationApplicationException { ContentProvider provider = getProvider(authority, getContext()); @@ -522,7 +517,7 @@ public class ShadowContentResolver { return provider.applyBatch(operations); } else { contentProviderOperations.put(authority, operations); - return contentProviderResults; + return new ContentProviderResult[0]; } } @@ -565,7 +560,7 @@ public class ShadowContentResolver { } for (Map.Entry<Account, Status> mp : map.getValue().entrySet()) { if (isSyncActive(mp.getKey(), map.getKey())) { - SyncInfo si = newSyncInfo(0, mp.getKey(), map.getKey(), 0); + SyncInfo si = new SyncInfo(0, mp.getKey(), map.getKey(), 0); list.add(si); } } @@ -573,20 +568,6 @@ public class ShadowContentResolver { return list; } - private static SyncInfo newSyncInfo( - int authorityId, Account account, String authority, long startTime) { - if (RuntimeEnvironment.getApiLevel() >= JELLY_BEAN_MR2) { - return new SyncInfo(authorityId, account, authority, startTime); - } else { - return ReflectionHelpers.callConstructor( - SyncInfo.class, - ClassParameter.from(int.class, authorityId), - ClassParameter.from(Account.class, account), - ClassParameter.from(String.class, authority), - ClassParameter.from(long.class, startTime)); - } - } - @Implementation protected static void setIsSyncable(Account account, String authority, int syncable) { getStatus(account, authority, true).state = syncable; @@ -665,7 +646,7 @@ public class ShadowContentResolver { return masterSyncAutomatically; } - @Implementation(minSdk = KITKAT) + @Implementation protected void takePersistableUriPermission(@NonNull Uri uri, int modeFlags) { Objects.requireNonNull(uri, "uri may not be null"); modeFlags &= (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); @@ -693,7 +674,7 @@ public class ShadowContentResolver { addUriPermission(uri, modeFlags); } - @Implementation(minSdk = KITKAT) + @Implementation protected void releasePersistableUriPermission(@NonNull Uri uri, int modeFlags) { Objects.requireNonNull(uri, "uri may not be null"); modeFlags &= (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); @@ -727,7 +708,7 @@ public class ShadowContentResolver { } } - @Implementation(minSdk = KITKAT) + @Implementation @NonNull protected List<UriPermission> getPersistedUriPermissions() { return uriPermissions; @@ -919,11 +900,6 @@ public class ShadowContentResolver { return operations; } - @Deprecated - public void setContentProviderResult(ContentProviderResult[] contentProviderResults) { - ShadowContentResolver.contentProviderResults = contentProviderResults; - } - private final Map<Uri, RuntimeException> registerContentProviderExceptions = new HashMap<>(); /** Makes {@link #registerContentObserver} throw the specified exception for the specified URI. */ @@ -951,7 +927,7 @@ public class ShadowContentResolver { contentObservers.add(new ContentObserverEntry(uri, notifyForDescendents, observer)); } - @Implementation(minSdk = JELLY_BEAN_MR1) + @Implementation protected void registerContentObserver( Uri uri, boolean notifyForDescendents, ContentObserver observer, int userHandle) { registerContentObserver(uri, notifyForDescendents, observer); diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowContextHubClient.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowContextHubClient.java index b7c2b7413..a07974be0 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowContextHubClient.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowContextHubClient.java @@ -2,13 +2,13 @@ package org.robolectric.shadows; import static org.robolectric.util.reflector.Reflector.reflector; +import android.annotation.RequiresApi; import android.annotation.TargetApi; import android.hardware.location.ContextHubClient; import android.hardware.location.ContextHubInfo; import android.hardware.location.ContextHubTransaction; import android.hardware.location.NanoAppMessage; import android.os.Build.VERSION_CODES; -import androidx.annotation.RequiresApi; import com.google.common.collect.ImmutableList; import java.util.ArrayList; import java.util.List; diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowContextHubManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowContextHubManager.java index 84aba53a8..9991a36ee 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowContextHubManager.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowContextHubManager.java @@ -2,6 +2,7 @@ package org.robolectric.shadows; import static org.robolectric.util.reflector.Reflector.reflector; +import android.annotation.Nullable; import android.app.PendingIntent; import android.content.Context; import android.hardware.location.ContextHubClient; @@ -13,7 +14,6 @@ import android.hardware.location.NanoAppState; import android.os.Build; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; -import androidx.annotation.Nullable; import com.google.common.collect.HashMultimap; import com.google.common.collect.ImmutableList; import com.google.common.collect.Multimap; diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowContextImpl.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowContextImpl.java index 86998b8f5..d55ba45c7 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowContextImpl.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowContextImpl.java @@ -1,8 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2; -import static android.os.Build.VERSION_CODES.KITKAT; import static android.os.Build.VERSION_CODES.LOLLIPOP; import static android.os.Build.VERSION_CODES.M; import static android.os.Build.VERSION_CODES.N; @@ -177,14 +174,14 @@ public class ShadowContextImpl { intent, /*userHandle=*/ null, receiverPermission, realContextImpl); } - @Implementation(minSdk = JELLY_BEAN_MR1) + @Implementation @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) protected void sendBroadcastAsUser(@RequiresPermission Intent intent, UserHandle user) { getShadowInstrumentation() .sendBroadcastWithPermission(intent, user, /*receiverPermission=*/ null, realContextImpl); } - @Implementation(minSdk = JELLY_BEAN_MR1) + @Implementation @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) protected void sendBroadcastAsUser( @RequiresPermission Intent intent, UserHandle user, @Nullable String receiverPermission) { @@ -224,7 +221,7 @@ public class ShadowContextImpl { * Allows the test to query for the broadcasts for specific users, for everything else behaves as * {@link #sendOrderedBroadcastAsUser}. */ - @Implementation(minSdk = JELLY_BEAN_MR1) + @Implementation protected void sendOrderedBroadcastAsUser( Intent intent, UserHandle userHandle, @@ -312,7 +309,7 @@ public class ShadowContextImpl { .registerReceiver(receiver, filter, broadcastPermission, scheduler, flags, realContextImpl); } - @Implementation(minSdk = JELLY_BEAN_MR1) + @Implementation protected Intent registerReceiverAsUser( BroadcastReceiver receiver, UserHandle user, @@ -371,7 +368,7 @@ public class ShadowContextImpl { } // This is a private method in ContextImpl so we copy the relevant portions of it here. - @Implementation(minSdk = KITKAT) + @Implementation protected void validateServiceIntent(Intent service) { if (service.getComponent() == null && service.getPackage() == null @@ -396,7 +393,7 @@ public class ShadowContextImpl { this.userId = userId; } - @Implementation(minSdk = JELLY_BEAN_MR2) + @Implementation protected int getUserId() { if (userId != null) { return userId; @@ -405,7 +402,7 @@ public class ShadowContextImpl { } } - @Implementation(maxSdk = JELLY_BEAN_MR2) + @Implementation protected File getExternalFilesDir(String type) { File externalDir = Environment.getExternalStoragePublicDirectory(/* type= */ null); if (externalDir == null) { @@ -421,7 +418,7 @@ public class ShadowContextImpl { return externalFilesDir; } - @Implementation(minSdk = KITKAT) + @Implementation protected File[] getExternalFilesDirs(String type) { return new File[] {getExternalFilesDir(type)}; } @@ -430,11 +427,10 @@ public class ShadowContextImpl { public static void reset() { String prefsCacheFieldName = RuntimeEnvironment.getApiLevel() >= N ? "sSharedPrefsCache" : "sSharedPrefs"; - Object prefsDefaultValue = RuntimeEnvironment.getApiLevel() >= KITKAT ? null : new HashMap<>(); Class<?> contextImplClass = ReflectionHelpers.loadClass( ShadowContextImpl.class.getClassLoader(), "android.app.ContextImpl"); - ReflectionHelpers.setStaticField(contextImplClass, prefsCacheFieldName, prefsDefaultValue); + ReflectionHelpers.setStaticField(contextImplClass, prefsCacheFieldName, null); if (RuntimeEnvironment.getApiLevel() <= VERSION_CODES.LOLLIPOP_MR1) { HashMap<String, Object> fetchers = @@ -450,12 +446,9 @@ public class ShadowContextImpl { } } - if (RuntimeEnvironment.getApiLevel() >= KITKAT) { - - Object windowServiceFetcher = fetchers.get(Context.WINDOW_SERVICE); - ReflectionHelpers.setField( - windowServiceFetcher.getClass(), windowServiceFetcher, "mDefaultDisplay", null); - } + Object windowServiceFetcher = fetchers.get(Context.WINDOW_SERVICE); + ReflectionHelpers.setField( + windowServiceFetcher.getClass(), windowServiceFetcher, "mDefaultDisplay", null); } } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowCursorWrapper.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowCursorWrapper.java index 04623f10c..9e659a93c 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowCursorWrapper.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowCursorWrapper.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.KITKAT; import static android.os.Build.VERSION_CODES.M; import android.content.ContentResolver; @@ -198,7 +197,8 @@ public class ShadowCursorWrapper implements Cursor { wrappedCursor.setNotificationUri(contentResolver, uri); } - @Override @Implementation(minSdk = KITKAT) + @Override + @Implementation public Uri getNotificationUri() { return wrappedCursor.getNotificationUri(); } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDateIntervalFormat.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDateIntervalFormat.java index 83d5235c0..8dce4c521 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDateIntervalFormat.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDateIntervalFormat.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.KITKAT; import static android.os.Build.VERSION_CODES.LOLLIPOP_MR1; import static android.os.Build.VERSION_CODES.TIRAMISU; @@ -8,11 +7,11 @@ import java.text.FieldPosition; import java.util.HashMap; import java.util.Locale; import java.util.Map; +import libcore.icu.DateIntervalFormat; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; -@Implements(className = "libcore.icu.DateIntervalFormat", isInAndroidSdk = false, minSdk = KITKAT, - maxSdk = TIRAMISU) +@Implements(value = DateIntervalFormat.class, isInAndroidSdk = false) public class ShadowDateIntervalFormat { private static long address; diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDatePickerDialog.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDatePickerDialog.java index 84fc39858..f0313f2a8 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDatePickerDialog.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDatePickerDialog.java @@ -7,11 +7,10 @@ import static org.robolectric.shadow.api.Shadow.invokeConstructor; import static org.robolectric.util.ReflectionHelpers.ClassParameter; import static org.robolectric.util.reflector.Reflector.reflector; -import android.annotation.TargetApi; +import android.annotation.RequiresApi; import android.app.DatePickerDialog; import android.app.DatePickerDialog.OnDateSetListener; import android.content.Context; -import androidx.annotation.RequiresApi; import java.util.Calendar; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Implementation; @@ -64,7 +63,7 @@ public class ShadowDatePickerDialog extends ShadowAlertDialog { } public DatePickerDialog.OnDateSetListener getOnDateSetListenerCallback() { - if (RuntimeEnvironment.getApiLevel() <= KITKAT) { + if (RuntimeEnvironment.getApiLevel() == KITKAT) { return reflector(DatePickerDialogReflector.class, realDatePickerDialog).getCallback(); } else { return reflector(DatePickerDialogReflector.class, realDatePickerDialog).getDateSetListener(); @@ -80,7 +79,6 @@ public class ShadowDatePickerDialog extends ShadowAlertDialog { OnDateSetListener getDateSetListener(); /** For sdk version is equals to {@link KITKAT} */ - @TargetApi(KITKAT) @Accessor("mCallBack") OnDateSetListener getCallback(); } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDevicePolicyManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDevicePolicyManager.java index 2b587d1c1..e4a197af8 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDevicePolicyManager.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDevicePolicyManager.java @@ -3,8 +3,6 @@ package org.robolectric.shadows; import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_HOME; import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS; import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_OVERVIEW; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2; import static android.os.Build.VERSION_CODES.LOLLIPOP; import static android.os.Build.VERSION_CODES.LOLLIPOP_MR1; import static android.os.Build.VERSION_CODES.M; @@ -215,7 +213,7 @@ public class ShadowDevicePolicyManager { storageEncryptionStatus = DevicePolicyManager.ENCRYPTION_STATUS_UNSUPPORTED; } - @Implementation(minSdk = JELLY_BEAN_MR2) + @Implementation protected boolean isDeviceOwnerApp(String packageName) { return deviceOwner != null && deviceOwner.getPackageName().equals(packageName); } @@ -351,7 +349,7 @@ public class ShadowDevicePolicyManager { /** * @see #setDeviceOwner(ComponentName) */ - @Implementation(minSdk = JELLY_BEAN_MR2) + @Implementation protected String getDeviceOwner() { return deviceOwner != null ? deviceOwner.getPackageName() : null; } @@ -1274,13 +1272,13 @@ public class ShadowDevicePolicyManager { .clearPackagePersistentPreferredActivities(packageName); } - @Implementation(minSdk = JELLY_BEAN_MR1) + @Implementation protected void setKeyguardDisabledFeatures(ComponentName admin, int which) { enforceActiveAdmin(admin); keyguardDisabledFeatures = which; } - @Implementation(minSdk = JELLY_BEAN_MR1) + @Implementation protected int getKeyguardDisabledFeatures(ComponentName admin) { return keyguardDisabledFeatures; } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDevicePolicyResourcesManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDevicePolicyResourcesManager.java index eaf90276b..70249de3c 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDevicePolicyResourcesManager.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDevicePolicyResourcesManager.java @@ -2,10 +2,10 @@ package org.robolectric.shadows; import static org.robolectric.util.reflector.Reflector.reflector; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.admin.DevicePolicyResourcesManager; import android.os.Build.VERSION_CODES; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import java.util.HashMap; import java.util.Map; import java.util.function.Supplier; diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDisplay.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDisplay.java index c23c5c162..6a88f769b 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDisplay.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDisplay.java @@ -1,11 +1,8 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.JELLY_BEAN; import static org.robolectric.util.reflector.Reflector.reflector; import android.content.Context; -import android.content.res.Configuration; -import android.graphics.Point; import android.os.Build; import android.os.Build.VERSION_CODES; import android.util.DisplayMetrics; @@ -45,43 +42,20 @@ public class ShadowDisplay { @RealObject Display realObject; private Float refreshRate; - - // the following fields are used only for Jelly Bean... - private String name; - private Integer displayId; - private Integer width; - private Integer height; - private Integer realWidth; - private Integer realHeight; - private Integer densityDpi; - private Float xdpi; - private Float ydpi; private Float scaledDensity; - private Integer rotation; - private Integer pixelFormat; /** * If {@link #setScaledDensity(float)} has been called, {@link DisplayMetrics#scaledDensity} will * be modified to reflect the value specified. Note that this is not a realistic state. * - * @deprecated This behavior is deprecated and will be removed in Robolectric 3.7. + * @deprecated This behavior is deprecated and will be removed in Robolectric 4.13. */ @Deprecated @Implementation protected void getMetrics(DisplayMetrics outMetrics) { - if (isJB()) { - outMetrics.density = densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE; - outMetrics.densityDpi = densityDpi; + reflector(_Display_.class, realObject).getMetrics(outMetrics); + if (scaledDensity != null) { outMetrics.scaledDensity = scaledDensity; - outMetrics.widthPixels = width; - outMetrics.heightPixels = height; - outMetrics.xdpi = xdpi; - outMetrics.ydpi = ydpi; - } else { - reflector(_Display_.class, realObject).getMetrics(outMetrics); - if (scaledDensity != null) { - outMetrics.scaledDensity = scaledDensity; - } } } @@ -89,32 +63,25 @@ public class ShadowDisplay { * If {@link #setScaledDensity(float)} has been called, {@link DisplayMetrics#scaledDensity} will * be modified to reflect the value specified. Note that this is not a realistic state. * - * @deprecated This behavior is deprecated and will be removed in Robolectric 3.7. + * @deprecated This behavior is deprecated and will be removed in Robolectric 4.13. */ @Deprecated @Implementation protected void getRealMetrics(DisplayMetrics outMetrics) { - if (isJB()) { - getMetrics(outMetrics); - outMetrics.widthPixels = realWidth; - outMetrics.heightPixels = realHeight; - } else { - reflector(_Display_.class, realObject).getRealMetrics(outMetrics); - if (scaledDensity != null) { - outMetrics.scaledDensity = scaledDensity; - } + reflector(_Display_.class, realObject).getRealMetrics(outMetrics); + if (scaledDensity != null) { + outMetrics.scaledDensity = scaledDensity; } } /** - * If {@link #setDisplayId(int)} has been called, this method will return the specified value. + * Changes the scaled density for this display. * - * @deprecated This behavior is deprecated and will be removed in Robolectric 3.7. + * @deprecated This method is deprecated and will be removed in Robolectric 4.13. */ @Deprecated - @Implementation - protected int getDisplayId() { - return displayId == null ? reflector(_Display_.class, realObject).getDisplayId() : displayId; + public void setScaledDensity(float scaledDensity) { + this.scaledDensity = scaledDensity; } /** @@ -137,38 +104,6 @@ public class ShadowDisplay { } /** - * If {@link #setPixelFormat(int)} has been called, this method will return the specified value. - * - * @deprecated This behavior is deprecated and will be removed in Robolectric 3.7. - */ - @Deprecated - @Implementation - protected int getPixelFormat() { - return pixelFormat == null - ? reflector(_Display_.class, realObject).getPixelFormat() - : pixelFormat; - } - - @Implementation(maxSdk = JELLY_BEAN) - protected void getSizeInternal(Point outSize, boolean doCompat) { - outSize.x = width; - outSize.y = height; - } - - @Implementation(maxSdk = JELLY_BEAN) - protected void getCurrentSizeRange(Point outSmallestSize, Point outLargestSize) { - int minimum = Math.min(width, height); - int maximum = Math.max(width, height); - outSmallestSize.set(minimum, minimum); - outLargestSize.set(maximum, maximum); - } - - @Implementation(maxSdk = JELLY_BEAN) - protected void getRealSize(Point outSize) { - outSize.set(realWidth, realHeight); - } - - /** * Changes the density for this display. * * <p>Any registered {@link android.hardware.display.DisplayManager.DisplayListener}s will be @@ -185,12 +120,8 @@ public class ShadowDisplay { * notified of the change. */ public void setDensityDpi(int densityDpi) { - if (isJB()) { - this.densityDpi = densityDpi; - } else { - ShadowDisplayManager.changeDisplay( - realObject.getDisplayId(), di -> di.logicalDensityDpi = densityDpi); - } + ShadowDisplayManager.changeDisplay( + realObject.getDisplayId(), di -> di.logicalDensityDpi = densityDpi); } /** @@ -200,11 +131,7 @@ public class ShadowDisplay { * notified of the change. */ public void setXdpi(float xdpi) { - if (isJB()) { - this.xdpi = xdpi; - } else { - ShadowDisplayManager.changeDisplay(realObject.getDisplayId(), di -> di.physicalXDpi = xdpi); - } + ShadowDisplayManager.changeDisplay(realObject.getDisplayId(), di -> di.physicalXDpi = xdpi); } /** @@ -214,34 +141,7 @@ public class ShadowDisplay { * notified of the change. */ public void setYdpi(float ydpi) { - if (isJB()) { - this.ydpi = ydpi; - } else { - ShadowDisplayManager.changeDisplay(realObject.getDisplayId(), di -> di.physicalYDpi = ydpi); - } - } - - /** - * Changes the scaled density for this display. - * - * @deprecated This method is deprecated and will be removed in Robolectric 3.7. - */ - @Deprecated - public void setScaledDensity(float scaledDensity) { - this.scaledDensity = scaledDensity; - } - - /** - * Changes the ID for this display. - * - * <p>Any registered {@link android.hardware.display.DisplayManager.DisplayListener}s will be - * notified of the change. - * - * @deprecated This method is deprecated and will be removed in Robolectric 3.7. - */ - @Deprecated - public void setDisplayId(int displayId) { - this.displayId = displayId; + ShadowDisplayManager.changeDisplay(realObject.getDisplayId(), di -> di.physicalYDpi = ydpi); } /** @@ -251,11 +151,7 @@ public class ShadowDisplay { * notified of the change. */ public void setName(String name) { - if (isJB()) { - this.name = name; - } else { - ShadowDisplayManager.changeDisplay(realObject.getDisplayId(), di -> di.name = name); - } + ShadowDisplayManager.changeDisplay(realObject.getDisplayId(), di -> di.name = name); } /** @@ -267,9 +163,7 @@ public class ShadowDisplay { public void setFlags(int flags) { reflector(_Display_.class, realObject).setFlags(flags); - if (!isJB()) { - ShadowDisplayManager.changeDisplay(realObject.getDisplayId(), di -> di.flags = flags); - } + ShadowDisplayManager.changeDisplay(realObject.getDisplayId(), di -> di.flags = flags); } /** @@ -281,11 +175,7 @@ public class ShadowDisplay { * @param width the new width in pixels */ public void setWidth(int width) { - if (isJB()) { - this.width = width; - } else { - ShadowDisplayManager.changeDisplay(realObject.getDisplayId(), di -> di.appWidth = width); - } + ShadowDisplayManager.changeDisplay(realObject.getDisplayId(), di -> di.appWidth = width); } /** @@ -297,11 +187,7 @@ public class ShadowDisplay { * @param height new height in pixels */ public void setHeight(int height) { - if (isJB()) { - this.height = height; - } else { - ShadowDisplayManager.changeDisplay(realObject.getDisplayId(), di -> di.appHeight = height); - } + ShadowDisplayManager.changeDisplay(realObject.getDisplayId(), di -> di.appHeight = height); } /** @@ -313,11 +199,7 @@ public class ShadowDisplay { * @param width the new width in pixels */ public void setRealWidth(int width) { - if (isJB()) { - this.realWidth = width; - } else { - ShadowDisplayManager.changeDisplay(realObject.getDisplayId(), di -> di.logicalWidth = width); - } + ShadowDisplayManager.changeDisplay(realObject.getDisplayId(), di -> di.logicalWidth = width); } /** @@ -329,12 +211,7 @@ public class ShadowDisplay { * @param height the new height in pixels */ public void setRealHeight(int height) { - if (isJB()) { - this.realHeight = height; - } else { - ShadowDisplayManager.changeDisplay( - realObject.getDisplayId(), di -> di.logicalHeight = height); - } + ShadowDisplayManager.changeDisplay(realObject.getDisplayId(), di -> di.logicalHeight = height); } /** @@ -357,21 +234,7 @@ public class ShadowDisplay { * Surface#ROTATION_180}, {@link Surface#ROTATION_270} */ public void setRotation(int rotation) { - if (isJB()) { - this.rotation = rotation; - } else { - ShadowDisplayManager.changeDisplay(realObject.getDisplayId(), di -> di.rotation = rotation); - } - } - - /** - * Changes the pixel format for this display. - * - * @deprecated This method is deprecated and will be removed in Robolectric 3.7. - */ - @Deprecated - public void setPixelFormat(int pixelFormat) { - this.pixelFormat = pixelFormat; + ShadowDisplayManager.changeDisplay(realObject.getDisplayId(), di -> di.rotation = rotation); } /** @@ -384,9 +247,7 @@ public class ShadowDisplay { * Display#STATE_DOZE}, {@link Display#STATE_DOZE_SUSPEND}, or {@link Display#STATE_UNKNOWN}. */ public void setState(int state) { - if (!isJB()) { - ShadowDisplayManager.changeDisplay(realObject.getDisplayId(), di -> di.state = state); - } + ShadowDisplayManager.changeDisplay(realObject.getDisplayId(), di -> di.state = state); } /** @@ -450,34 +311,9 @@ public class ShadowDisplay { realObject.getDisplayId(), displayConfig -> displayConfig.displayCutout = displayCutout); } - private boolean isJB() { - return RuntimeEnvironment.getApiLevel() == JELLY_BEAN; - } - - void configureForJBOnly(Configuration configuration, DisplayMetrics displayMetrics) { - int widthPx = (int) (configuration.screenWidthDp * displayMetrics.density); - int heightPx = (int) (configuration.screenHeightDp * displayMetrics.density); - - name = "Built-in screen"; - displayId = 0; - width = widthPx; - height = heightPx; - realWidth = widthPx; - realHeight = heightPx; - densityDpi = displayMetrics.densityDpi; - xdpi = (float) displayMetrics.densityDpi; - ydpi = (float) displayMetrics.densityDpi; - scaledDensity = displayMetrics.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE; - rotation = - configuration.orientation == Configuration.ORIENTATION_PORTRAIT - ? Surface.ROTATION_0 - : Surface.ROTATION_90; - } - /** Reflector interface for {@link Display}'s internals. */ @ForType(Display.class) interface _Display_ { - @Direct void getMetrics(DisplayMetrics outMetrics); @@ -485,14 +321,8 @@ public class ShadowDisplay { void getRealMetrics(DisplayMetrics outMetrics); @Direct - int getDisplayId(); - - @Direct float getRefreshRate(); - @Direct - int getPixelFormat(); - @Accessor("mFlags") void setFlags(int flags); } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDisplayEventReceiver.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDisplayEventReceiver.java index 145a37736..50a53d9d3 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDisplayEventReceiver.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDisplayEventReceiver.java @@ -1,7 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.JELLY_BEAN; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; import static android.os.Build.VERSION_CODES.KITKAT; import static android.os.Build.VERSION_CODES.KITKAT_WATCH; import static android.os.Build.VERSION_CODES.LOLLIPOP_MR1; @@ -133,7 +131,7 @@ public class ShadowDisplayEventReceiver { nativeObjRegistry.getNativeObject(receiverPtr).scheduleVsync(); } - @Implementation(minSdk = JELLY_BEAN_MR1, maxSdk = R) + @Implementation(maxSdk = R) protected void dispose(boolean finalized) { CloseGuard closeGuard = displayEventReceiverReflector.getCloseGuard(); // Suppresses noisy CloseGuard warning @@ -144,9 +142,7 @@ public class ShadowDisplayEventReceiver { } protected void onVsync() { - if (RuntimeEnvironment.getApiLevel() <= JELLY_BEAN) { - displayEventReceiverReflector.onVsync(ShadowSystem.nanoTime(), 1); - } else if (RuntimeEnvironment.getApiLevel() < Q) { + if (RuntimeEnvironment.getApiLevel() < Q) { displayEventReceiverReflector.onVsync( ShadowSystem.nanoTime(), 0, /* SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN */ 1); } else if (RuntimeEnvironment.getApiLevel() < S) { diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDisplayManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDisplayManager.java index 776f501c3..e03590f32 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDisplayManager.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDisplayManager.java @@ -2,7 +2,6 @@ package org.robolectric.shadows; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.content.res.Configuration.ORIENTATION_PORTRAIT; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; import static android.os.Build.VERSION_CODES.P; import static java.util.Objects.requireNonNull; import static org.robolectric.shadow.api.Shadow.extract; @@ -10,6 +9,8 @@ import static org.robolectric.shadow.api.Shadow.invokeConstructor; import static org.robolectric.shadows.ShadowLooper.shadowMainLooper; import static org.robolectric.util.reflector.Reflector.reflector; +import android.annotation.Nullable; +import android.annotation.RequiresApi; import android.content.Context; import android.content.res.Configuration; import android.hardware.display.BrightnessChangeEvent; @@ -20,8 +21,6 @@ import android.util.DisplayMetrics; import android.view.Display; import android.view.DisplayInfo; import android.view.Surface; -import androidx.annotation.Nullable; -import androidx.annotation.RequiresApi; import com.google.auto.value.AutoBuilder; import java.util.HashMap; import java.util.List; @@ -43,7 +42,7 @@ import org.robolectric.util.reflector.ForType; * For tests, display properties may be changed and devices may be added or removed * programmatically. */ -@Implements(value = DisplayManager.class, minSdk = JELLY_BEAN_MR1, looseSignatures = true) +@Implements(value = DisplayManager.class, looseSignatures = true) public class ShadowDisplayManager { @RealObject private DisplayManager realDisplayManager; @@ -96,12 +95,21 @@ public class ShadowDisplayManager { return id; } + static IllegalStateException configureDefaultDisplayCallstack; + /** internal only */ public static void configureDefaultDisplay( Configuration configuration, DisplayMetrics displayMetrics) { ShadowDisplayManagerGlobal shadowDisplayManagerGlobal = getShadowDisplayManagerGlobal(); - if (DisplayManagerGlobal.getInstance().getDisplayIds().length != 0) { - throw new IllegalStateException("this method should only be called by Robolectric"); + if (DisplayManagerGlobal.getInstance().getDisplayIds().length == 0) { + configureDefaultDisplayCallstack = + new IllegalStateException("configureDefaultDisplay should only be called once"); + } else { + configureDefaultDisplayCallstack.initCause( + new IllegalStateException( + "configureDefaultDisplay was called a second time", + configureDefaultDisplayCallstack)); + throw configureDefaultDisplayCallstack; } shadowDisplayManagerGlobal.addDisplay( @@ -143,7 +151,7 @@ public class ShadowDisplayManager { displayInfo.state = Display.STATE_ON; } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + if (Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT) { displayInfo.getAppMetrics(displayMetrics); } @@ -338,10 +346,6 @@ public class ShadowDisplayManager { } private static ShadowDisplayManagerGlobal getShadowDisplayManagerGlobal() { - if (Build.VERSION.SDK_INT < JELLY_BEAN_MR1) { - throw new UnsupportedOperationException("multiple displays not supported in Jelly Bean"); - } - return extract(DisplayManagerGlobal.getInstance()); } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDisplayManagerGlobal.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDisplayManagerGlobal.java index d0fdab014..5681eb218 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDisplayManagerGlobal.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDisplayManagerGlobal.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; import static android.os.Build.VERSION_CODES.O_MR1; import static android.os.Build.VERSION_CODES.P; import static org.robolectric.util.reflector.Reflector.reflector; @@ -38,7 +37,6 @@ import org.robolectric.util.reflector.ForType; @Implements( value = DisplayManagerGlobal.class, isInAndroidSdk = false, - minSdk = JELLY_BEAN_MR1, looseSignatures = true) public class ShadowDisplayManagerGlobal { private static DisplayManagerGlobal instance; diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDistanceMeasurementManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDistanceMeasurementManager.java new file mode 100644 index 000000000..0f0e4836e --- /dev/null +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDistanceMeasurementManager.java @@ -0,0 +1,159 @@ +package org.robolectric.shadows; + +import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE; + +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothStatusCodes; +import android.bluetooth.IBluetoothGatt; +import android.bluetooth.le.DistanceMeasurementManager; +import android.bluetooth.le.DistanceMeasurementMethod; +import android.bluetooth.le.DistanceMeasurementParams; +import android.bluetooth.le.DistanceMeasurementResult; +import android.bluetooth.le.DistanceMeasurementSession; +import android.content.AttributionSource; +import android.os.CancellationSignal; +import android.os.ParcelUuid; +import com.google.common.collect.ImmutableList; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.UUID; +import java.util.concurrent.Executor; +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; +import org.robolectric.util.ReflectionHelpers; + +/** Shadow implementation of {@link DistanceMeasurementManager}. */ +@Implements( + value = DistanceMeasurementManager.class, + minSdk = UPSIDE_DOWN_CAKE, + isInAndroidSdk = false) +public class ShadowDistanceMeasurementManager { + + private final Map<BluetoothDevice, DistanceMeasurementSession> sessionMap = new HashMap<>(); + private final Map<BluetoothDevice, DistanceMeasurementSession.Callback> sessionCallbackMap = + new HashMap<>(); + private List<DistanceMeasurementMethod> supportedMethods = new ArrayList<>(); + + @Implementation + protected List<DistanceMeasurementMethod> getSupportedMethods() { + return supportedMethods; + } + + @Implementation + protected CancellationSignal startMeasurementSession( + DistanceMeasurementParams params, + Executor executor, + DistanceMeasurementSession.Callback callback) { + IBluetoothGatt gatt = ReflectionHelpers.createNullProxy(IBluetoothGatt.class); + sessionMap.put( + params.getDevice(), + new DistanceMeasurementSession( + gatt, + new ParcelUuid(UUID.randomUUID()), + params, + executor, + AttributionSource.myAttributionSource(), + callback)); + sessionCallbackMap.put(params.getDevice(), callback); + + return new CancellationSignal(); + } + + /** + * Simulates {@link DistanceMeasurementSession.Callback#onResult(BluetoothDevice, + * DistanceMeasurementResult)}. + * + * @param device Remote {@link BluetoothDevice} to which this device is measuring distance. + * @param result {@link DistanceMeasurementResult} which should be passed to the callback. + */ + public void simulateOnResult(BluetoothDevice device, DistanceMeasurementResult result) { + DistanceMeasurementSession session = sessionMap.get(device); + DistanceMeasurementSession.Callback sessionCallback = sessionCallbackMap.get(device); + if (session == null || sessionCallback == null) { + throw new NoSuchElementException("Session or session callback is missing."); + } + sessionCallback.onStarted(session); + sessionCallback.onResult(device, result); + } + + /** + * Simulates {@link DistanceMeasurementSession.Callback#onStartFail(int)} with an error. + * + * @param device Remote {@link BluetoothDevice} to which this device is measuring distance. + * @param error Error to simulate. One of {@link DistanceMeasurementSession.Callback.Reason}. + */ + public void simulateOnStartFailError(BluetoothDevice device, int error) { + DistanceMeasurementSession.Callback sessionCallback = sessionCallbackMap.get(device); + if (sessionCallback == null) { + throw new NoSuchElementException("Session callback is missing."); + } + sessionCallback.onStartFail(error); + + sessionMap.remove(device); + sessionCallbackMap.remove(device); + } + + /** + * Simulates {@link DistanceMeasurementSession.Callback#onStopped(DistanceMeasurementSession, + * int)} with an error. + * + * @param device Remote {@link BluetoothDevice} to which this device is measuring distance. + * @param error Error to simulate. One of {@link DistanceMeasurementSession.Callback.Reason}. + */ + public void simulateOnStoppedError(BluetoothDevice device, int error) { + DistanceMeasurementSession session = sessionMap.get(device); + DistanceMeasurementSession.Callback sessionCallback = sessionCallbackMap.get(device); + if (session == null || sessionCallback == null) { + throw new NoSuchElementException("Session or session callback is missing."); + } + sessionCallback.onStarted(session); + sessionCallback.onStopped(session, error); + + sessionMap.remove(device); + sessionCallbackMap.remove(device); + } + + /** + * Simulates {@link DistanceMeasurementSession.Callback#onStopped(DistanceMeasurementSession, + * int)} without an error. + * + * @param device Remote {@link BluetoothDevice} to which this device is measuring distance. + */ + public void simulateSuccessfulTermination(BluetoothDevice device) { + DistanceMeasurementSession session = sessionMap.get(device); + DistanceMeasurementSession.Callback sessionCallback = sessionCallbackMap.get(device); + if (session == null || sessionCallback == null) { + throw new NoSuchElementException("Session or session callback is missing."); + } + sessionCallback.onStarted(session); + sessionCallback.onStopped(session, BluetoothStatusCodes.REASON_LOCAL_APP_REQUEST); + + sessionMap.remove(device); + sessionCallbackMap.remove(device); + } + + /** + * Simulates {@link DistanceMeasurementSession.Callback#onStopped(DistanceMeasurementSession, + * int)} after a timeout. + */ + public void simulateTimeout(BluetoothDevice device) { + DistanceMeasurementSession session = sessionMap.get(device); + DistanceMeasurementSession.Callback sessionCallback = sessionCallbackMap.get(device); + if (session == null || sessionCallback == null) { + throw new NoSuchElementException("Session or session callback is missing."); + } + sessionCallback.onStarted(session); + sessionCallback.onStopped(session, BluetoothStatusCodes.ERROR_TIMEOUT); + + sessionMap.remove(device); + sessionCallbackMap.remove(device); + } + + /** Sets a list of supported {@link DistanceMeasurementMethod}. */ + public void setSupportedMethods(List<DistanceMeasurementMethod> methods) { + supportedMethods = ImmutableList.copyOf(methods); + } +} diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDownloadManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDownloadManager.java index aadbf8005..36830fd23 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDownloadManager.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDownloadManager.java @@ -37,6 +37,8 @@ public class ShadowDownloadManager { protected long enqueue(DownloadManager.Request request) { queueCounter++; requestMap.put(queueCounter, request); + ShadowRequest shadowRequest = Shadow.extract(request); + shadowRequest.setId(queueCounter); return queueCounter; } @@ -141,6 +143,7 @@ public class ShadowDownloadManager { private int status; private long totalSize; private long bytesSoFar; + private long id; public int getStatus() { return this.status; @@ -166,6 +169,14 @@ public class ShadowDownloadManager { this.bytesSoFar = bytesSoFar; } + public long getId() { + return this.id; + } + + public void setId(long id) { + this.id = id; + } + public Uri getUri() { return getFieldReflectively("mUri", realObject, Uri.class); } @@ -264,6 +275,7 @@ public class ShadowDownloadManager { private static final int COLUMN_INDEX_TITLE = 6; private static final int COLUMN_INDEX_TOTAL_SIZE = 7; private static final int COLUMN_INDEX_BYTES_SO_FAR = 8; + private static final int COLUMN_INDEX_ID = 9; public List<DownloadManager.Request> requests = new ArrayList<>(); private int positionIndex = -1; @@ -322,6 +334,8 @@ public class ShadowDownloadManager { return COLUMN_INDEX_TOTAL_SIZE; } else if (DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR.equals(columnName)) { return COLUMN_INDEX_BYTES_SO_FAR; + } else if (DownloadManager.COLUMN_ID.equals(columnName)) { + return COLUMN_INDEX_ID; } return -1; @@ -393,6 +407,8 @@ public class ShadowDownloadManager { return request.getTotalSize(); } else if (columnIndex == COLUMN_INDEX_BYTES_SO_FAR) { return request.getBytesSoFar(); + } else if (columnIndex == COLUMN_INDEX_ID) { + return request.getId(); } return 0; } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowEnvironment.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowEnvironment.java index 91a855af9..2ce293834 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowEnvironment.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowEnvironment.java @@ -1,8 +1,6 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2; -import static android.os.Build.VERSION_CODES.KITKAT; import static android.os.Build.VERSION_CODES.LOLLIPOP; import static android.os.Build.VERSION_CODES.M; import static android.os.Build.VERSION_CODES.Q; @@ -122,7 +120,7 @@ public class ShadowEnvironment { return EXTERNAL_CACHE_DIR.toFile(); } - @Implementation(minSdk = KITKAT) + @Implementation protected static File[] buildExternalStorageAppCacheDirs(String packageName) { Path externalStorageDirectoryPath = getExternalStorageDirectory().toPath(); // Add cache directory in path. @@ -199,7 +197,7 @@ public class ShadowEnvironment { return exists != null ? exists : false; } - @Implementation(minSdk = KITKAT) + @Implementation protected static String getStorageState(File directory) { Path directoryPath = directory.toPath(); for (Map.Entry<Path, String> entry : storageState.entrySet()) { @@ -291,15 +289,7 @@ public class ShadowEnvironment { } } - if (RuntimeEnvironment.getApiLevel() >= JELLY_BEAN_MR1 - && RuntimeEnvironment.getApiLevel() < KITKAT) { - if (externalDirs.size() == 1 && externalFileDir != null) { - Environment.UserEnvironment userEnvironment = - ReflectionHelpers.getStaticField(Environment.class, "sCurrentUser"); - reflector(_UserEnvironment_.class, userEnvironment) - .setExternalStorageAndroidData(externalFileDir.toFile()); - } - } else if (RuntimeEnvironment.getApiLevel() >= KITKAT && RuntimeEnvironment.getApiLevel() < M) { + if (RuntimeEnvironment.getApiLevel() < M) { Environment.UserEnvironment userEnvironment = ReflectionHelpers.getStaticField(Environment.class, "sCurrentUser"); reflector(_UserEnvironment_.class, userEnvironment) @@ -321,8 +311,7 @@ public class ShadowEnvironment { storageState.put(directory.toPath(), state); } - @Implements(className = "android.os.Environment$UserEnvironment", isInAndroidSdk = false, - minSdk = JELLY_BEAN_MR1) + @Implements(className = "android.os.Environment$UserEnvironment", isInAndroidSdk = false) public static class ShadowUserEnvironment { @Implementation(minSdk = M) diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowFontBuilder.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowFontBuilder.java index 9bae6df15..50f8c7d60 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowFontBuilder.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowFontBuilder.java @@ -4,10 +4,10 @@ import static android.os.Build.VERSION_CODES.Q; import static android.os.Build.VERSION_CODES.R; import static android.os.Build.VERSION_CODES.S; +import android.annotation.RequiresApi; import android.content.res.AssetManager; import android.graphics.fonts.Font; import android.graphics.fonts.FontStyle; -import androidx.annotation.RequiresApi; import com.google.common.base.Preconditions; import java.io.IOException; import java.io.InputStream; diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowHidlSupport.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowHidlSupport.java index 78f88891e..82572b689 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowHidlSupport.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowHidlSupport.java @@ -5,6 +5,7 @@ import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; import org.robolectric.versioning.AndroidVersions.V; +/** Activates Hidl support */ @SuppressWarnings("NewApi") @Implements(value = HidlSupport.class, isInAndroidSdk = false, minSdk = V.SDK_INT) public class ShadowHidlSupport { diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowIAppOpsService.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowIAppOpsService.java index ee0da5c72..d23459c78 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowIAppOpsService.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowIAppOpsService.java @@ -1,7 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2; - import android.os.IBinder; import com.android.internal.app.IAppOpsService; import org.robolectric.annotation.Implementation; @@ -13,7 +11,7 @@ public class ShadowIAppOpsService { @Implements(value = IAppOpsService.Stub.class, isInAndroidSdk = false) public static class ShadowStub { - @Implementation(minSdk = JELLY_BEAN_MR2) + @Implementation public static IAppOpsService asInterface(IBinder obj) { return ReflectionHelpers.createNullProxy(IAppOpsService.class); } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowICU.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowICU.java index d09d1c0e0..941e260fb 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowICU.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowICU.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2; import static android.os.Build.VERSION_CODES.KITKAT_WATCH; import static android.os.Build.VERSION_CODES.LOLLIPOP; import static android.os.Build.VERSION_CODES.N; @@ -37,12 +36,18 @@ public class ShadowICU { switch (skeleton) { case "jmm": return getjmmPattern(locale); + case "yMMMd": // This is from {@code DatePickerDefaults.YearAbbrMonthDaySkeleton} + return "MMM d, y"; + case "yMMMMEEEEd": // This is from {@code DatePickerDefaults.YearMonthWeekdayDaySkeleton} + return "EEEE, MMMM d, y"; + case "yMMMM": // This is from {@code DatePickerDefaults.YearMonthSkeleton} + return "MMMM y"; default: return skeleton; } } - @Implementation(minSdk = JELLY_BEAN_MR2, maxSdk = KITKAT_WATCH) + @Implementation(maxSdk = KITKAT_WATCH) public static String getBestDateTimePattern(String skeleton, String locale) { return skeleton; } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowIcon.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowIcon.java index 72aa2fe32..04d255eb4 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowIcon.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowIcon.java @@ -3,6 +3,7 @@ package org.robolectric.shadows; import static android.os.Build.VERSION_CODES.M; import static org.robolectric.util.reflector.Reflector.reflector; +import android.annotation.Nullable; import android.content.Context; import android.graphics.Bitmap; import android.graphics.drawable.Drawable; @@ -11,7 +12,6 @@ import android.graphics.drawable.Icon.OnDrawableLoadedListener; import android.net.Uri; import android.os.Handler; import android.os.Message; -import androidx.annotation.Nullable; import java.util.concurrent.Executor; import org.robolectric.annotation.HiddenApi; import org.robolectric.annotation.Implementation; diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowImageReader.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowImageReader.java index 140f664be..9431560eb 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowImageReader.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowImageReader.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.KITKAT; import static android.os.Build.VERSION_CODES.S_V2; import static android.os.Build.VERSION_CODES.TIRAMISU; import static org.robolectric.util.reflector.Reflector.reflector; @@ -41,13 +40,13 @@ public class ShadowImageReader { @RealObject private ImageReader imageReader; private Canvas canvas; - @Implementation(minSdk = KITKAT) + @Implementation protected void close() { readerValid.set(false); openedImages.clear(); } - @Implementation(minSdk = KITKAT, maxSdk = S_V2) + @Implementation(maxSdk = S_V2) protected int nativeImageSetup(Image image) { if (!readerValid.get()) { throw new IllegalStateException("ImageReader closed."); @@ -75,12 +74,12 @@ public class ShadowImageReader { return nativeImageSetup((Image) image); } - @Implementation(minSdk = KITKAT) + @Implementation protected void nativeReleaseImage(Image i) { openedImages.remove(i); } - @Implementation(minSdk = KITKAT) + @Implementation protected Surface nativeGetSurface() { if (surface == null) { surface = new FakeSurface(); @@ -132,19 +131,19 @@ public class ShadowImageReader { public static class ShadowSurfaceImage { @RealObject Object surfaceImage; - @Implementation(minSdk = KITKAT) + @Implementation protected int getWidth() { ImageReader reader = ReflectionHelpers.getField(surfaceImage, "this$0"); return reader.getWidth(); } - @Implementation(minSdk = KITKAT) + @Implementation protected int getHeight() { ImageReader reader = ReflectionHelpers.getField(surfaceImage, "this$0"); return reader.getHeight(); } - @Implementation(minSdk = KITKAT) + @Implementation protected int getFormat() { ImageReader reader = ReflectionHelpers.getField(surfaceImage, "this$0"); return reader.getImageFormat(); diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowImsMmTelManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowImsMmTelManager.java index 8d447ed7c..aaa1dff22 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowImsMmTelManager.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowImsMmTelManager.java @@ -4,6 +4,8 @@ import static org.robolectric.util.reflector.Reflector.reflector; import android.Manifest; import android.annotation.CallbackExecutor; +import android.annotation.NonNull; +import android.annotation.RequiresApi; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.os.Build.VERSION_CODES; @@ -17,8 +19,6 @@ import android.telephony.ims.RegistrationManager; import android.telephony.ims.feature.MmTelFeature.MmTelCapabilities; import android.telephony.ims.stub.ImsRegistrationImplBase; import android.util.ArrayMap; -import androidx.annotation.NonNull; -import androidx.annotation.RequiresApi; import java.util.Map; import java.util.concurrent.Executor; import java.util.function.Consumer; @@ -41,7 +41,6 @@ import org.robolectric.util.reflector.Static; @Implements( value = ImsMmTelManager.class, minSdk = VERSION_CODES.Q, - looseSignatures = true, isInAndroidSdk = false) @SystemApi public class ShadowImsMmTelManager { diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowInCallService.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowInCallService.java index 4ab7b896d..c3b4a3e41 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowInCallService.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowInCallService.java @@ -3,6 +3,7 @@ package org.robolectric.shadows; import static android.os.Build.VERSION_CODES.M; import static android.os.Build.VERSION_CODES.N_MR1; import static android.os.Build.VERSION_CODES.P; +import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE; import static org.robolectric.shadow.api.Shadow.invokeConstructor; import static org.robolectric.util.reflector.Reflector.reflector; @@ -18,7 +19,6 @@ import android.telecom.InCallService; import android.telecom.ParcelableCall; import android.telecom.Phone; import com.android.internal.os.SomeArgs; -import com.android.internal.telecom.IInCallAdapter; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; import org.robolectric.annotation.RealObject; @@ -26,6 +26,7 @@ import org.robolectric.shadow.api.Shadow; import org.robolectric.util.ReflectionHelpers; import org.robolectric.util.ReflectionHelpers.ClassParameter; import org.robolectric.util.reflector.Accessor; +import org.robolectric.util.reflector.Constructor; import org.robolectric.util.reflector.Direct; import org.robolectric.util.reflector.ForType; @@ -37,14 +38,23 @@ public class ShadowInCallService extends ShadowService { private static final int MSG_SET_POST_DIAL_WAIT = 4; private static final int MSG_ON_CONNECTION_EVENT = 9; - private ShadowPhone shadowPhone; private boolean canAddCall; private boolean muted; private int audioRoute = CallAudioState.ROUTE_EARPIECE; private BluetoothDevice bluetoothDevice; private int supportedRouteMask; - @Implementation + /* Starting in Android V, the InCallService does not allow setting an InCallAdapter if Phone + * was already set. This is how the InCallService should be instantiated in tests: + * ``` + * InCallServiceController serviceController = + * Robolectric.buildService(InCallServiceImpl.class, intent).create(); + * IInCallService.Stub inCallServiceBinder = + * (IInCallService.Stub) serviceController.get().onBind(intent); + * inCallServiceBinder.setInCallAdapter(new InCallAdapterImpl()); + * ``` + * Do not rely on reflection for this and use the public APIs instead. */ + @Implementation(maxSdk = UPSIDE_DOWN_CAKE) protected void __constructor__() { InCallAdapter adapter = Shadow.newInstanceOf(InCallAdapter.class); Phone phone; @@ -60,13 +70,17 @@ public class ShadowInCallService extends ShadowService { ReflectionHelpers.callConstructor( Phone.class, ClassParameter.from(InCallAdapter.class, adapter)); } - shadowPhone = Shadow.extract(phone); ReflectionHelpers.setField(inCallService, "mPhone", phone); invokeConstructor(InCallService.class, inCallService); } + /** + * @deprecated Please add calls by adding a Call using {@link + * android.telecom.InCallService.InCallServiceBinder}. + */ + @Deprecated public void addCall(Call call) { - shadowPhone.addCall(call); + getShadowPhone().addCall(call); } public void addCall(ParcelableCall parcelableCall) { @@ -96,8 +110,12 @@ public class ShadowInCallService extends ShadowService { getHandler().obtainMessage(MSG_ON_CONNECTION_EVENT, args).sendToTarget(); } + /** + * @deprecated Please remove calls by invoking {@link Call#disconnect()}. + */ + @Deprecated public void removeCall(Call call) { - shadowPhone.removeCall(call); + getShadowPhone().removeCall(call); } @Implementation @@ -168,12 +186,34 @@ public class ShadowInCallService extends ShadowService { */ private boolean isInCallAdapterSet() { Phone phone = reflector(ReflectorInCallService.class, inCallService).getPhone(); + if (phone == null) { + return false; + } InCallAdapter inCallAdapter = reflector(ReflectorPhone.class, phone).getInCallAdapter(); Object internalAdapter = reflector(ReflectorInCallAdapter.class, inCallAdapter).getInternalInCallAdapter(); return internalAdapter != null; } + private ShadowPhone getShadowPhone() { + if (reflector(ReflectorInCallService.class, inCallService).getPhone() == null) { + setPhone(); + } + Phone phone = reflector(ReflectorInCallService.class, inCallService).getPhone(); + return Shadow.extract(phone); + } + + private void setPhone() { + InCallAdapter adapter = Shadow.newInstanceOf(InCallAdapter.class); + Phone phone; + if (VERSION.SDK_INT > N_MR1) { + phone = reflector(ReflectorPhone.class, inCallService).newInstance(adapter, "", 0); + } else { + phone = reflector(ReflectorPhone.class, inCallService).newInstance(adapter); + } + ReflectionHelpers.setField(inCallService, "mPhone", phone); + } + @ForType(InCallService.class) interface ReflectorInCallService { @Accessor("mHandler") @@ -199,6 +239,12 @@ public class ShadowInCallService extends ShadowService { interface ReflectorPhone { @Accessor("mInCallAdapter") InCallAdapter getInCallAdapter(); + + @Constructor + Phone newInstance(InCallAdapter inCallAdapter, String name, int type); + + @Constructor + Phone newInstance(InCallAdapter inCallAdapter); } @ForType(InCallAdapter.class) diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowIncidentManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowIncidentManager.java index d910f35c2..36c5aafac 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowIncidentManager.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowIncidentManager.java @@ -2,10 +2,10 @@ package org.robolectric.shadows; import static android.os.Build.VERSION_CODES.R; +import android.annotation.NonNull; import android.net.Uri; import android.os.IncidentManager; import android.os.IncidentManager.IncidentReport; -import androidx.annotation.NonNull; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowInputDevice.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowInputDevice.java index 70948566a..ea1efa2df 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowInputDevice.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowInputDevice.java @@ -1,7 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.KITKAT; - import android.view.InputDevice; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; @@ -25,12 +23,12 @@ public class ShadowInputDevice { return deviceName; } - @Implementation(minSdk = KITKAT) + @Implementation protected int getProductId() { return productId; } - @Implementation(minSdk = KITKAT) + @Implementation protected int getVendorId() { return vendorId; } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowInputEventReceiver.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowInputEventReceiver.java index 9082a5b0e..10d9e017d 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowInputEventReceiver.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowInputEventReceiver.java @@ -1,7 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; - import android.view.InputEventReceiver; import dalvik.system.CloseGuard; import org.robolectric.annotation.Implementation; @@ -24,7 +22,7 @@ public class ShadowInputEventReceiver { // ends up being rather spammy in test logs, so we no-op it. } - @Implementation(minSdk = JELLY_BEAN_MR1) + @Implementation protected void dispose(boolean finalized) { CloseGuard closeGuard = inputEventReceiverReflector.getCloseGuard(); // Suppresses noisy CloseGuard warning diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowInputManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowInputManager.java index e1e664759..30b4ea814 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowInputManager.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowInputManager.java @@ -1,7 +1,6 @@ package org.robolectric.shadows; import static android.os.Build.VERSION.SDK_INT; -import static android.os.Build.VERSION_CODES.KITKAT; import static android.os.Build.VERSION_CODES.R; import static android.os.Build.VERSION_CODES.TIRAMISU; import static java.util.concurrent.TimeUnit.MILLISECONDS; @@ -36,7 +35,7 @@ public class ShadowInputManager { return true; } - @Implementation(minSdk = KITKAT) + @Implementation protected boolean[] deviceHasKeys(int id, int[] keyCodes) { return new boolean[keyCodes.length]; } @@ -44,7 +43,21 @@ public class ShadowInputManager { /** Used in {@link InputDevice#getDeviceIds()} */ @Implementation protected int[] getInputDeviceIds() { - return new int[0]; + if (!ReflectionHelpers.hasField(InputManager.class, "mInputDevices")) { + return new int[0]; + } + + SparseArray<InputDevice> inputDevices = getInputDevices(); + if (inputDevices == null) { + return new int[0]; + } + + int[] ids = new int[inputDevices.size()]; + for (int i = 0; i < inputDevices.size(); i++) { + ids[i] = inputDevices.get(i).getId(); + } + + return ids; } @Implementation(maxSdk = TIRAMISU) diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowInputMethodManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowInputMethodManager.java index dde69ced6..093bb2d12 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowInputMethodManager.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowInputMethodManager.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; import static android.os.Build.VERSION_CODES.LOLLIPOP_MR1; import static android.os.Build.VERSION_CODES.M; import static android.os.Build.VERSION_CODES.N; @@ -13,7 +12,6 @@ import static org.robolectric.util.reflector.Reflector.reflector; import android.os.Bundle; import android.os.IBinder; -import android.os.Looper; import android.os.ResultReceiver; import android.util.SparseArray; import android.view.View; @@ -28,8 +26,6 @@ import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; import org.robolectric.annotation.Resetter; -import org.robolectric.util.ReflectionHelpers; -import org.robolectric.util.ReflectionHelpers.ClassParameter; import org.robolectric.util.reflector.Accessor; import org.robolectric.util.reflector.Direct; import org.robolectric.util.reflector.ForType; @@ -239,12 +235,7 @@ public class ShadowInputMethodManager { // Android has a bug pre M where peekInstance was dereferenced without a null check:- // https://github.com/aosp-mirror/platform_frameworks_base/commit/a046faaf38ad818e6b5e981a39fd7394cf7cee03 // So for earlier versions, just call through directly to getInstance() - if (RuntimeEnvironment.getApiLevel() <= JELLY_BEAN_MR1) { - return ReflectionHelpers.callStaticMethod( - InputMethodManager.class, - "getInstance", - ClassParameter.from(Looper.class, Looper.getMainLooper())); - } else if (RuntimeEnvironment.getApiLevel() <= LOLLIPOP_MR1) { + if (RuntimeEnvironment.getApiLevel() <= LOLLIPOP_MR1) { return InputMethodManager.getInstance(); } return reflector(_InputMethodManager_.class).peekInstance(); @@ -275,11 +266,7 @@ public class ShadowInputMethodManager { public static void reset() { int apiLevel = RuntimeEnvironment.getApiLevel(); _InputMethodManager_ _reflector = reflector(_InputMethodManager_.class); - if (apiLevel <= JELLY_BEAN_MR1) { - _reflector.setMInstance(null); - } else { - _reflector.setInstance(null); - } + _reflector.setInstance(null); if (apiLevel > P) { _reflector.getInstanceMap().clear(); } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowInsetsController.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowInsetsController.java index 1c47aaba2..0cd93e68e 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowInsetsController.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowInsetsController.java @@ -1,9 +1,9 @@ package org.robolectric.shadows; +import android.annotation.RequiresApi; import android.os.Build; import android.view.InsetsController; import android.view.WindowInsets; -import androidx.annotation.RequiresApi; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; import org.robolectric.annotation.ReflectorObject; diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowInstrumentation.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowInstrumentation.java index c66b0622d..aff4fd06e 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowInstrumentation.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowInstrumentation.java @@ -3,8 +3,6 @@ package org.robolectric.shadows; import static android.content.pm.PackageManager.MATCH_DEFAULT_ONLY; import static android.content.pm.PackageManager.PERMISSION_DENIED; import static android.content.pm.PackageManager.PERMISSION_GRANTED; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2; import static android.os.Build.VERSION_CODES.LOLLIPOP_MR1; import static android.os.Build.VERSION_CODES.M; import static android.os.Build.VERSION_CODES.N; @@ -16,6 +14,7 @@ import static com.google.common.util.concurrent.Futures.immediateFuture; import static com.google.common.util.concurrent.MoreExecutors.directExecutor; import static org.robolectric.util.reflector.Reflector.reflector; +import android.annotation.Nullable; import android.app.Activity; import android.app.ActivityThread; import android.app.Fragment; @@ -40,7 +39,6 @@ import android.os.Process; import android.os.UserHandle; import android.text.TextUtils; import android.util.Pair; -import androidx.annotation.Nullable; import com.google.common.collect.ImmutableList; import com.google.common.util.concurrent.AsyncFunction; import com.google.common.util.concurrent.Futures; @@ -75,7 +73,7 @@ import org.robolectric.util.reflector.Direct; import org.robolectric.util.reflector.ForType; import org.robolectric.util.reflector.WithType; -@Implements(value = Instrumentation.class, looseSignatures = true) +@Implements(value = Instrumentation.class) public class ShadowInstrumentation { @RealObject private Instrumentation realObject; @@ -198,7 +196,7 @@ public class ShadowInstrumentation { * * <p>Currently ignores the user. */ - @Implementation(minSdk = JELLY_BEAN_MR1, maxSdk = N_MR1) + @Implementation(maxSdk = N_MR1) protected ActivityResult execStartActivity( Context who, IBinder contextThread, @@ -234,7 +232,7 @@ public class ShadowInstrumentation { ShadowWindowManagerGlobal.setInTouchMode(inTouchMode); } - @Implementation(minSdk = JELLY_BEAN_MR2, maxSdk = M) + @Implementation(maxSdk = M) protected UiAutomation getUiAutomation() { return getUiAutomation(0); } @@ -1064,14 +1062,6 @@ public class ShadowInstrumentation { /** Reflector interface for {@link Instrumentation}'s internals. */ @ForType(Instrumentation.class) public interface _Instrumentation_ { - // <= JELLY_BEAN_MR1: - void init( - ActivityThread thread, - Context instrContext, - Context appContext, - ComponentName component, - @WithType("android.app.IInstrumentationWatcher") Object watcher); - // > JELLY_BEAN_MR1: void init( ActivityThread thread, diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLauncherApps.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLauncherApps.java index 0cbc9fb21..d640f23fc 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLauncherApps.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLauncherApps.java @@ -9,6 +9,8 @@ import static android.os.Build.VERSION_CODES.P; import static android.os.Build.VERSION_CODES.Q; import static org.robolectric.util.reflector.Reflector.reflector; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.content.ComponentName; import android.content.IntentSender; import android.content.pm.ApplicationInfo; @@ -26,8 +28,6 @@ import android.os.Looper; import android.os.Process; import android.os.UserHandle; import android.util.Pair; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import com.google.common.collect.HashMultimap; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyAssetManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyAssetManager.java index 33353358b..904712f50 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyAssetManager.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyAssetManager.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2; import static android.os.Build.VERSION_CODES.KITKAT_WATCH; import static android.os.Build.VERSION_CODES.LOLLIPOP; import static android.os.Build.VERSION_CODES.M; @@ -27,6 +26,7 @@ import android.os.ParcelFileDescriptor; import android.util.AttributeSet; import android.util.SparseArray; import android.util.TypedValue; +import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.Ordering; import dalvik.system.VMRuntime; import java.io.ByteArrayInputStream; @@ -621,7 +621,8 @@ public class ShadowLegacyAssetManager extends ShadowAssetManager { return 1; } - @HiddenApi @Implementation(minSdk = JELLY_BEAN_MR2, maxSdk = M) + @HiddenApi + @Implementation(maxSdk = M) final protected int addAssetPathNative(String path) { return addAssetPathNative(path, false); } @@ -1488,6 +1489,12 @@ public class ShadowLegacyAssetManager extends ShadowAssetManager { } } + @VisibleForTesting + @Override + long getNativePtr() { + return 0; + } + @ForType(AssetManager.class) interface AssetManagerReflector { diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyBitmap.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyBitmap.java index d345491dc..715bc87a0 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyBitmap.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyBitmap.java @@ -1,7 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; -import static android.os.Build.VERSION_CODES.KITKAT; import static android.os.Build.VERSION_CODES.M; import static android.os.Build.VERSION_CODES.O; import static android.os.Build.VERSION_CODES.Q; @@ -39,7 +37,6 @@ import java.util.Arrays; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; -import org.robolectric.annotation.RealObject; import org.robolectric.shadow.api.Shadow; import org.robolectric.util.ReflectionHelpers; import org.robolectric.versioning.AndroidVersions.U; @@ -55,7 +52,6 @@ public class ShadowLegacyBitmap extends ShadowBitmap { InputStream createdFromStream; FileDescriptor createdFromFileDescriptor; byte[] createdFromBytes; - @RealObject private Bitmap realBitmap; private Bitmap createdFromBitmap; private Bitmap scaledFromBitmap; private int createdFromX = -1; @@ -83,13 +79,13 @@ public class ShadowLegacyBitmap extends ShadowBitmap { return createBitmap((DisplayMetrics) null, width, height, config); } - @Implementation(minSdk = JELLY_BEAN_MR1) + @Implementation protected static Bitmap createBitmap( DisplayMetrics displayMetrics, int width, int height, Bitmap.Config config) { return createBitmap(displayMetrics, width, height, config, true); } - @Implementation(minSdk = JELLY_BEAN_MR1) + @Implementation protected static Bitmap createBitmap( DisplayMetrics displayMetrics, int width, @@ -199,7 +195,7 @@ public class ShadowLegacyBitmap extends ShadowBitmap { return createBitmap(null, colors, offset, stride, width, height, config); } - @Implementation(minSdk = JELLY_BEAN_MR1) + @Implementation protected static Bitmap createBitmap( DisplayMetrics displayMetrics, int[] colors, @@ -557,7 +553,7 @@ public class ShadowLegacyBitmap extends ShadowBitmap { return newBitmap; } - @Implementation(minSdk = KITKAT) + @Implementation protected final int getAllocationByteCount() { return getRowBytes() * getHeight(); } @@ -567,7 +563,7 @@ public class ShadowLegacyBitmap extends ShadowBitmap { return config; } - @Implementation(minSdk = KITKAT) + @Implementation protected void setConfig(Bitmap.Config config) { this.config = config; } @@ -624,12 +620,12 @@ public class ShadowLegacyBitmap extends ShadowBitmap { return extractAlpha(); } - @Implementation(minSdk = JELLY_BEAN_MR1) + @Implementation protected final boolean hasMipMap() { return hasMipMap; } - @Implementation(minSdk = JELLY_BEAN_MR1) + @Implementation protected final void setHasMipMap(boolean hasMipMap) { this.hasMipMap = hasMipMap; } @@ -639,7 +635,7 @@ public class ShadowLegacyBitmap extends ShadowBitmap { return width; } - @Implementation(minSdk = KITKAT) + @Implementation protected void setWidth(int width) { this.width = width; } @@ -649,7 +645,7 @@ public class ShadowLegacyBitmap extends ShadowBitmap { return height; } - @Implementation(minSdk = KITKAT) + @Implementation protected void setHeight(int height) { this.height = height; } @@ -761,7 +757,7 @@ public class ShadowLegacyBitmap extends ShadowBitmap { } } - @Implementation(minSdk = KITKAT) + @Implementation protected void reconfigure(int width, int height, Bitmap.Config config) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && this.config == Bitmap.Config.HARDWARE) { throw new IllegalStateException("native-backed bitmaps may not be reconfigured"); @@ -777,12 +773,12 @@ public class ShadowLegacyBitmap extends ShadowBitmap { bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); } - @Implementation(minSdk = KITKAT) + @Implementation protected boolean isPremultiplied() { return requestPremultiplied && hasAlpha(); } - @Implementation(minSdk = KITKAT) + @Implementation protected void setPremultiplied(boolean isPremultiplied) { this.requestPremultiplied = isPremultiplied; } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyCanvas.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyCanvas.java index 9c63ed3d4..0db52d49f 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyCanvas.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyCanvas.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.KITKAT; import static android.os.Build.VERSION_CODES.KITKAT_WATCH; import static android.os.Build.VERSION_CODES.LOLLIPOP; import static android.os.Build.VERSION_CODES.LOLLIPOP_MR1; @@ -478,7 +477,7 @@ public class ShadowLegacyCanvas extends ShadowCanvas { getNativeCanvas().restoreToCount(saveCount); } - @Implementation(minSdk = KITKAT) + @Implementation protected void release() { nativeObjectRegistry.unregister(getNativeId()); canvasReflector.release(); diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyLooper.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyLooper.java index 2fb348ebd..e7069b480 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyLooper.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyLooper.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2; import static org.robolectric.RuntimeEnvironment.isMainThread; import static org.robolectric.shadow.api.Shadow.invokeConstructor; import static org.robolectric.util.ReflectionHelpers.ClassParameter.from; @@ -142,7 +141,7 @@ public class ShadowLegacyLooper extends ShadowLooper { quitUnchecked(); } - @Implementation(minSdk = JELLY_BEAN_MR2) + @Implementation protected void quitSafely() { quit(); } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyMatrix.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyMatrix.java index 1401816df..b72627e5f 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyMatrix.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyMatrix.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.KITKAT; import static android.os.Build.VERSION_CODES.LOLLIPOP; import android.graphics.Matrix; @@ -378,7 +377,7 @@ public class ShadowLegacyMatrix extends ShadowMatrix { } } - @Implementation(minSdk = KITKAT) + @Implementation @Override public int hashCode() { return Objects.hashCode(simpleMatrix); diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyMessageQueue.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyMessageQueue.java index 9934fb4ef..5763ef5f8 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyMessageQueue.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyMessageQueue.java @@ -1,7 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2; -import static android.os.Build.VERSION_CODES.KITKAT; import static android.os.Build.VERSION_CODES.KITKAT_WATCH; import static android.os.Build.VERSION_CODES.LOLLIPOP; import static android.os.Build.VERSION_CODES.LOLLIPOP_MR1; @@ -55,7 +53,7 @@ public class ShadowLegacyMessageQueue extends ShadowMessageQueue { } @HiddenApi - @Implementation(minSdk = JELLY_BEAN_MR2, maxSdk = KITKAT_WATCH) + @Implementation(maxSdk = KITKAT_WATCH) public static void nativeDestroy(int ptr) { nativeDestroy((long) ptr); } @@ -64,7 +62,7 @@ public class ShadowLegacyMessageQueue extends ShadowMessageQueue { protected static void nativeDestroy(long ptr) {} @HiddenApi - @Implementation(minSdk = KITKAT, maxSdk = KITKAT_WATCH) + @Implementation(maxSdk = KITKAT_WATCH) public static boolean nativeIsIdling(int ptr) { return nativeIsIdling((long) ptr); } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyPath.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyPath.java index b4f113a4a..4f2040826 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyPath.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyPath.java @@ -1,7 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.JELLY_BEAN; -import static android.os.Build.VERSION_CODES.KITKAT; import static android.os.Build.VERSION_CODES.LOLLIPOP; import static org.robolectric.shadow.api.Shadow.extract; import static org.robolectric.shadows.ShadowPath.Point.Type.LINE_TO; @@ -174,7 +172,7 @@ public class ShadowLegacyPath extends ShadowPath { mPath.append(shadowSrc.mPath, false /*connect*/); } - @Implementation(minSdk = KITKAT) + @Implementation protected boolean op(Path path1, Path path2, Path.Op op) { Log.w(TAG, "android.graphics.Path#op() not supported yet."); return false; @@ -406,12 +404,12 @@ public class ShadowLegacyPath extends ShadowPath { false); } - @Implementation(minSdk = JELLY_BEAN) + @Implementation protected void addRoundRect(RectF rect, float rx, float ry, Direction dir) { addRoundRect(rect.left, rect.top, rect.right, rect.bottom, rx, ry, dir); } - @Implementation(minSdk = JELLY_BEAN) + @Implementation protected void addRoundRect(RectF rect, float[] radii, Direction dir) { if (rect == null) { throw new NullPointerException("need rect parameter"); diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacySystemClock.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacySystemClock.java index 11c2f9620..319b89586 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacySystemClock.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacySystemClock.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; import static android.os.Build.VERSION_CODES.P; import android.os.SystemClock; @@ -59,7 +58,7 @@ public class ShadowLegacySystemClock extends ShadowSystemClock { return uptimeMillis(); } - @Implementation(minSdk = JELLY_BEAN_MR1) + @Implementation protected static long elapsedRealtimeNanos() { return elapsedRealtime() * MILLIS_PER_NANO; } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLinux.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLinux.java index 41a109c7e..58405af44 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLinux.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLinux.java @@ -1,6 +1,7 @@ package org.robolectric.shadows; import static android.os.Build.VERSION_CODES.N_MR1; +import static android.os.Build.VERSION_CODES.R; import android.os.Build; import android.system.ErrnoException; @@ -79,6 +80,18 @@ public class ShadowLinux { } } + @Implementation(minSdk = R) + protected FileDescriptor memfd_create(String name, int flags) throws ErrnoException { + try { + File tempFile = File.createTempFile(name, /* suffix= */ "robo_memfd"); + tempFile.deleteOnExit(); + RandomAccessFile randomAccessFile = new RandomAccessFile(tempFile, /* mode= */ "rw"); + return randomAccessFile.getFD(); + } catch (IOException e) { + throw new ErrnoException("memfd_create", OsConstants.EIO, e); + } + } + @Implementation protected int pread(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, long offset) throws ErrnoException, InterruptedIOException { diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLocaleData.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLocaleData.java index 803495e08..ee3748664 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLocaleData.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLocaleData.java @@ -1,8 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2; -import static android.os.Build.VERSION_CODES.KITKAT; import static android.os.Build.VERSION_CODES.LOLLIPOP; import static android.os.Build.VERSION_CODES.LOLLIPOP_MR1; import static android.os.Build.VERSION_CODES.M; @@ -62,19 +59,17 @@ public class ShadowLocaleData { }; _LocaleData_ localDataReflector = reflector(_LocaleData_.class, localeData); - if (getApiLevel() >= JELLY_BEAN_MR1) { - localeData.tinyMonthNames = - new String[] {"J", "F", "M", "A", "M", "J", "J", "A", "S", "O", "N", "D"}; - localeData.tinyStandAloneMonthNames = localeData.tinyMonthNames; - localeData.tinyWeekdayNames = new String[] {"", "S", "M", "T", "W", "T", "F", "S"}; - localeData.tinyStandAloneWeekdayNames = localeData.tinyWeekdayNames; - - if (getApiLevel() <= R) { - localDataReflector.setYesterday("Yesterday"); - } - localeData.today = "Today"; - localeData.tomorrow = "Tomorrow"; + localeData.tinyMonthNames = + new String[] {"J", "F", "M", "A", "M", "J", "J", "A", "S", "O", "N", "D"}; + localeData.tinyStandAloneMonthNames = localeData.tinyMonthNames; + localeData.tinyWeekdayNames = new String[] {"", "S", "M", "T", "W", "T", "F", "S"}; + localeData.tinyStandAloneWeekdayNames = localeData.tinyWeekdayNames; + + if (getApiLevel() <= R) { + localDataReflector.setYesterday("Yesterday"); } + localeData.today = "Today"; + localeData.tomorrow = "Tomorrow"; localeData.longStandAloneMonthNames = localeData.longMonthNames; localeData.shortStandAloneMonthNames = localeData.shortMonthNames; @@ -97,7 +92,7 @@ public class ShadowLocaleData { if (getApiLevel() >= M) { localeData.timeFormat_hm = "h:mm a"; localeData.timeFormat_Hm = "HH:mm"; - } else if (getApiLevel() >= JELLY_BEAN_MR2) { + } else { localDataReflector.setTimeFormat12("h:mm a"); localDataReflector.setTimeFormat24("HH:mm"); } @@ -106,7 +101,7 @@ public class ShadowLocaleData { localDataReflector.setLongDateFormat("MMMM d, y"); localDataReflector.setMediumDateFormat("MMM d, y"); localDataReflector.setShortDateFormat("M/d/yy"); - if (getApiLevel() >= KITKAT && getApiLevel() < M) { + if (getApiLevel() < M) { localDataReflector.setShortDateFormat4("M/d/yyyy"); } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLocaleManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLocaleManager.java index d96a1e2c4..47843b53c 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLocaleManager.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLocaleManager.java @@ -1,11 +1,11 @@ package org.robolectric.shadows; +import android.annotation.RequiresApi; import android.app.LocaleManager; import android.content.res.Configuration; import android.content.res.Resources; import android.os.Build.VERSION_CODES; import android.os.LocaleList; -import androidx.annotation.RequiresApi; import java.util.HashMap; import java.util.HashSet; import java.util.Map; diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLocationManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLocationManager.java index 57fc6e943..d33e639c6 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLocationManager.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLocationManager.java @@ -12,6 +12,8 @@ import static android.provider.Settings.Secure.LOCATION_MODE_SENSORS_ONLY; import static android.provider.Settings.Secure.LOCATION_PROVIDERS_ALLOWED; import static java.util.concurrent.TimeUnit.NANOSECONDS; +import android.annotation.Nullable; +import android.annotation.RequiresApi; import android.app.PendingIntent; import android.app.PendingIntent.CanceledException; import android.content.Context; @@ -39,9 +41,7 @@ import android.os.WorkSource; import android.provider.Settings.Secure; import android.text.TextUtils; import android.util.Log; -import androidx.annotation.GuardedBy; -import androidx.annotation.Nullable; -import androidx.annotation.RequiresApi; +import com.android.internal.annotations.GuardedBy; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import java.lang.reflect.Constructor; @@ -370,11 +370,6 @@ public class ShadowLocationManager { @Implementation @Nullable protected LocationProvider getProvider(String name) { - if (RuntimeEnvironment.getApiLevel() < VERSION_CODES.KITKAT) { - // jelly bean has no way to properly construct a LocationProvider, we give up - return null; - } - ProviderEntry providerEntry = getProviderEntry(name); if (providerEntry == null) { return null; @@ -813,7 +808,7 @@ public class ShadowLocationManager { request.getProvider(), new RoboLocationRequest(request), executor, listener); } - @Implementation(minSdk = VERSION_CODES.KITKAT) + @Implementation protected void requestLocationUpdates( @Nullable LocationRequest request, LocationListener listener, Looper looper) { if (request == null) { @@ -833,7 +828,7 @@ public class ShadowLocationManager { listener); } - @Implementation(minSdk = VERSION_CODES.KITKAT) + @Implementation protected void requestLocationUpdates( @Nullable LocationRequest request, PendingIntent pendingIntent) { if (request == null) { @@ -915,7 +910,6 @@ public class ShadowLocationManager { * <p>Prior to Android S {@link LocationRequest} equality is not well defined, so prefer using * {@link #getLegacyLocationRequests(String)} instead if equality is required for testing. */ - @RequiresApi(VERSION_CODES.KITKAT) public List<LocationRequest> getLocationRequests(String provider) { ProviderEntry providerEntry = getProviderEntry(provider); if (providerEntry == null) { @@ -1802,7 +1796,6 @@ public class ShadowLocationManager { private final float minUpdateDistanceMeters; private final boolean singleShot; - @RequiresApi(VERSION_CODES.KITKAT) public RoboLocationRequest(LocationRequest locationRequest) { this.locationRequest = Objects.requireNonNull(locationRequest); intervalMillis = 0; @@ -1812,20 +1805,15 @@ public class ShadowLocationManager { public RoboLocationRequest( String provider, long intervalMillis, float minUpdateDistanceMeters, boolean singleShot) { - if (RuntimeEnvironment.getApiLevel() >= VERSION_CODES.KITKAT) { - locationRequest = - LocationRequest.createFromDeprecatedProvider( - provider, intervalMillis, minUpdateDistanceMeters, singleShot); - } else { - locationRequest = null; - } + locationRequest = + LocationRequest.createFromDeprecatedProvider( + provider, intervalMillis, minUpdateDistanceMeters, singleShot); this.intervalMillis = intervalMillis; this.minUpdateDistanceMeters = minUpdateDistanceMeters; this.singleShot = singleShot; } - @RequiresApi(VERSION_CODES.KITKAT) public LocationRequest getLocationRequest() { return (LocationRequest) Objects.requireNonNull(locationRequest); } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLog.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLog.java index 84df122b5..82e4e43d2 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLog.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLog.java @@ -1,6 +1,10 @@ package org.robolectric.shadows; +import static org.robolectric.util.reflector.Reflector.reflector; + import android.util.Log; +import android.util.Log.TerribleFailure; +import android.util.Log.TerribleFailureHandler; import com.google.common.base.Ascii; import com.google.common.base.Throwables; import com.google.common.collect.ImmutableList; @@ -13,9 +17,16 @@ import java.util.Map; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.function.Supplier; +import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; import org.robolectric.annotation.Resetter; +import org.robolectric.util.Util; +import org.robolectric.util.reflector.Accessor; +import org.robolectric.util.reflector.Constructor; +import org.robolectric.util.reflector.ForType; +import org.robolectric.util.reflector.Static; +import org.robolectric.versioning.AndroidVersions.L; /** Controls the behavior of {@link android.util.Log} and provides access to log messages. */ @Implements(Log.class) @@ -103,8 +114,18 @@ public class ShadowLog { @Implementation protected static int wtf(String tag, String msg, Throwable throwable) { addLog(Log.ASSERT, tag, msg, throwable); + // invoking the wtfHandler + TerribleFailure terribleFailure = + reflector(TerribleFailureReflector.class).newTerribleFailure(msg, throwable); if (wtfIsFatal) { - throw new TerribleFailure(msg, throwable); + Util.sneakyThrow(terribleFailure); + } + TerribleFailureHandler terribleFailureHandler = reflector(LogReflector.class).getWtfHandler(); + if (RuntimeEnvironment.getApiLevel() >= L.SDK_INT) { + terribleFailureHandler.onTerribleFailure(tag, terribleFailure, false); + } else { + reflector(TerribleFailureHandlerReflector.class, terribleFailureHandler) + .onTerribleFailure(tag, terribleFailure); } return 0; } @@ -339,14 +360,21 @@ public class ShadowLog { } } - /** - * Failure thrown when wtf_is_fatal is true and Log.wtf is called. This is a parallel - * implementation of framework's hidden API {@link android.util.Log#TerribleFailure}, to allow - * tests to catch / expect these exceptions. - */ - public static final class TerribleFailure extends RuntimeException { - TerribleFailure(String msg, Throwable cause) { - super(msg, cause); - } + @ForType(Log.class) + interface LogReflector { + @Static + @Accessor("sWtfHandler") + TerribleFailureHandler getWtfHandler(); + } + + @ForType(TerribleFailureHandler.class) + interface TerribleFailureHandlerReflector { + void onTerribleFailure(String tag, TerribleFailure what); + } + + @ForType(TerribleFailure.class) + interface TerribleFailureReflector { + @Constructor + TerribleFailure newTerribleFailure(String msg, Throwable cause); } } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMediaActionSound.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMediaActionSound.java index 82e405054..4af14e8b1 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMediaActionSound.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMediaActionSound.java @@ -4,7 +4,6 @@ import static android.os.Build.VERSION_CODES.TIRAMISU; import static org.robolectric.util.reflector.Reflector.reflector; import android.media.MediaActionSound; -import android.os.Build; import java.util.HashMap; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; @@ -16,7 +15,7 @@ import org.robolectric.util.reflector.Direct; import org.robolectric.util.reflector.ForType; /** A shadow implementation of {@link android.media.MediaActionSound}. */ -@Implements(value = MediaActionSound.class, minSdk = Build.VERSION_CODES.JELLY_BEAN) +@Implements(value = MediaActionSound.class) public class ShadowMediaActionSound { @RealObject MediaActionSound realObject; diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMediaCodec.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMediaCodec.java index ed7eb8c85..3bee37b16 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMediaCodec.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMediaCodec.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.JELLY_BEAN; import static android.os.Build.VERSION_CODES.LOLLIPOP; import static android.os.Build.VERSION_CODES.N_MR1; import static android.os.Build.VERSION_CODES.O; @@ -54,7 +53,7 @@ import org.robolectric.versioning.AndroidVersions.U; * implementation will present an input buffer, which will be copied to an output buffer once * queued, which will be subsequently presented to the callback handler. */ -@Implements(value = MediaCodec.class, minSdk = JELLY_BEAN, looseSignatures = true) +@Implements(value = MediaCodec.class, looseSignatures = true) public class ShadowMediaCodec { private static final int DEFAULT_BUFFER_SIZE = 512; @VisibleForTesting static final int BUFFER_COUNT = 10; diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMediaPlayer.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMediaPlayer.java index f08a53c3f..8a6926c63 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMediaPlayer.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMediaPlayer.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH; import static android.os.Build.VERSION_CODES.M; import static android.os.Build.VERSION_CODES.N; import static android.os.Build.VERSION_CODES.N_MR1; @@ -606,7 +605,7 @@ public class ShadowMediaPlayer extends ShadowPlayerBase { setDataSource(context, uri, null, null); } - @Implementation(minSdk = ICE_CREAM_SANDWICH, maxSdk = N_MR1) + @Implementation(maxSdk = N_MR1) protected void setDataSource(Context context, Uri uri, Map<String, String> headers) throws IOException { setDataSource(context, uri, headers, null); diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMediaRouter.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMediaRouter.java index 17da8e1d9..0099c95ab 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMediaRouter.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMediaRouter.java @@ -1,15 +1,11 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.JELLY_BEAN; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; - import android.media.AudioRoutesInfo; import android.media.MediaRouter; import android.media.MediaRouter.RouteInfo; import android.os.Parcel; import android.text.TextUtils; import javax.annotation.Nullable; -import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Implements; import org.robolectric.annotation.RealObject; import org.robolectric.annotation.Resetter; @@ -30,16 +26,7 @@ public class ShadowMediaRouter { public void addBluetoothRoute() { updateBluetoothAudioRoute(BLUETOOTH_DEVICE_NAME); - if (RuntimeEnvironment.getApiLevel() <= JELLY_BEAN_MR1) { - ReflectionHelpers.callInstanceMethod( - MediaRouter.class, - realObject, - "selectRouteInt", - ClassParameter.from(int.class, MediaRouter.ROUTE_TYPE_LIVE_AUDIO), - ClassParameter.from(RouteInfo.class, getBluetoothA2dpRoute())); - } else { - realObject.selectRoute(MediaRouter.ROUTE_TYPE_LIVE_AUDIO, getBluetoothA2dpRoute()); - } + realObject.selectRoute(MediaRouter.ROUTE_TYPE_LIVE_AUDIO, getBluetoothA2dpRoute()); } /** Removes the Bluetooth A2DP route, simulating disconnecting the Bluetooth device. */ @@ -85,9 +72,7 @@ public class ShadowMediaRouter { private void callUpdateAudioRoutes(AudioRoutesInfo routesInfo) { ReflectionHelpers.callInstanceMethod( ReflectionHelpers.getStaticField(MediaRouter.class, "sStatic"), - RuntimeEnvironment.getApiLevel() <= JELLY_BEAN - ? "updateRoutes" - : "updateAudioRoutes", + "updateAudioRoutes", ClassParameter.from(AudioRoutesInfo.class, routesInfo)); } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeAllocationRegistry.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeAllocationRegistry.java index ab3c6e3d5..e7018f36c 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeAllocationRegistry.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeAllocationRegistry.java @@ -12,13 +12,15 @@ import org.robolectric.shadows.ShadowNativeAllocationRegistry.Picker; import org.robolectric.util.reflector.Accessor; import org.robolectric.util.reflector.Direct; import org.robolectric.util.reflector.ForType; +import org.robolectric.versioning.AndroidVersions.U; /** Shadow for {@link NativeAllocationRegistry} that is backed by native code */ @Implements( value = NativeAllocationRegistry.class, minSdk = O, isInAndroidSdk = false, - shadowPicker = Picker.class) + shadowPicker = Picker.class, + callNativeMethodsByDefault = true) public class ShadowNativeAllocationRegistry { @RealObject protected NativeAllocationRegistry realNativeAllocationRegistry; @@ -42,7 +44,7 @@ public class ShadowNativeAllocationRegistry { != 0; } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void applyFreeFunction(long freeFunction, long nativePtr) { NativeAllocationRegistryNatives.applyFreeFunction(freeFunction, nativePtr); } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeAnimatedImageDrawable.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeAnimatedImageDrawable.java index a73f0a61f..562ac12ca 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeAnimatedImageDrawable.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeAnimatedImageDrawable.java @@ -15,11 +15,16 @@ import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; import org.robolectric.nativeruntime.AnimatedImageDrawableNatives; import org.robolectric.shadows.ShadowNativeAnimatedImageDrawable.Picker; +import org.robolectric.versioning.AndroidVersions.U; /** Shadow for {@link AnimatedImageDrawable} that is backed by native code */ -@Implements(value = AnimatedImageDrawable.class, shadowPicker = Picker.class, minSdk = P) +@Implements( + value = AnimatedImageDrawable.class, + shadowPicker = Picker.class, + minSdk = P, + callNativeMethodsByDefault = true) public class ShadowNativeAnimatedImageDrawable extends ShadowDrawable { - @Implementation(minSdk = Q) + @Implementation(minSdk = Q, maxSdk = U.SDK_INT) protected static long nCreate( long nativeImageDecoder, ImageDecoder decoder, @@ -40,52 +45,52 @@ public class ShadowNativeAnimatedImageDrawable extends ShadowDrawable { return nCreate(nativeImageDecoder, decoder, width, height, 0, false, cropRect); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static long nGetNativeFinalizer() { return AnimatedImageDrawableNatives.nGetNativeFinalizer(); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static long nDraw(long nativePtr, long canvasNativePtr) { return AnimatedImageDrawableNatives.nDraw(nativePtr, canvasNativePtr); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nSetAlpha(long nativePtr, int alpha) { AnimatedImageDrawableNatives.nSetAlpha(nativePtr, alpha); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static int nGetAlpha(long nativePtr) { return AnimatedImageDrawableNatives.nGetAlpha(nativePtr); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nSetColorFilter(long nativePtr, long nativeFilter) { AnimatedImageDrawableNatives.nSetColorFilter(nativePtr, nativeFilter); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static boolean nIsRunning(long nativePtr) { return AnimatedImageDrawableNatives.nIsRunning(nativePtr); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static boolean nStart(long nativePtr) { return AnimatedImageDrawableNatives.nStart(nativePtr); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static boolean nStop(long nativePtr) { return AnimatedImageDrawableNatives.nStop(nativePtr); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static int nGetRepeatCount(long nativePtr) { return AnimatedImageDrawableNatives.nGetRepeatCount(nativePtr); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nSetRepeatCount(long nativePtr, int repeatCount) { AnimatedImageDrawableNatives.nSetRepeatCount(nativePtr, repeatCount); } @@ -95,23 +100,23 @@ public class ShadowNativeAnimatedImageDrawable extends ShadowDrawable { AnimatedImageDrawableNatives.nSetOnAnimationEndListener(nativePtr, drawable); } - @Implementation(minSdk = TIRAMISU) + @Implementation(minSdk = TIRAMISU, maxSdk = U.SDK_INT) protected static void nSetOnAnimationEndListener( long nativePtr, WeakReference<AnimatedImageDrawable> drawable) { AnimatedImageDrawableNatives.nSetOnAnimationEndListener(nativePtr, drawable.get()); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static long nNativeByteSize(long nativePtr) { return AnimatedImageDrawableNatives.nNativeByteSize(nativePtr); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nSetMirrored(long nativePtr, boolean mirror) { AnimatedImageDrawableNatives.nSetMirrored(nativePtr, mirror); } - @Implementation(minSdk = S) + @Implementation(minSdk = S, maxSdk = U.SDK_INT) protected static void nSetBounds(long nativePtr, Rect rect) { AnimatedImageDrawableNatives.nSetBounds(nativePtr, rect); } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeAnimatedVectorDrawable.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeAnimatedVectorDrawable.java index 5286b0794..156464268 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeAnimatedVectorDrawable.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeAnimatedVectorDrawable.java @@ -15,9 +15,14 @@ import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader; import org.robolectric.shadows.ShadowNativeAnimatedVectorDrawable.Picker; import org.robolectric.util.reflector.Direct; import org.robolectric.util.reflector.ForType; +import org.robolectric.versioning.AndroidVersions.U; /** Shadow for {@link AnimatedVectorDrawable} that is backed by native code */ -@Implements(value = AnimatedVectorDrawable.class, minSdk = O, shadowPicker = Picker.class) +@Implements( + value = AnimatedVectorDrawable.class, + minSdk = O, + callNativeMethodsByDefault = true, + shadowPicker = Picker.class) public class ShadowNativeAnimatedVectorDrawable extends ShadowDrawable { @RealObject protected AnimatedVectorDrawable realAnimatedVectorDrawable; @@ -44,18 +49,18 @@ public class ShadowNativeAnimatedVectorDrawable extends ShadowDrawable { return startInitiated; } - @Implementation(minSdk = N) + @Implementation(minSdk = N, maxSdk = U.SDK_INT) protected static long nCreateAnimatorSet() { DefaultNativeRuntimeLoader.injectAndLoad(); return AnimatedVectorDrawableNatives.nCreateAnimatorSet(); } - @Implementation(minSdk = N_MR1) + @Implementation(minSdk = N_MR1, maxSdk = U.SDK_INT) protected static void nSetVectorDrawableTarget(long animatorPtr, long vectorDrawablePtr) { AnimatedVectorDrawableNatives.nSetVectorDrawableTarget(animatorPtr, vectorDrawablePtr); } - @Implementation(minSdk = N_MR1) + @Implementation(minSdk = N_MR1, maxSdk = U.SDK_INT) protected static void nAddAnimator( long setPtr, long propertyValuesHolder, @@ -74,67 +79,67 @@ public class ShadowNativeAnimatedVectorDrawable extends ShadowDrawable { repeatMode); } - @Implementation(minSdk = N) + @Implementation(minSdk = N, maxSdk = U.SDK_INT) protected static void nSetPropertyHolderData(long nativePtr, float[] data, int length) { AnimatedVectorDrawableNatives.nSetPropertyHolderData(nativePtr, data, length); } - @Implementation(minSdk = N_MR1) + @Implementation(minSdk = N_MR1, maxSdk = U.SDK_INT) protected static void nSetPropertyHolderData(long nativePtr, int[] data, int length) { AnimatedVectorDrawableNatives.nSetPropertyHolderData(nativePtr, data, length); } - @Implementation(minSdk = N) + @Implementation(minSdk = N, maxSdk = U.SDK_INT) protected static void nStart(long animatorSetPtr, VectorDrawableAnimatorRT set, int id) { AnimatedVectorDrawableNatives.nStart(animatorSetPtr, set, id); } - @Implementation(minSdk = N) + @Implementation(minSdk = N, maxSdk = U.SDK_INT) protected static void nReverse(long animatorSetPtr, VectorDrawableAnimatorRT set, int id) { AnimatedVectorDrawableNatives.nReverse(animatorSetPtr, set, id); } - @Implementation(minSdk = N) + @Implementation(minSdk = N, maxSdk = U.SDK_INT) protected static long nCreateGroupPropertyHolder( long nativePtr, int propertyId, float startValue, float endValue) { return AnimatedVectorDrawableNatives.nCreateGroupPropertyHolder( nativePtr, propertyId, startValue, endValue); } - @Implementation(minSdk = N) + @Implementation(minSdk = N, maxSdk = U.SDK_INT) protected static long nCreatePathDataPropertyHolder( long nativePtr, long startValuePtr, long endValuePtr) { return AnimatedVectorDrawableNatives.nCreatePathDataPropertyHolder( nativePtr, startValuePtr, endValuePtr); } - @Implementation(minSdk = N) + @Implementation(minSdk = N, maxSdk = U.SDK_INT) protected static long nCreatePathColorPropertyHolder( long nativePtr, int propertyId, int startValue, int endValue) { return AnimatedVectorDrawableNatives.nCreatePathColorPropertyHolder( nativePtr, propertyId, startValue, endValue); } - @Implementation(minSdk = N) + @Implementation(minSdk = N, maxSdk = U.SDK_INT) protected static long nCreatePathPropertyHolder( long nativePtr, int propertyId, float startValue, float endValue) { return AnimatedVectorDrawableNatives.nCreatePathPropertyHolder( nativePtr, propertyId, startValue, endValue); } - @Implementation(minSdk = N) + @Implementation(minSdk = N, maxSdk = U.SDK_INT) protected static long nCreateRootAlphaPropertyHolder( long nativePtr, float startValue, float endValue) { return AnimatedVectorDrawableNatives.nCreateRootAlphaPropertyHolder( nativePtr, startValue, endValue); } - @Implementation(minSdk = N) + @Implementation(minSdk = N, maxSdk = U.SDK_INT) protected static void nEnd(long animatorSetPtr) { AnimatedVectorDrawableNatives.nEnd(animatorSetPtr); } - @Implementation(minSdk = N) + @Implementation(minSdk = N, maxSdk = U.SDK_INT) protected static void nReset(long animatorSetPtr) { AnimatedVectorDrawableNatives.nReset(animatorSetPtr); } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBaseCanvas.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBaseCanvas.java index 945575eb1..0e55090e2 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBaseCanvas.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBaseCanvas.java @@ -28,12 +28,13 @@ import org.robolectric.versioning.AndroidVersions.U; value = BaseCanvas.class, minSdk = O, shadowPicker = Picker.class, - isInAndroidSdk = false) + isInAndroidSdk = false, + callNativeMethodsByDefault = true) public class ShadowNativeBaseCanvas extends ShadowCanvas { @RealObject BaseCanvas realBaseCanvas; - @Implementation(minSdk = Q) + @Implementation(minSdk = Q, maxSdk = U.SDK_INT) protected static void nDrawBitmap( long nativeCanvas, long bitmapHandle, @@ -54,7 +55,7 @@ public class ShadowNativeBaseCanvas extends ShadowCanvas { bitmapDensity); } - @Implementation(minSdk = Q) + @Implementation(minSdk = Q, maxSdk = U.SDK_INT) protected static void nDrawBitmap( long nativeCanvas, long bitmapHandle, @@ -85,7 +86,7 @@ public class ShadowNativeBaseCanvas extends ShadowCanvas { bitmapDensity); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nDrawBitmap( long nativeCanvas, int[] colors, @@ -153,64 +154,64 @@ public class ShadowNativeBaseCanvas extends ShadowCanvas { bitmapDensity); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nDrawColor(long nativeCanvas, int color, int mode) { BaseCanvasNatives.nDrawColor(nativeCanvas, color, mode); } - @Implementation(minSdk = Q) + @Implementation(minSdk = Q, maxSdk = U.SDK_INT) protected static void nDrawColor( long nativeCanvas, long nativeColorSpace, @ColorLong long color, int mode) { BaseCanvasNatives.nDrawColor(nativeCanvas, nativeColorSpace, color, mode); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nDrawPaint(long nativeCanvas, long nativePaint) { BaseCanvasNatives.nDrawPaint(nativeCanvas, nativePaint); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nDrawPoint(long canvasHandle, float x, float y, long paintHandle) { BaseCanvasNatives.nDrawPoint(canvasHandle, x, y, paintHandle); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nDrawPoints( long canvasHandle, float[] pts, int offset, int count, long paintHandle) { BaseCanvasNatives.nDrawPoints(canvasHandle, pts, offset, count, paintHandle); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nDrawLine( long nativeCanvas, float startX, float startY, float stopX, float stopY, long nativePaint) { BaseCanvasNatives.nDrawLine(nativeCanvas, startX, startY, stopX, stopY, nativePaint); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nDrawLines( long canvasHandle, float[] pts, int offset, int count, long paintHandle) { BaseCanvasNatives.nDrawLines(canvasHandle, pts, offset, count, paintHandle); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nDrawRect( long nativeCanvas, float left, float top, float right, float bottom, long nativePaint) { BaseCanvasNatives.nDrawRect(nativeCanvas, left, top, right, bottom, nativePaint); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nDrawOval( long nativeCanvas, float left, float top, float right, float bottom, long nativePaint) { BaseCanvasNatives.nDrawOval(nativeCanvas, left, top, right, bottom, nativePaint); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nDrawCircle( long nativeCanvas, float cx, float cy, float radius, long nativePaint) { BaseCanvasNatives.nDrawCircle(nativeCanvas, cx, cy, radius, nativePaint); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nDrawArc( long nativeCanvas, float left, @@ -225,7 +226,7 @@ public class ShadowNativeBaseCanvas extends ShadowCanvas { nativeCanvas, left, top, right, bottom, startAngle, sweep, useCenter, nativePaint); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nDrawRoundRect( long nativeCanvas, float left, @@ -238,7 +239,7 @@ public class ShadowNativeBaseCanvas extends ShadowCanvas { BaseCanvasNatives.nDrawRoundRect(nativeCanvas, left, top, right, bottom, rx, ry, nativePaint); } - @Implementation(minSdk = Q) + @Implementation(minSdk = Q, maxSdk = U.SDK_INT) protected static void nDrawDoubleRoundRect( long nativeCanvas, float outerLeft, @@ -271,7 +272,7 @@ public class ShadowNativeBaseCanvas extends ShadowCanvas { nativePaint); } - @Implementation(minSdk = Q) + @Implementation(minSdk = Q, maxSdk = U.SDK_INT) protected static void nDrawDoubleRoundRect( long nativeCanvas, float outerLeft, @@ -300,17 +301,17 @@ public class ShadowNativeBaseCanvas extends ShadowCanvas { nativePaint); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nDrawPath(long nativeCanvas, long nativePath, long nativePaint) { BaseCanvasNatives.nDrawPath(nativeCanvas, nativePath, nativePaint); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nDrawRegion(long nativeCanvas, long nativeRegion, long nativePaint) { BaseCanvasNatives.nDrawRegion(nativeCanvas, nativeRegion, nativePaint); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nDrawNinePatch( long nativeCanvas, long nativeBitmap, @@ -335,7 +336,7 @@ public class ShadowNativeBaseCanvas extends ShadowCanvas { bitmapDensity); } - @Implementation(minSdk = Q) + @Implementation(minSdk = Q, maxSdk = U.SDK_INT) protected static void nDrawBitmapMatrix( long nativeCanvas, long bitmapHandle, long nativeMatrix, long nativePaint) { BaseCanvasNatives.nDrawBitmapMatrix(nativeCanvas, bitmapHandle, nativeMatrix, nativePaint); @@ -348,7 +349,7 @@ public class ShadowNativeBaseCanvas extends ShadowCanvas { nativeCanvas, bitmap.getNativeInstance(), nativeMatrix, nativePaint); } - @Implementation(minSdk = Q) + @Implementation(minSdk = Q, maxSdk = U.SDK_INT) protected static void nDrawBitmapMesh( long nativeCanvas, long bitmapHandle, @@ -394,7 +395,7 @@ public class ShadowNativeBaseCanvas extends ShadowCanvas { nativePaint); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nDrawVertices( long nativeCanvas, int mode, @@ -425,7 +426,7 @@ public class ShadowNativeBaseCanvas extends ShadowCanvas { nativePaint); } - @Implementation(minSdk = S) + @Implementation(minSdk = S, maxSdk = U.SDK_INT) protected static void nDrawGlyphs( long nativeCanvas, int[] glyphIds, @@ -446,7 +447,7 @@ public class ShadowNativeBaseCanvas extends ShadowCanvas { nativePaint); } - @Implementation(minSdk = P) + @Implementation(minSdk = P, maxSdk = U.SDK_INT) protected static void nDrawText( long nativeCanvas, char[] text, @@ -461,7 +462,7 @@ public class ShadowNativeBaseCanvas extends ShadowCanvas { BaseCanvasNatives.nDrawText(nativeCanvas, text, index, count, x, y, flags, nativePaint); } - @Implementation(minSdk = P) + @Implementation(minSdk = P, maxSdk = U.SDK_INT) protected static void nDrawText( long nativeCanvas, String text, @@ -510,7 +511,7 @@ public class ShadowNativeBaseCanvas extends ShadowCanvas { nativeCanvas, text, start, end, x, y, flags, nativePaint, nativeTypeface); } - @Implementation(minSdk = P) + @Implementation(minSdk = P, maxSdk = U.SDK_INT) protected static void nDrawTextRun( long nativeCanvas, String text, @@ -532,7 +533,7 @@ public class ShadowNativeBaseCanvas extends ShadowCanvas { * The signature of this method is the same from SDK levels O and above, but the last native * pointer changed from a Typeface pointer to a MeasuredParagraph pointer in P. */ - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nDrawTextRun( long nativeCanvas, char[] text, @@ -605,7 +606,7 @@ public class ShadowNativeBaseCanvas extends ShadowCanvas { nativeTypeface); } - @Implementation(minSdk = P) + @Implementation(minSdk = P, maxSdk = U.SDK_INT) protected static void nDrawTextOnPath( long nativeCanvas, char[] text, @@ -622,7 +623,7 @@ public class ShadowNativeBaseCanvas extends ShadowCanvas { nativeCanvas, text, index, count, nativePath, hOffset, vOffset, bidiFlags, nativePaint); } - @Implementation(minSdk = P) + @Implementation(minSdk = P, maxSdk = U.SDK_INT) protected static void nDrawTextOnPath( long nativeCanvas, String text, @@ -686,7 +687,7 @@ public class ShadowNativeBaseCanvas extends ShadowCanvas { BaseCanvasNatives.nPunchHole(renderer, left, top, right, bottom, rx, ry); } - @Implementation(minSdk = U.SDK_INT) + @Implementation(minSdk = U.SDK_INT, maxSdk = U.SDK_INT) protected static void nPunchHole( long renderer, float left, diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBaseRecordingCanvas.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBaseRecordingCanvas.java index c0a8b0101..005b51905 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBaseRecordingCanvas.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBaseRecordingCanvas.java @@ -24,7 +24,7 @@ import org.robolectric.versioning.AndroidVersions.U; isInAndroidSdk = false) public class ShadowNativeBaseRecordingCanvas extends ShadowNativeCanvas { - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nDrawBitmap( long nativeCanvas, long bitmapHandle, @@ -45,7 +45,7 @@ public class ShadowNativeBaseRecordingCanvas extends ShadowNativeCanvas { bitmapDensity); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nDrawBitmap( long nativeCanvas, long bitmapHandle, @@ -76,7 +76,7 @@ public class ShadowNativeBaseRecordingCanvas extends ShadowNativeCanvas { bitmapDensity); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nDrawBitmap( long nativeCanvas, int[] colors, @@ -92,64 +92,64 @@ public class ShadowNativeBaseRecordingCanvas extends ShadowNativeCanvas { nativeCanvas, colors, offset, stride, x, y, width, height, hasAlpha, nativePaintOrZero); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nDrawColor(long nativeCanvas, int color, int mode) { BaseRecordingCanvasNatives.nDrawColor(nativeCanvas, color, mode); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nDrawColor( long nativeCanvas, long nativeColorSpace, @ColorLong long color, int mode) { BaseRecordingCanvasNatives.nDrawColor(nativeCanvas, nativeColorSpace, color, mode); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nDrawPaint(long nativeCanvas, long nativePaint) { BaseRecordingCanvasNatives.nDrawPaint(nativeCanvas, nativePaint); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nDrawPoint(long canvasHandle, float x, float y, long paintHandle) { BaseRecordingCanvasNatives.nDrawPoint(canvasHandle, x, y, paintHandle); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nDrawPoints( long canvasHandle, float[] pts, int offset, int count, long paintHandle) { BaseRecordingCanvasNatives.nDrawPoints(canvasHandle, pts, offset, count, paintHandle); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nDrawLine( long nativeCanvas, float startX, float startY, float stopX, float stopY, long nativePaint) { BaseRecordingCanvasNatives.nDrawLine(nativeCanvas, startX, startY, stopX, stopY, nativePaint); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nDrawLines( long canvasHandle, float[] pts, int offset, int count, long paintHandle) { BaseRecordingCanvasNatives.nDrawLines(canvasHandle, pts, offset, count, paintHandle); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nDrawRect( long nativeCanvas, float left, float top, float right, float bottom, long nativePaint) { BaseRecordingCanvasNatives.nDrawRect(nativeCanvas, left, top, right, bottom, nativePaint); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nDrawOval( long nativeCanvas, float left, float top, float right, float bottom, long nativePaint) { BaseRecordingCanvasNatives.nDrawOval(nativeCanvas, left, top, right, bottom, nativePaint); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nDrawCircle( long nativeCanvas, float cx, float cy, float radius, long nativePaint) { BaseRecordingCanvasNatives.nDrawCircle(nativeCanvas, cx, cy, radius, nativePaint); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nDrawArc( long nativeCanvas, float left, @@ -164,7 +164,7 @@ public class ShadowNativeBaseRecordingCanvas extends ShadowNativeCanvas { nativeCanvas, left, top, right, bottom, startAngle, sweep, useCenter, nativePaint); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nDrawRoundRect( long nativeCanvas, float left, @@ -178,7 +178,7 @@ public class ShadowNativeBaseRecordingCanvas extends ShadowNativeCanvas { nativeCanvas, left, top, right, bottom, rx, ry, nativePaint); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nDrawDoubleRoundRect( long nativeCanvas, float outerLeft, @@ -211,7 +211,7 @@ public class ShadowNativeBaseRecordingCanvas extends ShadowNativeCanvas { nativePaint); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nDrawDoubleRoundRect( long nativeCanvas, float outerLeft, @@ -240,17 +240,17 @@ public class ShadowNativeBaseRecordingCanvas extends ShadowNativeCanvas { nativePaint); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nDrawPath(long nativeCanvas, long nativePath, long nativePaint) { BaseRecordingCanvasNatives.nDrawPath(nativeCanvas, nativePath, nativePaint); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nDrawRegion(long nativeCanvas, long nativeRegion, long nativePaint) { BaseRecordingCanvasNatives.nDrawRegion(nativeCanvas, nativeRegion, nativePaint); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nDrawNinePatch( long nativeCanvas, long nativeBitmap, @@ -275,14 +275,14 @@ public class ShadowNativeBaseRecordingCanvas extends ShadowNativeCanvas { bitmapDensity); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nDrawBitmapMatrix( long nativeCanvas, long bitmapHandle, long nativeMatrix, long nativePaint) { BaseRecordingCanvasNatives.nDrawBitmapMatrix( nativeCanvas, bitmapHandle, nativeMatrix, nativePaint); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nDrawBitmapMesh( long nativeCanvas, long bitmapHandle, @@ -305,7 +305,7 @@ public class ShadowNativeBaseRecordingCanvas extends ShadowNativeCanvas { nativePaint); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nDrawVertices( long nativeCanvas, int mode, @@ -336,7 +336,7 @@ public class ShadowNativeBaseRecordingCanvas extends ShadowNativeCanvas { nativePaint); } - @Implementation(minSdk = S) + @Implementation(minSdk = S, maxSdk = U.SDK_INT) protected static void nDrawGlyphs( long nativeCanvas, int[] glyphIds, @@ -357,7 +357,7 @@ public class ShadowNativeBaseRecordingCanvas extends ShadowNativeCanvas { nativePaint); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nDrawText( long nativeCanvas, char[] text, @@ -371,7 +371,7 @@ public class ShadowNativeBaseRecordingCanvas extends ShadowNativeCanvas { nativeCanvas, text, index, count, x, y, flags, nativePaint); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nDrawText( long nativeCanvas, String text, @@ -414,7 +414,7 @@ public class ShadowNativeBaseRecordingCanvas extends ShadowNativeCanvas { nativeCanvas, text, start, end, x, y, flags, nativePaint, nativeTypeface); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nDrawTextRun( long nativeCanvas, String text, @@ -434,7 +434,7 @@ public class ShadowNativeBaseRecordingCanvas extends ShadowNativeCanvas { * The signature of this method is the same from SDK levels O and above, but the last native * pointer changed from a Typeface pointer to a MeasuredParagraph pointer in P. */ - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nDrawTextRun( long nativeCanvas, char[] text, @@ -503,7 +503,7 @@ public class ShadowNativeBaseRecordingCanvas extends ShadowNativeCanvas { nativeTypeface); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nDrawTextOnPath( long nativeCanvas, char[] text, @@ -518,7 +518,7 @@ public class ShadowNativeBaseRecordingCanvas extends ShadowNativeCanvas { nativeCanvas, text, index, count, nativePath, hOffset, vOffset, bidiFlags, nativePaint); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nDrawTextOnPath( long nativeCanvas, String text, @@ -576,7 +576,7 @@ public class ShadowNativeBaseRecordingCanvas extends ShadowNativeCanvas { BaseRecordingCanvasNatives.nPunchHole(renderer, left, top, right, bottom, rx, ry); } - @Implementation(minSdk = U.SDK_INT) + @Implementation(minSdk = U.SDK_INT, maxSdk = U.SDK_INT) protected static void nPunchHole( long renderer, float left, diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBitmap.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBitmap.java index a1ff96f25..140ba87ac 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBitmap.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBitmap.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; import static android.os.Build.VERSION_CODES.LOLLIPOP; import static android.os.Build.VERSION_CODES.M; import static android.os.Build.VERSION_CODES.N; @@ -28,7 +27,6 @@ import java.util.List; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; -import org.robolectric.annotation.RealObject; import org.robolectric.annotation.Resetter; import org.robolectric.nativeruntime.BitmapNatives; import org.robolectric.nativeruntime.ColorSpaceRgbNatives; @@ -40,11 +38,13 @@ import org.robolectric.util.reflector.Static; import org.robolectric.versioning.AndroidVersions.U; /** Shadow for {@link Bitmap} that is backed by native code */ -@Implements(value = Bitmap.class, looseSignatures = true, minSdk = O, isInAndroidSdk = false) +@Implements( + value = Bitmap.class, + looseSignatures = true, + minSdk = O, + isInAndroidSdk = false, + callNativeMethodsByDefault = true) public class ShadowNativeBitmap extends ShadowBitmap { - - @RealObject Bitmap realBitmap; - private int createdFromResId; private static final List<Long> colorSpaceAllocationsP = @@ -55,7 +55,7 @@ public class ShadowNativeBitmap extends ShadowBitmap { this.createdFromResId = createdFromResId; } - @Implementation(minSdk = Q) + @Implementation(minSdk = Q, maxSdk = U.SDK_INT) protected static Bitmap nativeCreate( int[] colors, int offset, @@ -100,75 +100,75 @@ public class ShadowNativeBitmap extends ShadowBitmap { colors, offset, stride, width, height, nativeConfig, mutable, colorSpacePtr); } - @Implementation(minSdk = LOLLIPOP) + @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT) protected static Bitmap nativeCopy(long nativeSrcBitmap, int nativeConfig, boolean isMutable) { return BitmapNatives.nativeCopy(nativeSrcBitmap, nativeConfig, isMutable); } - @Implementation(minSdk = M) + @Implementation(minSdk = M, maxSdk = U.SDK_INT) protected static Bitmap nativeCopyAshmem(long nativeSrcBitmap) { return BitmapNatives.nativeCopyAshmem(nativeSrcBitmap); } - @Implementation(minSdk = N) + @Implementation(minSdk = N, maxSdk = U.SDK_INT) protected static Bitmap nativeCopyAshmemConfig(long nativeSrcBitmap, int nativeConfig) { return BitmapNatives.nativeCopyAshmemConfig(nativeSrcBitmap, nativeConfig); } - @Implementation(minSdk = N) + @Implementation(minSdk = N, maxSdk = U.SDK_INT) protected static long nativeGetNativeFinalizer() { return BitmapNatives.nativeGetNativeFinalizer(); } - @Implementation(minSdk = LOLLIPOP) + @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT) protected static Object nativeRecycle(Object nativeBitmap) { BitmapNatives.nativeRecycle((long) nativeBitmap); return true; } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nativeReconfigure( long nativeBitmap, int width, int height, int config, boolean isPremultiplied) { BitmapNatives.nativeReconfigure(nativeBitmap, width, height, config, isPremultiplied); } - @Implementation(minSdk = LOLLIPOP) + @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT) protected static boolean nativeCompress( long nativeBitmap, int format, int quality, OutputStream stream, byte[] tempStorage) { return BitmapNatives.nativeCompress(nativeBitmap, format, quality, stream, tempStorage); } - @Implementation(minSdk = LOLLIPOP) + @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT) protected static void nativeErase(long nativeBitmap, int color) { BitmapNatives.nativeErase(nativeBitmap, color); } - @Implementation(minSdk = Q) + @Implementation(minSdk = Q, maxSdk = U.SDK_INT) protected static void nativeErase(long nativeBitmap, long colorSpacePtr, long color) { BitmapNatives.nativeErase(nativeBitmap, colorSpacePtr, color); } - @Implementation(minSdk = LOLLIPOP) + @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT) protected static int nativeRowBytes(long nativeBitmap) { return BitmapNatives.nativeRowBytes(nativeBitmap); } - @Implementation(minSdk = LOLLIPOP) + @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT) protected static int nativeConfig(long nativeBitmap) { return BitmapNatives.nativeConfig(nativeBitmap); } - @Implementation(minSdk = LOLLIPOP) + @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT) protected static int nativeGetPixel(long nativeBitmap, int x, int y) { return BitmapNatives.nativeGetPixel(nativeBitmap, x, y); } - @Implementation(minSdk = Q) + @Implementation(minSdk = Q, maxSdk = U.SDK_INT) protected static long nativeGetColor(long nativeBitmap, int x, int y) { return BitmapNatives.nativeGetColor(nativeBitmap, x, y); } - @Implementation(minSdk = LOLLIPOP) + @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT) protected static void nativeGetPixels( long nativeBitmap, int[] pixels, @@ -181,12 +181,12 @@ public class ShadowNativeBitmap extends ShadowBitmap { BitmapNatives.nativeGetPixels(nativeBitmap, pixels, offset, stride, x, y, width, height); } - @Implementation(minSdk = LOLLIPOP) + @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT) protected static void nativeSetPixel(long nativeBitmap, int x, int y, int color) { BitmapNatives.nativeSetPixel(nativeBitmap, x, y, color); } - @Implementation(minSdk = LOLLIPOP) + @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT) protected static void nativeSetPixels( long nativeBitmap, int[] colors, @@ -199,85 +199,85 @@ public class ShadowNativeBitmap extends ShadowBitmap { BitmapNatives.nativeSetPixels(nativeBitmap, colors, offset, stride, x, y, width, height); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nativeCopyPixelsToBuffer(long nativeBitmap, Buffer dst) { BitmapNatives.nativeCopyPixelsToBuffer(nativeBitmap, dst); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nativeCopyPixelsFromBuffer(long nativeBitmap, Buffer src) { BitmapNatives.nativeCopyPixelsFromBuffer(nativeBitmap, src); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static int nativeGenerationId(long nativeBitmap) { return BitmapNatives.nativeGenerationId(nativeBitmap); } // returns a new bitmap built from the native bitmap's alpha, and the paint - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static Bitmap nativeExtractAlpha(long nativeBitmap, long nativePaint, int[] offsetXY) { return BitmapNatives.nativeExtractAlpha(nativeBitmap, nativePaint, offsetXY); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static boolean nativeHasAlpha(long nativeBitmap) { return BitmapNatives.nativeHasAlpha(nativeBitmap); } - @Implementation(minSdk = LOLLIPOP) + @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT) protected static boolean nativeIsPremultiplied(long nativeBitmap) { return BitmapNatives.nativeIsPremultiplied(nativeBitmap); } - @Implementation(minSdk = LOLLIPOP) + @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT) protected static void nativeSetPremultiplied(long nativeBitmap, boolean isPremul) { BitmapNatives.nativeSetPremultiplied(nativeBitmap, isPremul); } - @Implementation(minSdk = LOLLIPOP) + @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT) protected static void nativeSetHasAlpha( long nativeBitmap, boolean hasAlpha, boolean requestPremul) { BitmapNatives.nativeSetHasAlpha(nativeBitmap, hasAlpha, requestPremul); } - @Implementation(minSdk = JELLY_BEAN_MR1) + @Implementation(maxSdk = U.SDK_INT) protected static boolean nativeHasMipMap(long nativeBitmap) { return BitmapNatives.nativeHasMipMap(nativeBitmap); } - @Implementation(minSdk = JELLY_BEAN_MR1) + @Implementation(maxSdk = U.SDK_INT) protected static void nativeSetHasMipMap(long nativeBitmap, boolean hasMipMap) { BitmapNatives.nativeSetHasMipMap(nativeBitmap, hasMipMap); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static boolean nativeSameAs(long nativeBitmap0, long nativeBitmap1) { return BitmapNatives.nativeSameAs(nativeBitmap0, nativeBitmap1); } - @Implementation(minSdk = N_MR1) + @Implementation(minSdk = N_MR1, maxSdk = U.SDK_INT) protected static void nativePrepareToDraw(long nativeBitmap) { BitmapNatives.nativePrepareToDraw(nativeBitmap); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static int nativeGetAllocationByteCount(long nativeBitmap) { return BitmapNatives.nativeGetAllocationByteCount(nativeBitmap); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static Bitmap nativeCopyPreserveInternalConfig(long nativeBitmap) { return BitmapNatives.nativeCopyPreserveInternalConfig(nativeBitmap); } - @Implementation(minSdk = Q) + @Implementation(minSdk = Q, maxSdk = U.SDK_INT) protected static Bitmap nativeWrapHardwareBufferBitmap( HardwareBuffer buffer, long nativeColorSpace) { return BitmapNatives.nativeWrapHardwareBufferBitmap(buffer, nativeColorSpace); } - @Implementation(minSdk = R) + @Implementation(minSdk = R, maxSdk = U.SDK_INT) protected static HardwareBuffer nativeGetHardwareBuffer(long nativeBitmap) { return BitmapNatives.nativeGetHardwareBuffer(nativeBitmap); } @@ -310,41 +310,51 @@ public class ShadowNativeBitmap extends ShadowBitmap { return true; } - @Implementation(minSdk = Q) + @Implementation(minSdk = Q, maxSdk = U.SDK_INT) protected static ColorSpace nativeComputeColorSpace(long nativePtr) { return BitmapNatives.nativeComputeColorSpace(nativePtr); } - @Implementation(minSdk = Q) + @Implementation(minSdk = Q, maxSdk = U.SDK_INT) protected static void nativeSetColorSpace(long nativePtr, long nativeColorSpace) { BitmapNatives.nativeSetColorSpace(nativePtr, nativeColorSpace); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static boolean nativeIsSRGB(long nativePtr) { return BitmapNatives.nativeIsSRGB(nativePtr); } - @Implementation(minSdk = P) + @Implementation(minSdk = P, maxSdk = U.SDK_INT) protected static boolean nativeIsSRGBLinear(long nativePtr) { return BitmapNatives.nativeIsSRGBLinear(nativePtr); } - @Implementation(minSdk = Q) + @Implementation(minSdk = Q, maxSdk = U.SDK_INT) protected static void nativeSetImmutable(long nativePtr) { BitmapNatives.nativeSetImmutable(nativePtr); } - @Implementation(minSdk = Q) + @Implementation(minSdk = Q, maxSdk = U.SDK_INT) protected static boolean nativeIsImmutable(long nativePtr) { return BitmapNatives.nativeIsImmutable(nativePtr); } - @Implementation(minSdk = S) + @Implementation(minSdk = S, maxSdk = U.SDK_INT) protected static boolean nativeIsBackedByAshmem(long nativePtr) { return BitmapNatives.nativeIsBackedByAshmem(nativePtr); } + /** + * This is called by {@link Bitmap#getGainmap} to check if a Gainmap exists for the Bitmap. This + * method must be present in Android U and below to avoid an UnsatisfiedLinkError. + */ + @Implementation(minSdk = U.SDK_INT) + protected static Object nativeExtractGainmap(Object nativePtr) { + // No-op implementation + return null; + } + @ForType(ColorSpace.class) interface ColorSpaceReflector { @Accessor("ILLUMINANT_D50_XYZ") @@ -426,6 +436,11 @@ public class ShadowNativeBitmap extends ShadowBitmap { return bitmap; } + @Implementation(minSdk = O, maxSdk = P) + protected static void nativeCopyColorSpace(long srcBitmap, long dstBitmap) { + BitmapNatives.nativeCopyColorSpaceP(srcBitmap, dstBitmap); + } + @Override public Bitmap getCreatedFromBitmap() { throw new UnsupportedOperationException("Legacy ShadowBitmap APIs are not supported"); diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBitmapFactory.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBitmapFactory.java index 54213d87c..06d3cb7e8 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBitmapFactory.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBitmapFactory.java @@ -21,13 +21,15 @@ import org.robolectric.shadow.api.Shadow; import org.robolectric.shadows.ShadowNativeBitmapFactory.Picker; import org.robolectric.util.reflector.Direct; import org.robolectric.util.reflector.ForType; +import org.robolectric.versioning.AndroidVersions.U; /** Shadow for {@link BitmapFactory} that is backed by native code */ @Implements( value = BitmapFactory.class, minSdk = O, shadowPicker = Picker.class, - isInAndroidSdk = false) + isInAndroidSdk = false, + callNativeMethodsByDefault = true) public class ShadowNativeBitmapFactory { static { @@ -46,6 +48,12 @@ public class ShadowNativeBitmapFactory { return bitmap; } + /** + * The real implementation of {@link BitmapFactory#decodeStream(InputStream, Rect, Options)} + * checks if the stream is an {@link android.content.res.AssetManager.AssetInputStream} object and + * subsequently passes in native asset ids into native code. Until native resources are + * implemented, this has to be shadowed. + */ @Implementation protected static Bitmap decodeStream(InputStream is, Rect outPadding, Options opts) { reflector(BitmapFactoryOptionsReflector.class).validate(opts); @@ -55,7 +63,7 @@ public class ShadowNativeBitmapFactory { return bitmap; } - @Implementation(minSdk = Q) + @Implementation(minSdk = Q, maxSdk = U.SDK_INT) protected static Bitmap nativeDecodeStream( InputStream is, byte[] storage, @@ -73,7 +81,7 @@ public class ShadowNativeBitmapFactory { return nativeDecodeStream(is, storage, padding, opts, nativeInBitmap(opts), 0); } - @Implementation(minSdk = Q) + @Implementation(minSdk = Q, maxSdk = U.SDK_INT) protected static Bitmap nativeDecodeFileDescriptor( FileDescriptor fd, Rect padding, Options opts, long inBitmapHandle, long colorSpaceHandle) { return BitmapFactoryNatives.nativeDecodeFileDescriptor( @@ -86,7 +94,7 @@ public class ShadowNativeBitmapFactory { return nativeDecodeFileDescriptor(fd, padding, opts, nativeInBitmap(opts), 0); } - @Implementation(minSdk = Q) + @Implementation(minSdk = Q, maxSdk = U.SDK_INT) protected static Bitmap nativeDecodeAsset( long nativeAsset, Rect padding, Options opts, long inBitmapHandle, long colorSpaceHandle) { return BitmapFactoryNatives.nativeDecodeAsset( @@ -98,7 +106,7 @@ public class ShadowNativeBitmapFactory { return nativeDecodeAsset(nativeAsset, padding, opts, nativeInBitmap(opts), 0); } - @Implementation(minSdk = Q) + @Implementation(minSdk = Q, maxSdk = U.SDK_INT) protected static Bitmap nativeDecodeByteArray( byte[] data, int offset, @@ -115,7 +123,7 @@ public class ShadowNativeBitmapFactory { return nativeDecodeByteArray(data, offset, length, opts, nativeInBitmap(opts), 0); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static boolean nativeIsSeekable(FileDescriptor fd) { return BitmapFactoryNatives.nativeIsSeekable(fd); } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBitmapShader.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBitmapShader.java index 7b04f9190..b44b63258 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBitmapShader.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBitmapShader.java @@ -16,10 +16,13 @@ import org.robolectric.nativeruntime.BitmapShaderNatives; import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader; import org.robolectric.shadows.ShadowNativeBitmapShader.Picker; import org.robolectric.versioning.AndroidVersions.U; -import org.robolectric.versioning.AndroidVersions.V; /** Shadow for {@link BitmapShader} that is backed by native code */ -@Implements(value = BitmapShader.class, minSdk = O, shadowPicker = Picker.class) +@Implements( + value = BitmapShader.class, + minSdk = O, + shadowPicker = Picker.class, + callNativeMethodsByDefault = true) public class ShadowNativeBitmapShader { @Implementation(minSdk = O, maxSdk = P) @@ -62,20 +65,6 @@ public class ShadowNativeBitmapShader { return nativeCreate(nativeMatrix, bitmapHandle, shaderTileModeX, shaderTileModeY, filter); } - @Implementation(minSdk = V.SDK_INT) - protected static long nativeCreate( - long nativeMatrix, - long bitmapHandle, - int shaderTileModeX, - int shaderTileModeY, - /* Ignored */ int maxAniso, - boolean filter, - boolean isDirectSampled, - /* Ignored */ long overrideGainmapHandle) { - return nativeCreate( - nativeMatrix, bitmapHandle, shaderTileModeX, shaderTileModeY, filter, isDirectSampled); - } - /** Shadow picker for {@link BitmapShader}. */ public static final class Picker extends GraphicsShadowPicker<Object> { public Picker() { diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBlendModeColorFilter.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBlendModeColorFilter.java index f4cbf9901..b82a6b0fc 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBlendModeColorFilter.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBlendModeColorFilter.java @@ -9,12 +9,17 @@ import org.robolectric.annotation.Implements; import org.robolectric.nativeruntime.BlendModeColorFilterNatives; import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader; import org.robolectric.shadows.ShadowNativeBlendModeColorFilter.Picker; +import org.robolectric.versioning.AndroidVersions.U; /** Shadow for {@link BlendModeColorFilter} that is backed by native code */ -@Implements(value = BlendModeColorFilter.class, minSdk = O, shadowPicker = Picker.class) +@Implements( + value = BlendModeColorFilter.class, + minSdk = O, + shadowPicker = Picker.class, + callNativeMethodsByDefault = true) public class ShadowNativeBlendModeColorFilter { - @Implementation(minSdk = Q) + @Implementation(minSdk = Q, maxSdk = U.SDK_INT) protected static long native_CreateBlendModeFilter(int srcColor, int blendmode) { DefaultNativeRuntimeLoader.injectAndLoad(); return BlendModeColorFilterNatives.native_CreateBlendModeFilter(srcColor, blendmode); diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBlurMaskFilter.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBlurMaskFilter.java index 77d4a9da2..0eac16508 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBlurMaskFilter.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBlurMaskFilter.java @@ -8,12 +8,17 @@ import org.robolectric.annotation.Implements; import org.robolectric.nativeruntime.BlurMaskFilterNatives; import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader; import org.robolectric.shadows.ShadowNativeBlurMaskFilter.Picker; +import org.robolectric.versioning.AndroidVersions.U; /** Shadow for {@link BlurMaskFilter} that is backed by native code */ -@Implements(value = BlurMaskFilter.class, minSdk = O, shadowPicker = Picker.class) +@Implements( + value = BlurMaskFilter.class, + minSdk = O, + shadowPicker = Picker.class, + callNativeMethodsByDefault = true) public class ShadowNativeBlurMaskFilter { - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static long nativeConstructor(float radius, int style) { DefaultNativeRuntimeLoader.injectAndLoad(); return BlurMaskFilterNatives.nativeConstructor(radius, style); diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeCanvas.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeCanvas.java index c2dffb8eb..788199ad1 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeCanvas.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeCanvas.java @@ -13,27 +13,32 @@ import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; import org.robolectric.nativeruntime.CanvasNatives; import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader; +import org.robolectric.versioning.AndroidVersions.U; /** Shadow for {@link Canvas} that is backed by native code */ -@Implements(value = Canvas.class, minSdk = O, isInAndroidSdk = false) +@Implements( + value = Canvas.class, + minSdk = O, + isInAndroidSdk = false, + callNativeMethodsByDefault = true) public class ShadowNativeCanvas extends ShadowNativeBaseCanvas { - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nFreeCaches() { CanvasNatives.nFreeCaches(); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nFreeTextLayoutCaches() { CanvasNatives.nFreeTextLayoutCaches(); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static long nGetNativeFinalizer() { return CanvasNatives.nGetNativeFinalizer(); } - @Implementation(minSdk = P) + @Implementation(minSdk = P, maxSdk = U.SDK_INT) protected static void nSetCompatibilityVersion(int apiLevel) { CanvasNatives.nSetCompatibilityVersion(apiLevel); } @@ -43,7 +48,7 @@ public class ShadowNativeCanvas extends ShadowNativeBaseCanvas { return nInitRaster(bitmap != null ? bitmap.getNativeInstance() : 0); } - @Implementation(minSdk = Q) + @Implementation(minSdk = Q, maxSdk = U.SDK_INT) protected static long nInitRaster(long bitmapHandle) { DefaultNativeRuntimeLoader.injectAndLoad(); return CanvasNatives.nInitRaster(bitmapHandle); @@ -54,37 +59,37 @@ public class ShadowNativeCanvas extends ShadowNativeBaseCanvas { CanvasNatives.nSetBitmap(canvasHandle, bitmap != null ? bitmap.getNativeInstance() : 0); } - @Implementation(minSdk = Q) + @Implementation(minSdk = Q, maxSdk = U.SDK_INT) protected static void nSetBitmap(long canvasHandle, long bitmapHandle) { CanvasNatives.nSetBitmap(canvasHandle, bitmapHandle); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static boolean nGetClipBounds(long nativeCanvas, Rect bounds) { return CanvasNatives.nGetClipBounds(nativeCanvas, bounds); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static boolean nIsOpaque(long canvasHandle) { return CanvasNatives.nIsOpaque(canvasHandle); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static int nGetWidth(long canvasHandle) { return CanvasNatives.nGetWidth(canvasHandle); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static int nGetHeight(long canvasHandle) { return CanvasNatives.nGetHeight(canvasHandle); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static int nSave(long canvasHandle, int saveFlags) { return CanvasNatives.nSave(canvasHandle, saveFlags); } - @Implementation(minSdk = S) + @Implementation(minSdk = S, maxSdk = U.SDK_INT) protected static int nSaveLayer( long nativeCanvas, float l, float t, float r, float b, long nativePaint) { return CanvasNatives.nSaveLayer(nativeCanvas, l, t, r, b, nativePaint); @@ -96,7 +101,7 @@ public class ShadowNativeCanvas extends ShadowNativeBaseCanvas { return nSaveLayer(nativeCanvas, l, t, r, b, nativePaint); } - @Implementation(minSdk = S) + @Implementation(minSdk = S, maxSdk = U.SDK_INT) protected static int nSaveLayerAlpha( long nativeCanvas, float l, float t, float r, float b, int alpha) { return CanvasNatives.nSaveLayerAlpha(nativeCanvas, l, t, r, b, alpha); @@ -108,88 +113,88 @@ public class ShadowNativeCanvas extends ShadowNativeBaseCanvas { return nSaveLayerAlpha(nativeCanvas, l, t, r, b, alpha); } - @Implementation(minSdk = Q) + @Implementation(minSdk = Q, maxSdk = U.SDK_INT) protected static int nSaveUnclippedLayer(long nativeCanvas, int l, int t, int r, int b) { return CanvasNatives.nSaveUnclippedLayer(nativeCanvas, l, t, r, b); } - @Implementation(minSdk = Q) + @Implementation(minSdk = Q, maxSdk = U.SDK_INT) protected static void nRestoreUnclippedLayer(long nativeCanvas, int saveCount, long nativePaint) { CanvasNatives.nRestoreUnclippedLayer(nativeCanvas, saveCount, nativePaint); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static boolean nRestore(long canvasHandle) { return CanvasNatives.nRestore(canvasHandle); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nRestoreToCount(long canvasHandle, int saveCount) { CanvasNatives.nRestoreToCount(canvasHandle, saveCount); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static int nGetSaveCount(long canvasHandle) { return CanvasNatives.nGetSaveCount(canvasHandle); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nTranslate(long canvasHandle, float dx, float dy) { CanvasNatives.nTranslate(canvasHandle, dx, dy); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nScale(long canvasHandle, float sx, float sy) { CanvasNatives.nScale(canvasHandle, sx, sy); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nRotate(long canvasHandle, float degrees) { CanvasNatives.nRotate(canvasHandle, degrees); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nSkew(long canvasHandle, float sx, float sy) { CanvasNatives.nSkew(canvasHandle, sx, sy); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nConcat(long nativeCanvas, long nativeMatrix) { CanvasNatives.nConcat(nativeCanvas, nativeMatrix); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nSetMatrix(long nativeCanvas, long nativeMatrix) { CanvasNatives.nSetMatrix(nativeCanvas, nativeMatrix); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static boolean nClipRect( long nativeCanvas, float left, float top, float right, float bottom, int regionOp) { return CanvasNatives.nClipRect(nativeCanvas, left, top, right, bottom, regionOp); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static boolean nClipPath(long nativeCanvas, long nativePath, int regionOp) { return CanvasNatives.nClipPath(nativeCanvas, nativePath, regionOp); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nSetDrawFilter(long nativeCanvas, long nativeFilter) { CanvasNatives.nSetDrawFilter(nativeCanvas, nativeFilter); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nGetMatrix(long nativeCanvas, long nativeMatrix) { CanvasNatives.nGetMatrix(nativeCanvas, nativeMatrix); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static boolean nQuickReject(long nativeCanvas, long nativePath) { return CanvasNatives.nQuickReject(nativeCanvas, nativePath); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static boolean nQuickReject( long nativeCanvas, float left, float top, float right, float bottom) { return CanvasNatives.nQuickReject(nativeCanvas, left, top, right, bottom); diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeCanvasProperty.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeCanvasProperty.java index 8bb4f1698..62069392e 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeCanvasProperty.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeCanvasProperty.java @@ -8,22 +8,24 @@ import org.robolectric.annotation.Implements; import org.robolectric.nativeruntime.CanvasPropertyNatives; import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader; import org.robolectric.shadows.ShadowNativeCanvasProperty.Picker; +import org.robolectric.versioning.AndroidVersions.U; /** Shadow for {@link CanvasProperty} that is backed by native code */ @Implements( value = CanvasProperty.class, minSdk = O, shadowPicker = Picker.class, - isInAndroidSdk = false) + isInAndroidSdk = false, + callNativeMethodsByDefault = true) public class ShadowNativeCanvasProperty<T> { - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static long nCreateFloat(float initialValue) { DefaultNativeRuntimeLoader.injectAndLoad(); return CanvasPropertyNatives.nCreateFloat(initialValue); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static long nCreatePaint(long initialValuePaintPtr) { DefaultNativeRuntimeLoader.injectAndLoad(); return CanvasPropertyNatives.nCreatePaint(initialValuePaintPtr); diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeColor.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeColor.java index 05e3aa3fa..d7d58dd13 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeColor.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeColor.java @@ -8,18 +8,24 @@ import org.robolectric.annotation.Implements; import org.robolectric.nativeruntime.ColorNatives; import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader; import org.robolectric.shadows.ShadowNativeColor.Picker; +import org.robolectric.versioning.AndroidVersions.U; /** Shadow for {@link Color} that is backed by native code */ -@Implements(value = Color.class, minSdk = O, shadowPicker = Picker.class, isInAndroidSdk = false) +@Implements( + value = Color.class, + minSdk = O, + shadowPicker = Picker.class, + isInAndroidSdk = false, + callNativeMethodsByDefault = true) public class ShadowNativeColor { - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nativeRGBToHSV(int red, int greed, int blue, float[] hsv) { DefaultNativeRuntimeLoader.injectAndLoad(); ColorNatives.nativeRGBToHSV(red, greed, blue, hsv); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static int nativeHSVToColor(int alpha, float[] hsv) { DefaultNativeRuntimeLoader.injectAndLoad(); return ColorNatives.nativeHSVToColor(alpha, hsv); diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeColorFilter.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeColorFilter.java index ed641a2f0..22ed5a44c 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeColorFilter.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeColorFilter.java @@ -8,12 +8,17 @@ import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; import org.robolectric.nativeruntime.ColorFilterNatives; import org.robolectric.shadows.ShadowNativeColorFilter.Picker; +import org.robolectric.versioning.AndroidVersions.U; /** Shadow for {@link ColorFilter} that is backed by native code */ -@Implements(value = ColorFilter.class, minSdk = O, shadowPicker = Picker.class) +@Implements( + value = ColorFilter.class, + minSdk = O, + shadowPicker = Picker.class, + callNativeMethodsByDefault = true) public class ShadowNativeColorFilter { - @Implementation(minSdk = O_MR1) + @Implementation(minSdk = O_MR1, maxSdk = U.SDK_INT) protected static long nativeGetFinalizer() { return ColorFilterNatives.nativeGetFinalizer(); } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeColorMatrixColorFilter.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeColorMatrixColorFilter.java index 307303bea..5ecaeea36 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeColorMatrixColorFilter.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeColorMatrixColorFilter.java @@ -8,12 +8,17 @@ import org.robolectric.annotation.Implements; import org.robolectric.nativeruntime.ColorMatrixColorFilterNatives; import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader; import org.robolectric.shadows.ShadowNativeColorMatrixColorFilter.Picker; +import org.robolectric.versioning.AndroidVersions.U; /** Shadow for {@link ColorMatrixColorFilter} that is backed by native code */ -@Implements(value = ColorMatrixColorFilter.class, minSdk = O, shadowPicker = Picker.class) +@Implements( + value = ColorMatrixColorFilter.class, + minSdk = O, + shadowPicker = Picker.class, + callNativeMethodsByDefault = true) public class ShadowNativeColorMatrixColorFilter { - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static long nativeColorMatrixFilter(float[] array) { DefaultNativeRuntimeLoader.injectAndLoad(); return ColorMatrixColorFilterNatives.nativeColorMatrixFilter(array); diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeColorSpace.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeColorSpace.java new file mode 100644 index 000000000..98d04c5fe --- /dev/null +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeColorSpace.java @@ -0,0 +1,32 @@ +package org.robolectric.shadows; + +import android.graphics.ColorSpace; +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; +import org.robolectric.shadows.ShadowNativeColorSpace.Picker; +import org.robolectric.versioning.AndroidVersions.V; + +/** Shadow for {@link ColorSpace} that defers its static initializer. */ +@Implements( + value = ColorSpace.class, + minSdk = V.SDK_INT, + isInAndroidSdk = false, + shadowPicker = Picker.class) +public class ShadowNativeColorSpace { + + /** + * The {@link ColorSpace} static initializer invokes its own native methods in its constructor + * when it initializes the named color spaces. This has to be deferred starting in Android V. + */ + @Implementation(minSdk = V.SDK_INT) + protected static void __staticInitializer__() { + // deferred + } + + /** Shadow picker for {@link ColorSpace}. */ + public static final class Picker extends GraphicsShadowPicker<Object> { + public Picker() { + super(null, ShadowNativeColorSpace.class); + } + } +} diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeColorSpaceRgb.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeColorSpaceRgb.java index 6bb12eeb2..54c824fe2 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeColorSpaceRgb.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeColorSpaceRgb.java @@ -6,24 +6,29 @@ import static android.os.Build.VERSION_CODES.Q; import android.graphics.ColorSpace; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; +import org.robolectric.annotation.RealObject; import org.robolectric.nativeruntime.ColorSpaceRgbNatives; import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader; import org.robolectric.shadows.ShadowNativeColorSpaceRgb.Picker; +import org.robolectric.versioning.AndroidVersions.U; /** Shadow for {@link ColorSpace.Rgb} that is backed by native code */ @Implements( value = ColorSpace.Rgb.class, minSdk = O, shadowPicker = Picker.class, - isInAndroidSdk = false) -public class ShadowNativeColorSpaceRgb { + isInAndroidSdk = false, + callNativeMethodsByDefault = true) +public class ShadowNativeColorSpaceRgb extends ShadowNativeColorSpace { - @Implementation(minSdk = Q) + @RealObject ColorSpace.Rgb colorSpaceRgb; + + @Implementation(minSdk = Q, maxSdk = U.SDK_INT) protected static long nativeGetNativeFinalizer() { return ColorSpaceRgbNatives.nativeGetNativeFinalizer(); } - @Implementation(minSdk = Q) + @Implementation(minSdk = Q, maxSdk = U.SDK_INT) protected static long nativeCreate( float a, float b, float c, float d, float e, float f, float g, float[] xyz) { DefaultNativeRuntimeLoader.injectAndLoad(); diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeComposePathEffect.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeComposePathEffect.java index 6bf1e3ff4..10fb22bfd 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeComposePathEffect.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeComposePathEffect.java @@ -8,12 +8,17 @@ import org.robolectric.annotation.Implements; import org.robolectric.nativeruntime.ComposePathEffectNatives; import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader; import org.robolectric.shadows.ShadowNativeComposePathEffect.Picker; +import org.robolectric.versioning.AndroidVersions.U; /** Shadow for {@link ComposePathEffect} that is backed by native code */ -@Implements(value = ComposePathEffect.class, minSdk = O, shadowPicker = Picker.class) +@Implements( + value = ComposePathEffect.class, + minSdk = O, + shadowPicker = Picker.class, + callNativeMethodsByDefault = true) public class ShadowNativeComposePathEffect { - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static long nativeCreate(long nativeOuterpe, long nativeInnerpe) { DefaultNativeRuntimeLoader.injectAndLoad(); return ComposePathEffectNatives.nativeCreate(nativeOuterpe, nativeInnerpe); diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeComposeShader.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeComposeShader.java index b5e5b4a25..4beb5ed23 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeComposeShader.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeComposeShader.java @@ -8,12 +8,17 @@ import org.robolectric.annotation.Implements; import org.robolectric.nativeruntime.ComposeShaderNatives; import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader; import org.robolectric.shadows.ShadowNativeComposeShader.Picker; +import org.robolectric.versioning.AndroidVersions.U; /** Shadow for {@link ComposeShader} that is backed by native code */ -@Implements(value = ComposeShader.class, minSdk = O, shadowPicker = Picker.class) +@Implements( + value = ComposeShader.class, + minSdk = O, + shadowPicker = Picker.class, + callNativeMethodsByDefault = true) public class ShadowNativeComposeShader { - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static long nativeCreate( long nativeMatrix, long nativeShaderA, long nativeShaderB, int porterDuffMode) { DefaultNativeRuntimeLoader.injectAndLoad(); diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeCornerPathEffect.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeCornerPathEffect.java index 7c306298b..bb771cb37 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeCornerPathEffect.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeCornerPathEffect.java @@ -8,12 +8,17 @@ import org.robolectric.annotation.Implements; import org.robolectric.nativeruntime.CornerPathEffectNatives; import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader; import org.robolectric.shadows.ShadowNativeCornerPathEffect.Picker; +import org.robolectric.versioning.AndroidVersions.U; /** Shadow for {@link CornerPathEffect} that is backed by native code */ -@Implements(value = CornerPathEffect.class, minSdk = O, shadowPicker = Picker.class) +@Implements( + value = CornerPathEffect.class, + minSdk = O, + shadowPicker = Picker.class, + callNativeMethodsByDefault = true) public class ShadowNativeCornerPathEffect { - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static long nativeCreate(float radius) { DefaultNativeRuntimeLoader.injectAndLoad(); return CornerPathEffectNatives.nativeCreate(radius); diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeCursorWindow.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeCursorWindow.java index 71e0de7ee..f7c242326 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeCursorWindow.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeCursorWindow.java @@ -11,12 +11,13 @@ import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; import org.robolectric.nativeruntime.CursorWindowNatives; import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader; +import org.robolectric.versioning.AndroidVersions.U; /** Shadow for {@link CursorWindow} that is backed by native code */ -@Implements(value = CursorWindow.class, isInAndroidSdk = false) +@Implements(value = CursorWindow.class, isInAndroidSdk = false, callNativeMethodsByDefault = true) public class ShadowNativeCursorWindow extends ShadowCursorWindow { - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static Number nativeCreate(String name, int cursorWindowSize) { DefaultNativeRuntimeLoader.injectAndLoad(); long result = CursorWindowNatives.nativeCreate(name, cursorWindowSize); @@ -32,7 +33,7 @@ public class ShadowNativeCursorWindow extends ShadowCursorWindow { PreLPointers.remove(windowPtr); } - @Implementation(minSdk = LOLLIPOP) + @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT) protected static void nativeDispose(long windowPtr) { CursorWindowNatives.nativeDispose(windowPtr); } @@ -42,7 +43,7 @@ public class ShadowNativeCursorWindow extends ShadowCursorWindow { return nativeGetName(PreLPointers.get(windowPtr)); } - @Implementation(minSdk = LOLLIPOP) + @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT) protected static String nativeGetName(long windowPtr) { return CursorWindowNatives.nativeGetName(windowPtr); } @@ -52,7 +53,7 @@ public class ShadowNativeCursorWindow extends ShadowCursorWindow { return nativeGetBlob(PreLPointers.get(windowPtr), row, column); } - @Implementation(minSdk = LOLLIPOP) + @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT) protected static byte[] nativeGetBlob(long windowPtr, int row, int column) { return CursorWindowNatives.nativeGetBlob(windowPtr, row, column); } @@ -62,12 +63,12 @@ public class ShadowNativeCursorWindow extends ShadowCursorWindow { return nativeGetString(PreLPointers.get(windowPtr), row, column); } - @Implementation(minSdk = LOLLIPOP) + @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT) protected static String nativeGetString(long windowPtr, int row, int column) { return CursorWindowNatives.nativeGetString(windowPtr, row, column); } - @Implementation(minSdk = LOLLIPOP) + @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT) protected static void nativeCopyStringToBuffer( long windowPtr, int row, int column, CharArrayBuffer buffer) { CursorWindowNatives.nativeCopyStringToBuffer(windowPtr, row, column, buffer); @@ -78,7 +79,7 @@ public class ShadowNativeCursorWindow extends ShadowCursorWindow { return nativePutBlob(PreLPointers.get(windowPtr), value, row, column); } - @Implementation(minSdk = LOLLIPOP) + @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT) protected static boolean nativePutBlob(long windowPtr, byte[] value, int row, int column) { // Real Android will crash in native code if putBlob is called with a null value. Preconditions.checkNotNull(value); @@ -90,7 +91,7 @@ public class ShadowNativeCursorWindow extends ShadowCursorWindow { return nativePutString(PreLPointers.get(windowPtr), value, row, column); } - @Implementation(minSdk = LOLLIPOP) + @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT) protected static boolean nativePutString(long windowPtr, String value, int row, int column) { // Real Android will crash in native code if putString is called with a null value. Preconditions.checkNotNull(value); @@ -102,7 +103,7 @@ public class ShadowNativeCursorWindow extends ShadowCursorWindow { nativeClear(PreLPointers.get(windowPtr)); } - @Implementation(minSdk = LOLLIPOP) + @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT) protected static void nativeClear(long windowPtr) { CursorWindowNatives.nativeClear(windowPtr); } @@ -112,7 +113,7 @@ public class ShadowNativeCursorWindow extends ShadowCursorWindow { return nativeGetNumRows(PreLPointers.get(windowPtr)); } - @Implementation(minSdk = LOLLIPOP) + @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT) protected static int nativeGetNumRows(long windowPtr) { return CursorWindowNatives.nativeGetNumRows(windowPtr); } @@ -122,7 +123,7 @@ public class ShadowNativeCursorWindow extends ShadowCursorWindow { return nativeSetNumColumns(PreLPointers.get(windowPtr), columnNum); } - @Implementation(minSdk = LOLLIPOP) + @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT) protected static boolean nativeSetNumColumns(long windowPtr, int columnNum) { return CursorWindowNatives.nativeSetNumColumns(windowPtr, columnNum); } @@ -132,12 +133,12 @@ public class ShadowNativeCursorWindow extends ShadowCursorWindow { return nativeAllocRow(PreLPointers.get(windowPtr)); } - @Implementation(minSdk = LOLLIPOP) + @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT) protected static boolean nativeAllocRow(long windowPtr) { return CursorWindowNatives.nativeAllocRow(windowPtr); } - @Implementation(minSdk = LOLLIPOP) + @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT) protected static void nativeFreeLastRow(long windowPtr) { CursorWindowNatives.nativeFreeLastRow(windowPtr); } @@ -147,7 +148,7 @@ public class ShadowNativeCursorWindow extends ShadowCursorWindow { return nativeGetType(PreLPointers.get(windowPtr), row, column); } - @Implementation(minSdk = LOLLIPOP) + @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT) protected static int nativeGetType(long windowPtr, int row, int column) { return CursorWindowNatives.nativeGetType(windowPtr, row, column); } @@ -157,7 +158,7 @@ public class ShadowNativeCursorWindow extends ShadowCursorWindow { return nativeGetLong(PreLPointers.get(windowPtr), row, column); } - @Implementation(minSdk = LOLLIPOP) + @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT) protected static long nativeGetLong(long windowPtr, int row, int column) { return CursorWindowNatives.nativeGetLong(windowPtr, row, column); } @@ -167,7 +168,7 @@ public class ShadowNativeCursorWindow extends ShadowCursorWindow { return nativeGetDouble(PreLPointers.get(windowPtr), row, column); } - @Implementation(minSdk = LOLLIPOP) + @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT) protected static double nativeGetDouble(long windowPtr, int row, int column) { return CursorWindowNatives.nativeGetDouble(windowPtr, row, column); } @@ -177,7 +178,7 @@ public class ShadowNativeCursorWindow extends ShadowCursorWindow { return nativePutLong(PreLPointers.get(windowPtr), value, row, column); } - @Implementation(minSdk = LOLLIPOP) + @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT) protected static boolean nativePutLong(long windowPtr, long value, int row, int column) { return CursorWindowNatives.nativePutLong(windowPtr, value, row, column); } @@ -187,7 +188,7 @@ public class ShadowNativeCursorWindow extends ShadowCursorWindow { return nativePutDouble(PreLPointers.get(windowPtr), value, row, column); } - @Implementation(minSdk = LOLLIPOP) + @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT) protected static boolean nativePutDouble(long windowPtr, double value, int row, int column) { return CursorWindowNatives.nativePutDouble(windowPtr, value, row, column); } @@ -197,7 +198,7 @@ public class ShadowNativeCursorWindow extends ShadowCursorWindow { return nativePutNull(PreLPointers.get(windowPtr), row, column); } - @Implementation(minSdk = LOLLIPOP) + @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT) protected static boolean nativePutNull(long windowPtr, int row, int column) { return CursorWindowNatives.nativePutNull(windowPtr, row, column); } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeDashPathEffect.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeDashPathEffect.java index d8ad1eb3f..d3e6c4bc8 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeDashPathEffect.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeDashPathEffect.java @@ -8,12 +8,17 @@ import org.robolectric.annotation.Implements; import org.robolectric.nativeruntime.DashPathEffectNatives; import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader; import org.robolectric.shadows.ShadowNativeDashPathEffect.Picker; +import org.robolectric.versioning.AndroidVersions.U; /** Shadow for {@link DashPathEffect} that is backed by native code */ -@Implements(value = DashPathEffect.class, minSdk = O, shadowPicker = Picker.class) +@Implements( + value = DashPathEffect.class, + minSdk = O, + shadowPicker = Picker.class, + callNativeMethodsByDefault = true) public class ShadowNativeDashPathEffect { - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static long nativeCreate(float[] intervals, float phase) { DefaultNativeRuntimeLoader.injectAndLoad(); return DashPathEffectNatives.nativeCreate(intervals, phase); diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeDiscretePathEffect.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeDiscretePathEffect.java index b7f5e3ed6..4f1f6f930 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeDiscretePathEffect.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeDiscretePathEffect.java @@ -8,12 +8,17 @@ import org.robolectric.annotation.Implements; import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader; import org.robolectric.nativeruntime.DiscretePathEffectNatives; import org.robolectric.shadows.ShadowNativeDiscretePathEffect.Picker; +import org.robolectric.versioning.AndroidVersions.U; /** Shadow for {@link DiscretePathEffect} that is backed by native code */ -@Implements(value = DiscretePathEffect.class, minSdk = O, shadowPicker = Picker.class) +@Implements( + value = DiscretePathEffect.class, + minSdk = O, + shadowPicker = Picker.class, + callNativeMethodsByDefault = true) public class ShadowNativeDiscretePathEffect { - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static long nativeCreate(float length, float deviation) { DefaultNativeRuntimeLoader.injectAndLoad(); return DiscretePathEffectNatives.nativeCreate(length, deviation); diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeEmbossMaskFilter.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeEmbossMaskFilter.java index 52ed28e42..ace282210 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeEmbossMaskFilter.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeEmbossMaskFilter.java @@ -8,12 +8,17 @@ import org.robolectric.annotation.Implements; import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader; import org.robolectric.nativeruntime.EmbossMaskFilterNatives; import org.robolectric.shadows.ShadowNativeEmbossMaskFilter.Picker; +import org.robolectric.versioning.AndroidVersions.U; /** Shadow for {@link EmbossMaskFilter} that is backed by native code */ -@Implements(value = EmbossMaskFilter.class, minSdk = O, shadowPicker = Picker.class) +@Implements( + value = EmbossMaskFilter.class, + minSdk = O, + shadowPicker = Picker.class, + callNativeMethodsByDefault = true) public class ShadowNativeEmbossMaskFilter { - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static long nativeConstructor( float[] direction, float ambient, float specular, float blurRadius) { DefaultNativeRuntimeLoader.injectAndLoad(); diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeFont.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeFont.java index 2c64de3cd..23f5b7f86 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeFont.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeFont.java @@ -29,82 +29,97 @@ import org.robolectric.nativeruntime.FontNatives; import org.robolectric.shadows.ShadowNativeFont.Picker; import org.robolectric.util.reflector.Accessor; import org.robolectric.util.reflector.ForType; +import org.robolectric.versioning.AndroidVersions.U; +import org.robolectric.versioning.AndroidVersions.V; /** Shadow for {@link Font} that is backed by native code */ -@Implements(value = Font.class, minSdk = P, shadowPicker = Picker.class, isInAndroidSdk = false) +@Implements( + value = Font.class, + minSdk = P, + shadowPicker = Picker.class, + isInAndroidSdk = false, + callNativeMethodsByDefault = true) public class ShadowNativeFont { - @Implementation(minSdk = S) + + /** + * {@link android.graphics.fonts.Font} invokes its own native methods in its static initializer. + * This must be deferred starting in Android V. + */ + @Implementation(minSdk = V.SDK_INT) + protected static void __staticInitializer__() {} + + @Implementation(minSdk = S, maxSdk = U.SDK_INT) protected static long nGetMinikinFontPtr(long font) { return FontNatives.nGetMinikinFontPtr(font); } - @Implementation(minSdk = S) + @Implementation(minSdk = S, maxSdk = U.SDK_INT) protected static long nCloneFont(long font) { return FontNatives.nCloneFont(font); } - @Implementation(minSdk = S) + @Implementation(minSdk = S, maxSdk = U.SDK_INT) protected static ByteBuffer nNewByteBuffer(long font) { return FontNatives.nNewByteBuffer(font); } - @Implementation(minSdk = S) + @Implementation(minSdk = S, maxSdk = U.SDK_INT) protected static long nGetBufferAddress(long font) { return FontNatives.nGetBufferAddress(font); } - @Implementation(minSdk = S) + @Implementation(minSdk = S, maxSdk = U.SDK_INT) protected static int nGetSourceId(long font) { return FontNatives.nGetSourceId(font); } - @Implementation(minSdk = S) + @Implementation(minSdk = S, maxSdk = U.SDK_INT) protected static long nGetReleaseNativeFont() { DefaultNativeRuntimeLoader.injectAndLoad(); return FontNatives.nGetReleaseNativeFont(); } - @Implementation(minSdk = S) + @Implementation(minSdk = S, maxSdk = U.SDK_INT) protected static float nGetGlyphBounds(long font, int glyphId, long paint, RectF rect) { return FontNatives.nGetGlyphBounds(font, glyphId, paint, rect); } - @Implementation(minSdk = S) + @Implementation(minSdk = S, maxSdk = U.SDK_INT) protected static float nGetFontMetrics(long font, long paint, Paint.FontMetrics metrics) { return FontNatives.nGetFontMetrics(font, paint, metrics); } - @Implementation(minSdk = S) + @Implementation(minSdk = S, maxSdk = U.SDK_INT) protected static String nGetFontPath(long fontPtr) { return FontNatives.nGetFontPath(fontPtr); } - @Implementation(minSdk = S) + @Implementation(minSdk = S, maxSdk = U.SDK_INT) protected static String nGetLocaleList(long familyPtr) { return FontNatives.nGetLocaleList(familyPtr); } - @Implementation(minSdk = S) + @Implementation(minSdk = S, maxSdk = U.SDK_INT) protected static int nGetPackedStyle(long fontPtr) { return FontNatives.nGetPackedStyle(fontPtr); } - @Implementation(minSdk = S) + @Implementation(minSdk = S, maxSdk = U.SDK_INT) protected static int nGetIndex(long fontPtr) { return FontNatives.nGetIndex(fontPtr); } - @Implementation(minSdk = S) + @Implementation(minSdk = S, maxSdk = U.SDK_INT) protected static int nGetAxisCount(long fontPtr) { return FontNatives.nGetAxisCount(fontPtr); } - @Implementation(minSdk = S) + @Implementation(minSdk = S, maxSdk = U.SDK_INT) protected static long nGetAxisInfo(long fontPtr, int i) { return FontNatives.nGetAxisInfo(fontPtr, i); } - @Implementation(minSdk = S) + @Implementation(minSdk = S, maxSdk = U.SDK_INT) protected static long[] nGetAvailableFontSet() { return FontNatives.nGetAvailableFontSet(); } @@ -114,7 +129,8 @@ public class ShadowNativeFont { value = Font.Builder.class, minSdk = P, shadowPicker = ShadowNativeFontBuilder.Picker.class, - isInAndroidSdk = false) + isInAndroidSdk = false, + callNativeMethodsByDefault = true) public static class ShadowNativeFontBuilder { @RealObject Font.Builder realFontBuilder; @@ -162,18 +178,18 @@ public class ShadowNativeFont { } } - @Implementation(minSdk = Q) + @Implementation(minSdk = Q, maxSdk = U.SDK_INT) protected static long nInitBuilder() { DefaultNativeRuntimeLoader.injectAndLoad(); return FontBuilderNatives.nInitBuilder(); } - @Implementation(minSdk = Q) + @Implementation(minSdk = Q, maxSdk = U.SDK_INT) protected static void nAddAxis(long builderPtr, int tag, float value) { FontBuilderNatives.nAddAxis(builderPtr, tag, value); } - @Implementation(minSdk = S) + @Implementation(minSdk = S, maxSdk = U.SDK_INT) protected static long nBuild( long builderPtr, ByteBuffer buffer, @@ -206,7 +222,7 @@ public class ShadowNativeFont { return FontNatives.nGetReleaseNativeFont(); } - @Implementation(minSdk = S) + @Implementation(minSdk = S, maxSdk = U.SDK_INT) protected static long nClone( long fontPtr, long builderPtr, int weight, boolean italic, int ttcIndex) { return FontBuilderNatives.nClone(fontPtr, builderPtr, weight, italic, ttcIndex); @@ -223,6 +239,12 @@ public class ShadowNativeFont { return assetToBuffer(am, path, isAsset, cookie); } + /** RNG does not support native assets */ + @Implementation(minSdk = Q, maxSdk = Q) + protected static long nGetReleaseNativeAssetFunc() { + return 0; + } + @ForType(Font.Builder.class) interface FontBuilderReflector { @Accessor("mBuffer") diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeFontFamily.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeFontFamily.java index 7a78e471f..495e22cb5 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeFontFamily.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeFontFamily.java @@ -14,15 +14,28 @@ import org.robolectric.annotation.Implements; import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader; import org.robolectric.nativeruntime.FontFamilyNatives; import org.robolectric.shadows.ShadowNativeFontFamily.Picker; +import org.robolectric.versioning.AndroidVersions.U; +import org.robolectric.versioning.AndroidVersions.V; /** Shadow for {@link FontFamily} that is backed by native code */ @Implements( value = FontFamily.class, minSdk = O, isInAndroidSdk = false, - shadowPicker = Picker.class) + shadowPicker = Picker.class, + callNativeMethodsByDefault = true) public class ShadowNativeFontFamily { - @Implementation(minSdk = O) + + /** + * {@link android.graphics.FontFamily} invokes its own native methods in its static initializer. + * This must be deferred starting in Android V. + */ + @Implementation(minSdk = V.SDK_INT) + protected static void __staticInitializer__() { + // deferred + } + + @Implementation(minSdk = O, maxSdk = U.SDK_INT) public static long nInitBuilder(String langs, int variant) { DefaultNativeRuntimeLoader.injectAndLoad(); return FontFamilyNatives.nInitBuilder(langs, variant); @@ -33,25 +46,25 @@ public class ShadowNativeFontFamily { FontFamilyNatives.nAllowUnsupportedFont(builderPtr); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static long nCreateFamily(long mBuilderPtr) { return FontFamilyNatives.nCreateFamily(mBuilderPtr); } - @Implementation(minSdk = P) + @Implementation(minSdk = P, maxSdk = U.SDK_INT) protected static long nGetBuilderReleaseFunc() { DefaultNativeRuntimeLoader.injectAndLoad(); return FontFamilyNatives.nGetBuilderReleaseFunc(); } - @Implementation(minSdk = P) + @Implementation(minSdk = P, maxSdk = U.SDK_INT) protected static long nGetFamilyReleaseFunc() { return FontFamilyNatives.nGetFamilyReleaseFunc(); } // By passing -1 to weight argument, the weight value is resolved by OS/2 table in the font. // By passing -1 to italic argument, the italic value is resolved by OS/2 table in the font. - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static boolean nAddFont( long builderPtr, ByteBuffer font, int ttcIndex, int weight, int isItalic) { return FontFamilyNatives.nAddFont(builderPtr, font, ttcIndex, weight, isItalic); @@ -75,18 +88,23 @@ public class ShadowNativeFontFamily { } } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static boolean nAddFontWeightStyle( long builderPtr, ByteBuffer font, int ttcIndex, int weight, int isItalic) { return FontFamilyNatives.nAddFontWeightStyle(builderPtr, font, ttcIndex, weight, isItalic); } // The added axis values are only valid for the next nAddFont* method call. - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nAddAxisValue(long builderPtr, int tag, float value) { FontFamilyNatives.nAddAxisValue(builderPtr, tag, value); } + @Implementation(minSdk = O, maxSdk = O_MR1) + protected static void nAbort(long mBuilderPtr) { + // no-op + } + /** Shadow picker for {@link FontFamily}. */ public static final class Picker extends GraphicsShadowPicker<Object> { public Picker() { diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeFontFileUtil.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeFontFileUtil.java index a38e280aa..23909c3bb 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeFontFileUtil.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeFontFileUtil.java @@ -10,27 +10,29 @@ import org.robolectric.annotation.Implements; import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader; import org.robolectric.nativeruntime.FontFileUtilNatives; import org.robolectric.shadows.ShadowNativeFontFileUtil.Picker; +import org.robolectric.versioning.AndroidVersions.U; /** Shadow for {@link FontFileUtil} that is backed by native code */ @Implements( value = FontFileUtil.class, isInAndroidSdk = false, minSdk = Q, - shadowPicker = Picker.class) + shadowPicker = Picker.class, + callNativeMethodsByDefault = true) public class ShadowNativeFontFileUtil { - @Implementation(minSdk = S) + @Implementation(minSdk = S, maxSdk = U.SDK_INT) protected static long nGetFontRevision(ByteBuffer buffer, int index) { DefaultNativeRuntimeLoader.injectAndLoad(); return FontFileUtilNatives.nGetFontRevision(buffer, index); } - @Implementation(minSdk = S) + @Implementation(minSdk = S, maxSdk = U.SDK_INT) protected static String nGetFontPostScriptName(ByteBuffer buffer, int index) { DefaultNativeRuntimeLoader.injectAndLoad(); return FontFileUtilNatives.nGetFontPostScriptName(buffer, index); } - @Implementation(minSdk = S) + @Implementation(minSdk = S, maxSdk = U.SDK_INT) protected static int nIsPostScriptType1Font(ByteBuffer buffer, int index) { DefaultNativeRuntimeLoader.injectAndLoad(); return FontFileUtilNatives.nIsPostScriptType1Font(buffer, index); diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeFontsFontFamily.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeFontsFontFamily.java index 365b6a9a1..d2174f977 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeFontsFontFamily.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeFontsFontFamily.java @@ -19,24 +19,26 @@ import org.robolectric.versioning.AndroidVersions.V; value = FontFamily.class, minSdk = Q, shadowPicker = Picker.class, - isInAndroidSdk = false) + isInAndroidSdk = false, + callNativeMethodsByDefault = true) public class ShadowNativeFontsFontFamily { - @Implementation(minSdk = S) + + @Implementation(minSdk = S, maxSdk = U.SDK_INT) protected static int nGetFontSize(long family) { return FontsFontFamilyNatives.nGetFontSize(family); } - @Implementation(minSdk = S) + @Implementation(minSdk = S, maxSdk = U.SDK_INT) protected static long nGetFont(long family, int i) { return FontsFontFamilyNatives.nGetFont(family, i); } - @Implementation(minSdk = S) + @Implementation(minSdk = S, maxSdk = U.SDK_INT) protected static String nGetLangTags(long family) { return FontsFontFamilyNatives.nGetLangTags(family); } - @Implementation(minSdk = S) + @Implementation(minSdk = S, maxSdk = U.SDK_INT) protected static int nGetVariant(long family) { return FontsFontFamilyNatives.nGetVariant(family); } @@ -46,15 +48,20 @@ public class ShadowNativeFontsFontFamily { value = FontFamily.Builder.class, minSdk = Q, shadowPicker = ShadowNativeFontFamilyBuilder.Picker.class, - isInAndroidSdk = false) + isInAndroidSdk = false, + callNativeMethodsByDefault = true) public static class ShadowNativeFontFamilyBuilder { - @Implementation + + @Implementation(minSdk = V.SDK_INT) + protected static void __staticInitializer__() {} + + @Implementation(maxSdk = U.SDK_INT) protected static long nInitBuilder() { DefaultNativeRuntimeLoader.injectAndLoad(); return FontFamilyBuilderNatives.nInitBuilder(); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nAddFont(long builderPtr, long fontPtr) { FontFamilyBuilderNatives.nAddFont(builderPtr, fontPtr); } @@ -75,18 +82,7 @@ public class ShadowNativeFontsFontFamily { return FontFamilyBuilderNatives.nBuild(builderPtr, langTags, variant, isCustomFallback); } - @Implementation(minSdk = V.SDK_INT) - protected static long nBuild( - long builderPtr, - String langTags, - int variant, - boolean isCustomFallback, - boolean isDefaultFallback, - int variableFamilyType) { - return FontFamilyBuilderNatives.nBuild(builderPtr, langTags, variant, isCustomFallback); - } - - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static long nGetReleaseNativeFamily() { return FontFamilyBuilderNatives.nGetReleaseNativeFamily(); } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeHardwareRenderer.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeHardwareRenderer.java index 408ea1c27..6c63df06c 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeHardwareRenderer.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeHardwareRenderer.java @@ -3,6 +3,7 @@ package org.robolectric.shadows; import static android.os.Build.VERSION_CODES.Q; import static android.os.Build.VERSION_CODES.R; import static android.os.Build.VERSION_CODES.S; +import static android.os.Build.VERSION_CODES.S_V2; import static android.os.Build.VERSION_CODES.TIRAMISU; import android.graphics.Bitmap; @@ -26,50 +27,51 @@ import org.robolectric.versioning.AndroidVersions.U; value = HardwareRenderer.class, minSdk = Q, looseSignatures = true, - shadowPicker = Picker.class) + shadowPicker = Picker.class, + callNativeMethodsByDefault = true) public class ShadowNativeHardwareRenderer { - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void disableVsync() { HardwareRendererNatives.disableVsync(); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void preload() { HardwareRendererNatives.preload(); } - @Implementation(minSdk = S) + @Implementation(minSdk = S, maxSdk = U.SDK_INT) protected static boolean isWebViewOverlaysEnabled() { return HardwareRendererNatives.isWebViewOverlaysEnabled(); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void setupShadersDiskCache(String cacheFile, String skiaCacheFile) { HardwareRendererNatives.setupShadersDiskCache(cacheFile, skiaCacheFile); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nRotateProcessStatsBuffer() { HardwareRendererNatives.nRotateProcessStatsBuffer(); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nSetProcessStatsBuffer(int fd) { HardwareRendererNatives.nSetProcessStatsBuffer(fd); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static int nGetRenderThreadTid(long nativeProxy) { return HardwareRendererNatives.nGetRenderThreadTid(nativeProxy); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static long nCreateRootRenderNode() { DefaultNativeRuntimeLoader.injectAndLoad(); return HardwareRendererNatives.nCreateRootRenderNode(); } - @Implementation(minSdk = S) + @Implementation(minSdk = S, maxSdk = U.SDK_INT) protected static long nCreateProxy(boolean translucent, long rootRenderNode) { return HardwareRendererNatives.nCreateProxy(translucent, rootRenderNode); } @@ -85,194 +87,194 @@ public class ShadowNativeHardwareRenderer { return nCreateProxy((boolean) translucent, (long) rootRenderNode); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nDeleteProxy(long nativeProxy) { HardwareRendererNatives.nDeleteProxy(nativeProxy); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static boolean nLoadSystemProperties(long nativeProxy) { return HardwareRendererNatives.nLoadSystemProperties(nativeProxy); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nSetName(long nativeProxy, String name) { HardwareRendererNatives.nSetName(nativeProxy, name); } - @Implementation(minSdk = R) + @Implementation(minSdk = R, maxSdk = U.SDK_INT) protected static void nSetSurface(long nativeProxy, Surface window, boolean discardBuffer) { HardwareRendererNatives.nSetSurface(nativeProxy, window, discardBuffer); } - @Implementation(minSdk = S) + @Implementation(minSdk = S, maxSdk = U.SDK_INT) protected static void nSetSurfaceControl(long nativeProxy, long nativeSurfaceControl) { HardwareRendererNatives.nSetSurfaceControl(nativeProxy, nativeSurfaceControl); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static boolean nPause(long nativeProxy) { return HardwareRendererNatives.nPause(nativeProxy); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nSetStopped(long nativeProxy, boolean stopped) { HardwareRendererNatives.nSetStopped(nativeProxy, stopped); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nSetLightGeometry( long nativeProxy, float lightX, float lightY, float lightZ, float lightRadius) { HardwareRendererNatives.nSetLightGeometry(nativeProxy, lightX, lightY, lightZ, lightRadius); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nSetLightAlpha( long nativeProxy, float ambientShadowAlpha, float spotShadowAlpha) { HardwareRendererNatives.nSetLightAlpha(nativeProxy, ambientShadowAlpha, spotShadowAlpha); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nSetOpaque(long nativeProxy, boolean opaque) { HardwareRendererNatives.nSetOpaque(nativeProxy, opaque); } - @Implementation(minSdk = S) + @Implementation(minSdk = S, maxSdk = U.SDK_INT) protected static Object nSetColorMode(long nativeProxy, int colorMode) { HardwareRendererNatives.nSetColorMode(nativeProxy, colorMode); return null; } - @Implementation(minSdk = S) + @Implementation(minSdk = S, maxSdk = U.SDK_INT) protected static void nSetSdrWhitePoint(long nativeProxy, float whitePoint) { HardwareRendererNatives.nSetSdrWhitePoint(nativeProxy, whitePoint); } - @Implementation(minSdk = S) + @Implementation(minSdk = S, maxSdk = U.SDK_INT) protected static void nSetIsHighEndGfx(boolean isHighEndGfx) { HardwareRendererNatives.nSetIsHighEndGfx(isHighEndGfx); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static int nSyncAndDrawFrame(long nativeProxy, long[] frameInfo, int size) { return HardwareRendererNatives.nSyncAndDrawFrame(nativeProxy, frameInfo, size); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nDestroy(long nativeProxy, long rootRenderNode) { HardwareRendererNatives.nDestroy(nativeProxy, rootRenderNode); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nRegisterAnimatingRenderNode(long rootRenderNode, long animatingNode) { HardwareRendererNatives.nRegisterAnimatingRenderNode(rootRenderNode, animatingNode); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nRegisterVectorDrawableAnimator(long rootRenderNode, long animator) { HardwareRendererNatives.nRegisterVectorDrawableAnimator(rootRenderNode, animator); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static long nCreateTextureLayer(long nativeProxy) { return HardwareRendererNatives.nCreateTextureLayer(nativeProxy); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nBuildLayer(long nativeProxy, long node) { HardwareRendererNatives.nBuildLayer(nativeProxy, node); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static boolean nCopyLayerInto(long nativeProxy, long layer, long bitmapHandle) { return HardwareRendererNatives.nCopyLayerInto(nativeProxy, layer, bitmapHandle); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nPushLayerUpdate(long nativeProxy, long layer) { HardwareRendererNatives.nPushLayerUpdate(nativeProxy, layer); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nCancelLayerUpdate(long nativeProxy, long layer) { HardwareRendererNatives.nCancelLayerUpdate(nativeProxy, layer); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nDetachSurfaceTexture(long nativeProxy, long layer) { HardwareRendererNatives.nDetachSurfaceTexture(nativeProxy, layer); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nDestroyHardwareResources(long nativeProxy) { HardwareRendererNatives.nDestroyHardwareResources(nativeProxy); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nTrimMemory(int level) { HardwareRendererNatives.nTrimMemory(level); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nOverrideProperty(String name, String value) { HardwareRendererNatives.nOverrideProperty(name, value); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nFence(long nativeProxy) { HardwareRendererNatives.nFence(nativeProxy); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nStopDrawing(long nativeProxy) { HardwareRendererNatives.nStopDrawing(nativeProxy); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nNotifyFramePending(long nativeProxy) { HardwareRendererNatives.nNotifyFramePending(nativeProxy); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nDumpProfileInfo(long nativeProxy, FileDescriptor fd, int dumpFlags) { HardwareRendererNatives.nDumpProfileInfo(nativeProxy, fd, dumpFlags); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nAddRenderNode(long nativeProxy, long rootRenderNode, boolean placeFront) { HardwareRendererNatives.nAddRenderNode(nativeProxy, rootRenderNode, placeFront); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nRemoveRenderNode(long nativeProxy, long rootRenderNode) { HardwareRendererNatives.nRemoveRenderNode(nativeProxy, rootRenderNode); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nDrawRenderNode(long nativeProxy, long rootRenderNode) { HardwareRendererNatives.nDrawRenderNode(nativeProxy, rootRenderNode); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nSetContentDrawBounds( long nativeProxy, int left, int top, int right, int bottom) { HardwareRendererNatives.nSetContentDrawBounds(nativeProxy, left, top, right, bottom); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nSetPictureCaptureCallback( long nativeProxy, PictureCapturedCallback callback) { HardwareRendererNatives.nSetPictureCaptureCallback(nativeProxy, callback); } - @Implementation(minSdk = S) + @Implementation(minSdk = S, maxSdk = U.SDK_INT) protected static void nSetASurfaceTransactionCallback(Object nativeProxy, Object callback) { // Requires looseSignatures because ASurfaceTransactionCallback is S+. HardwareRendererNatives.nSetASurfaceTransactionCallback( (long) nativeProxy, (ASurfaceTransactionCallback) callback); } - @Implementation(minSdk = S) + @Implementation(minSdk = S, maxSdk = U.SDK_INT) protected static void nSetPrepareSurfaceControlForWebviewCallback( Object nativeProxy, Object callback) { // Need to use loose signatures here as PrepareSurfaceControlForWebviewCallback is S+. @@ -280,23 +282,23 @@ public class ShadowNativeHardwareRenderer { (long) nativeProxy, (PrepareSurfaceControlForWebviewCallback) callback); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nSetFrameCallback(long nativeProxy, FrameDrawingCallback callback) { HardwareRendererNatives.nSetFrameCallback(nativeProxy, callback); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nSetFrameCompleteCallback( long nativeProxy, FrameCompleteCallback callback) { HardwareRendererNatives.nSetFrameCompleteCallback(nativeProxy, callback); } - @Implementation(minSdk = R) + @Implementation(minSdk = R, maxSdk = U.SDK_INT) protected static void nAddObserver(long nativeProxy, long nativeObserver) { HardwareRendererNatives.nAddObserver(nativeProxy, nativeObserver); } - @Implementation(minSdk = R) + @Implementation(minSdk = R, maxSdk = U.SDK_INT) protected static void nRemoveObserver(long nativeProxy, long nativeObserver) { HardwareRendererNatives.nRemoveObserver(nativeProxy, nativeObserver); } @@ -308,38 +310,43 @@ public class ShadowNativeHardwareRenderer { surface, srcLeft, srcTop, srcRight, srcBottom, bitmapHandle); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static Bitmap nCreateHardwareBitmap(long renderNode, int width, int height) { return HardwareRendererNatives.nCreateHardwareBitmap(renderNode, width, height); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nSetHighContrastText(boolean enabled) { HardwareRendererNatives.nSetHighContrastText(enabled); } - @Implementation(minSdk = Q, maxSdk = S) + @Implementation(minSdk = Q, maxSdk = S_V2) protected static void nHackySetRTAnimationsEnabled(boolean enabled) { DefaultNativeRuntimeLoader.injectAndLoad(); HardwareRendererNatives.nHackySetRTAnimationsEnabled(enabled); } - @Implementation + @Implementation(minSdk = TIRAMISU, maxSdk = U.SDK_INT) + protected static void nSetRtAnimationsEnabled(boolean enabled) { + nHackySetRTAnimationsEnabled(enabled); + } + + @Implementation(maxSdk = U.SDK_INT) protected static void nSetDebuggingEnabled(boolean enabled) { HardwareRendererNatives.nSetDebuggingEnabled(enabled); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nSetIsolatedProcess(boolean enabled) { HardwareRendererNatives.nSetIsolatedProcess(enabled); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nSetContextPriority(int priority) { HardwareRendererNatives.nSetContextPriority(priority); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nAllocateBuffers(long nativeProxy) { HardwareRendererNatives.nAllocateBuffers(nativeProxy); } @@ -351,7 +358,7 @@ public class ShadowNativeHardwareRenderer { // TODO(brettchabot): add support for V nSetForceDark(long, int) - @Implementation(minSdk = S) + @Implementation(minSdk = S, maxSdk = U.SDK_INT) protected static void nSetDisplayDensityDpi(int densityDpi) { HardwareRendererNatives.nSetDisplayDensityDpi(densityDpi); } @@ -373,7 +380,7 @@ public class ShadowNativeHardwareRenderer { presentationDeadlineNanos); } - @Implementation(minSdk = U.SDK_INT) + @Implementation(minSdk = U.SDK_INT, maxSdk = U.SDK_INT) protected static void nInitDisplayInfo( int width, int height, @@ -392,6 +399,11 @@ public class ShadowNativeHardwareRenderer { presentationDeadlineNanos); } + @Implementation(maxSdk = R) + protected static void nSetWideGamut(long nativeProxy, boolean wideGamut) { + // No-op + } + /** Shadow picker for {@link HardwareRenderer}. */ public static final class Picker extends GraphicsShadowPicker<Object> { public Picker() { diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeHardwareRendererObserver.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeHardwareRendererObserver.java index 97b05eb18..8f57d456f 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeHardwareRendererObserver.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeHardwareRendererObserver.java @@ -13,19 +13,21 @@ import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader; import org.robolectric.nativeruntime.HardwareRendererObserverNatives; import org.robolectric.shadow.api.Shadow; import org.robolectric.shadows.ShadowNativeHardwareRendererObserver.Picker; +import org.robolectric.versioning.AndroidVersions.U; /** Shadow for {@link HardwareRendererObserver} that is backed by native code */ @Implements( value = HardwareRendererObserver.class, minSdk = R, shadowPicker = Picker.class, - isInAndroidSdk = false) + isInAndroidSdk = false, + callNativeMethodsByDefault = true) public class ShadowNativeHardwareRendererObserver { public HardwareRendererObserverNatives hardwareRendererObserverNatives = new HardwareRendererObserverNatives(); - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static int nGetNextBuffer(long nativePtr, long[] data) { return HardwareRendererObserverNatives.nGetNextBuffer(nativePtr, data); } @@ -41,7 +43,7 @@ public class ShadowNativeHardwareRendererObserver { return hardwareRendererObserverNatives.nCreateObserver(waitForPresentTime); } - @Implementation(minSdk = TIRAMISU) + @Implementation(minSdk = TIRAMISU, maxSdk = U.SDK_INT) protected static long nCreateObserver( WeakReference<HardwareRendererObserver> observer, boolean waitForPresentTime) { HardwareRendererObserver hardwareRendererObserver = observer.get(); diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeImageDecoder.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeImageDecoder.java index 4913c9f24..886f5755d 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeImageDecoder.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeImageDecoder.java @@ -4,6 +4,7 @@ import static android.os.Build.VERSION_CODES.P; import static android.os.Build.VERSION_CODES.Q; import static android.os.Build.VERSION_CODES.R; import static android.os.Build.VERSION_CODES.S; +import static org.robolectric.util.reflector.Reflector.reflector; import android.content.res.AssetManager.AssetInputStream; import android.graphics.Bitmap; @@ -18,14 +19,22 @@ import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.nio.ByteBuffer; import java.nio.ByteOrder; +import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader; import org.robolectric.nativeruntime.ImageDecoderNatives; import org.robolectric.shadows.ShadowNativeImageDecoder.Picker; +import org.robolectric.util.reflector.ForType; +import org.robolectric.util.reflector.Static; +import org.robolectric.versioning.AndroidVersions.U; /** Shadow for {@link android.graphics.ImageDecoder} that is backed by native code */ -@Implements(value = ImageDecoder.class, minSdk = P, shadowPicker = Picker.class) +@Implements( + value = ImageDecoder.class, + minSdk = P, + shadowPicker = Picker.class, + callNativeMethodsByDefault = true) public class ShadowNativeImageDecoder { static { @@ -52,7 +61,12 @@ public class ShadowNativeImageDecoder { if (ais.read() != -1) { throw new IOException("Unable to access full contents of asset"); } - return nCreate(buffer, 0, bytesRead, preferAnimation, source); + if (RuntimeEnvironment.getApiLevel() > U.SDK_INT) { + return reflector(ImageDecoderReflector.class) + .nCreate(buffer, 0, bytesRead, preferAnimation, source); + } else { + return nCreate(buffer, 0, bytesRead, preferAnimation, source); + } } @Implementation(minSdk = P, maxSdk = Q) @@ -72,7 +86,7 @@ public class ShadowNativeImageDecoder { return nCreate(buffer, position, limit, false, src); } - @Implementation(minSdk = R) + @Implementation(minSdk = R, maxSdk = U.SDK_INT) protected static ImageDecoder nCreate( ByteBuffer buffer, int position, int limit, boolean preferAnimation, Source src) throws IOException { @@ -85,7 +99,7 @@ public class ShadowNativeImageDecoder { return nCreate(data, offset, length, false, src); } - @Implementation(minSdk = R) + @Implementation(minSdk = R, maxSdk = U.SDK_INT) protected static ImageDecoder nCreate( byte[] data, int offset, int length, boolean preferAnimation, Source src) throws IOException { return ImageDecoderNatives.nCreate(data, offset, length, preferAnimation, src); @@ -97,7 +111,7 @@ public class ShadowNativeImageDecoder { return nCreate(is, storage, false, src); } - @Implementation(minSdk = R) + @Implementation(minSdk = R, maxSdk = U.SDK_INT) protected static ImageDecoder nCreate( InputStream is, byte[] storage, boolean preferAnimation, Source src) throws IOException { return ImageDecoderNatives.nCreate(is, storage, preferAnimation, src); @@ -108,7 +122,7 @@ public class ShadowNativeImageDecoder { throw new UnsupportedEncodingException(); } - @Implementation(minSdk = S) + @Implementation(minSdk = S, maxSdk = U.SDK_INT) protected static ImageDecoder nCreate( FileDescriptor fd, long length, boolean preferAnimation, Source src) throws IOException { return ImageDecoderNatives.nCreate(fd, length, preferAnimation, src); @@ -145,7 +159,7 @@ public class ShadowNativeImageDecoder { /* extended = */ false); } - @Implementation(minSdk = Q) + @Implementation(minSdk = Q, maxSdk = U.SDK_INT) protected static Bitmap nDecodeBitmap( long nativePtr, ImageDecoder decoder, @@ -177,31 +191,38 @@ public class ShadowNativeImageDecoder { extended); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static Size nGetSampledSize(long nativePtr, int sampleSize) { return ImageDecoderNatives.nGetSampledSize(nativePtr, sampleSize); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nGetPadding(long nativePtr, Rect outRect) { ImageDecoderNatives.nGetPadding(nativePtr, outRect); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nClose(long nativePtr) { ImageDecoderNatives.nClose(nativePtr); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static String nGetMimeType(long nativePtr) { return ImageDecoderNatives.nGetMimeType(nativePtr); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static ColorSpace nGetColorSpace(long nativePtr) { return ImageDecoderNatives.nGetColorSpace(nativePtr); } + @ForType(ImageDecoder.class) + interface ImageDecoderReflector { + @Static + ImageDecoder nCreate( + ByteBuffer buffer, int position, int limit, boolean preferAnimation, Source src); + } + /** Shadow picker for {@link ImageDecoder}. */ public static final class Picker extends GraphicsShadowPicker<Object> { public Picker() { diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeImageReader.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeImageReader.java index 72d5d0cf6..633f1062c 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeImageReader.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeImageReader.java @@ -17,6 +17,7 @@ import org.robolectric.util.reflector.Accessor; import org.robolectric.util.reflector.ForType; import org.robolectric.versioning.AndroidVersions.T; import org.robolectric.versioning.AndroidVersions.U; +import org.robolectric.versioning.AndroidVersions.V; /** Shadow for {@link ImageReader} that is backed by native code */ @Implements( @@ -24,9 +25,19 @@ import org.robolectric.versioning.AndroidVersions.U; minSdk = Q, looseSignatures = true, isInAndroidSdk = false, - shadowPicker = Picker.class) + shadowPicker = Picker.class, + callNativeMethodsByDefault = true) public class ShadowNativeImageReader { + /** + * The {@link ImageReader} static initializer invokes its own native methods in static + * initializer. This has to be deferred starting in Android V. + */ + @Implementation(minSdk = V.SDK_INT) + protected static void __staticInitializer__() { + // deferred + } + @ReflectorObject private ImageReaderReflector imageReaderReflector; private final ImageReaderNatives natives = new ImageReaderNatives(); @@ -37,7 +48,7 @@ public class ShadowNativeImageReader { imageReaderReflector.setMemberNativeContext(natives.mNativeContext); } - @Implementation(minSdk = T.SDK_INT) + @Implementation(minSdk = T.SDK_INT, maxSdk = U.SDK_INT) protected synchronized void nativeInit( Object weakSelf, int w, @@ -56,17 +67,17 @@ public class ShadowNativeImageReader { imageReaderReflector.setMemberNativeContext(natives.mNativeContext); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected void nativeClose() { natives.nativeClose(); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected void nativeReleaseImage(Image i) { natives.nativeReleaseImage(i); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected Surface nativeGetSurface() { return natives.nativeGetSurface(); } @@ -76,7 +87,7 @@ public class ShadowNativeImageReader { return natives.nativeDetachImage(i); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected void nativeDiscardFreeBuffers() { natives.nativeDiscardFreeBuffers(); } @@ -94,14 +105,14 @@ public class ShadowNativeImageReader { return natives.nativeImageSetup(i); } - @Implementation(minSdk = U.SDK_INT) + @Implementation(minSdk = U.SDK_INT, maxSdk = U.SDK_INT) protected Object nativeImageSetup(Object i) { // Note: reverted to Q-S API return natives.nativeImageSetup((Image) i); } /** We use a class initializer to allow the native code to cache some field offsets. */ - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nativeClassInit() { DefaultNativeRuntimeLoader.injectAndLoad(); ImageReaderNatives.nativeClassInit(); diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeImageReaderSurfaceImage.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeImageReaderSurfaceImage.java index ec0e83188..d6665c18f 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeImageReaderSurfaceImage.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeImageReaderSurfaceImage.java @@ -4,11 +4,13 @@ import static android.os.Build.VERSION_CODES.Q; import static android.os.Build.VERSION_CODES.R; import static android.os.Build.VERSION_CODES.S; +import android.hardware.HardwareBuffer; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; import org.robolectric.annotation.RealObject; import org.robolectric.nativeruntime.ImageReaderSurfaceImageNatives; import org.robolectric.shadows.ShadowImageReader.ShadowSurfaceImage; +import org.robolectric.versioning.AndroidVersions.U; /** Shadow for {@code ImageReader.SurfaceImage} that is backed by native code. */ @Implements( @@ -16,7 +18,8 @@ import org.robolectric.shadows.ShadowImageReader.ShadowSurfaceImage; minSdk = Q, looseSignatures = true, isInAndroidSdk = false, - shadowPicker = ShadowNativeImageReaderSurfaceImage.Picker.class) + shadowPicker = ShadowNativeImageReaderSurfaceImage.Picker.class, + callNativeMethodsByDefault = true) public class ShadowNativeImageReaderSurfaceImage { @RealObject private Object realSurfaceImage; @@ -28,29 +31,34 @@ public class ShadowNativeImageReaderSurfaceImage { realSurfaceImage, (int) numPlanes, (int) readerFormat, /* readerUsage= */ 0); } - @Implementation(minSdk = S) + @Implementation(minSdk = S, maxSdk = U.SDK_INT) protected synchronized /*SurfacePlane[]*/ Object nativeCreatePlanes( /*int*/ Object numPlanes, /*int*/ Object readerFormat, /*long*/ Object readerUsage) { return ImageReaderSurfaceImageNatives.nativeSurfaceImageCreatePlanes( realSurfaceImage, (int) numPlanes, (int) readerFormat, (long) readerUsage); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected synchronized int nativeGetWidth() { return ImageReaderSurfaceImageNatives.nativeSurfaceImageGetWidth(realSurfaceImage); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected synchronized int nativeGetHeight() { return ImageReaderSurfaceImageNatives.nativeSurfaceImageGetHeight(realSurfaceImage); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected synchronized int nativeGetFormat(int readerFormat) { return ImageReaderSurfaceImageNatives.nativeSurfaceImageGetFormat( realSurfaceImage, readerFormat); } + @Implementation + protected synchronized HardwareBuffer nativeGetHardwareBuffer() { + return null; // TODO(hoisie): add an implementation + } + /** Shadow picker for {@code ImageReader.SurfaceImage}. */ public static final class Picker extends GraphicsShadowPicker<Object> { public Picker() { diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeInterpolator.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeInterpolator.java index 21a292c89..3d775ba75 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeInterpolator.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeInterpolator.java @@ -8,40 +8,45 @@ import org.robolectric.annotation.Implements; import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader; import org.robolectric.nativeruntime.InterpolatorNatives; import org.robolectric.shadows.ShadowNativeInterpolator.Picker; +import org.robolectric.versioning.AndroidVersions.U; /** Shadow for {@link Interpolator} that is backed by native code */ -@Implements(value = Interpolator.class, minSdk = O, shadowPicker = Picker.class) +@Implements( + value = Interpolator.class, + minSdk = O, + shadowPicker = Picker.class, + callNativeMethodsByDefault = true) public class ShadowNativeInterpolator { - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static long nativeConstructor(int valueCount, int frameCount) { DefaultNativeRuntimeLoader.injectAndLoad(); return InterpolatorNatives.nativeConstructor(valueCount, frameCount); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nativeDestructor(long nativeInstance) { InterpolatorNatives.nativeDestructor(nativeInstance); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nativeReset(long nativeInstance, int valueCount, int frameCount) { InterpolatorNatives.nativeReset(nativeInstance, valueCount, frameCount); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nativeSetKeyFrame( long nativeInstance, int index, int msec, float[] values, float[] blend) { InterpolatorNatives.nativeSetKeyFrame(nativeInstance, index, msec, values, blend); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nativeSetRepeatMirror( long nativeInstance, float repeatCount, boolean mirror) { InterpolatorNatives.nativeSetRepeatMirror(nativeInstance, repeatCount, mirror); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static int nativeTimeToValues(long nativeInstance, int msec, float[] values) { return InterpolatorNatives.nativeTimeToValues(nativeInstance, msec, values); } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeLightingColorFilter.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeLightingColorFilter.java index 88e4ea5c3..9ddb07337 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeLightingColorFilter.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeLightingColorFilter.java @@ -8,12 +8,17 @@ import org.robolectric.annotation.Implements; import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader; import org.robolectric.nativeruntime.LightingColorFilterNatives; import org.robolectric.shadows.ShadowNativeLightingColorFilter.Picker; +import org.robolectric.versioning.AndroidVersions.U; /** Shadow for {@link LightingColorFilter} that is backed by native code */ -@Implements(value = LightingColorFilter.class, minSdk = O, shadowPicker = Picker.class) +@Implements( + value = LightingColorFilter.class, + minSdk = O, + shadowPicker = Picker.class, + callNativeMethodsByDefault = true) public class ShadowNativeLightingColorFilter { - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static long native_CreateLightingFilter(int mul, int add) { DefaultNativeRuntimeLoader.injectAndLoad(); return LightingColorFilterNatives.native_CreateLightingFilter(mul, add); diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeLineBreaker.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeLineBreaker.java index 7133f83a7..ce0b92f7e 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeLineBreaker.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeLineBreaker.java @@ -14,32 +14,30 @@ import org.robolectric.versioning.AndroidVersions.U; import org.robolectric.versioning.AndroidVersions.V; /** Shadow for {@link LineBreaker} that is backed by native code */ -@Implements(value = LineBreaker.class, minSdk = Q, shadowPicker = Picker.class) +@Implements( + value = LineBreaker.class, + minSdk = Q, + shadowPicker = Picker.class, + callNativeMethodsByDefault = true) public class ShadowNativeLineBreaker { + + @Implementation(minSdk = V.SDK_INT) + protected static void __staticInitializer__() {} + @Implementation(maxSdk = U.SDK_INT) protected static long nInit( int breakStrategy, int hyphenationFrequency, boolean isJustified, int[] indents) { return LineBreakerNatives.nInit(breakStrategy, hyphenationFrequency, isJustified, indents); } - @Implementation(minSdk = V.SDK_INT) - protected static long nInit( - int breakStrategy, - int hyphenationFrequency, - boolean isJustified, - int[] indents, - boolean useBoundsForWidth) { - return nInit(breakStrategy, hyphenationFrequency, isJustified, indents); - } - - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static long nGetReleaseFunc() { // Called first by the static initializer. DefaultNativeRuntimeLoader.injectAndLoad(); return LineBreakerNatives.nGetReleaseFunc(); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static long nComputeLineBreaks( long nativePtr, char[] text, @@ -65,37 +63,37 @@ public class ShadowNativeLineBreaker { } // Result accessors - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static int nGetLineCount(long ptr) { return LineBreakerNatives.nGetLineCount(ptr); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static int nGetLineBreakOffset(long ptr, int idx) { return LineBreakerNatives.nGetLineBreakOffset(ptr, idx); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static float nGetLineWidth(long ptr, int idx) { return LineBreakerNatives.nGetLineWidth(ptr, idx); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static float nGetLineAscent(long ptr, int idx) { return LineBreakerNatives.nGetLineAscent(ptr, idx); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static float nGetLineDescent(long ptr, int idx) { return LineBreakerNatives.nGetLineDescent(ptr, idx); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static int nGetLineFlag(long ptr, int idx) { return LineBreakerNatives.nGetLineFlag(ptr, idx); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static long nGetReleaseResultFunc() { return LineBreakerNatives.nGetReleaseResultFunc(); } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeLinearGradient.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeLinearGradient.java index c3458fcf5..d0e263959 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeLinearGradient.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeLinearGradient.java @@ -7,14 +7,23 @@ import static android.os.Build.VERSION_CODES.Q; import android.graphics.LinearGradient; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; +import org.robolectric.annotation.RealObject; import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader; import org.robolectric.nativeruntime.LinearGradientNatives; import org.robolectric.shadows.ShadowNativeLinearGradient.Picker; +import org.robolectric.versioning.AndroidVersions.U; /** Shadow for {@link LinearGradient} that is backed by native code */ -@Implements(value = LinearGradient.class, minSdk = O, shadowPicker = Picker.class) +@Implements( + value = LinearGradient.class, + minSdk = O, + shadowPicker = Picker.class, + callNativeMethodsByDefault = true) public class ShadowNativeLinearGradient { - @Implementation(minSdk = Q) + + @RealObject LinearGradient realLinearGradient; + + @Implementation(minSdk = Q, maxSdk = U.SDK_INT) protected long nativeCreate( long matrix, float x0, diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeMaskFilter.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeMaskFilter.java index 97b18ac52..cbc1b7009 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeMaskFilter.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeMaskFilter.java @@ -7,12 +7,17 @@ import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; import org.robolectric.nativeruntime.MaskFilterNatives; import org.robolectric.shadows.ShadowNativeMaskFilter.Picker; +import org.robolectric.versioning.AndroidVersions.U; /** Shadow for {@link MaskFilter} that is backed by native code */ -@Implements(value = MaskFilter.class, minSdk = O, shadowPicker = Picker.class) +@Implements( + value = MaskFilter.class, + minSdk = O, + shadowPicker = Picker.class, + callNativeMethodsByDefault = true) public class ShadowNativeMaskFilter { - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nativeDestructor(long nativeFilter) { MaskFilterNatives.nativeDestructor(nativeFilter); } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeMatrix.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeMatrix.java index e840968db..61c4e7db5 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeMatrix.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeMatrix.java @@ -12,39 +12,54 @@ import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader; import org.robolectric.nativeruntime.MatrixNatives; +import org.robolectric.versioning.AndroidVersions.U; +import org.robolectric.versioning.AndroidVersions.V; /** Shadow for {@link Matrix} that is backed by native code */ -@Implements(value = Matrix.class, minSdk = O, isInAndroidSdk = false) +@Implements( + value = Matrix.class, + minSdk = O, + isInAndroidSdk = false, + callNativeMethodsByDefault = true) public class ShadowNativeMatrix extends ShadowMatrix { + /** + * The {@link Matrix} static initializer invokes its own native methods. This has to be deferred + * starting in Android V. + */ + @Implementation(minSdk = V.SDK_INT) + protected static void __staticInitializer__() { + // deferred + } + @Implementation(minSdk = LOLLIPOP, maxSdk = N_MR1) protected static long native_create(long nSrcOrZero) { return nCreate(nSrcOrZero); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static long nCreate(long nSrcOrZero) { DefaultNativeRuntimeLoader.injectAndLoad(); return MatrixNatives.nCreate(nSrcOrZero); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static long nGetNativeFinalizer() { return MatrixNatives.nGetNativeFinalizer(); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static boolean nSetRectToRect(long nObject, RectF src, RectF dst, int stf) { return MatrixNatives.nSetRectToRect(nObject, src, dst, stf); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static boolean nSetPolyToPoly( long nObject, float[] src, int srcIndex, float[] dst, int dstIndex, int pointCount) { return MatrixNatives.nSetPolyToPoly(nObject, src, srcIndex, dst, dstIndex, pointCount); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nMapPoints( long nObject, float[] dst, @@ -56,188 +71,188 @@ public class ShadowNativeMatrix extends ShadowMatrix { MatrixNatives.nMapPoints(nObject, dst, dstIndex, src, srcIndex, ptCount, isPts); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static boolean nMapRect(long nObject, RectF dst, RectF src) { return MatrixNatives.nMapRect(nObject, dst, src); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nGetValues(long nObject, float[] values) { MatrixNatives.nGetValues(nObject, values); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nSetValues(long nObject, float[] values) { MatrixNatives.nSetValues(nObject, values); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static boolean nIsIdentity(long nObject) { return MatrixNatives.nIsIdentity(nObject); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static boolean nIsAffine(long nObject) { return MatrixNatives.nIsAffine(nObject); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static boolean nRectStaysRect(long nObject) { return MatrixNatives.nRectStaysRect(nObject); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nReset(long nObject) { MatrixNatives.nReset(nObject); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nSet(long nObject, long nOther) { MatrixNatives.nSet(nObject, nOther); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nSetTranslate(long nObject, float dx, float dy) { MatrixNatives.nSetTranslate(nObject, dx, dy); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nSetScale(long nObject, float sx, float sy, float px, float py) { MatrixNatives.nSetScale(nObject, sx, sy, px, py); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nSetScale(long nObject, float sx, float sy) { MatrixNatives.nSetScale(nObject, sx, sy); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nSetRotate(long nObject, float degrees, float px, float py) { MatrixNatives.nSetRotate(nObject, degrees, px, py); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nSetRotate(long nObject, float degrees) { MatrixNatives.nSetRotate(nObject, degrees); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nSetSinCos( long nObject, float sinValue, float cosValue, float px, float py) { MatrixNatives.nSetSinCos(nObject, sinValue, cosValue, px, py); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nSetSinCos(long nObject, float sinValue, float cosValue) { MatrixNatives.nSetSinCos(nObject, sinValue, cosValue); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nSetSkew(long nObject, float kx, float ky, float px, float py) { MatrixNatives.nSetSkew(nObject, kx, ky, px, py); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nSetSkew(long nObject, float kx, float ky) { MatrixNatives.nSetSkew(nObject, kx, ky); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nSetConcat(long nObject, long nA, long nB) { MatrixNatives.nSetConcat(nObject, nA, nB); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nPreTranslate(long nObject, float dx, float dy) { MatrixNatives.nPreTranslate(nObject, dx, dy); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nPreScale(long nObject, float sx, float sy, float px, float py) { MatrixNatives.nPreScale(nObject, sx, sy, px, py); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nPreScale(long nObject, float sx, float sy) { MatrixNatives.nPreScale(nObject, sx, sy); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nPreRotate(long nObject, float degrees, float px, float py) { MatrixNatives.nPreRotate(nObject, degrees, px, py); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nPreRotate(long nObject, float degrees) { MatrixNatives.nPreRotate(nObject, degrees); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nPreSkew(long nObject, float kx, float ky, float px, float py) { MatrixNatives.nPreSkew(nObject, kx, ky, px, py); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nPreSkew(long nObject, float kx, float ky) { MatrixNatives.nPreSkew(nObject, kx, ky); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nPreConcat(long nObject, long nOtherMatrix) { MatrixNatives.nPreConcat(nObject, nOtherMatrix); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nPostTranslate(long nObject, float dx, float dy) { MatrixNatives.nPostTranslate(nObject, dx, dy); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nPostScale(long nObject, float sx, float sy, float px, float py) { MatrixNatives.nPostScale(nObject, sx, sy, px, py); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nPostScale(long nObject, float sx, float sy) { MatrixNatives.nPostScale(nObject, sx, sy); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nPostRotate(long nObject, float degrees, float px, float py) { MatrixNatives.nPostRotate(nObject, degrees, px, py); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nPostRotate(long nObject, float degrees) { MatrixNatives.nPostRotate(nObject, degrees); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nPostSkew(long nObject, float kx, float ky, float px, float py) { MatrixNatives.nPostSkew(nObject, kx, ky, px, py); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nPostSkew(long nObject, float kx, float ky) { MatrixNatives.nPostSkew(nObject, kx, ky); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nPostConcat(long nObject, long nOtherMatrix) { MatrixNatives.nPostConcat(nObject, nOtherMatrix); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static boolean nInvert(long nObject, long nInverse) { return MatrixNatives.nInvert(nObject, nInverse); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static float nMapRadius(long nObject, float radius) { return MatrixNatives.nMapRadius(nObject, radius); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static boolean nEquals(long nA, long nB) { return MatrixNatives.nEquals(nA, nB); } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeMeasuredText.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeMeasuredText.java index 321db9ed8..d22cd3834 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeMeasuredText.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeMeasuredText.java @@ -18,31 +18,35 @@ import org.robolectric.versioning.AndroidVersions.U; import org.robolectric.versioning.AndroidVersions.V; /** Shadow for {@link MeasuredText} that is backed by native code */ -@Implements(value = MeasuredText.class, minSdk = Q, shadowPicker = Picker.class) +@Implements( + value = MeasuredText.class, + minSdk = Q, + shadowPicker = Picker.class, + callNativeMethodsByDefault = true) public class ShadowNativeMeasuredText { - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static float nGetWidth( /* Non Zero */ long nativePtr, @IntRange(from = 0) int start, @IntRange(from = 0) int end) { return MeasuredTextNatives.nGetWidth(nativePtr, start, end); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static /* Non Zero */ long nGetReleaseFunc() { DefaultNativeRuntimeLoader.injectAndLoad(); return MeasuredTextNatives.nGetReleaseFunc(); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static int nGetMemoryUsage(/* Non Zero */ long nativePtr) { return MeasuredTextNatives.nGetMemoryUsage(nativePtr); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nGetBounds(long nativePtr, char[] buf, int start, int end, Rect rect) { MeasuredTextNatives.nGetBounds(nativePtr, buf, start, end, rect); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static float nGetCharWidthAt(long nativePtr, int offset) { return MeasuredTextNatives.nGetCharWidthAt(nativePtr, offset); } @@ -51,9 +55,20 @@ public class ShadowNativeMeasuredText { @Implements( value = MeasuredText.Builder.class, minSdk = Q, - shadowPicker = ShadowNativeMeasuredTextBuilder.Picker.class) + shadowPicker = ShadowNativeMeasuredTextBuilder.Picker.class, + callNativeMethodsByDefault = true) public static class ShadowNativeMeasuredTextBuilder { - @Implementation + + /** + * The {@link MeasuredText.Builder} static initializer invokes its own native methods. This has + * to be deferred starting in Android V. + */ + @Implementation(minSdk = V.SDK_INT) + protected static void __staticInitializer__() { + // deferred + } + + @Implementation(maxSdk = U.SDK_INT) protected static /* Non Zero */ long nInitBuilder() { return MeasuredTextBuilderNatives.nInitBuilder(); } @@ -80,20 +95,7 @@ public class ShadowNativeMeasuredText { MeasuredTextBuilderNatives.nAddStyleRun(nativeBuilderPtr, paintPtr, start, end, isRtl); } - @Implementation(minSdk = V.SDK_INT) - protected static void nAddStyleRun( - /* Non Zero */ long nativeBuilderPtr, - /* Non Zero */ long paintPtr, - int lineBreakStyle, - int lineBreakWordStyle, - /* Ignored */ boolean hyphenation, - int start, - int end, - boolean isRtl) { - MeasuredTextBuilderNatives.nAddStyleRun(nativeBuilderPtr, paintPtr, start, end, isRtl); - } - - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nAddReplacementRun( /* Non Zero */ long nativeBuilderPtr, /* Non Zero */ long paintPtr, @@ -126,21 +128,7 @@ public class ShadowNativeMeasuredText { nativeBuilderPtr, hintMtPtr, text, computeHyphenation, computeLayout); } - @Implementation(minSdk = V.SDK_INT) - protected static long nBuildMeasuredText( - /* Non Zero */ long nativeBuilderPtr, - long hintMtPtr, - char[] text, - boolean computeHyphenation, - boolean computeLayout, - boolean computeBounds, - /** ignored */ - boolean fastHyphenationMode) { - return MeasuredTextBuilderNatives.nBuildMeasuredText( - nativeBuilderPtr, hintMtPtr, text, computeHyphenation, computeLayout); - } - - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nFreeBuilder(/* Non Zero */ long nativeBuilderPtr) { MeasuredTextBuilderNatives.nFreeBuilder(nativeBuilderPtr); } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeNativeInterpolatorFactory.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeNativeInterpolatorFactory.java index 21c80e5c1..1eb917ea6 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeNativeInterpolatorFactory.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeNativeInterpolatorFactory.java @@ -8,70 +8,72 @@ import org.robolectric.annotation.Implements; import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader; import org.robolectric.nativeruntime.NativeInterpolatorFactoryNatives; import org.robolectric.shadows.ShadowNativeNativeInterpolatorFactory.Picker; +import org.robolectric.versioning.AndroidVersions.U; /** Shadow for {@link NativeInterpolatorFactory} that is backed by native code */ @Implements( value = NativeInterpolatorFactory.class, minSdk = R, shadowPicker = Picker.class, - isInAndroidSdk = false) + isInAndroidSdk = false, + callNativeMethodsByDefault = true) public class ShadowNativeNativeInterpolatorFactory { static { DefaultNativeRuntimeLoader.injectAndLoad(); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static long createAccelerateDecelerateInterpolator() { return NativeInterpolatorFactoryNatives.createAccelerateDecelerateInterpolator(); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static long createAccelerateInterpolator(float factor) { return NativeInterpolatorFactoryNatives.createAccelerateInterpolator(factor); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static long createAnticipateInterpolator(float tension) { return NativeInterpolatorFactoryNatives.createAnticipateInterpolator(tension); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static long createAnticipateOvershootInterpolator(float tension) { return NativeInterpolatorFactoryNatives.createAnticipateOvershootInterpolator(tension); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static long createBounceInterpolator() { return NativeInterpolatorFactoryNatives.createBounceInterpolator(); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static long createCycleInterpolator(float cycles) { return NativeInterpolatorFactoryNatives.createCycleInterpolator(cycles); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static long createDecelerateInterpolator(float factor) { return NativeInterpolatorFactoryNatives.createDecelerateInterpolator(factor); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static long createLinearInterpolator() { return NativeInterpolatorFactoryNatives.createLinearInterpolator(); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static long createOvershootInterpolator(float tension) { return NativeInterpolatorFactoryNatives.createOvershootInterpolator(tension); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static long createPathInterpolator(float[] x, float[] y) { return NativeInterpolatorFactoryNatives.createPathInterpolator(x, y); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static long createLutInterpolator(float[] values) { return NativeInterpolatorFactoryNatives.createLutInterpolator(values); } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeNinePatch.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeNinePatch.java index 11e5ba867..5b53d454b 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeNinePatch.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeNinePatch.java @@ -10,33 +10,35 @@ import org.robolectric.annotation.Implements; import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader; import org.robolectric.nativeruntime.NinePatchNatives; import org.robolectric.shadows.ShadowNativeNinePatch.Picker; +import org.robolectric.versioning.AndroidVersions.U; /** Shadow for {@link NinePatch} that is backed by native code */ @Implements( value = NinePatch.class, minSdk = O, shadowPicker = Picker.class, - isInAndroidSdk = false) + isInAndroidSdk = false, + callNativeMethodsByDefault = true) public class ShadowNativeNinePatch { - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static boolean isNinePatchChunk(byte[] chunk) { DefaultNativeRuntimeLoader.injectAndLoad(); return NinePatchNatives.isNinePatchChunk(chunk); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static long validateNinePatchChunk(byte[] chunk) { DefaultNativeRuntimeLoader.injectAndLoad(); return NinePatchNatives.validateNinePatchChunk(chunk); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nativeFinalize(long chunk) { NinePatchNatives.nativeFinalize(chunk); } - @Implementation(minSdk = Q) + @Implementation(minSdk = Q, maxSdk = U.SDK_INT) protected static long nativeGetTransparentRegion(long bitmapHandle, long chunk, Rect location) { return NinePatchNatives.nativeGetTransparentRegion(bitmapHandle, chunk, location); } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePaint.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePaint.java index 34cb9ebd9..7d2dda662 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePaint.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePaint.java @@ -6,20 +6,18 @@ import static android.os.Build.VERSION_CODES.P; import static android.os.Build.VERSION_CODES.Q; import static android.os.Build.VERSION_CODES.TIRAMISU; +import android.annotation.ColorInt; +import android.annotation.ColorLong; import android.graphics.Paint; import android.graphics.Paint.FontMetrics; import android.graphics.Paint.FontMetricsInt; import android.graphics.Rect; -import android.graphics.RectF; -import androidx.annotation.ColorInt; -import androidx.annotation.ColorLong; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader; import org.robolectric.nativeruntime.PaintNatives; import org.robolectric.shadows.ShadowNativePaint.Picker; import org.robolectric.versioning.AndroidVersions.U; -import org.robolectric.versioning.AndroidVersions.V; /** Shadow for {@link Paint} that is backed by native code */ @Implements( @@ -27,18 +25,19 @@ import org.robolectric.versioning.AndroidVersions.V; value = Paint.class, looseSignatures = true, shadowPicker = Picker.class, - isInAndroidSdk = false) + isInAndroidSdk = false, + callNativeMethodsByDefault = true) public class ShadowNativePaint { // nGetTextRunCursor methods are non-static private PaintNatives paintNatives = new PaintNatives(); - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static long nGetNativeFinalizer() { return PaintNatives.nGetNativeFinalizer(); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static long nInit() { DefaultNativeRuntimeLoader.injectAndLoad(); // This native code calls Typeface::resolveDefault, which requires Typeface clinit to have run. @@ -57,13 +56,13 @@ public class ShadowNativePaint { PaintNatives.nSetEndHyphenEdit(paintPtr, hyphen); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static long nInitWithPaint(long paint) { DefaultNativeRuntimeLoader.injectAndLoad(); return PaintNatives.nInitWithPaint(paint); } - @Implementation(minSdk = P) + @Implementation(minSdk = P, maxSdk = U.SDK_INT) protected static int nBreakText( long nObject, char[] text, @@ -75,7 +74,7 @@ public class ShadowNativePaint { return PaintNatives.nBreakText(nObject, text, index, count, maxWidth, bidiFlags, measuredWidth); } - @Implementation(minSdk = P) + @Implementation(minSdk = P, maxSdk = U.SDK_INT) protected static int nBreakText( long nObject, String text, @@ -114,7 +113,7 @@ public class ShadowNativePaint { nObject, typefacePtr, text, measureForwards, maxWidth, bidiFlags, measuredWidth); } - @Implementation(minSdk = P) + @Implementation(minSdk = P, maxSdk = U.SDK_INT) protected static float nGetTextAdvances( long paintPtr, char[] text, @@ -137,7 +136,7 @@ public class ShadowNativePaint { advancesIndex); } - @Implementation(minSdk = P) + @Implementation(minSdk = P, maxSdk = U.SDK_INT) protected static float nGetTextAdvances( long paintPtr, String text, @@ -202,7 +201,7 @@ public class ShadowNativePaint { advancesIndex); } - @Implementation(minSdk = P) + @Implementation(minSdk = P, maxSdk = U.SDK_INT) protected int nGetTextRunCursor( long paintPtr, char[] text, @@ -215,7 +214,7 @@ public class ShadowNativePaint { paintPtr, text, contextStart, contextLength, dir, offset, cursorOpt); } - @Implementation(minSdk = P) + @Implementation(minSdk = P, maxSdk = U.SDK_INT) protected int nGetTextRunCursor( long paintPtr, String text, @@ -256,7 +255,7 @@ public class ShadowNativePaint { paintPtr, typefacePtr, text, contextStart, contextEnd, dir, offset, cursorOpt); } - @Implementation(minSdk = P) + @Implementation(minSdk = P, maxSdk = U.SDK_INT) protected static void nGetTextPath( long paintPtr, int bidiFlags, @@ -269,7 +268,7 @@ public class ShadowNativePaint { PaintNatives.nGetTextPath(paintPtr, bidiFlags, text, index, count, x, y, path); } - @Implementation(minSdk = P) + @Implementation(minSdk = P, maxSdk = U.SDK_INT) protected static void nGetTextPath( long paintPtr, int bidiFlags, String text, int start, int end, float x, float y, long path) { PaintNatives.nGetTextPath(paintPtr, bidiFlags, text, start, end, x, y, path); @@ -303,7 +302,7 @@ public class ShadowNativePaint { PaintNatives.nGetTextPath(paintPtr, typefacePtr, bidiFlags, text, start, end, x, y, path); } - @Implementation(minSdk = P) + @Implementation(minSdk = P, maxSdk = U.SDK_INT) protected static void nGetStringBounds( long nativePaint, String text, int start, int end, int bidiFlags, Rect bounds) { PaintNatives.nGetStringBounds(nativePaint, text, start, end, bidiFlags, bounds); @@ -331,7 +330,7 @@ public class ShadowNativePaint { return PaintNatives.nGetAlpha(paintPtr); } - @Implementation(minSdk = P) + @Implementation(minSdk = P, maxSdk = U.SDK_INT) protected static void nGetCharArrayBounds( long nativePaint, char[] text, int index, int count, int bidiFlags, Rect bounds) { PaintNatives.nGetCharArrayBounds(nativePaint, text, index, count, bidiFlags, bounds); @@ -350,7 +349,7 @@ public class ShadowNativePaint { nativePaint, typefacePtr, text, index, count, bidiFlags, bounds); } - @Implementation(minSdk = P) + @Implementation(minSdk = P, maxSdk = U.SDK_INT) protected static boolean nHasGlyph(long paintPtr, int bidiFlags, String string) { return PaintNatives.nHasGlyph(paintPtr, bidiFlags, string); } @@ -361,7 +360,7 @@ public class ShadowNativePaint { return PaintNatives.nHasGlyph(paintPtr, typefacePtr, bidiFlags, string); } - @Implementation(minSdk = P) + @Implementation(minSdk = P, maxSdk = U.SDK_INT) protected static float nGetRunAdvance( long paintPtr, char[] text, @@ -405,7 +404,7 @@ public class ShadowNativePaint { paintPtr, typefacePtr, text, start, end, contextStart, contextEnd, isRtl, advance); } - @Implementation(minSdk = P) + @Implementation(minSdk = P, maxSdk = U.SDK_INT) protected static int nGetOffsetForAdvance( long paintPtr, char[] text, @@ -419,22 +418,16 @@ public class ShadowNativePaint { paintPtr, text, start, end, contextStart, contextEnd, isRtl, advance); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static int nSetTextLocales(long paintPtr, String locales) { return PaintNatives.nSetTextLocales(paintPtr, locales); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nSetFontFeatureSettings(long paintPtr, String settings) { PaintNatives.nSetFontFeatureSettings(paintPtr, settings); } - @Implementation(minSdk = V.SDK_INT) - protected static float nGetFontMetrics( - long paintPtr, FontMetrics metrics, /* Ignored */ boolean useLocale) { - return PaintNatives.nGetFontMetrics(paintPtr, metrics); - } - @Implementation(minSdk = P, maxSdk = U.SDK_INT) protected static float nGetFontMetrics(long paintPtr, FontMetrics metrics) { return PaintNatives.nGetFontMetrics(paintPtr, metrics); @@ -445,12 +438,6 @@ public class ShadowNativePaint { return PaintNatives.nGetFontMetrics(paintPtr, typefacePtr, metrics); } - @Implementation(minSdk = V.SDK_INT) - protected static int nGetFontMetricsInt( - long paintPtr, FontMetricsInt fmi, /* Ignored */ boolean useLocale) { - return PaintNatives.nGetFontMetricsInt(paintPtr, fmi); - } - @Implementation(minSdk = P, maxSdk = U.SDK_INT) protected static int nGetFontMetricsInt(long paintPtr, FontMetricsInt fmi) { return PaintNatives.nGetFontMetricsInt(paintPtr, fmi); @@ -461,77 +448,77 @@ public class ShadowNativePaint { return PaintNatives.nGetFontMetricsInt(paintPtr, typefacePtr, fmi); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nReset(long paintPtr) { PaintNatives.nReset(paintPtr); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nSet(long paintPtrDest, long paintPtrSrc) { PaintNatives.nSet(paintPtrDest, paintPtrSrc); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static int nGetStyle(long paintPtr) { return PaintNatives.nGetStyle(paintPtr); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nSetStyle(long paintPtr, int style) { PaintNatives.nSetStyle(paintPtr, style); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static int nGetStrokeCap(long paintPtr) { return PaintNatives.nGetStrokeCap(paintPtr); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nSetStrokeCap(long paintPtr, int cap) { PaintNatives.nSetStrokeCap(paintPtr, cap); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static int nGetStrokeJoin(long paintPtr) { return PaintNatives.nGetStrokeJoin(paintPtr); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nSetStrokeJoin(long paintPtr, int join) { PaintNatives.nSetStrokeJoin(paintPtr, join); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static boolean nGetFillPath(long paintPtr, long src, long dst) { return PaintNatives.nGetFillPath(paintPtr, src, dst); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static long nSetShader(long paintPtr, long shader) { return PaintNatives.nSetShader(paintPtr, shader); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static long nSetColorFilter(long paintPtr, long filter) { return PaintNatives.nSetColorFilter(paintPtr, filter); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nSetXfermode(long paintPtr, int xfermode) { PaintNatives.nSetXfermode(paintPtr, xfermode); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static long nSetPathEffect(long paintPtr, long effect) { return PaintNatives.nSetPathEffect(paintPtr, effect); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static long nSetMaskFilter(long paintPtr, long maskfilter) { return PaintNatives.nSetMaskFilter(paintPtr, maskfilter); } - @Implementation(minSdk = P) + @Implementation(minSdk = P, maxSdk = U.SDK_INT) protected static void nSetTypeface(long paintPtr, long typeface) { PaintNatives.nSetTypeface(paintPtr, typeface); } @@ -542,23 +529,23 @@ public class ShadowNativePaint { return paintPtr; } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static int nGetTextAlign(long paintPtr) { return PaintNatives.nGetTextAlign(paintPtr); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nSetTextAlign(long paintPtr, int align) { PaintNatives.nSetTextAlign(paintPtr, align); } - @Implementation(minSdk = P) + @Implementation(minSdk = P, maxSdk = U.SDK_INT) protected static void nSetTextLocalesByMinikinLocaleListId( long paintPtr, int mMinikinLocaleListId) { PaintNatives.nSetTextLocalesByMinikinLocaleListId(paintPtr, mMinikinLocaleListId); } - @Implementation(minSdk = Q) + @Implementation(minSdk = Q, maxSdk = U.SDK_INT) protected static void nSetShadowLayer( long paintPtr, float radius, @@ -575,142 +562,142 @@ public class ShadowNativePaint { PaintNatives.nSetShadowLayer(paintPtr, radius, dx, dy, color); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static boolean nHasShadowLayer(long paintPtr) { return PaintNatives.nHasShadowLayer(paintPtr); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static float nGetLetterSpacing(long paintPtr) { return PaintNatives.nGetLetterSpacing(paintPtr); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nSetLetterSpacing(long paintPtr, float letterSpacing) { PaintNatives.nSetLetterSpacing(paintPtr, letterSpacing); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static float nGetWordSpacing(long paintPtr) { return PaintNatives.nGetWordSpacing(paintPtr); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nSetWordSpacing(long paintPtr, float wordSpacing) { PaintNatives.nSetWordSpacing(paintPtr, wordSpacing); } - @Implementation(minSdk = Q) + @Implementation(minSdk = Q, maxSdk = U.SDK_INT) protected static int nGetStartHyphenEdit(long paintPtr) { return PaintNatives.nGetStartHyphenEdit(paintPtr); } - @Implementation(minSdk = Q) + @Implementation(minSdk = Q, maxSdk = U.SDK_INT) protected static int nGetEndHyphenEdit(long paintPtr) { return PaintNatives.nGetEndHyphenEdit(paintPtr); } - @Implementation(minSdk = Q) + @Implementation(minSdk = Q, maxSdk = U.SDK_INT) protected static void nSetStartHyphenEdit(long paintPtr, int hyphen) { PaintNatives.nSetStartHyphenEdit(paintPtr, hyphen); } - @Implementation(minSdk = Q) + @Implementation(minSdk = Q, maxSdk = U.SDK_INT) protected static void nSetEndHyphenEdit(long paintPtr, int hyphen) { PaintNatives.nSetEndHyphenEdit(paintPtr, hyphen); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nSetStrokeMiter(long paintPtr, float miter) { PaintNatives.nSetStrokeMiter(paintPtr, miter); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static float nGetStrokeMiter(long paintPtr) { return PaintNatives.nGetStrokeMiter(paintPtr); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nSetStrokeWidth(long paintPtr, float width) { PaintNatives.nSetStrokeWidth(paintPtr, width); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static float nGetStrokeWidth(long paintPtr) { return PaintNatives.nGetStrokeWidth(paintPtr); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nSetAlpha(long paintPtr, int a) { PaintNatives.nSetAlpha(paintPtr, a); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nSetDither(long paintPtr, boolean dither) { PaintNatives.nSetDither(paintPtr, dither); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static int nGetFlags(long paintPtr) { return PaintNatives.nGetFlags(paintPtr); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nSetFlags(long paintPtr, int flags) { PaintNatives.nSetFlags(paintPtr, flags); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static int nGetHinting(long paintPtr) { return PaintNatives.nGetHinting(paintPtr); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nSetHinting(long paintPtr, int mode) { PaintNatives.nSetHinting(paintPtr, mode); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nSetAntiAlias(long paintPtr, boolean aa) { PaintNatives.nSetAntiAlias(paintPtr, aa); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nSetLinearText(long paintPtr, boolean linearText) { PaintNatives.nSetLinearText(paintPtr, linearText); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nSetSubpixelText(long paintPtr, boolean subpixelText) { PaintNatives.nSetSubpixelText(paintPtr, subpixelText); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nSetUnderlineText(long paintPtr, boolean underlineText) { PaintNatives.nSetUnderlineText(paintPtr, underlineText); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nSetFakeBoldText(long paintPtr, boolean fakeBoldText) { PaintNatives.nSetFakeBoldText(paintPtr, fakeBoldText); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nSetFilterBitmap(long paintPtr, boolean filter) { PaintNatives.nSetFilterBitmap(paintPtr, filter); } - @Implementation(minSdk = Q) + @Implementation(minSdk = Q, maxSdk = U.SDK_INT) protected static void nSetColor(long paintPtr, long colorSpaceHandle, @ColorLong long color) { PaintNatives.nSetColor(paintPtr, colorSpaceHandle, color); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nSetColor(long paintPtr, @ColorInt int color) { PaintNatives.nSetColor(paintPtr, color); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nSetStrikeThruText(long paintPtr, boolean strikeThruText) { PaintNatives.nSetStrikeThruText(paintPtr, strikeThruText); } @@ -720,52 +707,42 @@ public class ShadowNativePaint { return PaintNatives.nIsElegantTextHeight(paintPtr); } - @Implementation(minSdk = V.SDK_INT) - protected static int nGetElegantTextHeight(long paintPtr) { - return PaintNatives.nGetElegantTextHeight(paintPtr); - } - // Note: the following three values must be equal to the ones in the JNI file: Paint.cpp private static final int ELEGANT_TEXT_HEIGHT_ENABLED = 0; private static final int ELEGANT_TEXT_HEIGHT_DISABLED = 1; @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nSetElegantTextHeight(long paintPtr, boolean elegant) { - nSetElegantTextHeight( + PaintNatives.nSetElegantTextHeight( paintPtr, elegant ? ELEGANT_TEXT_HEIGHT_ENABLED : ELEGANT_TEXT_HEIGHT_DISABLED); } - @Implementation(minSdk = V.SDK_INT) - protected static void nSetElegantTextHeight(long paintPtr, int value) { - PaintNatives.nSetElegantTextHeight(paintPtr, value); - } - - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static float nGetTextSize(long paintPtr) { return PaintNatives.nGetTextSize(paintPtr); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static float nGetTextScaleX(long paintPtr) { return PaintNatives.nGetTextScaleX(paintPtr); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nSetTextScaleX(long paintPtr, float scaleX) { PaintNatives.nSetTextScaleX(paintPtr, scaleX); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static float nGetTextSkewX(long paintPtr) { return PaintNatives.nGetTextSkewX(paintPtr); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nSetTextSkewX(long paintPtr, float skewX) { PaintNatives.nSetTextSkewX(paintPtr, skewX); } - @Implementation(minSdk = P) + @Implementation(minSdk = P, maxSdk = U.SDK_INT) protected static float nAscent(long paintPtr) { return PaintNatives.nAscent(paintPtr); } @@ -775,7 +752,7 @@ public class ShadowNativePaint { return PaintNatives.nAscent(paintPtr, typefacePtr); } - @Implementation(minSdk = P) + @Implementation(minSdk = P, maxSdk = U.SDK_INT) protected static float nDescent(long paintPtr) { return PaintNatives.nDescent(paintPtr); } @@ -785,37 +762,57 @@ public class ShadowNativePaint { return PaintNatives.nDescent(paintPtr, typefacePtr); } - @Implementation(minSdk = P) + @Implementation(minSdk = P, maxSdk = U.SDK_INT) protected static float nGetUnderlinePosition(long paintPtr) { return PaintNatives.nGetUnderlinePosition(paintPtr); } - @Implementation(minSdk = P) + @Implementation(minSdk = O_MR1, maxSdk = O_MR1) + protected static float nGetUnderlinePosition(long paintPtr, long typefacePtr) { + return nGetUnderlinePosition(paintPtr); + } + + @Implementation(minSdk = P, maxSdk = U.SDK_INT) protected static float nGetUnderlineThickness(long paintPtr) { return PaintNatives.nGetUnderlineThickness(paintPtr); } - @Implementation(minSdk = P) + @Implementation(minSdk = O_MR1, maxSdk = O_MR1) + protected static float nGetUnderlineThickness(long paintPtr, long typefacePtr) { + return nGetUnderlineThickness(paintPtr); + } + + @Implementation(minSdk = P, maxSdk = U.SDK_INT) protected static float nGetStrikeThruPosition(long paintPtr) { return PaintNatives.nGetStrikeThruPosition(paintPtr); } - @Implementation(minSdk = P) + @Implementation(minSdk = O_MR1, maxSdk = O_MR1) + protected static float nGetStrikeThruPosition(long paintPtr, long typefacePtr) { + return nGetStrikeThruPosition(paintPtr); + } + + @Implementation(minSdk = P, maxSdk = U.SDK_INT) protected static float nGetStrikeThruThickness(long paintPtr) { return PaintNatives.nGetStrikeThruThickness(paintPtr); } - @Implementation(minSdk = O) + @Implementation(minSdk = O_MR1, maxSdk = O_MR1) + protected static float nGetStrikeThruThickness(long paintPtr, long typefacePtr) { + return nGetStrikeThruThickness(paintPtr); + } + + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nSetTextSize(long paintPtr, float textSize) { PaintNatives.nSetTextSize(paintPtr, textSize); } - @Implementation(minSdk = P) + @Implementation(minSdk = P, maxSdk = U.SDK_INT) protected static boolean nEqualsForTextMeasurement(long leftPaintPtr, long rightPaintPtr) { return PaintNatives.nEqualsForTextMeasurement(leftPaintPtr, rightPaintPtr); } - @Implementation(minSdk = TIRAMISU) + @Implementation(minSdk = TIRAMISU, maxSdk = U.SDK_INT) protected static void nGetFontMetricsIntForText( long paintPtr, char[] text, @@ -829,7 +826,7 @@ public class ShadowNativePaint { paintPtr, text, start, count, ctxStart, ctxCount, isRtl, outMetrics); } - @Implementation(minSdk = TIRAMISU) + @Implementation(minSdk = TIRAMISU, maxSdk = U.SDK_INT) protected static void nGetFontMetricsIntForText( long paintPtr, String text, @@ -868,32 +865,9 @@ public class ShadowNativePaint { advancesIndex); } - /** Requires loose signatures because of RunInfo parameter */ - @Implementation(minSdk = V.SDK_INT) - protected static float nGetRunCharacterAdvance( - Object /* long */ paintPtr, - Object /* char[] */ text, - Object /* int */ start, - Object /* int */ end, - Object /* int */ contextStart, - Object /* int */ contextEnd, - Object /* boolean */ isRtl, - Object /* int */ offset, - Object /* float[] */ advances, - Object /* int */ advancesIndex, - Object /* RectF */ drawingBounds, - Object /* RunInfo */ runInfo) { - return nGetRunCharacterAdvance( - (long) paintPtr, - (char[]) text, - (int) start, - (int) end, - (int) contextStart, - (int) contextEnd, - (boolean) isRtl, - (int) offset, - (float[]) advances, - (int) advancesIndex); + @Implementation(minSdk = O, maxSdk = O_MR1) + protected static void nSetTextLocalesByMinikinLangListId(long paintPtr, int mMinikinLangListId) { + // no-op } /** Shadow picker for {@link Paint}. */ diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePath.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePath.java index c162df5a5..a5c9d2f8e 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePath.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePath.java @@ -10,89 +10,103 @@ import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader; import org.robolectric.nativeruntime.PathNatives; +import org.robolectric.versioning.AndroidVersions.U; +import org.robolectric.versioning.AndroidVersions.V; /** Shadow for {@link Path} that is backed by native code */ -@Implements(value = Path.class, minSdk = O, isInAndroidSdk = false) +@Implements( + value = Path.class, + minSdk = O, + isInAndroidSdk = false, + callNativeMethodsByDefault = true) public class ShadowNativePath extends ShadowPath { + /** + * The {@link Path} static initializer invokes its own native methods. This has to be deferred + * starting in Android V. + */ + @Implementation(minSdk = V.SDK_INT) + protected static void __staticInitializer__() { + // deferred + } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static long nInit() { DefaultNativeRuntimeLoader.injectAndLoad(); return PathNatives.nInit(); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static long nInit(long nPath) { // Required for pre-P. DefaultNativeRuntimeLoader.injectAndLoad(); return PathNatives.nInit(nPath); } - @Implementation(minSdk = P) + @Implementation(minSdk = P, maxSdk = U.SDK_INT) protected static long nGetFinalizer() { // Required for pre-P. DefaultNativeRuntimeLoader.injectAndLoad(); return PathNatives.nGetFinalizer(); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nSet(long nativeDst, long nSrc) { PathNatives.nSet(nativeDst, nSrc); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nComputeBounds(long nPath, RectF bounds) { PathNatives.nComputeBounds(nPath, bounds); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nIncReserve(long nPath, int extraPtCount) { PathNatives.nIncReserve(nPath, extraPtCount); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nMoveTo(long nPath, float x, float y) { PathNatives.nMoveTo(nPath, x, y); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nRMoveTo(long nPath, float dx, float dy) { PathNatives.nRMoveTo(nPath, dx, dy); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nLineTo(long nPath, float x, float y) { PathNatives.nLineTo(nPath, x, y); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nRLineTo(long nPath, float dx, float dy) { PathNatives.nRLineTo(nPath, dx, dy); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nQuadTo(long nPath, float x1, float y1, float x2, float y2) { PathNatives.nQuadTo(nPath, x1, y1, x2, y2); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nRQuadTo(long nPath, float dx1, float dy1, float dx2, float dy2) { PathNatives.nRQuadTo(nPath, dx1, dy1, dx2, dy2); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nCubicTo( long nPath, float x1, float y1, float x2, float y2, float x3, float y3) { PathNatives.nCubicTo(nPath, x1, y1, x2, y2, x3, y3); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nRCubicTo( long nPath, float x1, float y1, float x2, float y2, float x3, float y3) { PathNatives.nRCubicTo(nPath, x1, y1, x2, y2, x3, y3); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nArcTo( long nPath, float left, @@ -105,29 +119,29 @@ public class ShadowNativePath extends ShadowPath { PathNatives.nArcTo(nPath, left, top, right, bottom, startAngle, sweepAngle, forceMoveTo); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nClose(long nPath) { PathNatives.nClose(nPath); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nAddRect( long nPath, float left, float top, float right, float bottom, int dir) { PathNatives.nAddRect(nPath, left, top, right, bottom, dir); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nAddOval( long nPath, float left, float top, float right, float bottom, int dir) { PathNatives.nAddOval(nPath, left, top, right, bottom, dir); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nAddCircle(long nPath, float x, float y, float radius, int dir) { PathNatives.nAddCircle(nPath, x, y, radius, dir); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nAddArc( long nPath, float left, @@ -139,98 +153,103 @@ public class ShadowNativePath extends ShadowPath { PathNatives.nAddArc(nPath, left, top, right, bottom, startAngle, sweepAngle); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nAddRoundRect( long nPath, float left, float top, float right, float bottom, float rx, float ry, int dir) { PathNatives.nAddRoundRect(nPath, left, top, right, bottom, rx, ry, dir); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nAddRoundRect( long nPath, float left, float top, float right, float bottom, float[] radii, int dir) { PathNatives.nAddRoundRect(nPath, left, top, right, bottom, radii, dir); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nAddPath(long nPath, long src, float dx, float dy) { PathNatives.nAddPath(nPath, src, dx, dy); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nAddPath(long nPath, long src) { PathNatives.nAddPath(nPath, src); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nAddPath(long nPath, long src, long matrix) { PathNatives.nAddPath(nPath, src, matrix); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nOffset(long nPath, float dx, float dy) { PathNatives.nOffset(nPath, dx, dy); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nSetLastPoint(long nPath, float dx, float dy) { PathNatives.nSetLastPoint(nPath, dx, dy); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nTransform(long nPath, long matrix, long dstPath) { PathNatives.nTransform(nPath, matrix, dstPath); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nTransform(long nPath, long matrix) { PathNatives.nTransform(nPath, matrix); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static boolean nOp(long path1, long path2, int op, long result) { return PathNatives.nOp(path1, path2, op, result); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static boolean nIsRect(long nPath, RectF rect) { return PathNatives.nIsRect(nPath, rect); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nReset(long nPath) { PathNatives.nReset(nPath); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nRewind(long nPath) { PathNatives.nRewind(nPath); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static boolean nIsEmpty(long nPath) { return PathNatives.nIsEmpty(nPath); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static boolean nIsConvex(long nPath) { return PathNatives.nIsConvex(nPath); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static int nGetFillType(long nPath) { return PathNatives.nGetFillType(nPath); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nSetFillType(long nPath, int ft) { PathNatives.nSetFillType(nPath, ft); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static float[] nApproximate(long nPath, float error) { return PathNatives.nApproximate(nPath, error); } + @Implementation(minSdk = U.SDK_INT, maxSdk = U.SDK_INT) + protected static int nGetGenerationID(long nativePath) { + return 0; + } + @Override public List<Point> getPoints() { throw new UnsupportedOperationException("Legacy ShadowPath description APIs are not supported"); diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePathDashPathEffect.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePathDashPathEffect.java index b72b0b231..f04dacfb9 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePathDashPathEffect.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePathDashPathEffect.java @@ -8,12 +8,17 @@ import org.robolectric.annotation.Implements; import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader; import org.robolectric.nativeruntime.PathDashPathEffectNatives; import org.robolectric.shadows.ShadowNativePathDashPathEffect.Picker; +import org.robolectric.versioning.AndroidVersions.U; /** Shadow for {@link PathDashPathEffect} that is backed by native code */ -@Implements(value = PathDashPathEffect.class, minSdk = O, shadowPicker = Picker.class) +@Implements( + value = PathDashPathEffect.class, + minSdk = O, + shadowPicker = Picker.class, + callNativeMethodsByDefault = true) public class ShadowNativePathDashPathEffect { - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static long nativeCreate(long nativePath, float advance, float phase, int nativeStyle) { DefaultNativeRuntimeLoader.injectAndLoad(); return PathDashPathEffectNatives.nativeCreate(nativePath, advance, phase, nativeStyle); diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePathEffect.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePathEffect.java index 440eb2642..bcdccae99 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePathEffect.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePathEffect.java @@ -7,12 +7,17 @@ import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; import org.robolectric.nativeruntime.PathEffectNatives; import org.robolectric.shadows.ShadowNativePathEffect.Picker; +import org.robolectric.versioning.AndroidVersions.U; /** Shadow for {@link PathEffect} that is backed by native code */ -@Implements(value = PathEffect.class, minSdk = O, shadowPicker = Picker.class) +@Implements( + value = PathEffect.class, + minSdk = O, + shadowPicker = Picker.class, + callNativeMethodsByDefault = true) public class ShadowNativePathEffect { - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nativeDestructor(long nativePatheffect) { PathEffectNatives.nativeDestructor(nativePatheffect); } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePathMeasure.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePathMeasure.java index fd450e912..34540cc97 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePathMeasure.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePathMeasure.java @@ -8,61 +8,63 @@ import org.robolectric.annotation.Implements; import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader; import org.robolectric.nativeruntime.PathMeasureNatives; import org.robolectric.shadows.ShadowNativePathMeasure.Picker; +import org.robolectric.versioning.AndroidVersions.U; /** Shadow for {@link PathMeasure} that is backed by native code */ @Implements( value = PathMeasure.class, minSdk = O, shadowPicker = Picker.class, - isInAndroidSdk = false) + isInAndroidSdk = false, + callNativeMethodsByDefault = true) public class ShadowNativePathMeasure { - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static long native_create(long nativePath, boolean forceClosed) { DefaultNativeRuntimeLoader.injectAndLoad(); return PathMeasureNatives.native_create(nativePath, forceClosed); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void native_setPath(long nativeInstance, long nativePath, boolean forceClosed) { PathMeasureNatives.native_setPath(nativeInstance, nativePath, forceClosed); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static float native_getLength(long nativeInstance) { return PathMeasureNatives.native_getLength(nativeInstance); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static boolean native_getPosTan( long nativeInstance, float distance, float[] pos, float[] tan) { return PathMeasureNatives.native_getPosTan(nativeInstance, distance, pos, tan); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static boolean native_getMatrix( long nativeInstance, float distance, long nativeMatrix, int flags) { return PathMeasureNatives.native_getMatrix(nativeInstance, distance, nativeMatrix, flags); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static boolean native_getSegment( long nativeInstance, float startD, float stopD, long nativePath, boolean startWithMoveTo) { return PathMeasureNatives.native_getSegment( nativeInstance, startD, stopD, nativePath, startWithMoveTo); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static boolean native_isClosed(long nativeInstance) { return PathMeasureNatives.native_isClosed(nativeInstance); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static boolean native_nextContour(long nativeInstance) { return PathMeasureNatives.native_nextContour(nativeInstance); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void native_destroy(long nativeInstance) { PathMeasureNatives.native_destroy(nativeInstance); } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePathParser.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePathParser.java index cf83491ad..1b6ccd549 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePathParser.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePathParser.java @@ -8,61 +8,63 @@ import org.robolectric.annotation.Implements; import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader; import org.robolectric.nativeruntime.PathParserNatives; import org.robolectric.shadows.ShadowNativePathParser.Picker; +import org.robolectric.versioning.AndroidVersions.U; /** Shadow for {@link PathParser} that is backed by native code */ @Implements( value = PathParser.class, minSdk = O, shadowPicker = Picker.class, - isInAndroidSdk = false) + isInAndroidSdk = false, + callNativeMethodsByDefault = true) public class ShadowNativePathParser { static { DefaultNativeRuntimeLoader.injectAndLoad(); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nParseStringForPath(long pathPtr, String pathString, int stringLength) { PathParserNatives.nParseStringForPath(pathPtr, pathString, stringLength); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static long nCreatePathDataFromString(String pathString, int stringLength) { return PathParserNatives.nCreatePathDataFromString(pathString, stringLength); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nCreatePathFromPathData(long outPathPtr, long pathData) { PathParserNatives.nCreatePathFromPathData(outPathPtr, pathData); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static long nCreateEmptyPathData() { return PathParserNatives.nCreateEmptyPathData(); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static long nCreatePathData(long nativePtr) { return PathParserNatives.nCreatePathData(nativePtr); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static boolean nInterpolatePathData( long outDataPtr, long fromDataPtr, long toDataPtr, float fraction) { return PathParserNatives.nInterpolatePathData(outDataPtr, fromDataPtr, toDataPtr, fraction); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nFinalize(long nativePtr) { PathParserNatives.nFinalize(nativePtr); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static boolean nCanMorph(long fromDataPtr, long toDataPtr) { return PathParserNatives.nCanMorph(fromDataPtr, toDataPtr); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nSetPathData(long outDataPtr, long fromDataPtr) { PathParserNatives.nSetPathData(outDataPtr, fromDataPtr); } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePicture.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePicture.java index 493076c28..ce98fc8a1 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePicture.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePicture.java @@ -10,55 +10,61 @@ import org.robolectric.annotation.Implements; import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader; import org.robolectric.nativeruntime.PictureNatives; import org.robolectric.shadows.ShadowNativePicture.Picker; +import org.robolectric.versioning.AndroidVersions.U; /** Shadow for {@link Picture} that is backed by native code */ -@Implements(value = Picture.class, minSdk = O, shadowPicker = Picker.class, isInAndroidSdk = false) +@Implements( + value = Picture.class, + minSdk = O, + shadowPicker = Picker.class, + isInAndroidSdk = false, + callNativeMethodsByDefault = true) public class ShadowNativePicture { - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static long nativeConstructor(long nativeSrcOr0) { DefaultNativeRuntimeLoader.injectAndLoad(); return PictureNatives.nativeConstructor(nativeSrcOr0); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static long nativeCreateFromStream(InputStream stream, byte[] storage) { DefaultNativeRuntimeLoader.injectAndLoad(); return PictureNatives.nativeCreateFromStream(stream, storage); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static int nativeGetWidth(long nativePicture) { return PictureNatives.nativeGetWidth(nativePicture); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static int nativeGetHeight(long nativePicture) { return PictureNatives.nativeGetHeight(nativePicture); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static long nativeBeginRecording(long nativeCanvas, int w, int h) { return PictureNatives.nativeBeginRecording(nativeCanvas, w, h); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nativeEndRecording(long nativeCanvas) { PictureNatives.nativeEndRecording(nativeCanvas); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nativeDraw(long nativeCanvas, long nativePicture) { PictureNatives.nativeDraw(nativeCanvas, nativePicture); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static boolean nativeWriteToStream( long nativePicture, OutputStream stream, byte[] storage) { return PictureNatives.nativeWriteToStream(nativePicture, stream, storage); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nativeDestructor(long nativePicture) { PictureNatives.nativeDestructor(nativePicture); } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePluralRules.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePluralRules.java index e99af08c8..454843ee5 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePluralRules.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePluralRules.java @@ -1,7 +1,6 @@ package org.robolectric.shadows; import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2; -import static android.os.Build.VERSION_CODES.KITKAT; import static android.os.Build.VERSION_CODES.M; import org.robolectric.annotation.Implementation; @@ -10,7 +9,7 @@ import org.robolectric.annotation.Implements; @Implements(className = "libcore.icu.NativePluralRules", isInAndroidSdk = false, maxSdk = M) public class ShadowNativePluralRules { - @Implementation(minSdk = KITKAT) + @Implementation protected static int quantityForIntImpl(long address, int quantity) { // just return the mapping for english locale for now if (quantity == 1) return 1; diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePorterDuffColorFilter.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePorterDuffColorFilter.java index 837ab51d3..71c1b1919 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePorterDuffColorFilter.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePorterDuffColorFilter.java @@ -10,16 +10,18 @@ import org.robolectric.annotation.Implements; import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader; import org.robolectric.nativeruntime.PorterDuffColorFilterNatives; import org.robolectric.shadows.ShadowNativePorterDuffColorFilter.Picker; +import org.robolectric.versioning.AndroidVersions.U; /** Shadow for {@link PorterDuffColorFilter} that is backed by native code */ @Implements( value = PorterDuffColorFilter.class, minSdk = O, shadowPicker = Picker.class, - isInAndroidSdk = false) + isInAndroidSdk = false, + callNativeMethodsByDefault = true) public class ShadowNativePorterDuffColorFilter extends ShadowPorterDuffColorFilter { - @Implementation(minSdk = Q) + @Implementation(minSdk = Q, maxSdk = U.SDK_INT) protected static long native_CreateBlendModeFilter(int srcColor, int blendmode) { DefaultNativeRuntimeLoader.injectAndLoad(); return PorterDuffColorFilterNatives.native_CreateBlendModeFilter(srcColor, blendmode); diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePositionedGlyphs.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePositionedGlyphs.java index 90a6f977a..5b65023af 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePositionedGlyphs.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePositionedGlyphs.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import android.graphics.text.MeasuredText; import android.graphics.text.PositionedGlyphs; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; @@ -8,57 +7,72 @@ import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader; import org.robolectric.nativeruntime.PositionedGlyphsNatives; import org.robolectric.shadows.ShadowNativePositionedGlyphs.Picker; import org.robolectric.versioning.AndroidVersions.S; +import org.robolectric.versioning.AndroidVersions.U; +import org.robolectric.versioning.AndroidVersions.V; /** Shadow for {@link PositionedGlyphs} that is backed by native code */ -@Implements(value = PositionedGlyphs.class, minSdk = S.SDK_INT, shadowPicker = Picker.class) +@Implements( + value = PositionedGlyphs.class, + minSdk = S.SDK_INT, + shadowPicker = Picker.class, + callNativeMethodsByDefault = true) public class ShadowNativePositionedGlyphs { - @Implementation + /** + * The {@link PositionedGlyphs} static initializer invokes its own native methods. This has to be + * deferred starting in Android V. + */ + @Implementation(minSdk = V.SDK_INT) + protected static void __staticInitializer__() { + // deferred + } + + @Implementation(maxSdk = U.SDK_INT) protected static int nGetGlyphCount(long minikinLayout) { return PositionedGlyphsNatives.nGetGlyphCount(minikinLayout); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static float nGetTotalAdvance(long minikinLayout) { return PositionedGlyphsNatives.nGetTotalAdvance(minikinLayout); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static float nGetAscent(long minikinLayout) { return PositionedGlyphsNatives.nGetAscent(minikinLayout); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static float nGetDescent(long minikinLayout) { return PositionedGlyphsNatives.nGetDescent(minikinLayout); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static int nGetGlyphId(long minikinLayout, int i) { return PositionedGlyphsNatives.nGetGlyphId(minikinLayout, i); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static float nGetX(long minikinLayout, int i) { return PositionedGlyphsNatives.nGetX(minikinLayout, i); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static float nGetY(long minikinLayout, int i) { return PositionedGlyphsNatives.nGetY(minikinLayout, i); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static long nGetFont(long minikinLayout, int i) { return PositionedGlyphsNatives.nGetFont(minikinLayout, i); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static long nReleaseFunc() { DefaultNativeRuntimeLoader.injectAndLoad(); return PositionedGlyphsNatives.nReleaseFunc(); } - /** Shadow picker for {@link MeasuredText}. */ + /** Shadow picker for {@link PositionedGlyphs}. */ public static final class Picker extends GraphicsShadowPicker<Object> { public Picker() { super(null, ShadowNativePositionedGlyphs.class); diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePropertyValuesHolder.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePropertyValuesHolder.java index f5e9b8e9f..4e2c6f76c 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePropertyValuesHolder.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePropertyValuesHolder.java @@ -8,75 +8,80 @@ import org.robolectric.annotation.Implements; import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader; import org.robolectric.nativeruntime.PropertyValuesHolderNatives; import org.robolectric.shadows.ShadowNativePropertyValuesHolder.Picker; +import org.robolectric.versioning.AndroidVersions.U; /** Shadow for {@link PropertyValuesHolder} that is backed by native code */ -@Implements(value = PropertyValuesHolder.class, minSdk = O, shadowPicker = Picker.class) +@Implements( + value = PropertyValuesHolder.class, + minSdk = O, + shadowPicker = Picker.class, + callNativeMethodsByDefault = true) public class ShadowNativePropertyValuesHolder { static { DefaultNativeRuntimeLoader.injectAndLoad(); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static long nGetIntMethod(Class<?> targetClass, String methodName) { return PropertyValuesHolderNatives.nGetIntMethod(targetClass, methodName); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static long nGetFloatMethod(Class<?> targetClass, String methodName) { return PropertyValuesHolderNatives.nGetFloatMethod(targetClass, methodName); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static long nGetMultipleIntMethod( Class<?> targetClass, String methodName, int numParams) { return PropertyValuesHolderNatives.nGetMultipleIntMethod(targetClass, methodName, numParams); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static long nGetMultipleFloatMethod( Class<?> targetClass, String methodName, int numParams) { return PropertyValuesHolderNatives.nGetMultipleFloatMethod(targetClass, methodName, numParams); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nCallIntMethod(Object target, long methodID, int arg) { PropertyValuesHolderNatives.nCallIntMethod(target, methodID, arg); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nCallFloatMethod(Object target, long methodID, float arg) { PropertyValuesHolderNatives.nCallFloatMethod(target, methodID, arg); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nCallTwoIntMethod(Object target, long methodID, int arg1, int arg2) { PropertyValuesHolderNatives.nCallTwoIntMethod(target, methodID, arg1, arg2); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nCallFourIntMethod( Object target, long methodID, int arg1, int arg2, int arg3, int arg4) { PropertyValuesHolderNatives.nCallFourIntMethod(target, methodID, arg1, arg2, arg3, arg4); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nCallMultipleIntMethod(Object target, long methodID, int[] args) { PropertyValuesHolderNatives.nCallMultipleIntMethod(target, methodID, args); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nCallTwoFloatMethod(Object target, long methodID, float arg1, float arg2) { PropertyValuesHolderNatives.nCallTwoFloatMethod(target, methodID, arg1, arg2); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nCallFourFloatMethod( Object target, long methodID, float arg1, float arg2, float arg3, float arg4) { PropertyValuesHolderNatives.nCallFourFloatMethod(target, methodID, arg1, arg2, arg3, arg4); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nCallMultipleFloatMethod(Object target, long methodID, float[] args) { PropertyValuesHolderNatives.nCallMultipleFloatMethod(target, methodID, args); } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRadialGradient.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRadialGradient.java index 07d2a724c..4411a095e 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRadialGradient.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRadialGradient.java @@ -6,19 +6,24 @@ import static android.os.Build.VERSION_CODES.Q; import static android.os.Build.VERSION_CODES.R; import static android.os.Build.VERSION_CODES.S; +import android.annotation.ColorLong; import android.graphics.RadialGradient; -import androidx.annotation.ColorLong; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader; import org.robolectric.nativeruntime.RadialGradientNatives; import org.robolectric.shadows.ShadowNativeRadialGradient.Picker; +import org.robolectric.versioning.AndroidVersions.U; /** Shadow for {@link RadialGradient} that is backed by native code */ -@Implements(value = RadialGradient.class, minSdk = O, shadowPicker = Picker.class) +@Implements( + value = RadialGradient.class, + minSdk = O, + shadowPicker = Picker.class, + callNativeMethodsByDefault = true) public class ShadowNativeRadialGradient { - @Implementation(minSdk = S) + @Implementation(minSdk = S, maxSdk = U.SDK_INT) protected static long nativeCreate( long matrix, float startX, diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRecordingCanvas.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRecordingCanvas.java index 537419c86..24df09f04 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRecordingCanvas.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRecordingCanvas.java @@ -1,67 +1,92 @@ package org.robolectric.shadows; import static android.os.Build.VERSION_CODES.Q; +import static android.os.Build.VERSION_CODES.R; import static android.os.Build.VERSION_CODES.S; import android.graphics.RecordingCanvas; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; +import org.robolectric.annotation.Resetter; import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader; import org.robolectric.nativeruntime.RecordingCanvasNatives; import org.robolectric.shadows.ShadowNativeRecordingCanvas.Picker; +import org.robolectric.versioning.AndroidVersions.U; /** Shadow for {@link RecordingCanvas} that is backed by native code */ -@Implements(value = RecordingCanvas.class, minSdk = Q, shadowPicker = Picker.class) +@Implements( + value = RecordingCanvas.class, + minSdk = Q, + shadowPicker = Picker.class, + callNativeMethodsByDefault = true) public class ShadowNativeRecordingCanvas extends ShadowNativeBaseRecordingCanvas { - @Implementation + private static final Map<Long, Long> recordingCanvasToRenderNode = + Collections.synchronizedMap(new HashMap<>()); + + @Implementation(maxSdk = U.SDK_INT) protected static long nCreateDisplayListCanvas(long node, int width, int height) { DefaultNativeRuntimeLoader.injectAndLoad(); - return RecordingCanvasNatives.nCreateDisplayListCanvas(node, width, height); + long result = RecordingCanvasNatives.nCreateDisplayListCanvas(node, width, height); + recordingCanvasToRenderNode.put(result, node); + return result; } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nResetDisplayListCanvas(long canvas, long node, int width, int height) { RecordingCanvasNatives.nResetDisplayListCanvas(canvas, node, width, height); + recordingCanvasToRenderNode.put(canvas, node); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static int nGetMaximumTextureWidth() { return RecordingCanvasNatives.nGetMaximumTextureWidth(); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static int nGetMaximumTextureHeight() { return RecordingCanvasNatives.nGetMaximumTextureHeight(); } - @Implementation(minSdk = S) + @Implementation(minSdk = S, maxSdk = U.SDK_INT) protected static void nEnableZ(long renderer, boolean enableZ) { RecordingCanvasNatives.nEnableZ(renderer, enableZ); } - @Implementation(minSdk = S) + @Implementation(minSdk = S, maxSdk = U.SDK_INT) protected static void nFinishRecording(long renderer, long renderNode) { RecordingCanvasNatives.nFinishRecording(renderer, renderNode); } - @Implementation + @Implementation(minSdk = Q, maxSdk = R) + protected static long nFinishRecording(long renderer) { + Long renderNode = recordingCanvasToRenderNode.get(renderer); + if (renderNode != null && renderNode != 0) { + RecordingCanvasNatives.nFinishRecording(renderer, renderNode); + } + return 0; + } + + @Implementation(maxSdk = U.SDK_INT) protected static void nDrawRenderNode(long renderer, long renderNode) { RecordingCanvasNatives.nDrawRenderNode(renderer, renderNode); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nDrawTextureLayer(long renderer, long layer) { RecordingCanvasNatives.nDrawTextureLayer(renderer, layer); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nDrawCircle( long renderer, long propCx, long propCy, long propRadius, long propPaint) { RecordingCanvasNatives.nDrawCircle(renderer, propCx, propCy, propRadius, propPaint); } - @Implementation(minSdk = S) + @Implementation(minSdk = S, maxSdk = U.SDK_INT) protected static void nDrawRipple( long renderer, long propCx, @@ -84,7 +109,7 @@ public class ShadowNativeRecordingCanvas extends ShadowNativeBaseRecordingCanvas runtimeEffect); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nDrawRoundRect( long renderer, long propLeft, @@ -98,11 +123,21 @@ public class ShadowNativeRecordingCanvas extends ShadowNativeBaseRecordingCanvas renderer, propLeft, propTop, propRight, propBottom, propRx, propRy, propPaint); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nDrawWebViewFunctor(long canvas, int functor) { RecordingCanvasNatives.nDrawWebViewFunctor(canvas, functor); } + @Implementation(maxSdk = R) + protected static void nInsertReorderBarrier(long renderer, boolean enableReorder) { + // no-op + } + + @Resetter + public static void reset() { + recordingCanvasToRenderNode.clear(); + } + /** Shadow picker for {@link RecordingCanvas}. */ public static final class Picker extends GraphicsShadowPicker<Object> { public Picker() { diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRegion.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRegion.java index 6c855c48b..898ec4fc0 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRegion.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRegion.java @@ -17,21 +17,27 @@ import org.robolectric.shadows.ShadowNativeRegion.Picker; import org.robolectric.util.ReflectionHelpers.ClassParameter; import org.robolectric.util.reflector.Accessor; import org.robolectric.util.reflector.ForType; +import org.robolectric.versioning.AndroidVersions.U; /** Shadow for {@link Region} that is backed by native code */ -@Implements(value = Region.class, minSdk = O, shadowPicker = Picker.class, isInAndroidSdk = false) +@Implements( + value = Region.class, + minSdk = O, + shadowPicker = Picker.class, + isInAndroidSdk = false, + callNativeMethodsByDefault = true) public class ShadowNativeRegion { RegionNatives regionNatives = new RegionNatives(); @RealObject Region realRegion; - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected void __constructor__(long ni) { invokeConstructor(Region.class, realRegion, ClassParameter.from(long.class, ni)); regionNatives.mNativeRegion = ni; } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected void __constructor__(int left, int top, int right, int bottom) { invokeConstructor( Region.class, @@ -43,128 +49,128 @@ public class ShadowNativeRegion { regionNatives.mNativeRegion = reflector(RegionReflector.class, realRegion).getNativeRegion(); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected void __constructor__(Rect rect) { invokeConstructor(Region.class, realRegion, ClassParameter.from(Rect.class, rect)); regionNatives.mNativeRegion = reflector(RegionReflector.class, realRegion).getNativeRegion(); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static boolean nativeEquals(long nativeR1, long nativeR2) { return RegionNatives.nativeEquals(nativeR1, nativeR2); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static long nativeConstructor() { DefaultNativeRuntimeLoader.injectAndLoad(); return RegionNatives.nativeConstructor(); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nativeDestructor(long nativeRegion) { RegionNatives.nativeDestructor(nativeRegion); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nativeSetRegion(long nativeDst, long nativeSrc) { RegionNatives.nativeSetRegion(nativeDst, nativeSrc); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static boolean nativeSetRect(long nativeDst, int left, int top, int right, int bottom) { return RegionNatives.nativeSetRect(nativeDst, left, top, right, bottom); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static boolean nativeSetPath(long nativeDst, long nativePath, long nativeClip) { return RegionNatives.nativeSetPath(nativeDst, nativePath, nativeClip); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static boolean nativeGetBounds(long nativeRegion, Rect rect) { return RegionNatives.nativeGetBounds(nativeRegion, rect); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static boolean nativeGetBoundaryPath(long nativeRegion, long nativePath) { return RegionNatives.nativeGetBoundaryPath(nativeRegion, nativePath); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static boolean nativeOp( long nativeDst, int left, int top, int right, int bottom, int op) { return RegionNatives.nativeOp(nativeDst, left, top, right, bottom, op); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static boolean nativeOp(long nativeDst, Rect rect, long nativeRegion, int op) { return RegionNatives.nativeOp(nativeDst, rect, nativeRegion, op); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static boolean nativeOp( long nativeDst, long nativeRegion1, long nativeRegion2, int op) { return RegionNatives.nativeOp(nativeDst, nativeRegion1, nativeRegion2, op); } @DoNotCall("Always throws java.lang.UnsupportedOperationException") - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static long nativeCreateFromParcel(Parcel p) { throw new UnsupportedOperationException(); } @DoNotCall("Always throws java.lang.UnsupportedOperationException") - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static boolean nativeWriteToParcel(long nativeRegion, Parcel p) { throw new UnsupportedOperationException(); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static String nativeToString(long nativeRegion) { return RegionNatives.nativeToString(nativeRegion); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected boolean isEmpty() { return regionNatives.isEmpty(); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected boolean isRect() { return regionNatives.isRect(); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected boolean isComplex() { return regionNatives.isComplex(); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected boolean contains(int x, int y) { return regionNatives.contains(x, y); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected boolean quickContains(int left, int top, int right, int bottom) { return regionNatives.quickContains(left, top, right, bottom); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected boolean quickReject(int left, int top, int right, int bottom) { return regionNatives.quickReject(left, top, right, bottom); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected boolean quickReject(Region rgn) { return regionNatives.quickReject(rgn); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected void translate(int dx, int dy, Region dst) { regionNatives.translate(dx, dy, dst); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected void scale(float scale, Region dst) { regionNatives.scale(scale, dst); } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRegionIterator.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRegionIterator.java index b47bd420c..cb0055aed 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRegionIterator.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRegionIterator.java @@ -9,23 +9,28 @@ import org.robolectric.annotation.Implements; import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader; import org.robolectric.nativeruntime.RegionIteratorNatives; import org.robolectric.shadows.ShadowNativeRegionIterator.Picker; +import org.robolectric.versioning.AndroidVersions.U; /** Shadow for {@link RegionIterator} that is backed by native code */ -@Implements(value = RegionIterator.class, minSdk = O, shadowPicker = Picker.class) +@Implements( + value = RegionIterator.class, + minSdk = O, + shadowPicker = Picker.class, + callNativeMethodsByDefault = true) public class ShadowNativeRegionIterator { - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static long nativeConstructor(long nativeRegion) { DefaultNativeRuntimeLoader.injectAndLoad(); return RegionIteratorNatives.nativeConstructor(nativeRegion); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nativeDestructor(long nativeIter) { RegionIteratorNatives.nativeDestructor(nativeIter); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static boolean nativeNext(long nativeIter, Rect r) { return RegionIteratorNatives.nativeNext(nativeIter, r); } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRenderEffect.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRenderEffect.java index 0535803db..d56feb579 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRenderEffect.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRenderEffect.java @@ -9,26 +9,31 @@ import org.robolectric.annotation.Implements; import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader; import org.robolectric.nativeruntime.RenderEffectNatives; import org.robolectric.shadows.ShadowNativeRenderEffect.Picker; +import org.robolectric.versioning.AndroidVersions.U; /** Shadow for {@link RenderEffect} that is backed by native code */ -@Implements(value = RenderEffect.class, minSdk = O, shadowPicker = Picker.class) +@Implements( + value = RenderEffect.class, + minSdk = O, + shadowPicker = Picker.class, + callNativeMethodsByDefault = true) public class ShadowNativeRenderEffect { static { DefaultNativeRuntimeLoader.injectAndLoad(); } - @Implementation(minSdk = S) + @Implementation(minSdk = S, maxSdk = U.SDK_INT) protected static long nativeCreateOffsetEffect(float offsetX, float offsetY, long nativeInput) { return RenderEffectNatives.nativeCreateOffsetEffect(offsetX, offsetY, nativeInput); } - @Implementation(minSdk = S) + @Implementation(minSdk = S, maxSdk = U.SDK_INT) protected static long nativeCreateBlurEffect( float radiusX, float radiusY, long nativeInput, int edgeTreatment) { return RenderEffectNatives.nativeCreateBlurEffect(radiusX, radiusY, nativeInput, edgeTreatment); } - @Implementation(minSdk = S) + @Implementation(minSdk = S, maxSdk = U.SDK_INT) protected static long nativeCreateBitmapEffect( long bitmapHandle, float srcLeft, @@ -43,27 +48,27 @@ public class ShadowNativeRenderEffect { bitmapHandle, srcLeft, srcTop, srcRight, srcBottom, dstLeft, dstTop, dstRight, dstBottom); } - @Implementation(minSdk = S) + @Implementation(minSdk = S, maxSdk = U.SDK_INT) protected static long nativeCreateColorFilterEffect(long colorFilter, long nativeInput) { return RenderEffectNatives.nativeCreateColorFilterEffect(colorFilter, nativeInput); } - @Implementation(minSdk = S) + @Implementation(minSdk = S, maxSdk = U.SDK_INT) protected static long nativeCreateBlendModeEffect(long dst, long src, int blendmode) { return RenderEffectNatives.nativeCreateBlendModeEffect(dst, src, blendmode); } - @Implementation(minSdk = S) + @Implementation(minSdk = S, maxSdk = U.SDK_INT) protected static long nativeCreateChainEffect(long outer, long inner) { return RenderEffectNatives.nativeCreateChainEffect(outer, inner); } - @Implementation(minSdk = S) + @Implementation(minSdk = S, maxSdk = U.SDK_INT) protected static long nativeCreateShaderEffect(long shader) { return RenderEffectNatives.nativeCreateShaderEffect(shader); } - @Implementation(minSdk = S) + @Implementation(minSdk = S, maxSdk = U.SDK_INT) protected static long nativeGetFinalizer() { return RenderEffectNatives.nativeGetFinalizer(); } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRenderNode.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRenderNode.java index 4b11355f7..489602636 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRenderNode.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRenderNode.java @@ -4,40 +4,47 @@ import static android.os.Build.VERSION_CODES.Q; import static android.os.Build.VERSION_CODES.R; import static android.os.Build.VERSION_CODES.S; import static android.os.Build.VERSION_CODES.S_V2; +import static android.os.Build.VERSION_CODES.TIRAMISU; import android.graphics.RenderNode; import android.graphics.RenderNode.PositionUpdateListener; +import java.lang.ref.WeakReference; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader; import org.robolectric.nativeruntime.RenderNodeNatives; import org.robolectric.shadows.ShadowNativeRenderNode.Picker; +import org.robolectric.versioning.AndroidVersions.U; /** Shadow for {@link RenderNode} that is backed by native code */ -@Implements(value = RenderNode.class, minSdk = Q, shadowPicker = Picker.class) +@Implements( + value = RenderNode.class, + minSdk = Q, + shadowPicker = Picker.class, + callNativeMethodsByDefault = true) public class ShadowNativeRenderNode { - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static long nCreate(String name) { DefaultNativeRuntimeLoader.injectAndLoad(); return RenderNodeNatives.nCreate(name); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static long nGetNativeFinalizer() { return RenderNodeNatives.nGetNativeFinalizer(); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nOutput(long renderNode) { RenderNodeNatives.nOutput(renderNode); } - @Implementation(minSdk = R) + @Implementation(minSdk = R, maxSdk = U.SDK_INT) protected static int nGetUsageSize(long renderNode) { return RenderNodeNatives.nGetUsageSize(renderNode); } - @Implementation(minSdk = R) + @Implementation(minSdk = R, maxSdk = U.SDK_INT) protected static int nGetAllocatedSize(long renderNode) { return RenderNodeNatives.nGetAllocatedSize(renderNode); } @@ -47,418 +54,448 @@ public class ShadowNativeRenderNode { RenderNodeNatives.nRequestPositionUpdates(renderNode, callback); } - @Implementation + @Implementation(minSdk = TIRAMISU, maxSdk = U.SDK_INT) + protected static void nRequestPositionUpdates( + long renderNode, WeakReference<PositionUpdateListener> callback) { + nRequestPositionUpdates(renderNode, callback.get()); + } + + @Implementation(maxSdk = U.SDK_INT) protected static void nAddAnimator(long renderNode, long animatorPtr) { RenderNodeNatives.nAddAnimator(renderNode, animatorPtr); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nEndAllAnimators(long renderNode) { RenderNodeNatives.nEndAllAnimators(renderNode); } - @Implementation(minSdk = S) + @Implementation(minSdk = TIRAMISU, maxSdk = U.SDK_INT) + protected static void nForceEndAnimators(long renderNode) { + RenderNodeNatives.nForceEndAnimators(renderNode); + } + + @Implementation(minSdk = S, maxSdk = U.SDK_INT) protected static void nDiscardDisplayList(long renderNode) { RenderNodeNatives.nDiscardDisplayList(renderNode); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static boolean nIsValid(long renderNode) { return RenderNodeNatives.nIsValid(renderNode); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nGetTransformMatrix(long renderNode, long nativeMatrix) { RenderNodeNatives.nGetTransformMatrix(renderNode, nativeMatrix); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nGetInverseTransformMatrix(long renderNode, long nativeMatrix) { RenderNodeNatives.nGetInverseTransformMatrix(renderNode, nativeMatrix); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static boolean nHasIdentityMatrix(long renderNode) { return RenderNodeNatives.nHasIdentityMatrix(renderNode); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static boolean nOffsetTopAndBottom(long renderNode, int offset) { return RenderNodeNatives.nOffsetTopAndBottom(renderNode, offset); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static boolean nOffsetLeftAndRight(long renderNode, int offset) { return RenderNodeNatives.nOffsetLeftAndRight(renderNode, offset); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static boolean nSetLeftTopRightBottom( long renderNode, int left, int top, int right, int bottom) { return RenderNodeNatives.nSetLeftTopRightBottom(renderNode, left, top, right, bottom); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static boolean nSetLeft(long renderNode, int left) { return RenderNodeNatives.nSetLeft(renderNode, left); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static boolean nSetTop(long renderNode, int top) { return RenderNodeNatives.nSetTop(renderNode, top); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static boolean nSetRight(long renderNode, int right) { return RenderNodeNatives.nSetRight(renderNode, right); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static boolean nSetBottom(long renderNode, int bottom) { return RenderNodeNatives.nSetBottom(renderNode, bottom); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static int nGetLeft(long renderNode) { return RenderNodeNatives.nGetLeft(renderNode); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static int nGetTop(long renderNode) { return RenderNodeNatives.nGetTop(renderNode); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static int nGetRight(long renderNode) { return RenderNodeNatives.nGetRight(renderNode); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static int nGetBottom(long renderNode) { return RenderNodeNatives.nGetBottom(renderNode); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static boolean nSetCameraDistance(long renderNode, float distance) { return RenderNodeNatives.nSetCameraDistance(renderNode, distance); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static boolean nSetPivotY(long renderNode, float pivotY) { return RenderNodeNatives.nSetPivotY(renderNode, pivotY); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static boolean nSetPivotX(long renderNode, float pivotX) { return RenderNodeNatives.nSetPivotX(renderNode, pivotX); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static boolean nResetPivot(long renderNode) { return RenderNodeNatives.nResetPivot(renderNode); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static boolean nSetLayerType(long renderNode, int layerType) { return RenderNodeNatives.nSetLayerType(renderNode, layerType); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static int nGetLayerType(long renderNode) { return RenderNodeNatives.nGetLayerType(renderNode); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static boolean nSetLayerPaint(long renderNode, long paint) { return RenderNodeNatives.nSetLayerPaint(renderNode, paint); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static boolean nSetClipToBounds(long renderNode, boolean clipToBounds) { return RenderNodeNatives.nSetClipToBounds(renderNode, clipToBounds); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static boolean nGetClipToBounds(long renderNode) { return RenderNodeNatives.nGetClipToBounds(renderNode); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static boolean nSetClipBounds( long renderNode, int left, int top, int right, int bottom) { return RenderNodeNatives.nSetClipBounds(renderNode, left, top, right, bottom); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static boolean nSetClipBoundsEmpty(long renderNode) { return RenderNodeNatives.nSetClipBoundsEmpty(renderNode); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static boolean nSetProjectBackwards(long renderNode, boolean shouldProject) { return RenderNodeNatives.nSetProjectBackwards(renderNode, shouldProject); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static boolean nSetProjectionReceiver(long renderNode, boolean shouldReceive) { return RenderNodeNatives.nSetProjectionReceiver(renderNode, shouldReceive); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static boolean nSetOutlineRoundRect( long renderNode, int left, int top, int right, int bottom, float radius, float alpha) { return RenderNodeNatives.nSetOutlineRoundRect( renderNode, left, top, right, bottom, radius, alpha); } - @Implementation(minSdk = R) + @Implementation(minSdk = R, maxSdk = U.SDK_INT) protected static boolean nSetOutlinePath(long renderNode, long nativePath, float alpha) { return RenderNodeNatives.nSetOutlinePath(renderNode, nativePath, alpha); } - @Implementation + @Implementation(maxSdk = Q) + protected static boolean nSetOutlineConvexPath(long renderNode, long nativePath, float alpha) { + return nSetOutlinePath(renderNode, nativePath, alpha); + } + + @Implementation(maxSdk = U.SDK_INT) protected static boolean nSetOutlineEmpty(long renderNode) { return RenderNodeNatives.nSetOutlineEmpty(renderNode); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static boolean nSetOutlineNone(long renderNode) { return RenderNodeNatives.nSetOutlineNone(renderNode); } - @Implementation(minSdk = S) + @Implementation(minSdk = S, maxSdk = U.SDK_INT) protected static boolean nClearStretch(long renderNode) { return RenderNodeNatives.nClearStretch(renderNode); } - @Implementation(minSdk = S) + @Implementation(minSdk = S, maxSdk = U.SDK_INT) protected static boolean nStretch( long renderNode, float vecX, float vecY, float maxStretchX, float maxStretchY) { return RenderNodeNatives.nStretch(renderNode, vecX, vecY, maxStretchX, maxStretchY); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static boolean nHasShadow(long renderNode) { return RenderNodeNatives.nHasShadow(renderNode); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static boolean nSetSpotShadowColor(long renderNode, int color) { return RenderNodeNatives.nSetSpotShadowColor(renderNode, color); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static boolean nSetAmbientShadowColor(long renderNode, int color) { return RenderNodeNatives.nSetAmbientShadowColor(renderNode, color); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static int nGetSpotShadowColor(long renderNode) { return RenderNodeNatives.nGetSpotShadowColor(renderNode); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static int nGetAmbientShadowColor(long renderNode) { return RenderNodeNatives.nGetAmbientShadowColor(renderNode); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static boolean nSetClipToOutline(long renderNode, boolean clipToOutline) { return RenderNodeNatives.nSetClipToOutline(renderNode, clipToOutline); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static boolean nSetRevealClip( long renderNode, boolean shouldClip, float x, float y, float radius) { return RenderNodeNatives.nSetRevealClip(renderNode, shouldClip, x, y, radius); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static boolean nSetAlpha(long renderNode, float alpha) { return RenderNodeNatives.nSetAlpha(renderNode, alpha); } - @Implementation(minSdk = S) + @Implementation(minSdk = S, maxSdk = U.SDK_INT) protected static boolean nSetRenderEffect(long renderNode, long renderEffect) { return RenderNodeNatives.nSetRenderEffect(renderNode, renderEffect); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static boolean nSetHasOverlappingRendering( long renderNode, boolean hasOverlappingRendering) { return RenderNodeNatives.nSetHasOverlappingRendering(renderNode, hasOverlappingRendering); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nSetUsageHint(long renderNode, int usageHint) { RenderNodeNatives.nSetUsageHint(renderNode, usageHint); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static boolean nSetElevation(long renderNode, float lift) { return RenderNodeNatives.nSetElevation(renderNode, lift); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static boolean nSetTranslationX(long renderNode, float translationX) { return RenderNodeNatives.nSetTranslationX(renderNode, translationX); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static boolean nSetTranslationY(long renderNode, float translationY) { return RenderNodeNatives.nSetTranslationY(renderNode, translationY); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static boolean nSetTranslationZ(long renderNode, float translationZ) { return RenderNodeNatives.nSetTranslationZ(renderNode, translationZ); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static boolean nSetRotation(long renderNode, float rotation) { return RenderNodeNatives.nSetRotation(renderNode, rotation); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static boolean nSetRotationX(long renderNode, float rotationX) { return RenderNodeNatives.nSetRotationX(renderNode, rotationX); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static boolean nSetRotationY(long renderNode, float rotationY) { return RenderNodeNatives.nSetRotationY(renderNode, rotationY); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static boolean nSetScaleX(long renderNode, float scaleX) { return RenderNodeNatives.nSetScaleX(renderNode, scaleX); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static boolean nSetScaleY(long renderNode, float scaleY) { return RenderNodeNatives.nSetScaleY(renderNode, scaleY); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static boolean nSetStaticMatrix(long renderNode, long nativeMatrix) { return RenderNodeNatives.nSetStaticMatrix(renderNode, nativeMatrix); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static boolean nSetAnimationMatrix(long renderNode, long animationMatrix) { return RenderNodeNatives.nSetAnimationMatrix(renderNode, animationMatrix); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static boolean nHasOverlappingRendering(long renderNode) { return RenderNodeNatives.nHasOverlappingRendering(renderNode); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static boolean nGetAnimationMatrix(long renderNode, long animationMatrix) { return RenderNodeNatives.nGetAnimationMatrix(renderNode, animationMatrix); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static boolean nGetClipToOutline(long renderNode) { return RenderNodeNatives.nGetClipToOutline(renderNode); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static float nGetAlpha(long renderNode) { return RenderNodeNatives.nGetAlpha(renderNode); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static float nGetCameraDistance(long renderNode) { return RenderNodeNatives.nGetCameraDistance(renderNode); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static float nGetScaleX(long renderNode) { return RenderNodeNatives.nGetScaleX(renderNode); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static float nGetScaleY(long renderNode) { return RenderNodeNatives.nGetScaleY(renderNode); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static float nGetElevation(long renderNode) { return RenderNodeNatives.nGetElevation(renderNode); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static float nGetTranslationX(long renderNode) { return RenderNodeNatives.nGetTranslationX(renderNode); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static float nGetTranslationY(long renderNode) { return RenderNodeNatives.nGetTranslationY(renderNode); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static float nGetTranslationZ(long renderNode) { return RenderNodeNatives.nGetTranslationZ(renderNode); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static float nGetRotation(long renderNode) { return RenderNodeNatives.nGetRotation(renderNode); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static float nGetRotationX(long renderNode) { return RenderNodeNatives.nGetRotationX(renderNode); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static float nGetRotationY(long renderNode) { return RenderNodeNatives.nGetRotationY(renderNode); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static boolean nIsPivotExplicitlySet(long renderNode) { return RenderNodeNatives.nIsPivotExplicitlySet(renderNode); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static float nGetPivotX(long renderNode) { return RenderNodeNatives.nGetPivotX(renderNode); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static float nGetPivotY(long renderNode) { return RenderNodeNatives.nGetPivotY(renderNode); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static int nGetWidth(long renderNode) { return RenderNodeNatives.nGetWidth(renderNode); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static int nGetHeight(long renderNode) { return RenderNodeNatives.nGetHeight(renderNode); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static boolean nSetAllowForceDark(long renderNode, boolean allowForceDark) { return RenderNodeNatives.nSetAllowForceDark(renderNode, allowForceDark); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static boolean nGetAllowForceDark(long renderNode) { return RenderNodeNatives.nGetAllowForceDark(renderNode); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static long nGetUniqueId(long renderNode) { return RenderNodeNatives.nGetUniqueId(renderNode); } + @Implementation(minSdk = Q, maxSdk = R) + protected static void nSetDisplayList(long renderNode, long newData) { + // No-op + // In Q and R, ending recording was a two-part operation, one part is calling + // RecordingCanvas.finishRecording (which returned a long displayList), + // and then calling RenderNode.nSetDisplayList with that result. However, in S, these + // were combined into one, and all that is needed is to call RecordingCanvas.finishRecording. + } + + @Implementation(minSdk = U.SDK_INT, maxSdk = U.SDK_INT) + protected static void nSetIsTextureView(long renderNode) { + // no-op + } + /** Shadow picker for {@link RenderNode}. */ public static final class Picker extends GraphicsShadowPicker<Object> { public Picker() { diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRenderNodeAnimator.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRenderNodeAnimator.java index 5b3c32a1c..a8cf0f60a 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRenderNodeAnimator.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRenderNodeAnimator.java @@ -8,27 +8,29 @@ import org.robolectric.annotation.Implements; import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader; import org.robolectric.nativeruntime.RenderNodeAnimatorNatives; import org.robolectric.shadows.ShadowNativeRenderNodeAnimator.Picker; +import org.robolectric.versioning.AndroidVersions.U; /** Shadow for {@link RenderNodeAnimator} that is backed by native code */ @Implements( value = RenderNodeAnimator.class, minSdk = R, shadowPicker = Picker.class, - isInAndroidSdk = false) + isInAndroidSdk = false, + callNativeMethodsByDefault = true) public class ShadowNativeRenderNodeAnimator { - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static long nCreateAnimator(int property, float finalValue) { DefaultNativeRuntimeLoader.injectAndLoad(); return RenderNodeAnimatorNatives.nCreateAnimator(property, finalValue); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static long nCreateCanvasPropertyFloatAnimator(long canvasProperty, float finalValue) { DefaultNativeRuntimeLoader.injectAndLoad(); return RenderNodeAnimatorNatives.nCreateCanvasPropertyFloatAnimator(canvasProperty, finalValue); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static long nCreateCanvasPropertyPaintAnimator( long canvasProperty, int paintField, float finalValue) { DefaultNativeRuntimeLoader.injectAndLoad(); @@ -36,53 +38,53 @@ public class ShadowNativeRenderNodeAnimator { canvasProperty, paintField, finalValue); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static long nCreateRevealAnimator(int x, int y, float startRadius, float endRadius) { DefaultNativeRuntimeLoader.injectAndLoad(); return RenderNodeAnimatorNatives.nCreateRevealAnimator(x, y, startRadius, endRadius); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nSetStartValue(long nativePtr, float startValue) { RenderNodeAnimatorNatives.nSetStartValue(nativePtr, startValue); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nSetDuration(long nativePtr, long duration) { RenderNodeAnimatorNatives.nSetDuration(nativePtr, duration); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static long nGetDuration(long nativePtr) { return RenderNodeAnimatorNatives.nGetDuration(nativePtr); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nSetStartDelay(long nativePtr, long startDelay) { RenderNodeAnimatorNatives.nSetStartDelay(nativePtr, startDelay); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nSetInterpolator(long animPtr, long interpolatorPtr) { RenderNodeAnimatorNatives.nSetInterpolator(animPtr, interpolatorPtr); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nSetAllowRunningAsync(long animPtr, boolean mayRunAsync) { RenderNodeAnimatorNatives.nSetAllowRunningAsync(animPtr, mayRunAsync); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nSetListener(long animPtr, RenderNodeAnimator listener) { RenderNodeAnimatorNatives.nSetListener(animPtr, listener); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nStart(long animPtr) { RenderNodeAnimatorNatives.nStart(animPtr); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nEnd(long animPtr) { RenderNodeAnimatorNatives.nEnd(animPtr); } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRuntimeShader.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRuntimeShader.java index 9793d1d68..c048f62c4 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRuntimeShader.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRuntimeShader.java @@ -15,9 +15,14 @@ import org.robolectric.nativeruntime.RuntimeShaderNatives; import org.robolectric.shadow.api.Shadow; import org.robolectric.shadows.ShadowNativeRuntimeShader.Picker; import org.robolectric.util.ReflectionHelpers.ClassParameter; +import org.robolectric.versioning.AndroidVersions.U; /** Shadow for {@link RuntimeShader} that is backed by native code */ -@Implements(value = RuntimeShader.class, minSdk = O, shadowPicker = Picker.class) +@Implements( + value = RuntimeShader.class, + minSdk = O, + shadowPicker = Picker.class, + callNativeMethodsByDefault = true) public class ShadowNativeRuntimeShader { @RealObject RuntimeShader runtimeShader; @@ -127,7 +132,7 @@ public class ShadowNativeRuntimeShader { private static final String RIPPLE_SHADER_31 = RIPPLE_SHADER_UNIFORMS_31 + RIPPLE_SHADER_LIB_31 + RIPPLE_SHADER_MAIN_31; - @Implementation(minSdk = TIRAMISU) + @Implementation(minSdk = TIRAMISU, maxSdk = U.SDK_INT) protected void __constructor__(String sksl) { // This is a workaround for supporting RippleShader from T+ with the native code from S. // There were some new capabilities added to SKSL in T which are not available in S. Use the @@ -144,12 +149,12 @@ public class ShadowNativeRuntimeShader { RuntimeShader.class, runtimeShader, ClassParameter.from(String.class, sksl)); } - @Implementation(minSdk = R) + @Implementation(minSdk = R, maxSdk = U.SDK_INT) protected static long nativeGetFinalizer() { return RuntimeShaderNatives.nativeGetFinalizer(); } - @Implementation(minSdk = S) + @Implementation(minSdk = S, maxSdk = U.SDK_INT) protected static long nativeCreateBuilder(String sksl) { DefaultNativeRuntimeLoader.injectAndLoad(); return RuntimeShaderNatives.nativeCreateBuilder(sksl); @@ -160,13 +165,54 @@ public class ShadowNativeRuntimeShader { return RuntimeShaderNatives.nativeCreateShader(shaderBuilder, matrix, isOpaque); } + @Implementation(minSdk = TIRAMISU, maxSdk = U.SDK_INT) + protected static long nativeCreateShader(long shaderBuilder, long matrix) { + return nativeCreateShader(shaderBuilder, matrix, false); + } + @Implementation(minSdk = S, maxSdk = S_V2) protected static void nativeUpdateUniforms( long shaderBuilder, String uniformName, float[] uniforms) { RuntimeShaderNatives.nativeUpdateUniforms(shaderBuilder, uniformName, uniforms); } - @Implementation(minSdk = S) + @Implementation(minSdk = TIRAMISU, maxSdk = U.SDK_INT) + protected static void nativeUpdateUniforms( + long shaderBuilder, String uniformName, float[] uniforms, boolean isColor) { + nativeUpdateUniforms(shaderBuilder, uniformName, uniforms); + } + + @Implementation(minSdk = TIRAMISU, maxSdk = U.SDK_INT) + protected static void nativeUpdateUniforms( + long shaderBuilder, + String uniformName, + float value1, + float value2, + float value3, + float value4, + int count) { + // no-op + } + + @Implementation(minSdk = TIRAMISU, maxSdk = U.SDK_INT) + protected static void nativeUpdateUniforms( + long shaderBuilder, String uniformName, int[] uniforms) { + // no-op + } + + @Implementation(minSdk = TIRAMISU, maxSdk = U.SDK_INT) + protected static void nativeUpdateUniforms( + long shaderBuilder, + String uniformName, + int value1, + int value2, + int value3, + int value4, + int count) { + // no-op + } + + @Implementation(minSdk = S, maxSdk = U.SDK_INT) protected static void nativeUpdateShader(long shaderBuilder, String shaderName, long shader) { RuntimeShaderNatives.nativeUpdateShader(shaderBuilder, shaderName, shader); } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeSQLiteConnection.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeSQLiteConnection.java index 8c3b6de40..5f26774c3 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeSQLiteConnection.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeSQLiteConnection.java @@ -16,9 +16,14 @@ import org.robolectric.annotation.Implements; import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader; import org.robolectric.nativeruntime.SQLiteConnectionNatives; import org.robolectric.util.PerfStatsCollector; +import org.robolectric.versioning.AndroidVersions.T; +import org.robolectric.versioning.AndroidVersions.U; /** Shadow for {@link SQLiteConnection} that is backed by native code */ -@Implements(className = "android.database.sqlite.SQLiteConnection", isInAndroidSdk = false) +@Implements( + className = "android.database.sqlite.SQLiteConnection", + isInAndroidSdk = false, + callNativeMethodsByDefault = true) public class ShadowNativeSQLiteConnection extends ShadowSQLiteConnection { @Implementation(maxSdk = O) protected static Number nativeOpen( @@ -30,7 +35,7 @@ public class ShadowNativeSQLiteConnection extends ShadowSQLiteConnection { return result; } - @Implementation(minSdk = O_MR1) + @Implementation(minSdk = O_MR1, maxSdk = U.SDK_INT) protected static long nativeOpen( String path, int openFlags, @@ -59,7 +64,7 @@ public class ShadowNativeSQLiteConnection extends ShadowSQLiteConnection { nativeClose(PreLPointers.get(connectionPtr)); } - @Implementation(minSdk = LOLLIPOP) + @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT) protected static void nativeClose(long connectionPtr) { PerfStatsCollector.getInstance() .measure("androidsqlite", () -> SQLiteConnectionNatives.nativeClose(connectionPtr)); @@ -71,7 +76,7 @@ public class ShadowNativeSQLiteConnection extends ShadowSQLiteConnection { return PreLPointers.register(statementPtr); } - @Implementation(minSdk = LOLLIPOP) + @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT) protected static long nativePrepareStatement(long connectionPtr, String sql) { return PerfStatsCollector.getInstance() .measure( @@ -84,7 +89,7 @@ public class ShadowNativeSQLiteConnection extends ShadowSQLiteConnection { nativeFinalizeStatement(PreLPointers.get(connectionPtr), PreLPointers.get(statementPtr)); } - @Implementation(minSdk = LOLLIPOP) + @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT) protected static void nativeFinalizeStatement(long connectionPtr, long statementPtr) { PerfStatsCollector.getInstance() .measure( @@ -97,7 +102,7 @@ public class ShadowNativeSQLiteConnection extends ShadowSQLiteConnection { return nativeGetParameterCount(PreLPointers.get(connectionPtr), PreLPointers.get(statementPtr)); } - @Implementation(minSdk = LOLLIPOP) + @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT) protected static int nativeGetParameterCount(final long connectionPtr, final long statementPtr) { return PerfStatsCollector.getInstance() .measure( @@ -110,7 +115,7 @@ public class ShadowNativeSQLiteConnection extends ShadowSQLiteConnection { return nativeIsReadOnly(PreLPointers.get(connectionPtr), PreLPointers.get(statementPtr)); } - @Implementation(minSdk = LOLLIPOP) + @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT) protected static boolean nativeIsReadOnly(final long connectionPtr, final long statementPtr) { return PerfStatsCollector.getInstance() .measure( @@ -123,7 +128,7 @@ public class ShadowNativeSQLiteConnection extends ShadowSQLiteConnection { return nativeExecuteForString(PreLPointers.get(connectionPtr), PreLPointers.get(statementPtr)); } - @Implementation(minSdk = LOLLIPOP) + @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT) protected static String nativeExecuteForString( final long connectionPtr, final long statementPtr) { return PerfStatsCollector.getInstance() @@ -137,7 +142,7 @@ public class ShadowNativeSQLiteConnection extends ShadowSQLiteConnection { nativeRegisterLocalizedCollators(PreLPointers.get(connectionPtr), locale); } - @Implementation(minSdk = LOLLIPOP) + @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT) protected static void nativeRegisterLocalizedCollators(long connectionPtr, String locale) { PerfStatsCollector.getInstance() .measure( @@ -150,7 +155,7 @@ public class ShadowNativeSQLiteConnection extends ShadowSQLiteConnection { return nativeExecuteForLong(PreLPointers.get(connectionPtr), PreLPointers.get(statementPtr)); } - @Implementation(minSdk = LOLLIPOP) + @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT) protected static long nativeExecuteForLong(final long connectionPtr, final long statementPtr) { return PerfStatsCollector.getInstance() .measure( @@ -171,7 +176,7 @@ public class ShadowNativeSQLiteConnection extends ShadowSQLiteConnection { () -> SQLiteConnectionNatives.nativeExecute(connectionPtr, statementPtr, false)); } - @Implementation(minSdk = 33) + @Implementation(minSdk = T.SDK_INT, maxSdk = U.SDK_INT) protected static void nativeExecute( final long connectionPtr, final long statementPtr, boolean isPragmaStmt) { PerfStatsCollector.getInstance() @@ -186,7 +191,7 @@ public class ShadowNativeSQLiteConnection extends ShadowSQLiteConnection { PreLPointers.get(connectionPtr), PreLPointers.get(statementPtr)); } - @Implementation(minSdk = LOLLIPOP) + @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT) protected static int nativeExecuteForChangedRowCount( final long connectionPtr, final long statementPtr) { return PerfStatsCollector.getInstance() @@ -202,7 +207,7 @@ public class ShadowNativeSQLiteConnection extends ShadowSQLiteConnection { return nativeGetColumnCount(PreLPointers.get(connectionPtr), PreLPointers.get(statementPtr)); } - @Implementation(minSdk = LOLLIPOP) + @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT) protected static int nativeGetColumnCount(final long connectionPtr, final long statementPtr) { return PerfStatsCollector.getInstance() .measure( @@ -216,7 +221,7 @@ public class ShadowNativeSQLiteConnection extends ShadowSQLiteConnection { PreLPointers.get(connectionPtr), PreLPointers.get(statementPtr), index); } - @Implementation(minSdk = LOLLIPOP) + @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT) protected static String nativeGetColumnName( final long connectionPtr, final long statementPtr, final int index) { return PerfStatsCollector.getInstance() @@ -230,7 +235,7 @@ public class ShadowNativeSQLiteConnection extends ShadowSQLiteConnection { nativeBindNull(PreLPointers.get(connectionPtr), PreLPointers.get(statementPtr), index); } - @Implementation(minSdk = LOLLIPOP) + @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT) protected static void nativeBindNull( final long connectionPtr, final long statementPtr, final int index) { PerfStatsCollector.getInstance() @@ -244,7 +249,7 @@ public class ShadowNativeSQLiteConnection extends ShadowSQLiteConnection { nativeBindLong(PreLPointers.get(connectionPtr), PreLPointers.get(statementPtr), index, value); } - @Implementation(minSdk = LOLLIPOP) + @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT) protected static void nativeBindLong( final long connectionPtr, final long statementPtr, final int index, final long value) { PerfStatsCollector.getInstance() @@ -260,7 +265,7 @@ public class ShadowNativeSQLiteConnection extends ShadowSQLiteConnection { nativeBindDouble(PreLPointers.get(connectionPtr), PreLPointers.get(statementPtr), index, value); } - @Implementation(minSdk = LOLLIPOP) + @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT) protected static void nativeBindDouble( final long connectionPtr, final long statementPtr, final int index, final double value) { PerfStatsCollector.getInstance() @@ -277,7 +282,7 @@ public class ShadowNativeSQLiteConnection extends ShadowSQLiteConnection { nativeBindString(PreLPointers.get(connectionPtr), PreLPointers.get(statementPtr), index, value); } - @Implementation(minSdk = LOLLIPOP) + @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT) protected static void nativeBindString( final long connectionPtr, final long statementPtr, final int index, final String value) { PerfStatsCollector.getInstance() @@ -294,7 +299,7 @@ public class ShadowNativeSQLiteConnection extends ShadowSQLiteConnection { nativeBindBlob(PreLPointers.get(connectionPtr), PreLPointers.get(statementPtr), index, value); } - @Implementation(minSdk = LOLLIPOP) + @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT) protected static void nativeBindBlob( final long connectionPtr, final long statementPtr, final int index, final byte[] value) { PerfStatsCollector.getInstance() @@ -310,7 +315,7 @@ public class ShadowNativeSQLiteConnection extends ShadowSQLiteConnection { PreLPointers.get(connectionPtr), PreLPointers.get(statementPtr)); } - @Implementation(minSdk = LOLLIPOP) + @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT) protected static void nativeResetStatementAndClearBindings( final long connectionPtr, final long statementPtr) { PerfStatsCollector.getInstance() @@ -327,7 +332,7 @@ public class ShadowNativeSQLiteConnection extends ShadowSQLiteConnection { PreLPointers.get(connectionPtr), PreLPointers.get(statementPtr)); } - @Implementation(minSdk = LOLLIPOP) + @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT) protected static long nativeExecuteForLastInsertedRowId( final long connectionPtr, final long statementPtr) { return PerfStatsCollector.getInstance() @@ -355,7 +360,7 @@ public class ShadowNativeSQLiteConnection extends ShadowSQLiteConnection { countAllRows); } - @Implementation(minSdk = LOLLIPOP) + @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT) protected static long nativeExecuteForCursorWindow( final long connectionPtr, final long statementPtr, @@ -377,7 +382,7 @@ public class ShadowNativeSQLiteConnection extends ShadowSQLiteConnection { PreLPointers.get(connectionPtr), PreLPointers.get(statementPtr)); } - @Implementation(minSdk = LOLLIPOP) + @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT) protected static int nativeExecuteForBlobFileDescriptor( final long connectionPtr, final long statementPtr) { return PerfStatsCollector.getInstance() @@ -393,7 +398,7 @@ public class ShadowNativeSQLiteConnection extends ShadowSQLiteConnection { nativeCancel(PreLPointers.get(connectionPtr)); } - @Implementation(minSdk = LOLLIPOP) + @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT) protected static void nativeCancel(long connectionPtr) { PerfStatsCollector.getInstance() .measure("androidsqlite", () -> SQLiteConnectionNatives.nativeCancel(connectionPtr)); @@ -404,7 +409,7 @@ public class ShadowNativeSQLiteConnection extends ShadowSQLiteConnection { nativeResetCancel(PreLPointers.get(connectionPtr), cancelable); } - @Implementation(minSdk = LOLLIPOP) + @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT) protected static void nativeResetCancel(long connectionPtr, boolean cancelable) { PerfStatsCollector.getInstance() .measure( @@ -412,7 +417,7 @@ public class ShadowNativeSQLiteConnection extends ShadowSQLiteConnection { () -> SQLiteConnectionNatives.nativeResetCancel(connectionPtr, cancelable)); } - @Implementation(minSdk = R) + @Implementation(minSdk = R, maxSdk = U.SDK_INT) @SuppressWarnings("AndroidJdkLibsChecker") protected static void nativeRegisterCustomScalarFunction( long connectionPtr, String name, UnaryOperator<String> function) { @@ -424,7 +429,7 @@ public class ShadowNativeSQLiteConnection extends ShadowSQLiteConnection { connectionPtr, name, function)); } - @Implementation(minSdk = R) + @Implementation(minSdk = R, maxSdk = U.SDK_INT) @SuppressWarnings("AndroidJdkLibsChecker") protected static void nativeRegisterCustomAggregateFunction( long connectionPtr, String name, BinaryOperator<String> function) { @@ -441,7 +446,7 @@ public class ShadowNativeSQLiteConnection extends ShadowSQLiteConnection { return nativeGetDbLookaside(PreLPointers.get(connectionPtr)); } - @Implementation(minSdk = LOLLIPOP) + @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT) protected static int nativeGetDbLookaside(long connectionPtr) { return PerfStatsCollector.getInstance() .measure( diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeShader.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeShader.java index 1a216f8ab..4b71f0d19 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeShader.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeShader.java @@ -8,12 +8,17 @@ import org.robolectric.annotation.Implements; import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader; import org.robolectric.nativeruntime.ShaderNatives; import org.robolectric.shadows.ShadowNativeShader.Picker; +import org.robolectric.versioning.AndroidVersions.U; /** Shadow for {@link Shader} that is backed by native code */ -@Implements(value = Shader.class, minSdk = O, shadowPicker = Picker.class) +@Implements( + value = Shader.class, + minSdk = O, + shadowPicker = Picker.class, + callNativeMethodsByDefault = true) public class ShadowNativeShader { - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static long nativeGetFinalizer() { DefaultNativeRuntimeLoader.injectAndLoad(); return ShaderNatives.nativeGetFinalizer(); diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeSumPathEffect.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeSumPathEffect.java index 2d436ae23..1c21f918e 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeSumPathEffect.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeSumPathEffect.java @@ -8,12 +8,17 @@ import org.robolectric.annotation.Implements; import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader; import org.robolectric.nativeruntime.SumPathEffectNatives; import org.robolectric.shadows.ShadowNativeSumPathEffect.Picker; +import org.robolectric.versioning.AndroidVersions.U; /** Shadow for {@link SumPathEffect} that is backed by native code */ -@Implements(value = SumPathEffect.class, minSdk = O, shadowPicker = Picker.class) +@Implements( + value = SumPathEffect.class, + minSdk = O, + shadowPicker = Picker.class, + callNativeMethodsByDefault = true) public class ShadowNativeSumPathEffect { - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static long nativeCreate(long first, long second) { DefaultNativeRuntimeLoader.injectAndLoad(); return SumPathEffectNatives.nativeCreate(first, second); diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeSurface.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeSurface.java index 97556fcff..3ccae4a45 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeSurface.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeSurface.java @@ -2,6 +2,7 @@ package org.robolectric.shadows; import static android.os.Build.VERSION_CODES.O; import static android.os.Build.VERSION_CODES.O_MR1; +import static android.os.Build.VERSION_CODES.P; import static android.os.Build.VERSION_CODES.Q; import static android.os.Build.VERSION_CODES.S; @@ -17,31 +18,42 @@ import org.robolectric.annotation.Implements; import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader; import org.robolectric.nativeruntime.SurfaceNatives; import org.robolectric.shadows.ShadowNativeSurface.Picker; +import org.robolectric.versioning.AndroidVersions.U; /** Shadow for {@link Surface} that is backed by native code */ -@Implements(value = Surface.class, minSdk = O, shadowPicker = Picker.class, isInAndroidSdk = false) +@Implements( + value = Surface.class, + minSdk = O, + shadowPicker = Picker.class, + isInAndroidSdk = false, + callNativeMethodsByDefault = true) public class ShadowNativeSurface { - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static long nativeCreateFromSurfaceTexture(SurfaceTexture surfaceTexture) throws OutOfResourcesException { DefaultNativeRuntimeLoader.injectAndLoad(); return SurfaceNatives.nativeCreateFromSurfaceTexture(surfaceTexture); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static long nativeCreateFromSurfaceControl(long surfaceControlNativeObject) { DefaultNativeRuntimeLoader.injectAndLoad(); return SurfaceNatives.nativeCreateFromSurfaceControl(surfaceControlNativeObject); } - @Implementation(minSdk = Q) + @Implementation(minSdk = Q, maxSdk = U.SDK_INT) protected static long nativeGetFromSurfaceControl( long surfaceObject, long surfaceControlNativeObject) { DefaultNativeRuntimeLoader.injectAndLoad(); return SurfaceNatives.nativeGetFromSurfaceControl(surfaceObject, surfaceControlNativeObject); } - @Implementation(minSdk = S) + @Implementation(minSdk = P, maxSdk = P) + protected static long nativeGetFromSurfaceControl(long surfaceControlNativeObject) { + return nativeGetFromSurfaceControl(0, surfaceControlNativeObject); + } + + @Implementation(minSdk = S, maxSdk = U.SDK_INT) protected static long nativeGetFromBlastBufferQueue( long surfaceObject, long blastBufferQueueNativeObject) { return SurfaceNatives.nativeGetFromBlastBufferQueue( @@ -56,84 +68,84 @@ public class ShadowNativeSurface { throw new UnsupportedOperationException("Not implemented yet"); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nativeUnlockCanvasAndPost(long nativeObject, Canvas canvas) { SurfaceNatives.nativeUnlockCanvasAndPost(nativeObject, canvas); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nativeRelease(long nativeObject) { SurfaceNatives.nativeRelease(nativeObject); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static boolean nativeIsValid(long nativeObject) { return SurfaceNatives.nativeIsValid(nativeObject); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static boolean nativeIsConsumerRunningBehind(long nativeObject) { return SurfaceNatives.nativeIsConsumerRunningBehind(nativeObject); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static long nativeReadFromParcel(long nativeObject, Parcel source) { return SurfaceNatives.nativeReadFromParcel(nativeObject, source); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nativeWriteToParcel(long nativeObject, Parcel dest) { SurfaceNatives.nativeWriteToParcel(nativeObject, dest); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static void nativeAllocateBuffers(long nativeObject) { SurfaceNatives.nativeAllocateBuffers(nativeObject); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static int nativeGetWidth(long nativeObject) { return SurfaceNatives.nativeGetWidth(nativeObject); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static int nativeGetHeight(long nativeObject) { return SurfaceNatives.nativeGetHeight(nativeObject); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static long nativeGetNextFrameNumber(long nativeObject) { return SurfaceNatives.nativeGetNextFrameNumber(nativeObject); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static int nativeSetScalingMode(long nativeObject, int scalingMode) { return SurfaceNatives.nativeSetScalingMode(nativeObject, scalingMode); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static int nativeForceScopedDisconnect(long nativeObject) { return SurfaceNatives.nativeForceScopedDisconnect(nativeObject); } - @Implementation(minSdk = S) + @Implementation(minSdk = S, maxSdk = U.SDK_INT) protected static int nativeAttachAndQueueBufferWithColorSpace( long nativeObject, HardwareBuffer buffer, int colorSpaceId) { return SurfaceNatives.nativeAttachAndQueueBufferWithColorSpace( nativeObject, buffer, colorSpaceId); } - @Implementation(minSdk = O_MR1) + @Implementation(minSdk = O_MR1, maxSdk = U.SDK_INT) protected static int nativeSetSharedBufferModeEnabled(long nativeObject, boolean enabled) { return SurfaceNatives.nativeSetSharedBufferModeEnabled(nativeObject, enabled); } - @Implementation(minSdk = O_MR1) + @Implementation(minSdk = O_MR1, maxSdk = U.SDK_INT) protected static int nativeSetAutoRefreshEnabled(long nativeObject, boolean enabled) { return SurfaceNatives.nativeSetAutoRefreshEnabled(nativeObject, enabled); } - @Implementation(minSdk = S) + @Implementation(minSdk = S, maxSdk = U.SDK_INT) protected static int nativeSetFrameRate( long nativeObject, float frameRate, int compatibility, int changeFrameRateStrategy) { return SurfaceNatives.nativeSetFrameRate( diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeSweepGradient.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeSweepGradient.java index d51300f1c..158a1a198 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeSweepGradient.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeSweepGradient.java @@ -10,12 +10,17 @@ import org.robolectric.annotation.Implements; import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader; import org.robolectric.nativeruntime.SweepGradientNatives; import org.robolectric.shadows.ShadowNativeSweepGradient.Picker; +import org.robolectric.versioning.AndroidVersions.U; /** Shadow for {@link SweepGradient} that is backed by native code */ -@Implements(value = SweepGradient.class, minSdk = O, shadowPicker = Picker.class) +@Implements( + value = SweepGradient.class, + minSdk = O, + shadowPicker = Picker.class, + callNativeMethodsByDefault = true) public class ShadowNativeSweepGradient { - @Implementation(minSdk = Q) + @Implementation(minSdk = Q, maxSdk = U.SDK_INT) protected static long nativeCreate( long matrix, float x, float y, long[] colors, float[] positions, long colorSpaceHandle) { DefaultNativeRuntimeLoader.injectAndLoad(); diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeSystemFonts.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeSystemFonts.java index f154df3f0..4d53bf268 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeSystemFonts.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeSystemFonts.java @@ -25,6 +25,7 @@ import java.util.ArrayList; import java.util.Map; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; +import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader; import org.robolectric.shadows.ShadowNativeSystemFonts.Picker; import org.robolectric.util.reflector.Direct; import org.robolectric.util.reflector.ForType; @@ -52,7 +53,8 @@ public class ShadowNativeSystemFonts { String fontDir = System.getProperty("robolectric.nativeruntime.fontdir"); Preconditions.checkNotNull(fontDir); Preconditions.checkState(new File(fontDir).isDirectory(), "Missing fonts directory"); - Preconditions.checkState(fontDir.endsWith("/"), "Fonts directory must end with a slash"); + Preconditions.checkState( + fontDir.endsWith(File.separator), "Fonts directory must end with a slash"); return reflector(SystemFontsReflector.class) .getSystemFontConfigInternal( fontDir + "fonts.xml", @@ -71,10 +73,14 @@ public class ShadowNativeSystemFonts { FontCustomizationParser.Result oemCustomization, ArrayMap<String, FontFamily[]> fallbackMap, ArrayList<Font> availableFonts) { + // In Q and R, calling SystemFonts.getAvailableFonts does not automatically result in RNG being + // loaded, so we must ensure it is loaded for `robolectric.nativeruntime.fontdir` to be defined. + DefaultNativeRuntimeLoader.injectAndLoad(); String fontDir = System.getProperty("robolectric.nativeruntime.fontdir"); Preconditions.checkNotNull(fontDir); Preconditions.checkState(new File(fontDir).isDirectory(), "Missing fonts directory"); - Preconditions.checkState(fontDir.endsWith("/"), "Fonts directory must end with a slash"); + Preconditions.checkState( + fontDir.endsWith(File.separator), "Fonts directory must end with a slash"); return reflector(SystemFontsReflector.class) .buildSystemFallback( fontDir + "fonts.xml", fontDir, oemCustomization, fallbackMap, availableFonts); diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeTableMaskFilter.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeTableMaskFilter.java index 7d7c0a34c..78b297557 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeTableMaskFilter.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeTableMaskFilter.java @@ -8,28 +8,30 @@ import org.robolectric.annotation.Implements; import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader; import org.robolectric.nativeruntime.TableMaskFilterNatives; import org.robolectric.shadows.ShadowNativeTableMaskFilter.Picker; +import org.robolectric.versioning.AndroidVersions.U; /** Shadow for {@link TableMaskFilter} that is backed by native code */ @Implements( value = TableMaskFilter.class, minSdk = O, shadowPicker = Picker.class, - isInAndroidSdk = false) + isInAndroidSdk = false, + callNativeMethodsByDefault = true) public class ShadowNativeTableMaskFilter { - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static long nativeNewTable(byte[] table) { DefaultNativeRuntimeLoader.injectAndLoad(); return TableMaskFilterNatives.nativeNewTable(table); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static long nativeNewClip(int min, int max) { DefaultNativeRuntimeLoader.injectAndLoad(); return TableMaskFilterNatives.nativeNewClip(min, max); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static long nativeNewGamma(float gamma) { DefaultNativeRuntimeLoader.injectAndLoad(); return TableMaskFilterNatives.nativeNewGamma(gamma); diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeTextRunShaper.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeTextRunShaper.java index aaa8cb059..f6aed6be2 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeTextRunShaper.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeTextRunShaper.java @@ -7,12 +7,17 @@ import org.robolectric.annotation.Implements; import org.robolectric.nativeruntime.TextRunShaperNatives; import org.robolectric.shadows.ShadowNativeTextRunShaper.Picker; import org.robolectric.versioning.AndroidVersions.S; +import org.robolectric.versioning.AndroidVersions.U; /** Shadow for {@link TextRunShaper} that is backed by native code */ -@Implements(value = TextRunShaper.class, minSdk = S.SDK_INT, shadowPicker = Picker.class) +@Implements( + value = TextRunShaper.class, + minSdk = S.SDK_INT, + shadowPicker = Picker.class, + callNativeMethodsByDefault = true) public class ShadowNativeTextRunShaper { - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static long nativeShapeTextRun( char[] text, int start, @@ -25,7 +30,7 @@ public class ShadowNativeTextRunShaper { text, start, count, contextStart, contextCount, isRtl, nativePaint); } - @Implementation + @Implementation(maxSdk = U.SDK_INT) protected static long nativeShapeTextRun( String text, int start, diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeTypeface.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeTypeface.java index a0b5d2760..897ea85d8 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeTypeface.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeTypeface.java @@ -25,6 +25,7 @@ import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.util.List; import java.util.Map; +import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader; @@ -36,7 +37,12 @@ import org.robolectric.util.reflector.Static; import org.robolectric.versioning.AndroidVersions.U; /** Shadow for {@link Typeface} that is backed by native code */ -@Implements(value = Typeface.class, looseSignatures = true, minSdk = O, isInAndroidSdk = false) +@Implements( + value = Typeface.class, + looseSignatures = true, + minSdk = O, + isInAndroidSdk = false, + callNativeMethodsByDefault = true) public class ShadowNativeTypeface extends ShadowTypeface { private static final String TAG = "ShadowNativeTypeface"; @@ -45,12 +51,17 @@ public class ShadowNativeTypeface extends ShadowTypeface { private static final int STYLE_NORMAL = 0; private static final int STYLE_ITALIC = 1; + @Implementation(minSdk = S) protected static void __staticInitializer__() { - Shadow.directInitialize(Typeface.class); - // Initialize the system font map. In real Android this is done as part of Application startup - // and uses a more complex SharedMemory system not supported in Robolectric. - Typeface.loadPreinstalledSystemFontMap(); + if (RuntimeEnvironment.getApiLevel() <= U.SDK_INT) { + Shadow.directInitialize(Typeface.class); + // Initialize the system font map. In real Android this is done as part of Application startup + // and uses a more complex SharedMemory system not supported in Robolectric. + Typeface.loadPreinstalledSystemFontMap(); + } + // The Typeface static initializer invokes its own native methods. This has to be deferred + // starting in Android V. } @Implementation(minSdk = P, maxSdk = P) @@ -62,7 +73,8 @@ public class ShadowNativeTypeface extends ShadowTypeface { String fontDir = System.getProperty("robolectric.nativeruntime.fontdir"); Preconditions.checkNotNull(fontDir); Preconditions.checkState(new File(fontDir).isDirectory(), "Missing fonts directory"); - Preconditions.checkState(fontDir.endsWith("/"), "Fonts directory must end with a slash"); + Preconditions.checkState( + fontDir.endsWith(File.separator), "Fonts directory must end with a slash"); reflector(TypefaceReflector.class) .buildSystemFallback(fontDir + "fonts.xml", fontDir, fontMap, fallbackMap); } @@ -75,7 +87,8 @@ public class ShadowNativeTypeface extends ShadowTypeface { String fontDir = System.getProperty("robolectric.nativeruntime.fontdir"); Preconditions.checkNotNull(fontDir); Preconditions.checkState(new File(fontDir).isDirectory(), "Missing fonts directory"); - Preconditions.checkState(fontDir.endsWith("/"), "Fonts directory must end with a slash"); + Preconditions.checkState( + fontDir.endsWith(File.separator), "Fonts directory must end with a slash"); return new File(fontDir); } @@ -124,24 +137,24 @@ public class ShadowNativeTypeface extends ShadowTypeface { return fontFamily; } - @Implementation(minSdk = LOLLIPOP) + @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT) protected static long nativeCreateFromTypeface(long nativeInstance, int style) { return TypefaceNatives.nativeCreateFromTypeface(nativeInstance, style); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static long nativeCreateFromTypefaceWithExactStyle( long nativeInstance, int weight, boolean italic) { return TypefaceNatives.nativeCreateFromTypefaceWithExactStyle(nativeInstance, weight, italic); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static long nativeCreateFromTypefaceWithVariation( long nativeInstance, List<FontVariationAxis> axes) { return TypefaceNatives.nativeCreateFromTypefaceWithVariation(nativeInstance, axes); } - @Implementation(minSdk = LOLLIPOP) + @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT) protected static long nativeCreateWeightAlias(long nativeInstance, int weight) { return TypefaceNatives.nativeCreateWeightAlias(nativeInstance, weight); } @@ -151,33 +164,33 @@ public class ShadowNativeTypeface extends ShadowTypeface { return TypefaceNatives.nativeCreateFromArray(familyArray, 0, weight, italic); } - @Implementation(minSdk = S) + @Implementation(minSdk = S, maxSdk = U.SDK_INT) protected static long nativeCreateFromArray( long[] familyArray, long fallbackTypeface, int weight, int italic) { return TypefaceNatives.nativeCreateFromArray(familyArray, fallbackTypeface, weight, italic); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static int[] nativeGetSupportedAxes(long nativeInstance) { return TypefaceNatives.nativeGetSupportedAxes(nativeInstance); } - @Implementation(minSdk = LOLLIPOP) + @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT) protected static void nativeSetDefault(long nativePtr) { TypefaceNatives.nativeSetDefault(nativePtr); } - @Implementation(minSdk = LOLLIPOP) + @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT) protected static int nativeGetStyle(long nativePtr) { return TypefaceNatives.nativeGetStyle(nativePtr); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static int nativeGetWeight(long nativePtr) { return TypefaceNatives.nativeGetWeight(nativePtr); } - @Implementation(minSdk = P) + @Implementation(minSdk = P, maxSdk = U.SDK_INT) protected static long nativeGetReleaseFunc() { DefaultNativeRuntimeLoader.injectAndLoad(); return TypefaceNatives.nativeGetReleaseFunc(); @@ -193,7 +206,7 @@ public class ShadowNativeTypeface extends ShadowTypeface { return TypefaceNatives.nativeGetFamily(nativePtr, index); } - @Implementation(minSdk = Q) + @Implementation(minSdk = Q, maxSdk = U.SDK_INT) protected static void nativeRegisterGenericFamily(String str, long nativePtr) { TypefaceNatives.nativeRegisterGenericFamily(str, nativePtr); } @@ -203,7 +216,7 @@ public class ShadowNativeTypeface extends ShadowTypeface { return TypefaceNatives.nativeWriteTypefaces(buffer, nativePtrs); } - @Implementation(minSdk = U.SDK_INT) + @Implementation(minSdk = U.SDK_INT, maxSdk = U.SDK_INT) protected static int nativeWriteTypefaces(ByteBuffer buffer, int position, long[] nativePtrs) { return nativeWriteTypefaces(buffer, nativePtrs); } @@ -213,21 +226,26 @@ public class ShadowNativeTypeface extends ShadowTypeface { return TypefaceNatives.nativeReadTypefaces(buffer); } - @Implementation(minSdk = U.SDK_INT) + @Implementation(minSdk = U.SDK_INT, maxSdk = U.SDK_INT) protected static long[] nativeReadTypefaces(ByteBuffer buffer, int position) { return nativeReadTypefaces(buffer); } - @Implementation(minSdk = S) + @Implementation(minSdk = S, maxSdk = U.SDK_INT) protected static void nativeForceSetStaticFinalField(String fieldName, Typeface typeface) { TypefaceNatives.nativeForceSetStaticFinalField(fieldName, typeface); } - @Implementation(minSdk = S) + @Implementation(minSdk = S, maxSdk = U.SDK_INT) protected static void nativeAddFontCollections(long nativePtr) { TypefaceNatives.nativeAddFontCollections(nativePtr); } + @Implementation(minSdk = U.SDK_INT, maxSdk = U.SDK_INT) + protected static void nativeRegisterLocaleList(String locales) { + // no-op + } + static void ensureInitialized() { try { // Forces static initialization. This should be called before any native code that calls diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeVectorDrawable.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeVectorDrawable.java index 88a4a76d5..877d7589d 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeVectorDrawable.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeVectorDrawable.java @@ -10,16 +10,18 @@ import org.robolectric.annotation.Implements; import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader; import org.robolectric.nativeruntime.VectorDrawableNatives; import org.robolectric.shadows.ShadowNativeVectorDrawable.Picker; +import org.robolectric.versioning.AndroidVersions.U; /** Shadow for {@link VectorDrawable} that is backed by native code */ @Implements( value = VectorDrawable.class, minSdk = O, shadowPicker = Picker.class, - isInAndroidSdk = false) + isInAndroidSdk = false, + callNativeMethodsByDefault = true) public class ShadowNativeVectorDrawable extends ShadowDrawable { - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static int nDraw( long rendererPtr, long canvasWrapperPtr, @@ -31,73 +33,73 @@ public class ShadowNativeVectorDrawable extends ShadowDrawable { rendererPtr, canvasWrapperPtr, colorFilterPtr, bounds, needsMirroring, canReuseCache); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static boolean nGetFullPathProperties(long pathPtr, byte[] properties, int length) { return VectorDrawableNatives.nGetFullPathProperties(pathPtr, properties, length); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nSetName(long nodePtr, String name) { VectorDrawableNatives.nSetName(nodePtr, name); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static boolean nGetGroupProperties(long groupPtr, float[] properties, int length) { return VectorDrawableNatives.nGetGroupProperties(groupPtr, properties, length); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nSetPathString(long pathPtr, String pathString, int length) { VectorDrawableNatives.nSetPathString(pathPtr, pathString, length); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static long nCreateTree(long rootGroupPtr) { return VectorDrawableNatives.nCreateTree(rootGroupPtr); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static long nCreateTreeFromCopy(long treeToCopy, long rootGroupPtr) { return VectorDrawableNatives.nCreateTreeFromCopy(treeToCopy, rootGroupPtr); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nSetRendererViewportSize( long rendererPtr, float viewportWidth, float viewportHeight) { VectorDrawableNatives.nSetRendererViewportSize(rendererPtr, viewportWidth, viewportHeight); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static boolean nSetRootAlpha(long rendererPtr, float alpha) { return VectorDrawableNatives.nSetRootAlpha(rendererPtr, alpha); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static float nGetRootAlpha(long rendererPtr) { return VectorDrawableNatives.nGetRootAlpha(rendererPtr); } - @Implementation(minSdk = Q) + @Implementation(minSdk = Q, maxSdk = U.SDK_INT) protected static void nSetAntiAlias(long rendererPtr, boolean aa) { VectorDrawableNatives.nSetAntiAlias(rendererPtr, aa); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nSetAllowCaching(long rendererPtr, boolean allowCaching) { VectorDrawableNatives.nSetAllowCaching(rendererPtr, allowCaching); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static long nCreateFullPath() { return VectorDrawableNatives.nCreateFullPath(); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static long nCreateFullPath(long nativeFullPathPtr) { return VectorDrawableNatives.nCreateFullPath(nativeFullPathPtr); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nUpdateFullPathProperties( long pathPtr, float strokeWidth, @@ -128,39 +130,39 @@ public class ShadowNativeVectorDrawable extends ShadowDrawable { fillType); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nUpdateFullPathFillGradient(long pathPtr, long fillGradientPtr) { VectorDrawableNatives.nUpdateFullPathFillGradient(pathPtr, fillGradientPtr); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nUpdateFullPathStrokeGradient(long pathPtr, long strokeGradientPtr) { VectorDrawableNatives.nUpdateFullPathStrokeGradient(pathPtr, strokeGradientPtr); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static long nCreateClipPath() { return VectorDrawableNatives.nCreateClipPath(); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static long nCreateClipPath(long clipPathPtr) { return VectorDrawableNatives.nCreateClipPath(clipPathPtr); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static long nCreateGroup() { DefaultNativeRuntimeLoader.injectAndLoad(); return VectorDrawableNatives.nCreateGroup(); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static long nCreateGroup(long groupPtr) { DefaultNativeRuntimeLoader.injectAndLoad(); return VectorDrawableNatives.nCreateGroup(groupPtr); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nUpdateGroupProperties( long groupPtr, float rotate, @@ -174,162 +176,162 @@ public class ShadowNativeVectorDrawable extends ShadowDrawable { groupPtr, rotate, pivotX, pivotY, scaleX, scaleY, translateX, translateY); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nAddChild(long groupPtr, long nodePtr) { VectorDrawableNatives.nAddChild(groupPtr, nodePtr); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static float nGetRotation(long groupPtr) { return VectorDrawableNatives.nGetRotation(groupPtr); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nSetRotation(long groupPtr, float rotation) { VectorDrawableNatives.nSetRotation(groupPtr, rotation); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static float nGetPivotX(long groupPtr) { return VectorDrawableNatives.nGetPivotX(groupPtr); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nSetPivotX(long groupPtr, float pivotX) { VectorDrawableNatives.nSetPivotX(groupPtr, pivotX); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static float nGetPivotY(long groupPtr) { return VectorDrawableNatives.nGetPivotY(groupPtr); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nSetPivotY(long groupPtr, float pivotY) { VectorDrawableNatives.nSetPivotY(groupPtr, pivotY); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static float nGetScaleX(long groupPtr) { return VectorDrawableNatives.nGetScaleX(groupPtr); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nSetScaleX(long groupPtr, float scaleX) { VectorDrawableNatives.nSetScaleX(groupPtr, scaleX); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static float nGetScaleY(long groupPtr) { return VectorDrawableNatives.nGetScaleY(groupPtr); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nSetScaleY(long groupPtr, float scaleY) { VectorDrawableNatives.nSetScaleY(groupPtr, scaleY); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static float nGetTranslateX(long groupPtr) { return VectorDrawableNatives.nGetTranslateX(groupPtr); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nSetTranslateX(long groupPtr, float translateX) { VectorDrawableNatives.nSetTranslateX(groupPtr, translateX); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static float nGetTranslateY(long groupPtr) { return VectorDrawableNatives.nGetTranslateY(groupPtr); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nSetTranslateY(long groupPtr, float translateY) { VectorDrawableNatives.nSetTranslateY(groupPtr, translateY); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nSetPathData(long pathPtr, long pathDataPtr) { VectorDrawableNatives.nSetPathData(pathPtr, pathDataPtr); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static float nGetStrokeWidth(long pathPtr) { return VectorDrawableNatives.nGetStrokeWidth(pathPtr); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nSetStrokeWidth(long pathPtr, float width) { VectorDrawableNatives.nSetStrokeWidth(pathPtr, width); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static int nGetStrokeColor(long pathPtr) { return VectorDrawableNatives.nGetStrokeColor(pathPtr); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nSetStrokeColor(long pathPtr, int strokeColor) { VectorDrawableNatives.nSetStrokeColor(pathPtr, strokeColor); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static float nGetStrokeAlpha(long pathPtr) { return VectorDrawableNatives.nGetStrokeAlpha(pathPtr); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nSetStrokeAlpha(long pathPtr, float alpha) { VectorDrawableNatives.nSetStrokeAlpha(pathPtr, alpha); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static int nGetFillColor(long pathPtr) { return VectorDrawableNatives.nGetFillColor(pathPtr); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nSetFillColor(long pathPtr, int fillColor) { VectorDrawableNatives.nSetFillColor(pathPtr, fillColor); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static float nGetFillAlpha(long pathPtr) { return VectorDrawableNatives.nGetFillAlpha(pathPtr); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nSetFillAlpha(long pathPtr, float fillAlpha) { VectorDrawableNatives.nSetFillAlpha(pathPtr, fillAlpha); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static float nGetTrimPathStart(long pathPtr) { return VectorDrawableNatives.nGetTrimPathStart(pathPtr); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nSetTrimPathStart(long pathPtr, float trimPathStart) { VectorDrawableNatives.nSetTrimPathStart(pathPtr, trimPathStart); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static float nGetTrimPathEnd(long pathPtr) { return VectorDrawableNatives.nGetTrimPathEnd(pathPtr); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nSetTrimPathEnd(long pathPtr, float trimPathEnd) { VectorDrawableNatives.nSetTrimPathEnd(pathPtr, trimPathEnd); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static float nGetTrimPathOffset(long pathPtr) { return VectorDrawableNatives.nGetTrimPathOffset(pathPtr); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nSetTrimPathOffset(long pathPtr, float trimPathOffset) { VectorDrawableNatives.nSetTrimPathOffset(pathPtr, trimPathOffset); } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeVirtualRefBasePtr.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeVirtualRefBasePtr.java index 8343912c5..e4c4640b5 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeVirtualRefBasePtr.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeVirtualRefBasePtr.java @@ -7,21 +7,23 @@ import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; import org.robolectric.nativeruntime.VirtualRefBasePtrNatives; import org.robolectric.shadows.ShadowNativeVirtualRefBasePtr.Picker; +import org.robolectric.versioning.AndroidVersions.U; /** Shadow for {@link VirtualRefBasePtr} that is backed by native code */ @Implements( value = VirtualRefBasePtr.class, minSdk = O, shadowPicker = Picker.class, - isInAndroidSdk = false) + isInAndroidSdk = false, + callNativeMethodsByDefault = true) public class ShadowNativeVirtualRefBasePtr { - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nIncStrong(long ptr) { VirtualRefBasePtrNatives.nIncStrong(ptr); } - @Implementation(minSdk = O) + @Implementation(minSdk = O, maxSdk = U.SDK_INT) protected static void nDecStrong(long ptr) { VirtualRefBasePtrNatives.nDecStrong(ptr); } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNfcAdapter.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNfcAdapter.java index 38137474f..9713efd5d 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNfcAdapter.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNfcAdapter.java @@ -14,6 +14,7 @@ import android.nfc.Tag; import android.os.Build; import android.os.Bundle; import java.util.Map; +import javax.annotation.concurrent.GuardedBy; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; @@ -30,23 +31,67 @@ import org.robolectric.util.reflector.Static; @Implements(NfcAdapter.class) public class ShadowNfcAdapter { @RealObject NfcAdapter nfcAdapter; + + @GuardedBy("ShadowNfcAdapter.class") private static boolean hardwareExists = true; + + @GuardedBy("this") private boolean enabled; + + @GuardedBy("this") + private boolean secureNfcSupported; + + @GuardedBy("this") + private boolean secureNfcEnabled; + + @GuardedBy("this") private Activity enabledActivity; + + @GuardedBy("this") private PendingIntent intent; + + @GuardedBy("this") private IntentFilter[] filters; + + @GuardedBy("this") private String[][] techLists; + + @GuardedBy("this") private Activity disabledActivity; + + @GuardedBy("this") private NdefMessage ndefPushMessage; + + @GuardedBy("this") private boolean ndefPushMessageSet; + + @GuardedBy("this") private NfcAdapter.CreateNdefMessageCallback ndefPushMessageCallback; + + @GuardedBy("this") private NfcAdapter.OnNdefPushCompleteCallback onNdefPushCompleteCallback; + + @GuardedBy("this") private NfcAdapter.ReaderCallback readerCallback; @Implementation + protected static NfcAdapter getDefaultAdapter(Context context) { + // The result of `getNfcAdapter` is cached, so need to check `hardwareExists` again here in case + // its value was set after the value returned by `getNfcAdapter` got cached. + synchronized (ShadowNfcAdapter.class) { + if (!hardwareExists) { + return null; + } + } + return reflector(NfcAdapterReflector.class).getDefaultAdapter(context); + } + + @Implementation protected static NfcAdapter getNfcAdapter(Context context) { - if (!hardwareExists) { - return null; + synchronized (ShadowNfcAdapter.class) { + if (!hardwareExists) { + return null; + } } return reflector(NfcAdapterReflector.class).getNfcAdapter(context); } @@ -75,18 +120,22 @@ public class ShadowNfcAdapter { @Implementation protected void enableForegroundDispatch( Activity activity, PendingIntent intent, IntentFilter[] filters, String[][] techLists) { - this.enabledActivity = activity; - this.intent = intent; - this.filters = filters; - this.techLists = techLists; + synchronized (this) { + this.enabledActivity = activity; + this.intent = intent; + this.filters = filters; + this.techLists = techLists; + } } @Implementation protected void disableForegroundDispatch(Activity activity) { - disabledActivity = activity; + synchronized (this) { + disabledActivity = activity; + } } - @Implementation(minSdk = Build.VERSION_CODES.KITKAT) + @Implementation protected void enableReaderMode( Activity activity, NfcAdapter.ReaderCallback callback, int flags, Bundle extras) { if (!RuntimeEnvironment.getApplication() @@ -97,28 +146,37 @@ public class ShadowNfcAdapter { if (callback == null) { throw new NullPointerException("ReaderCallback is null"); } - readerCallback = callback; + + synchronized (this) { + readerCallback = callback; + } } - @Implementation(minSdk = Build.VERSION_CODES.KITKAT) + @Implementation protected void disableReaderMode(Activity activity) { if (!RuntimeEnvironment.getApplication() .getPackageManager() .hasSystemFeature(PackageManager.FEATURE_NFC)) { throw new UnsupportedOperationException(); } - readerCallback = null; + synchronized (this) { + readerCallback = null; + } } /** Returns true if NFC is in reader mode. */ - public boolean isInReaderMode() { + public synchronized boolean isInReaderMode() { return readerCallback != null; } /** Dispatches the tag onto any registered readers. */ public void dispatchTagDiscovered(Tag tag) { - if (readerCallback != null) { - readerCallback.onTagDiscovered(tag); + NfcAdapter.ReaderCallback callback; + synchronized (this) { + callback = readerCallback; + } + if (callback != null) { + callback.onTagDiscovered(tag); } } @@ -137,14 +195,18 @@ public class ShadowNfcAdapter { throw new NullPointerException("activities cannot contain null"); } } - this.ndefPushMessage = message; - this.ndefPushMessageSet = true; + synchronized (this) { + this.ndefPushMessage = message; + this.ndefPushMessageSet = true; + } } @Implementation protected void setNdefPushMessageCallback( NfcAdapter.CreateNdefMessageCallback callback, Activity activity, Activity... activities) { - this.ndefPushMessageCallback = callback; + synchronized (this) { + this.ndefPushMessageCallback = callback; + } } /** @@ -164,23 +226,53 @@ public class ShadowNfcAdapter { throw new NullPointerException("activities cannot contain null"); } } - this.onNdefPushCompleteCallback = callback; + synchronized (this) { + this.onNdefPushCompleteCallback = callback; + } } @Implementation protected boolean isEnabled() { - return enabled; + synchronized (this) { + return enabled; + } } @Implementation protected boolean enable() { - enabled = true; + synchronized (this) { + enabled = true; + } return true; } @Implementation protected boolean disable() { - enabled = false; + synchronized (this) { + enabled = false; + } + return true; + } + + @Implementation(minSdk = Build.VERSION_CODES.Q) + protected boolean isSecureNfcSupported() { + synchronized (this) { + return secureNfcSupported; + } + } + + @Implementation(minSdk = Build.VERSION_CODES.Q) + protected boolean isSecureNfcEnabled() { + synchronized (this) { + return secureNfcEnabled; + } + } + + @Implementation(minSdk = Build.VERSION_CODES.Q) + protected boolean enableSecureNfc(boolean enableSecureNfc) { + synchronized (this) { + this.secureNfcEnabled = enableSecureNfc; + } return true; } @@ -188,45 +280,49 @@ public class ShadowNfcAdapter { * Modifies the behavior of {@link #getNfcAdapter(Context)} to return {@code null}, to simulate * absence of NFC hardware. */ - public static void setNfcHardwareExists(boolean hardwareExists) { + public static synchronized void setNfcHardwareExists(boolean hardwareExists) { ShadowNfcAdapter.hardwareExists = hardwareExists; } - public void setEnabled(boolean enabled) { + public synchronized void setEnabled(boolean enabled) { this.enabled = enabled; } - public Activity getEnabledActivity() { + public synchronized void setSecureNfcSupported(boolean secureNfcSupported) { + this.secureNfcSupported = secureNfcSupported; + } + + public synchronized Activity getEnabledActivity() { return enabledActivity; } - public PendingIntent getIntent() { + public synchronized PendingIntent getIntent() { return intent; } - public IntentFilter[] getFilters() { + public synchronized IntentFilter[] getFilters() { return filters; } - public String[][] getTechLists() { + public synchronized String[][] getTechLists() { return techLists; } - public Activity getDisabledActivity() { + public synchronized Activity getDisabledActivity() { return disabledActivity; } /** Returns last registered callback, or {@code null} if none was set. */ - public NfcAdapter.CreateNdefMessageCallback getNdefPushMessageCallback() { + public synchronized NfcAdapter.CreateNdefMessageCallback getNdefPushMessageCallback() { return ndefPushMessageCallback; } - public NfcAdapter.OnNdefPushCompleteCallback getOnNdefPushCompleteCallback() { + public synchronized NfcAdapter.OnNdefPushCompleteCallback getOnNdefPushCompleteCallback() { return onNdefPushCompleteCallback; } /** Returns last set NDEF message, or throws {@code IllegalStateException} if it was never set. */ - public NdefMessage getNdefPushMessage() { + public synchronized NdefMessage getNdefPushMessage() { if (!ndefPushMessageSet) { throw new IllegalStateException(); } @@ -271,6 +367,10 @@ public class ShadowNfcAdapter { @Direct @Static NfcAdapter getNfcAdapter(Context context); + + @Direct + @Static + NfcAdapter getDefaultAdapter(Context context); } @ForType(Tag.class) diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNotification.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNotification.java index 164f5bd3a..ea08d71f1 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNotification.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNotification.java @@ -14,8 +14,10 @@ import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.ProgressBar; import android.widget.TextView; +import com.google.common.collect.ImmutableList; import java.io.ByteArrayOutputStream; import java.io.PrintStream; +import java.util.List; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Implements; import org.robolectric.annotation.RealObject; @@ -76,6 +78,15 @@ public class ShadowNotification { } } + public List<CharSequence> getTextLines() { + CharSequence[] linesArray = + realNotification.extras.getCharSequenceArray(Notification.EXTRA_TEXT_LINES); + if (linesArray == null) { + return ImmutableList.of(); + } + return ImmutableList.copyOf(linesArray); + } + public Bitmap getBigPicture() { if (RuntimeEnvironment.getApiLevel() >= Build.VERSION_CODES.N) { return realNotification.extras.getParcelable(Notification.EXTRA_PICTURE); diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNotificationManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNotificationManager.java index ef0f0542a..8a566a680 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNotificationManager.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNotificationManager.java @@ -8,6 +8,7 @@ import static android.os.Build.VERSION_CODES.P; import static android.os.Build.VERSION_CODES.Q; import static android.os.Build.VERSION_CODES.R; +import android.annotation.NonNull; import android.app.AutomaticZenRule; import android.app.Notification; import android.app.NotificationChannel; @@ -17,7 +18,6 @@ import android.content.ComponentName; import android.os.Build; import android.os.Parcel; import android.service.notification.StatusBarNotification; -import androidx.annotation.NonNull; import com.google.common.base.Preconditions; import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPackageManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPackageManager.java index 3f493e3c9..f753496c7 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPackageManager.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPackageManager.java @@ -27,8 +27,6 @@ import static android.content.pm.PackageManager.SIGNATURE_NEITHER_SIGNED; import static android.content.pm.PackageManager.SIGNATURE_NO_MATCH; import static android.content.pm.PackageManager.SIGNATURE_SECOND_NOT_SIGNED; import static android.content.pm.PackageManager.VERIFICATION_ALLOW; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; -import static android.os.Build.VERSION_CODES.KITKAT; import static android.os.Build.VERSION_CODES.N; import static android.os.Build.VERSION_CODES.TIRAMISU; import static java.util.Arrays.asList; @@ -70,7 +68,6 @@ import android.content.res.Resources; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Binder; -import android.os.Build; import android.os.PersistableBundle; import android.os.Process; import android.os.RemoteException; @@ -642,11 +639,7 @@ public class ShadowPackageManager { public void addResolveInfoForIntent(Intent intent, ResolveInfo info) { info.isDefault = true; ComponentInfo[] componentInfos = - new ComponentInfo[] { - info.activityInfo, - info.serviceInfo, - Build.VERSION.SDK_INT >= KITKAT ? info.providerInfo : null - }; + new ComponentInfo[] {info.activityInfo, info.serviceInfo, info.providerInfo}; for (ComponentInfo component : componentInfos) { if (component != null && component.applicationInfo != null) { component.applicationInfo.flags |= ApplicationInfo.FLAG_INSTALLED; @@ -1102,7 +1095,7 @@ public class ShadowPackageManager { return null; } - @Implementation(minSdk = JELLY_BEAN_MR1) + @Implementation protected List<ResolveInfo> queryBroadcastReceivers( Intent intent, int flags, @UserIdInt int userId) { return null; diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPackageParser.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPackageParser.java index a37fbf9f5..0f31e379c 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPackageParser.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPackageParser.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.JELLY_BEAN; import static android.os.Build.VERSION_CODES.LOLLIPOP; import static android.os.Build.VERSION_CODES.LOLLIPOP_MR1; import static org.robolectric.util.reflector.Reflector.reflector; @@ -100,16 +99,6 @@ public class ShadowPackageParser { @ForType(PackageParser.class) interface _PackageParser_ { - // <= JELLY_BEAN - @Static - PackageInfo generatePackageInfo( - PackageParser.Package p, - int[] gids, - int flags, - long firstInstallTime, - long lastUpdateTime, - HashSet<String> grantedPermissions); - // <= LOLLIPOP @Static PackageInfo generatePackageInfo( @@ -152,10 +141,7 @@ public class ShadowPackageParser { long lastUpdateTime) { int apiLevel = RuntimeEnvironment.getApiLevel(); - if (apiLevel <= JELLY_BEAN) { - return generatePackageInfo(p, gids, flags, firstInstallTime, lastUpdateTime, - new HashSet<>()); - } else if (apiLevel <= LOLLIPOP) { + if (apiLevel <= LOLLIPOP) { return generatePackageInfo( p, gids, diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPaint.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPaint.java index e5bbc8287..8eececdd7 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPaint.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPaint.java @@ -1,7 +1,6 @@ package org.robolectric.shadows; import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2; import static android.os.Build.VERSION_CODES.KITKAT; import static android.os.Build.VERSION_CODES.KITKAT_WATCH; import static android.os.Build.VERSION_CODES.L; @@ -20,7 +19,6 @@ import android.graphics.ColorFilter; import android.graphics.Paint; import android.graphics.Paint.FontMetricsInt; import android.graphics.PathEffect; -import android.graphics.RectF; import android.graphics.Shader; import android.graphics.Typeface; import org.robolectric.annotation.Implementation; @@ -396,7 +394,7 @@ public class ShadowPaint { return breakText(text, maxWidth, measuredWidth); } - @Implementation(minSdk = JELLY_BEAN_MR2, maxSdk = KITKAT_WATCH) + @Implementation(maxSdk = KITKAT_WATCH) protected int native_breakText( char[] text, int index, int count, float maxWidth, int bidiFlags, float[] measuredWidth) { return breakText(text, maxWidth, measuredWidth); @@ -453,7 +451,7 @@ public class ShadowPaint { return breakText(text, maxWidth, measuredWidth); } - @Implementation(minSdk = JELLY_BEAN_MR2, maxSdk = KITKAT_WATCH) + @Implementation(maxSdk = KITKAT_WATCH) protected int native_breakText( String text, boolean measureForwards, float maxWidth, int bidiFlags, float[] measuredWidth) { return breakText(text, maxWidth, measuredWidth); @@ -661,7 +659,7 @@ public class ShadowPaint { return nGetRunAdvance(0, text.toCharArray(), start, end, contextStart, contextEnd, isRtl, 0); } - @Implementation(minSdk = JELLY_BEAN_MR2, maxSdk = KITKAT) + @Implementation(maxSdk = KITKAT) protected static float native_getTextRunAdvances( int nativeObject, char[] text, @@ -676,7 +674,7 @@ public class ShadowPaint { 0, text, index, index + count, contextIndex, contextIndex + contextCount, false, index); } - @Implementation(minSdk = JELLY_BEAN_MR2, maxSdk = KITKAT) + @Implementation(maxSdk = KITKAT) protected static float native_getTextRunAdvances( int nativeObject, String text, diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPaintDrawable.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPaintDrawable.java new file mode 100644 index 000000000..de8e894ec --- /dev/null +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPaintDrawable.java @@ -0,0 +1,48 @@ +package org.robolectric.shadows; + +import static org.robolectric.util.reflector.Reflector.reflector; + +import android.graphics.drawable.PaintDrawable; +import android.graphics.drawable.ShapeDrawable; +import android.graphics.drawable.shapes.RoundRectShape; +import android.graphics.drawable.shapes.Shape; +import org.robolectric.annotation.Implements; +import org.robolectric.annotation.RealObject; +import org.robolectric.util.reflector.Accessor; +import org.robolectric.util.reflector.ForType; + +/** Shadow for {@link PaintDrawable}. */ +@Implements(PaintDrawable.class) +public class ShadowPaintDrawable extends ShadowDrawable { + + @RealObject PaintDrawable realPaintDrawable; + + /** Gets the corder radii */ + public float[] getCornerRadii() { + Object shapeState = reflector(ShapeDrawableReflector.class, realPaintDrawable).getShapeState(); + Shape shape = reflector(ShapeStateReflector.class, shapeState).getShape(); + if (!(shape instanceof RoundRectShape)) { + return null; + } + RoundRectShape roundRectShape = (RoundRectShape) shape; + return reflector(RoundRectShapeReflector.class, roundRectShape).getOuterRadii(); + } + + @ForType(ShapeDrawable.class) + interface ShapeDrawableReflector { + @Accessor("mShapeState") + Object getShapeState(); + } + + @ForType(className = "android.graphics.drawable.ShapeDrawable$ShapeState") + interface ShapeStateReflector { + @Accessor("mShape") + Shape getShape(); + } + + @ForType(RoundRectShape.class) + interface RoundRectShapeReflector { + @Accessor("mOuterRadii") + float[] getOuterRadii(); + } +} diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowParcel.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowParcel.java index 10fc4081d..a27e5038e 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowParcel.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowParcel.java @@ -1,7 +1,6 @@ package org.robolectric.shadows; import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2; import static android.os.Build.VERSION_CODES.KITKAT_WATCH; import static android.os.Build.VERSION_CODES.LOLLIPOP; import static android.os.Build.VERSION_CODES.M; @@ -83,7 +82,7 @@ public class ShadowParcel { } @HiddenApi - @Implementation(minSdk = JELLY_BEAN_MR2) + @Implementation public Parcelable.Creator<?> readParcelableCreator(ClassLoader loader) { // note: calling `readString` will also consume the string, and increment the data-pointer. // which is exactly what we need, since we do not call the real `readParcelableCreator`. diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowParcelFileDescriptor.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowParcelFileDescriptor.java index 0982971fe..08cdae467 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowParcelFileDescriptor.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowParcelFileDescriptor.java @@ -1,7 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.JELLY_BEAN; -import static android.os.Build.VERSION_CODES.KITKAT; import static android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE; import static org.robolectric.shadow.api.Shadow.invokeConstructor; import static org.robolectric.util.ReflectionHelpers.ClassParameter.from; @@ -12,11 +10,16 @@ import android.os.Handler; import android.os.Parcel; import android.os.ParcelFileDescriptor; import android.os.Parcelable; +import android.system.Os; +import com.google.common.io.ByteStreams; import java.io.File; import java.io.FileDescriptor; +import java.io.FileInputStream; import java.io.FileNotFoundException; +import java.io.FileOutputStream; import java.io.IOException; import java.io.RandomAccessFile; +import java.nio.channels.FileChannel; import java.nio.file.Files; import java.util.Collections; import java.util.HashMap; @@ -30,7 +33,6 @@ import org.robolectric.annotation.RealObject; import org.robolectric.annotation.Resetter; import org.robolectric.shadow.api.Shadow; import org.robolectric.util.ReflectionHelpers; -import org.robolectric.util.ReflectionHelpers.ClassParameter; import org.robolectric.util.reflector.Direct; import org.robolectric.util.reflector.ForType; @@ -111,14 +113,7 @@ public class ShadowParcelFileDescriptor { } private static ParcelFileDescriptor newParcelFileDescriptor() { - if (RuntimeEnvironment.getApiLevel() > JELLY_BEAN) { - return new ParcelFileDescriptor(new FileDescriptor()); - } else { - // In Jelly Bean, the ParcelFileDescriptor(FileDescriptor) constructor was non-public. - return ReflectionHelpers.callConstructor( - ParcelFileDescriptor.class, - ClassParameter.from(FileDescriptor.class, new FileDescriptor())); - } + return new ParcelFileDescriptor(new FileDescriptor()); } @Implementation @@ -147,7 +142,7 @@ public class ShadowParcelFileDescriptor { return pfd; } - @Implementation(minSdk = KITKAT) + @Implementation protected static ParcelFileDescriptor open( File file, int mode, Handler handler, ParcelFileDescriptor.OnCloseListener listener) throws IOException { @@ -193,7 +188,7 @@ public class ShadowParcelFileDescriptor { return new ParcelFileDescriptor[] {readSide, writeSide}; } - @Implementation(minSdk = KITKAT) + @Implementation protected static ParcelFileDescriptor[] createReliablePipe() throws IOException { return createPipe(); } @@ -212,7 +207,11 @@ public class ShadowParcelFileDescriptor { @Implementation protected FileDescriptor getFileDescriptor() { try { - return getFile().getFD(); + RandomAccessFile file = getFile(); + if (file != null) { + return file.getFD(); + } + return reflector(ParcelFileDescriptorReflector.class, realParcelFd).getFileDescriptor(); } catch (IOException e) { throw new RuntimeException(e); } @@ -275,6 +274,34 @@ public class ShadowParcelFileDescriptor { return new ParcelFileDescriptor(realParcelFd); } + /** + * Support shadowing of the static method {@link ParcelFileDescriptor#dup}. + * + * <p>The real implementation calls {@link Os#fcntlInt} in order to duplicate the FileDescriptor + * in native code. This cannot be simulated on the JVM without the use of native code. + */ + @Implementation + protected static ParcelFileDescriptor dup(FileDescriptor fileDescriptor) throws IOException { + File dupFile = + new File( + RuntimeEnvironment.getTempDirectory().createIfNotExists(PIPE_TMP_DIR).toFile(), + "dupfd-" + UUID.randomUUID()); + + // Duplicate the file represented by the file descriptor. Note that neither file streams should + // be closed because doing so will invalidate the corresponding file descriptor. + FileInputStream fileInputStream = new FileInputStream(fileDescriptor); + FileOutputStream fileOutputStream = new FileOutputStream(dupFile); + FileChannel sourceChannel = fileInputStream.getChannel(); + + long originalPosition = sourceChannel.position(); + + sourceChannel.position(0); + ByteStreams.copy(fileInputStream, fileOutputStream); + sourceChannel.position(originalPosition); + RandomAccessFile randomAccessFile = new RandomAccessFile(dupFile, "rw"); + return new ParcelFileDescriptor(randomAccessFile.getFD()); + } + static class FileDescriptorFromParcelUnavailableException extends RuntimeException { FileDescriptorFromParcelUnavailableException() { super( @@ -291,5 +318,8 @@ public class ShadowParcelFileDescriptor { @Direct void close(); + + @Direct + FileDescriptor getFileDescriptor(); } } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPausedChoreographer.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPausedChoreographer.java index 8d1f04212..efeed94bd 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPausedChoreographer.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPausedChoreographer.java @@ -2,13 +2,22 @@ package org.robolectric.shadows; import static org.robolectric.util.reflector.Reflector.reflector; +import android.os.Looper; import android.view.Choreographer; import android.view.DisplayEventReceiver; -import androidx.annotation.VisibleForTesting; +import com.android.internal.annotations.VisibleForTesting; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArraySet; +import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; import org.robolectric.annotation.LooperMode; -import org.robolectric.annotation.Resetter; +import org.robolectric.annotation.RealObject; +import org.robolectric.shadow.api.Shadow; import org.robolectric.shadows.ShadowDisplayEventReceiver.DisplayEventReceiverReflector; +import org.robolectric.versioning.AndroidVersions.NMR1; +import org.robolectric.versioning.AndroidVersions.O; +import org.robolectric.versioning.AndroidVersions.T; +import org.robolectric.versioning.AndroidVersions.U; /** * A {@link Choreographer} shadow for {@link LooperMode.Mode.PAUSED}. @@ -24,9 +33,51 @@ import org.robolectric.shadows.ShadowDisplayEventReceiver.DisplayEventReceiverRe isInAndroidSdk = false) public class ShadowPausedChoreographer extends ShadowChoreographer { - @Resetter - public static void reset() { - reflector(ChoreographerReflector.class).getThreadInstance().remove(); + // keep track of all Loopers with active Choreographer so they can be selectively reset + private static final Set<Looper> choreographedLoopers = new CopyOnWriteArraySet<>(); + + @RealObject private Choreographer realChoreographer; + + @Implementation(maxSdk = NMR1.SDK_INT) + protected void __constructor__(Looper looper) { + reflector(ChoreographerReflector.class, realChoreographer).__constructor__(looper); + choreographedLoopers.add(looper); + } + + @Implementation(minSdk = O.SDK_INT, maxSdk = T.SDK_INT) + protected void __constructor__(Looper looper, int vsyncSource) { + reflector(ChoreographerReflector.class, realChoreographer).__constructor__(looper, vsyncSource); + choreographedLoopers.add(looper); + } + + @Implementation(minSdk = U.SDK_INT) + protected void __constructor__(Looper looper, int vsyncSource, long layerHandle) { + reflector(ChoreographerReflector.class, realChoreographer) + .__constructor__(looper, vsyncSource, layerHandle); + choreographedLoopers.add(looper); + } + + /** + * Resets the choreographer ThreadLocal instance for the given Looper + * + * @param looper an active looper whose queue has already been reset + */ + static void reset(Looper looper) { + if (choreographedLoopers.remove(looper)) { + if (looper.getThread() == Thread.currentThread()) { + reflector(ChoreographerReflector.class).getThreadInstance().remove(); + } else if (looper.getThread().isAlive()) { + ShadowPausedLooper shadowLooper = Shadow.extract(looper); + shadowLooper.postSyncQuiet( + () -> reflector(ChoreographerReflector.class).getThreadInstance().remove()); + } + } + } + + // safeguard that clears the list of choreographed Loopers. Intended to clean up references + // to Loopers that are no longer running + static void clearLoopers() { + choreographedLoopers.clear(); } /** diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPausedLooper.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPausedLooper.java index 3114b11c5..aa63a6872 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPausedLooper.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPausedLooper.java @@ -6,7 +6,6 @@ import static org.robolectric.util.ReflectionHelpers.ClassParameter.from; import static org.robolectric.util.reflector.Reflector.reflector; import android.app.Instrumentation; -import android.os.Build; import android.os.ConditionVariable; import android.os.Handler; import android.os.Looper; @@ -14,6 +13,7 @@ import android.os.Message; import android.os.MessageQueue.IdleHandler; import android.os.SystemClock; import android.util.Log; +import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import java.time.Duration; import java.util.ArrayList; @@ -79,7 +79,7 @@ public final class ShadowPausedLooper extends ShadowLooper { invokeConstructor(Looper.class, realLooper, from(boolean.class, quitAllowed)); loopingLoopers.add(realLooper); - looperExecutor = new HandlerExecutor(new Handler(realLooper)); + looperExecutor = new HandlerExecutor(realLooper); } protected static Collection<Looper> getLoopers() { @@ -218,6 +218,22 @@ public final class ShadowPausedLooper extends ShadowLooper { executeOnLooper(new PostAndIdleToRunnable(runnable)); } + /** + * Posts the runnable as an asynchronous task and wait until it has been run. Ignores all + * exceptions. + * + * <p>This method is similar to postSync, but used in internal cases where you want to make a best + * effort quick attempt to execute the Runnable, and do not need to idle all the non-async tasks + * that might be posted to the Looper's queue. + */ + void postSyncQuiet(Runnable runnable) { + try { + executeOnLooper(new PostAsyncAndIdleToRunnable(runnable)); + } catch (RuntimeException e) { + Log.w("ShadowPausedLooper", "ignoring exception on postSyncQuiet", e); + } + } + // this API doesn't make sense in LooperMode.PAUSED, but just retain it for backwards // compatibility for now @Override @@ -291,6 +307,7 @@ public final class ShadowPausedLooper extends ShadowLooper { ShadowPausedLooper shadowPausedLooper = Shadow.extract(looper); shadowPausedLooper.resetLooperToInitialState(); } + ShadowPausedChoreographer.clearLoopers(); } private static final Object instrumentationTestMainThreadLock = new Object(); @@ -359,20 +376,23 @@ public final class ShadowPausedLooper extends ShadowLooper { } } - private synchronized void resetLooperToInitialState() { + @VisibleForTesting + synchronized void resetLooperToInitialState() { // Do not use looperMode() here, because its cached value might already have been reset LooperMode.Mode looperMode = ConfigurationRegistry.get(LooperMode.Mode.class); ShadowPausedMessageQueue shadowQueue = Shadow.extract(realLooper.getQueue()); shadowQueue.reset(); - boolean canBeUnpaused = - !(realLooper == Looper.getMainLooper() - && looperMode != LooperMode.Mode.INSTRUMENTATION_TEST); - if (canBeUnpaused && realLooper.getThread().isAlive()) { - if (isPaused()) { - unPause(); - } + if (realLooper.getThread().isAlive() + && !shadowQueue + .isQuitting()) { // Trying to unpause a quitted background Looper may deadlock. + + if (isPaused() + && !(realLooper == Looper.getMainLooper() && looperMode != Mode.INSTRUMENTATION_TEST)) { + unPause(); + } + ShadowPausedChoreographer.reset(realLooper); } } @@ -388,10 +408,13 @@ public final class ShadowPausedLooper extends ShadowLooper { if (isPaused()) { executeOnLooper(new UnPauseRunnable()); } - reflector(LooperReflector.class, realLooper).quit(); + synchronized (realLooper.getQueue()) { + drainQueueSafely(shadowQueue()); + reflector(LooperReflector.class, realLooper).quit(); + } } - @Implementation(minSdk = Build.VERSION_CODES.JELLY_BEAN_MR2) + @Implementation protected void quitSafely() { if (isPaused()) { executeOnLooper(new UnPauseRunnable()); @@ -457,15 +480,7 @@ public final class ShadowPausedLooper extends ShadowLooper { } else { synchronized (realLooper.getQueue()) { shadowQueue.setUncaughtException(e); - // release any ControlRunnables currently in queue to prevent deadlocks - shadowQueue.drainQueue( - input -> { - if (input instanceof ControlRunnable) { - ((ControlRunnable) input).runLatch.countDown(); - return true; - } - return false; - }); + drainQueueSafely(shadowQueue); } } if (e instanceof ControlException) { @@ -476,6 +491,18 @@ public final class ShadowPausedLooper extends ShadowLooper { } } + private static void drainQueueSafely(ShadowPausedMessageQueue shadowQueue) { + // release any ControlRunnables currently in queue to prevent deadlocks + shadowQueue.drainQueue( + input -> { + if (input instanceof ControlRunnable) { + ((ControlRunnable) input).runLatch.countDown(); + return true; + } + return false; + }); + } + /** * If the given {@code lastMessageRead} is not null and the queue is now idle, get the idle * handlers and run them. This synchronization mirrors what happens in the real message queue @@ -618,8 +645,37 @@ public final class ShadowPausedLooper extends ShadowLooper { } } - /** Executes the given runnable on the loopers thread, and waits for it to complete. */ - private void executeOnLooper(ControlRunnable runnable) { + private class PostAsyncAndIdleToRunnable extends ControlRunnable { + private final Runnable runnable; + private final Handler handler; + + PostAsyncAndIdleToRunnable(Runnable runnable) { + this.runnable = runnable; + this.handler = createAsyncHandler(realLooper); + } + + @Override + public void doRun() { + handler.postAtFrontOfQueue(runnable); + Message msg; + do { + msg = getNextExecutableMessage(); + if (msg == null) { + throw new IllegalStateException("Runnable is not in the queue"); + } + msg.getTarget().dispatchMessage(msg); + + } while (msg.getCallback() != runnable); + } + } + + /** + * Executes the given runnable on the loopers thread, and waits for it to complete. + * + * @throws IllegalStateException if Looper is quitting or has stopped due to uncaught exception + */ + private void executeOnLooper(ControlRunnable runnable) throws IllegalStateException { + checkState(!shadowQueue().isQuitting(), "Looper is quitting"); if (Thread.currentThread() == realLooper.getThread()) { if (runnable instanceof UnPauseRunnable) { // Need to trigger the unpause action in PausedLooperExecutor @@ -682,16 +738,27 @@ public final class ShadowPausedLooper extends ShadowLooper { private class UnPauseRunnable extends ControlRunnable { @Override public void doRun() { - setLooperExecutor(new HandlerExecutor(new Handler(realLooper))); + setLooperExecutor(new HandlerExecutor(realLooper)); isPaused = false; } } + static Handler createAsyncHandler(Looper looper) { + if (RuntimeEnvironment.getApiLevel() >= 28) { + // createAsync is only available in API 28+ + return Handler.createAsync(looper); + } else { + return new Handler(looper, null, true); + } + } + private static class HandlerExecutor implements Executor { private final Handler handler; - private HandlerExecutor(Handler handler) { - this.handler = handler; + private HandlerExecutor(Looper looper) { + // always post async messages so ControlRunnables get processed even if Looper is blocked on a + // sync barrier + this.handler = createAsyncHandler(looper); } @Override diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPausedMessageQueue.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPausedMessageQueue.java index c40abfce9..8af76ac18 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPausedMessageQueue.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPausedMessageQueue.java @@ -1,7 +1,6 @@ package org.robolectric.shadows; import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2; import static android.os.Build.VERSION_CODES.KITKAT; import static android.os.Build.VERSION_CODES.KITKAT_WATCH; import static android.os.Build.VERSION_CODES.LOLLIPOP_MR1; @@ -17,11 +16,10 @@ import android.os.MessageQueue; import android.os.MessageQueue.IdleHandler; import android.os.SystemClock; import android.util.Log; -import androidx.annotation.VisibleForTesting; +import com.android.internal.annotations.VisibleForTesting; import com.google.common.base.Predicate; import java.time.Duration; import java.util.ArrayList; -import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; import org.robolectric.annotation.LooperMode; @@ -77,7 +75,7 @@ public class ShadowPausedMessageQueue extends ShadowMessageQueue { nativeDestroy(reflector(MessageQueueReflector.class, realQueue).getPtr()); } - @Implementation(minSdk = JELLY_BEAN_MR2, maxSdk = KITKAT) + @Implementation(maxSdk = KITKAT) protected static void nativeDestroy(int ptr) { nativeDestroy((long) ptr); } @@ -95,7 +93,7 @@ public class ShadowPausedMessageQueue extends ShadowMessageQueue { // use the generic Object parameter types here, to avoid conflicts with the non-static // nativePollOnce - @Implementation(minSdk = JELLY_BEAN_MR2, maxSdk = LOLLIPOP_MR1) + @Implementation(maxSdk = LOLLIPOP_MR1) protected static void nativePollOnce(Object ptr, Object timeoutMillis) { long ptrLong = getLong(ptr); nativeQueueRegistry.getNativeObject(ptrLong).nativePollOnce(ptrLong, (int) timeoutMillis); @@ -164,7 +162,7 @@ public class ShadowPausedMessageQueue extends ShadowMessageQueue { // use the generic Object parameter types here, to avoid conflicts with the non-static // nativeWake - @Implementation(minSdk = JELLY_BEAN_MR2, maxSdk = KITKAT) + @Implementation(maxSdk = KITKAT) protected static void nativeWake(Object ptr) { // JELLY_BEAN_MR2 has a bug where nativeWake can get called when pointer has already been // destroyed. See here where nativeWake is called outside the synchronized block @@ -260,25 +258,17 @@ public class ShadowPausedMessageQueue extends ShadowMessageQueue { @Implementation(maxSdk = JELLY_BEAN_MR1) protected void quit() { - if (RuntimeEnvironment.getApiLevel() >= JELLY_BEAN_MR2) { - reflector(MessageQueueReflector.class, realQueue).quit(false); - } else { - reflector(MessageQueueReflector.class, realQueue).quit(); - } + reflector(MessageQueueReflector.class, realQueue).quit(false); } - @Implementation(minSdk = JELLY_BEAN_MR2) + @Implementation protected void quit(boolean allowed) { reflector(MessageQueueReflector.class, realQueue).quit(allowed); ShadowPausedSystemClock.removeListener(clockListener); } - private boolean isQuitting() { - if (RuntimeEnvironment.getApiLevel() >= KITKAT) { - return reflector(MessageQueueReflector.class, realQueue).getQuitting(); - } else { - return reflector(MessageQueueReflector.class, realQueue).getQuiting(); - } + boolean isQuitting() { + return reflector(MessageQueueReflector.class, realQueue).getQuitting(); } private static long getLong(Object intOrLongObj) { @@ -509,17 +499,9 @@ public class ShadowPausedMessageQueue extends ShadowMessageQueue { @Accessor("mPtr") int getPtr(); - // for APIs < JELLYBEAN_MR2 - @Direct - void quit(); - @Direct void quit(boolean b); - // for APIs < KITKAT - @Accessor("mQuiting") - boolean getQuiting(); - @Accessor("mQuitting") boolean getQuitting(); } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPausedSystemClock.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPausedSystemClock.java index 193c08615..5f6edfc8b 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPausedSystemClock.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPausedSystemClock.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; import static android.os.Build.VERSION_CODES.P; import android.os.SystemClock; @@ -11,15 +10,15 @@ import javax.annotation.concurrent.GuardedBy; import org.robolectric.annotation.HiddenApi; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; -import org.robolectric.annotation.LooperMode; import org.robolectric.annotation.Resetter; /** * A shadow SystemClock used when {@link LooperMode.Mode#PAUSED} is active. * - * <p>In this variant, there is just one global system time controlled by this class. The current - * time is fixed in place, and manually advanced by calling {@link - * SystemClock#setCurrentTimeMillis(long)} + * <p>In this variant, System times (both elapsed realtime and uptime) are controlled by this class. + * The current times are fixed in place. You can manually advance both by calling {@link + * SystemClock#setCurrentTimeMillis(long)} or just advance elapsed realtime only by calling {@link + * deepSleep(long)}. * * <p>{@link SystemClock#uptimeMillis()} and {@link SystemClock#currentThreadTimeMillis()} are * identical. @@ -34,8 +33,13 @@ public class ShadowPausedSystemClock extends ShadowSystemClock { private static final long INITIAL_TIME = 100; private static final int MILLIS_PER_NANO = 1000000; + @SuppressWarnings("NonFinalStaticField") @GuardedBy("ShadowPausedSystemClock.class") - private static long currentTimeMillis = INITIAL_TIME; + private static long currentUptimeMillis = INITIAL_TIME; + + @SuppressWarnings("NonFinalStaticField") + @GuardedBy("ShadowPausedSystemClock.class") + private static long currentRealtimeMillis = INITIAL_TIME; private static final List<Listener> listeners = new CopyOnWriteArrayList<>(); // hopefully temporary list of clock listeners that are NOT cleared between tests @@ -62,11 +66,29 @@ public class ShadowPausedSystemClock extends ShadowSystemClock { staticListeners.add(listener); } - /** Advances the current time by given millis, without sleeping the current thread/ */ + /** + * Advances the current time (both elapsed realtime and uptime) by given millis, without sleeping + * the current thread. + */ @Implementation protected static void sleep(long millis) { synchronized (ShadowPausedSystemClock.class) { - currentTimeMillis += millis; + currentUptimeMillis += millis; + currentRealtimeMillis += millis; + } + informListeners(); + } + + /** + * Advances the current time (elapsed realtime only) by given millis, without sleeping the current + * thread. + * + * <p>This is to simulate scenarios like suspend-to-RAM, where only elapsed realtime is + * incremented when the device is in deep sleep. + */ + protected static void deepSleep(long millis) { + synchronized (ShadowPausedSystemClock.class) { + currentRealtimeMillis += millis; } informListeners(); } @@ -81,21 +103,24 @@ public class ShadowPausedSystemClock extends ShadowSystemClock { } /** - * Sets the current wall time. + * Sets the current wall time (both elapsed realtime and uptime). + * + * <p>This API sets both of the elapsed realtime and uptime to the specified value. * * <p>Currently does not perform any permission checks. * - * @return false if specified time is less than current time. + * @return false if specified time is less than current uptime. */ @Implementation protected static boolean setCurrentTimeMillis(long millis) { synchronized (ShadowPausedSystemClock.class) { - if (currentTimeMillis > millis) { + if (currentUptimeMillis > millis) { return false; - } else if (currentTimeMillis == millis) { + } else if (currentUptimeMillis == millis) { return true; } else { - currentTimeMillis = millis; + currentUptimeMillis = millis; + currentRealtimeMillis = millis; } } informListeners(); @@ -104,15 +129,15 @@ public class ShadowPausedSystemClock extends ShadowSystemClock { @Implementation protected static synchronized long uptimeMillis() { - return currentTimeMillis; + return currentUptimeMillis; } @Implementation - protected static long elapsedRealtime() { - return uptimeMillis(); + protected static synchronized long elapsedRealtime() { + return currentRealtimeMillis; } - @Implementation(minSdk = JELLY_BEAN_MR1) + @Implementation protected static long elapsedRealtimeNanos() { return elapsedRealtime() * MILLIS_PER_NANO; } @@ -138,7 +163,7 @@ public class ShadowPausedSystemClock extends ShadowSystemClock { @HiddenApi protected static synchronized long currentNetworkTimeMillis() { if (networkTimeAvailable) { - return currentTimeMillis; + return currentUptimeMillis; } else { throw new DateTimeException("Network time not available"); } @@ -146,7 +171,8 @@ public class ShadowPausedSystemClock extends ShadowSystemClock { @Resetter public static synchronized void reset() { - currentTimeMillis = INITIAL_TIME; + currentUptimeMillis = INITIAL_TIME; + currentRealtimeMillis = INITIAL_TIME; ShadowSystemClock.reset(); listeners.clear(); } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPendingIntent.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPendingIntent.java index c7a86f7e9..c005d51f7 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPendingIntent.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPendingIntent.java @@ -5,7 +5,6 @@ import static android.app.PendingIntent.FLAG_IMMUTABLE; import static android.app.PendingIntent.FLAG_NO_CREATE; import static android.app.PendingIntent.FLAG_ONE_SHOT; import static android.app.PendingIntent.FLAG_UPDATE_CURRENT; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; import static android.os.Build.VERSION_CODES.M; import static android.os.Build.VERSION_CODES.N; import static android.os.Build.VERSION_CODES.O; @@ -477,7 +476,7 @@ public class ShadowPendingIntent { return getCreatorPackage(); } - @Implementation(minSdk = JELLY_BEAN_MR1) + @Implementation protected String getCreatorPackage() { return (creatorPackage == null) ? RuntimeEnvironment.getApplication().getPackageName() @@ -488,7 +487,7 @@ public class ShadowPendingIntent { this.creatorPackage = creatorPackage; } - @Implementation(minSdk = JELLY_BEAN_MR1) + @Implementation protected int getCreatorUid() { return creatorUid; } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPhoneWindow.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPhoneWindow.java index 9411ff1c8..3cd7ff8ff 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPhoneWindow.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPhoneWindow.java @@ -4,10 +4,10 @@ import static android.os.Build.VERSION_CODES.M; import static android.os.Build.VERSION_CODES.R; import static org.robolectric.util.reflector.Reflector.reflector; +import android.annotation.RequiresApi; import android.graphics.drawable.Drawable; import android.view.Gravity; import android.view.Window; -import androidx.annotation.RequiresApi; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; import org.robolectric.annotation.RealObject; diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPixelCopy.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPixelCopy.java index ae1bfd86d..a070ce1cb 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPixelCopy.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPixelCopy.java @@ -4,6 +4,8 @@ import static android.os.Build.VERSION_CODES.O; import static com.google.common.base.Preconditions.checkNotNull; import static org.robolectric.util.reflector.Reflector.reflector; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Paint; @@ -18,8 +20,6 @@ import android.view.View; import android.view.ViewRootImpl; import android.view.Window; import android.view.WindowManagerGlobal; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import java.util.function.Consumer; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPorterDuffColorFilter.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPorterDuffColorFilter.java index f0da84485..68f65553b 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPorterDuffColorFilter.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPorterDuffColorFilter.java @@ -24,7 +24,7 @@ public class ShadowPorterDuffColorFilter { protected void __constructor__(int color, PorterDuff.Mode mode) { // We need these copies because before Lollipop, PorterDuffColorFilter had no fields, it would // just delegate to a native instance. If we remove them, the shadow cannot access the fields - // on KitKat and earlier. + // on KitKat this.color = color; this.mode = mode; } @@ -34,7 +34,7 @@ public class ShadowPorterDuffColorFilter { */ @Implementation(minSdk = LOLLIPOP) public int getColor() { - if (RuntimeEnvironment.getApiLevel() <= KITKAT) { + if (RuntimeEnvironment.getApiLevel() == KITKAT) { return color; } else { return reflector(PorterDuffColorFilterReflector.class, realPorterDuffColorFilter).getColor(); @@ -47,7 +47,7 @@ public class ShadowPorterDuffColorFilter { */ @Implementation(minSdk = LOLLIPOP) public PorterDuff.Mode getMode() { - if (RuntimeEnvironment.getApiLevel() <= KITKAT) { + if (RuntimeEnvironment.getApiLevel() == KITKAT) { return mode; } else { return reflector(PorterDuffColorFilterReflector.class, realPorterDuffColorFilter).getMode(); diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPowerManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPowerManager.java index 9bce4cca6..cc6e427a7 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPowerManager.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPowerManager.java @@ -26,7 +26,6 @@ import android.annotation.TargetApi; import android.content.Context; import android.content.Intent; import android.os.Binder; -import android.os.Build.VERSION_CODES; import android.os.PowerManager; import android.os.PowerManager.LowPowerStandbyPortDescription; import android.os.PowerManager.LowPowerStandbyPortsLock; @@ -559,11 +558,7 @@ public class ShadowPowerManager { } private Context getContext() { - if (RuntimeEnvironment.getApiLevel() < VERSION_CODES.JELLY_BEAN_MR1) { - return RuntimeEnvironment.getApplication(); - } else { - return reflector(ReflectorPowerManager.class, realPowerManager).getContext(); - } + return reflector(ReflectorPowerManager.class, realPowerManager).getContext(); } @Implementation(minSdk = TIRAMISU) diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowProcess.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowProcess.java index 001391da0..965994d5c 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowProcess.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowProcess.java @@ -3,7 +3,7 @@ package org.robolectric.shadows; import static android.os.Build.VERSION_CODES.TIRAMISU; import static com.google.common.base.Preconditions.checkArgument; -import androidx.annotation.NonNull; +import android.annotation.NonNull; import java.util.HashMap; import java.util.HashSet; import java.util.Map; diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowResources.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowResources.java index e6120d78e..93e56ccfd 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowResources.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowResources.java @@ -81,10 +81,8 @@ public class ShadowResources { protected static Resources getSystem() { if (system == null) { AssetManager assetManager = AssetManager.getSystem(); - DisplayMetrics metrics = new DisplayMetrics(); - Configuration config = new Configuration(); - system = new Resources(assetManager, metrics, config); - Bootstrap.updateConfiguration(system); + system = + new Resources(assetManager, Bootstrap.getDisplayMetrics(), Bootstrap.getConfiguration()); } return system; } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowResourcesManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowResourcesManager.java index 64e72870a..d9651f6c9 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowResourcesManager.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowResourcesManager.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.KITKAT; import static org.robolectric.util.reflector.Reflector.reflector; import android.app.ResourcesManager; @@ -13,7 +12,7 @@ import org.robolectric.util.reflector.Accessor; import org.robolectric.util.reflector.ForType; import org.robolectric.util.reflector.Static; -@Implements(value = ResourcesManager.class, isInAndroidSdk = false, minSdk = KITKAT) +@Implements(value = ResourcesManager.class, isInAndroidSdk = false) public class ShadowResourcesManager { @RealObject ResourcesManager realResourcesManager; diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowRoleManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowRoleManager.java index ae36b83ae..8f8cf4fe8 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowRoleManager.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowRoleManager.java @@ -1,14 +1,25 @@ package org.robolectric.shadows; +import static org.robolectric.shadow.api.Shadow.invokeConstructor; +import static org.robolectric.util.ReflectionHelpers.ClassParameter.from; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.role.IRoleManager; import android.app.role.RoleManager; +import android.content.Context; +import android.content.pm.PackageManager; import android.os.Build; -import android.util.ArraySet; -import androidx.annotation.NonNull; import com.android.internal.util.Preconditions; -import java.util.Set; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.Executor; +import java.util.function.Consumer; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; import org.robolectric.annotation.RealObject; +import org.robolectric.annotation.Resetter; /** A shadow implementation of {@link android.app.role.RoleManager}. */ @Implements(value = RoleManager.class, minSdk = Build.VERSION_CODES.Q) @@ -16,8 +27,37 @@ public class ShadowRoleManager { @RealObject protected RoleManager roleManager; - private final Set<String> heldRoles = new ArraySet<>(); - private final Set<String> availableRoles = new ArraySet<>(); + // See RoleService implementation + private static final String[] DEFAULT_APPLICATION_ROLES = { + RoleManager.ROLE_ASSISTANT, + RoleManager.ROLE_BROWSER, + RoleManager.ROLE_CALL_REDIRECTION, + RoleManager.ROLE_CALL_SCREENING, + RoleManager.ROLE_DIALER, + RoleManager.ROLE_HOME, + RoleManager.ROLE_SMS, + }; + + private Context context; + + // Roles that exist but are currently unavailable have their value set to {@code null}. + private static final Map<String, String> roleToHolder = new HashMap<>(); + + @Implementation(maxSdk = Build.VERSION_CODES.R) + protected void __constructor__(Context context) { + this.context = context; + invokeConstructor(RoleManager.class, roleManager, from(Context.class, context)); + } + + @Implementation(minSdk = Build.VERSION_CODES.S) + protected void __constructor__(Context context, IRoleManager service) { + this.context = context; + invokeConstructor( + RoleManager.class, + roleManager, + from(Context.class, context), + from(IRoleManager.class, service)); + } /** * Check whether the calling application is holding a particular role. @@ -30,28 +70,32 @@ public class ShadowRoleManager { @Implementation protected boolean isRoleHeld(@NonNull String roleName) { Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty"); - return heldRoles.contains(roleName); + return context.getPackageName().equals(roleToHolder.get(roleName)); } /** * Add a role that would be held by the calling app when invoking {@link * RoleManager#isRoleHeld(String)}. + * + * <p>This method makes the role available as well. */ public void addHeldRole(@NonNull String roleName) { - heldRoles.add(roleName); + addAvailableRole(roleName); + roleToHolder.put(roleName, context.getPackageName()); } /* Remove a role previously added via {@link #addHeldRole(String)}. */ public void removeHeldRole(@NonNull String roleName) { - Preconditions.checkArgument( - heldRoles.contains(roleName), "the supplied roleName was never added."); - heldRoles.remove(roleName); + Preconditions.checkArgument(isRoleHeld(roleName), "the supplied roleName was never added."); + roleToHolder.put(roleName, null); } /** * Check whether a particular role is available on the device. * - * <p>Callers can add available roles via {@link #addAvailableRole(String)} + * <p>Ideally available roles would be autodetected based on the state of other services or + * features present, but for now callers can add available roles via {@link + * #addAvailableRole(String)}. * * @param roleName the name of the role to check for * @return whether the role is available @@ -59,7 +103,7 @@ public class ShadowRoleManager { @Implementation protected boolean isRoleAvailable(@NonNull String roleName) { Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty"); - return availableRoles.contains(roleName); + return roleToHolder.containsKey(roleName); } /** @@ -68,13 +112,49 @@ public class ShadowRoleManager { */ public void addAvailableRole(@NonNull String roleName) { Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty"); - availableRoles.add(roleName); + if (!isRoleAvailable(roleName)) { + roleToHolder.put(roleName, null); + } } /* Remove a role previously added via {@link #addAvailableRole(String)}. */ public void removeAvailableRole(@NonNull String roleName) { Preconditions.checkArgument( - availableRoles.contains(roleName), "the supplied roleName was never added."); - availableRoles.remove(roleName); + roleToHolder.containsKey(roleName), "the supplied roleName was never added."); + roleToHolder.remove(roleName); + } + + @Nullable + @Implementation(minSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + protected String getDefaultApplication(@NonNull String roleName) { + return roleToHolder.get(roleName); + } + + @Implementation(minSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + protected void setDefaultApplication( + @NonNull String roleName, + @Nullable String packageName, + int flags, + @NonNull Executor executor, + @NonNull Consumer<Boolean> callback) { + Preconditions.checkArgument( + Arrays.asList(DEFAULT_APPLICATION_ROLES).contains(roleName), + "the supplied roleName in not a default app."); + try { + context.getPackageManager().getPackageInfo(packageName, 0); + if (isRoleAvailable(roleName)) { + roleToHolder.put(roleName, packageName); + executor.execute(() -> callback.accept(true)); + return; + } + } catch (PackageManager.NameNotFoundException e) { + // fall through to failure + } + executor.execute(() -> callback.accept(false)); + } + + @Resetter + public static void reset() { + roleToHolder.clear(); } } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSafetyCenterManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSafetyCenterManager.java index 43600c6c1..4840614cd 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSafetyCenterManager.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSafetyCenterManager.java @@ -1,14 +1,17 @@ package org.robolectric.shadows; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.os.Build.VERSION_CODES; import android.safetycenter.SafetyCenterManager; import android.safetycenter.SafetyEvent; import android.safetycenter.SafetySourceData; import android.safetycenter.SafetySourceErrorDetails; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; +import com.google.errorprone.annotations.concurrent.GuardedBy; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; +import java.util.Set; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; @@ -19,15 +22,28 @@ import org.robolectric.annotation.Implements; isInAndroidSdk = false) public class ShadowSafetyCenterManager { + private final Object lock = new Object(); + + @GuardedBy("lock") private final Map<String, SafetySourceData> dataById = new HashMap<>(); + + @GuardedBy("lock") private final Map<String, SafetyEvent> eventsById = new HashMap<>(); + + @GuardedBy("lock") private final Map<String, SafetySourceErrorDetails> errorsById = new HashMap<>(); + @GuardedBy("lock") + private final Set<String> throwForId = new HashSet<>(); + + @GuardedBy("lock") private boolean enabled = false; @Implementation protected boolean isSafetyCenterEnabled() { - return enabled; + synchronized (lock) { + return enabled; + } } @Implementation @@ -35,7 +51,11 @@ public class ShadowSafetyCenterManager { @NonNull String safetySourceId, @Nullable SafetySourceData safetySourceData, @NonNull SafetyEvent safetyEvent) { - if (isSafetyCenterEnabled()) { + synchronized (lock) { + if (!isSafetyCenterEnabled()) { + return; + } + maybeThrowForId(safetySourceId); dataById.put(safetySourceId, safetySourceData); eventsById.put(safetySourceId, safetyEvent); } @@ -43,27 +63,51 @@ public class ShadowSafetyCenterManager { @Implementation protected SafetySourceData getSafetySourceData(@NonNull String safetySourceId) { - if (isSafetyCenterEnabled()) { + synchronized (lock) { + if (!isSafetyCenterEnabled()) { + return null; + } + maybeThrowForId(safetySourceId); return dataById.get(safetySourceId); - } else { - return null; } } @Implementation protected void reportSafetySourceError( @NonNull String safetySourceId, @NonNull SafetySourceErrorDetails safetySourceErrorDetails) { - if (isSafetyCenterEnabled()) { + synchronized (lock) { + if (!isSafetyCenterEnabled()) { + return; + } + maybeThrowForId(safetySourceId); errorsById.put(safetySourceId, safetySourceErrorDetails); } } + @GuardedBy("lock") + private void maybeThrowForId(String safetySourceId) { + if (throwForId.contains(safetySourceId)) { + throw new IllegalArgumentException(String.format("%s is invalid", safetySourceId)); + } + } + /** * Sets the return value for {@link #isSafetyCenterEnabled} which also enables the {@link * #setSafetySourceData} and {@link #getSafetySourceData} methods. */ public void setSafetyCenterEnabled(boolean enabled) { - this.enabled = enabled; + synchronized (lock) { + this.enabled = enabled; + } + } + + /** + * Makes the APIs throw an {@link IllegalArgumentException} for the given {@code safetySourceId}. + */ + public void throwOnSafetySourceId(@NonNull String safetySourceId) { + synchronized (lock) { + throwForId.add(safetySourceId); + } } /** @@ -71,7 +115,9 @@ public class ShadowSafetyCenterManager { * {@link #setSafetySourceData} was called with this {@code safetySourceId}. */ public SafetyEvent getLastSafetyEvent(@NonNull String safetySourceId) { - return eventsById.get(safetySourceId); + synchronized (lock) { + return eventsById.get(safetySourceId); + } } /** @@ -79,6 +125,8 @@ public class ShadowSafetyCenterManager { * last time {@link #reportSafetySourceError} was called with this {@code safetySourceId}. */ public SafetySourceErrorDetails getLastSafetySourceError(@NonNull String safetySourceId) { - return errorsById.get(safetySourceId); + synchronized (lock) { + return errorsById.get(safetySourceId); + } } } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSensorManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSensorManager.java index 13d5d2ec6..4cea614d2 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSensorManager.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSensorManager.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.KITKAT; import static android.os.Build.VERSION_CODES.O; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; @@ -93,7 +92,7 @@ public class ShadowSensorManager { /** * @param maxLatency is ignored. */ - @Implementation(minSdk = KITKAT) + @Implementation protected boolean registerListener( SensorEventListener listener, Sensor sensor, int rate, int maxLatency) { return registerListener(listener, sensor, rate); @@ -103,7 +102,7 @@ public class ShadowSensorManager { * @param maxLatency is ignored. * @param handler is ignored */ - @Implementation(minSdk = KITKAT) + @Implementation protected boolean registerListener( SensorEventListener listener, Sensor sensor, int rate, int maxLatency, Handler handler) { return registerListener(listener, sensor, rate); @@ -167,7 +166,7 @@ public class ShadowSensorManager { } } - @Implementation(minSdk = KITKAT) + @Implementation protected boolean flush(SensorEventListener listener) { // ShadowSensorManager doesn't queue up any sensor events, so nothing actually needs to be // flushed. Just call onFlushCompleted for each sensor that would have been flushed. diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowServiceManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowServiceManager.java index 1dfbfeccf..7abe78b02 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowServiceManager.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowServiceManager.java @@ -1,8 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2; -import static android.os.Build.VERSION_CODES.KITKAT; import static android.os.Build.VERSION_CODES.LOLLIPOP; import static android.os.Build.VERSION_CODES.M; import static android.os.Build.VERSION_CODES.N; @@ -105,6 +102,8 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; +import javax.annotation.Nullable; +import javax.annotation.concurrent.GuardedBy; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; @@ -117,128 +116,18 @@ import android.companion.virtual.IVirtualDeviceManager; @Implements(value = ServiceManager.class, isInAndroidSdk = false) public class ShadowServiceManager { - private static final Map<String, BinderService> binderServices = new HashMap<>(); - private static final Set<String> unavailableServices = new HashSet<>(); - - static { - addBinderService(Context.CLIPBOARD_SERVICE, IClipboard.class); - addBinderService(Context.WIFI_P2P_SERVICE, IWifiP2pManager.class); - addBinderService(Context.ACCOUNT_SERVICE, IAccountManager.class); - addBinderService(Context.USB_SERVICE, IUsbManager.class); - addBinderService(Context.LOCATION_SERVICE, ILocationManager.class); - addBinderService(Context.INPUT_METHOD_SERVICE, IInputMethodManager.class); - addBinderService(Context.ALARM_SERVICE, IAlarmManager.class); - addBinderService(Context.POWER_SERVICE, IPowerManager.class); - addBinderService(BatteryStats.SERVICE_NAME, IBatteryStats.class); - addBinderService(Context.DROPBOX_SERVICE, IDropBoxManagerService.class); - addBinderService(Context.DEVICE_POLICY_SERVICE, IDevicePolicyManager.class); - addBinderService(Context.TELEPHONY_SERVICE, ITelephony.class); - addBinderService(Context.CONNECTIVITY_SERVICE, IConnectivityManager.class); - addBinderService(Context.WIFI_SERVICE, IWifiManager.class); - addBinderService(Context.SEARCH_SERVICE, ISearchManager.class); - addBinderService(Context.UI_MODE_SERVICE, IUiModeManager.class); - addBinderService(Context.NETWORK_POLICY_SERVICE, INetworkPolicyManager.class); - addBinderService(Context.INPUT_SERVICE, IInputManager.class); - addBinderService(Context.COUNTRY_DETECTOR, ICountryDetector.class); - addBinderService(Context.NSD_SERVICE, INsdManager.class); - addBinderService(Context.AUDIO_SERVICE, IAudioService.class); - addBinderService(Context.APPWIDGET_SERVICE, IAppWidgetService.class); - addBinderService(Context.NOTIFICATION_SERVICE, INotificationManager.class); - addBinderService(Context.WALLPAPER_SERVICE, IWallpaperManager.class); - addBinderService(Context.BLUETOOTH_SERVICE, IBluetooth.class); - addBinderService(Context.WINDOW_SERVICE, IWindowManager.class); - addBinderService(Context.NFC_SERVICE, INfcAdapter.class, true); - addBinderService(Context.VIRTUAL_DEVICE_SERVICE, IVirtualDeviceManager.class); + // A mutable map that contains a list of binder services. It is mutable so entries can be added by + // ShadowServiceManager subclasses. This is useful to support prerelease SDKs. + protected static final Map<String, BinderService> binderServices = buildBinderServicesMap(); - if (RuntimeEnvironment.getApiLevel() >= JELLY_BEAN_MR1) { - addBinderService(Context.USER_SERVICE, IUserManager.class); - addBinderService(BluetoothAdapter.BLUETOOTH_MANAGER_SERVICE, IBluetoothManager.class); - } - if (RuntimeEnvironment.getApiLevel() >= JELLY_BEAN_MR2) { - addBinderService(Context.APP_OPS_SERVICE, IAppOpsService.class); - } - if (RuntimeEnvironment.getApiLevel() >= KITKAT) { - addBinderService("batteryproperties", IBatteryPropertiesRegistrar.class); - } - if (RuntimeEnvironment.getApiLevel() >= LOLLIPOP) { - addBinderService(Context.RESTRICTIONS_SERVICE, IRestrictionsManager.class); - addBinderService(Context.TRUST_SERVICE, ITrustManager.class); - addBinderService(Context.JOB_SCHEDULER_SERVICE, IJobScheduler.class); - addBinderService(Context.NETWORK_SCORE_SERVICE, INetworkScoreService.class); - addBinderService(Context.USAGE_STATS_SERVICE, IUsageStatsManager.class); - addBinderService(Context.MEDIA_ROUTER_SERVICE, IMediaRouterService.class); - addBinderService(Context.MEDIA_SESSION_SERVICE, ISessionManager.class, true); - addBinderService( - Context.VOICE_INTERACTION_MANAGER_SERVICE, IVoiceInteractionManagerService.class, true); - } - if (RuntimeEnvironment.getApiLevel() >= M) { - addBinderService(Context.FINGERPRINT_SERVICE, IFingerprintService.class); - } - if (RuntimeEnvironment.getApiLevel() >= N) { - addBinderService(Context.CONTEXTHUB_SERVICE, IContextHubService.class); - addBinderService(Context.SOUND_TRIGGER_SERVICE, ISoundTriggerService.class); - } - if (RuntimeEnvironment.getApiLevel() >= N_MR1) { - addBinderService(Context.SHORTCUT_SERVICE, IShortcutService.class); - } - if (RuntimeEnvironment.getApiLevel() >= O) { - addBinderService("mount", IStorageManager.class); - addBinderService(Context.WIFI_AWARE_SERVICE, IWifiAwareManager.class); - addBinderService(Context.STORAGE_STATS_SERVICE, IStorageStatsManager.class); - addBinderService(Context.COMPANION_DEVICE_SERVICE, ICompanionDeviceManager.class); - } else { - addBinderService("mount", "android.os.storage.IMountService"); - } - if (RuntimeEnvironment.getApiLevel() >= P) { - addBinderService(Context.SLICE_SERVICE, ISliceManager.class); - addBinderService(Context.CROSS_PROFILE_APPS_SERVICE, ICrossProfileApps.class); - addBinderService(Context.WIFI_RTT_RANGING_SERVICE, IWifiRttManager.class); - addBinderService(Context.IPSEC_SERVICE, IIpSecService.class); - } - if (RuntimeEnvironment.getApiLevel() >= Q) { - addBinderService(Context.BIOMETRIC_SERVICE, IBiometricService.class); - addBinderService(Context.CONTENT_CAPTURE_MANAGER_SERVICE, IContentCaptureManager.class); - addBinderService(Context.ROLE_SERVICE, IRoleManager.class); - addBinderService(Context.ROLLBACK_SERVICE, IRollbackManager.class); - addBinderService(Context.THERMAL_SERVICE, IThermalService.class); - addBinderService(Context.BUGREPORT_SERVICE, IDumpstate.class); - } - if (RuntimeEnvironment.getApiLevel() >= R) { - addBinderService(Context.APP_INTEGRITY_SERVICE, IAppIntegrityManager.class); - addBinderService(Context.AUTH_SERVICE, IAuthService.class); - addBinderService(Context.TETHERING_SERVICE, ITetheringConnector.class); - addBinderService("telephony.registry", ITelephonyRegistry.class); - addBinderService(Context.PLATFORM_COMPAT_SERVICE, IPlatformCompat.class); - } - if (RuntimeEnvironment.getApiLevel() >= S) { - addBinderService("permissionmgr", IPermissionManager.class); - addBinderService(Context.TIME_ZONE_DETECTOR_SERVICE, ITimeZoneDetectorService.class); - addBinderService(Context.TIME_DETECTOR_SERVICE, ITimeDetectorService.class); - addBinderService(Context.SPEECH_RECOGNITION_SERVICE, IRecognitionServiceManager.class); - addBinderService(Context.LEGACY_PERMISSION_SERVICE, ILegacyPermissionManager.class); - addBinderService(Context.UWB_SERVICE, IUwbAdapter.class); - addBinderService(Context.VCN_MANAGEMENT_SERVICE, IVcnManagementService.class); - addBinderService(Context.TRANSLATION_MANAGER_SERVICE, ITranslationManager.class); - addBinderService(Context.SENSOR_PRIVACY_SERVICE, ISensorPrivacyManager.class); - addBinderService(Context.VPN_MANAGEMENT_SERVICE, IVpnManager.class); - } - if (RuntimeEnvironment.getApiLevel() >= TIRAMISU) { - addBinderService(Context.AMBIENT_CONTEXT_SERVICE, IAmbientContextManager.class); - addBinderService(Context.LOCALE_SERVICE, ILocaleManager.class); - addBinderService(Context.SAFETY_CENTER_SERVICE, ISafetyCenterManager.class); - addBinderService(Context.STATUS_BAR_SERVICE, IStatusBar.class); - } - if (RuntimeEnvironment.getApiLevel() >= UPSIDE_DOWN_CAKE) { - addBinderService(Context.VIRTUAL_DEVICE_SERVICE, IVirtualDeviceManager.class); - addBinderService(Context.WEARABLE_SENSING_SERVICE, IWearableSensingManager.class); - } - } + @GuardedBy("ShadowServiceManager.class") + private static final Set<String> unavailableServices = new HashSet<>(); /** * A data class that holds descriptor information about binder services. It also holds the cached * binder object if it is requested by {@link #getService(String)}. */ - private static class BinderService { + private static final class BinderService { private final Class<? extends IInterface> clazz; private final String className; @@ -251,9 +140,8 @@ public class ShadowServiceManager { this.useDeepBinder = useDeepBinder; } - // Needs to be synchronized in case multiple threads call ServiceManager.getService - // concurrently. - synchronized IBinder getBinder() { + @GuardedBy("ShadowServiceManager.class") + IBinder getBinder() { if (cachedBinder == null) { cachedBinder = new Binder(); cachedBinder.attachInterface( @@ -266,52 +154,193 @@ public class ShadowServiceManager { } } - protected static void addBinderService(String name, Class<? extends IInterface> clazz) { - addBinderService(name, clazz, clazz.getCanonicalName(), false); + private static Map<String, BinderService> buildBinderServicesMap() { + Map<String, BinderService> binderServices = new HashMap<>(); + addBinderService(binderServices, Context.CLIPBOARD_SERVICE, IClipboard.class); + addBinderService(binderServices, Context.WIFI_P2P_SERVICE, IWifiP2pManager.class); + addBinderService(binderServices, Context.ACCOUNT_SERVICE, IAccountManager.class); + addBinderService(binderServices, Context.USB_SERVICE, IUsbManager.class); + addBinderService(binderServices, Context.LOCATION_SERVICE, ILocationManager.class); + addBinderService(binderServices, Context.INPUT_METHOD_SERVICE, IInputMethodManager.class); + addBinderService(binderServices, Context.ALARM_SERVICE, IAlarmManager.class); + addBinderService(binderServices, Context.POWER_SERVICE, IPowerManager.class); + addBinderService(binderServices, BatteryStats.SERVICE_NAME, IBatteryStats.class); + addBinderService(binderServices, Context.DROPBOX_SERVICE, IDropBoxManagerService.class); + addBinderService(binderServices, Context.DEVICE_POLICY_SERVICE, IDevicePolicyManager.class); + addBinderService(binderServices, Context.TELEPHONY_SERVICE, ITelephony.class); + addBinderService(binderServices, Context.CONNECTIVITY_SERVICE, IConnectivityManager.class); + addBinderService(binderServices, Context.WIFI_SERVICE, IWifiManager.class); + addBinderService(binderServices, Context.SEARCH_SERVICE, ISearchManager.class); + addBinderService(binderServices, Context.UI_MODE_SERVICE, IUiModeManager.class); + addBinderService(binderServices, Context.NETWORK_POLICY_SERVICE, INetworkPolicyManager.class); + addBinderService(binderServices, Context.INPUT_SERVICE, IInputManager.class); + addBinderService(binderServices, Context.COUNTRY_DETECTOR, ICountryDetector.class); + addBinderService(binderServices, Context.NSD_SERVICE, INsdManager.class); + addBinderService(binderServices, Context.AUDIO_SERVICE, IAudioService.class); + addBinderService(binderServices, Context.APPWIDGET_SERVICE, IAppWidgetService.class); + addBinderService(binderServices, Context.NOTIFICATION_SERVICE, INotificationManager.class); + addBinderService(binderServices, Context.WALLPAPER_SERVICE, IWallpaperManager.class); + addBinderService(binderServices, Context.BLUETOOTH_SERVICE, IBluetooth.class); + addBinderService(binderServices, Context.WINDOW_SERVICE, IWindowManager.class); + addBinderService(binderServices, Context.NFC_SERVICE, INfcAdapter.class, true); + addBinderService(binderServices, Context.USER_SERVICE, IUserManager.class); + addBinderService( + binderServices, BluetoothAdapter.BLUETOOTH_MANAGER_SERVICE, IBluetoothManager.class); + addBinderService(binderServices, Context.APP_OPS_SERVICE, IAppOpsService.class); + addBinderService(binderServices, "batteryproperties", IBatteryPropertiesRegistrar.class); + + if (RuntimeEnvironment.getApiLevel() >= LOLLIPOP) { + addBinderService(binderServices, Context.RESTRICTIONS_SERVICE, IRestrictionsManager.class); + addBinderService(binderServices, Context.TRUST_SERVICE, ITrustManager.class); + addBinderService(binderServices, Context.JOB_SCHEDULER_SERVICE, IJobScheduler.class); + addBinderService(binderServices, Context.NETWORK_SCORE_SERVICE, INetworkScoreService.class); + addBinderService(binderServices, Context.USAGE_STATS_SERVICE, IUsageStatsManager.class); + addBinderService(binderServices, Context.MEDIA_ROUTER_SERVICE, IMediaRouterService.class); + addBinderService(binderServices, Context.MEDIA_SESSION_SERVICE, ISessionManager.class, true); + addBinderService( + binderServices, + Context.VOICE_INTERACTION_MANAGER_SERVICE, + IVoiceInteractionManagerService.class, + true); + } + if (RuntimeEnvironment.getApiLevel() >= M) { + addBinderService(binderServices, Context.FINGERPRINT_SERVICE, IFingerprintService.class); + } + if (RuntimeEnvironment.getApiLevel() >= N) { + addBinderService(binderServices, Context.CONTEXTHUB_SERVICE, IContextHubService.class); + addBinderService(binderServices, Context.SOUND_TRIGGER_SERVICE, ISoundTriggerService.class); + } + if (RuntimeEnvironment.getApiLevel() >= N_MR1) { + addBinderService(binderServices, Context.SHORTCUT_SERVICE, IShortcutService.class); + } + if (RuntimeEnvironment.getApiLevel() >= O) { + addBinderService(binderServices, "mount", IStorageManager.class); + addBinderService(binderServices, Context.WIFI_AWARE_SERVICE, IWifiAwareManager.class); + addBinderService(binderServices, Context.STORAGE_STATS_SERVICE, IStorageStatsManager.class); + addBinderService( + binderServices, Context.COMPANION_DEVICE_SERVICE, ICompanionDeviceManager.class); + } else { + addBinderService(binderServices, "mount", "android.os.storage.IMountService"); + } + if (RuntimeEnvironment.getApiLevel() >= P) { + addBinderService(binderServices, Context.SLICE_SERVICE, ISliceManager.class); + addBinderService(binderServices, Context.CROSS_PROFILE_APPS_SERVICE, ICrossProfileApps.class); + addBinderService(binderServices, Context.WIFI_RTT_RANGING_SERVICE, IWifiRttManager.class); + addBinderService(binderServices, Context.IPSEC_SERVICE, IIpSecService.class); + } + if (RuntimeEnvironment.getApiLevel() >= Q) { + addBinderService(binderServices, Context.BIOMETRIC_SERVICE, IBiometricService.class); + addBinderService( + binderServices, Context.CONTENT_CAPTURE_MANAGER_SERVICE, IContentCaptureManager.class); + addBinderService(binderServices, Context.ROLE_SERVICE, IRoleManager.class); + addBinderService(binderServices, Context.ROLLBACK_SERVICE, IRollbackManager.class); + addBinderService(binderServices, Context.THERMAL_SERVICE, IThermalService.class); + addBinderService(binderServices, Context.BUGREPORT_SERVICE, IDumpstate.class); + } + if (RuntimeEnvironment.getApiLevel() >= R) { + addBinderService(binderServices, Context.APP_INTEGRITY_SERVICE, IAppIntegrityManager.class); + addBinderService(binderServices, Context.AUTH_SERVICE, IAuthService.class); + addBinderService(binderServices, Context.TETHERING_SERVICE, ITetheringConnector.class); + addBinderService(binderServices, "telephony.registry", ITelephonyRegistry.class); + addBinderService(binderServices, Context.PLATFORM_COMPAT_SERVICE, IPlatformCompat.class); + } + if (RuntimeEnvironment.getApiLevel() >= S) { + addBinderService(binderServices, "permissionmgr", IPermissionManager.class); + addBinderService( + binderServices, Context.TIME_ZONE_DETECTOR_SERVICE, ITimeZoneDetectorService.class); + addBinderService(binderServices, Context.TIME_DETECTOR_SERVICE, ITimeDetectorService.class); + addBinderService( + binderServices, Context.SPEECH_RECOGNITION_SERVICE, IRecognitionServiceManager.class); + addBinderService( + binderServices, Context.LEGACY_PERMISSION_SERVICE, ILegacyPermissionManager.class); + addBinderService(binderServices, Context.UWB_SERVICE, IUwbAdapter.class); + addBinderService(binderServices, Context.VCN_MANAGEMENT_SERVICE, IVcnManagementService.class); + addBinderService( + binderServices, Context.TRANSLATION_MANAGER_SERVICE, ITranslationManager.class); + addBinderService(binderServices, Context.SENSOR_PRIVACY_SERVICE, ISensorPrivacyManager.class); + addBinderService(binderServices, Context.VPN_MANAGEMENT_SERVICE, IVpnManager.class); + } + if (RuntimeEnvironment.getApiLevel() >= TIRAMISU) { + addBinderService( + binderServices, Context.AMBIENT_CONTEXT_SERVICE, IAmbientContextManager.class); + addBinderService(binderServices, Context.LOCALE_SERVICE, ILocaleManager.class); + addBinderService(binderServices, Context.SAFETY_CENTER_SERVICE, ISafetyCenterManager.class); + addBinderService(binderServices, Context.STATUS_BAR_SERVICE, IStatusBar.class); + } + if (RuntimeEnvironment.getApiLevel() >= UPSIDE_DOWN_CAKE) { + addBinderService(binderServices, Context.VIRTUAL_DEVICE_SERVICE, IVirtualDeviceManager.class); + addBinderService( + binderServices, Context.WEARABLE_SENSING_SERVICE, IWearableSensingManager.class); + } + return binderServices; } protected static void addBinderService( - String name, Class<? extends IInterface> clazz, boolean useDeepBinder) { - addBinderService(name, clazz, clazz.getCanonicalName(), useDeepBinder); + Map<String, BinderService> binderServices, String name, Class<? extends IInterface> clazz) { + addBinderService(binderServices, name, clazz, clazz.getCanonicalName(), false); + } + + private static void addBinderService( + Map<String, BinderService> binderServices, + String name, + Class<? extends IInterface> clazz, + boolean useDeepBinder) { + addBinderService(binderServices, name, clazz, clazz.getCanonicalName(), useDeepBinder); } - protected static void addBinderService(String name, String className) { + private static void addBinderService( + Map<String, BinderService> binderServices, String name, String className) { Class<? extends IInterface> clazz; try { clazz = Class.forName(className).asSubclass(IInterface.class); } catch (ClassNotFoundException e) { throw new RuntimeException(e); } - addBinderService(name, clazz, className, false); + addBinderService(binderServices, name, clazz, className, false); } - protected static void addBinderService( - String name, Class<? extends IInterface> clazz, String className, boolean useDeepBinder) { + private static void addBinderService( + Map<String, BinderService> binderServices, + String name, + Class<? extends IInterface> clazz, + String className, + boolean useDeepBinder) { binderServices.put(name, new BinderService(clazz, className, useDeepBinder)); } + /** - * Returns the binder associated with the given system service. If the given service is set to - * unavailable in {@link #setServiceAvailability}, {@code null} will be returned. + * Returns the {@link IBinder} associated with the given system service. If the given service is + * set to unavailable in {@link #setServiceAvailability}, {@code null} will be returned. */ @Implementation protected static IBinder getService(String name) { - if (unavailableServices.contains(name)) { - return null; - } - BinderService binderService = binderServices.get(name); - if (binderService == null) { - return null; + synchronized (ShadowServiceManager.class) { + if (unavailableServices.contains(name)) { + return null; + } + return getBinderForService(name); } - - return binderService.getBinder(); } @Implementation protected static void addService(String name, IBinder service) {} + /** + * Same as {@link #getService}. + * + * <p>The real implementation of {@link #checkService} differs from {@link #getService} in that it + * is not a blocking call; so it is more likely to return {@code null} in cases where the service + * isn't available (whereas {@link #getService} will block until it becomes available, until a + * timeout or error happens). + */ @Implementation protected static IBinder checkService(String name) { - return null; + synchronized (ShadowServiceManager.class) { + if (unavailableServices.contains(name)) { + return null; + } + return getBinderForService(name); + } } @Implementation @@ -325,8 +354,10 @@ public class ShadowServiceManager { /** * Sets the availability of the given system service. If the service is set as unavailable, * subsequent calls to {@link Context#getSystemService} for that service will return {@code null}. + * + * <p>A service is considered available by default. */ - public static void setServiceAvailability(String service, boolean available) { + public static synchronized void setServiceAvailability(String service, boolean available) { if (available) { unavailableServices.remove(service); } else { @@ -334,8 +365,18 @@ public class ShadowServiceManager { } } + @GuardedBy("ShadowServiceManager.class") + @Nullable + private static IBinder getBinderForService(String name) { + BinderService binderService = binderServices.get(name); + if (binderService == null) { + return null; + } + return binderService.getBinder(); + } + @Resetter - public static void reset() { + public static synchronized void reset() { unavailableServices.clear(); } } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSettings.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSettings.java index 16cad3b6a..00149ac68 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSettings.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSettings.java @@ -1,8 +1,6 @@ package org.robolectric.shadows; import static android.os.Build.VERSION_CODES.JELLY_BEAN; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; -import static android.os.Build.VERSION_CODES.KITKAT; import static android.os.Build.VERSION_CODES.LOLLIPOP; import static android.os.Build.VERSION_CODES.M; import static android.os.Build.VERSION_CODES.P; @@ -15,7 +13,6 @@ import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.location.LocationManager; -import android.os.Build; import android.provider.Settings; import android.provider.Settings.Secure; import android.provider.Settings.SettingNotFoundException; @@ -74,7 +71,7 @@ public class ShadowSettings { return get(String.class, name).orElse(null); } - @Implementation(minSdk = JELLY_BEAN_MR1) + @Implementation protected static String getStringForUser(ContentResolver cr, String name, int userHandle) { return get(String.class, name).orElse(null); } @@ -161,7 +158,7 @@ public class ShadowSettings { private static final Map<String, Optional<Object>> dataMap = new ConcurrentHashMap<>(SECURE_DEFAULTS); - @Implementation(minSdk = JELLY_BEAN_MR1, maxSdk = P) + @Implementation(maxSdk = P) @SuppressWarnings("robolectric.ShadowReturnTypeMismatch") protected static boolean setLocationProviderEnabledForUser( ContentResolver cr, String provider, boolean enabled, int uid) { @@ -250,13 +247,13 @@ public class ShadowSettings { return true; } - @Implementation(minSdk = JELLY_BEAN_MR1) + @Implementation protected static int getIntForUser(ContentResolver cr, String name, int def, int userHandle) { // ignore userhandle return getInt(cr, name, def); } - @Implementation(minSdk = JELLY_BEAN_MR1) + @Implementation protected static int getIntForUser(ContentResolver cr, String name, int userHandle) throws SettingNotFoundException { // ignore userhandle @@ -266,7 +263,6 @@ public class ShadowSettings { @Implementation protected static int getInt(ContentResolver cr, String name) throws SettingNotFoundException { if (Settings.Secure.LOCATION_MODE.equals(name) - && RuntimeEnvironment.getApiLevel() >= KITKAT && RuntimeEnvironment.getApiLevel() < P) { // Map from to underlying location provider storage API to location mode return reflector(SettingsSecureReflector.class).getLocationModeForUser(cr, 0); @@ -278,7 +274,6 @@ public class ShadowSettings { @Implementation protected static int getInt(ContentResolver cr, String name, int def) { if (Settings.Secure.LOCATION_MODE.equals(name) - && RuntimeEnvironment.getApiLevel() >= KITKAT && RuntimeEnvironment.getApiLevel() < P) { // Map from to underlying location provider storage API to location mode return reflector(SettingsSecureReflector.class).getLocationModeForUser(cr, 0); @@ -297,7 +292,7 @@ public class ShadowSettings { return get(String.class, name).orElse(null); } - @Implementation(minSdk = JELLY_BEAN_MR1) + @Implementation protected static String getStringForUser(ContentResolver cr, String name, int userHandle) { return getString(cr, name); } @@ -354,7 +349,7 @@ public class ShadowSettings { } } - @Implements(value = Settings.Global.class, minSdk = JELLY_BEAN_MR1) + @Implements(value = Settings.Global.class) public static class ShadowGlobal { private static final ImmutableMap<String, Optional<Object>> DEFAULTS = ImmutableMap.<String, Optional<Object>>builder() @@ -387,7 +382,7 @@ public class ShadowSettings { return get(String.class, name).orElse(null); } - @Implementation(minSdk = JELLY_BEAN_MR1) + @Implementation protected static String getStringForUser(ContentResolver cr, String name, int userHandle) { return getString(cr, name); } @@ -514,13 +509,10 @@ public class ShadowSettings { * @param adbEnabled new value for whether adb is enabled */ public static void setAdbEnabled(boolean adbEnabled) { - // This setting moved from Secure to Global in JELLY_BEAN_MR1 - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { - Settings.Global.putInt( - RuntimeEnvironment.getApplication().getContentResolver(), - Settings.Global.ADB_ENABLED, - adbEnabled ? 1 : 0); - } + Settings.Global.putInt( + RuntimeEnvironment.getApplication().getContentResolver(), + Settings.Global.ADB_ENABLED, + adbEnabled ? 1 : 0); // Support all clients by always setting the Secure version of the setting Settings.Secure.putInt( RuntimeEnvironment.getApplication().getContentResolver(), @@ -538,12 +530,10 @@ public class ShadowSettings { // This setting moved from Secure to Global in JELLY_BEAN_MR1 and then moved it back to Global // in LOLLIPOP. Support all clients by always setting this field on all versions >= // JELLY_BEAN_MR1. - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { - Settings.Global.putInt( - RuntimeEnvironment.getApplication().getContentResolver(), - Settings.Global.INSTALL_NON_MARKET_APPS, - installNonMarketApps ? 1 : 0); - } + Settings.Global.putInt( + RuntimeEnvironment.getApplication().getContentResolver(), + Settings.Global.INSTALL_NON_MARKET_APPS, + installNonMarketApps ? 1 : 0); // Always set the Secure version of the setting Settings.Secure.putInt( RuntimeEnvironment.getApplication().getContentResolver(), diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSliceManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSliceManager.java index 74d64181b..8f8ef2517 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSliceManager.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSliceManager.java @@ -2,6 +2,7 @@ package org.robolectric.shadows; import static android.os.Build.VERSION_CODES.P; +import android.annotation.NonNull; import android.app.slice.SliceManager; import android.app.slice.SliceSpec; import android.content.Context; @@ -9,7 +10,6 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.net.Uri; import android.os.Handler; -import androidx.annotation.NonNull; import com.google.common.collect.ImmutableSet; import java.util.ArrayList; import java.util.Collection; diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSmsManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSmsManager.java index 95232dbd0..81a1521fe 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSmsManager.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSmsManager.java @@ -1,18 +1,17 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2; import static android.os.Build.VERSION_CODES.LOLLIPOP; import static android.os.Build.VERSION_CODES.LOLLIPOP_MR1; import static android.os.Build.VERSION_CODES.R; import static android.os.Build.VERSION_CODES.S; +import android.annotation.Nullable; import android.app.PendingIntent; import android.content.Context; import android.net.Uri; import android.os.Bundle; import android.telephony.SmsManager; import android.text.TextUtils; -import androidx.annotation.Nullable; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -22,7 +21,7 @@ import org.robolectric.annotation.Implements; import org.robolectric.annotation.Resetter; import org.robolectric.util.ReflectionHelpers; -@Implements(value = SmsManager.class, minSdk = JELLY_BEAN_MR2) +@Implements(value = SmsManager.class) public class ShadowSmsManager { private String smscAddress; diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSpeechRecognizer.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSpeechRecognizer.java index 4f60510a7..dc206ae80 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSpeechRecognizer.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSpeechRecognizer.java @@ -2,6 +2,9 @@ package org.robolectric.shadows; import static org.robolectric.util.reflector.Reflector.reflector; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.RequiresApi; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -13,9 +16,6 @@ import android.os.Message; import android.speech.IRecognitionService; import android.speech.RecognitionListener; import android.speech.SpeechRecognizer; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.RequiresApi; import com.google.common.base.Preconditions; import java.util.Queue; import java.util.concurrent.Executor; @@ -28,6 +28,7 @@ import org.robolectric.util.reflector.Accessor; import org.robolectric.util.reflector.Direct; import org.robolectric.util.reflector.ForType; import org.robolectric.util.reflector.Static; +import org.robolectric.versioning.AndroidVersions.U; /** Robolectric shadow for SpeechRecognizer. */ @Implements(value = SpeechRecognizer.class, looseSignatures = true) @@ -112,7 +113,7 @@ public class ShadowSpeechRecognizer { * Handles changing the listener and allows access to the internal listener to trigger events and * sets the latest SpeechRecognizer. */ - @Implementation + @Implementation(maxSdk = U.SDK_INT) // TODO(hoisie): Update this to support Android V protected void handleChangeListener(RecognitionListener listener) { recognitionListener = listener; } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowStatFs.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowStatFs.java index d685d5b3c..06f0de565 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowStatFs.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowStatFs.java @@ -1,7 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2; - import android.os.StatFs; import java.io.File; import java.util.Map; @@ -43,22 +41,22 @@ public class ShadowStatFs { return stat.freeBlocks; } - @Implementation(minSdk = JELLY_BEAN_MR2) + @Implementation protected long getFreeBlocksLong() { return stat.freeBlocks; } - @Implementation(minSdk = JELLY_BEAN_MR2) + @Implementation protected long getFreeBytes() { return getBlockSizeLong() * getFreeBlocksLong(); } - @Implementation(minSdk = JELLY_BEAN_MR2) + @Implementation protected long getAvailableBytes() { return getBlockSizeLong() * getAvailableBlocksLong(); } - @Implementation(minSdk = JELLY_BEAN_MR2) + @Implementation protected long getTotalBytes() { return getBlockSizeLong() * getBlockCountLong(); } @@ -88,17 +86,17 @@ public class ShadowStatFs { } /** Robolectric always uses a block size of 4096. */ - @Implementation(minSdk = JELLY_BEAN_MR2) + @Implementation protected long getBlockSizeLong() { return BLOCK_SIZE; } - @Implementation(minSdk = JELLY_BEAN_MR2) + @Implementation protected long getBlockCountLong() { return stat.blockCount; } - @Implementation(minSdk = JELLY_BEAN_MR2) + @Implementation protected long getAvailableBlocksLong() { return stat.availableBlocks; } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowStatusBarManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowStatusBarManager.java index 7282eee21..5fb1b89ba 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowStatusBarManager.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowStatusBarManager.java @@ -6,7 +6,7 @@ import static android.os.Build.VERSION_CODES.TIRAMISU; import static org.robolectric.util.reflector.Reflector.reflector; import android.app.StatusBarManager; -import androidx.annotation.VisibleForTesting; +import com.android.internal.annotations.VisibleForTesting; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; import org.robolectric.util.reflector.Accessor; diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowStorageManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowStorageManager.java index 929c36aef..0a79ee7c3 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowStorageManager.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowStorageManager.java @@ -16,6 +16,7 @@ import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.HiddenApi; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; +import org.robolectric.annotation.Resetter; import org.robolectric.shadow.api.Shadow; /** @@ -25,11 +26,11 @@ import org.robolectric.shadow.api.Shadow; public class ShadowStorageManager { private static boolean isFileEncryptionSupported = true; - private final List<StorageVolume> storageVolumeList = new ArrayList<>(); + private static final List<StorageVolume> storageVolumeList = new ArrayList<>(); @Implementation(minSdk = M) protected static StorageVolume[] getVolumeList(int userId, int flags) { - return new StorageVolume[0]; + return storageVolumeList.toArray(new StorageVolume[0]); } /** @@ -110,4 +111,9 @@ public class ShadowStorageManager { Shadow.extract(RuntimeEnvironment.getApplication().getSystemService(UserManager.class)); return extract.isUserUnlocked(); } + + @Resetter + public static void reset() { + storageVolumeList.clear(); + } } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSubscriptionManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSubscriptionManager.java index 27b787d96..41b349b26 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSubscriptionManager.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSubscriptionManager.java @@ -131,11 +131,19 @@ public class ShadowSubscriptionManager { * {@link #setActiveSubscriptionInfoList}. */ private List<SubscriptionInfo> subscriptionList = new ArrayList<>(); + + /** + * Cache of {@link SubscriptionInfo} used by {@link #getAccessibleSubscriptionInfoList}. Managed + * by {@link #setAccessibleSubscriptionInfos}. + */ + private List<SubscriptionInfo> accessibleSubscriptionList = new ArrayList<>(); + /** * Cache of {@link SubscriptionInfo} used by {@link #getAvailableSubscriptionInfoList}. Managed by * {@link #setAvailableSubscriptionInfos}. */ private List<SubscriptionInfo> availableSubscriptionList = new ArrayList<>(); + /** * List of listeners to be notified if the list of {@link SubscriptionInfo} changes. Managed by * {@link #addOnSubscriptionsChangedListener} and {@link removeOnSubscriptionsChangedListener}. @@ -158,6 +166,15 @@ public class ShadowSubscriptionManager { } /** + * Returns the accessible list of {@link SubscriptionInfo} that were set via {@link + * #setAccessibleSubscriptionInfoList}. + */ + @Implementation(minSdk = O_MR1) + protected List<SubscriptionInfo> getAccessibleSubscriptionInfoList() { + return accessibleSubscriptionList; + } + + /** * Returns the available list of {@link SubscriptionInfo} that were set via {@link * #setAvailableSubscriptionInfoList}. */ @@ -234,6 +251,12 @@ public class ShadowSubscriptionManager { * Sets the active list of {@link SubscriptionInfo}. This call internally triggers {@link * OnSubscriptionsChangedListener#onSubscriptionsChanged()} to all the listeners. * + * <p>"Active" here means subscriptions which are currently mapped to a live modem stack in the + * device (i.e. the modem will attempt to use them to connect to nearby towers), and they are + * expected to have {@link SubscriptionInfo#getSimSlotIndex()} >= 0. A subscription being "active" + * in the device does NOT have any relation to a carrier's "activation" process for subscribers' + * SIMs. + * * @param list - The subscription info list, can be null. */ public void setActiveSubscriptionInfoList(List<SubscriptionInfo> list) { @@ -242,9 +265,34 @@ public class ShadowSubscriptionManager { } /** - * Sets the active list of {@link SubscriptionInfo}. This call internally triggers {@link + * Sets the accessible list of {@link SubscriptionInfo}. This call internally triggers {@link * OnSubscriptionsChangedListener#onSubscriptionsChanged()} to all the listeners. * + * <p>"Accessible" here means subscriptions which are eSIM ({@link SubscriptionInfo#isEmbedded}) + * and "owned" by the calling app, i.e. by {@link + * SubscriptionManager#canManageSubscription(SubscriptionInfo)}. They may be active, or + * installed-but-inactive. This is generally intended to be called by carrier apps that directly + * manage their own eSIM profiles on the device in concert with {@link + * android.telephony.EuiccManager}. + * + * @param list - The subscription info list, can be null. + */ + public void setAccessibleSubscriptionInfoList(List<SubscriptionInfo> list) { + accessibleSubscriptionList = list; + dispatchOnSubscriptionsChanged(); + } + + /** + * Sets the available list of {@link SubscriptionInfo}. This call internally triggers {@link + * OnSubscriptionsChangedListener#onSubscriptionsChanged()} to all the listeners. + * + * <p>"Available" here means all active subscriptions (see {@link #setActiveSubscriptionInfoList}) + * combined with all installed-but-inactive eSIM subscriptions (similar to {@link + * #setAccessibleSubscriptionInfoList}, but not filtered to one particular app's "ownership" + * rights for subscriptions). This is generally intended to be called by system components such as + * the eSIM LPA or Settings that allow the user to manage all subscriptions on the device through + * some system-provided user interface. + * * @param list - The subscription info list, can be null. */ public void setAvailableSubscriptionInfoList(List<SubscriptionInfo> list) { @@ -265,7 +313,19 @@ public class ShadowSubscriptionManager { } /** - * Sets the active list of {@link SubscriptionInfo}. This call internally triggers {@link + * Sets the accessible list of {@link SubscriptionInfo}. This call internally triggers {@link + * OnSubscriptionsChangedListener#onSubscriptionsChanged()} to all the listeners. + */ + public void setAccessibleSubscriptionInfos(SubscriptionInfo... infos) { + if (infos == null) { + setAccessibleSubscriptionInfoList(ImmutableList.of()); + } else { + setAccessibleSubscriptionInfoList(Arrays.asList(infos)); + } + } + + /** + * Sets the available list of {@link SubscriptionInfo}. This call internally triggers {@link * OnSubscriptionsChangedListener#onSubscriptionsChanged()} to all the listeners. */ public void setAvailableSubscriptionInfos(SubscriptionInfo... infos) { diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSurface.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSurface.java index ba8899bb1..be5ebeda7 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSurface.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSurface.java @@ -1,7 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2; import static android.os.Build.VERSION_CODES.KITKAT; import static android.os.Build.VERSION_CODES.M; import static android.os.Build.VERSION_CODES.Q; @@ -52,7 +50,7 @@ public class ShadowSurface { return surfaceTexture; } - @Implementation(minSdk = JELLY_BEAN_MR1) + @Implementation protected void finalize() throws Throwable { // Suppress noisy CloseGuard errors that may exist in SDK 17+. CloseGuard closeGuard = surfaceReflector.getCloseGuard(); @@ -129,12 +127,12 @@ public class ShadowSurface { canvasLocked.set(false); } - @Implementation(minSdk = JELLY_BEAN_MR2) + @Implementation protected static Object nativeCreateFromSurfaceTexture(Object surfaceTexture) { return nativeObject.incrementAndGet(); } - @Implementation(minSdk = JELLY_BEAN_MR2) + @Implementation protected static Object nativeCreateFromSurfaceControl(Object surfaceControlNativeObject) { return nativeObject.incrementAndGet(); } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSurfaceControl.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSurfaceControl.java index b279892c9..0ec68c381 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSurfaceControl.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSurfaceControl.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2; import static android.os.Build.VERSION_CODES.N_MR1; import static android.os.Build.VERSION_CODES.O; import static android.os.Build.VERSION_CODES.P; @@ -22,7 +21,7 @@ import org.robolectric.util.reflector.ForType; import org.robolectric.versioning.AndroidVersions.U; /** Shadow for {@link android.view.SurfaceControl} */ -@Implements(value = SurfaceControl.class, isInAndroidSdk = false, minSdk = JELLY_BEAN_MR2) +@Implements(value = SurfaceControl.class, isInAndroidSdk = false) public class ShadowSurfaceControl { private static final AtomicInteger nativeObject = new AtomicInteger(); diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSystemClock.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSystemClock.java index 03d756c4e..bba7005c4 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSystemClock.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSystemClock.java @@ -83,6 +83,18 @@ public abstract class ShadowSystemClock { SystemClock.setCurrentTimeMillis(SystemClock.uptimeMillis() + duration.toMillis()); } + /** + * In a deep sleep scenario, {@param elapsedRealtime} is advanced for this duration when in deep + * sleep whilst {@param uptime} maintains its original value. + * + * <p>May only be used for {@link LooperMode.Mode#PAUSED}. For {@link LooperMode.Mode#LEGACY}, + * {@param elapsedRealtime} is equal to {@param uptime}. + */ + public static void simulateDeepSleep(Duration duration) { + assertLooperMode(Mode.PAUSED); + ShadowPausedSystemClock.deepSleep(duration.toMillis()); + } + @Implementation(minSdk = Q) protected static Object currentGnssTimeClock() { if (gnssTimeAvailable) { diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSystemHealthManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSystemHealthManager.java new file mode 100644 index 000000000..4eff63dfb --- /dev/null +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSystemHealthManager.java @@ -0,0 +1,52 @@ +package org.robolectric.shadows; + +import static android.os.Build.VERSION_CODES.N; + +import android.os.Process; +import android.os.health.HealthStats; +import android.os.health.SystemHealthManager; +import java.util.HashMap; +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; + +/** Shadow for {@link android.os.health.SystemHealthManager} */ +@Implements(value = SystemHealthManager.class, minSdk = N) +public class ShadowSystemHealthManager { + + private static final HealthStats DEFAULT_HEALTH_STATS = + HealthStatsBuilder.newBuilder().setDataType("default_health_stats").build(); + + private final HashMap<Integer, HealthStats> uidToHealthStats = new HashMap<>(); + + @Implementation + protected HealthStats takeMyUidSnapshot() { + return takeUidSnapshot(Process.myUid()); + } + + @Implementation + protected HealthStats takeUidSnapshot(int uid) { + return uidToHealthStats.getOrDefault(uid, DEFAULT_HEALTH_STATS); + } + + @Implementation + protected HealthStats[] takeUidSnapshots(int[] uids) { + HealthStats[] stats = new HealthStats[uids.length]; + for (int i = 0; i < uids.length; i++) { + stats[i] = takeUidSnapshot(uids[i]); + } + return stats; + } + + /** + * Add {@link HealthStats} for the given UID. Calling {@link SystemHealthManager#takeUidSnapshot} + * with the given UID will return this HealthStats object. + */ + public void addHealthStatsForUid(int uid, HealthStats stats) { + uidToHealthStats.put(uid, stats); + } + + /** The same as {@code addHealthStatsForUid(android.os.Process.myUid(), stats)}. */ + public void addHealthStats(HealthStats stats) { + addHealthStatsForUid(Process.myUid(), stats); + } +} diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSystemVibrator.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSystemVibrator.java index 41fbf4e7d..1b44991e3 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSystemVibrator.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSystemVibrator.java @@ -1,7 +1,6 @@ package org.robolectric.shadows; import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2; import static android.os.Build.VERSION_CODES.KITKAT_WATCH; import static android.os.Build.VERSION_CODES.LOLLIPOP; import static android.os.Build.VERSION_CODES.N_MR1; @@ -47,7 +46,7 @@ public class ShadowSystemVibrator extends ShadowVibrator { recordVibratePattern(pattern, repeat); } - @Implementation(minSdk = JELLY_BEAN_MR2, maxSdk = KITKAT_WATCH) + @Implementation(maxSdk = KITKAT_WATCH) protected void vibrate(int owningUid, String owningPackage, long[] pattern, int repeat) { recordVibratePattern(pattern, repeat); } @@ -63,7 +62,7 @@ public class ShadowSystemVibrator extends ShadowVibrator { recordVibrate(milliseconds); } - @Implementation(minSdk = JELLY_BEAN_MR2, maxSdk = KITKAT_WATCH) + @Implementation(maxSdk = KITKAT_WATCH) public void vibrate(int owningUid, String owningPackage, long milliseconds) { recordVibrate(milliseconds); } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTelecomManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTelecomManager.java index 87dbf83e1..2a8459609 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTelecomManager.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTelecomManager.java @@ -1,13 +1,16 @@ package org.robolectric.shadows; +import static android.os.Build.VERSION.SDK_INT; import static android.os.Build.VERSION_CODES.LOLLIPOP; import static android.os.Build.VERSION_CODES.LOLLIPOP_MR1; import static android.os.Build.VERSION_CODES.M; import static android.os.Build.VERSION_CODES.N; import static android.os.Build.VERSION_CODES.O; import static android.os.Build.VERSION_CODES.R; +import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE; import static com.google.common.base.Verify.verifyNotNull; +import android.annotation.Nullable; import android.annotation.SystemApi; import android.annotation.TargetApi; import android.bluetooth.BluetoothDevice; @@ -27,7 +30,6 @@ import android.telecom.TelecomManager; import android.telecom.VideoProfile; import android.text.TextUtils; import android.util.ArrayMap; -import androidx.annotation.Nullable; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import java.util.ArrayList; @@ -245,9 +247,22 @@ public class ShadowTelecomManager { @Implementation protected void registerPhoneAccount(PhoneAccount account) { + account = adjustCapabilities(account); accounts.put(account.getAccountHandle(), account); } + private PhoneAccount adjustCapabilities(PhoneAccount account) { + // Mirror the capabilities adjustments done in com.android.server.telecom.PhoneAccountRegistrar. + if (SDK_INT >= UPSIDE_DOWN_CAKE + && account.hasCapabilities(PhoneAccount.CAPABILITY_SUPPORTS_TRANSACTIONAL_OPERATIONS) + && !account.hasCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED)) { + return account.toBuilder() + .setCapabilities(account.getCapabilities() | PhoneAccount.CAPABILITY_SELF_MANAGED) + .build(); + } + return account; + } + @Implementation protected void unregisterPhoneAccount(PhoneAccountHandle accountHandle) { accounts.remove(accountHandle); diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTelephony.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTelephony.java index 7f3f57aec..aba5d2ad2 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTelephony.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTelephony.java @@ -2,16 +2,15 @@ package org.robolectric.shadows; import android.annotation.Nullable; import android.content.Context; -import android.os.Build.VERSION_CODES; import android.provider.Telephony; import android.provider.Telephony.Sms; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; import org.robolectric.annotation.Resetter; -@Implements(value = Telephony.class, minSdk = VERSION_CODES.KITKAT) +@Implements(value = Telephony.class) public class ShadowTelephony { - @Implements(value = Sms.class, minSdk = VERSION_CODES.KITKAT) + @Implements(value = Sms.class) public static class ShadowSms { @Nullable private static String defaultSmsPackage; diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTelephonyManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTelephonyManager.java index 7b2a756b2..871e9f3a8 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTelephonyManager.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTelephonyManager.java @@ -1,8 +1,6 @@ package org.robolectric.shadows; import static android.content.pm.PackageManager.PERMISSION_GRANTED; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2; import static android.os.Build.VERSION_CODES.LOLLIPOP; import static android.os.Build.VERSION_CODES.LOLLIPOP_MR1; import static android.os.Build.VERSION_CODES.M; @@ -54,19 +52,17 @@ import android.telephony.TelephonyManager.CellInfoCallback; import android.telephony.VisualVoicemailSmsFilterSettings; import android.telephony.emergency.EmergencyNumber; import android.text.TextUtils; -import android.util.SparseArray; -import android.util.SparseBooleanArray; -import android.util.SparseIntArray; import com.google.common.base.Ascii; import com.google.common.base.Preconditions; import com.google.common.base.Predicate; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import java.util.ArrayList; import java.util.Collections; -import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; import java.util.Map; @@ -88,17 +84,24 @@ public class ShadowTelephonyManager { @RealObject protected TelephonyManager realTelephonyManager; - private final Map<PhoneStateListener, Integer> phoneStateRegistrations = new HashMap<>(); + private final Map<PhoneStateListener, Integer> phoneStateRegistrations = + Collections.synchronizedMap(new LinkedHashMap<>()); private final /*List<TelephonyCallback>*/ List<Object> telephonyCallbackRegistrations = new ArrayList<>(); - private final Map<Integer, String> slotIndexToDeviceId = new HashMap<>(); - private final Map<Integer, String> slotIndexToImei = new HashMap<>(); - private final Map<Integer, String> slotIndexToMeid = new HashMap<>(); - private final Map<PhoneAccountHandle, Boolean> voicemailVibrationEnabledMap = new HashMap<>(); - private final Map<PhoneAccountHandle, Uri> voicemailRingtoneUriMap = new HashMap<>(); - private final Map<PhoneAccountHandle, TelephonyManager> phoneAccountToTelephonyManagers = - new HashMap<>(); - private final Map<PhoneAccountHandle, Integer> phoneAccountHandleSubscriptionId = new HashMap<>(); + private static final Map<Integer, String> slotIndexToDeviceId = + Collections.synchronizedMap(new LinkedHashMap<>()); + private static final Map<Integer, String> slotIndexToImei = + Collections.synchronizedMap(new LinkedHashMap<>()); + private static final Map<Integer, String> slotIndexToMeid = + Collections.synchronizedMap(new LinkedHashMap<>()); + private static final Map<PhoneAccountHandle, Boolean> voicemailVibrationEnabledMap = + Collections.synchronizedMap(new LinkedHashMap<>()); + private static final Map<PhoneAccountHandle, Uri> voicemailRingtoneUriMap = + Collections.synchronizedMap(new LinkedHashMap<>()); + private static final Map<PhoneAccountHandle, TelephonyManager> phoneAccountToTelephonyManagers = + Collections.synchronizedMap(new LinkedHashMap<>()); + private static final Map<PhoneAccountHandle, Integer> phoneAccountHandleSubscriptionId = + Collections.synchronizedMap(new LinkedHashMap<>()); private PhoneStateListener lastListener; private /*TelephonyCallback*/ Object lastTelephonyCallback; @@ -117,47 +120,53 @@ public class ShadowTelephonyManager { private String simOperator = ""; private String simOperatorName; private String simSerialNumber; - private boolean readPhoneStatePermission = true; + private static volatile boolean readPhoneStatePermission = true; private int phoneType = TelephonyManager.PHONE_TYPE_GSM; private String line1Number; private int networkType; private int dataNetworkType = TelephonyManager.NETWORK_TYPE_UNKNOWN; private int voiceNetworkType = TelephonyManager.NETWORK_TYPE_UNKNOWN; - private List<CellInfo> allCellInfo = Collections.emptyList(); - private List<CellInfo> callbackCellInfos = null; - private CellLocation cellLocation = null; + private static volatile List<CellInfo> allCellInfo = Collections.emptyList(); + private static volatile List<CellInfo> callbackCellInfos = null; + private static volatile CellLocation cellLocation = null; private int callState = CALL_STATE_IDLE; private int dataState = TelephonyManager.DATA_DISCONNECTED; private int dataActivity = TelephonyManager.DATA_ACTIVITY_NONE; private String incomingPhoneNumber = null; - private boolean isSmsCapable = true; - private boolean voiceCapable = true; + private static volatile boolean isSmsCapable = true; + private static volatile boolean voiceCapable = true; private String voiceMailNumber; private String voiceMailAlphaTag; - private int phoneCount = 1; - private int activeModemCount = 1; - private Map<Integer, TelephonyManager> subscriptionIdsToTelephonyManagers = new HashMap<>(); + private static volatile int phoneCount = 1; + private static volatile int activeModemCount = 1; + private static volatile Map<Integer, TelephonyManager> subscriptionIdsToTelephonyManagers = + Collections.synchronizedMap(new LinkedHashMap<>()); private PersistableBundle carrierConfig; private ServiceState serviceState; private boolean isNetworkRoaming; - private final SparseIntArray simStates = new SparseIntArray(); - private final SparseIntArray currentPhoneTypes = new SparseIntArray(); - private final SparseArray<List<String>> carrierPackageNames = new SparseArray<>(); - private final Map<Integer, String> simCountryIsoMap = new HashMap<>(); + private static final Map<Integer, Integer> simStates = + Collections.synchronizedMap(new LinkedHashMap<>()); + private static final Map<Integer, Integer> currentPhoneTypes = + Collections.synchronizedMap(new LinkedHashMap<>()); + private static final Map<Integer, List<String>> carrierPackageNames = + Collections.synchronizedMap(new LinkedHashMap<>()); + private static final Map<Integer, String> simCountryIsoMap = + Collections.synchronizedMap(new LinkedHashMap<>()); private int simCarrierId; private int carrierIdFromSimMccMnc; private String subscriberId; - private /*UiccSlotInfo[]*/ Object uiccSlotInfos; - private /*UiccCardInfo[]*/ Object uiccCardsInfo = new ArrayList<>(); + private static volatile /*UiccSlotInfo[]*/ Object uiccSlotInfos; + private static volatile /*List<UiccCardInfo>*/ Object uiccCardsInfo = new ArrayList<>(); private String visualVoicemailPackageName = null; private SignalStrength signalStrength; private boolean dataEnabled = false; private final Set<Integer> dataDisabledReasons = new HashSet<>(); private boolean isRttSupported; - private boolean isTtyModeSupported; - private final SparseBooleanArray subIdToHasCarrierPrivileges = new SparseBooleanArray(); - private final List<String> sentDialerSpecialCodes = new ArrayList<>(); - private boolean hearingAidCompatibilitySupported = false; + private static volatile boolean isTtyModeSupported; + private static final Map<Integer, Boolean> subIdToHasCarrierPrivileges = + Collections.synchronizedMap(new LinkedHashMap<>()); + private static final List<String> sentDialerSpecialCodes = new ArrayList<>(); + private static volatile boolean hearingAidCompatibilitySupported = false; private int requestCellInfoUpdateErrorCode = 0; private Throwable requestCellInfoUpdateDetail = null; private Object telephonyDisplayInfo; @@ -165,7 +174,7 @@ public class ShadowTelephonyManager { private static int callComposerStatus = 0; private VisualVoicemailSmsParams lastVisualVoicemailSmsParams; private VisualVoicemailSmsFilterSettings visualVoicemailSmsFilterSettings; - private boolean emergencyCallbackMode; + private static volatile boolean emergencyCallbackMode; private static Map<Integer, List<EmergencyNumber>> emergencyNumbersList; /** @@ -176,17 +185,45 @@ public class ShadowTelephonyManager { */ private Object callback; - private /*PhoneCapability*/ Object phoneCapability; + private static volatile /*PhoneCapability*/ Object phoneCapability; - { - resetSimStates(); - resetSimCountryIsos(); + static { + resetAllSimStates(); + resetAllSimCountryIsos(); } @Resetter public static void reset() { + subscriptionIdsToTelephonyManagers.clear(); + resetAllSimStates(); + currentPhoneTypes.clear(); + carrierPackageNames.clear(); + resetAllSimCountryIsos(); + slotIndexToDeviceId.clear(); + slotIndexToImei.clear(); + slotIndexToMeid.clear(); + voicemailVibrationEnabledMap.clear(); + voicemailRingtoneUriMap.clear(); + phoneAccountToTelephonyManagers.clear(); + phoneAccountHandleSubscriptionId.clear(); + subIdToHasCarrierPrivileges.clear(); + allCellInfo = Collections.emptyList(); + cellLocation = null; + callbackCellInfos = null; + uiccSlotInfos = null; + uiccCardsInfo = new ArrayList<>(); callComposerStatus = 0; + emergencyCallbackMode = false; emergencyNumbersList = null; + phoneCapability = null; + isTtyModeSupported = false; + readPhoneStatePermission = true; + isSmsCapable = true; + voiceCapable = true; + phoneCount = 1; + activeModemCount = 1; + sentDialerSpecialCodes.clear(); + hearingAidCompatibilitySupported = false; } @Implementation(minSdk = S) @@ -217,7 +254,7 @@ public class ShadowTelephonyManager { } public void setPhoneCapability(/*PhoneCapability*/ Object phoneCapability) { - this.phoneCapability = phoneCapability; + ShadowTelephonyManager.phoneCapability = phoneCapability; } @Implementation(minSdk = S) @@ -548,11 +585,21 @@ public class ShadowTelephonyManager { } /** Clears {@code subId} to simCountryIso mapping and resets to default state. */ - public void resetSimCountryIsos() { + public static void resetAllSimCountryIsos() { simCountryIsoMap.clear(); simCountryIsoMap.put(0, ""); } + /** + * Clears {@code subId} to simCountryIso mapping and resets to default state. + * + * @deprecated for resetAllSimCountryIsos + */ + @Deprecated + public void resetSimCountryIsos() { + resetAllSimCountryIsos(); + } + @Implementation protected int getSimState() { return getSimState(/* slotIndex= */ 0); @@ -570,12 +617,12 @@ public class ShadowTelephonyManager { @Implementation(minSdk = O) protected int getSimState(int slotIndex) { - return simStates.get(slotIndex, TelephonyManager.SIM_STATE_UNKNOWN); + return simStates.getOrDefault(slotIndex, TelephonyManager.SIM_STATE_UNKNOWN); } /** Sets the UICC slots information returned by {@link #getUiccSlotsInfo()}. */ public void setUiccSlotsInfo(/*UiccSlotInfo[]*/ Object uiccSlotsInfos) { - this.uiccSlotInfos = uiccSlotsInfos; + ShadowTelephonyManager.uiccSlotInfos = uiccSlotsInfos; } /** Returns the UICC slots information set by {@link #setUiccSlotsInfo}. */ @@ -586,25 +633,35 @@ public class ShadowTelephonyManager { } /** Sets the UICC cards information returned by {@link #getUiccCardsInfo()}. */ - public void setUiccCardsInfo(/*UiccCardsInfo[]*/ Object uiccCardsInfo) { - this.uiccCardsInfo = uiccCardsInfo; + public void setUiccCardsInfo(/*List<UiccCardInfo>*/ Object uiccCardsInfo) { + ShadowTelephonyManager.uiccCardsInfo = uiccCardsInfo; } /** Returns the UICC cards information set by {@link #setUiccCardsInfo}. */ @Implementation(minSdk = Q) @HiddenApi - protected /*UiccSlotInfo[]*/ Object getUiccCardsInfo() { + protected /*List<UiccCardInfo>*/ Object getUiccCardsInfo() { return uiccCardsInfo; } /** Clears {@code slotIndex} to state mapping and resets to default state. */ - public void resetSimStates() { + public static void resetAllSimStates() { simStates.clear(); simStates.put(0, TelephonyManager.SIM_STATE_READY); } + /** + * Clears {@code slotIndex} to state mapping and resets to default state. + * + * @deprecated use resetAllSimStates() + */ + @Deprecated + public void resetSimStates() { + resetAllSimStates(); + } + public void setReadPhoneStatePermission(boolean readPhoneStatePermission) { - this.readPhoneStatePermission = readPhoneStatePermission; + ShadowTelephonyManager.readPhoneStatePermission = readPhoneStatePermission; } private void checkReadPhoneStatePermission() { @@ -712,18 +769,16 @@ public class ShadowTelephonyManager { this.voiceNetworkType = voiceNetworkType; } - @Implementation(minSdk = JELLY_BEAN_MR1) + @Implementation protected List<CellInfo> getAllCellInfo() { return allCellInfo; } public void setAllCellInfo(List<CellInfo> allCellInfo) { - this.allCellInfo = allCellInfo; + ShadowTelephonyManager.allCellInfo = allCellInfo; - if (VERSION.SDK_INT >= JELLY_BEAN_MR1) { - for (PhoneStateListener listener : getListenersForFlags(LISTEN_CELL_INFO)) { - listener.onCellInfoChanged(allCellInfo); - } + for (PhoneStateListener listener : getListenersForFlags(LISTEN_CELL_INFO)) { + listener.onCellInfoChanged(allCellInfo); } if (VERSION.SDK_INT >= S) { for (CellInfoListener listener : getCallbackForListener(CellInfoListener.class)) { @@ -739,6 +794,7 @@ public class ShadowTelephonyManager { @Implementation(minSdk = Q) protected void requestCellInfoUpdate(Object cellInfoExecutor, Object cellInfoCallback) { Executor executor = (Executor) cellInfoExecutor; + List<CellInfo> callbackCellInfos = ShadowTelephonyManager.callbackCellInfos; if (callbackCellInfos == null) { // ignore } else if (requestCellInfoUpdateErrorCode != 0 || requestCellInfoUpdateDetail != null) { @@ -768,7 +824,7 @@ public class ShadowTelephonyManager { * setAllCellInfo}. */ public void setCallbackCellInfos(List<CellInfo> callbackCellInfos) { - this.callbackCellInfos = callbackCellInfos; + ShadowTelephonyManager.callbackCellInfos = callbackCellInfos; } /** @@ -782,12 +838,11 @@ public class ShadowTelephonyManager { @Implementation protected CellLocation getCellLocation() { - return this.cellLocation; + return ShadowTelephonyManager.cellLocation; } public void setCellLocation(CellLocation cellLocation) { - this.cellLocation = cellLocation; - + ShadowTelephonyManager.cellLocation = cellLocation; for (PhoneStateListener listener : getListenersForFlags(LISTEN_CELL_LOCATION)) { listener.onCellLocationChanged(cellLocation); } @@ -798,7 +853,7 @@ public class ShadowTelephonyManager { } } - @Implementation(minSdk = JELLY_BEAN_MR2) + @Implementation protected String getGroupIdLevel1() { checkReadPhoneStatePermission(); return this.groupIdLevel1; @@ -810,13 +865,18 @@ public class ShadowTelephonyManager { @CallSuper protected void initListener(PhoneStateListener listener, int flags) { + // grab the state "atomically" before doing callbacks, in case they modify the state + String incomingPhoneNumber = this.incomingPhoneNumber; + List<CellInfo> allCellInfo = ShadowTelephonyManager.allCellInfo; + CellLocation cellLocation = ShadowTelephonyManager.cellLocation; + Object telephonyDisplayInfo = this.telephonyDisplayInfo; + ServiceState serviceState = this.serviceState; + if ((flags & LISTEN_CALL_STATE) != 0) { listener.onCallStateChanged(callState, incomingPhoneNumber); } if ((flags & LISTEN_CELL_INFO) != 0) { - if (VERSION.SDK_INT >= JELLY_BEAN_MR1) { - listener.onCellInfoChanged(allCellInfo); - } + listener.onCellInfoChanged(allCellInfo); } if ((flags & LISTEN_CELL_LOCATION) != 0) { listener.onCellLocationChanged(cellLocation); @@ -837,6 +897,12 @@ public class ShadowTelephonyManager { if (VERSION.SDK_INT < S) { return; } + // grab the state "atomically" before doing callbacks, in case they modify the state + int callState = this.callState; + List<CellInfo> allCellInfo = ShadowTelephonyManager.allCellInfo; + CellLocation cellLocation = ShadowTelephonyManager.cellLocation; + Object telephonyDisplayInfo = this.telephonyDisplayInfo; + ServiceState serviceState = this.serviceState; if (callback instanceof CallStateListener) { ((CallStateListener) callback).onCallStateChanged(callState); @@ -858,7 +924,7 @@ public class ShadowTelephonyManager { protected Iterable<PhoneStateListener> getListenersForFlags(int flags) { return Iterables.filter( - phoneStateRegistrations.keySet(), + ImmutableSet.copyOf(phoneStateRegistrations.keySet()), new Predicate<PhoneStateListener>() { @Override public boolean apply(PhoneStateListener input) { @@ -874,7 +940,7 @@ public class ShadowTelephonyManager { */ protected <T> Iterable<T> getCallbackForListener(Class<T> clazz) { // Only selects TelephonyCallback with matching class. - return Iterables.filter(telephonyCallbackRegistrations, clazz); + return Iterables.filter(ImmutableList.copyOf(telephonyCallbackRegistrations), clazz); } /** @@ -887,7 +953,7 @@ public class ShadowTelephonyManager { /** Sets the value returned by {@link TelephonyManager#isSmsCapable()}. */ public void setIsSmsCapable(boolean isSmsCapable) { - this.isSmsCapable = isSmsCapable; + ShadowTelephonyManager.isSmsCapable = isSmsCapable; } /** @@ -947,7 +1013,7 @@ public class ShadowTelephonyManager { /** Sets the value returned by {@link TelephonyManager#getPhoneCount()}. */ public void setPhoneCount(int phoneCount) { - this.phoneCount = phoneCount; + ShadowTelephonyManager.phoneCount = phoneCount; } /** Returns 1 by default or the value specified via {@link #setActiveModemCount(int)}. */ @@ -958,7 +1024,7 @@ public class ShadowTelephonyManager { /** Sets the value returned by {@link TelephonyManager#getActiveModemCount()}. */ public void setActiveModemCount(int activeModemCount) { - this.activeModemCount = activeModemCount; + ShadowTelephonyManager.activeModemCount = activeModemCount; } /** @@ -985,7 +1051,7 @@ public class ShadowTelephonyManager { /** Sets the value returned by {@link #isVoiceCapable()}. */ public void setVoiceCapable(boolean voiceCapable) { - this.voiceCapable = voiceCapable; + ShadowTelephonyManager.voiceCapable = voiceCapable; } /** @@ -1109,7 +1175,7 @@ public class ShadowTelephonyManager { @Implementation(minSdk = M) @HiddenApi protected int getCurrentPhoneType(int subId) { - return currentPhoneTypes.get(subId, TelephonyManager.PHONE_TYPE_NONE); + return currentPhoneTypes.getOrDefault(subId, TelephonyManager.PHONE_TYPE_NONE); } /** Sets the phone type for the given {@code subId}. */ @@ -1118,7 +1184,7 @@ public class ShadowTelephonyManager { } /** Removes all {@code subId} to {@code phoneType} mappings. */ - public void clearPhoneTypes() { + public static void clearPhoneTypes() { currentPhoneTypes.clear(); } @@ -1288,7 +1354,7 @@ public class ShadowTelephonyManager { * @param emergencyCallbackMode whether the device is in ECBM or not. */ public void setEmergencyCallbackMode(boolean emergencyCallbackMode) { - this.emergencyCallbackMode = emergencyCallbackMode; + ShadowTelephonyManager.emergencyCallbackMode = emergencyCallbackMode; } @Implementation(minSdk = Build.VERSION_CODES.O) @@ -1377,7 +1443,7 @@ public class ShadowTelephonyManager { /** Sets the value to be returned by {@link #isTtyModeSupported()} */ public void setTtyModeSupported(boolean isTtyModeSupported) { - this.isTtyModeSupported = isTtyModeSupported; + ShadowTelephonyManager.isTtyModeSupported = isTtyModeSupported; } /** @@ -1386,7 +1452,7 @@ public class ShadowTelephonyManager { @Implementation(minSdk = Build.VERSION_CODES.N) @HiddenApi protected boolean hasCarrierPrivileges(int subId) { - return subIdToHasCarrierPrivileges.get(subId); + return subIdToHasCarrierPrivileges.getOrDefault(subId, false); } public void setHasCarrierPrivileges(boolean hasCarrierPrivileges) { diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTextToSpeech.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTextToSpeech.java index e5b6b5ee7..83be824d9 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTextToSpeech.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTextToSpeech.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1; import static android.os.Build.VERSION_CODES.LOLLIPOP; import static java.nio.charset.StandardCharsets.UTF_8; import static org.robolectric.util.reflector.Reflector.reflector; @@ -129,31 +128,29 @@ public class ShadowTextToSpeech { spokenTextList.add(text.toString()); this.queueMode = queueMode; - if (RuntimeEnvironment.getApiLevel() >= ICE_CREAM_SANDWICH_MR1) { - if (utteranceId != null) { - // The onStart and onDone callbacks are normally delivered asynchronously. Since in - // Robolectric we don't need the wait for TTS package, the asynchronous callbacks are - // simulated by posting it on a handler. The behavior of the callback can be changed for - // each individual test by changing the idling mode of the foreground scheduler. - Handler handler = new Handler(Looper.getMainLooper()); - handler.post( - () -> { - UtteranceProgressListener utteranceProgressListener = getUtteranceProgressListener(); - if (utteranceProgressListener != null) { - utteranceProgressListener.onStart(utteranceId); - } - // The onDone callback is posted in a separate run-loop from onStart, so that tests - // can pause the scheduler and test the behavior between these two callbacks. - handler.post( - () -> { - UtteranceProgressListener utteranceProgressListener2 = - getUtteranceProgressListener(); - if (utteranceProgressListener2 != null) { - utteranceProgressListener2.onDone(utteranceId); - } - }); - }); - } + if (utteranceId != null) { + // The onStart and onDone callbacks are normally delivered asynchronously. Since in + // Robolectric we don't need the wait for TTS package, the asynchronous callbacks are + // simulated by posting it on a handler. The behavior of the callback can be changed for + // each individual test by changing the idling mode of the foreground scheduler. + Handler handler = new Handler(Looper.getMainLooper()); + handler.post( + () -> { + UtteranceProgressListener utteranceProgressListener = getUtteranceProgressListener(); + if (utteranceProgressListener != null) { + utteranceProgressListener.onStart(utteranceId); + } + // The onDone callback is posted in a separate run-loop from onStart, so that tests + // can pause the scheduler and test the behavior between these two callbacks. + handler.post( + () -> { + UtteranceProgressListener utteranceProgressListener2 = + getUtteranceProgressListener(); + if (utteranceProgressListener2 != null) { + utteranceProgressListener2.onDone(utteranceId); + } + }); + }); } return TextToSpeech.SUCCESS; } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowToneGenerator.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowToneGenerator.java index f24497647..e6c08a998 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowToneGenerator.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowToneGenerator.java @@ -1,7 +1,7 @@ package org.robolectric.shadows; import android.media.ToneGenerator; -import androidx.annotation.VisibleForTesting; +import com.android.internal.annotations.VisibleForTesting; import com.google.auto.value.AutoValue; import com.google.common.collect.ImmutableList; import java.time.Duration; diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTrace.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTrace.java index 16e501563..63a7d7c8c 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTrace.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTrace.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2; import static android.os.Build.VERSION_CODES.Q; import static com.google.common.base.Verify.verifyNotNull; @@ -51,7 +50,7 @@ public class ShadowTrace { private static long tags = TRACE_TAG_APP; /** Starts a new trace section with given name. */ - @Implementation(minSdk = JELLY_BEAN_MR2) + @Implementation protected static void beginSection(String sectionName) { if (tags == 0) { return; @@ -63,7 +62,7 @@ public class ShadowTrace { } /** Ends the most recent active trace section. */ - @Implementation(minSdk = JELLY_BEAN_MR2) + @Implementation protected static void endSection() { if (tags == 0) { return; @@ -112,12 +111,12 @@ public class ShadowTrace { previousAsyncSections.add(section); } - @Implementation(minSdk = JELLY_BEAN_MR2) + @Implementation protected static long nativeGetEnabledTags() { return tags; } - @Implementation(minSdk = JELLY_BEAN_MR2) + @Implementation protected static void setAppTracingAllowed(boolean appTracingAllowed) { tags = appTracingAllowed ? TRACE_TAG_APP : 0; } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTransportControls.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTransportControls.java new file mode 100644 index 000000000..cb5d112ab --- /dev/null +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTransportControls.java @@ -0,0 +1,222 @@ +package org.robolectric.shadows; + +import static android.media.session.PlaybackState.ACTION_PAUSE; +import static android.media.session.PlaybackState.ACTION_PLAY; +import static android.media.session.PlaybackState.ACTION_PLAY_FROM_SEARCH; +import static android.media.session.PlaybackState.ACTION_PLAY_FROM_URI; +import static android.media.session.PlaybackState.ACTION_PREPARE_FROM_SEARCH; +import static android.media.session.PlaybackState.ACTION_PREPARE_FROM_URI; +import static android.media.session.PlaybackState.ACTION_SEEK_TO; +import static android.media.session.PlaybackState.ACTION_SET_RATING; +import static android.media.session.PlaybackState.ACTION_SKIP_TO_NEXT; +import static android.media.session.PlaybackState.ACTION_SKIP_TO_PREVIOUS; +import static android.media.session.PlaybackState.ACTION_SKIP_TO_QUEUE_ITEM; +import static android.media.session.PlaybackState.ACTION_STOP; +import static android.media.session.PlaybackState.STATE_NONE; +import static android.os.Build.VERSION_CODES.LOLLIPOP; +import static android.os.Build.VERSION_CODES.M; +import static android.os.Build.VERSION_CODES.N; +import static org.robolectric.util.reflector.Reflector.reflector; + +import android.annotation.Nullable; +import android.media.Rating; +import android.media.session.MediaController.TransportControls; +import android.net.Uri; +import android.os.Bundle; +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; +import org.robolectric.annotation.RealObject; +import org.robolectric.util.reflector.Direct; +import org.robolectric.util.reflector.ForType; + +/** + * Shadow class for using {@link TransportControls} in tests. + * + * <p>TransportControls should always be created by first creating a corresponding MediaController; + * *NOT*, for instance, via Shadows.newInstanceOf(TransportControls.class). + */ +@Implements(value = TransportControls.class, minSdk = LOLLIPOP) +public class ShadowTransportControls { + @RealObject protected TransportControls realTransportControls; + + private long lastPerformedAction = STATE_NONE; + + @Nullable private String customAction; + @Nullable private Bundle customActionArgs; + + /** The current item id in playlist set by {@link TransportControls#skipToQueueItem(long)}. */ + private long queueItemId; + + /** The rating value set by last call of {@link TransportControls#setRating(Rating)}. */ + @Nullable private Rating rating; + + /** The current position in milliseconds set by {@link TransportControls#seekTo(long)} method. */ + private long seekToPositionMs; + + /** + * URI argument provided when {@link TransportControls#prepareFromUri(Uri, Bundle)} or {@link + * TransportControls#playFromUri(Uri, Bundle)} was called. + */ + @Nullable private Uri uri; + + @Implementation + protected void pause() { + lastPerformedAction = ACTION_PAUSE; + reflector(TransportControlsReflector.class, realTransportControls).pause(); + } + + @Implementation + protected void play() { + lastPerformedAction = ACTION_PLAY; + reflector(TransportControlsReflector.class, realTransportControls).play(); + } + + @Implementation + protected void playFromSearch(String query, Bundle extras) { + lastPerformedAction = ACTION_PLAY_FROM_SEARCH; + reflector(TransportControlsReflector.class, realTransportControls) + .playFromSearch(query, extras); + } + + @Implementation(minSdk = M) + protected void playFromUri(Uri uri, Bundle extras) { + lastPerformedAction = ACTION_PLAY_FROM_URI; + this.uri = uri; + reflector(TransportControlsReflector.class, realTransportControls).playFromUri(uri, extras); + } + + @Implementation(minSdk = N) + protected void prepareFromSearch(String query, Bundle extras) { + lastPerformedAction = ACTION_PREPARE_FROM_SEARCH; + reflector(TransportControlsReflector.class, realTransportControls) + .prepareFromSearch(query, extras); + } + + @Implementation(minSdk = N) + protected void prepareFromUri(Uri uri, Bundle extras) { + lastPerformedAction = ACTION_PREPARE_FROM_URI; + this.uri = uri; + reflector(TransportControlsReflector.class, realTransportControls).prepareFromUri(uri, extras); + } + + @Implementation + protected void seekTo(long pos) { + lastPerformedAction = ACTION_SEEK_TO; + seekToPositionMs = pos; + reflector(TransportControlsReflector.class, realTransportControls).seekTo(pos); + } + + @Implementation + protected void sendCustomAction(String action, Bundle args) { + customAction = action; + customActionArgs = args; + reflector(TransportControlsReflector.class, realTransportControls) + .sendCustomAction(action, args); + } + + @Implementation + protected void setRating(Rating rating) { + lastPerformedAction = ACTION_SET_RATING; + this.rating = rating; + reflector(TransportControlsReflector.class, realTransportControls).setRating(rating); + } + + @Implementation + protected void skipToNext() { + lastPerformedAction = ACTION_SKIP_TO_NEXT; + reflector(TransportControlsReflector.class, realTransportControls).skipToNext(); + } + + @Implementation + protected void skipToPrevious() { + lastPerformedAction = ACTION_SKIP_TO_PREVIOUS; + reflector(TransportControlsReflector.class, realTransportControls).skipToPrevious(); + } + + @Implementation + protected void skipToQueueItem(long id) { + lastPerformedAction = ACTION_SKIP_TO_QUEUE_ITEM; + queueItemId = id; + reflector(TransportControlsReflector.class, realTransportControls).skipToQueueItem(id); + } + + @Implementation + protected void stop() { + lastPerformedAction = ACTION_STOP; + reflector(TransportControlsReflector.class, realTransportControls).stop(); + } + + public long getLastPerformedAction() { + return lastPerformedAction; + } + + @Nullable + public String getCustomAction() { + return customAction; + } + + @Nullable + public Bundle getCustomActionArgs() { + return customActionArgs; + } + + public long getSeekToPositionMs() { + return seekToPositionMs; + } + + @Nullable + public Uri getUri() { + return uri; + } + + @Nullable + public Rating getRating() { + return rating; + } + + public long getQueueItemId() { + return queueItemId; + } + + @ForType(TransportControls.class) + private interface TransportControlsReflector { + @Direct + void pause(); + + @Direct + void play(); + + @Direct + void playFromSearch(String query, Bundle extras); + + @Direct + void playFromUri(Uri uri, Bundle extras); + + @Direct + void prepareFromSearch(String query, Bundle extras); + + @Direct + void prepareFromUri(Uri uri, Bundle extras); + + @Direct + void seekTo(long pos); + + @Direct + void sendCustomAction(String action, Bundle args); + + @Direct + void setRating(Rating rating); + + @Direct + void skipToNext(); + + @Direct + void skipToPrevious(); + + @Direct + void skipToQueueItem(long id); + + @Direct + void stop(); + } +} diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowUIModeManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowUIModeManager.java index 04c574401..af3cfe8fe 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowUIModeManager.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowUIModeManager.java @@ -12,9 +12,11 @@ import android.content.res.Configuration; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; import android.provider.Settings; -import androidx.annotation.GuardedBy; +import com.android.internal.annotations.GuardedBy; import com.google.common.collect.ImmutableSet; +import java.util.HashMap; import java.util.HashSet; +import java.util.Map; import java.util.Set; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.HiddenApi; @@ -32,7 +34,7 @@ public class ShadowUIModeManager { public int lastFlags; public int lastCarModePriority; private int currentApplicationNightMode = 0; - private final Set<Integer> activeProjectionTypes = new HashSet<>(); + private final Map<Integer, Set<String>> activeProjectionTypes = new HashMap<>(); private boolean failOnProjectionToggle; private static final ImmutableSet<Integer> VALID_NIGHT_MODES = @@ -116,12 +118,22 @@ public class ShadowUIModeManager { } } + @Implementation(minSdk = VERSION_CODES.S) + protected Set<String> getProjectingPackages(int projectionType) { + if (projectionType == UiModeManager.PROJECTION_TYPE_ALL) { + Set<String> projections = new HashSet<>(); + activeProjectionTypes.values().forEach(projections::addAll); + return projections; + } + return activeProjectionTypes.getOrDefault(projectionType, new HashSet<>()); + } + public int getApplicationNightMode() { return currentApplicationNightMode; } public Set<Integer> getActiveProjectionTypes() { - return new HashSet<>(activeProjectionTypes); + return new HashSet<>(activeProjectionTypes.keySet()); } public void setFailOnProjectionToggle(boolean failOnProjectionToggle) { @@ -143,7 +155,10 @@ public class ShadowUIModeManager { if (failOnProjectionToggle) { return false; } - activeProjectionTypes.add(projectionType); + Set<String> projections = activeProjectionTypes.getOrDefault(projectionType, new HashSet<>()); + projections.add(RuntimeEnvironment.getApplication().getPackageName()); + activeProjectionTypes.put(projectionType, projections); + return true; } @@ -156,7 +171,19 @@ public class ShadowUIModeManager { if (failOnProjectionToggle) { return false; } - return activeProjectionTypes.remove(projectionType); + String packageName = RuntimeEnvironment.getApplication().getPackageName(); + Set<String> projections = activeProjectionTypes.getOrDefault(projectionType, new HashSet<>()); + if (projections.contains(packageName)) { + projections.remove(packageName); + if (projections.isEmpty()) { + activeProjectionTypes.remove(projectionType); + } else { + activeProjectionTypes.put(projectionType, projections); + } + return true; + } + + return false; } @Implementation(minSdk = TIRAMISU) @@ -228,8 +255,10 @@ public class ShadowUIModeManager { private void assertHasPermission(String... permissions) { Context context = RuntimeEnvironment.getApplication(); for (String permission : permissions) { - if (context.getPackageManager().checkPermission(permission, context.getPackageName()) - != PackageManager.PERMISSION_GRANTED) { + // Check both the Runtime based and Manifest based permissions + if (context.checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED + && context.getPackageManager().checkPermission(permission, context.getPackageName()) + != PackageManager.PERMISSION_GRANTED) { throw new SecurityException("Missing required permission: " + permission); } } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowUiAutomation.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowUiAutomation.java index 956afb008..bfe39ea95 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowUiAutomation.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowUiAutomation.java @@ -2,8 +2,6 @@ package org.robolectric.shadows; import static android.app.UiAutomation.ROTATION_FREEZE_0; import static android.app.UiAutomation.ROTATION_FREEZE_180; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2; import static android.os.Build.VERSION_CODES.TIRAMISU; import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; @@ -25,10 +23,8 @@ import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Point; -import android.os.Build; import android.os.IBinder; import android.provider.Settings; -import android.util.Log; import android.view.Display; import android.view.InputEvent; import android.view.KeyEvent; @@ -37,7 +33,6 @@ import android.view.View; import android.view.ViewRootImpl; import android.view.WindowManager; import android.view.WindowManagerGlobal; -import android.view.WindowManagerImpl; import androidx.test.runner.lifecycle.ActivityLifecycleMonitor; import androidx.test.runner.lifecycle.ActivityLifecycleMonitorRegistry; import androidx.test.runner.lifecycle.Stage; @@ -55,7 +50,7 @@ import org.robolectric.annotation.Implements; import org.robolectric.util.ReflectionHelpers; /** Shadow for {@link UiAutomation}. */ -@Implements(value = UiAutomation.class, minSdk = JELLY_BEAN_MR2) +@Implements(value = UiAutomation.class) public class ShadowUiAutomation { private static final Predicate<Root> IS_FOCUSABLE = hasLayoutFlag(FLAG_NOT_FOCUSABLE).negate(); @@ -69,18 +64,11 @@ public class ShadowUiAutomation { * Sets the animation scale, see {@link UiAutomation#setAnimationScale(float)}. Provides backwards * compatible access to SDKs < T. */ - @SuppressWarnings("deprecation") public static void setAnimationScaleCompat(float scale) { ContentResolver cr = RuntimeEnvironment.getApplication().getContentResolver(); - if (RuntimeEnvironment.getApiLevel() >= JELLY_BEAN_MR1) { - Settings.Global.putFloat(cr, Settings.Global.ANIMATOR_DURATION_SCALE, scale); - Settings.Global.putFloat(cr, Settings.Global.TRANSITION_ANIMATION_SCALE, scale); - Settings.Global.putFloat(cr, Settings.Global.WINDOW_ANIMATION_SCALE, scale); - } else { - Settings.System.putFloat(cr, Settings.System.ANIMATOR_DURATION_SCALE, scale); - Settings.System.putFloat(cr, Settings.System.TRANSITION_ANIMATION_SCALE, scale); - Settings.System.putFloat(cr, Settings.System.WINDOW_ANIMATION_SCALE, scale); - } + Settings.Global.putFloat(cr, Settings.Global.ANIMATOR_DURATION_SCALE, scale); + Settings.Global.putFloat(cr, Settings.Global.TRANSITION_ANIMATION_SCALE, scale); + Settings.Global.putFloat(cr, Settings.Global.WINDOW_ANIMATION_SCALE, scale); } @Implementation(minSdk = TIRAMISU) @@ -144,7 +132,6 @@ public class ShadowUiAutomation { Bitmap.createBitmap( rootView.getWidth(), rootView.getHeight(), Bitmap.Config.ARGB_8888); if (HardwareRenderingScreenshot.canTakeScreenshot()) { - Log.d("@@", "@@ USE NEW takeScreenshot"); // RM DEBUG HardwareRenderingScreenshot.takeScreenshot(rootView, window); } else { Canvas windowCanvas = new Canvas(window); @@ -271,11 +258,7 @@ public class ShadowUiAutomation { } private static Object getViewRootsContainer() { - if (RuntimeEnvironment.getApiLevel() <= Build.VERSION_CODES.JELLY_BEAN) { - return ReflectionHelpers.callStaticMethod(WindowManagerImpl.class, "getDefault"); - } else { - return WindowManagerGlobal.getInstance(); - } + return WindowManagerGlobal.getInstance(); } private static Set<IBinder> getStartedActivityTokens() { diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowUsbDeviceConnection.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowUsbDeviceConnection.java index 45fd5eb13..dcfdc5ae3 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowUsbDeviceConnection.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowUsbDeviceConnection.java @@ -1,7 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2; -import static android.os.Build.VERSION_CODES.KITKAT; import static android.os.Build.VERSION_CODES.LOLLIPOP; import static android.os.Build.VERSION_CODES.O; @@ -64,13 +62,13 @@ public class ShadowUsbDeviceConnection { return true; } - @Implementation(minSdk = KITKAT) + @Implementation protected int controlTransfer( int requestType, int request, int value, int index, byte[] buffer, int length, int timeout) { return length; } - @Implementation(minSdk = KITKAT) + @Implementation protected int controlTransfer( int requestType, int request, @@ -97,7 +95,7 @@ public class ShadowUsbDeviceConnection { return requestWait(); } - @Implementation(minSdk = JELLY_BEAN_MR2) + @Implementation protected int bulkTransfer( UsbEndpoint endpoint, byte[] buffer, int offset, int length, int timeout) { try { diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowUserManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowUserManager.java index f6ef2deb5..78a96ec31 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowUserManager.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowUserManager.java @@ -1,7 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2; import static android.os.Build.VERSION_CODES.LOLLIPOP; import static android.os.Build.VERSION_CODES.M; import static android.os.Build.VERSION_CODES.N; @@ -59,7 +57,7 @@ import org.robolectric.util.reflector.Direct; import org.robolectric.util.reflector.ForType; /** Robolectric implementation of {@link android.os.UserManager}. */ -@Implements(value = UserManager.class, minSdk = JELLY_BEAN_MR1) +@Implements(value = UserManager.class) public class ShadowUserManager { /** @@ -172,7 +170,7 @@ public class ShadowUserManager { * * @see #setApplicationRestrictions(String, Bundle) */ - @Implementation(minSdk = JELLY_BEAN_MR2) + @Implementation protected Bundle getApplicationRestrictions(String packageName) { Bundle bundle = userManagerState.applicationRestrictions.get(packageName); return bundle != null ? bundle : new Bundle(); @@ -501,7 +499,7 @@ public class ShadowUserManager { * return meaningful results in test environment; thus, allowing test to verify the invoking of * UserManager.setUserRestriction(). */ - @Implementation(minSdk = JELLY_BEAN_MR2) + @Implementation protected void setUserRestriction(String key, boolean value, UserHandle userHandle) { Bundle bundle = getUserRestrictionsForUser(userHandle); synchronized (lock) { @@ -509,7 +507,7 @@ public class ShadowUserManager { } } - @Implementation(minSdk = JELLY_BEAN_MR2) + @Implementation protected void setUserRestriction(String key, boolean value) { setUserRestriction(key, value, Process.myUserHandle()); } @@ -528,7 +526,7 @@ public class ShadowUserManager { userManagerState.userRestrictions.remove(userHandle.getIdentifier()); } - @Implementation(minSdk = JELLY_BEAN_MR2) + @Implementation protected Bundle getUserRestrictions(UserHandle userHandle) { return new Bundle(getUserRestrictionsForUser(userHandle)); } @@ -643,9 +641,11 @@ public class ShadowUserManager { userManagerState.userIcon.put(userId, icon); } - /** @return user id for given user serial number. */ + /** + * @return user id for given user serial number. + */ @HiddenApi - @Implementation(minSdk = JELLY_BEAN_MR1) + @Implementation @UserIdInt protected int getUserHandle(int serialNumber) { Integer userHandle = userManagerState.userSerialNumbers.inverse().get((long) serialNumber); @@ -663,7 +663,7 @@ public class ShadowUserManager { } @HiddenApi - @Implementation(minSdk = JELLY_BEAN_MR1) + @Implementation protected static int getMaxSupportedUsers() { return maxSupportedUsers; } @@ -769,8 +769,10 @@ public class ShadowUserManager { } } - /** @return 'false' by default, or the value specified via {@link #setIsLinkedUser(boolean)} */ - @Implementation(minSdk = JELLY_BEAN_MR2) + /** + * @return 'false' by default, or the value specified via {@link #setIsLinkedUser(boolean)} + */ + @Implementation protected boolean isLinkedUser() { return isRestrictedProfile(); } @@ -1059,7 +1061,7 @@ public class ShadowUserManager { seedAccountOptions = null; } - @Implementation(minSdk = JELLY_BEAN_MR1) + @Implementation protected boolean removeUser(int userHandle) { if (!userManagerState.userInfoMap.containsKey(userHandle)) { return false; diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowUwbManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowUwbManager.java index 18b08f6a4..118b155c1 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowUwbManager.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowUwbManager.java @@ -5,8 +5,11 @@ import static android.os.Build.VERSION_CODES.TIRAMISU; import android.os.Build.VERSION_CODES; import android.os.CancellationSignal; import android.os.PersistableBundle; +import android.uwb.AdapterState; import android.uwb.RangingSession; +import android.uwb.StateChangeReason; import android.uwb.UwbManager; +import android.uwb.UwbManager.AdapterStateCallback; import com.google.common.collect.ImmutableList; import java.util.ArrayList; import java.util.List; @@ -19,6 +22,12 @@ import org.robolectric.shadow.api.Shadow; @Implements(value = UwbManager.class, minSdk = VERSION_CODES.S, isInAndroidSdk = false) public class ShadowUwbManager { + private AdapterStateCallback callback; + + private int adapterState = AdapterStateCallback.STATE_ENABLED_INACTIVE; + + private int stateChangedReason = AdapterStateCallback.STATE_CHANGED_REASON_SYSTEM_POLICY; + private PersistableBundle specificationInfo = new PersistableBundle(); private List<PersistableBundle> chipInfos = new ArrayList<>(); @@ -44,6 +53,32 @@ public class ShadowUwbManager { public void onClose(RangingSession session, RangingSession.Callback callback) {} }; + @Implementation + protected void registerAdapterStateCallback(Executor executor, AdapterStateCallback callback) { + this.callback = callback; + callback.onStateChanged(adapterState, stateChangedReason); + } + + /** + * Simulates adapter state change by invoking a callback registered by {@link + * ShadowUwbManager#registerAdapterStateCallback(Executor executor, AdapterStateCallback + * callback)}. + * + * @param state A state that should be passed to the callback. + * @param reason A reason that should be passed to the callback. + * @throws IllegalArgumentException if the callback is missing. + */ + public void simulateAdapterStateChange(@AdapterState int state, @StateChangeReason int reason) { + if (this.callback == null) { + throw new IllegalArgumentException("AdapterStateCallback should not be null"); + } + + adapterState = state; + stateChangedReason = reason; + + this.callback.onStateChanged(state, reason); + } + /** * Simply returns the bundle provided by {@link ShadowUwbManager#setSpecificationInfo()}, allowing * the tester to dictate available features. @@ -56,10 +91,15 @@ public class ShadowUwbManager { /** * Instantiates a {@link ShadowRangingSession} with the adapter provided by {@link * ShadowUwbManager#setUwbAdapter()}, allowing the tester dictate the results of ranging attempts. + * + * @throws IllegalArgumentException if UWB is disabled. */ @Implementation protected CancellationSignal openRangingSession( PersistableBundle params, Executor executor, RangingSession.Callback callback) { + if (!isUwbEnabled()) { + throw new IllegalStateException("Uwb is not enabled"); + } RangingSession session = ShadowRangingSession.newInstance(executor, callback, adapter); CancellationSignal cancellationSignal = new CancellationSignal(); cancellationSignal.setOnCancelListener(session::close); @@ -91,6 +131,35 @@ public class ShadowUwbManager { return openRangingSession(params, executor, callback); } + /** Returns whether UWB is enabled or disabled. */ + @Implementation(minSdk = TIRAMISU) + protected boolean isUwbEnabled() { + return adapterState != AdapterStateCallback.STATE_DISABLED; + } + + /** + * Disables or enables UWB by the user. + * + * @param enabled value representing intent to disable or enable UWB. + */ + @Implementation + protected void setUwbEnabled(boolean enabled) { + boolean stateChanged = false; + + if (enabled && adapterState == AdapterStateCallback.STATE_DISABLED) { + adapterState = AdapterStateCallback.STATE_ENABLED_INACTIVE; + stateChanged = true; + } else if (!enabled && adapterState != AdapterStateCallback.STATE_DISABLED) { + adapterState = AdapterStateCallback.STATE_DISABLED; + stateChanged = true; + } + + if (this.callback != null && stateChanged) { + this.callback.onStateChanged( + adapterState, AdapterStateCallback.STATE_CHANGED_REASON_SYSTEM_POLICY); + } + } + /** * Simply returns the List of bundles provided by {@link ShadowUwbManager#setChipInfos(List)} , * allowing the tester to set multi-chip configuration. diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowView.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowView.java index 78526fade..848502e60 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowView.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowView.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2; import static android.os.Build.VERSION_CODES.KITKAT; import static android.os.Build.VERSION_CODES.KITKAT_WATCH; import static android.os.Build.VERSION_CODES.N; @@ -793,7 +792,7 @@ public class ShadowView { } } - @Implementation(minSdk = KITKAT) + @Implementation protected boolean isAttachedToWindow() { return getAttachInfo() != null; } @@ -954,7 +953,7 @@ public class ShadowView { reflector(_View_.class, realView).onDetachedFromWindow(); } - @Implementation(minSdk = JELLY_BEAN_MR2) + @Implementation protected WindowId getWindowId() { return WindowIdHelper.getWindowId(this); } @@ -1092,6 +1091,7 @@ public class ShadowView { * set. */ static boolean useRealScrolling() { - return useRealGraphics() || Boolean.getBoolean("robolectric.useRealScrolling"); + return useRealGraphics() + || Boolean.parseBoolean(System.getProperty("robolectric.useRealScrolling", "true")); } } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowViewRootImpl.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowViewRootImpl.java index 9dd19321b..744b1c02e 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowViewRootImpl.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowViewRootImpl.java @@ -6,7 +6,6 @@ import static android.os.Build.VERSION_CODES.S_V2; import static org.robolectric.annotation.TextLayoutMode.Mode.REALISTIC; import static org.robolectric.util.reflector.Reflector.reflector; -import android.content.Context; import android.content.res.Configuration; import android.graphics.Rect; import android.os.Build; @@ -222,14 +221,7 @@ public class ShadowViewRootImpl { } protected Display getDisplay() { - if (RuntimeEnvironment.getApiLevel() > VERSION_CODES.JELLY_BEAN_MR1) { - return reflector(ViewRootImplReflector.class, realObject).getDisplay(); - } else { - WindowManager windowManager = - (WindowManager) - realObject.getView().getContext().getSystemService(Context.WINDOW_SERVICE); - return windowManager.getDefaultDisplay(); - } + return reflector(ViewRootImplReflector.class, realObject).getDisplay(); } @Implementation @@ -356,24 +348,7 @@ public class ShadowViewRootImpl { @Accessor("mWindowAttributes") WindowManager.LayoutParams getWindowAttributes(); - // <= JELLY_BEAN - void dispatchResized( - int w, - int h, - Rect contentInsets, - Rect visibleInsets, - boolean reportDraw, - Configuration newConfig); - - // <= JELLY_BEAN_MR1 - void dispatchResized( - Rect frame, - Rect contentInsets, - Rect visibleInsets, - boolean reportDraw, - Configuration newConfig); - - // <= KITKAT + // == KITKAT void dispatchResized( Rect frame, Rect overscanInsets, @@ -452,11 +427,7 @@ public class ShadowViewRootImpl { Rect emptyRect = new Rect(0, 0, 0, 0); int apiLevel = RuntimeEnvironment.getApiLevel(); - if (apiLevel <= Build.VERSION_CODES.JELLY_BEAN) { - dispatchResized(frame.width(), frame.height(), emptyRect, emptyRect, true, null); - } else if (apiLevel <= VERSION_CODES.JELLY_BEAN_MR1) { - dispatchResized(frame, emptyRect, emptyRect, true, null); - } else if (apiLevel <= Build.VERSION_CODES.KITKAT) { + if (apiLevel == Build.VERSION_CODES.KITKAT) { dispatchResized(frame, emptyRect, emptyRect, emptyRect, true, null); } else if (apiLevel <= Build.VERSION_CODES.LOLLIPOP_MR1) { dispatchResized(frame, emptyRect, emptyRect, emptyRect, emptyRect, true, null); diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowVirtualDeviceManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowVirtualDeviceManager.java index ea978511f..f4f15fb79 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowVirtualDeviceManager.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowVirtualDeviceManager.java @@ -14,6 +14,15 @@ import android.companion.virtual.sensor.VirtualSensor; import android.companion.virtual.sensor.VirtualSensorCallback; import android.companion.virtual.sensor.VirtualSensorDirectChannelCallback; import android.content.Context; +import android.hardware.display.VirtualDisplay; +import android.hardware.input.VirtualKeyboard; +import android.hardware.input.VirtualKeyboardConfig; +import android.hardware.input.VirtualMouse; +import android.hardware.input.VirtualMouseConfig; +import android.hardware.input.VirtualTouchscreen; +import android.hardware.input.VirtualTouchscreenConfig; +import android.os.Binder; +import android.os.IBinder; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Executor; @@ -21,6 +30,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.IntConsumer; import java.util.stream.Collectors; +import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; import org.robolectric.annotation.RealObject; @@ -83,8 +93,8 @@ public class ShadowVirtualDeviceManager { // Use the new constructor when the old constructor does not exist DeviceManagerVirtualDeviceReflector virtualDeviceReflector = reflector(DeviceManagerVirtualDeviceReflector.class, virtualDevice); - return accessor.newInstance( - virtualDeviceReflector.getVirtualDevice(), + return accessor.newInstanceV( + ReflectionHelpers.createNullProxy(IVirtualDevice.class), virtualDevice.getDeviceId(), virtualDeviceReflector.getPersistentDeviceId(), deviceName); @@ -193,6 +203,75 @@ public class ShadowVirtualDeviceManager { executor.execute(() -> listener.accept(pendingIntentResultCode)); } + @Implementation + protected VirtualMouse createVirtualMouse( + @NonNull VirtualDisplay display, + @NonNull String inputDeviceName, + int vendorId, + int productId) { + return createVirtualMouse( + new VirtualMouseConfig.Builder().setInputDeviceName(inputDeviceName).build()); + } + + @Implementation + protected VirtualMouse createVirtualMouse(@NonNull VirtualMouseConfig config) { + IBinder token = + new Binder("android.hardware.input.VirtualMouse:" + config.getInputDeviceName()); + VirtualMouseReflector accessor = reflector(VirtualMouseReflector.class); + if (RuntimeEnvironment.getApiLevel() <= U.SDK_INT) { + return accessor.newInstance(ReflectionHelpers.createNullProxy(IVirtualDevice.class), token); + } else { + return accessor.newInstanceV( + config, ReflectionHelpers.createNullProxy(IVirtualDevice.class), token); + } + } + + @Implementation + protected void setShowPointerIcon(boolean showPointerIcon) { + // no-op + } + + @Implementation + protected VirtualTouchscreen createVirtualTouchscreen( + @NonNull VirtualDisplay display, + @NonNull String inputDeviceName, + int vendorId, + int productId) { + int displayWidth = 720; + int displayHeight = 1280; + return createVirtualTouchscreen( + new VirtualTouchscreenConfig.Builder(displayWidth, displayHeight) + .setInputDeviceName(inputDeviceName) + .build()); + } + + @Implementation + protected VirtualTouchscreen createVirtualTouchscreen( + @NonNull VirtualTouchscreenConfig config) { + IBinder token = + new Binder("android.hardware.input.VirtualTouchscreen:" + config.getInputDeviceName()); + VirtualTouchscreenReflector accessor = reflector(VirtualTouchscreenReflector.class); + if (RuntimeEnvironment.getApiLevel() <= U.SDK_INT) { + return accessor.newInstance(ReflectionHelpers.createNullProxy(IVirtualDevice.class), token); + } else { + return accessor.newInstanceV( + config, ReflectionHelpers.createNullProxy(IVirtualDevice.class), token); + } + } + + @Implementation + protected VirtualKeyboard createVirtualKeyboard(@NonNull VirtualKeyboardConfig config) { + IBinder token = + new Binder("android.hardware.input.VirtualKeyboard:" + config.getInputDeviceName()); + VirtualKeyboardReflector accessor = reflector(VirtualKeyboardReflector.class); + if (RuntimeEnvironment.getApiLevel() <= U.SDK_INT) { + return accessor.newInstance(ReflectionHelpers.createNullProxy(IVirtualDevice.class), token); + } else { + return accessor.newInstanceV( + config, ReflectionHelpers.createNullProxy(IVirtualDevice.class), token); + } + } + public void setPendingIntentCallbackResultCode(int resultCode) { this.pendingIntentResultCode = resultCode; } @@ -234,12 +313,40 @@ public class ShadowVirtualDeviceManager { VirtualSensorDirectChannelCallback getDirectChannelCallback(); } + @ForType(VirtualMouse.class) + private interface VirtualMouseReflector { + @Constructor + VirtualMouse newInstanceV( + VirtualMouseConfig config, IVirtualDevice virtualDevice, IBinder token); + + @Constructor + VirtualMouse newInstance(IVirtualDevice virtualDevice, IBinder token); + } + + @ForType(VirtualTouchscreen.class) + private interface VirtualTouchscreenReflector { + @Constructor + VirtualTouchscreen newInstanceV( + VirtualTouchscreenConfig config, IVirtualDevice virtualDevice, IBinder token); + + @Constructor + VirtualTouchscreen newInstance(IVirtualDevice virtualDevice, IBinder token); + } + + @ForType(VirtualKeyboard.class) + private interface VirtualKeyboardReflector { + @Constructor + VirtualKeyboard newInstanceV( + VirtualKeyboardConfig config, IVirtualDevice virtualDevice, IBinder token); + + @Constructor + VirtualKeyboard newInstance(IVirtualDevice virtualDevice, IBinder token); + } + @ForType(VirtualDevice.class) private interface VirtualDeviceReflector { - - // new constructor after U @Constructor - VirtualDevice newInstance( + VirtualDevice newInstanceV( IVirtualDevice virtualDevice, int id, String persistentId, String name); @Constructor @@ -248,10 +355,6 @@ public class ShadowVirtualDeviceManager { @ForType(VirtualDeviceManager.VirtualDevice.class) private interface DeviceManagerVirtualDeviceReflector { - // U and before var and method - @Accessor("mVirtualDevice") - IVirtualDevice getVirtualDevice(); - String getPersistentDeviceId(); } } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowVirtualInputDevice.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowVirtualInputDevice.java new file mode 100644 index 000000000..f7fa9ee84 --- /dev/null +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowVirtualInputDevice.java @@ -0,0 +1,25 @@ +package org.robolectric.shadows; + +import android.os.Build.VERSION_CODES; +import java.util.concurrent.atomic.AtomicBoolean; +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; + +/** Shadow for VirtualInputDevice. */ +@Implements( + className = "android.hardware.input.VirtualInputDevice", + isInAndroidSdk = false, + minSdk = VERSION_CODES.UPSIDE_DOWN_CAKE) +public class ShadowVirtualInputDevice { + + private final AtomicBoolean isClosed = new AtomicBoolean(false); + + @Implementation + protected void close() { + isClosed.set(true); + } + + public boolean isClosed() { + return isClosed.get(); + } +} diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowVirtualKeyboard.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowVirtualKeyboard.java new file mode 100644 index 000000000..65b797064 --- /dev/null +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowVirtualKeyboard.java @@ -0,0 +1,25 @@ +package org.robolectric.shadows; + +import android.hardware.input.VirtualKeyEvent; +import android.hardware.input.VirtualKeyboard; +import android.os.Build.VERSION_CODES; +import java.util.ArrayList; +import java.util.List; +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; + +/** Shadow for VirtualKeyboard. */ +@Implements(value = VirtualKeyboard.class, minSdk = VERSION_CODES.TIRAMISU, isInAndroidSdk = false) +public class ShadowVirtualKeyboard extends ShadowVirtualInputDevice { + + private final List<VirtualKeyEvent> sentEvents = new ArrayList<>(); + + @Implementation + protected void sendKeyEvent(VirtualKeyEvent event) { + sentEvents.add(event); + } + + public List<VirtualKeyEvent> getSentEvents() { + return sentEvents; + } +} diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowVirtualMouse.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowVirtualMouse.java new file mode 100644 index 000000000..08fa1d654 --- /dev/null +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowVirtualMouse.java @@ -0,0 +1,47 @@ +package org.robolectric.shadows; + +import android.hardware.input.VirtualMouse; +import android.hardware.input.VirtualMouseButtonEvent; +import android.hardware.input.VirtualMouseRelativeEvent; +import android.hardware.input.VirtualMouseScrollEvent; +import android.os.Build.VERSION_CODES; +import java.util.ArrayList; +import java.util.List; +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; + +/** Shadow for VirtualMouse. */ +@Implements(value = VirtualMouse.class, minSdk = VERSION_CODES.TIRAMISU, isInAndroidSdk = false) +public class ShadowVirtualMouse extends ShadowVirtualInputDevice { + + private final List<VirtualMouseButtonEvent> sentButtonEvents = new ArrayList<>(); + private final List<VirtualMouseScrollEvent> sentScrollEvents = new ArrayList<>(); + private final List<VirtualMouseRelativeEvent> sentRelativeEvents = new ArrayList<>(); + + @Implementation + protected void sendButtonEvent(VirtualMouseButtonEvent event) { + sentButtonEvents.add(event); + } + + @Implementation + protected void sendScrollEvent(VirtualMouseScrollEvent event) { + sentScrollEvents.add(event); + } + + @Implementation + protected void sendRelativeEvent(VirtualMouseRelativeEvent event) { + sentRelativeEvents.add(event); + } + + public List<VirtualMouseButtonEvent> getSentButtonEvents() { + return sentButtonEvents; + } + + public List<VirtualMouseScrollEvent> getSentScrollEvents() { + return sentScrollEvents; + } + + public List<VirtualMouseRelativeEvent> getSentRelativeEvents() { + return sentRelativeEvents; + } +} diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowVirtualTouchscreen.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowVirtualTouchscreen.java new file mode 100644 index 000000000..403212a07 --- /dev/null +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowVirtualTouchscreen.java @@ -0,0 +1,28 @@ +package org.robolectric.shadows; + +import android.hardware.input.VirtualTouchEvent; +import android.hardware.input.VirtualTouchscreen; +import android.os.Build.VERSION_CODES; +import java.util.ArrayList; +import java.util.List; +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; + +/** Shadow for VirtualTouchscreen. */ +@Implements( + value = VirtualTouchscreen.class, + minSdk = VERSION_CODES.TIRAMISU, + isInAndroidSdk = false) +public class ShadowVirtualTouchscreen extends ShadowVirtualInputDevice { + + private final List<VirtualTouchEvent> sentEvents = new ArrayList<>(); + + @Implementation + protected void sendTouchEvent(VirtualTouchEvent event) { + sentEvents.add(event); + } + + public List<VirtualTouchEvent> getSentEvents() { + return sentEvents; + } +} diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowVisualizer.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowVisualizer.java index 9451cb0e4..ca559490b 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowVisualizer.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowVisualizer.java @@ -1,7 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.GINGERBREAD; -import static android.os.Build.VERSION_CODES.KITKAT; import static org.robolectric.util.reflector.Reflector.reflector; import android.media.audiofx.Visualizer; @@ -15,7 +13,7 @@ import org.robolectric.util.reflector.Accessor; import org.robolectric.util.reflector.ForType; /** Shadow for the {@link Visualizer} class. */ -@Implements(value = Visualizer.class, minSdk = GINGERBREAD) +@Implements(value = Visualizer.class) public class ShadowVisualizer { @RealObject private Visualizer realObject; @@ -34,7 +32,7 @@ public class ShadowVisualizer { this.source.set(source); } - @Implementation(minSdk = GINGERBREAD) + @Implementation protected int setDataCaptureListener( OnDataCaptureListener listener, int rate, boolean waveform, boolean fft) { if (errorCode != Visualizer.SUCCESS) { @@ -46,27 +44,27 @@ public class ShadowVisualizer { return Visualizer.SUCCESS; } - @Implementation(minSdk = GINGERBREAD) + @Implementation protected int native_getSamplingRate() { return source.get().getSamplingRate(); } - @Implementation(minSdk = GINGERBREAD) + @Implementation protected int native_getWaveForm(byte[] waveform) { return source.get().getWaveForm(waveform); } - @Implementation(minSdk = GINGERBREAD) + @Implementation protected int native_getFft(byte[] fft) { return source.get().getFft(fft); } - @Implementation(minSdk = GINGERBREAD) + @Implementation protected boolean native_getEnabled() { return enabled; } - @Implementation(minSdk = GINGERBREAD) + @Implementation protected int native_setCaptureSize(int size) { if (errorCode != Visualizer.SUCCESS) { return errorCode; @@ -75,12 +73,12 @@ public class ShadowVisualizer { return Visualizer.SUCCESS; } - @Implementation(minSdk = GINGERBREAD) + @Implementation protected int native_getCaptureSize() { return captureSize; } - @Implementation(minSdk = GINGERBREAD) + @Implementation protected int native_setEnabled(boolean enabled) { if (errorCode != Visualizer.SUCCESS) { return errorCode; @@ -89,12 +87,12 @@ public class ShadowVisualizer { return Visualizer.SUCCESS; } - @Implementation(minSdk = KITKAT) + @Implementation protected int native_getPeakRms(MeasurementPeakRms measurement) { return source.get().getPeakRms(measurement); } - @Implementation(minSdk = GINGERBREAD) + @Implementation protected void native_release() { source.get().release(); } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWallpaperManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWallpaperManager.java index 648b44988..1b59b3d5d 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWallpaperManager.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWallpaperManager.java @@ -18,7 +18,6 @@ import android.graphics.Bitmap; import android.graphics.Bitmap.CompressFormat; import android.graphics.BitmapFactory; import android.graphics.Rect; -import android.os.Build.VERSION_CODES; import android.os.Bundle; import android.os.IBinder; import android.os.ParcelFileDescriptor; @@ -52,6 +51,7 @@ public class ShadowWallpaperManager { private int homeScreenId; private float wallpaperDimAmount = 0.0f; + private final ArrayList<Float> allWallpaperDimAmounts = new ArrayList<>(); @Implementation protected void sendWallpaperCommand( @@ -88,7 +88,7 @@ public class ShadowWallpaperManager { * Returns whether the current wallpaper has been set through {@link #setResource(int)} or {@link * #setResource(int, int)} with the same resource id. */ - @Implementation(minSdk = VERSION_CODES.JELLY_BEAN_MR1) + @Implementation protected boolean hasResourceWallpaper(int resid) { return resid == this.lockScreenId || resid == this.homeScreenId; } @@ -240,6 +240,15 @@ public class ShadowWallpaperManager { @Implementation(minSdk = TIRAMISU) protected void setWallpaperDimAmount(@FloatRange(from = 0f, to = 1f) float dimAmount) { wallpaperDimAmount = MathUtils.saturate(dimAmount); + allWallpaperDimAmounts.add(dimAmount); + } + + /** + * Returns a list of all dim amounts set from calls to setWallpaperDimAmount. This can be used to + * verify that repeated calls to setWallpaperDimAmount are not done which can cause issues. + */ + public List<Float> getAllWallpaperDimAmounts() { + return Collections.unmodifiableList(allWallpaperDimAmounts); } @Implementation(minSdk = TIRAMISU) diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWebSettings.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWebSettings.java index 555a21122..427a10190 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWebSettings.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWebSettings.java @@ -1,7 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2; - import android.content.Context; import android.webkit.WebSettings; import org.robolectric.annotation.Implementation; @@ -23,7 +21,7 @@ public class ShadowWebSettings { * * @param context a Context object used to access application assets */ - @Implementation(minSdk = JELLY_BEAN_MR2) + @Implementation protected static String getDefaultUserAgent(Context context) { return defaultUserAgent; } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWebView.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWebView.java index 1a8131f01..8847b5e2f 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWebView.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWebView.java @@ -542,7 +542,7 @@ public class ShadowWebView extends ShadowViewGroup { currentFavicon = favicon; } - @Implementation(minSdk = Build.VERSION_CODES.KITKAT) + @Implementation protected void evaluateJavascript(String script, ValueCallback<String> callback) { this.lastEvaluatedJavascript = script; this.lastEvaluatedJavascriptCallback = callback; @@ -645,7 +645,7 @@ public class ShadowWebView extends ShadowViewGroup { packageInfo = null; } - @Implementation(minSdk = VERSION_CODES.KITKAT) + @Implementation public static void setWebContentsDebuggingEnabled(boolean enabled) {} /** diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWifiInfo.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWifiInfo.java index 7f3b1c0a1..183637167 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWifiInfo.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWifiInfo.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.JELLY_BEAN; import static android.os.Build.VERSION_CODES.LOLLIPOP; import static org.robolectric.util.reflector.Reflector.reflector; @@ -8,7 +7,6 @@ import android.net.wifi.SupplicantState; import android.net.wifi.WifiInfo; import android.net.wifi.WifiSsid; import java.net.InetAddress; -import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; import org.robolectric.annotation.RealObject; @@ -37,13 +35,8 @@ public class ShadowWifiInfo { reflector(WifiInfoReflector.class, realObject).setMacAddress(newMacAddress); } - @Implementation(maxSdk = JELLY_BEAN) public void setSSID(String ssid) { - if (RuntimeEnvironment.getApiLevel() <= JELLY_BEAN) { - reflector(WifiInfoReflector.class, realObject).setSSID(ssid); - } else { - reflector(WifiInfoReflector.class, realObject).setSSID(getWifiSsid(ssid)); - } + reflector(WifiInfoReflector.class, realObject).setSSID(getWifiSsid(ssid)); } private static Object getWifiSsid(String ssid) { diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWifiManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWifiManager.java index 72c000931..9e17e3531 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWifiManager.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWifiManager.java @@ -1,7 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2; -import static android.os.Build.VERSION_CODES.KITKAT; import static android.os.Build.VERSION_CODES.LOLLIPOP; import static android.os.Build.VERSION_CODES.Q; import static android.os.Build.VERSION_CODES.R; @@ -402,7 +400,7 @@ public class ShadowWifiManager { return dhcpInfo; } - @Implementation(minSdk = JELLY_BEAN_MR2) + @Implementation protected boolean isScanAlwaysAvailable() { return Settings.Global.getInt( getContext().getContentResolver(), Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, 1) @@ -410,7 +408,7 @@ public class ShadowWifiManager { } @HiddenApi - @Implementation(minSdk = KITKAT) + @Implementation protected void connect(WifiConfiguration wifiConfiguration, WifiManager.ActionListener listener) { WifiInfo wifiInfo = getConnectionInfo(); @@ -450,7 +448,7 @@ public class ShadowWifiManager { } @HiddenApi - @Implementation(minSdk = KITKAT) + @Implementation protected void connect(int networkId, WifiManager.ActionListener listener) { WifiConfiguration wifiConfiguration = new WifiConfiguration(); wifiConfiguration.networkId = networkId; diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWifiP2pManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWifiP2pManager.java index 54e6b2800..a433db1e5 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWifiP2pManager.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWifiP2pManager.java @@ -1,7 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.KITKAT; - import android.content.Context; import android.net.wifi.p2p.WifiP2pGroup; import android.net.wifi.p2p.WifiP2pManager; @@ -40,7 +38,7 @@ public class ShadowWifiP2pManager { return groupInfoListener; } - @Implementation(minSdk = KITKAT) + @Implementation protected void setWifiP2pChannels( Channel c, int listeningChannel, int operatingChannel, ActionListener al) { Preconditions.checkNotNull(c); diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWindow.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWindow.java index 91e10364e..78feb1bb4 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWindow.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWindow.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.KITKAT; import static android.os.Build.VERSION_CODES.M; import static android.os.Build.VERSION_CODES.N; import static android.os.Build.VERSION_CODES.Q; @@ -60,7 +59,7 @@ public class ShadowWindow { reflector(WindowReflector.class, realWindow).addSystemFlags(flags); } - @Implementation(minSdk = KITKAT, maxSdk = R) + @Implementation(maxSdk = R) @HiddenApi protected void addPrivateFlags(int flags) { this.privateFlags |= flags; diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWindowManagerGlobal.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWindowManagerGlobal.java index 72992fa80..4f988c39f 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWindowManagerGlobal.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWindowManagerGlobal.java @@ -1,11 +1,11 @@ package org.robolectric.shadows; import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2; import static android.os.Build.VERSION_CODES.P; import static org.robolectric.shadows.ShadowView.useRealGraphics; import static org.robolectric.util.reflector.Reflector.reflector; +import android.annotation.Nullable; import android.app.Instrumentation; import android.content.ClipData; import android.content.Context; @@ -18,7 +18,6 @@ import android.view.IWindowManager; import android.view.IWindowSession; import android.view.View; import android.view.WindowManagerGlobal; -import androidx.annotation.Nullable; import java.lang.reflect.Proxy; import java.util.List; import org.robolectric.RuntimeEnvironment; @@ -35,7 +34,6 @@ import org.robolectric.util.reflector.Static; @Implements( value = WindowManagerGlobal.class, isInAndroidSdk = false, - minSdk = JELLY_BEAN_MR1, looseSignatures = true) public class ShadowWindowManagerGlobal { private static WindowSessionDelegate windowSessionDelegate = new WindowSessionDelegate(); @@ -74,7 +72,7 @@ public class ShadowWindowManagerGlobal { windowSessionDelegate.lastDragClipData = null; } - @Implementation(minSdk = JELLY_BEAN_MR2) + @Implementation protected static synchronized IWindowSession getWindowSession() { if (windowSession == null) { // Use Proxy.newProxyInstance instead of ReflectionHelpers.createDelegatingProxy as there are diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWindowManagerImpl.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWindowManagerImpl.java index 0f6972b26..9fd17e952 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWindowManagerImpl.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWindowManagerImpl.java @@ -9,11 +9,8 @@ import static org.robolectric.RuntimeEnvironment.getApiLevel; import static org.robolectric.util.reflector.Reflector.reflector; import android.content.Context; -import android.content.res.Configuration; import android.graphics.Insets; import android.graphics.Rect; -import android.os.Build.VERSION_CODES; -import android.util.DisplayMetrics; import android.view.Display; import android.view.DisplayCutout; import android.view.InsetsState; @@ -25,13 +22,11 @@ import android.view.WindowManagerImpl; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ImmutableList; import com.google.common.collect.Multimap; -import java.util.HashMap; import java.util.List; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; import org.robolectric.annotation.RealObject; import org.robolectric.annotation.Resetter; -import org.robolectric.shadow.api.Shadow; import org.robolectric.shadows.ShadowViewRootImpl.ViewRootImplReflector; import org.robolectric.util.ReflectionHelpers; import org.robolectric.util.ReflectionHelpers.ClassParameter; @@ -42,28 +37,12 @@ import org.robolectric.util.reflector.ForType; @Implements(value = WindowManagerImpl.class, isInAndroidSdk = false) public class ShadowWindowManagerImpl extends ShadowWindowManager { - private static Display defaultDisplayJB; - @RealObject WindowManagerImpl realObject; private static final Multimap<Integer, View> views = ArrayListMultimap.create(); // removed from WindowManagerImpl in S public static final int NEW_INSETS_MODE_FULL = 2; - /** internal only */ - public static void configureDefaultDisplayForJBOnly( - Configuration configuration, DisplayMetrics displayMetrics) { - Class<?> arg2Type = - ReflectionHelpers.loadClass( - ShadowWindowManagerImpl.class.getClassLoader(), "android.view.CompatibilityInfoHolder"); - - defaultDisplayJB = - ReflectionHelpers.callConstructor( - Display.class, ClassParameter.from(int.class, 0), ClassParameter.from(arg2Type, null)); - ShadowDisplay shadowDisplay = Shadow.extract(defaultDisplayJB); - shadowDisplay.configureForJBOnly(configuration, displayMetrics); - } - @Implementation public void addView(View view, android.view.ViewGroup.LayoutParams layoutParams) { views.put(realObject.getDefaultDisplay().getDisplayId(), view); @@ -89,19 +68,7 @@ public class ShadowWindowManagerImpl extends ShadowWindowManager { @Implementation(maxSdk = JELLY_BEAN) public Display getDefaultDisplay() { - if (getApiLevel() > JELLY_BEAN) { - return reflector(ReflectorWindowManagerImpl.class, realObject).getDefaultDisplay(); - } else { - return defaultDisplayJB; - } - } - - @Implements(className = "android.view.WindowManagerImpl$CompatModeWrapper", maxSdk = JELLY_BEAN) - public static class ShadowCompatModeWrapper { - @Implementation(maxSdk = JELLY_BEAN) - protected Display getDefaultDisplay() { - return defaultDisplayJB; - } + return reflector(ReflectorWindowManagerImpl.class, realObject).getDefaultDisplay(); } /** Re implement to avoid server call */ @@ -160,16 +127,6 @@ public class ShadowWindowManagerImpl extends ShadowWindowManager { @Resetter public static void reset() { - defaultDisplayJB = null; views.clear(); - if (getApiLevel() <= VERSION_CODES.JELLY_BEAN) { - ReflectionHelpers.setStaticField( - WindowManagerImpl.class, - "sWindowManager", - ReflectionHelpers.newInstance(WindowManagerImpl.class)); - HashMap windowManagers = - ReflectionHelpers.getStaticField(WindowManagerImpl.class, "sCompatWindowManagers"); - windowManagers.clear(); - } } } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/UiccCardInfoBuilder.java b/shadows/framework/src/main/java/org/robolectric/shadows/UiccCardInfoBuilder.java index 77db2609c..0ae152b0b 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/UiccCardInfoBuilder.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/UiccCardInfoBuilder.java @@ -1,10 +1,10 @@ package org.robolectric.shadows; +import android.annotation.NonNull; +import android.annotation.RequiresApi; import android.os.Build.VERSION_CODES; import android.telephony.UiccCardInfo; import android.telephony.UiccPortInfo; -import androidx.annotation.NonNull; -import androidx.annotation.RequiresApi; import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.util.ArrayList; import java.util.List; diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/UiccPortInfoBuilder.java b/shadows/framework/src/main/java/org/robolectric/shadows/UiccPortInfoBuilder.java index 23b45b0e5..149596926 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/UiccPortInfoBuilder.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/UiccPortInfoBuilder.java @@ -1,8 +1,8 @@ package org.robolectric.shadows; +import android.annotation.RequiresApi; import android.os.Build.VERSION_CODES; import android.telephony.UiccPortInfo; -import androidx.annotation.RequiresApi; import com.google.errorprone.annotations.CanIgnoreReturnValue; import org.robolectric.util.ReflectionHelpers; import org.robolectric.util.ReflectionHelpers.ClassParameter; diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/_Activity_.java b/shadows/framework/src/main/java/org/robolectric/shadows/_Activity_.java index f6ab51296..fcfcfda7c 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/_Activity_.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/_Activity_.java @@ -28,7 +28,7 @@ public interface _Activity_ { @Accessor("mToken") IBinder getToken(); - // <= KITKAT: + // == KITKAT: void attach( Context context, ActivityThread activityThread, @@ -181,7 +181,7 @@ public interface _Activity_ { @WithType("android.app.Activity$NonConfigurationInstances") Object lastNonConfigurationInstances) { int apiLevel = RuntimeEnvironment.getApiLevel(); - if (apiLevel <= Build.VERSION_CODES.KITKAT) { + if (apiLevel == Build.VERSION_CODES.KITKAT) { attach( baseContext, activityThread, diff --git a/shadows/httpclient/src/main/java/org/robolectric/shadows/httpclient/ShadowDefaultRequestDirector.java b/shadows/httpclient/src/main/java/org/robolectric/shadows/httpclient/ShadowDefaultRequestDirector.java index 12fecbb02..6160dfa0c 100644 --- a/shadows/httpclient/src/main/java/org/robolectric/shadows/httpclient/ShadowDefaultRequestDirector.java +++ b/shadows/httpclient/src/main/java/org/robolectric/shadows/httpclient/ShadowDefaultRequestDirector.java @@ -138,12 +138,16 @@ public class ShadowDefaultRequestDirector { * Get the sent {@link HttpRequest} for the given index. * * @param index The index - * @deprecated Use {@link FakeHttp#getSentHttpRequestInfo(int)} instead.) + * @deprecated Use {@link FakeHttp#getSentHttpRequestInfo(int)} instead. This method will be + * removed in Robolectric 4.13. * @return HttpRequest */ @Deprecated + @InlineMe( + replacement = "FakeHttp.getFakeHttpLayer().getSentHttpRequestInfo(index).getHttpRequest()", + imports = "org.robolectric.shadows.httpclient.FakeHttp") public static HttpRequest getSentHttpRequest(int index) { - return getSentHttpRequestInfo(index).getHttpRequest(); + return FakeHttp.getFakeHttpLayer().getSentHttpRequestInfo(index).getHttpRequest(); } public static HttpRequest getLatestSentHttpRequest() { @@ -159,14 +163,15 @@ public class ShadowDefaultRequestDirector { * Get the sent {@link HttpRequestInfo} for the given index. * * @param index The index - * @deprecated Use {@link FakeHttp#getSentHttpRequest(int)} instead.) + * @deprecated Use {@link FakeHttp#getSentHttpRequest(int)} instead. This method will be removed + * in Robolectric 4.13. * @return HttpRequestInfo */ @Deprecated @InlineMe( replacement = "FakeHttp.getFakeHttpLayer().getSentHttpRequestInfo(index)", imports = "org.robolectric.shadows.httpclient.FakeHttp") - public static final HttpRequestInfo getSentHttpRequestInfo(int index) { + public static HttpRequestInfo getSentHttpRequestInfo(int index) { return FakeHttp.getFakeHttpLayer().getSentHttpRequestInfo(index); } |