From f0e9ac9fca2fca4129fee4958098d7586db8a363 Mon Sep 17 00:00:00 2001 From: Spencer Date: Tue, 2 Apr 2024 11:46:02 -0400 Subject: [PATCH] Fix #5195: Fix RestrictedApi Errors (#5348) ## Explanation Fixes #5195 In the Android/Lint errors, all I can see are just NewApi Errors, which related methods are only available on API 26 and our current min API is 19. Correct me if I am wrong, as discussed with Ben in a meeting, most errors test-related file can be fixed by adding require api annotation, and for all the changed files I think they are test related so I added annotation. I did some investigation java.util.Time replacement method and it requires to install third-party dependency, which I think adds too much complexity so I proceed with the annotation way. And for the color matcher one, I cannot find the replacement method. ## Essential Checklist - [z] The PR title and explanation each start with "Fix #bugnum: " (If this PR fixes part of an issue, prefix the title with "Fix part of #bugnum: ...".) - [x] Any changes to [scripts/assets](https://github.com/oppia/oppia-android/tree/develop/scripts/assets) files have their rationale included in the PR explanation. - [x] The PR follows the [style guide](https://github.com/oppia/oppia-android/wiki/Coding-style-guide). - [x] The PR does not contain any unnecessary code changes from Android Studio ([reference](https://github.com/oppia/oppia-android/wiki/Guidance-on-submitting-a-PR#undo-unnecessary-changes)). - [x] The PR is made from a branch that's **not** called "develop" and is up-to-date with "develop". - [x] The PR is **assigned** to the appropriate reviewers ([reference](https://github.com/oppia/oppia-android/wiki/Guidance-on-submitting-a-PR#clarification-regarding-assignees-and-reviewers-section)). ## For UI-specific PRs only If your PR includes UI-related changes, then: - Add screenshots for portrait/landscape for both a tablet & phone of the before & after UI changes - For the screenshots above, include both English and pseudo-localized (RTL) screenshots (see [RTL guide](https://github.com/oppia/oppia-android/wiki/RTL-Guidelines)) - Add a video showing the full UX flow with a screen reader enabled (see [accessibility guide](https://github.com/oppia/oppia-android/wiki/Accessibility-A11y-Guide)) - For PRs introducing new UI elements or color changes, both light and dark mode screenshots must be included - Add a screenshot demonstrating that you ran affected Espresso tests locally & that they're passing ![Screenshot 2024-02-21 092439](https://github.com/oppia/oppia-android/assets/74568012/2f18db96-4a1e-4c9b-ab61-4986310736b1) --------- Co-authored-by: Adhiambo Peres <59600948+adhiamboperes@users.noreply.github.com> --- .../android/testing/espresso/GenericViewMatchers.kt | 4 ++++ .../testing/junit/OppiaParameterizedTestRunner.kt | 7 +++++++ .../threading/TestCoroutineDispatchersRobolectricImpl.kt | 9 +++++++++ 3 files changed, 20 insertions(+) diff --git a/testing/src/main/java/org/oppia/android/testing/espresso/GenericViewMatchers.kt b/testing/src/main/java/org/oppia/android/testing/espresso/GenericViewMatchers.kt index ea1f8b15de4..efbb360d967 100644 --- a/testing/src/main/java/org/oppia/android/testing/espresso/GenericViewMatchers.kt +++ b/testing/src/main/java/org/oppia/android/testing/espresso/GenericViewMatchers.kt @@ -1,7 +1,9 @@ package org.oppia.android.testing.espresso import android.graphics.drawable.GradientDrawable +import android.os.Build import android.view.View +import androidx.annotation.RequiresApi import org.hamcrest.Description import org.hamcrest.Matcher import org.hamcrest.TypeSafeMatcher @@ -19,6 +21,7 @@ class GenericViewMatchers { * Returns a [Matcher] that verifies a view has a fully opaque background. The view is expected * to have a [GradientDrawable] background. */ + @RequiresApi(Build.VERSION_CODES.N) fun withOpaqueBackground(): Matcher = withColorBackgroundMatching( descriptionSuffix = "an opaque background" ) { color -> color?.extractAlpha() == 0xff } @@ -27,6 +30,7 @@ class GenericViewMatchers { * Returns a [Matcher] with the specified description suffix and color matcher, matching against * filled background colors of views. */ + @RequiresApi(Build.VERSION_CODES.N) private fun withColorBackgroundMatching( @Suppress("SameParameterValue") descriptionSuffix: String, colorMatcher: (Long?) -> Boolean diff --git a/testing/src/main/java/org/oppia/android/testing/junit/OppiaParameterizedTestRunner.kt b/testing/src/main/java/org/oppia/android/testing/junit/OppiaParameterizedTestRunner.kt index 4dd7b3427c7..ae5e3e5f55c 100644 --- a/testing/src/main/java/org/oppia/android/testing/junit/OppiaParameterizedTestRunner.kt +++ b/testing/src/main/java/org/oppia/android/testing/junit/OppiaParameterizedTestRunner.kt @@ -1,5 +1,7 @@ package org.oppia.android.testing.junit +import android.os.Build +import androidx.annotation.RequiresApi import org.junit.runner.Description import org.junit.runner.Runner import org.junit.runner.manipulation.Filter @@ -76,6 +78,7 @@ import kotlin.reflect.KClass * contain (thus they should be treated as undefined outside of tests that specific define their * value via [Iteration]). */ +@RequiresApi(Build.VERSION_CODES.N) class OppiaParameterizedTestRunner(private val testClass: Class<*>) : Suite(testClass, listOf()) { private val parameterizedMethods = computeParameterizedMethods() private val selectedRunnerClass by lazy { fetchSelectedRunnerPlatformClass() } @@ -95,6 +98,7 @@ class OppiaParameterizedTestRunner(private val testClass: Class<*>) : Suite(test override fun getChildren(): MutableList = childrenRunners.toMutableList() + @RequiresApi(Build.VERSION_CODES.N) private fun computeParameterizedMethods(): Map { val fieldsAndParsers = fetchParameterizedFields().map { field -> val valueParser = ParameterValue.createParserForField(field) @@ -184,12 +188,14 @@ class OppiaParameterizedTestRunner(private val testClass: Class<*>) : Suite(test }.associateBy { it.methodName } } + @RequiresApi(Build.VERSION_CODES.N) private fun fetchParameterizedFields(): List { return testClass.declaredFields.mapNotNull { field -> field.getDeclaredAnnotation(Parameter::class.java)?.let { field } } } + @RequiresApi(Build.VERSION_CODES.N) private fun fetchParameterizedMethodDeclarations(): List { return testClass.declaredMethods.mapNotNull { method -> method.getDeclaredAnnotationsByType(Iteration::class.java).map { parameters -> @@ -208,6 +214,7 @@ class OppiaParameterizedTestRunner(private val testClass: Class<*>) : Suite(test } } + @RequiresApi(Build.VERSION_CODES.N) private fun fetchSelectedRunnerPlatformClass(): Class<*> { return checkNotNull(testClass.getDeclaredAnnotation(SelectRunnerPlatform::class.java)) { "All suites using OppiaParameterizedTestRunner must declare their base platform runner" + diff --git a/testing/src/main/java/org/oppia/android/testing/threading/TestCoroutineDispatchersRobolectricImpl.kt b/testing/src/main/java/org/oppia/android/testing/threading/TestCoroutineDispatchersRobolectricImpl.kt index 3c93d9065bd..2f27175670b 100644 --- a/testing/src/main/java/org/oppia/android/testing/threading/TestCoroutineDispatchersRobolectricImpl.kt +++ b/testing/src/main/java/org/oppia/android/testing/threading/TestCoroutineDispatchersRobolectricImpl.kt @@ -1,5 +1,7 @@ package org.oppia.android.testing.threading +import android.os.Build +import androidx.annotation.RequiresApi import org.oppia.android.testing.time.FakeSystemClock import java.lang.reflect.Method import java.time.Duration @@ -34,6 +36,7 @@ class TestCoroutineDispatchersRobolectricImpl @Inject constructor( } while (hasPendingCompletableTasks()) } + @RequiresApi(Build.VERSION_CODES.O) override fun advanceTimeBy(delayTimeMillis: Long) { var remainingDelayMillis = delayTimeMillis while (remainingDelayMillis > 0) { @@ -49,6 +52,7 @@ class TestCoroutineDispatchersRobolectricImpl @Inject constructor( } } + @RequiresApi(Build.VERSION_CODES.O) override fun advanceUntilIdle() { // First, run through all tasks that are currently pending and can be run immediately. runCurrent() @@ -67,6 +71,7 @@ class TestCoroutineDispatchersRobolectricImpl @Inject constructor( } } + @RequiresApi(Build.VERSION_CODES.O) private fun advanceToNextFutureTask( currentTimeMillis: Long, maxDelayMs: Long = Long.MAX_VALUE @@ -94,6 +99,7 @@ class TestCoroutineDispatchersRobolectricImpl @Inject constructor( } /** Returns whether any of the dispatchers have any tasks to run, including in the future. */ + @RequiresApi(Build.VERSION_CODES.O) private fun hasPendingTasks(): Boolean { return backgroundTestDispatcher.hasPendingTasks() || blockingTestDispatcher.hasPendingTasks() || @@ -107,6 +113,7 @@ class TestCoroutineDispatchersRobolectricImpl @Inject constructor( !uiTaskCoordinator.isIdle() } + @RequiresApi(Build.VERSION_CODES.O) private fun getNextFutureTaskTimeMillis(timeMillis: Long): Long? { val nextBackgroundFutureTaskTimeMills = backgroundTestDispatcher.getNextFutureTaskCompletionTimeMillis(timeMillis) @@ -120,6 +127,7 @@ class TestCoroutineDispatchersRobolectricImpl @Inject constructor( return futureTimes.firstOrNull() } + @RequiresApi(Build.VERSION_CODES.O) private fun getNextUiThreadFutureTaskTimeMillis(timeMillis: Long): Long? { return uiTaskCoordinator.getNextUiThreadFutureTaskTimeMillis(timeMillis) } @@ -139,6 +147,7 @@ class TestCoroutineDispatchersRobolectricImpl @Inject constructor( idleMethod.invoke(shadowUiLooper) } + @RequiresApi(Build.VERSION_CODES.O) fun getNextUiThreadFutureTaskTimeMillis(timeMillis: Long): Long? { val nextScheduledTime = nextScheduledTimeMethod.invoke(shadowUiLooper) as Duration val delayMs = nextScheduledTime.toMillis()