diff options
author | Ram Peri <ramperi@google.com> | 2023-01-11 18:53:19 +0000 |
---|---|---|
committer | Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> | 2023-01-11 18:53:19 +0000 |
commit | 686fa50510b1f90838ff431ad8c1fb0d4960f6ba (patch) | |
tree | f36377366d74bb4b35e1ac871b35cb72dbaab84b | |
parent | 3a8c396d0d84fb66549188cc8e80c0374073a72c (diff) | |
parent | 2f74975e5f182ef3aa166e423377e27402829882 (diff) | |
download | robolectric-686fa50510b1f90838ff431ad8c1fb0d4960f6ba.tar.gz |
Merge branch 'upstream-google' into rng7 am: 2f74975e5f
Original change: https://googleplex-android-review.googlesource.com/c/platform/external/robolectric/+/20865458
Change-Id: Ic94f775c7833de804379f44941e7a5fb79c70c73
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
49 files changed, 972 insertions, 364 deletions
diff --git a/.github/workflows/build_native_runtime.yml b/.github/workflows/build_native_runtime.yml index ae94d7c38..0ef105ea2 100644 --- a/.github/workflows/build_native_runtime.yml +++ b/.github/workflows/build_native_runtime.yml @@ -30,11 +30,10 @@ jobs: platform-check-severity: warn location: D:\ install: >- - make - mingw-w64-x86_64-gcc - mingw-w64-x86_64-ninja + base-devel + mingw-w64-x86_64-toolchain mingw-w64-x86_64-cmake - mingw-w64-x86_64-make + mingw-w64-x86_64-ninja - name: Set up JDK 11 uses: actions/setup-java@v3 diff --git a/.github/workflows/check_aggregateDocs.yml b/.github/workflows/check_aggregateDocs.yml deleted file mode 100644 index e3251c429..000000000 --- a/.github/workflows/check_aggregateDocs.yml +++ /dev/null @@ -1,32 +0,0 @@ -name: Check aggregateDocs - -on: - push: - branches: [ master ] - - pull_request: - branches: [ master, google ] - -permissions: - contents: read - -jobs: - check_aggregateDocs: - runs-on: ubuntu-20.04 - - steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 - submodules: recursive - - - name: Set up JDK - uses: actions/setup-java@v3 - with: - distribution: 'zulu' # zulu suports complete JDK list - java-version: 14 - - - uses: gradle/gradle-build-action@v2 - - - name: Run aggregateDocs - run: SKIP_NATIVERUNTIME_BUILD=true ./gradlew clean aggregateDocs # building the native runtime is not required for checking javadoc diff --git a/.github/workflows/gradle_tasks_validation.yml b/.github/workflows/gradle_tasks_validation.yml new file mode 100644 index 000000000..6a8c2fd12 --- /dev/null +++ b/.github/workflows/gradle_tasks_validation.yml @@ -0,0 +1,55 @@ +name: Gradle Tasks Validation + +on: + push: + branches: [ master ] + + pull_request: + branches: [ master, google ] + +permissions: + contents: read + +jobs: + run_aggregateDocs: + runs-on: ubuntu-20.04 + + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + submodules: recursive + + - name: Set up JDK + uses: actions/setup-java@v3 + with: + distribution: 'zulu' + java-version: 14 + + - uses: gradle/gradle-build-action@v2 + + - name: Run aggregateDocs + run: SKIP_NATIVERUNTIME_BUILD=true ./gradlew clean aggregateDocs # building the native runtime is not required for checking javadoc + + run_instrumentAll: + runs-on: ubuntu-20.04 + + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + submodules: recursive + + - name: Set up JDK + uses: actions/setup-java@v3 + with: + distribution: 'zulu' + java-version: 11 + + - uses: gradle/gradle-build-action@v2 + + - name: Run :preinstrumented:instrumentAll + run: SKIP_NATIVERUNTIME_BUILD=true ./gradlew :preinstrumented:instrumentAll + + - name: Run :preinstrumented:instrumentAll with SDK 33 + run: SKIP_NATIVERUNTIME_BUILD=true PREINSTRUMENTED_SDK_VERSIONS=33 ./gradlew :preinstrumented:instrumentAll
\ No newline at end of file @@ -40,7 +40,7 @@ If you'd like to start a new project with Robolectric tests you can refer to `de ```groovy testImplementation "junit:junit:4.13.2" -testImplementation "org.robolectric:robolectric:4.9" +testImplementation "org.robolectric:robolectric:4.9.1" ``` ## Building And Contributing @@ -94,6 +94,6 @@ repositories { } dependencies { - testImplementation "org.robolectric:robolectric:4.9-SNAPSHOT" + testImplementation "org.robolectric:robolectric:4.10-SNAPSHOT" } ``` diff --git a/build.gradle b/build.gradle index ee22a54d1..c224f5b8c 100644 --- a/build.gradle +++ b/build.gradle @@ -14,7 +14,7 @@ buildscript { dependencies { gradle - classpath 'com.android.tools.build:gradle:7.3.0' + classpath 'com.android.tools.build:gradle:7.3.1' classpath 'net.ltgt.gradle:gradle-errorprone-plugin:2.0.2' classpath 'com.netflix.nebula:gradle-aggregate-javadocs-plugin:3.0.1' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion" diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle index a6ba7d79c..082d9dbf3 100644 --- a/buildSrc/build.gradle +++ b/buildSrc/build.gradle @@ -14,5 +14,5 @@ dependencies { api "com.google.guava:guava:31.1-jre" api 'org.jetbrains:annotations:16.0.2' implementation "org.ow2.asm:asm-tree:9.2" - implementation 'com.android.tools.build:gradle:7.3.0' + implementation 'com.android.tools.build:gradle:7.3.1' } diff --git a/buildSrc/src/main/groovy/ShadowsPlugin.groovy b/buildSrc/src/main/groovy/ShadowsPlugin.groovy index c41dc8c30..27aa14fa4 100644 --- a/buildSrc/src/main/groovy/ShadowsPlugin.groovy +++ b/buildSrc/src/main/groovy/ShadowsPlugin.groovy @@ -32,6 +32,7 @@ class ShadowsPlugin implements Plugin<Project> { options.compilerArgs.add("-Aorg.robolectric.annotation.processing.jsonDocsDir=${project.buildDir}/docs/json") options.compilerArgs.add("-Aorg.robolectric.annotation.processing.shadowPackage=${project.shadows.packageName}") options.compilerArgs.add("-Aorg.robolectric.annotation.processing.sdkCheckMode=${project.shadows.sdkCheckMode}") + options.compilerArgs.add("-Aorg.robolectric.annotation.processing.sdks=${project.rootProject.buildDir}/sdks.txt") } // include generated sources in javadoc jar diff --git a/dependencies.gradle b/dependencies.gradle index 1e93657b3..10a586c1f 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -5,20 +5,20 @@ ext { errorproneJavacVersion='9+181-r4173-1' // AndroidX test versions - axtMonitorVersion='1.6.0-beta01' - axtRunnerVersion='1.5.0-beta01' - axtRulesVersion='1.4.1-beta01' - axtCoreVersion='1.5.0-beta01' - axtTruthVersion='1.5.0-beta01' - espressoVersion='3.5.0-beta01' - axtJunitVersion='1.1.4-beta01' + axtMonitorVersion='1.6.0' + axtRunnerVersion='1.5.0' + axtRulesVersion='1.5.0' + axtCoreVersion='1.5.0' + axtTruthVersion='1.5.0' + espressoVersion='3.5.0' + axtJunitVersion='1.1.4' + axtTestServicesVersion='1.4.2' // AndroidX versions coreVersion='1.9.0' appCompatVersion='1.4.1' constraintlayoutVersion='2.1.4' windowVersion='1.0.0' - lifecycleVersion='2.2.0' fragmentVersion='1.5.3' truthVersion='1.1.3' @@ -29,7 +29,7 @@ ext { guavaJREVersion='31.1-jre' - asmVersion='9.3' + asmVersion='9.4' kotlinVersion='1.7.20' autoServiceVersion='1.0.1' diff --git a/integration_tests/androidx/build.gradle b/integration_tests/androidx/build.gradle index 0f2124fcb..96535e1dd 100644 --- a/integration_tests/androidx/build.gradle +++ b/integration_tests/androidx/build.gradle @@ -38,8 +38,6 @@ dependencies { testImplementation("androidx.test:rules:$axtRulesVersion") testImplementation("androidx.test.espresso:espresso-intents:$espressoVersion") testImplementation("androidx.test.ext:truth:$axtTruthVersion") - // TODO: this should be a transitive dependency of core... - testImplementation("androidx.lifecycle:lifecycle-common:$lifecycleVersion") testImplementation("androidx.test.ext:junit:$axtJunitVersion") testImplementation("com.google.truth:truth:$truthVersion") } diff --git a/integration_tests/androidx_test/build.gradle b/integration_tests/androidx_test/build.gradle index 6abd17474..11ac35de6 100644 --- a/integration_tests/androidx_test/build.gradle +++ b/integration_tests/androidx_test/build.gradle @@ -67,4 +67,5 @@ dependencies { androidTestImplementation "androidx.test:core:$axtCoreVersion" androidTestImplementation "androidx.test.ext:junit:$axtJunitVersion" androidTestImplementation "com.google.truth:truth:$truthVersion" + androidTestUtil "androidx.test.services:test-services:$axtTestServicesVersion" } diff --git a/integration_tests/androidx_test/src/main/java/org/robolectric/integrationtests/axt/EspressoActivity.java b/integration_tests/androidx_test/src/main/java/org/robolectric/integrationtests/axt/EspressoActivity.java index 1964fa066..f3e2550ed 100644 --- a/integration_tests/androidx_test/src/main/java/org/robolectric/integrationtests/axt/EspressoActivity.java +++ b/integration_tests/androidx_test/src/main/java/org/robolectric/integrationtests/axt/EspressoActivity.java @@ -2,6 +2,7 @@ package org.robolectric.integrationtests.axt; import android.app.Activity; import android.os.Bundle; +import android.text.InputType; import android.widget.Button; import android.widget.EditText; import org.robolectric.integration.axt.R; @@ -20,6 +21,10 @@ public class EspressoActivity extends Activity { setContentView(R.layout.espresso_activity); editText = findViewById(R.id.edit_text); + // Disable auto-correct for EditText to avoid typed text is changed + // by these features when running tests. + editText.setInputType(editText.getInputType() & (~InputType.TYPE_TEXT_FLAG_AUTO_CORRECT)); + button = findViewById(R.id.button); button.setOnClickListener(view -> buttonClicked = true); } diff --git a/integration_tests/androidx_test/src/main/res/layout/appcompat_activity_with_toolbar_menu.xml b/integration_tests/androidx_test/src/main/res/layout/appcompat_activity_with_toolbar_menu.xml index fc61cc564..c30507330 100644 --- a/integration_tests/androidx_test/src/main/res/layout/appcompat_activity_with_toolbar_menu.xml +++ b/integration_tests/androidx_test/src/main/res/layout/appcompat_activity_with_toolbar_menu.xml @@ -1,17 +1,15 @@ <?xml version="1.0" encoding="utf-8"?> -<LinearLayout - xmlns:android="http://schemas.android.com/apk/res/android" +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/layout" - android:orientation="vertical" android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center_horizontal" - > + android:orientation="vertical"> <androidx.appcompat.widget.Toolbar android:id="@+id/toolbar" - android:layout_height="wrap_content" - android:layout_width="match_parent"/> + android:layout_width="match_parent" + android:layout_height="wrap_content" /> </LinearLayout>
\ No newline at end of file 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 716d4e085..1cbc1979d 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 @@ -1,56 +1,53 @@ <?xml version="1.0" encoding="utf-8"?> -<LinearLayout - xmlns:android="http://schemas.android.com/apk/res/android" +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/layout" - android:orientation="vertical" android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="center_horizontal" - > - - <EditText - android:id="@+id/edit_text" - android:layout_height="wrap_content" - android:layout_width="wrap_content"/> - - <Button - android:id="@+id/button" - android:layout_height="wrap_content" - android:layout_width="wrap_content"/> - - <TextView - android:id="@+id/text_view" - android:layout_height="wrap_content" - android:layout_width="wrap_content" - android:text="Text View"/> - - <TextView - android:id="@+id/text_view_positive_scale_x" - android:layout_height="wrap_content" - android:layout_width="wrap_content" - android:textScaleX="1.5" - android:text="Text View with positive textScaleX"/> - - <TextView - android:id="@+id/text_view_negative_scale_x" - android:layout_height="wrap_content" - android:layout_width="wrap_content" - android:textScaleX="-1.5" - android:text="Text View with negative textScaleX"/> - - <TextView - android:id="@+id/text_view_letter_spacing" - android:layout_height="wrap_content" - android:layout_width="wrap_content" - android:letterSpacing="0.05" - android:text="Text View with letterSpacing"/> - - <EditText - android:id="@+id/edit_text_phone" - android:layout_height="wrap_content" - android:layout_width="wrap_content" - android:inputType="phone" - /> + android:orientation="vertical"> + + <EditText + android:id="@+id/edit_text" + android:layout_width="wrap_content" + android:layout_height="wrap_content" /> + + <Button + android:id="@+id/button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" /> + + <TextView + android:id="@+id/text_view" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Text View" /> + + <TextView + android:id="@+id/text_view_positive_scale_x" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Text View with positive textScaleX" + android:textScaleX="1.5" /> + + <TextView + android:id="@+id/text_view_negative_scale_x" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Text View with negative textScaleX" + android:textScaleX="-1.5" /> + + <TextView + android:id="@+id/text_view_letter_spacing" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:letterSpacing="0.05" + android:text="Text View with letterSpacing" /> + + <EditText + android:id="@+id/edit_text_phone" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:inputType="phone" /> </LinearLayout> diff --git a/integration_tests/androidx_test/src/main/res/layout/espresso_scrolling_activity.xml b/integration_tests/androidx_test/src/main/res/layout/espresso_scrolling_activity.xml index 73b6f48a1..1312a244d 100644 --- a/integration_tests/androidx_test/src/main/res/layout/espresso_scrolling_activity.xml +++ b/integration_tests/androidx_test/src/main/res/layout/espresso_scrolling_activity.xml @@ -1,23 +1,23 @@ <?xml version="1.0" encoding="utf-8"?> -<ScrollView - xmlns:android="http://schemas.android.com/apk/res/android" +<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/scroll_view" android:layout_width="match_parent" - android:layout_height="100dp" - android:id="@+id/scroll_view" > - <LinearLayout - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="vertical"> - <!-- Spacer View --> - <View + android:layout_height="100dp"> + + <LinearLayout android:layout_width="match_parent" - android:layout_height="60dp" - android:background="#FF0000FF"/> - <!-- Button View that is only partially visible --> - <Button - android:layout_width="match_parent" - android:layout_height="60dp" - android:id="@+id/button" - android:text="Click me!" /> - </LinearLayout> + android:layout_height="wrap_content" + android:orientation="vertical"> + <!-- Spacer View --> + <View + android:layout_width="match_parent" + android:layout_height="60dp" + android:background="#FF0000FF" /> + <!-- Button View that is only partially visible --> + <Button + android:id="@+id/button" + android:layout_width="match_parent" + android:layout_height="60dp" + android:text="Click me!" /> + </LinearLayout> </ScrollView>
\ No newline at end of file diff --git a/integration_tests/compat-target28/src/main/AndroidManifest.xml b/integration_tests/compat-target28/src/main/AndroidManifest.xml index daca43208..a0c0db960 100644 --- a/integration_tests/compat-target28/src/main/AndroidManifest.xml +++ b/integration_tests/compat-target28/src/main/AndroidManifest.xml @@ -1,4 +1,4 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="org.robolectric.integrationtests.compattarget29"> +<manifest package="org.robolectric.integrationtests.compattarget28"> <application /> </manifest> 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 4605b9ac6..ee56fc6d2 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 @@ -2,6 +2,7 @@ package org.robolectric.integration.compat.target28 import android.content.Context import android.os.Build +import android.speech.SpeechRecognizer import com.google.common.truth.Truth.assertThat import org.junit.Test import org.junit.runner.RunWith @@ -35,4 +36,15 @@ class NormalCompatibilityTest { fun `Initialize Activity succeed`() { Robolectric.setupActivity(TestActivity::class.java) } + + @Test + fun `Initialize TelephonyManager succeed`() { + val telephonyManager = application.getSystemService(Context.TELEPHONY_SERVICE) + assertThat(telephonyManager).isNotNull() + } + + @Test + fun `Create speech recognizer succeed`() { + assertThat(SpeechRecognizer.createSpeechRecognizer(application)).isNotNull() + } } diff --git a/integration_tests/ctesque/build.gradle b/integration_tests/ctesque/build.gradle index 7d88d3d58..11f27e1d6 100644 --- a/integration_tests/ctesque/build.gradle +++ b/integration_tests/ctesque/build.gradle @@ -47,7 +47,7 @@ android { dependencies { implementation project(':testapp') - testImplementation project(":robolectric") + testImplementation project(':robolectric') testImplementation "junit:junit:${junitVersion}" testImplementation("androidx.test:monitor:$axtMonitorVersion") testImplementation("androidx.test:runner:$axtRunnerVersion") @@ -67,4 +67,5 @@ dependencies { androidTestImplementation("androidx.test.ext:truth:$axtTruthVersion") androidTestImplementation("com.google.truth:truth:${truthVersion}") androidTestImplementation("com.google.guava:guava:$guavaJREVersion") + androidTestUtil "androidx.test.services:test-services:$axtTestServicesVersion" } diff --git a/integration_tests/ctesque/src/sharedTest/java/android/content/res/ResourcesTest.java b/integration_tests/ctesque/src/sharedTest/java/android/content/res/ResourcesTest.java index 131c45e34..c1922b60d 100644 --- a/integration_tests/ctesque/src/sharedTest/java/android/content/res/ResourcesTest.java +++ b/integration_tests/ctesque/src/sharedTest/java/android/content/res/ResourcesTest.java @@ -647,9 +647,12 @@ public class ResourcesTest { } @Test - @Ignore("todo: incorrect behavior on robolectric vs framework?") - public void openRawResourceFd_returnsNull_todo_FIX() { - assertThat(resources.openRawResourceFd(R.raw.raw_resource)).isNull(); + public void openRawResourceFd_withNonCompressedFile_returnsNotNull() throws IOException { + // This test will run on non-legacy resource mode in Robolectric environment. + // To test behavior on legacy mode environment, please see ShadowResourceTest. + try (AssetFileDescriptor afd = resources.openRawResourceFd(R.raw.raw_resource)) { + assertThat(afd).isNotNull(); + } } @Test diff --git a/nativeruntime/src/main/java/org/robolectric/nativeruntime/DefaultNativeRuntimeLoader.java b/nativeruntime/src/main/java/org/robolectric/nativeruntime/DefaultNativeRuntimeLoader.java index 57160522a..5221a4b8a 100644 --- a/nativeruntime/src/main/java/org/robolectric/nativeruntime/DefaultNativeRuntimeLoader.java +++ b/nativeruntime/src/main/java/org/robolectric/nativeruntime/DefaultNativeRuntimeLoader.java @@ -1,22 +1,33 @@ package org.robolectric.nativeruntime; +import static android.os.Build.VERSION_CODES.O; import static com.google.common.base.StandardSystemProperty.OS_ARCH; import static com.google.common.base.StandardSystemProperty.OS_NAME; import android.database.CursorWindow; +import android.os.Build; import com.google.auto.service.AutoService; import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableMap; import com.google.common.io.Files; import com.google.common.io.Resources; -import java.io.File; import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; import java.net.URL; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Iterator; import java.util.Locale; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Stream; import javax.annotation.Priority; import org.robolectric.pluginapi.NativeRuntimeLoader; import org.robolectric.util.PerfStatsCollector; +import org.robolectric.util.TempDirectory; import org.robolectric.util.inject.Injector; /** Loads the Robolectric native runtime. */ @@ -28,6 +39,8 @@ public class DefaultNativeRuntimeLoader implements NativeRuntimeLoader { private static final AtomicReference<NativeRuntimeLoader> nativeRuntimeLoader = new AtomicReference<>(); + private TempDirectory extractDirectory; + public static void injectAndLoad() { // Ensure a single instance. synchronized (nativeRuntimeLoader) { @@ -60,20 +73,86 @@ public class DefaultNativeRuntimeLoader implements NativeRuntimeLoader { .measure( "loadNativeRuntime", () -> { - String libraryName = System.mapLibraryName("robolectric-nativeruntime"); + extractDirectory = new TempDirectory("nativeruntime"); System.setProperty( "robolectric.nativeruntime.languageTag", Locale.getDefault().toLanguageTag()); - File tmpLibraryFile = java.nio.file.Files.createTempFile("", libraryName).toFile(); - tmpLibraryFile.deleteOnExit(); - URL resource = Resources.getResource(nativeLibraryPath()); - Resources.asByteSource(resource).copyTo(Files.asByteSink(tmpLibraryFile)); - System.load(tmpLibraryFile.getAbsolutePath()); + if (Build.VERSION.SDK_INT >= O) { + maybeCopyFonts(extractDirectory); + } + maybeCopyIcuData(extractDirectory); + loadLibrary(extractDirectory); }); } catch (IOException e) { throw new AssertionError("Unable to load Robolectric native runtime library", e); } } + /** Attempts to load the ICU dat file. This is only relevant for native graphics. */ + private void maybeCopyIcuData(TempDirectory tempDirectory) throws IOException { + URL icuDatUrl; + try { + icuDatUrl = Resources.getResource("icu/icudt68l.dat"); + } catch (IllegalArgumentException e) { + return; + } + Path icuPath = tempDirectory.create("icu"); + Path icuDatPath = tempDirectory.getBasePath().resolve("icu/icudt68l.dat"); + Resources.asByteSource(icuDatUrl).copyTo(Files.asByteSink(icuDatPath.toFile())); + System.setProperty("icu.dir", icuPath.toAbsolutePath().toString()); + } + + /** + * Attempts to copy the system fonts to a temporary directory. This is only relevant for native + * graphics. + */ + private void maybeCopyFonts(TempDirectory tempDirectory) throws IOException { + URI fontsUri = null; + try { + fontsUri = Resources.getResource("fonts/").toURI(); + } catch (IllegalArgumentException | URISyntaxException e) { + return; + } + + FileSystem zipfs = null; + + if ("jar".equals(fontsUri.getScheme())) { + zipfs = FileSystems.newFileSystem(fontsUri, ImmutableMap.of("create", "true")); + } + + Path fontsInputPath = Paths.get(fontsUri); + Path fontsOutputPath = tempDirectory.create("fonts"); + + try (Stream<Path> pathStream = java.nio.file.Files.walk(fontsInputPath)) { + Iterator<Path> fileIterator = pathStream.iterator(); + while (fileIterator.hasNext()) { + Path path = fileIterator.next(); + // Avoid copying parent directory. + if ("fonts".equals(path.getFileName().toString())) { + continue; + } + String fontPath = "fonts/" + path.getFileName(); + URL resource = Resources.getResource(fontPath); + Path outputPath = tempDirectory.getBasePath().resolve(fontPath); + Resources.asByteSource(resource).copyTo(Files.asByteSink(outputPath.toFile())); + } + } + System.setProperty( + "robolectric.nativeruntime.fontdir", fontsOutputPath.toAbsolutePath().toString()); + if (zipfs != null) { + zipfs.close(); + } + } + + 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())); + System.load(libraryPath.toAbsolutePath().toString()); + } + private static boolean isSupported() { return ("mac".equals(osName()) && ("aarch64".equals(arch()) || "x86_64".equals(arch()))) || ("linux".equals(osName()) && "x86_64".equals(arch())) @@ -111,4 +190,14 @@ public class DefaultNativeRuntimeLoader implements NativeRuntimeLoader { static boolean isLoaded() { return loaded.get(); } + + @VisibleForTesting + Path getDirectory() { + return extractDirectory == null ? null : extractDirectory.getBasePath(); + } + + @VisibleForTesting + static void resetLoaded() { + loaded.set(false); + } } diff --git a/nativeruntime/src/test/java/org/robolectric/nativeruntime/DefaultNativeRuntimeLazyLoadTest.java b/nativeruntime/src/test/java/org/robolectric/nativeruntime/DefaultNativeRuntimeLazyLoadTest.java index ec86818f1..5fa5ad364 100644 --- a/nativeruntime/src/test/java/org/robolectric/nativeruntime/DefaultNativeRuntimeLazyLoadTest.java +++ b/nativeruntime/src/test/java/org/robolectric/nativeruntime/DefaultNativeRuntimeLazyLoadTest.java @@ -1,6 +1,7 @@ package org.robolectric.nativeruntime; import static com.google.common.truth.Truth.assertThat; +import static org.robolectric.annotation.Config.ALL_SDKS; import android.app.Application; import android.database.CursorWindow; @@ -8,8 +9,10 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; @RunWith(RobolectricTestRunner.class) +@Config(sdk = ALL_SDKS) public final class DefaultNativeRuntimeLazyLoadTest { /** diff --git a/nativeruntime/src/test/java/org/robolectric/nativeruntime/DefaultNativeRuntimeLoaderTest.java b/nativeruntime/src/test/java/org/robolectric/nativeruntime/DefaultNativeRuntimeLoaderTest.java index cbb9cf1f5..03911593e 100644 --- a/nativeruntime/src/test/java/org/robolectric/nativeruntime/DefaultNativeRuntimeLoaderTest.java +++ b/nativeruntime/src/test/java/org/robolectric/nativeruntime/DefaultNativeRuntimeLoaderTest.java @@ -1,21 +1,59 @@ package org.robolectric.nativeruntime; +import static android.os.Build.VERSION_CODES.O; +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assume.assumeTrue; + import android.database.CursorWindow; import android.database.sqlite.SQLiteDatabase; +import java.nio.file.Path; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; @RunWith(RobolectricTestRunner.class) public final class DefaultNativeRuntimeLoaderTest { ExecutorService executor = Executors.newSingleThreadExecutor(); + @Before + public void setUp() { + DefaultNativeRuntimeLoader.resetLoaded(); + } + @Test public void concurrentLoad() throws Exception { executor.execute(() -> SQLiteDatabase.create(null)); CursorWindow cursorWindow = new CursorWindow("sdfsdf"); cursorWindow.close(); } + + @Test + public void extracts_fontsAndIcuData() { + assumeTrue(hasResource("fonts")); + assumeTrue(hasResource("icu/icudt68l.dat")); + DefaultNativeRuntimeLoader defaultNativeRuntimeLoader = new DefaultNativeRuntimeLoader(); + defaultNativeRuntimeLoader.ensureLoaded(); + // Check that extraction of some key files worked. + Path root = defaultNativeRuntimeLoader.getDirectory(); + assertThat(root.resolve("icu/icudt68l.dat").toFile().exists()).isTrue(); + if (RuntimeEnvironment.getApiLevel() >= O) { + assertThat(root.resolve("fonts/fonts.xml").toFile().exists()).isTrue(); + } + } + + @Test + public void tempDirectory() { + DefaultNativeRuntimeLoader defaultNativeRuntimeLoader = new DefaultNativeRuntimeLoader(); + assertThat((Object) defaultNativeRuntimeLoader.getDirectory()).isNull(); + defaultNativeRuntimeLoader.ensureLoaded(); + assertThat((Object) defaultNativeRuntimeLoader.getDirectory()).isNotNull(); + } + + private static boolean hasResource(String name) { + return Thread.currentThread().getContextClassLoader().getResource(name) != null; + } } diff --git a/nativeruntime/src/test/resources/AndroidManifest.xml b/nativeruntime/src/test/resources/AndroidManifest.xml new file mode 100644 index 000000000..efda5aea1 --- /dev/null +++ b/nativeruntime/src/test/resources/AndroidManifest.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + package="org.robolectric.nativeruntime"> + + <uses-sdk android:minSdkVersion="16" android:targetSdkVersion="33"/> + <application /> +</manifest> diff --git a/nativeruntime/src/test/resources/com/android/tools/test_config.properties b/nativeruntime/src/test/resources/com/android/tools/test_config.properties new file mode 100644 index 000000000..1fa076d61 --- /dev/null +++ b/nativeruntime/src/test/resources/com/android/tools/test_config.properties @@ -0,0 +1,5 @@ +android_merged_assets=src/test/resources/assets +android_merged_resources=src/test/resources/res +android_merged_manifest=src/test/resources/AndroidManifest.xml +android_custom_package=org.robolectric +android_resource_apk=src/test/resources/resources.ap_ diff --git a/nativeruntime/src/test/resources/resources.ap_ b/nativeruntime/src/test/resources/resources.ap_ Binary files differnew file mode 100644 index 000000000..bc05da2ad --- /dev/null +++ b/nativeruntime/src/test/resources/resources.ap_ diff --git a/preinstrumented/build.gradle b/preinstrumented/build.gradle index 438307c42..95d533e4d 100644 --- a/preinstrumented/build.gradle +++ b/preinstrumented/build.gradle @@ -116,11 +116,14 @@ if (System.getenv('PUBLISH_PREINSTRUMENTED_JARS') == "true") { } } -def sdksToInstrument() { +static def sdksToInstrument() { var result = AndroidSdk.ALL_SDKS - var sdkFilter = (System.getenv('PREINSTRUMENTED_SDK_VERSIONS') ?: "").split(",").collect { it as Integer } - if (sdkFilter.size > 0) { - result = result.findAll { sdkFilter.contains(it.apiLevel) } + var preInstrumentedSdkVersions = (System.getenv('PREINSTRUMENTED_SDK_VERSIONS') ?: "") + if (preInstrumentedSdkVersions.length() > 0) { + var sdkFilter = preInstrumentedSdkVersions.split(",").collect { it as Integer } + if (sdkFilter.size > 0) { + result = result.findAll { sdkFilter.contains(it.apiLevel) } + } } return result } diff --git a/resources/src/main/java/org/robolectric/res/android/ResTable.java b/resources/src/main/java/org/robolectric/res/android/ResTable.java index 627a169ba..edc1a0c86 100644 --- a/resources/src/main/java/org/robolectric/res/android/ResTable.java +++ b/resources/src/main/java/org/robolectric/res/android/ResTable.java @@ -46,8 +46,10 @@ import org.robolectric.res.android.ResourceTypes.ResTable_type; import org.robolectric.res.android.ResourceTypes.ResTable_typeSpec; import org.robolectric.res.android.ResourceTypes.Res_value; -// transliterated from https://android.googlesource.com/platform/frameworks/base/+/android-9.0.0_r12/libs/androidfw/ResourceTypes.cpp -// and https://android.googlesource.com/platform/frameworks/base/+/android-9.0.0_r12/include/androidfw/ResourceTypes.h +// transliterated from +// https://android.googlesource.com/platform/frameworks/base/+/android-9.0.0_r12/libs/androidfw/ResourceTypes.cpp +// and +// https://android.googlesource.com/platform/frameworks/base/+/android-9.0.0_r12/libs/androidfw/include/androidfw/ResourceTypes.h @SuppressWarnings("NewApi") public class ResTable { @@ -2693,11 +2695,7 @@ public class ResTable { } public void lock() { - try { - mLock.acquire(); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } + mLock.acquireUninterruptibly(); } public void unlock() { diff --git a/robolectric/build.gradle b/robolectric/build.gradle index 163dfb70f..cf3f7e3d5 100644 --- a/robolectric/build.gradle +++ b/robolectric/build.gradle @@ -53,7 +53,6 @@ dependencies { testImplementation "org.mockito:mockito-core:${mockitoVersion}" testImplementation "org.hamcrest:hamcrest-junit:2.0.0.0" testImplementation "androidx.test:core:$axtCoreVersion@aar" - testImplementation "androidx.lifecycle:lifecycle-common:2.5.1" testImplementation "androidx.test.ext:junit:$axtJunitVersion@aar" testImplementation "androidx.test.ext:truth:$axtTruthVersion@aar" testImplementation "androidx.test:runner:$axtRunnerVersion@aar" diff --git a/robolectric/src/main/java/org/robolectric/RobolectricTestRunner.java b/robolectric/src/main/java/org/robolectric/RobolectricTestRunner.java index 3db82c082..affe32afa 100644 --- a/robolectric/src/main/java/org/robolectric/RobolectricTestRunner.java +++ b/robolectric/src/main/java/org/robolectric/RobolectricTestRunner.java @@ -273,8 +273,11 @@ public class RobolectricTestRunner extends SandboxTestRunner { if (resourcesMode == ResourcesMode.LEGACY && sdk.getApiLevel() > Build.VERSION_CODES.P) { System.err.println( - "Skip " + method.getName() + " because Robolectric doesn't support legacy mode after P"); - throw new AssumptionViolatedException("Robolectric doesn't support legacy mode after P"); + "Skip " + + method.getName() + + " because Robolectric doesn't support legacy resources mode after P"); + throw new AssumptionViolatedException( + "Robolectric doesn't support legacy resources mode after P"); } LooperMode.Mode looperMode = roboMethod.configuration == null diff --git a/robolectric/src/test/java/org/robolectric/junit/rules/ExpectedLogMessagesRuleTest.java b/robolectric/src/test/java/org/robolectric/junit/rules/ExpectedLogMessagesRuleTest.java index cd0f30191..a84ad0606 100644 --- a/robolectric/src/test/java/org/robolectric/junit/rules/ExpectedLogMessagesRuleTest.java +++ b/robolectric/src/test/java/org/robolectric/junit/rules/ExpectedLogMessagesRuleTest.java @@ -179,7 +179,7 @@ public final class ExpectedLogMessagesRuleTest { + "\\s+tag='Mytag'" + "\\s+msg='message2'" + "\\s+throwable=java.lang.IllegalArgumentException" - + "(\\s+at .*\\)\\n)+" + + "(\\s+at .*\\)\\R)+" + "\\s+}][\\s\\S]*"; String expectedNotObservedPattern = "[\\s\\S]*Expected, but not observed:" diff --git a/robolectric/src/test/java/org/robolectric/junit/runner/EnclosedTest.java b/robolectric/src/test/java/org/robolectric/junit/runner/EnclosedTest.java new file mode 100644 index 000000000..ee4ee3c07 --- /dev/null +++ b/robolectric/src/test/java/org/robolectric/junit/runner/EnclosedTest.java @@ -0,0 +1,46 @@ +package org.robolectric.junit.runner; + +import static com.google.common.truth.Truth.assertThat; + +import org.junit.Before; +import org.junit.Test; +import org.junit.experimental.runners.Enclosed; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; + +@RunWith(Enclosed.class) +public class EnclosedTest { + public abstract static class BaseTest { + protected String foo; + } + + @RunWith(RobolectricTestRunner.class) + public static class MyFirstTest extends BaseTest { + private static final String STRING = "Hello1"; + + @Before + public void setUp() { + foo = STRING; + } + + @Test + public void testStringInitialization() { + assertThat(foo).isEqualTo(STRING); + } + } + + @RunWith(RobolectricTestRunner.class) + public static class MySecondTest extends BaseTest { + private static final String STRING = "Hello2"; + + @Before + public void setUp() { + foo = STRING; + } + + @Test + public void testStringInitialization() { + assertThat(foo).isEqualTo(STRING); + } + } +} diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowActivityTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowActivityTest.java index c3250a7a8..08b2a2060 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowActivityTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowActivityTest.java @@ -118,14 +118,15 @@ public class ShadowActivityTest { @Test public void createActivity_noDisplayFinished_shouldFinishActivity() { - ActivityController<Activity> controller = Robolectric.buildActivity(Activity.class); - controller.get().setTheme(android.R.style.Theme_NoDisplay); - controller.create(); - controller.get().finish(); - controller.start().visible().resume(); + try (ActivityController<Activity> controller = Robolectric.buildActivity(Activity.class)) { + controller.get().setTheme(android.R.style.Theme_NoDisplay); + controller.create(); + controller.get().finish(); + controller.start().visible().resume(); - activity = controller.get(); - assertThat(activity.isFinishing()).isTrue(); + activity = controller.get(); + assertThat(activity.isFinishing()).isTrue(); + } } @Config(minSdk = M) @@ -154,35 +155,38 @@ public class ShadowActivityTest { public void shouldNotComplainIfActivityIsDestroyedWhileAnotherActivityHasRegisteredBroadcastReceivers() throws Exception { - ActivityController<DialogCreatingActivity> controller = - Robolectric.buildActivity(DialogCreatingActivity.class); - activity = controller.get(); + try (ActivityController<DialogCreatingActivity> controller = + Robolectric.buildActivity(DialogCreatingActivity.class)) { + activity = controller.get(); - DialogLifeCycleActivity activity2 = Robolectric.setupActivity(DialogLifeCycleActivity.class); - activity2.registerReceiver(new AppWidgetProvider(), new IntentFilter()); + DialogLifeCycleActivity activity2 = Robolectric.setupActivity(DialogLifeCycleActivity.class); + activity2.registerReceiver(new AppWidgetProvider(), new IntentFilter()); - controller.destroy(); + controller.destroy(); + } } @Test public void shouldNotRegisterNullBroadcastReceiver() { - ActivityController<DialogCreatingActivity> controller = - Robolectric.buildActivity(DialogCreatingActivity.class); - activity = controller.get(); - activity.registerReceiver(null, new IntentFilter()); + try (ActivityController<DialogCreatingActivity> controller = + Robolectric.buildActivity(DialogCreatingActivity.class)) { + activity = controller.get(); + activity.registerReceiver(null, new IntentFilter()); - controller.destroy(); + controller.destroy(); + } } @Test @Config(minSdk = JELLY_BEAN_MR1) public void shouldReportDestroyedStatus() { - ActivityController<DialogCreatingActivity> controller = - Robolectric.buildActivity(DialogCreatingActivity.class); - activity = controller.get(); + try (ActivityController<DialogCreatingActivity> controller = + Robolectric.buildActivity(DialogCreatingActivity.class)) { + activity = controller.get(); - controller.destroy(); - assertThat(activity.isDestroyed()).isTrue(); + controller.destroy(); + assertThat(activity.isDestroyed()).isTrue(); + } } @Test @@ -259,12 +263,14 @@ public class ShadowActivityTest { @Test public void startActivityForResultAndReceiveResult_whenNoIntentMatches_shouldThrowException() { - ThrowOnResultActivity activity = Robolectric.buildActivity(ThrowOnResultActivity.class).get(); - activity.startActivityForResult(new Intent().setType("audio/*"), 123); - activity.startActivityForResult(new Intent().setType("image/*"), 456); - - Intent requestIntent = new Intent().setType("video/*"); - try { + Intent requestIntent = new Intent(); + try (ActivityController<ThrowOnResultActivity> controller = + Robolectric.buildActivity(ThrowOnResultActivity.class)) { + ThrowOnResultActivity activity = controller.get(); + activity.startActivityForResult(new Intent().setType("audio/*"), 123); + activity.startActivityForResult(new Intent().setType("image/*"), 456); + + requestIntent.setType("video/*"); shadowOf(activity) .receiveResult( requestIntent, Activity.RESULT_OK, new Intent().setData(Uri.parse("content:foo"))); @@ -600,11 +606,13 @@ public class ShadowActivityTest { @Test // unclear what the correct behavior should be here... public void shouldPopulateWindowDecorViewWithMergeLayoutContents() { - Activity activity = Robolectric.buildActivity(Activity.class).create().get(); - activity.setContentView(R.layout.toplevel_merge); + try (ActivityController<Activity> controller = Robolectric.buildActivity(Activity.class)) { + Activity activity = controller.create().get(); + activity.setContentView(R.layout.toplevel_merge); - View contentView = activity.findViewById(android.R.id.content); - assertThat(((ViewGroup) contentView).getChildCount()).isEqualTo(2); + View contentView = activity.findViewById(android.R.id.content); + assertThat(((ViewGroup) contentView).getChildCount()).isEqualTo(2); + } } @Test @@ -1011,10 +1019,12 @@ public class ShadowActivityTest { @Test public void getActionBar_shouldWorkIfActivityHasAnAppropriateTheme() { - ActionBarThemedActivity myActivity = - Robolectric.buildActivity(ActionBarThemedActivity.class).create().get(); - ActionBar actionBar = myActivity.getActionBar(); - assertThat(actionBar).isNotNull(); + try (ActivityController<ActionBarThemedActivity> controller = + Robolectric.buildActivity(ActionBarThemedActivity.class)) { + ActionBarThemedActivity myActivity = controller.create().get(); + ActionBar actionBar = myActivity.getActionBar(); + assertThat(actionBar).isNotNull(); + } } public static class ActionBarThemedActivity extends Activity { @@ -1330,157 +1340,168 @@ public class ShadowActivityTest { @Test @Config(minSdk = O) public void buildActivity_noOptionsBundle_launchesOnDefaultDisplay() { - Activity activity = Robolectric.buildActivity(Activity.class, null).setup().get(); + try (ActivityController<Activity> controller = + Robolectric.buildActivity(Activity.class, null)) { + Activity activity = controller.setup().get(); - assertThat(activity.getWindowManager().getDefaultDisplay().getDisplayId()) - .isEqualTo(Display.DEFAULT_DISPLAY); + assertThat(activity.getWindowManager().getDefaultDisplay().getDisplayId()) + .isEqualTo(Display.DEFAULT_DISPLAY); + } } @Test @Config(minSdk = O) public void buildActivity_optionBundleWithNoDisplaySet_launchesOnDefaultDisplay() { - Activity activity = - Robolectric.buildActivity(Activity.class, null, ActivityOptions.makeBasic().toBundle()) - .setup() - .get(); + try (ActivityController<Activity> controller = + Robolectric.buildActivity(Activity.class, null, ActivityOptions.makeBasic().toBundle())) { + Activity activity = controller.setup().get(); - assertThat(activity.getWindowManager().getDefaultDisplay().getDisplayId()) - .isEqualTo(Display.DEFAULT_DISPLAY); + assertThat(activity.getWindowManager().getDefaultDisplay().getDisplayId()) + .isEqualTo(Display.DEFAULT_DISPLAY); + } } @Test @Config(minSdk = O) public void buildActivity_optionBundleWithDefaultDisplaySet_launchesOnDefaultDisplay() { - Activity activity = + try (ActivityController<Activity> controller = Robolectric.buildActivity( - Activity.class, - null, - ActivityOptions.makeBasic().setLaunchDisplayId(Display.DEFAULT_DISPLAY).toBundle()) - .setup() - .get(); - - assertThat(activity.getWindowManager().getDefaultDisplay().getDisplayId()) - .isEqualTo(Display.DEFAULT_DISPLAY); + Activity.class, + null, + ActivityOptions.makeBasic().setLaunchDisplayId(Display.DEFAULT_DISPLAY).toBundle())) { + Activity activity = controller.setup().get(); + assertThat(activity.getWindowManager().getDefaultDisplay().getDisplayId()) + .isEqualTo(Display.DEFAULT_DISPLAY); + } } @Test @Config(minSdk = O) public void buildActivity_optionBundleWithValidNonDefaultDisplaySet_launchesOnSpecifiedDisplay() { int displayId = ShadowDisplayManager.addDisplay(""); - - Activity activity = + try (ActivityController<Activity> controller = Robolectric.buildActivity( - Activity.class, - null, - ActivityOptions.makeBasic().setLaunchDisplayId(displayId).toBundle()) - .setup() - .get(); - - assertThat(activity.getWindowManager().getDefaultDisplay().getDisplayId()) - .isNotEqualTo(Display.DEFAULT_DISPLAY); - assertThat(activity.getWindowManager().getDefaultDisplay().getDisplayId()).isEqualTo(displayId); + Activity.class, + null, + ActivityOptions.makeBasic().setLaunchDisplayId(displayId).toBundle())) { + Activity activity = controller.setup().get(); + assertThat(activity.getWindowManager().getDefaultDisplay().getDisplayId()) + .isNotEqualTo(Display.DEFAULT_DISPLAY); + assertThat(activity.getWindowManager().getDefaultDisplay().getDisplayId()) + .isEqualTo(displayId); + } } @Test @Config(minSdk = O) public void buildActivity_optionBundleWithInvalidNonDefaultDisplaySet_launchesOnDefaultDisplay() { - Activity activity = + try (ActivityController<Activity> controller = Robolectric.buildActivity( - Activity.class, - null, - ActivityOptions.makeBasic().setLaunchDisplayId(123).toBundle()) - .setup() - .get(); - - assertThat(activity.getWindowManager().getDefaultDisplay().getDisplayId()) - .isEqualTo(Display.DEFAULT_DISPLAY); + Activity.class, null, ActivityOptions.makeBasic().setLaunchDisplayId(123).toBundle())) { + Activity activity = controller.setup().get(); + assertThat(activity.getWindowManager().getDefaultDisplay().getDisplayId()) + .isEqualTo(Display.DEFAULT_DISPLAY); + } } @Test @Config(minSdk = Q) public void callOnGetDirectActions_succeeds() { - ActivityController<TestActivity> controller = Robolectric.buildActivity(TestActivity.class); - TestActivity testActivity = controller.setup().get(); - Consumer<List<DirectAction>> testConsumer = - (directActions) -> { - assertThat(directActions.size()).isEqualTo(1); - DirectAction action = directActions.get(0); - assertThat(action.getId()).isEqualTo(testActivity.getDirectActionForTesting().getId()); - ComponentName componentName = action.getExtras().getParcelable("componentName"); - assertThat(componentName.compareTo(testActivity.getComponentName())).isEqualTo(0); - }; - shadowOf(testActivity).callOnGetDirectActions(new CancellationSignal(), testConsumer); + try (ActivityController<TestActivity> controller = + Robolectric.buildActivity(TestActivity.class)) { + TestActivity testActivity = controller.setup().get(); + Consumer<List<DirectAction>> testConsumer = + (directActions) -> { + assertThat(directActions.size()).isEqualTo(1); + DirectAction action = directActions.get(0); + assertThat(action.getId()).isEqualTo(testActivity.getDirectActionForTesting().getId()); + ComponentName componentName = action.getExtras().getParcelable("componentName"); + assertThat(componentName.compareTo(testActivity.getComponentName())).isEqualTo(0); + }; + shadowOf(testActivity).callOnGetDirectActions(new CancellationSignal(), testConsumer); + } } @Test @Config(minSdk = Q) public void callOnGetDirectActions_malformedDirectAction_fails() { - ActivityController<TestActivity> controller = Robolectric.buildActivity(TestActivity.class); - TestActivity testActivity = controller.setup().get(); - // malformed DirectAction has missing LocusId - testActivity.setReturnMalformedDirectAction(true); - assertThrows( - NullPointerException.class, - () -> { - shadowOf(testActivity).callOnGetDirectActions(new CancellationSignal(), (unused) -> {}); - }); + try (ActivityController<TestActivity> controller = + Robolectric.buildActivity(TestActivity.class)) { + TestActivity testActivity = controller.setup().get(); + // malformed DirectAction has missing LocusId + testActivity.setReturnMalformedDirectAction(true); + assertThrows( + NullPointerException.class, + () -> { + shadowOf(testActivity).callOnGetDirectActions(new CancellationSignal(), (unused) -> {}); + }); + } } @Test @Config(minSdk = S) public void splashScreen_setThemeId_succeeds() { int splashScreenThemeId = 173; - Activity activity = Robolectric.buildActivity(Activity.class, null).setup().get(); + try (ActivityController<Activity> controller = Robolectric.buildActivity(Activity.class)) { + Activity activity = controller.setup().get(); - activity.getSplashScreen().setSplashScreenTheme(splashScreenThemeId); + activity.getSplashScreen().setSplashScreenTheme(splashScreenThemeId); - RoboSplashScreen roboSplashScreen = (RoboSplashScreen) activity.getSplashScreen(); - assertThat(roboSplashScreen.getSplashScreenTheme()).isEqualTo(splashScreenThemeId); + RoboSplashScreen roboSplashScreen = (RoboSplashScreen) activity.getSplashScreen(); + assertThat(roboSplashScreen.getSplashScreenTheme()).isEqualTo(splashScreenThemeId); + } } @Test @Config(minSdk = S) public void splashScreen_instanceOfRoboSplashScreen_succeeds() { - Activity activity = Robolectric.buildActivity(Activity.class, null).setup().get(); - - assertThat(activity.getSplashScreen()).isInstanceOf(RoboSplashScreen.class); + try (ActivityController<Activity> controller = Robolectric.buildActivity(Activity.class)) { + Activity activity = controller.setup().get(); + assertThat(activity.getSplashScreen()).isInstanceOf(RoboSplashScreen.class); + } } @Test public void applicationWindow_hasCorrectWindowTokens() { - Activity activity = Robolectric.buildActivity(TestActivity.class).setup().get(); - View activityView = activity.getWindow().getDecorView(); - WindowManager.LayoutParams activityLp = - (WindowManager.LayoutParams) activityView.getLayoutParams(); - - View windowView = new View(activity); - WindowManager.LayoutParams windowViewLp = new WindowManager.LayoutParams(); - windowViewLp.type = WindowManager.LayoutParams.TYPE_APPLICATION; - ((WindowManager) activity.getSystemService(Context.WINDOW_SERVICE)) - .addView(windowView, windowViewLp); - ShadowLooper.idleMainLooper(); - - assertThat(activityLp.token).isNotNull(); - assertThat(windowViewLp.token).isEqualTo(activityLp.token); + try (ActivityController<TestActivity> controller = + Robolectric.buildActivity(TestActivity.class)) { + Activity activity = controller.setup().get(); + View activityView = activity.getWindow().getDecorView(); + WindowManager.LayoutParams activityLp = + (WindowManager.LayoutParams) activityView.getLayoutParams(); + + View windowView = new View(activity); + WindowManager.LayoutParams windowViewLp = new WindowManager.LayoutParams(); + windowViewLp.type = WindowManager.LayoutParams.TYPE_APPLICATION; + ((WindowManager) activity.getSystemService(Context.WINDOW_SERVICE)) + .addView(windowView, windowViewLp); + ShadowLooper.idleMainLooper(); + + assertThat(activityLp.token).isNotNull(); + assertThat(windowViewLp.token).isEqualTo(activityLp.token); + } } @Test public void subWindow_hasCorrectWindowTokens() { - Activity activity = Robolectric.buildActivity(TestActivity.class).setup().get(); - View activityView = activity.getWindow().getDecorView(); - WindowManager.LayoutParams activityLp = - (WindowManager.LayoutParams) activityView.getLayoutParams(); - - View windowView = new View(activity); - WindowManager.LayoutParams windowViewLp = new WindowManager.LayoutParams(); - windowViewLp.type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL; - ((WindowManager) activity.getSystemService(Context.WINDOW_SERVICE)) - .addView(windowView, windowViewLp); - ShadowLooper.idleMainLooper(); - - assertThat(activityLp.token).isNotNull(); - assertThat(windowViewLp.token).isEqualTo(activityView.getWindowToken()); - assertThat(windowView.getApplicationWindowToken()).isEqualTo(activityView.getWindowToken()); + try (ActivityController<TestActivity> controller = + Robolectric.buildActivity(TestActivity.class)) { + Activity activity = controller.setup().get(); + View activityView = activity.getWindow().getDecorView(); + WindowManager.LayoutParams activityLp = + (WindowManager.LayoutParams) activityView.getLayoutParams(); + + View windowView = new View(activity); + WindowManager.LayoutParams windowViewLp = new WindowManager.LayoutParams(); + windowViewLp.type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL; + ((WindowManager) activity.getSystemService(Context.WINDOW_SERVICE)) + .addView(windowView, windowViewLp); + ShadowLooper.idleMainLooper(); + + assertThat(activityLp.token).isNotNull(); + assertThat(windowViewLp.token).isEqualTo(activityView.getWindowToken()); + assertThat(windowView.getApplicationWindowToken()).isEqualTo(activityView.getWindowToken()); + } } @Test diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowLocaleManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowLocaleManagerTest.java index 6c889e25f..aa7cc4c47 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowLocaleManagerTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowLocaleManagerTest.java @@ -22,12 +22,14 @@ public final class ShadowLocaleManagerTest { private static final LocaleList DEFAULT_LOCALES = LocaleList.forLanguageTags("en-XC,ar-XB"); private Context context; - private ShadowLocaleManager localeManager; + private LocaleManager localeManager; + private ShadowLocaleManager shadowLocaleManager; @Before public void setUp() { context = ApplicationProvider.getApplicationContext(); - localeManager = Shadow.extract(context.getSystemService(LocaleManager.class)); + localeManager = context.getSystemService(LocaleManager.class); + shadowLocaleManager = Shadow.extract(localeManager); } @Test @@ -38,7 +40,7 @@ public final class ShadowLocaleManagerTest { localeManager.setApplicationLocales(DEFAULT_PACKAGE_NAME, DEFAULT_LOCALES); - localeManager.enforceInstallerCheck(false); + shadowLocaleManager.enforceInstallerCheck(false); assertThat(localeManager.getApplicationLocales(DEFAULT_PACKAGE_NAME)) .isEqualTo(DEFAULT_LOCALES); } @@ -46,8 +48,8 @@ public final class ShadowLocaleManagerTest { @Test public void getApplicationLocales_fetchAsInstaller_returnsLocales() { localeManager.setApplicationLocales(DEFAULT_PACKAGE_NAME, DEFAULT_LOCALES); - localeManager.setCallerAsInstallerForPackage(DEFAULT_PACKAGE_NAME); - localeManager.enforceInstallerCheck(true); + shadowLocaleManager.setCallerAsInstallerForPackage(DEFAULT_PACKAGE_NAME); + shadowLocaleManager.enforceInstallerCheck(true); assertThat(localeManager.getApplicationLocales(DEFAULT_PACKAGE_NAME)) .isEqualTo(DEFAULT_LOCALES); @@ -56,9 +58,25 @@ public final class ShadowLocaleManagerTest { @Test public void getApplicationLocales_fetchAsInstaller_throwsSecurityExceptionIfIncorrectInstaller() { localeManager.setApplicationLocales(DEFAULT_PACKAGE_NAME, DEFAULT_LOCALES); - localeManager.enforceInstallerCheck(true); + shadowLocaleManager.enforceInstallerCheck(true); assertThrows( SecurityException.class, () -> localeManager.getApplicationLocales(DEFAULT_PACKAGE_NAME)); } + + @Test + @Config(qualifiers = "en") + public void getSystemLocales_en() { + LocaleList localeList = localeManager.getSystemLocales(); + assertThat(localeList.size()).isEqualTo(1); + assertThat(localeList.get(0).getLanguage()).isEqualTo("en"); + } + + @Test + @Config(qualifiers = "zh") + public void getSystemLocales_zh() { + LocaleList localeList = localeManager.getSystemLocales(); + assertThat(localeList.size()).isEqualTo(1); + assertThat(localeList.get(0).getLanguage()).isEqualTo("zh"); + } } diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowResourcesTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowResourcesTest.java index 51e7cdfea..da3440139 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowResourcesTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowResourcesTest.java @@ -2,6 +2,8 @@ package org.robolectric.shadows; import static android.os.Build.VERSION_CODES.N_MR1; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assume.assumeFalse; +import static org.junit.Assume.assumeTrue; import static org.robolectric.Shadows.shadowOf; import static org.robolectric.shadows.ShadowAssetManager.useLegacy; @@ -82,13 +84,18 @@ public class ShadowResourcesTest { } @Test - public void openRawResourceFd_returnsNull_todo_FIX() throws Exception { + public void openRawResourceFd_shouldReturnsNullForLegacyResource() throws Exception { + assumeTrue(useLegacy()); try (AssetFileDescriptor afd = resources.openRawResourceFd(R.raw.raw_resource)) { - if (useLegacy()) { assertThat(afd).isNull(); - } else { + } + } + + @Test + public void openRawResourceFd_shouldReturnsValidFdForUnCompressFile() throws Exception { + assumeFalse(useLegacy()); + try (AssetFileDescriptor afd = resources.openRawResourceFd(R.raw.raw_resource)) { assertThat(afd).isNotNull(); - } } } diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowStorageStatsManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowStorageStatsManagerTest.java index b3d613909..376529d87 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowStorageStatsManagerTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowStorageStatsManagerTest.java @@ -134,7 +134,7 @@ public final class ShadowStorageStatsManagerTest { } @Test - public void queryWithoutSetup_shouldFail() { + public void queryPackageWithoutSetup_shouldFail() { assertThrows( PackageManager.NameNotFoundException.class, () -> @@ -144,7 +144,16 @@ public final class ShadowStorageStatsManagerTest { } @Test - public void queryWithCorrectArguments_shouldReturnSetupValue() throws Exception { + public void queryUserWithoutSetup_shouldFail() { + assertThrows( + PackageManager.NameNotFoundException.class, + () -> + shadowOf(storageStatsManager) + .queryStatsForUser(UUID.randomUUID(), Process.myUserHandle())); + } + + @Test + public void queryPackageWithCorrectArguments_shouldReturnSetupValue() throws Exception { // Arrange StorageStats expected = buildStorageStats(); UUID uuid = UUID.randomUUID(); @@ -161,7 +170,105 @@ public final class ShadowStorageStatsManagerTest { } @Test - public void queryWithWrongArguments_shouldFail() { + public void queryUserWithCorrectArguments_shouldReturnSetupValue() throws Exception { + // Arrange + StorageStats expected = buildStorageStats(); + UUID uuid = UUID.randomUUID(); + String packageName = "somePackageName"; + UserHandle userHandle = Process.myUserHandle(); + shadowOf(storageStatsManager).addStorageStats(uuid, packageName, userHandle, expected); + + // Act + StorageStats actual = shadowOf(storageStatsManager).queryStatsForUser(uuid, userHandle); + + // Assert + assertThat(actual).isEqualTo(expected); + } + + @Test + public void queryUser_shouldReturnAccumulatedStats() throws Exception { + // Arrange + StorageStats storageStats = buildStorageStats(); + UUID uuid1 = UUID.randomUUID(); + UUID uuid2 = UUID.randomUUID(); + String packageName1 = "somePackageName1"; + String packageName2 = "somePackageName2"; + String packageName3 = "somePackageName3"; + UserHandle userHandle = Process.myUserHandle(); + shadowOf(storageStatsManager).addStorageStats(uuid1, packageName1, userHandle, storageStats); + shadowOf(storageStatsManager).addStorageStats(uuid1, packageName2, userHandle, storageStats); + shadowOf(storageStatsManager).addStorageStats(uuid1, packageName3, userHandle, storageStats); + shadowOf(storageStatsManager).addStorageStats(uuid2, packageName1, userHandle, storageStats); + shadowOf(storageStatsManager).addStorageStats(uuid2, packageName2, userHandle, storageStats); + + // Act + StorageStats actual1 = shadowOf(storageStatsManager).queryStatsForUser(uuid1, userHandle); + StorageStats actual2 = shadowOf(storageStatsManager).queryStatsForUser(uuid2, userHandle); + + // Assert + assertThat(actual1.getAppBytes()).isEqualTo(9000L); // 3000 * 3 + assertThat(actual1.getDataBytes()).isEqualTo(6000L); // 2000 * 3 + assertThat(actual1.getCacheBytes()).isEqualTo(3000L); // 1000 * 3 + assertThat(actual2.getAppBytes()).isEqualTo(6000L); // 3000 * 2 + assertThat(actual2.getDataBytes()).isEqualTo(4000L); // 2000 * 2 + assertThat(actual2.getCacheBytes()).isEqualTo(2000L); // 1000 * 2 + } + + @Test + public void queryUser_packageStatsUpdated_shouldUpdateUserStats() throws Exception { + // Arrange + UUID uuid = UUID.randomUUID(); + String packageName1 = "somePackageName1"; + String packageName2 = "somePackageName2"; + UserHandle userHandle = Process.myUserHandle(); + shadowOf(storageStatsManager) + .addStorageStats(uuid, packageName1, userHandle, buildStorageStats()); + shadowOf(storageStatsManager) + .addStorageStats(uuid, packageName2, userHandle, buildStorageStats()); + shadowOf(storageStatsManager) + .addStorageStats( + uuid, + packageName2, + userHandle, + buildStorageStats( + /* codeSize= */ 2000L, /* dataSize= */ 1000L, /* cacheSize= */ 3000L)); + + // Act + StorageStats actual = shadowOf(storageStatsManager).queryStatsForUser(uuid, userHandle); + + // Assert + assertThat(actual.getAppBytes()).isEqualTo(5000L); // 3000 + 2000 + assertThat(actual.getDataBytes()).isEqualTo(3000L); // 2000 + 1000 + assertThat(actual.getCacheBytes()).isEqualTo(4000L); // 1000 + 3000 + } + + @Test + public void queryUser_packageStatsUpdated_singlePackage_shouldUpdateUserStats() throws Exception { + // Arrange + UUID uuid = UUID.randomUUID(); + String packageName = "somePackageName1"; + UserHandle userHandle = Process.myUserHandle(); + shadowOf(storageStatsManager) + .addStorageStats(uuid, packageName, userHandle, buildStorageStats()); + shadowOf(storageStatsManager) + .addStorageStats( + uuid, + packageName, + userHandle, + buildStorageStats( + /* codeSize= */ 2000L, /* dataSize= */ 1000L, /* cacheSize= */ 3000L)); + + // Act + StorageStats actual = shadowOf(storageStatsManager).queryStatsForUser(uuid, userHandle); + + // Assert + assertThat(actual.getAppBytes()).isEqualTo(2000L); + assertThat(actual.getDataBytes()).isEqualTo(1000L); + assertThat(actual.getCacheBytes()).isEqualTo(3000L); + } + + @Test + public void queryPackageWithWrongArguments_shouldFail() { // Arrange StorageStats expected = buildStorageStats(); UUID uuid = UUID.randomUUID(); @@ -199,7 +306,35 @@ public final class ShadowStorageStatsManagerTest { } @Test - public void queryAfterClearSetup_shouldFail() { + public void queryUserWithWrongArguments_shouldFail() { + // Arrange + StorageStats expected = buildStorageStats(); + UUID uuid = UUID.randomUUID(); + UUID differentUUID = UUID.randomUUID(); + UserHandle userHandle = UserHandle.getUserHandleForUid(0); + // getUserHandleForUid will divide uid by 100000. Pass in some arbitrary number > 100000 to be + // different from system uid 0. + UserHandle differentUserHandle = UserHandle.getUserHandleForUid(1200000); + + assertThat(uuid).isNotEqualTo(differentUUID); + assertThat(userHandle).isNotEqualTo(differentUserHandle); + + // Act + shadowOf(storageStatsManager) + .addStorageStats(uuid, /* packageName= */ "somePackageName", userHandle, expected); + + // Assert + assertThrows( + PackageManager.NameNotFoundException.class, + () -> shadowOf(storageStatsManager).queryStatsForUser(differentUUID, userHandle)); + + assertThrows( + PackageManager.NameNotFoundException.class, + () -> shadowOf(storageStatsManager).queryStatsForUser(uuid, differentUserHandle)); + } + + @Test + public void queryPackageAfterClearSetup_shouldFail() { // Arrange StorageStats expected = buildStorageStats(); UUID uuid = UUID.randomUUID(); @@ -216,10 +351,29 @@ public final class ShadowStorageStatsManagerTest { () -> shadowOf(storageStatsManager).queryStatsForPackage(uuid, packageName, userHandle)); } + @Test + public void queryUserAfterClearSetup_shouldFail() { + // Arrange + StorageStats expected = buildStorageStats(); + UUID uuid = UUID.randomUUID(); + String packageName = "somePackageName"; + UserHandle userHandle = Process.myUserHandle(); + shadowOf(storageStatsManager).addStorageStats(uuid, packageName, userHandle, expected); + + // Act + shadowOf(storageStatsManager).clearStorageStats(); + + // Assert + assertThrows( + PackageManager.NameNotFoundException.class, + () -> shadowOf(storageStatsManager).queryStatsForUser(uuid, userHandle)); + } + private static StorageStats buildStorageStats() { - long codeSize = 3000L; - long dataSize = 2000L; - long cacheSize = 1000L; + return buildStorageStats(/* codeSize= */ 3000L, /* dataSize= */ 2000L, /* cacheSize= */ 1000L); + } + + private static StorageStats buildStorageStats(long codeSize, long dataSize, long cacheSize) { Parcel parcel = Parcel.obtain(); parcel.writeLong(codeSize); parcel.writeLong(dataSize); diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowUIModeManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowUIModeManagerTest.java index 2dff1347c..0fb9bc605 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowUIModeManagerTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowUIModeManagerTest.java @@ -53,6 +53,13 @@ public class ShadowUIModeManagerTest { } @Test + public void testModeType() { + assertThat(uiModeManager.getCurrentModeType()).isEqualTo(Configuration.UI_MODE_TYPE_UNDEFINED); + shadowOf(uiModeManager).setCurrentModeType(Configuration.UI_MODE_TYPE_DESK); + assertThat(uiModeManager.getCurrentModeType()).isEqualTo(Configuration.UI_MODE_TYPE_DESK); + } + + @Test @Config(minSdk = R) public void testCarModePriority() { int priority = 9; diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowUsbDeviceConnectionTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowUsbDeviceConnectionTest.java index 09f568c89..52ba0285b 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowUsbDeviceConnectionTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowUsbDeviceConnectionTest.java @@ -3,6 +3,7 @@ package org.robolectric.shadows; import static android.os.Build.VERSION_CODES.LOLLIPOP; import static com.google.common.truth.Truth.assertThat; import static java.nio.charset.StandardCharsets.UTF_8; +import static org.junit.Assert.assertThrows; import static org.mockito.Mockito.when; import static org.robolectric.Shadows.shadowOf; @@ -17,6 +18,9 @@ import android.hardware.usb.UsbManager; import androidx.annotation.Nullable; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -47,7 +51,6 @@ public class ShadowUsbDeviceConnectionTest { when(usbDevice.getConfiguration(0)).thenReturn(usbConfiguration); when(usbConfiguration.getInterfaceCount()).thenReturn(1); when(usbConfiguration.getInterface(0)).thenReturn(usbInterface); - when(usbConfiguration.getInterface(0)).thenReturn(usbInterface); } @Test @@ -56,38 +59,47 @@ public class ShadowUsbDeviceConnectionTest { UsbDeviceConnection usbDeviceConnection = usbManager.openDevice(usbDevice); UsbInterface usbInterface = selectInterface(usbDevice); - assertThat(usbDeviceConnection.claimInterface(usbInterface, /*force=*/ false)).isTrue(); + assertThat(usbDeviceConnection.claimInterface(usbInterface, /* force= */ false)).isTrue(); assertThat(usbDeviceConnection.releaseInterface(usbInterface)).isTrue(); } @Test @Config(minSdk = LOLLIPOP) + public void setInterface() { + UsbDeviceConnection usbDeviceConnection = usbManager.openDevice(usbDevice); + UsbInterface usbInterface = selectInterface(usbDevice); + + assertThat(usbDeviceConnection.setInterface(usbInterface)).isTrue(); + } + + @Test + @Config(minSdk = LOLLIPOP) public void controlTransfer() { UsbDeviceConnection usbDeviceConnection = usbManager.openDevice(usbDevice); UsbInterface usbInterface = selectInterface(usbDevice); - usbDeviceConnection.claimInterface(usbInterface, /*force=*/ false); + usbDeviceConnection.claimInterface(usbInterface, /* force= */ false); int len = 10; assertThat( usbDeviceConnection.controlTransfer( - /*requestType=*/ 0, - /*request=*/ 0, - /*value=*/ 0, - /*index=*/ 0, - /*buffer=*/ new byte[len], - /*length=*/ len, - /*timeout=*/ 0)) + /* requestType= */ 0, + /* request= */ 0, + /* value= */ 0, + /* index= */ 0, + /* buffer= */ new byte[len], + /* length= */ len, + /* timeout= */ 0)) .isEqualTo(len); assertThat( usbDeviceConnection.controlTransfer( - /*requestType=*/ 0, - /*request=*/ 0, - /*value=*/ 0, - /*index=*/ 0, - /*buffer=*/ new byte[len], - /*offset=*/ 0, - /*length=*/ len, - /*timeout=*/ 0)) + /* requestType= */ 0, + /* request= */ 0, + /* value= */ 0, + /* index= */ 0, + /* buffer= */ new byte[len], + /* offset= */ 0, + /* length= */ len, + /* timeout= */ 0)) .isEqualTo(len); } @@ -97,25 +109,44 @@ public class ShadowUsbDeviceConnectionTest { UsbDeviceConnection usbDeviceConnection = usbManager.openDevice(usbDevice); UsbInterface usbInterface = selectInterface(usbDevice); UsbEndpoint usbEndpointOut = getEndpoint(usbInterface, UsbConstants.USB_DIR_OUT); - usbDeviceConnection.claimInterface(usbInterface, /*force=*/ false); + usbDeviceConnection.claimInterface(usbInterface, /* force= */ false); + InputStream outgoingData = shadowOf(usbDeviceConnection).getOutgoingDataStream(); byte[] msg = "Hello World".getBytes(UTF_8); - assertThat(usbDeviceConnection.bulkTransfer(usbEndpointOut, msg, msg.length, /*timeout=*/ 0)) + assertThat(usbDeviceConnection.bulkTransfer(usbEndpointOut, msg, msg.length, /* timeout= */ 0)) .isEqualTo(msg.length); - byte[] buffer = new byte[msg.length]; - shadowOf(usbDeviceConnection).readOutgoingData(buffer); - assertThat(buffer).isEqualTo(msg); + byte[] buffer = new byte[1024]; + int read = outgoingData.read(buffer); + assertThat(Arrays.copyOf(buffer, read)).isEqualTo(msg); msg = "Goodbye World".getBytes(UTF_8); assertThat( usbDeviceConnection.bulkTransfer( - usbEndpointOut, msg, /*offset=*/ 0, msg.length, /*timeout=*/ 0)) + usbEndpointOut, msg, /* offset= */ 0, msg.length, /* timeout= */ 0)) + .isEqualTo(msg.length); + + buffer = new byte[1024]; + read = outgoingData.read(buffer); + assertThat(Arrays.copyOf(buffer, read)).isEqualTo(msg); + } + + @Test + @Config(minSdk = LOLLIPOP) + public void releaseInterface_closesOutgoingDataStream() throws Exception { + UsbDeviceConnection usbDeviceConnection = usbManager.openDevice(usbDevice); + UsbInterface usbInterface = selectInterface(usbDevice); + UsbEndpoint usbEndpointOut = getEndpoint(usbInterface, UsbConstants.USB_DIR_OUT); + usbDeviceConnection.claimInterface(usbInterface, /* force= */ false); + InputStream outgoingData = shadowOf(usbDeviceConnection).getOutgoingDataStream(); + + byte[] msg = "Hello World".getBytes(UTF_8); + assertThat(usbDeviceConnection.bulkTransfer(usbEndpointOut, msg, msg.length, /* timeout= */ 0)) .isEqualTo(msg.length); + usbDeviceConnection.releaseInterface(usbInterface); - buffer = new byte[msg.length]; - shadowOf(usbDeviceConnection).readOutgoingData(buffer); - assertThat(buffer).isEqualTo(msg); + byte[] buffer = new byte[1024]; + assertThrows(IOException.class, () -> outgoingData.read(buffer)); } @Nullable diff --git a/scripts/build-resources.sh b/scripts/build-resources.sh index 8ba26204a..d85b7156c 100755 --- a/scripts/build-resources.sh +++ b/scripts/build-resources.sh @@ -2,20 +2,25 @@ set -x +# Exit the script if ANDROID_HOME is unset +set -u + rootDir=$(dirname $(dirname $0)) -projects=("robolectric") +projects=("robolectric" "nativeruntime") for project in "${projects[@]}" do androidProjDir="$rootDir/$project" echo $androidProjDir - aapts=( $ANDROID_HOME/build-tools/28.0.*/aapt ) + aapts=( $ANDROID_HOME/build-tools/*/aapt ) aapt=${aapts[-1]} inDir=$androidProjDir/src/test/resources outDir=$androidProjDir/src/test/resources javaSrc=$androidProjDir/src/test/java + mkdir -p $inDir/assets + mkdir -p $inDir/res mkdir -p $outDir mkdir -p $javaSrc diff --git a/shadowapi/src/test/java/org/robolectric/util/ReflectionHelpersTest.java b/shadowapi/src/test/java/org/robolectric/util/ReflectionHelpersTest.java index 814efd69d..56c489df1 100644 --- a/shadowapi/src/test/java/org/robolectric/util/ReflectionHelpersTest.java +++ b/shadowapi/src/test/java/org/robolectric/util/ReflectionHelpersTest.java @@ -124,8 +124,6 @@ public class ReflectionHelpersTest { @Test public void setFinalStaticFieldReflectively_withFieldName_setsStaticFields() { - int startingValue = ReflectionHelpers.getStaticField(ExampleWithFinalStatic.class, "FIELD"); - RuntimeException thrown = assertThrows( RuntimeException.class, 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 4a99909de..9e871a4c2 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowContextHubClient.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowContextHubClient.java @@ -12,11 +12,7 @@ import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; /** Shadow for {@link ContextHubClient}. */ -@Implements( - value = ContextHubClient.class, - minSdk = VERSION_CODES.P, - isInAndroidSdk = false, - looseSignatures = true) +@Implements(value = ContextHubClient.class, minSdk = VERSION_CODES.P, isInAndroidSdk = false) public class ShadowContextHubClient { private final List<NanoAppMessage> messages = new ArrayList<>(); 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 880c21ade..2244a2be7 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLocaleManager.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLocaleManager.java @@ -1,8 +1,11 @@ package org.robolectric.shadows; 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; @@ -30,6 +33,7 @@ public class ShadowLocaleManager { * @see #enforceInstallerCheck * @see #setCallerAsInstallerForPackage */ + @RequiresApi(api = VERSION_CODES.N) @Implementation protected LocaleList getApplicationLocales(String packageName) { if (enforceInstallerCheck) { @@ -51,6 +55,16 @@ public class ShadowLocaleManager { appLocales.put(packageName, locales); } + @RequiresApi(api = VERSION_CODES.N) + @Implementation + protected LocaleList getSystemLocales() { + Configuration configuration = Resources.getSystem().getConfiguration(); + if (configuration != null) { + return configuration.getLocales(); + } + return LocaleList.getEmptyLocaleList(); + } + /** * Sets the value of {@link #enforceInstallerCheck}. * 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 f82e91b25..9287cbb0f 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSpeechRecognizer.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSpeechRecognizer.java @@ -12,11 +12,11 @@ import android.os.Looper; import android.os.Message; import android.speech.IRecognitionService; import android.speech.RecognitionListener; -import android.speech.RecognitionSupport; -import android.speech.RecognitionSupportCallback; 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; import org.robolectric.annotation.Implementation; @@ -30,7 +30,7 @@ import org.robolectric.util.reflector.ForType; import org.robolectric.util.reflector.Static; /** Robolectric shadow for SpeechRecognizer. */ -@Implements(SpeechRecognizer.class) +@Implements(value = SpeechRecognizer.class, looseSignatures = true) public class ShadowSpeechRecognizer { @RealObject SpeechRecognizer realSpeechRecognizer; @@ -39,13 +39,13 @@ public class ShadowSpeechRecognizer { private RecognitionListener recognitionListener; private static boolean isOnDeviceRecognitionAvailable = true; - private RecognitionSupportCallback recognitionSupportCallback; + private /*RecognitionSupportCallback*/ Object recognitionSupportCallback; private Executor recognitionSupportExecutor; @Nullable private Intent latestModelDownloadIntent; /** * Returns the latest SpeechRecognizer. This method can only be called after {@link - * SpeechRecognizer#createSpeechRecognizer()} is called. + * SpeechRecognizer#createSpeechRecognizer(Context)} is called. */ public static SpeechRecognizer getLatestSpeechRecognizer() { return latestSpeechRecognizer; @@ -138,10 +138,17 @@ public class ShadowSpeechRecognizer { return isOnDeviceRecognitionAvailable; } + @RequiresApi(api = VERSION_CODES.TIRAMISU) @Implementation(minSdk = VERSION_CODES.TIRAMISU) protected void checkRecognitionSupport( - Intent recognizerIntent, Executor executor, RecognitionSupportCallback supportListener) { - recognitionSupportExecutor = executor; + @NonNull /*Intent*/ Object recognizerIntent, + @NonNull /*Executor*/ Object executor, + @NonNull /*RecognitionSupportCallback*/ Object supportListener) { + Preconditions.checkArgument(recognizerIntent instanceof Intent); + Preconditions.checkArgument(executor instanceof Executor); + Preconditions.checkArgument( + supportListener instanceof android.speech.RecognitionSupportCallback); + recognitionSupportExecutor = (Executor) executor; recognitionSupportCallback = supportListener; } @@ -155,14 +162,20 @@ public class ShadowSpeechRecognizer { } @RequiresApi(VERSION_CODES.TIRAMISU) - public void triggerSupportResult(RecognitionSupport recognitionSupport) { + public void triggerSupportResult(/*RecognitionSupport*/ Object recognitionSupport) { + Preconditions.checkArgument(recognitionSupport instanceof android.speech.RecognitionSupport); recognitionSupportExecutor.execute( - () -> recognitionSupportCallback.onSupportResult(recognitionSupport)); + () -> + ((android.speech.RecognitionSupportCallback) recognitionSupportCallback) + .onSupportResult((android.speech.RecognitionSupport) recognitionSupport)); } @RequiresApi(VERSION_CODES.TIRAMISU) public void triggerSupportError(int error) { - recognitionSupportExecutor.execute(() -> recognitionSupportCallback.onError(error)); + recognitionSupportExecutor.execute( + () -> + ((android.speech.RecognitionSupportCallback) recognitionSupportCallback) + .onError(error)); } @RequiresApi(VERSION_CODES.TIRAMISU) diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowStorageStatsManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowStorageStatsManager.java index 4720787b9..aee4d14f9 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowStorageStatsManager.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowStorageStatsManager.java @@ -6,6 +6,7 @@ import android.app.usage.StorageStats; import android.app.usage.StorageStatsManager; import android.content.pm.PackageManager; import android.os.Build; +import android.os.Parcel; import android.os.UserHandle; import android.os.storage.StorageManager; import com.google.auto.value.AutoValue; @@ -29,7 +30,10 @@ public class ShadowStorageStatsManager { private final Map<UUID, FreeAndTotalBytesPair> freeAndTotalBytesMap = createFreeAndTotalBytesMapWithSingleEntry( StorageManager.UUID_DEFAULT, DEFAULT_STORAGE_FREE_BYTES, DEFAULT_STORAGE_TOTAL_BYTES); - private final Map<StorageStatsKey, StorageStats> storageStatsMap = new ConcurrentHashMap<>(); + private final Map<StorageStatsKey, StorageStats> storageStatsMapForPackage = + new ConcurrentHashMap<>(); + private final Map<StorageStatsKey, StorageStats> storageStatsMapForUser = + new ConcurrentHashMap<>(); /** * Sets the {@code storageUuid} to return the specified {@code freeBytes} and {@code totalBytes} @@ -53,21 +57,50 @@ public class ShadowStorageStatsManager { } /** - * Sets the {@link StorageStats} to return when queried with matching {@code storageUuid}, {@code - * packageName} and {@code userHandle}. + * Sets the {@link StorageStats} for given {@code storageUuid}, {@code packageName} and {@code + * userHandle}. If {@code queryStatsForPackage} is called with matching {@code storageUuid}, + * {@code packageName} and {@code userHandle}, the {@code storageStatsToReturn} will be returned + * directly. If {@code queryStatsForUser} is called with matching {@code storageUuid} and {@code + * userHandle}, then an accumulated {@link StorageStats} will be returned. */ public void addStorageStats( UUID storageUuid, String packageName, UserHandle userHandle, StorageStats storageStatsToReturn) { - storageStatsMap.put( - StorageStatsKey.create(storageUuid, packageName, userHandle), storageStatsToReturn); + StorageStatsKey storageStatsKeyForPackage = + StorageStatsKey.create(storageUuid, packageName, userHandle); + StorageStats storageStatsForPackage = storageStatsMapForPackage.get(storageStatsKeyForPackage); + storageStatsMapForPackage.put(storageStatsKeyForPackage, storageStatsToReturn); + + StorageStatsKey storageStatsKeyForUser = + StorageStatsKey.create(storageUuid, /* packageName= */ "", userHandle); + StorageStats storageStatsForUser = storageStatsMapForUser.get(storageStatsKeyForUser); + if (storageStatsForUser == null) { + storageStatsMapForUser.put(storageStatsKeyForUser, storageStatsToReturn); + } else { + long moreAppBytes = storageStatsToReturn.getAppBytes(); + long moreDataBytes = storageStatsToReturn.getDataBytes(); + long moreCacheBytes = storageStatsToReturn.getCacheBytes(); + if (storageStatsForPackage != null) { + moreAppBytes -= storageStatsForPackage.getAppBytes(); + moreDataBytes -= storageStatsForPackage.getDataBytes(); + moreCacheBytes -= storageStatsForPackage.getCacheBytes(); + } + Parcel parcel = Parcel.obtain(); + parcel.writeLong(storageStatsForUser.getAppBytes() + moreAppBytes); + parcel.writeLong(storageStatsForUser.getDataBytes() + moreDataBytes); + parcel.writeLong(storageStatsForUser.getCacheBytes() + moreCacheBytes); + parcel.setDataPosition(0); + storageStatsMapForUser.put( + storageStatsKeyForUser, StorageStats.CREATOR.createFromParcel(parcel)); + } } /** Clears all {@link StorageStats} set in {@link ShadowStorageStatsManager#addStorageStats}. */ public void clearStorageStats() { - storageStatsMap.clear(); + storageStatsMapForPackage.clear(); + storageStatsMapForUser.clear(); } /** @@ -112,7 +145,7 @@ public class ShadowStorageStatsManager { protected StorageStats queryStatsForPackage(UUID storageUuid, String packageName, UserHandle user) throws PackageManager.NameNotFoundException, IOException { StorageStats storageStat = - storageStatsMap.get(StorageStatsKey.create(storageUuid, packageName, user)); + storageStatsMapForPackage.get(StorageStatsKey.create(storageUuid, packageName, user)); if (storageStat == null) { throw new PackageManager.NameNotFoundException( "queryStatsForPackage with non matching arguments. Did you forget to call" @@ -121,6 +154,26 @@ public class ShadowStorageStatsManager { return storageStat; } + /** + * Fake implementation of {@link StorageStatsManager#queryStatsForUser} that returns an + * accumulated {@link StorageStats} based on the setup values for the user. This fake + * implementation does not check for access permission. It only checks for arguments matching + * those set in {@link ShadowStorageStatsManager#addStorageStats}. + */ + @Implementation + protected StorageStats queryStatsForUser(UUID storageUuid, UserHandle user) + throws PackageManager.NameNotFoundException, IOException { + StorageStats storageStat = + storageStatsMapForUser.get( + StorageStatsKey.create(storageUuid, /* packageName= */ "", user)); + if (storageStat == null) { + throw new PackageManager.NameNotFoundException( + "queryStatsForUser with non matching arguments. Did you forget to call" + + " addStorageStats?"); + } + return storageStat; + } + private static Map<UUID, FreeAndTotalBytesPair> createFreeAndTotalBytesMapWithSingleEntry( UUID storageUuid, long freeBytes, long totalBytes) { Map<UUID, FreeAndTotalBytesPair> currMap = new ConcurrentHashMap<>(); 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 dc456f3f6..902669fbc 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTelephonyManager.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTelephonyManager.java @@ -77,7 +77,8 @@ public class ShadowTelephonyManager { @RealObject protected TelephonyManager realTelephonyManager; private final Map<PhoneStateListener, Integer> phoneStateRegistrations = new HashMap<>(); - private final List<TelephonyCallback> telephonyCallbackRegistrations = new ArrayList<>(); + 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<>(); @@ -87,7 +88,7 @@ public class ShadowTelephonyManager { new HashMap<>(); private PhoneStateListener lastListener; - private TelephonyCallback lastTelephonyCallback; + private /*TelephonyCallback*/ Object lastTelephonyCallback; private int lastEventFlags; private String deviceId; @@ -227,7 +228,10 @@ public class ShadowTelephonyManager { } @Implementation(minSdk = S) - public void registerTelephonyCallback(Executor executor, TelephonyCallback callback) { + public void registerTelephonyCallback( + /*Executor*/ Object executor, /*TelephonyCallback*/ Object callback) { + Preconditions.checkArgument(executor instanceof Executor); + Preconditions.checkArgument(callback instanceof TelephonyCallback); lastTelephonyCallback = callback; initTelephonyCallback(callback); telephonyCallbackRegistrations.add(callback); @@ -235,17 +239,20 @@ public class ShadowTelephonyManager { @Implementation(minSdk = TIRAMISU) protected void registerTelephonyCallback( - int includeLocationData, Executor executor, TelephonyCallback callback) { + /*int*/ Object includeLocationData, /*Executor*/ + Object executor, /*TelephonyCallback*/ + Object callback) { + Preconditions.checkArgument(includeLocationData instanceof Integer); registerTelephonyCallback(executor, callback); } @Implementation(minSdk = S) - public void unregisterTelephonyCallback(TelephonyCallback callback) { + public void unregisterTelephonyCallback(/*TelephonyCallback*/ Object callback) { telephonyCallbackRegistrations.remove(callback); } /** Returns the most recent callback passed to #registerTelephonyCallback(). */ - public TelephonyCallback getLastTelephonyCallback() { + public /*TelephonyCallback*/ Object getLastTelephonyCallback() { return lastTelephonyCallback; } @@ -715,7 +722,7 @@ public class ShadowTelephonyManager { } @CallSuper - protected void initTelephonyCallback(TelephonyCallback callback) { + protected void initTelephonyCallback(Object callback) { if (VERSION.SDK_INT < S) { return; } 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 44d6ecaef..04c574401 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowUIModeManager.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowUIModeManager.java @@ -61,6 +61,10 @@ public class ShadowUIModeManager { return currentModeType; } + public void setCurrentModeType(int modeType) { + this.currentModeType = modeType; + } + @Implementation(maxSdk = VERSION_CODES.Q) protected void enableCarMode(int flags) { enableCarMode(DEFAULT_PRIORITY, flags); 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 af3501a14..45fd5eb13 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowUsbDeviceConnection.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowUsbDeviceConnection.java @@ -2,13 +2,16 @@ 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; import android.hardware.usb.UsbDeviceConnection; import android.hardware.usb.UsbEndpoint; import android.hardware.usb.UsbInterface; import android.hardware.usb.UsbRequest; +import java.io.FilterInputStream; import java.io.IOException; +import java.io.InputStream; import java.io.PipedInputStream; import java.io.PipedOutputStream; import java.util.concurrent.TimeoutException; @@ -52,6 +55,15 @@ public class ShadowUsbDeviceConnection { return true; } + /** + * No-op on Robolectrict. The real implementation would return false on Robolectric and make it + * impossible to test callers that expect a successful result. Always returns {@code true}. + */ + @Implementation(minSdk = LOLLIPOP) + protected boolean setInterface(UsbInterface intf) { + return true; + } + @Implementation(minSdk = KITKAT) protected int controlTransfer( int requestType, int request, int value, int index, byte[] buffer, int length, int timeout) { @@ -106,9 +118,30 @@ public class ShadowUsbDeviceConnection { } } - /** Fills the buffer with data that was written by UsbDeviceConnection#bulkTransfer. */ + /** + * Fills the buffer with data that was written by UsbDeviceConnection#bulkTransfer. + * + * @deprecated prefer {@link #getOutgoingDataStream()}, which allows callers to know how much data + * has been read and when the {@link UsbDeviceConnection} closes. + */ + @Deprecated public void readOutgoingData(byte[] buffer) throws IOException { - outgoingDataPipedInputStream.read(buffer); + getOutgoingDataStream().read(buffer); + } + + /** + * Provides an {@link InputStream} that allows reading data written by + * UsbDeviceConnection#bulkTransfer. Closing this stream has no effect. It is effectively closed + * during {@link UsbDeviceConnection#releaseInterface(UsbInterface)}. + */ + public InputStream getOutgoingDataStream() { + return new FilterInputStream(outgoingDataPipedInputStream) { + @Override + public void close() throws IOException { + // Override close() to prevent clients from closing the piped stream and causing unexpected + // side-effects if further writes happen. + } + }; } /** Passes data that can then be read by an initialized UsbRequest#queue(ByteBuffer). */ 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 2c283d6f7..5c8de7314 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowUserManager.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowUserManager.java @@ -9,6 +9,7 @@ import static android.os.Build.VERSION_CODES.N_MR1; import static android.os.Build.VERSION_CODES.P; import static android.os.Build.VERSION_CODES.Q; import static android.os.Build.VERSION_CODES.R; +import static android.os.Build.VERSION_CODES.S; import static android.os.Build.VERSION_CODES.TIRAMISU; import static android.os.UserManager.USER_TYPE_FULL_GUEST; import static android.os.UserManager.USER_TYPE_FULL_RESTRICTED; @@ -75,6 +76,7 @@ public class ShadowUserManager { private static int maxSupportedUsers = DEFAULT_MAX_SUPPORTED_USERS; private static boolean isMultiUserSupported = false; + private static boolean isHeadlessSystemUserMode = false; @RealObject private UserManager realObject; private UserManagerState userManagerState; @@ -1133,6 +1135,16 @@ public class ShadowUserManager { return requestQuietModeEnabled(enableQuietMode, userHandle); } + @Implementation(minSdk = S) + protected static boolean isHeadlessSystemUserMode() { + return isHeadlessSystemUserMode; + } + + /** Updates headless system user mode. */ + public static void setHeadlessSystemUserMode(boolean isEnabled) { + ShadowUserManager.isHeadlessSystemUserMode = isEnabled; + } + @Implementation(minSdk = TIRAMISU) protected Bundle getUserRestrictions() { return getUserRestrictions(UserHandle.getUserHandleForUid(Process.myUid())); @@ -1148,6 +1160,7 @@ public class ShadowUserManager { public static void reset() { maxSupportedUsers = DEFAULT_MAX_SUPPORTED_USERS; isMultiUserSupported = false; + isHeadlessSystemUserMode = false; } @ForType(UserManager.class) diff --git a/shadows/playservices/build.gradle b/shadows/playservices/build.gradle index df8c75321..c3abbba05 100644 --- a/shadows/playservices/build.gradle +++ b/shadows/playservices/build.gradle @@ -16,7 +16,7 @@ dependencies { api project(":annotations") api "com.google.guava:guava:$guavaJREVersion" - compileOnly "com.android.support:support-fragment:28.0.0" + compileOnly "androidx.fragment:fragment:1.2.0" compileOnly "com.google.android.gms:play-services-base:8.4.0" compileOnly "com.google.android.gms:play-services-basement:8.4.0" @@ -30,7 +30,7 @@ dependencies { testImplementation "junit:junit:$junitVersion" testImplementation "com.google.truth:truth:$truthVersion" testImplementation "org.mockito:mockito-core:$mockitoVersion" - testRuntimeOnly "com.android.support:support-fragment:28.0.0" + testRuntimeOnly "androidx.fragment:fragment:1.2.0" testRuntimeOnly "com.google.android.gms:play-services-base:8.4.0" testRuntimeOnly "com.google.android.gms:play-services-basement:8.4.0" diff --git a/shadows/playservices/src/main/java/org/robolectric/shadows/gms/ShadowGooglePlayServicesUtil.java b/shadows/playservices/src/main/java/org/robolectric/shadows/gms/ShadowGooglePlayServicesUtil.java index e2f9dcdf9..deb738b27 100644 --- a/shadows/playservices/src/main/java/org/robolectric/shadows/gms/ShadowGooglePlayServicesUtil.java +++ b/shadows/playservices/src/main/java/org/robolectric/shadows/gms/ShadowGooglePlayServicesUtil.java @@ -7,7 +7,7 @@ import android.content.Context; import android.content.DialogInterface.OnCancelListener; import android.content.Intent; import android.content.res.Resources; -import android.support.v4.app.Fragment; +import androidx.fragment.app.Fragment; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.GooglePlayServicesUtil; import com.google.common.base.Preconditions; diff --git a/utils/src/main/java/org/robolectric/util/TempDirectory.java b/utils/src/main/java/org/robolectric/util/TempDirectory.java index b8527a7a1..3ce362065 100644 --- a/utils/src/main/java/org/robolectric/util/TempDirectory.java +++ b/utils/src/main/java/org/robolectric/util/TempDirectory.java @@ -52,6 +52,10 @@ public class TempDirectory { } } + public Path getBasePath() { + return basePath; + } + static void clearAllDirectories() { ExecutorService deletionExecutorService = Executors.newFixedThreadPool(DELETE_THREAD_POOL_SIZE); synchronized (tempDirectoriesToDelete) { |