diff options
164 files changed, 1902 insertions, 3712 deletions
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 172759662..7ae48559e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -91,6 +91,12 @@ jobs: api-level: [ 29, 34 ] steps: + - name: Free disk space + uses: jlumbroso/free-disk-space@v1.3.1 + with: + tool-cache: true + android: false + - uses: actions/checkout@v4 - name: Set up JDK 17 @@ -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.12.1" +testImplementation "org.robolectric:robolectric:4.12.2" ``` ## Building And Contributing diff --git a/gradle.properties b/gradle.properties index f7df00a5a..378474b3d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -11,3 +11,6 @@ kotlin.stdlib.default.dependency=false # Enable Gradle's Build Cache # https://docs.gradle.org/current/userguide/performance.html#enable_the_build_cache org.gradle.caching=true + +# Give Kotlin's daemon 2g of memory +kotlin.daemon.jvmargs=-Xmx2g diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d4a7e931f..a56eeb46d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,8 +1,8 @@ [versions] -robolectric-nativeruntime-dist-compat = "1.0.9" +robolectric-nativeruntime-dist-compat = "1.0.10" # https://developer.android.com/studio/releases -android-gradle = "8.4.0" +android-gradle = "8.4.1" # https://github.com/google/conscrypt/tags conscrypt = "2.5.2" @@ -27,10 +27,10 @@ error-prone-javac = "9+181-r4173-1" error-prone-gradle = "3.1.0" # https://kotlinlang.org/docs/releases.html#release-details -kotlin = "1.9.24" +kotlin = "2.0.0" # https://github.com/Kotlin/kotlinx.coroutines/releases/ -kotlinx-coroutines = '1.8.0' +kotlinx-coroutines = '1.8.1' # https://github.com/diffplug/spotless/blob/main/plugin-gradle/CHANGES.md spotless-gradle = "6.25.0" @@ -57,10 +57,10 @@ compile-testing = "0.21.0" guava-jre = "31.1-jre" # https://github.com/google/gson/releases -gson = "2.10.1" +gson = "2.11.0" # https://github.com/google/truth/releases -truth = "1.4.0" +truth = "1.4.2" # https://github.com/unicode-org/icu/releases icu4j = "75.1" @@ -80,7 +80,7 @@ jetbrains-annotations = "24.1.0" junit4 = "4.13.2" # https://github.com/google/libphonenumber/releases -libphonenumber = "8.13.36" +libphonenumber = "8.13.37" # https://github.com/mockito/mockito/releases mockito = "4.11.0" @@ -89,7 +89,7 @@ mockito = "4.11.0" mockk = "1.13.7" # https://github.com/takahirom/roborazzi/releases -roborazzi = "1.15.0" +roborazzi = "1.18.0" # https://square.github.io/okhttp/changelogs/changelog/ okhttp = "4.12.0" @@ -100,12 +100,12 @@ powermock = "2.0.9" sqlite4java = "1.0.392" # https://developer.android.com/jetpack/androidx/versions -androidx-annotation = "1.7.1" +androidx-annotation = "1.8.0" androidx-appcompat = "1.6.1" androidx-biometric = "1.1.0" androidx-constraintlayout = "2.1.4" androidx-core = "1.13.1" -androidx-fragment = "1.7.0" +androidx-fragment = "1.7.1" androidx-multidex = "2.0.1" androidx-window = "1.2.0" androidx-room = "2.6.1" @@ -201,7 +201,6 @@ sqlite4java-linux-i386 = { module = "com.almworks.sqlite4java:libsqlite4java-lin sqlite4java-win32-x86 = { module = "com.almworks.sqlite4java:sqlite4java-win32-x86", version.ref = "sqlite4java" } truth = { module = "com.google.truth:truth", version.ref = "truth" } -truth-java8-extension = { module = "com.google.truth.extensions:truth-java8-extension", version.ref = "truth" } mockito = { module = "org.mockito:mockito-core", version.ref = "mockito" } mockito-inline = { module = "org.mockito:mockito-inline", version.ref = "mockito" } diff --git a/integration_tests/ctesque/src/sharedTest/java/android/app/ActivityTest.java b/integration_tests/ctesque/src/sharedTest/java/android/app/ActivityTest.java index 71d404ca4..11ea831f0 100644 --- a/integration_tests/ctesque/src/sharedTest/java/android/app/ActivityTest.java +++ b/integration_tests/ctesque/src/sharedTest/java/android/app/ActivityTest.java @@ -1,6 +1,7 @@ package android.app; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; import android.graphics.drawable.ColorDrawable; import android.widget.Button; @@ -10,6 +11,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.annotation.internal.DoNotInstrument; +import org.robolectric.testapp.AbstractTestActivity; import org.robolectric.testapp.ActivityWithAnotherTheme; import org.robolectric.testapp.ActivityWithoutTheme; import org.robolectric.testapp.R; @@ -19,7 +21,7 @@ import org.robolectric.testapp.R; public class ActivityTest { @Before - public void setUp() throws Exception { + public void setUp() { ActivityWithAnotherTheme.setThemeBeforeContentView = null; } @@ -77,4 +79,16 @@ public class ActivityTest { }); } } + + @Test + public void launchActivity_abstractActivity_throwsRuntimeException() { + assertThrows( + RuntimeException.class, + () -> { + try (ActivityScenario<AbstractTestActivity> scenario = + ActivityScenario.launch(AbstractTestActivity.class)) { + assertThat(scenario).isNull(); + } + }); + } } diff --git a/integration_tests/kotlin/build.gradle b/integration_tests/kotlin/build.gradle index 46539a9f3..4f6aad259 100644 --- a/integration_tests/kotlin/build.gradle +++ b/integration_tests/kotlin/build.gradle @@ -23,6 +23,7 @@ dependencies { testCompileOnly AndroidSdk.MAX_SDK.coordinates testRuntimeOnly AndroidSdk.MAX_SDK.coordinates testImplementation libs.kotlin.stdlib + testImplementation libs.kotlinx.coroutines.android testImplementation libs.junit4 testImplementation libs.truth testImplementation "androidx.test:core:$axtCoreVersion@aar" diff --git a/integration_tests/kotlin/src/test/kotlin/org/robolectric/integrationtests/kotlin/flow/BluetoothProvisioner.kt b/integration_tests/kotlin/src/test/kotlin/org/robolectric/integrationtests/kotlin/flow/BluetoothProvisioner.kt new file mode 100644 index 000000000..c3aeee711 --- /dev/null +++ b/integration_tests/kotlin/src/test/kotlin/org/robolectric/integrationtests/kotlin/flow/BluetoothProvisioner.kt @@ -0,0 +1,79 @@ +package org.robolectric.integrationtests.kotlin.flow + +import android.bluetooth.BluetoothDevice +import android.bluetooth.BluetoothGatt +import android.bluetooth.BluetoothGattCallback +import android.bluetooth.BluetoothGattService +import android.bluetooth.BluetoothManager +import android.bluetooth.BluetoothProfile +import android.bluetooth.le.ScanCallback +import android.bluetooth.le.ScanResult +import android.content.Context +import kotlinx.coroutines.cancel +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.flow.firstOrNull +import kotlinx.coroutines.flow.flow + +/** A class that invokes Android Bluetooth LE APIs. */ +class BluetoothProvisioner(applicationContext: Context) { + + val context: Context + + init { + context = applicationContext + } + + fun startScan(): Flow<BluetoothDevice> = callbackFlow { + val scanCallback = + object : ScanCallback() { + override fun onScanResult(callbackType: Int, result: ScanResult?) { + if (result?.device != null) { + trySend(result.device) + } + } + + override fun onScanFailed(errorCode: Int) { + cancel("BLE Scan Failed", null) + } + } + val bluetoothManager = context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager + val scanner = bluetoothManager.adapter.bluetoothLeScanner + scanner.startScan(scanCallback) + awaitClose { scanner.stopScan(scanCallback) } + } + + fun connectToDevice(device: BluetoothDevice): Flow<BluetoothGatt> = callbackFlow { + val gattCallback = + object : BluetoothGattCallback() { + override fun onConnectionStateChange(gatt: BluetoothGatt?, status: Int, newState: Int) { + if (newState == BluetoothProfile.STATE_CONNECTED) { + gatt!!.discoverServices() + } else { + cancel("Connect Failed", null) + } + } + + override fun onServicesDiscovered(gatt: BluetoothGatt?, status: Int) { + if (status == BluetoothGatt.GATT_SUCCESS) { + trySend(gatt!!) + } else { + cancel("Service discovery failed", null) + } + } + } + + device.connectGatt(context, true, gattCallback) + awaitClose {} + } + + fun scanAndConnect() = + flow<BluetoothGattService> { + val device = startScan().firstOrNull() + if (device != null) { + val gatt = connectToDevice(device).firstOrNull() + emit(gatt!!.services[0]) + } + } +} diff --git a/integration_tests/kotlin/src/test/kotlin/org/robolectric/integrationtests/kotlin/flow/BluetoothProvisionerTest.kt b/integration_tests/kotlin/src/test/kotlin/org/robolectric/integrationtests/kotlin/flow/BluetoothProvisionerTest.kt new file mode 100644 index 000000000..e57b6badc --- /dev/null +++ b/integration_tests/kotlin/src/test/kotlin/org/robolectric/integrationtests/kotlin/flow/BluetoothProvisionerTest.kt @@ -0,0 +1,85 @@ +package org.robolectric.integrationtests.kotlin.flow + +import android.bluetooth.BluetoothGattService +import android.bluetooth.BluetoothManager +import android.bluetooth.le.ScanResult +import android.content.Context +import android.os.Build.VERSION_CODES.S +import com.google.common.truth.Truth.assertThat +import java.util.UUID +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.asCoroutineDispatcher +import kotlinx.coroutines.flow.firstOrNull +import kotlinx.coroutines.launch +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.RuntimeEnvironment +import org.robolectric.android.util.concurrent.PausedExecutorService +import org.robolectric.annotation.Config +import org.robolectric.shadow.api.Shadow +import org.robolectric.shadows.ShadowBluetoothDevice +import org.robolectric.shadows.ShadowBluetoothGatt +import org.robolectric.shadows.ShadowBluetoothLeScanner + +/** + * A test that uses a custom executor-backed coroutine dispatcher to control the execution of + * coroutines. + */ +@RunWith(RobolectricTestRunner::class) +@Config(sdk = [S]) +class BluetoothProvisionerTest { + + val context = RuntimeEnvironment.getApplication() + + val bluetoothManager = context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager + + fun newScanResult(): ScanResult { + val bluetoothDevice = bluetoothManager.adapter.getRemoteDevice(BLUETOOTH_MAC) + return ScanResult(bluetoothDevice, null, 0, 0) + } + + @Test + fun testBluetoothProvisioner() { + val executor = PausedExecutorService() + val dispatcher = executor.asCoroutineDispatcher() + val scope = CoroutineScope(dispatcher) + + scope.launch { + val gattService = + BluetoothProvisioner(RuntimeEnvironment.getApplication()).scanAndConnect().firstOrNull() + assertThat(gattService).isNotNull() + } + + executor.runAll() + + val scanner = bluetoothManager.adapter.bluetoothLeScanner + val shadowScanner = Shadow.extract<ShadowBluetoothLeScanner>(scanner) + + val scanResult = newScanResult() + val bluetoothDevice = scanResult.device + shadowScanner.scanCallbacks.first().onScanResult(0, newScanResult()) + + executor.runAll() + + val shadowDevice = Shadow.extract<ShadowBluetoothDevice>(bluetoothDevice) + + val gatt = shadowDevice.bluetoothGatts.first() + val shadowGatt = Shadow.extract<ShadowBluetoothGatt>(gatt) + + val service = + BluetoothGattService( + UUID.fromString("00000000-0000-0000-0000-0000000000A1"), + BluetoothGattService.SERVICE_TYPE_PRIMARY, + ) + + shadowGatt.addDiscoverableService(service) + shadowGatt.notifyConnection(BLUETOOTH_MAC) + + executor.runAll() + } + + private companion object { + private const val BLUETOOTH_MAC = "00:11:22:33:AA:BB" + } +} diff --git a/integration_tests/memoryleaks/build.gradle b/integration_tests/memoryleaks/build.gradle index fe9d3b003..8cefb3930 100644 --- a/integration_tests/memoryleaks/build.gradle +++ b/integration_tests/memoryleaks/build.gradle @@ -31,4 +31,5 @@ dependencies { testImplementation libs.junit4 testImplementation libs.guava.testlib testImplementation libs.androidx.fragment + testImplementation libs.truth } diff --git a/integration_tests/memoryleaks/src/test/java/org/robolectric/integrationtests/memoryleaks/BaseMemoryLeaksTest.java b/integration_tests/memoryleaks/src/test/java/org/robolectric/integrationtests/memoryleaks/BaseMemoryLeaksTest.java index 2361cbc1c..8d4e6b9e0 100644 --- a/integration_tests/memoryleaks/src/test/java/org/robolectric/integrationtests/memoryleaks/BaseMemoryLeaksTest.java +++ b/integration_tests/memoryleaks/src/test/java/org/robolectric/integrationtests/memoryleaks/BaseMemoryLeaksTest.java @@ -1,6 +1,7 @@ package org.robolectric.integrationtests.memoryleaks; import static android.os.Build.VERSION_CODES.N; +import static com.google.common.truth.Truth.assertThat; import static org.robolectric.Shadows.shadowOf; import android.app.Activity; @@ -16,8 +17,10 @@ import com.google.common.testing.GcFinalization; import java.lang.ref.WeakReference; import java.util.concurrent.Callable; import java.util.concurrent.atomic.AtomicLong; +import org.junit.FixMethodOrder; import org.junit.Test; import org.junit.runner.RunWith; +import org.junit.runners.MethodSorters; import org.robolectric.Robolectric; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; @@ -35,11 +38,33 @@ import org.robolectric.util.ReflectionHelpers; */ @RunWith(RobolectricTestRunner.class) @Config(sdk = Config.ALL_SDKS) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) public abstract class BaseMemoryLeaksTest { private static WeakReference<Activity> awr = null; @Test + // Do not shard these two tests, they must run on the same machine sequentially. + // These tests are prefixed with aaa_ to ensure they run first. Some leaks are caused by + // static setup that occurs once at the start of a test class, so these must be the first tests + // that run. + public void aaa_activityCanBeGcdBetweenTest_1() { + assertThat(awr).isNull(); + ActivityController<Activity> ac = Robolectric.buildActivity(Activity.class).setup(); + awr = new WeakReference<>(ac.get()); + } + + @Test + // Do not shard these two tests, they must run on the same machine sequentially. + // These tests are prefixed with aaa_ to ensure they run first. Some leaks are caused by + // static setup that occurs once at the start of a test class, so these must be the first tests + // that run. + public void aaa_activityCanBeGcdBetweenTest_2() { + assertThat(awr).isNotNull(); + assertNotLeaking(awr::get); + } + + @Test public void activityCanBeGcdAfterDestroyed() { assertNotLeaking( () -> { @@ -114,28 +139,6 @@ public abstract class BaseMemoryLeaksTest { } @Test - // Do not shard these two tests, they must run on the same machine sequentially. - public void activityCanBeGcdBetweenTest_1() { - if (awr == null) { - ActivityController<Activity> ac = Robolectric.buildActivity(Activity.class).setup(); - awr = new WeakReference<>(ac.get()); - } else { - assertNotLeaking(awr::get); - } - } - - @Test - // Do not shard these two tests, they must run on the same machine sequentially. - public void activityCanBeGcdBetweenTest_2() { - if (awr == null) { - ActivityController<Activity> ac = Robolectric.buildActivity(Activity.class).setup(); - awr = new WeakReference<>(ac.get()); - } else { - assertNotLeaking(awr::get); - } - } - - @Test public void typedArrayData() { assertNotLeaking( () -> { diff --git a/integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/HardwareAcceleratedActivityRenderTest.java b/integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/HardwareAcceleratedActivityRenderTest.java index c5389a2ea..91eec987a 100644 --- a/integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/HardwareAcceleratedActivityRenderTest.java +++ b/integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/HardwareAcceleratedActivityRenderTest.java @@ -14,7 +14,6 @@ import android.view.PixelCopy; import android.view.View; import android.view.ViewGroup.LayoutParams; import android.view.Window; -import android.view.WindowManager; import android.widget.FrameLayout; import java.util.Locale; import java.util.Objects; @@ -67,12 +66,6 @@ public class HardwareAcceleratedActivityRenderTest { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - // TODO(hoisie): manually setting these flags should not be required. Robolectric should - // set them automatically by default (they have been default since ICS). - getWindow() - .setFlags( - WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, - WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED); FrameLayout frameLayout = new FrameLayout(this); frameLayout.setLayoutParams( new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); diff --git a/processor/src/main/java/org/robolectric/annotation/processing/validator/SdkStore.java b/processor/src/main/java/org/robolectric/annotation/processing/validator/SdkStore.java index 06008a57b..f3e2adfef 100644 --- a/processor/src/main/java/org/robolectric/annotation/processing/validator/SdkStore.java +++ b/processor/src/main/java/org/robolectric/annotation/processing/validator/SdkStore.java @@ -55,7 +55,10 @@ public class SdkStore { private final Set<Sdk> sdks = new TreeSet<>(); private boolean loaded = false; + + /** Should only ever be needed for android platform development */ private final boolean loadFromClasspath; + private final String overrideSdkLocation; private final int overrideSdkInt; private final String sdksFile; @@ -155,7 +158,8 @@ public class SdkStore { /** * Returns a list of sdks to process, either the compilation's classpaths sdk in a list of size - * one, or the list of sdks in a sdkFile. + * one, or the list of sdks in a sdkFile. This should not be needed unless building in the android + * codebase. Otherwise, should prefer using the sdks.txt and the released jars. * * @param localSdk validate sdk found in compile time classpath, takes precedence over sdkFile * @param sdkFileName the sdkFile name, may be null, or empty @@ -169,13 +173,17 @@ public class SdkStore { Sdk sdk = null; if (overrideSdkLocation != null) { sdk = new Sdk(overrideSdkLocation, overrideSdkInt); + return sdk == null ? ImmutableList.of() : ImmutableList.of(sdk); } else { String target = compilationSdkTarget(); if (target != null) { sdk = new Sdk(target); + // We don't want to test released versions in Android source tree. + return sdk == null || sdk.sdkRelease.isReleased() + ? ImmutableList.of() + : ImmutableList.of(sdk); } } - return sdk == null ? ImmutableList.of() : ImmutableList.of(sdk); } if (sdkFileName == null || Files.notExists(Paths.get(sdkFileName))) { return ImmutableList.of(); diff --git a/resources/src/main/java/org/robolectric/res/Qualifiers.java b/resources/src/main/java/org/robolectric/res/Qualifiers.java index ac423005a..6faf4c4e3 100644 --- a/resources/src/main/java/org/robolectric/res/Qualifiers.java +++ b/resources/src/main/java/org/robolectric/res/Qualifiers.java @@ -99,24 +99,6 @@ public class Qualifiers { } /** - * If the Config already has a version qualifier, do nothing. Otherwise, add a version - * qualifier for the target api level (which comes from the manifest or Config.sdk()). - * - * @deprecated Figure something else out. - */ - @Deprecated - public static String addPlatformVersion(String qualifiers, int apiLevel) { - int versionQualifierApiLevel = Qualifiers.getPlatformVersion(qualifiers); - if (versionQualifierApiLevel == -1) { - if (qualifiers.length() > 0) { - qualifiers += "-"; - } - qualifiers += "v" + apiLevel; - } - return qualifiers; - } - - /** * If the Config already has a {@code sw} qualifier, do nothing. Otherwise, add a {@code sw} * qualifier for the given width. * diff --git a/resources/src/main/java/org/robolectric/res/android/CppAssetManager2.java b/resources/src/main/java/org/robolectric/res/android/CppAssetManager2.java index 803f82363..92280825e 100644 --- a/resources/src/main/java/org/robolectric/res/android/CppAssetManager2.java +++ b/resources/src/main/java/org/robolectric/res/android/CppAssetManager2.java @@ -885,7 +885,10 @@ public class CppAssetManager2 { out_value.set(device_value.copy()); // Convert the package ID to the runtime assigned package ID. - entry.get().dynamic_ref_table.lookupResourceValue(out_value); + int err = entry.get().dynamic_ref_table.lookupResourceValue(out_value); + if (err != NO_ERROR) { + return K_INVALID_COOKIE; + } out_selected_config.set(new ResTable_config(entry.get().config)); out_flags.set(entry.get().type_flags); diff --git a/resources/src/main/java/org/robolectric/res/android/DataType.java b/resources/src/main/java/org/robolectric/res/android/DataType.java index 30938926a..b238544b6 100644 --- a/resources/src/main/java/org/robolectric/res/android/DataType.java +++ b/resources/src/main/java/org/robolectric/res/android/DataType.java @@ -70,6 +70,8 @@ public enum DataType { } public static DataType fromCode(byte code) { - return Preconditions.checkNotNull(FROM_BYTE.get(code), "Unknown resource type: %s", code); + DataType type = FROM_BYTE.get(code); + Preconditions.checkArgument(type != null, "Unknown resource type: %s", code); + return type; } } diff --git a/resources/src/main/java/org/robolectric/res/android/DynamicRefTable.java b/resources/src/main/java/org/robolectric/res/android/DynamicRefTable.java index 0604116be..537a9f2f2 100644 --- a/resources/src/main/java/org/robolectric/res/android/DynamicRefTable.java +++ b/resources/src/main/java/org/robolectric/res/android/DynamicRefTable.java @@ -1,7 +1,9 @@ package org.robolectric.res.android; -// transliterated from 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/include/androidfw/ResourceTypes.h +import static org.robolectric.res.android.Errors.BAD_TYPE; import static org.robolectric.res.android.Errors.NO_ERROR; import static org.robolectric.res.android.Errors.UNKNOWN_ERROR; import static org.robolectric.res.android.ResTable.APP_PACKAGE_ID; @@ -145,7 +147,15 @@ public class DynamicRefTable int lookupResourceValue(Ref<Res_value> value) { byte resolvedType = DataType.REFERENCE.code(); Res_value inValue = value.get(); - switch (DataType.fromCode(inValue.dataType)) { + + DataType dataType; + try { + dataType = DataType.fromCode(inValue.dataType); + } catch (IllegalArgumentException e) { + return BAD_TYPE; + } + + switch (dataType) { case ATTRIBUTE: resolvedType = DataType.ATTRIBUTE.code(); // fallthrough diff --git a/resources/src/test/java/org/robolectric/res/QualifiersTest.java b/resources/src/test/java/org/robolectric/res/QualifiersTest.java index 1b59e683a..ac0bdbbff 100644 --- a/resources/src/test/java/org/robolectric/res/QualifiersTest.java +++ b/resources/src/test/java/org/robolectric/res/QualifiersTest.java @@ -38,26 +38,25 @@ public class QualifiersTest { ///////// deprecated stuff... - @Test public void addPlatformVersion() throws Exception { - assertThat(Qualifiers.addPlatformVersion("", 21)).isEqualTo("v21"); - assertThat(Qualifiers.addPlatformVersion("v23", 21)).isEqualTo("v23"); - assertThat(Qualifiers.addPlatformVersion("foo-v14", 21)).isEqualTo("foo-v14"); - } - - @Test public void addSmallestScreenWidth() throws Exception { + @Test + public void addSmallestScreenWidth() throws Exception { assertThat(Qualifiers.addSmallestScreenWidth("", 320)).isEqualTo("sw320dp"); assertThat(Qualifiers.addSmallestScreenWidth("sw160dp", 320)).isEqualTo("sw160dp"); assertThat(Qualifiers.addSmallestScreenWidth("sw480dp", 320)).isEqualTo("sw480dp"); - assertThat(Qualifiers.addSmallestScreenWidth("en-v23", 320)).isEqualTo("en-v23-sw320dp"); // todo: order is wrong here - assertThat(Qualifiers.addSmallestScreenWidth("en-sw160dp-v23", 320)).isEqualTo("en-sw160dp-v23"); - assertThat(Qualifiers.addSmallestScreenWidth("en-sw480dp-v23", 320)).isEqualTo("en-sw480dp-v23"); + assertThat(Qualifiers.addSmallestScreenWidth("en-v23", 320)) + .isEqualTo("en-v23-sw320dp"); // todo: order is wrong here + assertThat(Qualifiers.addSmallestScreenWidth("en-sw160dp-v23", 320)) + .isEqualTo("en-sw160dp-v23"); + assertThat(Qualifiers.addSmallestScreenWidth("en-sw480dp-v23", 320)) + .isEqualTo("en-sw480dp-v23"); } @Test public void addScreenWidth() throws Exception { assertThat(Qualifiers.addScreenWidth("", 320)).isEqualTo("w320dp"); assertThat(Qualifiers.addScreenWidth("w160dp", 320)).isEqualTo("w160dp"); assertThat(Qualifiers.addScreenWidth("w480dp", 320)).isEqualTo("w480dp"); - assertThat(Qualifiers.addScreenWidth("en-v23", 320)).isEqualTo("en-v23-w320dp"); // todo: order is wrong here + assertThat(Qualifiers.addScreenWidth("en-v23", 320)) + .isEqualTo("en-v23-w320dp"); // todo: order is wrong here assertThat(Qualifiers.addScreenWidth("en-w160dp-v23", 320)).isEqualTo("en-w160dp-v23"); assertThat(Qualifiers.addScreenWidth("en-w480dp-v23", 320)).isEqualTo("en-w480dp-v23"); } diff --git a/resources/src/test/java/org/robolectric/res/android/DataTypeTest.java b/resources/src/test/java/org/robolectric/res/android/DataTypeTest.java new file mode 100644 index 000000000..3dea9d35e --- /dev/null +++ b/resources/src/test/java/org/robolectric/res/android/DataTypeTest.java @@ -0,0 +1,23 @@ +package org.robolectric.res.android; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public final class DataTypeTest { + + @Test + public void fromCode_shouldThrowExceptionForInvalidCode() { + assertThrows(IllegalArgumentException.class, () -> DataType.fromCode(99)); + } + + @Test + public void fromCode_shouldReturnCorrectDataTypeForValidCode() { + assertThat(DataType.fromCode(0)).isEqualTo(DataType.NULL); + assertThat(DataType.fromCode(3)).isEqualTo(DataType.STRING); + } +} diff --git a/resources/src/test/java/org/robolectric/res/android/DynamicRefTableTest.java b/resources/src/test/java/org/robolectric/res/android/DynamicRefTableTest.java new file mode 100644 index 000000000..f03578626 --- /dev/null +++ b/resources/src/test/java/org/robolectric/res/android/DynamicRefTableTest.java @@ -0,0 +1,23 @@ +package org.robolectric.res.android; + +import static com.google.common.truth.Truth.assertThat; +import static org.robolectric.res.android.Errors.BAD_TYPE; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.robolectric.res.android.ResourceTypes.Res_value; + +@RunWith(JUnit4.class) +public final class DynamicRefTableTest { + + private static final Ref<Res_value> RES_VALUE_OF_BAD_TYPE = + new Ref<>(new Res_value(/* dataType= */ (byte) 99, /* data= */ 0)); + + @Test + public void lookupResourceValue_returnsBadTypeIfTypeOutOfEnumRange() { + DynamicRefTable pseudoRefTable = + new DynamicRefTable(/* packageId= */ (byte) 0, /* appAsLib= */ true); + assertThat(pseudoRefTable.lookupResourceValue(RES_VALUE_OF_BAD_TYPE)).isEqualTo(BAD_TYPE); + } +} diff --git a/robolectric/build.gradle b/robolectric/build.gradle index 859504e6d..47f774fb9 100644 --- a/robolectric/build.gradle +++ b/robolectric/build.gradle @@ -38,7 +38,6 @@ dependencies { testImplementation libs.androidx.annotation testImplementation libs.junit4 testImplementation libs.truth - testImplementation libs.truth.java8.extension testImplementation libs.mockito testImplementation libs.hamcrest.junit testImplementation "androidx.test:core:$axtCoreVersion@aar" diff --git a/robolectric/src/main/java/org/robolectric/Robolectric.java b/robolectric/src/main/java/org/robolectric/Robolectric.java index 7b694ec82..e985d959a 100644 --- a/robolectric/src/main/java/org/robolectric/Robolectric.java +++ b/robolectric/src/main/java/org/robolectric/Robolectric.java @@ -2,7 +2,6 @@ package org.robolectric; import static android.os.Build.VERSION_CODES.P; import static com.google.common.base.Preconditions.checkState; -import static org.robolectric.shadows.ShadowAssetManager.useLegacy; import android.annotation.IdRes; import android.annotation.RequiresApi; @@ -21,10 +20,10 @@ import android.os.Bundle; import android.os.Looper; import android.util.AttributeSet; import android.view.View; +import java.lang.reflect.Modifier; import javax.annotation.Nullable; import org.robolectric.android.AttributeSetBuilderImpl; import org.robolectric.android.AttributeSetBuilderImpl.ArscResourceResolver; -import org.robolectric.android.AttributeSetBuilderImpl.LegacyResourceResolver; import org.robolectric.android.controller.ActivityController; import org.robolectric.android.controller.BackupAgentController; import org.robolectric.android.controller.ContentProviderController; @@ -116,6 +115,9 @@ public class Robolectric { checkState( Thread.currentThread() == Looper.getMainLooper().getThread(), "buildActivity must be called on main Looper thread"); + if (Modifier.isAbstract(activityClass.getModifiers())) { + throw new RuntimeException("buildActivity must be called with non-abstract class"); + } return ActivityController.of( instantiateActivity(activityClass, intent), intent, activityOptions); } @@ -307,15 +309,10 @@ public class Robolectric { * Useful for testing {@link View} classes without the need for creating XML snippets. */ public static org.robolectric.android.AttributeSetBuilder buildAttributeSet() { - if (useLegacy()) { - return new AttributeSetBuilderImpl( - new LegacyResourceResolver( - RuntimeEnvironment.getApplication(), - RuntimeEnvironment.getCompileTimeResourceTable())) {}; - } else { + return new AttributeSetBuilderImpl( new ArscResourceResolver(RuntimeEnvironment.getApplication())) {}; - } + } /** diff --git a/robolectric/src/main/java/org/robolectric/android/AttributeSetBuilderImpl.java b/robolectric/src/main/java/org/robolectric/android/AttributeSetBuilderImpl.java index 00a8fcad6..5bbe385f8 100644 --- a/robolectric/src/main/java/org/robolectric/android/AttributeSetBuilderImpl.java +++ b/robolectric/src/main/java/org/robolectric/android/AttributeSetBuilderImpl.java @@ -6,7 +6,7 @@ import static org.robolectric.res.android.ResourceTypes.RES_XML_END_ELEMENT_TYPE import static org.robolectric.res.android.ResourceTypes.RES_XML_RESOURCE_MAP_TYPE; import static org.robolectric.res.android.ResourceTypes.RES_XML_START_ELEMENT_TYPE; import static org.robolectric.res.android.ResourceTypes.ResTable_map.ATTR_TYPE; -import static org.robolectric.shadows.ShadowLegacyAssetManager.ATTRIBUTE_TYPE_PRECIDENCE; +import static org.robolectric.shadows.ShadowAssetManager.ATTRIBUTE_TYPE_PRECIDENCE; import android.content.Context; import android.util.AttributeSet; @@ -17,7 +17,6 @@ import java.nio.Buffer; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.Map; import java.util.Map.Entry; @@ -27,9 +26,6 @@ import org.robolectric.res.AttrData; import org.robolectric.res.AttrData.Pair; import org.robolectric.res.AttributeResource; import org.robolectric.res.ResName; -import org.robolectric.res.ResType; -import org.robolectric.res.ResourceTable; -import org.robolectric.res.TypedResource; import org.robolectric.res.android.DataType; import org.robolectric.res.android.ResTable; import org.robolectric.res.android.ResTable.ResourceName; @@ -42,11 +38,9 @@ import org.robolectric.res.android.ResourceTypes.ResXMLTree_header; import org.robolectric.res.android.ResourceTypes.ResXMLTree_node; import org.robolectric.res.android.ResourceTypes.Res_value; import org.robolectric.shadow.api.Shadow; -import org.robolectric.shadows.Converter; import org.robolectric.shadows.Converter2; import org.robolectric.shadows.ShadowArscAssetManager; import org.robolectric.shadows.ShadowAssetManager; -import org.robolectric.shadows.ShadowLegacyAssetManager; import org.robolectric.util.ReflectionHelpers; import org.robolectric.util.ReflectionHelpers.ClassParameter; @@ -163,72 +157,6 @@ public class AttributeSetBuilderImpl implements AttributeSetBuilder { } } - public static class LegacyResourceResolver implements ResourceResolver { - - private final Context context; - private final ResourceTable resourceTable; - - public LegacyResourceResolver(Context context, ResourceTable compileTimeResourceTable) { - this.context = context; - resourceTable = compileTimeResourceTable; - } - - @Override - public String getPackageName() { - return context.getPackageName(); - } - - @Override - public String getResourceName(Integer attrId) { - return resourceTable.getResName(attrId).getFullyQualifiedName(); - } - - @Override - public Integer getIdentifier(String name, String type, String packageName) { - Integer resourceId = resourceTable.getResourceId(new ResName(packageName, type, name)); - if (resourceId == 0) { - resourceId = resourceTable.getResourceId( - new ResName(packageName, type, name.replace('.', '_'))); - } - return resourceId; - } - - @Override - public void parseValue(Integer attrId, ResName attrResName, AttributeResource attribute, - TypedValue outValue) { - ShadowLegacyAssetManager shadowAssetManager = Shadow - .extract(context.getResources().getAssets()); - TypedResource attrTypeData = shadowAssetManager.getAttrTypeData(attribute.resName); - if (attrTypeData != null) { - AttrData attrData = (AttrData) attrTypeData.getData(); - String format = attrData.getFormat(); - String[] types = format.split("\\|"); - Arrays.sort(types, ATTRIBUTE_TYPE_PRECIDENCE); - for (String type : types) { - if ("reference".equals(type)) continue; // already handled above - Converter2 converter = Converter2.getConverterFor(attrData, type); - - if (converter != null) { - if (converter.fillTypedValue(attribute.value, outValue, true)) { - break; - } - } - - } - // throw new IllegalArgumentException("wha? " + format); - } else { - /* In cases where the runtime framework doesn't know this attribute, e.g: viewportHeight (added in 21) on a - * KitKat runtine, then infer the attribute type from the value. - * - * TODO: When we are able to pass the SDK resources from the build environment then we can remove this - * and replace the NullResourceLoader with simple ResourceProvider that only parses attribute type information. - */ - ResType resType = ResType.inferFromValue(attribute.value); - Converter.getConverter(resType).fillTypedValue(attribute.value, outValue); - } - } - } - protected AttributeSetBuilderImpl(ResourceResolver resourceResolver) { this.resourceResolver = resourceResolver; } diff --git a/robolectric/src/main/java/org/robolectric/android/internal/AndroidTestEnvironment.java b/robolectric/src/main/java/org/robolectric/android/internal/AndroidTestEnvironment.java index 9b90d7603..ab29dea7f 100755 --- a/robolectric/src/main/java/org/robolectric/android/internal/AndroidTestEnvironment.java +++ b/robolectric/src/main/java/org/robolectric/android/internal/AndroidTestEnvironment.java @@ -34,7 +34,6 @@ import com.google.common.base.Supplier; import com.google.common.base.Suppliers; import java.lang.reflect.Method; import java.nio.file.FileSystem; -import java.nio.file.Files; import java.nio.file.Path; import java.security.Security; import java.security.cert.Certificate; @@ -78,12 +77,10 @@ import org.robolectric.res.ResourceTableFactory; import org.robolectric.res.RoutingResourceTable; import org.robolectric.shadow.api.Shadow; import org.robolectric.shadows.ClassNameResolver; -import org.robolectric.shadows.LegacyManifestParser; import org.robolectric.shadows.ShadowActivityThread; import org.robolectric.shadows.ShadowActivityThread._ActivityThread_; import org.robolectric.shadows.ShadowActivityThread._AppBindData_; import org.robolectric.shadows.ShadowApplication; -import org.robolectric.shadows.ShadowAssetManager; import org.robolectric.shadows.ShadowContextImpl._ContextImpl_; import org.robolectric.shadows.ShadowInstrumentation; import org.robolectric.shadows.ShadowInstrumentation._Instrumentation_; @@ -385,10 +382,6 @@ public class AndroidTestEnvironment implements TestEnvironment { appResources.updateConfiguration(androidConfiguration, Bootstrap.getDisplayMetrics()); - if (ShadowAssetManager.useLegacy()) { - populateAssetPaths(appResources.getAssets(), appManifest); - } - // Circumvent the 'No Compatibility callbacks set!' log. See #8509 if (apiLevel >= AndroidVersions.V.SDK_INT) { // Adds loggableChanges parameter. @@ -420,35 +413,17 @@ public class AndroidTestEnvironment implements TestEnvironment { private Package loadAppPackage_measured(Config config, AndroidManifest appManifest) { Package parsedPackage; - if (RuntimeEnvironment.useLegacyResources()) { - injectResourceStuffForLegacy(appManifest); - if (appManifest.getAndroidManifestFile() != null - && Files.exists(appManifest.getAndroidManifestFile())) { - parsedPackage = LegacyManifestParser.createPackage(appManifest); - } else { - parsedPackage = new Package("org.robolectric.default"); - parsedPackage.applicationInfo.targetSdkVersion = appManifest.getTargetSdkVersion(); - } - // Support overriding the package name specified in the Manifest. - if (!Config.DEFAULT_PACKAGE_NAME.equals(config.packageName())) { - parsedPackage.packageName = config.packageName(); - parsedPackage.applicationInfo.packageName = config.packageName(); - } else { - parsedPackage.packageName = appManifest.getPackageName(); - parsedPackage.applicationInfo.packageName = appManifest.getPackageName(); - } - } else { - RuntimeEnvironment.compileTimeSystemResourcesFile = compileSdk.getJarPath(); + RuntimeEnvironment.compileTimeSystemResourcesFile = compileSdk.getJarPath(); - Path packageFile = appManifest.getApkFile(); - if (packageFile != null) { - parsedPackage = ShadowPackageParser.callParsePackage(packageFile); - } else { - parsedPackage = new Package("org.robolectric.default"); - parsedPackage.applicationInfo.targetSdkVersion = appManifest.getTargetSdkVersion(); - } + Path packageFile = appManifest.getApkFile(); + if (packageFile != null) { + parsedPackage = ShadowPackageParser.callParsePackage(packageFile); + } else { + parsedPackage = new Package("org.robolectric.default"); + parsedPackage.applicationInfo.targetSdkVersion = appManifest.getTargetSdkVersion(); } + if (parsedPackage != null && parsedPackage.applicationInfo != null && RuntimeEnvironment.getApiLevel() >= P) { @@ -696,14 +671,8 @@ public class AndroidTestEnvironment implements TestEnvironment { // packageInfo.setVolumeUuid(tempDirectory.createIfNotExists(packageInfo.packageName + // "-dataDir").toAbsolutePath().toString()); - if (RuntimeEnvironment.useLegacyResources()) { - applicationInfo.sourceDir = createTempDir(applicationInfo.packageName + "-sourceDir"); - applicationInfo.publicSourceDir = - createTempDir(applicationInfo.packageName + "-publicSourceDir"); - } else { - applicationInfo.publicSourceDir = parsedPackage.codePath; - applicationInfo.sourceDir = parsedPackage.codePath; - } + applicationInfo.publicSourceDir = parsedPackage.codePath; + applicationInfo.sourceDir = parsedPackage.codePath; applicationInfo.dataDir = createTempDir(applicationInfo.packageName + "-dataDir"); diff --git a/robolectric/src/main/java/org/robolectric/android/internal/LocalUiController.java b/robolectric/src/main/java/org/robolectric/android/internal/LocalUiController.java index dd1bc5cca..7bfd8a2f0 100644 --- a/robolectric/src/main/java/org/robolectric/android/internal/LocalUiController.java +++ b/robolectric/src/main/java/org/robolectric/android/internal/LocalUiController.java @@ -9,7 +9,6 @@ import static org.robolectric.Shadows.shadowOf; import static org.robolectric.shadows.ShadowLooper.shadowMainLooper; import android.annotation.SuppressLint; -import android.os.Build; import android.os.Handler; import android.os.Looper; import android.os.SystemClock; @@ -141,18 +140,8 @@ public class LocalUiController implements UiController { @SuppressLint("InlinedApi") @VisibleForTesting - @SuppressWarnings("deprecation") static KeyCharacterMap getKeyCharacterMap() { - KeyCharacterMap keyCharacterMap = null; - - // KeyCharacterMap.VIRTUAL_KEYBOARD is present from API11. - // For earlier APIs we use KeyCharacterMap.BUILT_IN_KEYBOARD - if (Build.VERSION.SDK_INT < 11) { - keyCharacterMap = KeyCharacterMap.load(KeyCharacterMap.BUILT_IN_KEYBOARD); - } else { - keyCharacterMap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD); - } - return keyCharacterMap; + return KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD); } @Override diff --git a/robolectric/src/test/java/org/robolectric/QualifiersTest.java b/robolectric/src/test/java/org/robolectric/QualifiersTest.java index bf2959d24..1d5e4a77f 100644 --- a/robolectric/src/test/java/org/robolectric/QualifiersTest.java +++ b/robolectric/src/test/java/org/robolectric/QualifiersTest.java @@ -32,7 +32,7 @@ public class QualifiersTest { public void testDefaultQualifiers() throws Exception { assertThat(RuntimeEnvironment.getQualifiers()) .isEqualTo( - "en-rUS-ldltr-sw320dp-w320dp-h470dp-normal-notlong-notround-nowidecg-lowdr-port-notnight-mdpi-finger-keyssoft-nokeys-navhidden-nonav-v26"); + "en-rUS-ldltr-sw320dp-w320dp-h470dp-normal-notlong-notround-nowidecg-lowdr-port-notnight-mdpi-finger-keyssoft-nokeys-navhidden-nonav"); } @Test @@ -40,7 +40,7 @@ public class QualifiersTest { public void testDefaultQualifiers_withoutRegion() throws Exception { assertThat(RuntimeEnvironment.getQualifiers()) .isEqualTo( - "en-ldltr-sw320dp-w320dp-h470dp-normal-notlong-notround-nowidecg-lowdr-port-notnight-mdpi-finger-keyssoft-nokeys-navhidden-nonav-v26"); + "en-ldltr-sw320dp-w320dp-h470dp-normal-notlong-notround-nowidecg-lowdr-port-notnight-mdpi-finger-keyssoft-nokeys-navhidden-nonav"); } @Test diff --git a/robolectric/src/test/java/org/robolectric/RobolectricTestRunnerSelfTest.java b/robolectric/src/test/java/org/robolectric/RobolectricTestRunnerSelfTest.java index a60af551b..d44ccbd40 100644 --- a/robolectric/src/test/java/org/robolectric/RobolectricTestRunnerSelfTest.java +++ b/robolectric/src/test/java/org/robolectric/RobolectricTestRunnerSelfTest.java @@ -29,11 +29,6 @@ public class RobolectricTestRunnerSelfTest { assertWithMessage("onCreate called") .that(((MyTestApplication) ApplicationProvider.getApplicationContext()).onCreateWasCalled) .isTrue(); - if (RuntimeEnvironment.useLegacyResources()) { - assertWithMessage("Application resource loader") - .that(RuntimeEnvironment.getAppResourceTable()) - .isNotNull(); - } } @Test diff --git a/robolectric/src/test/java/org/robolectric/RuntimeEnvironmentTest.java b/robolectric/src/test/java/org/robolectric/RuntimeEnvironmentTest.java index bdbf97278..de5cf5ee7 100644 --- a/robolectric/src/test/java/org/robolectric/RuntimeEnvironmentTest.java +++ b/robolectric/src/test/java/org/robolectric/RuntimeEnvironmentTest.java @@ -144,4 +144,10 @@ public class RuntimeEnvironmentTest { RuntimeEnvironment.setQualifiers("en-rUS"); assertThat(DateUtils.formatElapsedTime(120)).isEqualTo("02:00"); } + + @Test + public void setQualifiers_withResultFromGetQualifiers() { + // Calling this should not cause an exception, e.g. API level mismatch. + RuntimeEnvironment.setQualifiers(RuntimeEnvironment.getQualifiers()); + } } diff --git a/robolectric/src/test/java/org/robolectric/android/BootstrapTest.java b/robolectric/src/test/java/org/robolectric/android/BootstrapTest.java index de38ffa9a..d40f6d177 100644 --- a/robolectric/src/test/java/org/robolectric/android/BootstrapTest.java +++ b/robolectric/src/test/java/org/robolectric/android/BootstrapTest.java @@ -147,12 +147,11 @@ public class BootstrapTest { "en-rUS-ldltr-sw320dp-w320dp-h470dp-normal-notlong-notround-" + optsForO + "port-notnight-mdpi" - + "-finger-keyssoft-nokeys-navhidden-nonav-v" - + Build.VERSION.RESOURCES_SDK_INT); + + "-finger-keyssoft-nokeys-navhidden-nonav"); assertThat(configuration.mcc).isEqualTo(0); assertThat(configuration.mnc).isEqualTo(0); - assertThat(configuration.locale).isEqualTo(new Locale("en", "US")); + assertThat(configuration.locale).isEqualTo(Locale.US); assertThat(configuration.screenLayout & SCREENLAYOUT_LAYOUTDIR_MASK).isEqualTo(SCREENLAYOUT_LAYOUTDIR_LTR); assertThat(configuration.smallestScreenWidthDp).isEqualTo(320); assertThat(configuration.screenWidthDp).isEqualTo(320); @@ -187,9 +186,11 @@ public class BootstrapTest { Bootstrap.applyQualifiers( "mcc310-mnc004-fr-rFR-ldrtl-sw400dp-w480dp-h456dp-" - + "xlarge-long-round" + altOptsForO + "-land-appliance-night-hdpi-notouch-" + + "xlarge-long-round" + + altOptsForO + + "-land-appliance-night-hdpi-notouch-" + "keyshidden-12key-navhidden-dpad", - Build.VERSION.RESOURCES_SDK_INT, + RuntimeEnvironment.getApiLevel(), configuration, displayMetrics); String outQualifiers = ConfigurationV25.resourceQualifierString(configuration, displayMetrics); @@ -201,12 +202,11 @@ public class BootstrapTest { + "-xlarge-long-round" + altOptsForO + "-land-appliance-night-hdpi-notouch-" - + "keyshidden-12key-navhidden-dpad-v" - + Build.VERSION.RESOURCES_SDK_INT); + + "keyshidden-12key-navhidden-dpad"); assertThat(configuration.mcc).isEqualTo(310); assertThat(configuration.mnc).isEqualTo(4); - assertThat(configuration.locale).isEqualTo(new Locale("fr", "FR")); + assertThat(configuration.locale).isEqualTo(Locale.FRANCE); assertThat(configuration.screenLayout & SCREENLAYOUT_LAYOUTDIR_MASK) .isEqualTo(SCREENLAYOUT_LAYOUTDIR_LTR); assertThat(configuration.smallestScreenWidthDp).isEqualTo(400); @@ -350,7 +350,7 @@ public class BootstrapTest { @Test @Config(minSdk = N) public void testUpdateDisplayResourcesWithDifferentLocale() { - Locale locale = new Locale("en", "IN"); + Locale locale = Locale.forLanguageTag("en-IN"); RuntimeEnvironment.setQualifiers("ar"); LocaleList originalDefault = LocaleList.getDefault(); try { diff --git a/robolectric/src/test/java/org/robolectric/android/DeviceConfigTest.java b/robolectric/src/test/java/org/robolectric/android/DeviceConfigTest.java index d71101765..ac6e9e4f5 100644 --- a/robolectric/src/test/java/org/robolectric/android/DeviceConfigTest.java +++ b/robolectric/src/test/java/org/robolectric/android/DeviceConfigTest.java @@ -101,15 +101,7 @@ public class DeviceConfigTest { String language = "he"; applyQualifiers(language); DeviceConfig.applyRules(configuration, displayMetrics, apiLevel); - // Locale's constructor has always converted three language codes to their earlier, obsoleted - // forms: he maps to iw, yi maps to ji, and id maps to in. Since Java SE 17, this is no longer - // the case. Each language maps to its new form; iw maps to he, ji maps to yi, and in maps to - // id. - // See - // https://stackoverflow.com/questions/8202406/locale-code-for-hebrew-reference-to-other-locale-codes/70882234#70882234, - // and https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Locale.html. - // To make sure this test can work with different JDK versions, using the following workaround. - Locale locale = new Locale(language); + Locale locale = Locale.forLanguageTag(language); assertThat(asQualifierString()) .isEqualTo( locale.getLanguage() @@ -230,6 +222,6 @@ public class DeviceConfigTest { } private String asQualifierString() { - return ConfigurationV25.resourceQualifierString(configuration, displayMetrics, false); + return ConfigurationV25.resourceQualifierString(configuration, displayMetrics); } } diff --git a/robolectric/src/test/java/org/robolectric/android/DrawableResourceLoaderTest.java b/robolectric/src/test/java/org/robolectric/android/DrawableResourceLoaderTest.java deleted file mode 100644 index bf5855e23..000000000 --- a/robolectric/src/test/java/org/robolectric/android/DrawableResourceLoaderTest.java +++ /dev/null @@ -1,99 +0,0 @@ -package org.robolectric.android; - -import static com.google.common.truth.Truth.assertThat; -import static com.google.common.truth.TruthJUnit.assume; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.robolectric.shadows.ShadowAssetManager.useLegacy; - -import android.animation.Animator; -import android.animation.AnimatorInflater; -import android.content.res.Resources; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.ColorDrawable; -import android.graphics.drawable.LayerDrawable; -import android.graphics.drawable.NinePatchDrawable; -import android.graphics.drawable.VectorDrawable; -import androidx.test.core.app.ApplicationProvider; -import androidx.test.ext.junit.runners.AndroidJUnit4; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.robolectric.R; -import org.robolectric.RuntimeEnvironment; -import org.robolectric.annotation.Config; - -@RunWith(AndroidJUnit4.class) -public class DrawableResourceLoaderTest { - private Resources resources; - - @Before - public void setup() throws Exception { - assume().that(useLegacy()).isTrue(); - resources = ApplicationProvider.getApplicationContext().getResources(); - } - - @Test - public void testGetDrawable_rainbow() throws Exception { - assertNotNull( - ApplicationProvider.getApplicationContext().getResources().getDrawable(R.drawable.rainbow)); - } - - @Test - public void testGetDrawableBundle_shouldWorkWithSystem() throws Exception { - assertNotNull(resources.getDrawable(android.R.drawable.ic_popup_sync)); - } - - @Test - public void testGetDrawable_red() throws Exception { - assertNotNull(Resources.getSystem().getDrawable(android.R.drawable.ic_menu_help)); - } - - @Test - public void testDrawableTypes() { - assertThat(resources.getDrawable(R.drawable.l7_white)).isInstanceOf(BitmapDrawable.class); - assertThat(resources.getDrawable(R.drawable.l0_red)).isInstanceOf(BitmapDrawable.class); - assertThat(resources.getDrawable(R.drawable.nine_patch_drawable)).isInstanceOf(NinePatchDrawable.class); - assertThat(resources.getDrawable(R.drawable.rainbow)).isInstanceOf(LayerDrawable.class); - } - - @Test - public void testVectorDrawableType() { - assertThat(resources.getDrawable(R.drawable.an_image_or_vector)).isInstanceOf(VectorDrawable.class); - } - - @Test - @Config(qualifiers = "land") - public void testLayerDrawable_xlarge() { - assertEquals( - 6, - ((LayerDrawable) - ApplicationProvider.getApplicationContext() - .getResources() - .getDrawable(R.drawable.rainbow)) - .getNumberOfLayers()); - } - - @Test - public void testLayerDrawable() { - assertEquals( - 8, - ((LayerDrawable) - ApplicationProvider.getApplicationContext() - .getResources() - .getDrawable(R.drawable.rainbow)) - .getNumberOfLayers()); - } - - @Test - public void shouldCreateAnimators() throws Exception { - Animator animator = - AnimatorInflater.loadAnimator(RuntimeEnvironment.getApplication(), R.animator.spinning); - assertThat(animator).isInstanceOf((Class<? extends Animator>) Animator.class); - } - - @Test - public void shouldCreateAnimsAndColors() throws Exception { - assertThat(resources.getDrawable(R.color.grey42)).isInstanceOf((Class<? extends android.graphics.drawable.Drawable>) ColorDrawable.class); - } -} diff --git a/robolectric/src/test/java/org/robolectric/android/ResourceLoaderTest.java b/robolectric/src/test/java/org/robolectric/android/ResourceLoaderTest.java deleted file mode 100644 index b2c1edf64..000000000 --- a/robolectric/src/test/java/org/robolectric/android/ResourceLoaderTest.java +++ /dev/null @@ -1,110 +0,0 @@ -package org.robolectric.android; - -import static android.os.Build.VERSION_CODES.O; -import static com.google.common.truth.Truth.assertThat; -import static com.google.common.truth.TruthJUnit.assume; -import static org.robolectric.shadows.ShadowAssetManager.useLegacy; - -import android.content.res.Configuration; -import android.content.res.Resources; -import android.os.Build; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.TextView; -import androidx.test.core.app.ApplicationProvider; -import androidx.test.ext.junit.runners.AndroidJUnit4; -import java.util.Locale; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.robolectric.R; -import org.robolectric.Robolectric; -import org.robolectric.RuntimeEnvironment; -import org.robolectric.annotation.Config; -import org.robolectric.res.ResName; -import org.robolectric.res.ResourceTable; - -@RunWith(AndroidJUnit4.class) -public class ResourceLoaderTest { - - private String optsForO; - - @Before - public void setUp() { - assume().that(useLegacy()).isTrue(); - - optsForO = RuntimeEnvironment.getApiLevel() >= O - ? "nowidecg-lowdr-" - : ""; - } - - @Test - @Config(qualifiers="w0dp") - public void checkDefaultBooleanValue() throws Exception { - assertThat( - ApplicationProvider.getApplicationContext() - .getResources() - .getBoolean(R.bool.different_resource_boolean)) - .isEqualTo(false); - } - - @Test - @Config(qualifiers="w820dp") - public void checkQualifiedBooleanValue() throws Exception { - assertThat( - ApplicationProvider.getApplicationContext() - .getResources() - .getBoolean(R.bool.different_resource_boolean)) - .isEqualTo(true); - } - - @Test - public void checkForPollution1() throws Exception { - checkForPollutionHelper(); - } - - @Test - public void checkForPollution2() throws Exception { - checkForPollutionHelper(); - } - - private void checkForPollutionHelper() { - assertThat(RuntimeEnvironment.getQualifiers()) - .isEqualTo( - "en-rUS-ldltr-sw320dp-w320dp-h470dp-normal-notlong-notround-" - + optsForO - + "port-notnight-mdpi-finger-keyssoft-nokeys-navhidden-nonav-v" - + Build.VERSION.RESOURCES_SDK_INT); - - View view = - LayoutInflater.from(ApplicationProvider.getApplicationContext()) - .inflate(R.layout.different_screen_sizes, null); - TextView textView = view.findViewById(android.R.id.text1); - assertThat(textView.getText().toString()).isEqualTo("default"); - RuntimeEnvironment.setQualifiers("fr-land"); // testing if this pollutes the other test - Configuration configuration = Resources.getSystem().getConfiguration(); - configuration.setLocale(new Locale("fr", "FR")); - configuration.orientation = Configuration.ORIENTATION_LANDSCAPE; - Resources.getSystem().updateConfiguration(configuration, null); - } - - @Test - public void shouldMakeInternalResourcesAvailable() throws Exception { - ResourceTable resourceProvider = RuntimeEnvironment.getSystemResourceTable(); - ResName internalResource = new ResName("android", "string", "badPin"); - Integer resId = resourceProvider.getResourceId(internalResource); - assertThat(resId).isNotNull(); - assertThat(resourceProvider.getResName(resId)).isEqualTo(internalResource); - - Class<?> internalRIdClass = - Robolectric.class - .getClassLoader() - .loadClass("com.android.internal.R$" + internalResource.type); - int internalResourceId; - internalResourceId = (Integer) internalRIdClass.getDeclaredField(internalResource.name).get(null); - assertThat(resId).isEqualTo(internalResourceId); - - assertThat(ApplicationProvider.getApplicationContext().getResources().getString(resId)) - .isEqualTo("The old PIN you typed isn't correct."); - } -} diff --git a/robolectric/src/test/java/org/robolectric/android/ResourceTableFactoryIntegrationTest.java b/robolectric/src/test/java/org/robolectric/android/ResourceTableFactoryIntegrationTest.java deleted file mode 100644 index f513b2f93..000000000 --- a/robolectric/src/test/java/org/robolectric/android/ResourceTableFactoryIntegrationTest.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.robolectric.android; - -import static com.google.common.truth.Truth.assertThat; -import static com.google.common.truth.TruthJUnit.assume; -import static org.robolectric.shadows.ShadowAssetManager.useLegacy; - -import androidx.test.ext.junit.runners.AndroidJUnit4; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.robolectric.RuntimeEnvironment; -import org.robolectric.res.ResName; - -@RunWith(AndroidJUnit4.class) -public class ResourceTableFactoryIntegrationTest { - @Test - public void shouldIncludeStyleableAttributesThatDoNotHaveACorrespondingEntryInAttrClass() throws Exception { - assume().that(useLegacy()).isTrue(); - // This covers a corner case in Framework resources where an attribute is mentioned in a styleable array, e.g: R.styleable.Toolbar_buttonGravity but there is no corresponding R.attr.buttonGravity - assertThat(RuntimeEnvironment.getSystemResourceTable() - .getResourceId(new ResName("android", "attr", "buttonGravity"))).isGreaterThan(0); - } -} diff --git a/robolectric/src/test/java/org/robolectric/android/XmlResourceParserImplTest.java b/robolectric/src/test/java/org/robolectric/android/XmlResourceParserImplTest.java index 0c8d977d0..bc7301ad5 100644 --- a/robolectric/src/test/java/org/robolectric/android/XmlResourceParserImplTest.java +++ b/robolectric/src/test/java/org/robolectric/android/XmlResourceParserImplTest.java @@ -2,7 +2,6 @@ package org.robolectric.android; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; -import static com.google.common.truth.TruthJUnit.assume; import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Arrays.asList; import static org.junit.Assert.assertTrue; @@ -29,7 +28,6 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.R; -import org.robolectric.RuntimeEnvironment; import org.w3c.dom.Document; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -275,15 +273,6 @@ public class XmlResourceParserImplTest { } @Test - public void testIsWhitespace() throws Exception { - assume().that(RuntimeEnvironment.useLegacyResources()).isTrue(); - - XmlResourceParserImpl parserImpl = (XmlResourceParserImpl) parser; - assertThat(parserImpl.isWhitespace("bar")).isFalse(); - assertThat(parserImpl.isWhitespace(" ")).isTrue(); - } - - @Test public void testGetPrefix() { try { parser.getPrefix(); diff --git a/robolectric/src/test/java/org/robolectric/android/internal/AndroidTestEnvironmentTest.java b/robolectric/src/test/java/org/robolectric/android/internal/AndroidTestEnvironmentTest.java index 54828ea5d..c2ef9f355 100644 --- a/robolectric/src/test/java/org/robolectric/android/internal/AndroidTestEnvironmentTest.java +++ b/robolectric/src/test/java/org/robolectric/android/internal/AndroidTestEnvironmentTest.java @@ -162,17 +162,6 @@ public class AndroidTestEnvironmentTest { } @Test - public void setUpApplicationState_setsVersionQualifierFromSdk() { - String givenQualifiers = ""; - ConfigurationImpl config = new ConfigurationImpl(); - config.put(Config.class, new Config.Builder().setQualifiers(givenQualifiers).build()); - config.put(LooperMode.Mode.class, LEGACY); - bootstrapWrapper.changeConfig(config); - bootstrapWrapper.callSetUpApplicationState(); - assertThat(RuntimeEnvironment.getQualifiers()).contains("v" + Build.VERSION.RESOURCES_SDK_INT); - } - - @Test public void setUpApplicationState_setsVersionQualifierFromSdkWithOtherQualifiers() { String givenQualifiers = "large-land"; ConfigurationImpl config = new ConfigurationImpl(); @@ -186,9 +175,11 @@ public class AndroidTestEnvironmentTest { ? "nowidecg-lowdr-" : ""; assertThat(RuntimeEnvironment.getQualifiers()) - .contains("large-notlong-notround-" + optsForO + "land-notnight-mdpi-finger-keyssoft" - + "-nokeys-navhidden-nonav-v" - + Build.VERSION.RESOURCES_SDK_INT); + .contains( + "large-notlong-notround-" + + optsForO + + "land-notnight-mdpi-finger-keyssoft" + + "-nokeys-navhidden-nonav"); } @Test diff --git a/robolectric/src/test/java/org/robolectric/res/StyleResourceLoaderTest.java b/robolectric/src/test/java/org/robolectric/res/StyleResourceLoaderTest.java deleted file mode 100644 index dc940d272..000000000 --- a/robolectric/src/test/java/org/robolectric/res/StyleResourceLoaderTest.java +++ /dev/null @@ -1,38 +0,0 @@ -package org.robolectric.res; - -import static android.os.Build.VERSION_CODES.LOLLIPOP; -import static com.google.common.truth.Truth.assertThat; -import static com.google.common.truth.TruthJUnit.assume; -import static org.robolectric.util.TestUtil.sdkResources; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; -import org.robolectric.RuntimeEnvironment; -import org.robolectric.res.android.ResTable_config; - -@RunWith(JUnit4.class) -public class StyleResourceLoaderTest { - private PackageResourceTable resourceTable; - - @Before - public void setUp() throws Exception { - assume().that(RuntimeEnvironment.useLegacyResources()).isTrue(); - ResourcePath resourcePath = sdkResources(LOLLIPOP); - resourceTable = new ResourceTableFactory().newResourceTable("android", resourcePath); - } - - @Test - public void testStyleDataIsLoadedCorrectly() throws Exception { - TypedResource typedResource = - resourceTable.getValue( - new ResName("android", "style", "Theme_Holo"), new ResTable_config()); - StyleData styleData = (StyleData) typedResource.getData(); - assertThat(styleData.getName()).isEqualTo("Theme_Holo"); - assertThat(styleData.getParent()).isEqualTo("Theme"); - assertThat(styleData.getPackageName()).isEqualTo("android"); - assertThat(styleData.getAttrValue(new ResName("android", "attr", "colorForeground")).value) - .isEqualTo("@android:color/bright_foreground_holo_dark"); - } -} diff --git a/robolectric/src/test/java/org/robolectric/shadows/CompatibilityTest.java b/robolectric/src/test/java/org/robolectric/shadows/CompatibilityTest.java index 85621c7eb..0bf3215c5 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/CompatibilityTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/CompatibilityTest.java @@ -10,11 +10,16 @@ import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; import org.robolectric.annotation.experimental.LazyApplication; import org.robolectric.annotation.experimental.LazyApplication.LazyLoad; +import org.robolectric.versioning.AndroidVersions.U; +import org.robolectric.versioning.AndroidVersions.V; /** Tests to make sure {@link android.compat.Compatibility} is instrumented correctly */ @RunWith(RobolectricTestRunner.class) @Config(minSdk = Build.VERSION_CODES.S) public class CompatibilityTest { + + private static final long ENFORCE_EDGE_TO_EDGE = 309578419L; + @Test public void isChangeEnabled() { assertThat(Compatibility.isChangeEnabled(100)).isTrue(); @@ -33,4 +38,10 @@ public class CompatibilityTest { // verify there are no CompatibilityChangeReporter spam logs assertThat(ShadowLog.getLogsForTag("CompatibilityChangeReporter")).isEmpty(); } + + @Test + public void edgeToEdgeEncorcement_minSdk() { + assertThat(ShadowCompatibility.isEnabled(ENFORCE_EDGE_TO_EDGE, U.SDK_INT)).isFalse(); + assertThat(ShadowCompatibility.isEnabled(ENFORCE_EDGE_TO_EDGE, V.SDK_INT)).isTrue(); + } } diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowActivityTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowActivityTest.java index f9b136da7..ca909f590 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowActivityTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowActivityTest.java @@ -1502,6 +1502,21 @@ public class ShadowActivityTest { } @Test + public void buildActivity_abstractActivityClass_throwsRuntimeException() { + Throwable throwable = + assertThrows( + RuntimeException.class, + () -> { + ActivityController<AbstractTestActivity> controller = + Robolectric.buildActivity(AbstractTestActivity.class, null); + // This line will not be executed. + assertThat(controller).isNull(); + }); + assertThat(throwable.getMessage()) + .isEqualTo("buildActivity must be called with non-abstract class"); + } + + @Test @Config(minSdk = Q) public void callOnGetDirectActions_succeeds() { try (ActivityController<TestActivity> controller = @@ -1777,6 +1792,9 @@ public class ShadowActivityTest { } } + /** Test Activity for abstract checking scenario. */ + abstract static class AbstractTestActivity extends Activity {} + /** Activity for testing */ public static class TestActivityWithAnotherTheme extends org.robolectric.shadows.testing.TestActivity {} diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowAssetManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowAssetManagerTest.java index 5de84d80d..dce72fd29 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowAssetManagerTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowAssetManagerTest.java @@ -1,12 +1,7 @@ package org.robolectric.shadows; import static com.google.common.truth.Truth.assertThat; -import static com.google.common.truth.TruthJUnit.assume; import static org.junit.Assert.fail; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; -import static org.robolectric.shadows.ShadowAssetManager.legacyShadowOf; -import static org.robolectric.shadows.ShadowAssetManager.useLegacy; import android.content.res.AssetManager; import android.content.res.Resources; @@ -25,9 +20,6 @@ import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; import org.robolectric.R; import org.robolectric.Robolectric; -import org.robolectric.annotation.Config; -import org.robolectric.shadow.api.Shadow; -import org.robolectric.shadows.ShadowResources.ShadowLegacyTheme; @RunWith(AndroidJUnit4.class) public class ShadowAssetManagerTest { @@ -46,7 +38,6 @@ public class ShadowAssetManagerTest { @Test public void openFd_shouldProvideFileDescriptorForDeflatedAsset() throws Exception { - assume().that(useLegacy()).isFalse(); expectedException.expect(FileNotFoundException.class); expectedException.expectMessage( "This file can not be opened as a file descriptor; it is probably compressed"); @@ -68,64 +59,10 @@ public class ShadowAssetManagerTest { @Test public void openNonAssetShouldOpenFileFromAndroidJar() throws IOException { String fileName = "res/raw/fallbackring.ogg"; - if (useLegacy()) { - // Not the real full path (it's in .m2/repository), but it only cares about the last folder and file name; - // retrieves the uncompressed, un-version-qualified file from raw-res/... - fileName = "jar:" + fileName; - } InputStream inputStream = assetManager.openNonAsset(0, fileName, 0); assertThat(countBytes(inputStream)).isEqualTo(14611); } - @Test - public void openNonAssetShouldThrowExceptionWhenFileDoesNotExist() throws IOException { - assume().that(useLegacy()).isTrue(); - - expectedException.expect(IOException.class); - expectedException.expectMessage( - "res/drawable/does_not_exist.png"); - - assetManager.openNonAsset(0, "res/drawable/does_not_exist.png", 0); - } - - @Test - public void unknownResourceIdsShouldReportPackagesSearched() throws IOException { - assume().that(useLegacy()).isTrue(); - - expectedException.expect(Resources.NotFoundException.class); - expectedException.expectMessage("Resource ID #0xffffffff"); - - resources.newTheme().applyStyle(-1, false); - assetManager.openNonAsset(0, "res/drawable/does_not_exist.png", 0); - } - - @Test - public void forSystemResources_unknownResourceIdsShouldReportPackagesSearched() - throws IOException { - assume().that(useLegacy()).isTrue(); - expectedException.expect(Resources.NotFoundException.class); - expectedException.expectMessage("Resource ID #0xffffffff"); - - Resources.getSystem().newTheme().applyStyle(-1, false); - assetManager.openNonAsset(0, "res/drawable/does_not_exist.png", 0); - } - - @Test - @Config(qualifiers = "mdpi") - public void openNonAssetShouldOpenCorrectAssetBasedOnQualifierMdpi() throws IOException { - assume().that(useLegacy()).isTrue(); - InputStream inputStream = assetManager.openNonAsset(0, "res/drawable/robolectric.png", 0); - assertThat(countBytes(inputStream)).isEqualTo(8141); - } - - @Test - @Config(qualifiers = "hdpi") - public void openNonAssetShouldOpenCorrectAssetBasedOnQualifierHdpi() throws IOException { - assume().that(useLegacy()).isTrue(); - InputStream inputStream = assetManager.openNonAsset(0, "res/drawable/robolectric.png", 0); - assertThat(countBytes(inputStream)).isEqualTo(23447); - } - // todo: port to ResourcesTest @Test public void multiFormatAttributes_integerDecimalValue() { @@ -174,41 +111,6 @@ public class ShadowAssetManagerTest { assertThat(outValue.type).isEqualTo(TypedValue.TYPE_INT_BOOLEAN); } - @Test - public void attrsToTypedArray_shouldAllowMockedAttributeSets() { - assume().that(useLegacy()).isTrue(); - AttributeSet mockAttributeSet = mock(AttributeSet.class); - when(mockAttributeSet.getAttributeCount()).thenReturn(1); - when(mockAttributeSet.getAttributeNameResource(0)).thenReturn(android.R.attr.windowBackground); - when(mockAttributeSet.getAttributeName(0)).thenReturn("android:windowBackground"); - when(mockAttributeSet.getAttributeValue(0)).thenReturn("value"); - - resources.obtainAttributes(mockAttributeSet, new int[]{android.R.attr.windowBackground}); - } - - @Test - public void whenStyleAttrResolutionFails_attrsToTypedArray_returnsNiceErrorMessage() { - assume().that(useLegacy()).isTrue(); - expectedException.expect(RuntimeException.class); - expectedException.expectMessage( - "no value for org.robolectric:attr/styleNotSpecifiedInAnyTheme in theme with applied" - + " styles: [Style org.robolectric:Theme.Robolectric (and parents)]"); - - Resources.Theme theme = resources.newTheme(); - theme.applyStyle(R.style.Theme_Robolectric, false); - - legacyShadowOf(assetManager) - .attrsToTypedArray( - resources, - Robolectric.buildAttributeSet() - .setStyleAttribute("?attr/styleNotSpecifiedInAnyTheme") - .build(), - new int[] {R.attr.string1}, - 0, - ((ShadowLegacyTheme) Shadow.extract(theme)).getNativePtr(), - 0); - } - /////////////////////////////// private static int countBytes(InputStream i) throws IOException { diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowCameraCharacteristicsTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowCameraCharacteristicsTest.java index 22ab0cab1..a3738cae3 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowCameraCharacteristicsTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowCameraCharacteristicsTest.java @@ -5,14 +5,11 @@ import static org.junit.Assert.fail; import static org.robolectric.Shadows.shadowOf; import android.hardware.camera2.CameraCharacteristics; -import android.os.Build.VERSION_CODES; import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; -import org.robolectric.annotation.Config; /** Tests for {@link ShadowCameraCharacteristics}. */ -@Config(minSdk = VERSION_CODES.LOLLIPOP) @RunWith(AndroidJUnit4.class) public class ShadowCameraCharacteristicsTest { diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowCameraDeviceImplTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowCameraDeviceImplTest.java index cbc79a46f..b9f9398ec 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowCameraDeviceImplTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowCameraDeviceImplTest.java @@ -38,7 +38,6 @@ import org.junit.runner.RunWith; import org.robolectric.annotation.Config; /** Tests for {@link ShadowCameraDeviceImpl}. */ -@Config(minSdk = VERSION_CODES.LOLLIPOP) @RunWith(AndroidJUnit4.class) public final class ShadowCameraDeviceImplTest { private static final String CAMERA_ID_0 = "cameraId0"; @@ -70,7 +69,7 @@ public final class ShadowCameraDeviceImplTest { } @Test - @Config(minSdk = VERSION_CODES.LOLLIPOP, maxSdk = VERSION_CODES.Q) + @Config(maxSdk = VERSION_CODES.Q) public void createCaptureRequest() throws CameraAccessException { builder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD); CaptureRequest request = builder.build(); diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowCameraManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowCameraManagerTest.java index 14c751d42..84c94e797 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowCameraManagerTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowCameraManagerTest.java @@ -24,7 +24,6 @@ import org.mockito.ArgumentCaptor; import org.robolectric.annotation.Config; /** Tests for {@link ShadowCameraManager}. */ -@Config(minSdk = VERSION_CODES.LOLLIPOP) @RunWith(AndroidJUnit4.class) public class ShadowCameraManagerTest { @@ -174,7 +173,6 @@ public class ShadowCameraManagerTest { } @Test - @Config(minSdk = VERSION_CODES.LOLLIPOP) public void openCamera() throws CameraAccessException { shadowOf(cameraManager).addCamera(CAMERA_ID_0, characteristics); @@ -185,7 +183,6 @@ public class ShadowCameraManagerTest { } @Test - @Config(minSdk = VERSION_CODES.LOLLIPOP) public void triggerDisconnect() throws CameraAccessException { shadowOf(cameraManager).addCamera(CAMERA_ID_0, characteristics); @@ -202,7 +199,6 @@ public class ShadowCameraManagerTest { } @Test - @Config(minSdk = VERSION_CODES.LOLLIPOP) public void triggerDisconnect_noCameraOpen() throws CameraAccessException { shadowOf(cameraManager).addCamera(CAMERA_ID_0, characteristics); shadowOf(cameraManager).triggerDisconnect(); @@ -264,7 +260,6 @@ public class ShadowCameraManagerTest { } @Test - @Config(minSdk = VERSION_CODES.LOLLIPOP) public void registerCallbackAvailable() throws CameraAccessException { CameraManager.AvailabilityCallback mockCallback = mock(CameraManager.AvailabilityCallback.class); @@ -276,7 +271,6 @@ public class ShadowCameraManagerTest { } @Test - @Config(minSdk = VERSION_CODES.LOLLIPOP) public void unregisterCallbackAvailable() throws CameraAccessException { CameraManager.AvailabilityCallback mockCallback = mock(CameraManager.AvailabilityCallback.class); @@ -292,7 +286,6 @@ public class ShadowCameraManagerTest { } @Test - @Config(minSdk = VERSION_CODES.LOLLIPOP) public void registerCallbackUnavailable() throws CameraAccessException { CameraManager.AvailabilityCallback mockCallback = mock(CameraManager.AvailabilityCallback.class); @@ -306,7 +299,6 @@ public class ShadowCameraManagerTest { } @Test - @Config(minSdk = VERSION_CODES.LOLLIPOP) public void unregisterCallbackUnavailable() throws CameraAccessException { CameraManager.AvailabilityCallback mockCallback = mock(CameraManager.AvailabilityCallback.class); @@ -321,7 +313,6 @@ public class ShadowCameraManagerTest { } @Test - @Config(minSdk = VERSION_CODES.LOLLIPOP) public void registerCallbackUnavailableInvalidCameraId() throws CameraAccessException { CameraManager.AvailabilityCallback mockCallback = mock(CameraManager.AvailabilityCallback.class); diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowCaptioningManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowCaptioningManagerTest.java index 470eeff75..bb9e408fd 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowCaptioningManagerTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowCaptioningManagerTest.java @@ -1,31 +1,81 @@ package org.robolectric.shadows; +import static android.os.Build.VERSION_CODES.KITKAT; +import static android.os.Build.VERSION_CODES.TIRAMISU; import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.robolectric.Shadows.shadowOf; import android.content.Context; +import android.os.Looper; +import android.provider.Settings; +import android.provider.Settings.Secure; import android.view.accessibility.CaptioningManager; +import android.view.accessibility.CaptioningManager.CaptionStyle; import android.view.accessibility.CaptioningManager.CaptioningChangeListener; +import androidx.annotation.NonNull; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import java.util.Locale; +import javax.annotation.Nullable; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; /** Tests for the ShadowCaptioningManager. */ @RunWith(AndroidJUnit4.class) -@Config(minSdk = 19) +@Config(minSdk = KITKAT) public final class ShadowCaptioningManagerTest { - @Mock private CaptioningChangeListener captioningChangeListener; + private TestCaptioningChangeListener captioningChangeListener = + new TestCaptioningChangeListener(); + + private static final int ENABLED = 1; + private static final int DISABLED = 0; private CaptioningManager captioningManager; + private Context context; + + public class TestCaptioningChangeListener extends CaptioningChangeListener { + public boolean isEnabled = false; + @Nullable public CaptionStyle captionStyle = null; + @Nullable public Locale locale = null; + public float fontScale = 1.0f; + public boolean systemAudioCaptioningEnabled = false; + public boolean systemAudioCaptioningUiEnabled = false; + + @Override + public void onEnabledChanged(boolean enabled) { + isEnabled = enabled; + } + + @Override + public void onUserStyleChanged(@NonNull CaptionStyle userStyle) { + captionStyle = userStyle; + } + + @Override + public void onLocaleChanged(@Nullable Locale locale) { + this.locale = locale; + } + + @Override + public void onFontScaleChanged(float fontScale) { + this.fontScale = fontScale; + } + + @Override + public void onSystemAudioCaptioningChanged(boolean enabled) { + this.systemAudioCaptioningEnabled = enabled; + } + + @Override + public void onSystemAudioCaptioningUiChanged(boolean enabled) { + this.systemAudioCaptioningUiEnabled = enabled; + } + } @Before public void setUp() { @@ -34,87 +84,106 @@ public final class ShadowCaptioningManagerTest { (CaptioningManager) ApplicationProvider.getApplicationContext() .getSystemService(Context.CAPTIONING_SERVICE); + context = RuntimeEnvironment.getApplication(); } @Test public void setEnabled_true() { - assertThat(captioningManager.isEnabled()).isFalse(); - - shadowOf(captioningManager).setEnabled(true); + Settings.Secure.putInt( + context.getContentResolver(), Secure.ACCESSIBILITY_CAPTIONING_ENABLED, ENABLED); assertThat(captioningManager.isEnabled()).isTrue(); } @Test public void setEnabled_false() { - shadowOf(captioningManager).setEnabled(false); + Settings.Secure.putInt( + context.getContentResolver(), Secure.ACCESSIBILITY_CAPTIONING_ENABLED, DISABLED); assertThat(captioningManager.isEnabled()).isFalse(); } @Test - public void setFontScale_changesValueOfGetFontScale() { - float fontScale = 1.5f; - shadowOf(captioningManager).setFontScale(fontScale); + public void setEnabled_callsCallback() { + captioningManager.addCaptioningChangeListener(captioningChangeListener); + Settings.Secure.putInt( + context.getContentResolver(), Secure.ACCESSIBILITY_CAPTIONING_ENABLED, ENABLED); - assertThat(captioningManager.getFontScale()).isWithin(0.001f).of(fontScale); + shadowOf(Looper.getMainLooper()).idle(); + assertThat(captioningChangeListener.isEnabled).isTrue(); } @Test - public void setFontScale_notifiesObservers() { - float fontScale = 1.5f; - captioningManager.addCaptioningChangeListener(captioningChangeListener); - - shadowOf(captioningManager).setFontScale(fontScale); + public void setFontScale_updatesValue() { + Settings.Secure.putFloat( + context.getContentResolver(), Secure.ACCESSIBILITY_CAPTIONING_FONT_SCALE, 2.0f); - verify(captioningChangeListener).onFontScaleChanged(fontScale); + assertThat(captioningManager.getFontScale()).isEqualTo(2.0f); } @Test - public void addCaptioningChangeListener_doesNotRegisterSameListenerTwice() { - float fontScale = 1.5f; + public void setFontScale_callsCallback() { captioningManager.addCaptioningChangeListener(captioningChangeListener); + Settings.Secure.putFloat( + context.getContentResolver(), Secure.ACCESSIBILITY_CAPTIONING_FONT_SCALE, 3.0f); - captioningManager.addCaptioningChangeListener(captioningChangeListener); + shadowOf(Looper.getMainLooper()).idle(); + assertThat(captioningChangeListener.fontScale).isEqualTo(3.0f); + } - shadowOf(captioningManager).setFontScale(fontScale); - verify(captioningChangeListener).onFontScaleChanged(fontScale); + @Test + public void setLocale_updatesValue() { + Settings.Secure.putString( + context.getContentResolver(), + Secure.ACCESSIBILITY_CAPTIONING_LOCALE, + Locale.JAPANESE.toLanguageTag()); + + assertThat(captioningManager.getLocale()).isEqualTo(Locale.JAPANESE); } @Test - public void removeCaptioningChangeListener_unregistersFontScaleListener() { + public void setLocale_callsCallback() { captioningManager.addCaptioningChangeListener(captioningChangeListener); + Settings.Secure.putString( + context.getContentResolver(), + Secure.ACCESSIBILITY_CAPTIONING_LOCALE, + Locale.FRENCH.toLanguageTag()); - captioningManager.removeCaptioningChangeListener(captioningChangeListener); - - shadowOf(captioningManager).setFontScale(1.5f); - verifyNoMoreInteractions(captioningChangeListener); + shadowOf(Looper.getMainLooper()).idle(); + assertThat(captioningChangeListener.locale).isEqualTo(Locale.FRENCH); } @Test - public void setLocale_nonNull() { - Locale locale = Locale.US; - assertThat(captioningManager.getLocale()).isNull(); - - shadowOf(captioningManager).setLocale(locale); + @Config(minSdk = TIRAMISU) + public void setSystemAudioCaptioningEnabled_updatesValue() { + captioningManager.setSystemAudioCaptioningEnabled(true); - assertThat(captioningManager.getLocale()).isEqualTo(locale); + assertThat(captioningManager.isSystemAudioCaptioningEnabled()).isEqualTo(true); } @Test - public void setLocale_null() { - shadowOf(captioningManager).setLocale(null); + @Config(minSdk = TIRAMISU) + public void setSystemAudioCaptioningEnabled_callsCallback() { + captioningManager.setSystemAudioCaptioningEnabled(false); - assertThat(captioningManager.getLocale()).isNull(); + shadowOf(Looper.getMainLooper()).idle(); + assertThat(captioningChangeListener.systemAudioCaptioningEnabled).isEqualTo(false); } @Test - public void setLocale_notifiesObservers() { - Locale locale = Locale.US; - captioningManager.addCaptioningChangeListener(captioningChangeListener); + @Config(minSdk = TIRAMISU) + public void setSystemAudioCaptioningUiEnabled_updatesValue() { + captioningManager.setSystemAudioCaptioningUiEnabled(true); + + assertThat(captioningManager.isSystemAudioCaptioningUiEnabled()).isEqualTo(true); + } - shadowOf(captioningManager).setLocale(locale); + @Test + @Config(minSdk = TIRAMISU) + public void setSystemAudioCaptioningUiEnabled_callsCallback() { + captioningManager.setSystemAudioCaptioningUiEnabled(false); - verify(captioningChangeListener).onLocaleChanged(locale); + shadowOf(Looper.getMainLooper()).idle(); + assertThat(captioningChangeListener.systemAudioCaptioningUiEnabled).isEqualTo(false); } } diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowCaptureRequestBuilderTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowCaptureRequestBuilderTest.java index e6049813f..3364ebe71 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowCaptureRequestBuilderTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowCaptureRequestBuilderTest.java @@ -21,7 +21,6 @@ import org.junit.runner.RunWith; import org.robolectric.annotation.Config; /** Tests for {@link ShadowCaptureRequestBuilder}. */ -@Config(minSdk = VERSION_CODES.LOLLIPOP) @RunWith(AndroidJUnit4.class) public class ShadowCaptureRequestBuilderTest { diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowCaptureResultTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowCaptureResultTest.java index 348f8e060..b77e8e5f5 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowCaptureResultTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowCaptureResultTest.java @@ -5,14 +5,11 @@ import static org.junit.Assert.fail; import static org.robolectric.Shadows.shadowOf; import android.hardware.camera2.CaptureResult; -import android.os.Build.VERSION_CODES; import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; -import org.robolectric.annotation.Config; /** Tests for {@link ShadowCaptureResult}. */ -@Config(minSdk = VERSION_CODES.LOLLIPOP) @RunWith(AndroidJUnit4.class) public class ShadowCaptureResultTest { diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowCardEmulationTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowCardEmulationTest.java index c6fe1eb9b..5285bde71 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowCardEmulationTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowCardEmulationTest.java @@ -9,14 +9,12 @@ import android.content.ComponentName; import android.content.pm.PackageManager; import android.nfc.NfcAdapter; import android.nfc.cardemulation.CardEmulation; -import android.os.Build; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.Robolectric; -import org.robolectric.annotation.Config; /** Test the shadow implementation of {@link CardEmulation}. */ @RunWith(AndroidJUnit4.class) @@ -42,7 +40,6 @@ public final class ShadowCardEmulationTest { } @Test - @Config(minSdk = Build.VERSION_CODES.LOLLIPOP) public void isDefaultServiceForCategory_canOverride() { assertThat(cardEmulation.isDefaultServiceForCategory(service, TEST_CATEGORY)).isFalse(); ShadowCardEmulation.setDefaultServiceForCategory(service, TEST_CATEGORY); @@ -52,7 +49,6 @@ public final class ShadowCardEmulationTest { } @Test - @Config(minSdk = Build.VERSION_CODES.LOLLIPOP) public void setPreferredService_canCapture() { assertThat(ShadowCardEmulation.getPreferredService() == null).isTrue(); cardEmulation.setPreferredService(activity, service); @@ -62,7 +58,6 @@ public final class ShadowCardEmulationTest { } @Test - @Config(minSdk = Build.VERSION_CODES.LOLLIPOP) public void categoryAllowsForegroundPreference_canSet() { assertThat(cardEmulation.categoryAllowsForegroundPreference(CardEmulation.CATEGORY_PAYMENT)) .isFalse(); diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowConnectivityManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowConnectivityManagerTest.java index 236c59225..ac4374a19 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowConnectivityManagerTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowConnectivityManagerTest.java @@ -386,6 +386,14 @@ public class ShadowConnectivityManagerTest { } @Test + @Config(minSdk = O) + public void registerDefaultCallback_withHandler_shouldAddCallback() { + ConnectivityManager.NetworkCallback callback = createSimpleCallback(); + connectivityManager.registerDefaultNetworkCallback(callback, new Handler()); + assertThat(shadowOf(connectivityManager).getNetworkCallbacks()).hasSize(1); + } + + @Test @Config(minSdk = S) public void registerBestMatchingNetworkCallback_shouldAddCallback() { NetworkRequest.Builder builder = new NetworkRequest.Builder(); diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowContextImplTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowContextImplTest.java index 2a348b1ec..a799e1e7a 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowContextImplTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowContextImplTest.java @@ -1,11 +1,10 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.KITKAT; import static android.os.Build.VERSION_CODES.N; import static android.os.Build.VERSION_CODES.Q; import static android.os.Build.VERSION_CODES.TIRAMISU; import static com.google.common.truth.Truth.assertThat; -import static org.junit.Assert.fail; +import static org.junit.Assert.assertThrows; import static org.robolectric.Shadows.shadowOf; import android.app.Application; @@ -206,12 +205,11 @@ public class ShadowContextImplTest { ServiceConnection serviceConnection = buildServiceConnection(); int flags = 0; - try { - context.bindServiceAsUser(serviceIntent, serviceConnection, flags, Process.myUserHandle()); - fail("bindServiceAsUser should throw IllegalArgumentException!"); - } catch (IllegalArgumentException e) { - // expected - } + assertThrows( + IllegalArgumentException.class, + () -> + context.bindServiceAsUser( + serviceIntent, serviceConnection, flags, Process.myUserHandle())); } @Test @@ -226,29 +224,14 @@ public class ShadowContextImplTest { } @Test - public void bindService_shouldAllowImplicitIntentPreLollipop() { - context.getApplicationInfo().targetSdkVersion = KITKAT; - Intent serviceIntent = new Intent(); - ServiceConnection serviceConnection = buildServiceConnection(); - int flags = 0; - - assertThat(context.bindService(serviceIntent, serviceConnection, flags)).isTrue(); - - assertThat(shadowOf(context).getBoundServiceConnections()).hasSize(1); - } - - @Test - public void bindService_shouldThrowOnImplicitIntentOnLollipop() { + public void bindService_shouldThrowOnImplicitIntent() { Intent serviceIntent = new Intent(); ServiceConnection serviceConnection = buildServiceConnection(); int flags = 0; - try { - context.bindService(serviceIntent, serviceConnection, flags); - fail("bindService should throw IllegalArgumentException!"); - } catch (IllegalArgumentException e) { - // expected - } + assertThrows( + IllegalArgumentException.class, + () -> context.bindService(serviceIntent, serviceConnection, flags)); } @Test @@ -293,36 +276,15 @@ public class ShadowContextImplTest { } @Test - public void startService_shouldAllowImplicitIntentPreLollipop() { - context.getApplicationInfo().targetSdkVersion = KITKAT; - context.startService(new Intent("dummy_action")); - assertThat(shadowOf(context).getNextStartedService().getAction()).isEqualTo("dummy_action"); - } - - @Test - public void startService_shouldThrowOnImplicitIntentOnLollipop() { - try { - context.startService(new Intent("dummy_action")); - fail("startService should throw IllegalArgumentException!"); - } catch (IllegalArgumentException e) { - // expected - } + public void startService_shouldThrowOnImplicitIntent() { + assertThrows( + IllegalArgumentException.class, () -> context.startService(new Intent("dummy_action"))); } @Test - public void stopService_shouldAllowImplicitIntentPreLollipop() { - context.getApplicationInfo().targetSdkVersion = KITKAT; - context.stopService(new Intent("dummy_action")); - } - - @Test - public void stopService_shouldThrowOnImplicitIntentOnLollipop() { - try { - context.stopService(new Intent("dummy_action")); - fail("stopService should throw IllegalArgumentException!"); - } catch (IllegalArgumentException e) { - // expected - } + public void stopService_shouldThrowOnImplicitIntent() { + assertThrows( + IllegalArgumentException.class, () -> context.stopService(new Intent("dummy_action"))); } @Test @@ -371,12 +333,8 @@ public class ShadowContextImplTest { @Test public void createPackageContext_absent() { - try { - context.createPackageContext("doesnt.exist", 0); - fail("Should throw NameNotFoundException"); - } catch (NameNotFoundException e) { - // expected - } + assertThrows( + NameNotFoundException.class, () -> context.createPackageContext("does.not.exist", 0)); } @Test diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowDeviceConfigTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowDeviceConfigTest.java index af5d76460..909c4b52b 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowDeviceConfigTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowDeviceConfigTest.java @@ -1,18 +1,583 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package org.robolectric.shadows; import static android.os.Build.VERSION_CODES.Q; +import static android.os.Build.VERSION_CODES.R; +import static android.os.Build.VERSION_CODES.TIRAMISU; +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; +import android.provider.DeviceConfig; +import android.provider.DeviceConfig.Properties; import androidx.test.ext.junit.runners.AndroidJUnit4; +import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.annotation.Config; -/** Tests for {@link ShadowDeviceConfig} */ +/** Tests that ensure appropriate settings are backed up. */ @RunWith(AndroidJUnit4.class) @Config(minSdk = Q) public class ShadowDeviceConfigTest { + // 2 sec + private static final String DEFAULT_VALUE = "test_defaultValue"; + private static final String NAMESPACE = "namespace1"; + private static final String KEY = "key1"; + private static final String KEY2 = "key2"; + private static final String KEY3 = "key3"; + private static final String VALUE = "value1"; + private static final String VALUE2 = "value2"; + private static final String VALUE3 = "value3"; + + @Test + public void getProperty_empty() { + String result = DeviceConfig.getProperty(NAMESPACE, KEY); + assertThat(result).isNull(); + } + + @Test + public void getProperty_nullNamespace() { + assertThrows( + NullPointerException.class, + () -> { + DeviceConfig.getProperty(null, KEY); + }); + } + + @Test + public void getProperty_nullName() { + assertThrows( + NullPointerException.class, + () -> { + DeviceConfig.getProperty(NAMESPACE, null); + }); + } + + @Test + public void getString_empty() { + final String defaultValue = "defaultValue"; + final String result = DeviceConfig.getString(NAMESPACE, KEY, defaultValue); + assertThat(result).isEqualTo(defaultValue); + } + + @Test + public void getString_nullDefault() { + final String result = DeviceConfig.getString(NAMESPACE, KEY, null); + assertThat(result).isNull(); + } + + @Test + public void getString_nonEmpty() { + final String value = "new_value"; + final String defaultValue = "default"; + DeviceConfig.setProperty(NAMESPACE, KEY, value, false); + + final String result = DeviceConfig.getString(NAMESPACE, KEY, defaultValue); + assertThat(result).isEqualTo(value); + } + + @Test + public void getString_nullNamespace() { + try { + DeviceConfig.getString(null, KEY, "defaultValue"); + Assert.fail("Null namespace should have resulted in an NPE."); + } catch (NullPointerException e) { + // expected + } + } + + @Test + public void getString_nullName() { + assertThrows( + NullPointerException.class, + () -> { + DeviceConfig.getString(NAMESPACE, null, "defaultValue"); + }); + } + + @Test + public void getBoolean_empty() { + final boolean defaultValue = true; + final boolean result = DeviceConfig.getBoolean(NAMESPACE, KEY, defaultValue); + assertThat(result).isEqualTo(defaultValue); + } + + @Test + public void getBoolean_valid() { + final boolean value = true; + final boolean defaultValue = false; + DeviceConfig.setProperty(NAMESPACE, KEY, String.valueOf(value), false); + + final boolean result = DeviceConfig.getBoolean(NAMESPACE, KEY, defaultValue); + assertThat(result).isEqualTo(value); + } + + @Test + public void getBoolean_invalid() { + final boolean defaultValue = true; + DeviceConfig.setProperty(NAMESPACE, KEY, "not_a_boolean", false); + + final boolean result = DeviceConfig.getBoolean(NAMESPACE, KEY, defaultValue); + // Anything non-null other than case insensitive "true" parses to false. + assertThat(result).isFalse(); + } + + @Test + public void getBoolean_nullNamespace() { + assertThrows(NullPointerException.class, () -> DeviceConfig.getBoolean(null, KEY, false)); + } + + @Test + public void getBoolean_nullName() { + assertThrows(NullPointerException.class, () -> DeviceConfig.getBoolean(NAMESPACE, null, false)); + } + + @Test + public void getInt_empty() { + final int defaultValue = 999; + final int result = DeviceConfig.getInt(NAMESPACE, KEY, defaultValue); + assertThat(result).isEqualTo(defaultValue); + } + + @Test + public void getInt_valid() { + final int value = 123; + final int defaultValue = 999; + DeviceConfig.setProperty(NAMESPACE, KEY, String.valueOf(value), false); + + final int result = DeviceConfig.getInt(NAMESPACE, KEY, defaultValue); + assertThat(result).isEqualTo(value); + } + + @Test + public void getInt_invalid() { + final int defaultValue = 999; + DeviceConfig.setProperty(NAMESPACE, KEY, "not_an_int", false); + + final int result = DeviceConfig.getInt(NAMESPACE, KEY, defaultValue); + // Failure to parse results in using the default value + assertThat(result).isEqualTo(defaultValue); + } + + @Test + public void getInt_nullNamespace() { + assertThrows(NullPointerException.class, () -> DeviceConfig.getInt(null, KEY, 0)); + } + + @Test + public void getInt_nullName() { + assertThrows(NullPointerException.class, () -> DeviceConfig.getInt(NAMESPACE, null, 0)); + } + + @Test + public void getLong_empty() { + final long defaultValue = 123456; + final long result = DeviceConfig.getLong(NAMESPACE, KEY, defaultValue); + assertThat(result).isEqualTo(defaultValue); + } + + @Test + public void getLong_valid() { + final long value = 456789; + final long defaultValue = 123456; + DeviceConfig.setProperty(NAMESPACE, KEY, String.valueOf(value), false); + + final long result = DeviceConfig.getLong(NAMESPACE, KEY, defaultValue); + assertThat(result).isEqualTo(value); + } + + @Test + public void getLong_invalid() { + final long defaultValue = 123456; + DeviceConfig.setProperty(NAMESPACE, KEY, "not_a_long", false); + + final long result = DeviceConfig.getLong(NAMESPACE, KEY, defaultValue); + // Failure to parse results in using the default value + assertThat(result).isEqualTo(defaultValue); + } + + @Test + public void getLong_nullNamespace() { + assertThrows(NullPointerException.class, () -> DeviceConfig.getLong(null, KEY, 0)); + } + + @Test + public void getLong_nullName() { + assertThrows(NullPointerException.class, () -> DeviceConfig.getLong(NAMESPACE, null, 0)); + } + + @Test + public void getFloat_empty() { + final float defaultValue = 123.456f; + final float result = DeviceConfig.getFloat(NAMESPACE, KEY, defaultValue); + assertThat(result).isEqualTo(defaultValue); + } + + @Test + public void getFloat_valid() { + final float value = 456.789f; + final float defaultValue = 123.456f; + DeviceConfig.setProperty(NAMESPACE, KEY, String.valueOf(value), false); + + final float result = DeviceConfig.getFloat(NAMESPACE, KEY, defaultValue); + assertThat(result).isEqualTo(value); + } + + @Test + public void getFloat_invalid() { + final float defaultValue = 123.456f; + DeviceConfig.setProperty(NAMESPACE, KEY, "not_a_float", false); + + final float result = DeviceConfig.getFloat(NAMESPACE, KEY, defaultValue); + // Failure to parse results in using the default value + assertThat(result).isEqualTo(defaultValue); + } + + @Test + public void getFloat_nullNamespace() { + assertThrows(NullPointerException.class, () -> DeviceConfig.getFloat(null, KEY, 0)); + } + + @Test + public void getFloat_nullName() { + assertThrows(NullPointerException.class, () -> DeviceConfig.getFloat(NAMESPACE, null, 0)); + } + + @Test + public void setProperty_nullNamespace() { + assertThrows( + NullPointerException.class, () -> DeviceConfig.setProperty(null, KEY, VALUE, false)); + } + + @Test + public void setProperty_nullName() { + assertThrows( + NullPointerException.class, () -> DeviceConfig.setProperty(NAMESPACE, null, VALUE, false)); + } + + @Test + public void setAndGetProperty_sameNamespace() { + DeviceConfig.setProperty(NAMESPACE, KEY, VALUE, false); + String result = DeviceConfig.getProperty(NAMESPACE, KEY); + assertThat(result).isEqualTo(VALUE); + } + + @Test + public void setAndGetProperty_differentNamespace() { + String newNamespace = "namespace2"; + DeviceConfig.setProperty(NAMESPACE, KEY, VALUE, false); + String result = DeviceConfig.getProperty(newNamespace, KEY); + assertThat(result).isNull(); + } + + @Test + public void setAndGetProperty_multipleNamespaces() { + String newNamespace = "namespace2"; + String newValue = "value2"; + DeviceConfig.setProperty(NAMESPACE, KEY, VALUE, false); + DeviceConfig.setProperty(newNamespace, KEY, newValue, false); + String result = DeviceConfig.getProperty(NAMESPACE, KEY); + assertThat(result).isEqualTo(VALUE); + result = DeviceConfig.getProperty(newNamespace, KEY); + assertThat(result).isEqualTo(newValue); + } + + @Test + public void setAndGetProperty_overrideValue() { + String newValue = "value2"; + DeviceConfig.setProperty(NAMESPACE, KEY, VALUE, false); + DeviceConfig.setProperty(NAMESPACE, KEY, newValue, false); + String result = DeviceConfig.getProperty(NAMESPACE, KEY); + assertThat(result).isEqualTo(newValue); + } + + @Config(minSdk = R) + @Test + public void getProperties_fullNamespace() { + Properties properties = DeviceConfig.getProperties(NAMESPACE); + assertThat(properties.getKeyset()).isEmpty(); + + DeviceConfig.setProperty(NAMESPACE, KEY, VALUE, false); + DeviceConfig.setProperty(NAMESPACE, KEY2, VALUE2, false); + properties = DeviceConfig.getProperties(NAMESPACE); + assertThat(properties.getKeyset()).containsExactly(KEY, KEY2); + assertThat(properties.getString(KEY, DEFAULT_VALUE)).isEqualTo(VALUE); + assertThat(properties.getString(KEY2, DEFAULT_VALUE)).isEqualTo(VALUE2); + + DeviceConfig.setProperty(NAMESPACE, KEY, VALUE3, false); + properties = DeviceConfig.getProperties(NAMESPACE); + assertThat(properties.getKeyset()).containsExactly(KEY, KEY2); + assertThat(properties.getString(KEY, DEFAULT_VALUE)).isEqualTo(VALUE3); + assertThat(properties.getString(KEY2, DEFAULT_VALUE)).isEqualTo(VALUE2); + + DeviceConfig.setProperty(NAMESPACE, KEY3, VALUE, false); + properties = DeviceConfig.getProperties(NAMESPACE); + assertThat(properties.getKeyset()).containsExactly(KEY, KEY2, KEY3); + assertThat(properties.getString(KEY, DEFAULT_VALUE)).isEqualTo(VALUE3); + assertThat(properties.getString(KEY2, DEFAULT_VALUE)).isEqualTo(VALUE2); + assertThat(properties.getString(KEY3, DEFAULT_VALUE)).isEqualTo(VALUE); + } + + @Config(minSdk = R) + @Test + public void getProperties_getString() { + DeviceConfig.setProperty(NAMESPACE, KEY, VALUE, false); + DeviceConfig.setProperty(NAMESPACE, KEY2, VALUE2, false); + + Properties properties = DeviceConfig.getProperties(NAMESPACE, KEY, KEY2); + assertThat(properties.getKeyset()).containsExactly(KEY, KEY2); + assertThat(properties.getString(KEY, DEFAULT_VALUE)).isEqualTo(VALUE); + assertThat(properties.getString(KEY2, DEFAULT_VALUE)).isEqualTo(VALUE2); + } + + @Config(minSdk = R) + @Test + public void getProperties_getBoolean() { + DeviceConfig.setProperty(NAMESPACE, KEY, "true", false); + DeviceConfig.setProperty(NAMESPACE, KEY2, "false", false); + DeviceConfig.setProperty(NAMESPACE, KEY3, "not a valid boolean", false); + + Properties properties = DeviceConfig.getProperties(NAMESPACE, KEY, KEY2, KEY3); + assertThat(properties.getKeyset()).containsExactly(KEY, KEY2, KEY3); + assertThat(properties.getBoolean(KEY, true)).isTrue(); + assertThat(properties.getBoolean(KEY, false)).isTrue(); + assertThat(properties.getBoolean(KEY2, true)).isFalse(); + assertThat(properties.getBoolean(KEY2, false)).isFalse(); + // KEY3 was set to garbage, anything nonnull but "true" will parse as false + assertThat(properties.getBoolean(KEY3, true)).isFalse(); + assertThat(properties.getBoolean(KEY3, false)).isFalse(); + // If a key was not set, it will return the default value + assertThat(properties.getBoolean("missing_key", true)).isTrue(); + assertThat(properties.getBoolean("missing_key", false)).isFalse(); + } + + @Config(minSdk = R) + @Test + public void getProperties_getInt() { + final int value = 101; + + DeviceConfig.setProperty(NAMESPACE, KEY, Integer.toString(value), false); + DeviceConfig.setProperty(NAMESPACE, KEY2, "not a valid int", false); + + Properties properties = DeviceConfig.getProperties(NAMESPACE, KEY, KEY2); + assertThat(properties.getKeyset()).containsExactly(KEY, KEY2); + assertThat(properties.getInt(KEY, -1)).isEqualTo(value); + // KEY2 was set to garbage, the default value is returned if an int cannot be parsed + assertThat(properties.getInt(KEY2, -1)).isEqualTo(-1); + } + + @Config(minSdk = R) + @Test + public void getProperties_getFloat() { + final float value = 101.010f; + + DeviceConfig.setProperty(NAMESPACE, KEY, Float.toString(value), false); + DeviceConfig.setProperty(NAMESPACE, KEY2, "not a valid float", false); + + Properties properties = DeviceConfig.getProperties(NAMESPACE, KEY, KEY2); + assertThat(properties.getKeyset()).containsExactly(KEY, KEY2); + assertThat(properties.getFloat(KEY, -1.0f)).isEqualTo(value); + // KEY2 was set to garbage, the default value is returned if a float cannot be parsed + assertThat(properties.getFloat(KEY2, -1.0f)).isEqualTo(-1.0f); + } + + @Config(minSdk = R) + @Test + public void getProperties_getLong() { + final long value = 101; + + DeviceConfig.setProperty(NAMESPACE, KEY, Long.toString(value), false); + DeviceConfig.setProperty(NAMESPACE, KEY2, "not a valid long", false); + + Properties properties = DeviceConfig.getProperties(NAMESPACE, KEY, KEY2); + assertThat(properties.getKeyset()).containsExactly(KEY, KEY2); + assertThat(properties.getLong(KEY, -1)).isEqualTo(value); + // KEY2 was set to garbage, the default value is returned if a long cannot be parsed + assertThat(properties.getLong(KEY2, -1)).isEqualTo(-1); + } + + @Config(minSdk = R) + @Test + public void getProperties_defaults() { + DeviceConfig.setProperty(NAMESPACE, KEY, VALUE, false); + DeviceConfig.setProperty(NAMESPACE, KEY3, VALUE3, false); + + Properties properties = DeviceConfig.getProperties(NAMESPACE, KEY, KEY2); + assertThat(properties.getKeyset()).containsExactly(KEY); + assertThat(properties.getString(KEY, DEFAULT_VALUE)).isEqualTo(VALUE); + // not set in DeviceConfig, but requested in getProperties + assertThat(properties.getString(KEY2, DEFAULT_VALUE)).isEqualTo(DEFAULT_VALUE); + // set in DeviceConfig, but not requested in getProperties + assertThat(properties.getString(KEY3, DEFAULT_VALUE)).isEqualTo(DEFAULT_VALUE); + } + + @Config(minSdk = R) + @Test + public void setProperties() throws Exception { + Properties properties = + new Properties.Builder(NAMESPACE).setString(KEY, VALUE).setString(KEY2, VALUE2).build(); + + DeviceConfig.setProperties(properties); + properties = DeviceConfig.getProperties(NAMESPACE); + assertThat(properties.getKeyset()).containsExactly(KEY, KEY2); + assertThat(properties.getString(KEY, DEFAULT_VALUE)).isEqualTo(VALUE); + assertThat(properties.getString(KEY2, DEFAULT_VALUE)).isEqualTo(VALUE2); + + properties = + new Properties.Builder(NAMESPACE).setString(KEY, VALUE2).setString(KEY3, VALUE3).build(); + + DeviceConfig.setProperties(properties); + properties = DeviceConfig.getProperties(NAMESPACE); + assertThat(properties.getKeyset()).containsExactly(KEY, KEY3); + assertThat(properties.getString(KEY, DEFAULT_VALUE)).isEqualTo(VALUE2); + assertThat(properties.getString(KEY3, DEFAULT_VALUE)).isEqualTo(VALUE3); + + assertThat(properties.getKeyset()).doesNotContain(KEY2); + assertThat(properties.getString(KEY2, DEFAULT_VALUE)).isEqualTo(DEFAULT_VALUE); + } + + @Config(minSdk = R) + @Test + public void setProperties_multipleNamespaces() throws Exception { + final String namespace2 = "namespace2"; + Properties properties1 = + new Properties.Builder(NAMESPACE).setString(KEY, VALUE).setString(KEY2, VALUE2).build(); + Properties properties2 = + new Properties.Builder(namespace2).setString(KEY2, VALUE).setString(KEY3, VALUE2).build(); + + assertThat(DeviceConfig.setProperties(properties1)).isTrue(); + assertThat(DeviceConfig.setProperties(properties2)).isTrue(); + + Properties properties = DeviceConfig.getProperties(NAMESPACE); + assertThat(properties.getKeyset()).containsExactly(KEY, KEY2); + assertThat(properties.getString(KEY, DEFAULT_VALUE)).isEqualTo(VALUE); + assertThat(properties.getString(KEY2, DEFAULT_VALUE)).isEqualTo(VALUE2); + + assertThat(properties.getKeyset()).doesNotContain(KEY3); + assertThat(properties.getString(KEY3, DEFAULT_VALUE)).isEqualTo(DEFAULT_VALUE); + + properties = DeviceConfig.getProperties(namespace2); + assertThat(properties.getKeyset()).containsExactly(KEY2, KEY3); + assertThat(properties.getString(KEY2, DEFAULT_VALUE)).isEqualTo(VALUE); + assertThat(properties.getString(KEY3, DEFAULT_VALUE)).isEqualTo(VALUE2); + + assertThat(properties.getKeyset()).doesNotContain(KEY); + assertThat(properties.getString(KEY, DEFAULT_VALUE)).isEqualTo(DEFAULT_VALUE); + } + + @Config(minSdk = R) + @Test + public void propertiesBuilder() { + boolean booleanValue = true; + int intValue = 123; + float floatValue = 4.56f; + long longValue = -789L; + String key4 = "key4"; + String key5 = "key5"; + + Properties properties = + new Properties.Builder(NAMESPACE) + .setString(KEY, VALUE) + .setBoolean(KEY2, booleanValue) + .setInt(KEY3, intValue) + .setLong(key4, longValue) + .setFloat(key5, floatValue) + .build(); + assertThat(properties.getNamespace()).isEqualTo(NAMESPACE); + assertThat(properties.getString(KEY, "defaultValue")).isEqualTo(VALUE); + assertThat(properties.getBoolean(KEY2, false)).isEqualTo(booleanValue); + assertThat(properties.getInt(KEY3, 0)).isEqualTo(intValue); + assertThat(properties.getLong("key4", 0L)).isEqualTo(longValue); + assertThat(properties.getFloat("key5", 0f)).isEqualTo(floatValue); + } + + @Config(minSdk = TIRAMISU) + @Test + public void deleteProperty_nullNamespace() { + assertThrows(NullPointerException.class, () -> DeviceConfig.deleteProperty(null, KEY)); + } + + @Config(minSdk = TIRAMISU) + @Test + public void deleteProperty_nullName() { + assertThrows(NullPointerException.class, () -> DeviceConfig.deleteProperty(NAMESPACE, null)); + } + + @Config(minSdk = TIRAMISU) + @Test + public void deletePropertyString() { + final String value = "new_value"; + final String defaultValue = "default"; + DeviceConfig.setProperty(NAMESPACE, KEY, value, false); + DeviceConfig.deleteProperty(NAMESPACE, KEY); + final String result = DeviceConfig.getString(NAMESPACE, KEY, defaultValue); + assertThat(result).isEqualTo(defaultValue); + } + + @Config(minSdk = TIRAMISU) + @Test + public void deletePropertyBoolean() { + final boolean value = true; + final boolean defaultValue = false; + DeviceConfig.setProperty(NAMESPACE, KEY, String.valueOf(value), false); + DeviceConfig.deleteProperty(NAMESPACE, KEY); + final boolean result = DeviceConfig.getBoolean(NAMESPACE, KEY, defaultValue); + assertThat(result).isEqualTo(defaultValue); + } + + @Config(minSdk = TIRAMISU) + @Test + public void deletePropertyInt() { + final int value = 123; + final int defaultValue = 999; + DeviceConfig.setProperty(NAMESPACE, KEY, String.valueOf(value), false); + DeviceConfig.deleteProperty(NAMESPACE, KEY); + final int result = DeviceConfig.getInt(NAMESPACE, KEY, defaultValue); + assertThat(result).isEqualTo(defaultValue); + } + + @Config(minSdk = TIRAMISU) + @Test + public void deletePropertyLong() { + final long value = 456789; + final long defaultValue = 123456; + DeviceConfig.setProperty(NAMESPACE, KEY, String.valueOf(value), false); + DeviceConfig.deleteProperty(NAMESPACE, KEY); + final long result = DeviceConfig.getLong(NAMESPACE, KEY, defaultValue); + assertThat(result).isEqualTo(defaultValue); + } + + @Config(minSdk = TIRAMISU) + @Test + public void deletePropertyFloat() { + final float value = 456.789f; + final float defaultValue = 123.456f; + DeviceConfig.setProperty(NAMESPACE, KEY, String.valueOf(value), false); + DeviceConfig.deleteProperty(NAMESPACE, KEY); + final float result = DeviceConfig.getFloat(NAMESPACE, KEY, defaultValue); + assertThat(result).isEqualTo(defaultValue); + } + + @Config(minSdk = TIRAMISU) @Test - public void testReset() { - ShadowDeviceConfig.reset(); + public void deleteProperty_empty() { + assertThat(DeviceConfig.deleteProperty(NAMESPACE, KEY)).isTrue(); + final String result = DeviceConfig.getString(NAMESPACE, KEY, null); + assertThat(result).isNull(); } } diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowDisplayManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowDisplayManagerTest.java index 5c9228e89..f8f5f540b 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowDisplayManagerTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowDisplayManagerTest.java @@ -506,8 +506,7 @@ public class ShadowDisplayManagerTest { .contains("configureDefaultDisplay was called a second time"); } - // because DisplayManagerGlobal don't exist in Jelly Bean, - // and we don't want them resolved as part of the test class. + // because we don't want DisplayManagerGlobal resolved as part of the test class. static class HideFromJB { public static DisplayManagerGlobal getGlobal() { return DisplayManagerGlobal.getInstance(); diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowDrawableTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowDrawableTest.java index ba9fa2b78..ced87af17 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowDrawableTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowDrawableTest.java @@ -1,7 +1,6 @@ package org.robolectric.shadows; import static com.google.common.truth.Truth.assertThat; -import static com.google.common.truth.TruthJUnit.assume; import static org.junit.Assert.assertNotNull; import static org.robolectric.Shadows.shadowOf; @@ -14,7 +13,6 @@ import android.graphics.ColorFilter; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.graphics.drawable.VectorDrawable; -import android.os.Build; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import java.io.ByteArrayInputStream; @@ -120,11 +118,6 @@ public class ShadowDrawableTest { @Test @Config(qualifiers = "hdpi") public void drawableShouldLoadImageOfCorrectSizeWithHdpiQualifier() { - if (Build.VERSION.SDK_INT >= 28) { - // getDrawable depends on ImageDecoder, which depends on binary resources - assume().that(ShadowAssetManager.useLegacy()).isFalse(); - } - final Drawable anImage = context.getResources().getDrawable(R.drawable.robolectric); assertThat(anImage.getIntrinsicHeight()).isEqualTo(251); diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowEGL14Test.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowEGL14Test.java index 4da4963cd..8a75dc35d 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowEGL14Test.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowEGL14Test.java @@ -6,15 +6,12 @@ import android.opengl.EGL14; import android.opengl.EGLConfig; import android.opengl.EGLContext; import android.opengl.EGLDisplay; -import android.os.Build.VERSION_CODES; import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; -import org.robolectric.annotation.Config; /** Unit tests for {@link ShadowEGL14Test} */ @RunWith(AndroidJUnit4.class) -@Config(minSdk = VERSION_CODES.LOLLIPOP) public final class ShadowEGL14Test { @Test public void eglGetCurrentContext() { @@ -27,8 +24,10 @@ public final class ShadowEGL14Test { } @Test - public void eglChooseConfig() { + public void eglChooseConfig_retrieveConfigs() { EGLDisplay display = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY); + // When the config array is not null, numConfig is filled with the number of configs returned + // (up to the size of the config array). EGLConfig[] configs = new EGLConfig[1]; int[] numConfig = new int[1]; assertThat(EGL14.eglChooseConfig(display, new int[0], 0, configs, 0, 1, numConfig, 0)).isTrue(); @@ -37,6 +36,16 @@ public final class ShadowEGL14Test { } @Test + public void eglChooseConfig_countMatching() { + EGLDisplay display = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY); + // When the config array is null, numConfig is filled with the total number of configs matched. + EGLConfig[] configs = null; + int[] numConfig = new int[1]; + assertThat(EGL14.eglChooseConfig(display, new int[0], 0, configs, 0, 0, numConfig, 0)).isTrue(); + assertThat(numConfig[0]).isGreaterThan(0); + } + + @Test public void eglCreateContext_v2() { EGLDisplay display = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY); int[] attribList = {EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, EGL14.EGL_NONE}; diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowEnvironmentTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowEnvironmentTest.java index cb29f8677..69aced556 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowEnvironmentTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowEnvironmentTest.java @@ -253,7 +253,7 @@ public class ShadowEnvironmentTest { } @Test - @Config(maxSdk = LOLLIPOP) + @Config(sdk = LOLLIPOP) public void getExternalStorageStatePreLollipopMR1() { File storageDir1 = ShadowEnvironment.addExternalDir("dir1"); File storageDir2 = ShadowEnvironment.addExternalDir("dir2"); diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowLocationManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowLocationManagerTest.java index 2c7bb282f..b29c63dbe 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowLocationManagerTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowLocationManagerTest.java @@ -902,7 +902,6 @@ public class ShadowLocationManagerTest { } @Test - @Config(minSdk = VERSION_CODES.LOLLIPOP) public void testRequestLocationUpdates_LocationRequest() { Location loc1 = createLocation(NETWORK_PROVIDER); Location loc2 = createLocation(NETWORK_PROVIDER); diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowMediaCodecTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowMediaCodecTest.java index 72a75bec1..e51c89a82 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowMediaCodecTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowMediaCodecTest.java @@ -20,7 +20,6 @@ import android.media.MediaCodec.CodecException; import android.media.MediaCodecInfo.CodecProfileLevel; import android.media.MediaCrypto; import android.media.MediaFormat; -import android.os.Build; import android.view.Surface; import androidx.test.ext.junit.runners.AndroidJUnit4; import java.io.IOException; @@ -735,10 +734,6 @@ public final class ShadowMediaCodecTest { private static void writeToInputBuffer(MediaCodec codec, ByteBuffer src) { int inputBufferId = codec.dequeueInputBuffer(WITHOUT_TIMEOUT); ByteBuffer inputBuffer = codec.getInputBuffer(inputBufferId); - // API versions lower than 21 don't clear the buffer before returning it. - if (Build.VERSION.SDK_INT < 21) { - inputBuffer.clear(); - } int srcLimit = src.limit(); int numberOfBytesToWrite = Math.min(src.remaining(), inputBuffer.remaining()); src.limit(src.position() + numberOfBytesToWrite); diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowMediaRecorderTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowMediaRecorderTest.java index 28be1b653..0eac22c6b 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowMediaRecorderTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowMediaRecorderTest.java @@ -5,14 +5,12 @@ import static org.junit.Assert.assertThrows; import android.hardware.Camera; import android.media.MediaRecorder; -import android.os.Build.VERSION_CODES; import android.view.Surface; import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.Shadows; -import org.robolectric.annotation.Config; import org.robolectric.shadow.api.Shadow; @RunWith(AndroidJUnit4.class) @@ -222,7 +220,6 @@ public class ShadowMediaRecorderTest { } @Test - @Config(minSdk = VERSION_CODES.LOLLIPOP) public void testGetSurface() throws Exception { mediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE); mediaRecorder.prepare(); @@ -230,14 +227,12 @@ public class ShadowMediaRecorderTest { } @Test - @Config(minSdk = VERSION_CODES.LOLLIPOP) public void testGetSurface_beforePrepare() { mediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE); assertThrows(IllegalStateException.class, () -> mediaRecorder.getSurface()); } @Test - @Config(minSdk = VERSION_CODES.LOLLIPOP) public void testGetSurface_afterStop() throws Exception { mediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE); mediaRecorder.prepare(); @@ -247,7 +242,6 @@ public class ShadowMediaRecorderTest { } @Test - @Config(minSdk = VERSION_CODES.LOLLIPOP) public void testGetSurface_wrongVideoSource() throws Exception { mediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); mediaRecorder.prepare(); diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowMediaRouterTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowMediaRouterTest.java index 44b2ee8f6..41e5b3b7b 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowMediaRouterTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowMediaRouterTest.java @@ -48,12 +48,6 @@ public final class ShadowMediaRouterTest { RouteInfo bluetoothRoute = mediaRouter.getRouteAt(1); assertThat(bluetoothRoute.getName().toString()) .isEqualTo(ShadowMediaRouter.BLUETOOTH_DEVICE_NAME); - } - - @Test - public void testAddBluetoothRoute_checkBluetoothRouteProperties_apiJbMr2() { - shadowOf(mediaRouter).addBluetoothRoute(); - RouteInfo bluetoothRoute = mediaRouter.getRouteAt(1); assertThat(bluetoothRoute.getDescription().toString()).isEqualTo("Bluetooth audio"); } diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowMediaSessionManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowMediaSessionManagerTest.java index dbd37216f..bc96883ad 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowMediaSessionManagerTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowMediaSessionManagerTest.java @@ -6,7 +6,6 @@ import android.content.Context; import android.media.session.MediaController; import android.media.session.MediaSession; import android.media.session.MediaSessionManager; -import android.os.Build; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import java.util.ArrayList; @@ -15,11 +14,9 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.Shadows; -import org.robolectric.annotation.Config; /** Tests for {@link ShadowMediaSessionManager} */ @RunWith(AndroidJUnit4.class) -@Config(minSdk = Build.VERSION_CODES.LOLLIPOP) public class ShadowMediaSessionManagerTest { private MediaSessionManager mediaSessionManager; diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowMediaSessionTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowMediaSessionTest.java index 3f1e48621..bae7f3bc8 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowMediaSessionTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowMediaSessionTest.java @@ -1,16 +1,13 @@ package org.robolectric.shadows; import android.media.session.MediaSession; -import android.os.Build; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; -import org.robolectric.annotation.Config; /** Tests for robolectric functionality around {@link MediaSession}. */ @RunWith(AndroidJUnit4.class) -@Config(minSdk = Build.VERSION_CODES.LOLLIPOP) public class ShadowMediaSessionTest { @Test public void mediaSessionCompat_creation() { diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowNotificationListenerServiceTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowNotificationListenerServiceTest.java index 3a249a878..6c8f0e630 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowNotificationListenerServiceTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowNotificationListenerServiceTest.java @@ -7,7 +7,6 @@ import static org.robolectric.Shadows.shadowOf; import android.app.Notification; import android.content.ComponentName; import android.content.Context; -import android.os.Build; import android.os.Build.VERSION_CODES; import android.service.notification.NotificationListenerService; import android.service.notification.StatusBarNotification; @@ -21,7 +20,6 @@ import org.robolectric.Robolectric; import org.robolectric.annotation.Config; /** Test for ShadowNotificationListenerService. */ -@Config(minSdk = Build.VERSION_CODES.LOLLIPOP) @RunWith(AndroidJUnit4.class) public final class ShadowNotificationListenerServiceTest { diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowPackageManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowPackageManagerTest.java index 91f3127fa..7f2762b02 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowPackageManagerTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowPackageManagerTest.java @@ -43,7 +43,6 @@ import static android.os.Build.VERSION_CODES.S; import static android.os.Build.VERSION_CODES.TIRAMISU; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; -import static com.google.common.truth.TruthJUnit.assume; import static java.util.concurrent.TimeUnit.SECONDS; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThrows; @@ -2758,8 +2757,6 @@ public class ShadowPackageManagerTest { @Test public void getResourcesForApplication_ApkNotInstalled() throws NameNotFoundException { - assume().that(RuntimeEnvironment.useLegacyResources()).isFalse(); - File testApk = TestUtil.resourcesBaseDir().resolve(REAL_TEST_APP_ASSET_PATH).toFile(); PackageInfo packageInfo = packageManager.getPackageArchiveInfo(testApk.getAbsolutePath(), 0); diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowResourcesTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowResourcesTest.java index 9a71b9f87..799fce722 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowResourcesTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowResourcesTest.java @@ -4,9 +4,7 @@ import static android.os.Build.VERSION_CODES.N_MR1; import static android.os.Build.VERSION_CODES.Q; import static android.os.Build.VERSION_CODES.S; import static com.google.common.truth.Truth.assertThat; -import static com.google.common.truth.TruthJUnit.assume; import static org.robolectric.Shadows.shadowOf; -import static org.robolectric.shadows.ShadowAssetManager.useLegacy; import android.content.Context; import android.content.res.AssetFileDescriptor; @@ -34,7 +32,6 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.R; import org.robolectric.Robolectric; -import org.robolectric.android.XmlResourceParserImpl; import org.robolectric.annotation.Config; @RunWith(AndroidJUnit4.class) @@ -108,16 +105,7 @@ public class ShadowResourcesTest { } @Test - public void openRawResourceFd_shouldReturnsNullForLegacyResource() throws Exception { - assume().that(useLegacy()).isTrue(); - try (AssetFileDescriptor afd = resources.openRawResourceFd(R.raw.raw_resource)) { - assertThat(afd).isNull(); - } - } - - @Test public void openRawResourceFd_shouldReturnsValidFdForUnCompressFile() throws Exception { - assume().that(useLegacy()).isFalse(); try (AssetFileDescriptor afd = resources.openRawResourceFd(R.raw.raw_resource)) { assertThat(afd).isNotNull(); } @@ -181,19 +169,6 @@ public class ShadowResourcesTest { } @Test - public void getXml_shouldHavePackageContextForReferenceResolution() { - if (!useLegacy()) { - return; - } - XmlResourceParserImpl xmlResourceParser = - (XmlResourceParserImpl) resources.getXml(R.xml.preferences); - assertThat(xmlResourceParser.qualify("?ref")).isEqualTo("?org.robolectric:attr/ref"); - - xmlResourceParser = (XmlResourceParserImpl) resources.getXml(android.R.layout.list_content); - assertThat(xmlResourceParser.qualify("?ref")).isEqualTo("?android:attr/ref"); - } - - @Test @Config(minSdk = N_MR1) public void obtainAttributes() { TypedArray typedArray = diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowSettingsTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowSettingsTest.java index 6c037a073..43fcb8194 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowSettingsTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowSettingsTest.java @@ -28,6 +28,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.annotation.Config; +import org.robolectric.versioning.AndroidVersions.U; @RunWith(AndroidJUnit4.class) public class ShadowSettingsTest { @@ -318,4 +319,22 @@ public class ShadowSettingsTest { context.getContentResolver(), Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0)) .isEqualTo(0); } + + @Config(minSdk = U.SDK_INT) + @Test + public void testConfig_putAndGetString() { + assertThat(Settings.Config.putString("namespace", "key", "value", false)).isTrue(); + assertThat(Settings.Config.getString("namespace/key")).isEqualTo("value"); + assertThat(Settings.Config.getString("namespace/missing_key")).isEqualTo(null); + assertThat(Settings.Config.getString("missing_namespace/key")).isEqualTo(null); + } + + @Config(minSdk = U.SDK_INT) + @Test + public void testConfig_putAndGetStrings() { + assertThat(Settings.Config.putString("namespace", "key", "value", false)).isTrue(); + assertThat(Settings.Config.getString("namespace/key")).isEqualTo("value"); + assertThat(Settings.Config.getString("namespace/missing_key")).isEqualTo(null); + assertThat(Settings.Config.getString("missing_namespace/key")).isEqualTo(null); + } } diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowSoundPoolTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowSoundPoolTest.java index d38cbb63a..1617bc037 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowSoundPoolTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowSoundPoolTest.java @@ -1,19 +1,16 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.LOLLIPOP; import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.robolectric.Shadows.shadowOf; -import android.media.AudioManager; import android.media.SoundPool; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.R; -import org.robolectric.RuntimeEnvironment; import org.robolectric.shadows.ShadowSoundPool.Playback; @RunWith(AndroidJUnit4.class) @@ -167,8 +164,6 @@ public class ShadowSoundPoolTest { } private SoundPool createSoundPool() { - return RuntimeEnvironment.getApiLevel() >= LOLLIPOP - ? new SoundPool.Builder().build() - : new SoundPool(1, AudioManager.STREAM_MUSIC, 0); + return new SoundPool.Builder().build(); } } diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowSpeechRecognizerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowSpeechRecognizerTest.java index c0ff1622f..25c7ed722 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowSpeechRecognizerTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowSpeechRecognizerTest.java @@ -18,6 +18,7 @@ import android.speech.SpeechRecognizer; import android.util.Log; import androidx.test.core.app.ApplicationProvider; import java.util.ArrayList; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -42,6 +43,11 @@ public class ShadowSpeechRecognizerTest { supportCallback = new TestRecognitionSupportCallback(); } + @After + public void tearDown() { + speechRecognizer.destroy(); + } + @Test public void onErrorCalled() { startListening(); @@ -118,6 +124,7 @@ public class ShadowSpeechRecognizerTest { shadowOf(speechRecognizer).triggerOnResults(new Bundle()); speechRecognizer.stopListening(); + shadowOf(getMainLooper()).idle(); assertNoErrorLogs(); } @@ -136,6 +143,8 @@ public class ShadowSpeechRecognizerTest { /** Verify the startlistening flow works when using custom component name. */ @Test public void startListeningWithCustomComponent() { + speechRecognizer.destroy(); + speechRecognizer = SpeechRecognizer.createSpeechRecognizer( ApplicationProvider.getApplicationContext(), @@ -157,6 +166,7 @@ public class ShadowSpeechRecognizerTest { shadowOf(getMainLooper()).idle(); assertThat(ShadowSpeechRecognizer.getLatestSpeechRecognizer()) .isSameInstanceAs(newSpeechRecognizer); + newSpeechRecognizer.destroy(); } @Test @@ -170,6 +180,7 @@ public class ShadowSpeechRecognizerTest { newSpeechRecognizer.startListening(intent); shadowOf(getMainLooper()).idle(); assertThat(shadowOf(newSpeechRecognizer).getLastRecognizerIntent()).isEqualTo(intent); + newSpeechRecognizer.destroy(); } private void startListening() { @@ -236,6 +247,7 @@ public class ShadowSpeechRecognizerTest { @Config(minSdk = TIRAMISU) @Test public void onCreateOnDeviceRecognizer_setsLatestSpeechRecognizer() { + speechRecognizer.destroy(); speechRecognizer = SpeechRecognizer.createOnDeviceSpeechRecognizer(applicationContext); assertThat(speechRecognizer) diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowTelecomManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowTelecomManagerTest.java index f7606cff6..e31897355 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowTelecomManagerTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowTelecomManagerTest.java @@ -710,6 +710,22 @@ public class ShadowTelecomManagerTest { assertThat(telecomService.handleMmi("123", phoneAccountHandle)).isTrue(); } + @Test + @Config(minSdk = O) + public void isOutgoingCallPermitted_false() { + shadowOf(telecomService).setIsOutgoingCallPermitted(false); + + assertThat(telecomService.isOutgoingCallPermitted(/* phoneAccountHandle= */ null)).isFalse(); + } + + @Test + @Config(minSdk = O) + public void isOutgoingCallPermitted_true() { + shadowOf(telecomService).setIsOutgoingCallPermitted(true); + + assertThat(telecomService.isOutgoingCallPermitted(/* phoneAccountHandle= */ null)).isTrue(); + } + private static PhoneAccountHandle createHandle(String id) { return new PhoneAccountHandle( new ComponentName(ApplicationProvider.getApplicationContext(), TestConnectionService.class), diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowTelephonyManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowTelephonyManagerTest.java index c3535c043..ead36aeea 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowTelephonyManagerTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowTelephonyManagerTest.java @@ -1530,4 +1530,21 @@ public class ShadowTelephonyManagerTest { IllegalStateException.class, () -> shadowTelephonyManager.setCarrierRestrictionRules(new Object())); } + + @Test + @Config(minSdk = Q) + public void rebootModem_rebootsModem() { + shadowOf((Application) ApplicationProvider.getApplicationContext()) + .grantPermissions(permission.MODIFY_PHONE_STATE); + + shadowTelephonyManager.rebootModem(); + + assertThat(shadowTelephonyManager.getModemRebootCount()).isEqualTo(1); + } + + @Test() + @Config(minSdk = Q) + public void rebootModem_noModifyPhoneStatePermission_throwsSecurityException() { + assertThrows(SecurityException.class, () -> shadowTelephonyManager.rebootModem()); + } } diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowThemeTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowThemeTest.java index ff07540d8..7c7047d66 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowThemeTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowThemeTest.java @@ -16,7 +16,6 @@ import android.view.View; import android.widget.Button; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; -import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -35,11 +34,6 @@ public class ShadowThemeTest { resources = ApplicationProvider.getApplicationContext().getResources(); } - @After - public void tearDown() { - ShadowLegacyAssetManager.strictErrors = false; - } - @Test public void whenExplicitlySetOnActivity_afterSetContentView_activityGetsThemeFromActivityInManifest() { diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowUIModeManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowUIModeManagerTest.java index 72c5e52e7..11326af57 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowUIModeManagerTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowUIModeManagerTest.java @@ -12,7 +12,6 @@ import android.app.UiModeManager; import android.content.Context; import android.content.pm.PackageInfo; import android.content.res.Configuration; -import android.os.Build.VERSION_CODES; import android.provider.Settings; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -25,7 +24,6 @@ import org.robolectric.shadow.api.Shadow; /** */ @RunWith(AndroidJUnit4.class) -@Config(minSdk = VERSION_CODES.LOLLIPOP) public class ShadowUIModeManagerTest { private Context context; private UiModeManager uiModeManager; diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowVirtualDeviceManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowVirtualDeviceManagerTest.java index dcc5c4a1c..4b798d3b9 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowVirtualDeviceManagerTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowVirtualDeviceManagerTest.java @@ -206,7 +206,11 @@ public class ShadowVirtualDeviceManagerTest { new VirtualMouseRelativeEvent.Builder().setRelativeX(0.1f).setRelativeY(0.1f).build(); VirtualMouse virtualMouse = - virtualDevice.createVirtualMouse(new VirtualMouseConfig.Builder().build()); + virtualDevice.createVirtualMouse( + new VirtualMouseConfig.Builder() + .setAssociatedDisplayId(0) + .setInputDeviceName("mouse") + .build()); virtualMouse.sendButtonEvent(buttonDownEvent); virtualMouse.sendButtonEvent(buttonUpEvent); virtualMouse.sendScrollEvent(scrollEvent); @@ -236,7 +240,10 @@ public class ShadowVirtualDeviceManagerTest { VirtualTouchscreen virtualTouchscreen = virtualDevice.createVirtualTouchscreen( - new VirtualTouchscreenConfig.Builder(DISPLAY_WIDTH, DISPLAY_HEIGHT).build()); + new VirtualTouchscreenConfig.Builder(DISPLAY_WIDTH, DISPLAY_HEIGHT) + .setAssociatedDisplayId(0) + .setInputDeviceName("touchscreen") + .build()); virtualTouchscreen.sendTouchEvent(virtualTouchEvent); assertThat(virtualTouchscreen).isNotNull(); @@ -258,7 +265,11 @@ public class ShadowVirtualDeviceManagerTest { .build(); VirtualKeyboard virtualKeyboard = - virtualDevice.createVirtualKeyboard(new VirtualKeyboardConfig.Builder().build()); + virtualDevice.createVirtualKeyboard( + new VirtualKeyboardConfig.Builder() + .setAssociatedDisplayId(0) + .setInputDeviceName("keyboard") + .build()); virtualKeyboard.sendKeyEvent(keyEvent1); virtualKeyboard.sendKeyEvent(keyEvent2); @@ -273,12 +284,23 @@ public class ShadowVirtualDeviceManagerTest { virtualDeviceManager.createVirtualDevice( 0, new VirtualDeviceParams.Builder().setName("foo").build()); VirtualKeyboard virtualKeyboard = - virtualDevice.createVirtualKeyboard(new VirtualKeyboardConfig.Builder().build()); + virtualDevice.createVirtualKeyboard( + new VirtualKeyboardConfig.Builder() + .setAssociatedDisplayId(0) + .setInputDeviceName("keyboard") + .build()); VirtualTouchscreen virtualTouchscreen = virtualDevice.createVirtualTouchscreen( - new VirtualTouchscreenConfig.Builder(DISPLAY_WIDTH, DISPLAY_HEIGHT).build()); + new VirtualTouchscreenConfig.Builder(DISPLAY_WIDTH, DISPLAY_HEIGHT) + .setAssociatedDisplayId(1) + .setInputDeviceName("touchscreen") + .build()); VirtualMouse virtualMouse = - virtualDevice.createVirtualMouse(new VirtualMouseConfig.Builder().build()); + virtualDevice.createVirtualMouse( + new VirtualMouseConfig.Builder() + .setAssociatedDisplayId(2) + .setInputDeviceName("mouse") + .build()); virtualKeyboard.close(); virtualTouchscreen.close(); diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowWifiManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowWifiManagerTest.java index 1d376a432..685614138 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowWifiManagerTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowWifiManagerTest.java @@ -1,7 +1,6 @@ package org.robolectric.shadows; import static android.net.wifi.WifiManager.SCAN_RESULTS_AVAILABLE_ACTION; -import static android.os.Build.VERSION_CODES.LOLLIPOP; import static android.os.Build.VERSION_CODES.Q; import static android.os.Build.VERSION_CODES.R; import static android.os.Build.VERSION_CODES.S; @@ -40,7 +39,6 @@ import android.net.wifi.WifiManager.PnoScanResultsCallback; import android.net.wifi.WifiNetworkSpecifier; import android.net.wifi.WifiSsid; import android.net.wifi.WifiUsabilityStatsEntry; -import android.os.Build; import android.util.Pair; import androidx.test.ext.junit.runners.AndroidJUnit4; import java.util.ArrayList; @@ -56,7 +54,6 @@ import org.mockito.ArgumentCaptor; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; import org.robolectric.shadow.api.Shadow; -import org.robolectric.util.ReflectionHelpers; import org.robolectric.versioning.AndroidVersions.U; @RunWith(AndroidJUnit4.class) @@ -75,19 +72,11 @@ public class ShadowWifiManagerTest { @Test public void setWifiInfo_shouldUpdateWifiInfo() { - WifiInfo wifiInfo = newWifiInfo(); + WifiInfo wifiInfo = new WifiInfo(); shadowOf(wifiManager).setConnectionInfo(wifiInfo); assertThat(wifiManager.getConnectionInfo()).isSameInstanceAs(wifiInfo); } - private static WifiInfo newWifiInfo() { - if (RuntimeEnvironment.getApiLevel() >= LOLLIPOP) { - return new WifiInfo(); - } else { - return ReflectionHelpers.callConstructor(WifiInfo.class); - } - } - @Test public void setWifiEnabled_shouldThrowSecurityExceptionWhenAccessWifiStatePermissionNotGranted() { shadowOf(wifiManager).setAccessWifiStatePermission(false); @@ -358,7 +347,6 @@ public class ShadowWifiManagerTest { } @Test - @Config(minSdk = Build.VERSION_CODES.LOLLIPOP) public void getPrivilegedConfiguredNetworks_shouldReturnConfiguredNetworks() { WifiConfiguration wifiConfiguration = new WifiConfiguration(); wifiConfiguration.networkId = 123; diff --git a/robolectric/src/test/java/org/robolectric/shadows/StreamConfigurationMapBuilderTest.java b/robolectric/src/test/java/org/robolectric/shadows/StreamConfigurationMapBuilderTest.java index 2d3a04b06..da614ac41 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/StreamConfigurationMapBuilderTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/StreamConfigurationMapBuilderTest.java @@ -15,7 +15,6 @@ import org.junit.runner.RunWith; import org.robolectric.annotation.Config; /** Tests for {@link StreamConfigurationMapBuilder}. */ -@Config(minSdk = VERSION_CODES.LOLLIPOP) @RunWith(AndroidJUnit4.class) public class StreamConfigurationMapBuilderTest { @Test diff --git a/robolectric/src/test/java/org/robolectric/shadows/XmlPullParserTest.java b/robolectric/src/test/java/org/robolectric/shadows/XmlPullParserTest.java index b5008287b..339a2e55a 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/XmlPullParserTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/XmlPullParserTest.java @@ -15,7 +15,6 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.R; import org.robolectric.Robolectric; -import org.robolectric.RuntimeEnvironment; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -67,10 +66,8 @@ http://schemas.android.com/apk/res-auto:title(resId=2130771971) type=CDATA: valu ":id" ); - if (!RuntimeEnvironment.useLegacyResources()) { - // doesn't work in legacy mode, sorry - assertAttribute(parser, - ANDROID_NS, "id", android.R.attr.id, "@" + android.R.id.text1, android.R.id.text1); + assertAttribute( + parser, ANDROID_NS, "id", android.R.attr.id, "@" + android.R.id.text1, android.R.id.text1); assertAttribute(parser, ANDROID_NS, "height", android.R.attr.height, "@" + android.R.dimen.app_icon_size, android.R.dimen.app_icon_size); @@ -78,7 +75,7 @@ http://schemas.android.com/apk/res-auto:title(resId=2130771971) type=CDATA: valu ANDROID_NS, "width", android.R.attr.width, "1234.0px", -1); assertThat(parser.getAttributeResourceValue(null, "style", /*defaultValue=*/ -1)) .isEqualTo(android.R.style.TextAppearance_Small); - } + assertAttribute(parser, ANDROID_NS, "title", android.R.attr.title, "Android Title", -1); assertAttribute(parser, @@ -86,10 +83,8 @@ http://schemas.android.com/apk/res-auto:title(resId=2130771971) type=CDATA: valu assertAttribute(parser, AUTO_NS, "title", R.attr.title, "App Title", -1); - if (!RuntimeEnvironment.useLegacyResources()) { - // doesn't work in legacy mode, sorry - assertThat(parser.getStyleAttribute()).isEqualTo(android.R.style.TextAppearance_Small); - } + assertThat(parser.getStyleAttribute()).isEqualTo(android.R.style.TextAppearance_Small); + assertThat(parser.getIdAttributeResourceValue(/*defaultValue=*/ -1)) .isEqualTo(android.R.id.text2); assertThat(parser.getClassAttribute()).isEqualTo("none"); diff --git a/shadows/framework/src/main/java/org/robolectric/RuntimeEnvironment.java b/shadows/framework/src/main/java/org/robolectric/RuntimeEnvironment.java index c262e889b..da2a04e6a 100644 --- a/shadows/framework/src/main/java/org/robolectric/RuntimeEnvironment.java +++ b/shadows/framework/src/main/java/org/robolectric/RuntimeEnvironment.java @@ -315,14 +315,4 @@ public class RuntimeEnvironment { public static Path getAndroidFrameworkJarPath() { return RuntimeEnvironment.androidFrameworkJar; } - - /** - * Internal only. - * - * @deprecated Do not use. - */ - @Deprecated - public static boolean useLegacyResources() { - return false; - } } diff --git a/shadows/framework/src/main/java/org/robolectric/android/ConfigurationV25.java b/shadows/framework/src/main/java/org/robolectric/android/ConfigurationV25.java index da0c0f8ea..ff5bb7c0b 100644 --- a/shadows/framework/src/main/java/org/robolectric/android/ConfigurationV25.java +++ b/shadows/framework/src/main/java/org/robolectric/android/ConfigurationV25.java @@ -60,18 +60,8 @@ public class ConfigurationV25 { return sb.toString(); } - - /** - * Returns a string representation of the configuration that can be parsed - * by build tools (like AAPT). - * - * @hide - */ - public static String resourceQualifierString(Configuration config, DisplayMetrics displayMetrics) { - return resourceQualifierString(config, displayMetrics, true); - } - - public static String resourceQualifierString(Configuration config, DisplayMetrics displayMetrics, boolean includeSdk) { + public static String resourceQualifierString( + Configuration config, DisplayMetrics displayMetrics) { ArrayList<String> parts = new ArrayList<String>(); if (config.mcc != 0) { @@ -325,10 +315,6 @@ public class ConfigurationV25 { break; } - if (includeSdk) { - parts.add("v" + Build.VERSION.RESOURCES_SDK_INT); - } - return TextUtils.join("-", parts); } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/HardwareRenderingScreenshot.java b/shadows/framework/src/main/java/org/robolectric/shadows/HardwareRenderingScreenshot.java index 63e9d7bd8..886c2a807 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/HardwareRenderingScreenshot.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/HardwareRenderingScreenshot.java @@ -15,7 +15,10 @@ import android.os.Build.VERSION_CODES; import android.util.DisplayMetrics; import android.view.Surface; import android.view.View; +import android.view.ViewRootImpl; import com.android.internal.R; +import com.google.common.base.Preconditions; +import java.util.WeakHashMap; import org.robolectric.annotation.GraphicsMode; import org.robolectric.util.ReflectionHelpers; @@ -25,6 +28,11 @@ import org.robolectric.util.ReflectionHelpers; */ public final class HardwareRenderingScreenshot { + // It is important to reuse HardwareRenderer objects, and ensure that after a HardwareRenderer is + // collected, no associated views in the same View hierarchy will be rendered as well. + private static final WeakHashMap<ViewRootImpl, HardwareRenderer> hardwareRenderers = + new WeakHashMap<>(); + static final String PIXEL_COPY_RENDER_MODE = "robolectric.pixelCopyRenderMode"; private HardwareRenderingScreenshot() {} @@ -34,10 +42,11 @@ public final class HardwareRenderingScreenshot { * the presence of the {@link #USE_HARDWARE_RENDERER_NATIVE_ENV} property, and the {@link * GraphicsMode}. */ - static boolean canTakeScreenshot() { + static boolean canTakeScreenshot(View view) { return VERSION.SDK_INT >= VERSION_CODES.S && "hardware".equalsIgnoreCase(System.getProperty(PIXEL_COPY_RENDER_MODE, "")) - && ShadowView.useRealGraphics(); + && ShadowView.useRealGraphics() + && view.canHaveDisplayList(); } /** @@ -58,8 +67,10 @@ public final class HardwareRenderingScreenshot { // - ImageReader is configured as RGBA_8888. // - However the native libs/hwui/pipeline/skia/SkiaHostPipeline.cpp always treats // the buffer as BGRA_8888, thus matching what the Android Bitmap object requires. - - HardwareRenderer renderer = new HardwareRenderer(); + ViewRootImpl viewRootImpl = view.getViewRootImpl(); + Preconditions.checkNotNull(viewRootImpl, "View not attached"); + HardwareRenderer renderer = + hardwareRenderers.computeIfAbsent(viewRootImpl, k -> new HardwareRenderer()); Surface surface = imageReader.getSurface(); renderer.setSurface(surface); Image nativeImage = imageReader.acquireNextImage(); diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ResourceModeShadowPicker.java b/shadows/framework/src/main/java/org/robolectric/shadows/ResourceModeShadowPicker.java index 4af12cd58..416f34d3d 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ResourceModeShadowPicker.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ResourceModeShadowPicker.java @@ -7,16 +7,13 @@ import org.robolectric.shadow.api.ShadowPicker; public class ResourceModeShadowPicker<T> implements ShadowPicker<T> { - private Class<? extends T> legacyShadowClass; private Class<? extends T> binaryShadowClass; private Class<? extends T> binary9ShadowClass; private Class<? extends T> binary10ShadowClass; private Class<? extends T> binary14ShadowClass; - public ResourceModeShadowPicker(Class<? extends T> legacyShadowClass, - Class<? extends T> binaryShadowClass, - Class<? extends T> binary9ShadowClass) { - this.legacyShadowClass = legacyShadowClass; + public ResourceModeShadowPicker( + Class<? extends T> binaryShadowClass, Class<? extends T> binary9ShadowClass) { this.binaryShadowClass = binaryShadowClass; this.binary9ShadowClass = binary9ShadowClass; this.binary10ShadowClass = binary9ShadowClass; @@ -24,12 +21,10 @@ public class ResourceModeShadowPicker<T> implements ShadowPicker<T> { } public ResourceModeShadowPicker( - Class<? extends T> legacyShadowClass, Class<? extends T> binaryShadowClass, Class<? extends T> binary9ShadowClass, Class<? extends T> binary10ShadowClass, Class<? extends T> binary14ShadowClass) { - this.legacyShadowClass = legacyShadowClass; this.binaryShadowClass = binaryShadowClass; this.binary9ShadowClass = binary9ShadowClass; this.binary10ShadowClass = binary10ShadowClass; @@ -38,18 +33,14 @@ public class ResourceModeShadowPicker<T> implements ShadowPicker<T> { @Override public Class<? extends T> pickShadowClass() { - if (ShadowAssetManager.useLegacy()) { - return legacyShadowClass; + if (RuntimeEnvironment.getApiLevel() > VERSION_CODES.TIRAMISU) { + return binary14ShadowClass; + } else if (RuntimeEnvironment.getApiLevel() >= Build.VERSION_CODES.Q) { + return binary10ShadowClass; + } else if (RuntimeEnvironment.getApiLevel() >= Build.VERSION_CODES.P) { + return binary9ShadowClass; } else { - if (RuntimeEnvironment.getApiLevel() > VERSION_CODES.TIRAMISU) { - return binary14ShadowClass; - } else if (RuntimeEnvironment.getApiLevel() >= Build.VERSION_CODES.Q) { - return binary10ShadowClass; - } else if (RuntimeEnvironment.getApiLevel() >= Build.VERSION_CODES.P) { - return binary9ShadowClass; - } else { - return binaryShadowClass; - } + return binaryShadowClass; } } } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowActivityManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowActivityManager.java index 17516f9f8..4004e8d02 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowActivityManager.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowActivityManager.java @@ -25,6 +25,7 @@ import android.os.UserHandle; import android.util.ArrayMap; import android.util.SparseIntArray; import com.google.common.base.Preconditions; +import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Deque; @@ -393,66 +394,79 @@ public class ShadowActivityManager { return new ApplicationExitInfoBuilder(); } + @CanIgnoreReturnValue public ApplicationExitInfoBuilder setDefiningUid(int uid) { instance.setDefiningUid(uid); return this; } + @CanIgnoreReturnValue public ApplicationExitInfoBuilder setDescription(String description) { instance.setDescription(description); return this; } + @CanIgnoreReturnValue public ApplicationExitInfoBuilder setImportance(int importance) { instance.setImportance(importance); return this; } + @CanIgnoreReturnValue public ApplicationExitInfoBuilder setPackageUid(int packageUid) { instance.setPackageUid(packageUid); return this; } + @CanIgnoreReturnValue public ApplicationExitInfoBuilder setPid(int pid) { instance.setPid(pid); return this; } + @CanIgnoreReturnValue public ApplicationExitInfoBuilder setProcessName(String processName) { instance.setProcessName(processName); return this; } + @CanIgnoreReturnValue public ApplicationExitInfoBuilder setProcessStateSummary(byte[] processStateSummary) { instance.setProcessStateSummary(processStateSummary); return this; } + @CanIgnoreReturnValue public ApplicationExitInfoBuilder setPss(long pss) { instance.setPss(pss); return this; } + @CanIgnoreReturnValue public ApplicationExitInfoBuilder setRealUid(int realUid) { instance.setRealUid(realUid); return this; } + @CanIgnoreReturnValue public ApplicationExitInfoBuilder setReason(int reason) { instance.setReason(reason); return this; } + @CanIgnoreReturnValue public ApplicationExitInfoBuilder setRss(long rss) { instance.setRss(rss); return this; } + @CanIgnoreReturnValue public ApplicationExitInfoBuilder setStatus(int status) { instance.setStatus(status); return this; } + @CanIgnoreReturnValue public ApplicationExitInfoBuilder setTimestamp(long timestamp) { instance.setTimestamp(timestamp); return this; diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowApkAssets.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowApkAssets.java index c7177518d..219233da1 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowApkAssets.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowApkAssets.java @@ -8,7 +8,7 @@ abstract public class ShadowApkAssets { public static class Picker extends ResourceModeShadowPicker<ShadowApkAssets> { public Picker() { - super(ShadowLegacyApkAssets.class, null, ShadowArscApkAssets9.class); + super(null, ShadowArscApkAssets9.class); } } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowApplicationPackageManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowApplicationPackageManager.java index ffe019520..2daa2dd4a 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowApplicationPackageManager.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowApplicationPackageManager.java @@ -1029,14 +1029,6 @@ public class ShadowApplicationPackageManager extends ShadowPackageManager { } Resources resources = null; - if (RuntimeEnvironment.useLegacyResources() - && (applicationInfo.publicSourceDir == null - || !new File(applicationInfo.publicSourceDir).exists())) { - // In legacy mode, the underlying getResourcesForApplication implementation just returns an - // empty Resources instance in this case. - throw new NameNotFoundException(applicationInfo.packageName); - } - try { resources = reflector(ReflectorApplicationPackageManager.class, realObject) diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowArscAssetManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowArscAssetManager.java index d482bba1c..07ce84c29 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowArscAssetManager.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowArscAssetManager.java @@ -84,8 +84,9 @@ public class ShadowArscAssetManager extends ShadowAssetManager.ArscBase { @Resetter public static void reset() { - // todo: ShadowPicker doesn't discriminate properly between concrete shadow classes for resetters... - if (!useLegacy() && RuntimeEnvironment.getApiLevel() < P) { + // todo: ShadowPicker doesn't discriminate properly between concrete shadow classes for + // resetters... + if (RuntimeEnvironment.getApiLevel() < P) { reflector(_AssetManager_.class).setSystem(null); // NATIVE_THEME_REGISTRY.clear(); // nativeXMLParserRegistry.clear(); // todo: shouldn't these be freed explicitly? [yes! xw] diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowArscAssetManager10.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowArscAssetManager10.java index bc5200e97..a3e2432b9 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowArscAssetManager10.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowArscAssetManager10.java @@ -213,7 +213,7 @@ public class ShadowArscAssetManager10 extends ShadowAssetManager.ArscBase { public static void reset() { // todo: ShadowPicker doesn't discriminate properly between concrete shadow classes for // resetters... - if (!useLegacy() && RuntimeEnvironment.getApiLevel() >= P) { + if (RuntimeEnvironment.getApiLevel() >= P) { _AssetManager28_ _assetManagerStatic_ = reflector(_AssetManager28_.class); _assetManagerStatic_.setSystemApkAssetsSet(null); _assetManagerStatic_.setSystemApkAssets(null); diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowArscAssetManager9.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowArscAssetManager9.java index 9bbe660c7..44787cf0a 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowArscAssetManager9.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowArscAssetManager9.java @@ -207,7 +207,7 @@ public class ShadowArscAssetManager9 extends ShadowAssetManager.ArscBase { public static void reset() { // todo: ShadowPicker doesn't discriminate properly between concrete shadow classes for // resetters... - if (!useLegacy() && RuntimeEnvironment.getApiLevel() >= P) { + if (RuntimeEnvironment.getApiLevel() >= P) { _AssetManager28_ _assetManagerStatic_ = reflector(_AssetManager28_.class); _assetManagerStatic_.setSystemApkAssetsSet(null); _assetManagerStatic_.setSystemApkAssets(null); diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowArscResourcesImpl.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowArscResourcesImpl.java index 6ffc578a8..9012341b8 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowArscResourcesImpl.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowArscResourcesImpl.java @@ -4,7 +4,6 @@ import static android.os.Build.VERSION_CODES.M; import static android.os.Build.VERSION_CODES.N; import static android.os.Build.VERSION_CODES.N_MR1; import static android.os.Build.VERSION_CODES.O; -import static org.robolectric.shadows.ShadowAssetManager.legacyShadowOf; import static org.robolectric.util.reflector.Reflector.reflector; import android.content.res.AssetFileDescriptor; @@ -21,18 +20,9 @@ import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.List; -import java.util.Locale; -import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; import org.robolectric.annotation.RealObject; -import org.robolectric.annotation.Resetter; -import org.robolectric.res.Plural; -import org.robolectric.res.PluralRules; -import org.robolectric.res.ResName; -import org.robolectric.res.ResType; -import org.robolectric.res.ResourceTable; -import org.robolectric.res.TypedResource; import org.robolectric.shadows.ShadowResourcesImpl.Picker; import org.robolectric.util.reflector.Direct; import org.robolectric.util.reflector.ForType; @@ -48,13 +38,6 @@ public class ShadowArscResourcesImpl extends ShadowResourcesImpl { @RealObject ResourcesImpl realResourcesImpl; - @Resetter - public static void reset() { - if (RuntimeEnvironment.useLegacyResources()) { - ShadowResourcesImpl.reset(); - } - } - private static List<LongSparseArray<?>> obtainResettableArrays() { List<LongSparseArray<?>> resettableArrays = new ArrayList<>(); Field[] allFields = Resources.class.getDeclaredFields(); @@ -74,53 +57,6 @@ public class ShadowArscResourcesImpl extends ShadowResourcesImpl { return resettableArrays; } - @Implementation(maxSdk = M) - public String getQuantityString(int id, int quantity, Object... formatArgs) throws Resources.NotFoundException { - String raw = getQuantityString(id, quantity); - return String.format(Locale.ENGLISH, raw, formatArgs); - } - - @Implementation(maxSdk = M) - public String getQuantityString(int resId, int quantity) throws Resources.NotFoundException { - ShadowLegacyAssetManager shadowAssetManager = legacyShadowOf(realResourcesImpl.getAssets()); - - TypedResource typedResource = shadowAssetManager.getResourceTable().getValue(resId, shadowAssetManager.config); - if (typedResource != null && typedResource instanceof PluralRules) { - PluralRules pluralRules = (PluralRules) typedResource; - Plural plural = pluralRules.find(quantity); - - if (plural == null) { - return null; - } - - TypedResource<?> resolvedTypedResource = - shadowAssetManager.resolve( - new TypedResource<>( - plural.getString(), ResType.CHAR_SEQUENCE, pluralRules.getXmlContext()), - shadowAssetManager.config, - resId); - return resolvedTypedResource == null ? null : resolvedTypedResource.asString(); - } else { - return null; - } - } - - @Implementation(maxSdk = M) - public InputStream openRawResource(int id) throws Resources.NotFoundException { - if (false) { - ShadowLegacyAssetManager shadowAssetManager = legacyShadowOf(realResourcesImpl.getAssets()); - ResourceTable resourceTable = shadowAssetManager.getResourceTable(); - InputStream inputStream = resourceTable.getRawValue(id, shadowAssetManager.config); - if (inputStream == null) { - throw newNotFoundException(id); - } else { - return inputStream; - } - } else { - return reflector(ResourcesImplReflector.class, realResourcesImpl).openRawResource(id); - } - } - /** * Since {@link AssetFileDescriptor}s are not yet supported by Robolectric, {@code null} will * be returned if the resource is found. If the resource cannot be found, {@link Resources.NotFoundException} will @@ -128,7 +64,9 @@ public class ShadowArscResourcesImpl extends ShadowResourcesImpl { */ @Implementation(maxSdk = M) public AssetFileDescriptor openRawResourceFd(int id) throws Resources.NotFoundException { - InputStream inputStream = openRawResource(id); + InputStream inputStream = + reflector(ResourcesImplReflector.class, realResourcesImpl).openRawResource(id); + ; if (!(inputStream instanceof FileInputStream)) { // todo fixme return null; @@ -143,13 +81,7 @@ public class ShadowArscResourcesImpl extends ShadowResourcesImpl { } private Resources.NotFoundException newNotFoundException(int id) { - ResourceTable resourceTable = legacyShadowOf(realResourcesImpl.getAssets()).getResourceTable(); - ResName resName = resourceTable.getResName(id); - if (resName == null) { - return new Resources.NotFoundException("resource ID #0x" + Integer.toHexString(id)); - } else { - return new Resources.NotFoundException(resName.getFullyQualifiedName()); - } + return new Resources.NotFoundException("resource ID #0x" + Integer.toHexString(id)); } @Implementation(maxSdk = N_MR1) diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAssetInputStream.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAssetInputStream.java index 10de51eb8..77a177a58 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAssetInputStream.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAssetInputStream.java @@ -22,19 +22,13 @@ public abstract class ShadowAssetInputStream { from(long.class, assetPtr)); ShadowAssetInputStream sais = Shadow.extract(ais); - if (sais instanceof ShadowLegacyAssetInputStream) { - ShadowLegacyAssetInputStream slais = (ShadowLegacyAssetInputStream) sais; - slais.setDelegate(delegateInputStream); - slais.setNinePatch(asset.isNinePatch()); - } return ais; } public static class Picker extends ResourceModeShadowPicker<ShadowAssetInputStream> { public Picker() { - super(ShadowLegacyAssetInputStream.class, ShadowArscAssetInputStream.class, - ShadowArscAssetInputStream.class); + super(ShadowArscAssetInputStream.class, ShadowArscAssetInputStream.class); } } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAssetManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAssetManager.java index 37ba3ec4f..74c9ad1e7 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAssetManager.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAssetManager.java @@ -5,6 +5,7 @@ import android.content.res.ApkAssets; import android.content.res.AssetManager; import android.util.ArraySet; import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.Ordering; import java.nio.file.Path; import java.util.Collection; import java.util.List; @@ -13,7 +14,6 @@ import org.robolectric.res.android.AssetPath; import org.robolectric.res.android.CppAssetManager; import org.robolectric.res.android.ResTable; import org.robolectric.res.android.String8; -import org.robolectric.shadow.api.Shadow; import org.robolectric.util.reflector.Accessor; import org.robolectric.util.reflector.Direct; import org.robolectric.util.reflector.ForType; @@ -21,11 +21,24 @@ import org.robolectric.util.reflector.Static; abstract public class ShadowAssetManager { + public static final Ordering<String> ATTRIBUTE_TYPE_PRECIDENCE = + Ordering.explicit( + "reference", + "color", + "boolean", + "integer", + "fraction", + "dimension", + "float", + "enum", + "flag", + "flags", + "string"); + public static class Picker extends ResourceModeShadowPicker<ShadowAssetManager> { public Picker() { super( - ShadowLegacyAssetManager.class, ShadowArscAssetManager.class, ShadowArscAssetManager9.class, ShadowArscAssetManager10.class, @@ -33,22 +46,6 @@ abstract public class ShadowAssetManager { } } - /** - * @deprecated Avoid use. - */ - @Deprecated - public static boolean useLegacy() { - return RuntimeEnvironment.useLegacyResources(); - } - - /** - * @deprecated Avoid use. - */ - @Deprecated - static ShadowLegacyAssetManager legacyShadowOf(AssetManager assetManager) { - return Shadow.extract(assetManager); - } - abstract Collection<Path> getAllAssetDirs(); @VisibleForTesting diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothLeAdvertiser.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothLeAdvertiser.java index c4e614dd5..5853071dd 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothLeAdvertiser.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothLeAdvertiser.java @@ -4,11 +4,13 @@ import static android.os.Build.VERSION_CODES.O; import static android.os.Build.VERSION_CODES.R; import static android.os.Build.VERSION_CODES.S; import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE; +import static org.robolectric.util.reflector.Reflector.reflector; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothGattServer; import android.bluetooth.BluetoothUuid; +import android.bluetooth.IBluetoothGatt; import android.bluetooth.IBluetoothManager; import android.bluetooth.le.AdvertiseCallback; import android.bluetooth.le.AdvertiseData; @@ -26,14 +28,16 @@ import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; +import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; import org.robolectric.annotation.ReflectorObject; import org.robolectric.util.PerfStatsCollector; import org.robolectric.util.ReflectionHelpers; -import org.robolectric.util.ReflectionHelpers.ClassParameter; +import org.robolectric.util.reflector.Constructor; import org.robolectric.util.reflector.Direct; import org.robolectric.util.reflector.ForType; +import org.robolectric.versioning.AndroidVersions.V; /** Shadow implementation of {@link BluetoothLeAdvertiser}. */ @Implements(value = BluetoothLeAdvertiser.class, minSdk = O) @@ -231,16 +235,28 @@ public class ShadowBluetoothLeAdvertiser { return; } - AdvertisingSet advertisingSet = - ReflectionHelpers.callConstructor( - AdvertisingSet.class, - ClassParameter.from(int.class, advertiserId.getAndAdd(1)), - ClassParameter.from( - IBluetoothManager.class, - ReflectionHelpers.createNullProxy(IBluetoothManager.class)), - ClassParameter.from( - AttributionSource.class, - ReflectionHelpers.callInstanceMethod(bluetoothAdapter, "getAttributionSource"))); + AdvertisingSet advertisingSet; + if (RuntimeEnvironment.getApiLevel() >= V.SDK_INT) { + IBluetoothGatt gatt = + ReflectionHelpers.callInstanceMethod(bluetoothAdapter, "getBluetoothGatt"); + + advertisingSet = + reflector(AdvertisingSetReflector.class) + .__constructor__( + gatt == null ? ReflectionHelpers.createNullProxy(IBluetoothGatt.class) : gatt, + advertiserId.getAndAdd(1), + bluetoothAdapter, + bluetoothAdapter.getAttributionSource()); + } else { + advertisingSet = + reflector(AdvertisingSetReflector.class) + .__constructor__( + advertiserId.getAndAdd(1), + ReflectionHelpers.createNullProxy(IBluetoothManager.class), + (AttributionSource) + ReflectionHelpers.callInstanceMethod( + bluetoothAdapter, "getAttributionSource")); + } callback.onAdvertisingSetStarted( advertisingSet, parameters.getTxPowerLevel(), AdvertisingSetCallback.ADVERTISE_SUCCESS); @@ -343,4 +359,18 @@ public class ShadowBluetoothLeAdvertiser { @Direct void __constructor__(BluetoothAdapter bluetoothAdapter); } + + @ForType(AdvertisingSet.class) + interface AdvertisingSetReflector { + @Constructor + AdvertisingSet __constructor__( + IBluetoothGatt bluetoothGatt, + int advertiserId, + BluetoothAdapter bluetoothAdapter, + AttributionSource attributionSource); + + @Constructor + AdvertisingSet __constructor__( + int advertiserId, IBluetoothManager bluetoothManager, AttributionSource attributionSource); + } } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBuild.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBuild.java index 7b1516152..56a6b925e 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBuild.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBuild.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.LOLLIPOP; import static android.os.Build.VERSION_CODES.M; import static android.os.Build.VERSION_CODES.O; import static android.os.Build.VERSION_CODES.S; @@ -185,31 +184,28 @@ public class ShadowBuild { } /** - * Sets the value of the {@link Build#SUPPORTED_32_BIT_ABIS} field. Available in Android L+. + * Sets the value of the {@link Build#SUPPORTED_32_BIT_ABIS} field. * * <p>It will be reset for the next test. */ - @TargetApi(LOLLIPOP) public static void setSupported32BitAbis(String[] supported32BitAbis) { ReflectionHelpers.setStaticField(Build.class, "SUPPORTED_32_BIT_ABIS", supported32BitAbis); } /** - * Sets the value of the {@link Build#SUPPORTED_64_BIT_ABIS} field. Available in Android L+. + * Sets the value of the {@link Build#SUPPORTED_64_BIT_ABIS} field. * * <p>It will be reset for the next test. */ - @TargetApi(LOLLIPOP) public static void setSupported64BitAbis(String[] supported64BitAbis) { ReflectionHelpers.setStaticField(Build.class, "SUPPORTED_64_BIT_ABIS", supported64BitAbis); } /** - * Sets the value of the {@link Build#SUPPORTED_ABIS} field. Available in Android L+. + * Sets the value of the {@link Build#SUPPORTED_ABIS} field. * * <p>It will be reset for the next test. */ - @TargetApi(LOLLIPOP) public static void setSupportedAbis(String[] supportedAbis) { ReflectionHelpers.setStaticField(Build.class, "SUPPORTED_ABIS", supportedAbis); } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowCameraDeviceImpl.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowCameraDeviceImpl.java index dbcd46121..12a5c73b2 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowCameraDeviceImpl.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowCameraDeviceImpl.java @@ -20,7 +20,7 @@ import org.robolectric.shadow.api.Shadow; import org.robolectric.util.ReflectionHelpers; /** Shadow class for {@link CameraDeviceImpl} */ -@Implements(value = CameraDeviceImpl.class, minSdk = VERSION_CODES.LOLLIPOP, isInAndroidSdk = false) +@Implements(value = CameraDeviceImpl.class, isInAndroidSdk = false) public class ShadowCameraDeviceImpl { @RealObject private CameraDeviceImpl realObject; private boolean closed = false; diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowCameraManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowCameraManager.java index be1e29713..3b38a45a7 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowCameraManager.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowCameraManager.java @@ -33,9 +33,10 @@ import org.robolectric.util.ReflectionHelpers.ClassParameter; import org.robolectric.util.reflector.Accessor; import org.robolectric.util.reflector.ForType; import org.robolectric.versioning.AndroidVersions.U; +import org.robolectric.versioning.AndroidVersions.V; /** Shadow class for {@link CameraManager} */ -@Implements(value = CameraManager.class, minSdk = VERSION_CODES.LOLLIPOP) +@Implements(value = CameraManager.class) public class ShadowCameraManager { @RealObject private CameraManager realObject; @@ -83,7 +84,7 @@ public class ShadowCameraManager { } } - @Implementation(minSdk = U.SDK_INT) + @Implementation(minSdk = U.SDK_INT, maxSdk = U.SDK_INT) protected CameraDevice openCameraDeviceUserAsync( String cameraId, CameraDevice.StateCallback callback, @@ -94,6 +95,17 @@ public class ShadowCameraManager { return openCameraDeviceUserAsync(cameraId, callback, executor, uid, oomScoreOffset); } + @Implementation(minSdk = V.SDK_INT) + protected CameraDevice openCameraDeviceUserAsync( + String cameraId, + CameraDevice.StateCallback callback, + Executor executor, + final int uid, + final int oomScoreOffset, + int rotationOverride) { + return openCameraDeviceUserAsync(cameraId, callback, executor, uid, oomScoreOffset); + } + @Implementation(minSdk = Build.VERSION_CODES.S, maxSdk = Build.VERSION_CODES.TIRAMISU) protected CameraDevice openCameraDeviceUserAsync( String cameraId, @@ -185,7 +197,7 @@ public class ShadowCameraManager { * CameraDevice.StateCallback#onDisconnected(CameraDevice)} will not be triggered by {@link * CameraManager#openCamera(String, StateCallback, Handler)}. */ - @Implementation(minSdk = VERSION_CODES.LOLLIPOP, maxSdk = VERSION_CODES.N) + @Implementation(maxSdk = VERSION_CODES.N) protected CameraDevice openCameraDeviceUserAsync( String cameraId, CameraDevice.StateCallback callback, Handler handler) throws CameraAccessException { @@ -329,9 +341,7 @@ public class ShadowCameraManager { } /** Shadow class for internal class CameraManager$CameraManagerGlobal */ - @Implements( - className = "android.hardware.camera2.CameraManager$CameraManagerGlobal", - minSdk = VERSION_CODES.LOLLIPOP) + @Implements(className = "android.hardware.camera2.CameraManager$CameraManagerGlobal") public static class ShadowCameraManagerGlobal { /** diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowCameraMetadataNative.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowCameraMetadataNative.java index f3cde0665..33184ab55 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowCameraMetadataNative.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowCameraMetadataNative.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.LOLLIPOP; import static android.os.Build.VERSION_CODES.Q; import android.hardware.camera2.impl.CameraMetadataNative; @@ -10,7 +9,6 @@ import org.robolectric.annotation.Implements; /** Shadow class for {@link CameraMetadataNative} */ @Implements( value = CameraMetadataNative.class, - minSdk = LOLLIPOP, maxSdk = Q, isInAndroidSdk = false) public class ShadowCameraMetadataNative { diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowCaptioningManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowCaptioningManager.java index 19762d193..203e43968 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowCaptioningManager.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowCaptioningManager.java @@ -1,72 +1,48 @@ package org.robolectric.shadows; -import android.annotation.NonNull; -import android.util.ArraySet; +import static android.os.Build.VERSION_CODES.TIRAMISU; +import static org.robolectric.util.reflector.Reflector.reflector; + +import android.content.ContentResolver; +import android.provider.Settings; import android.view.accessibility.CaptioningManager; -import android.view.accessibility.CaptioningManager.CaptioningChangeListener; -import java.util.Locale; -import javax.annotation.Nullable; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; +import org.robolectric.annotation.RealObject; +import org.robolectric.util.reflector.Accessor; +import org.robolectric.util.reflector.ForType; /** Shadow of {@link android.view.accessibility.CaptioningManager}. */ @Implements(CaptioningManager.class) public class ShadowCaptioningManager { - private float fontScale = 1; - private boolean isEnabled = false; - @Nullable private Locale locale; - - private final ArraySet<CaptioningChangeListener> listeners = new ArraySet<>(); - - /** Returns 1.0 as default or the most recent value passed to {@link #setFontScale()} */ - @Implementation(minSdk = 19) - protected float getFontScale() { - return fontScale; - } - - /** Sets the value to be returned by {@link CaptioningManager#getFontScale()} */ - public void setFontScale(float fontScale) { - this.fontScale = fontScale; + @RealObject private CaptioningManager realCaptioningManager; - for (CaptioningChangeListener captioningChangeListener : listeners) { - captioningChangeListener.onFontScaleChanged(fontScale); - } + @Implementation(minSdk = TIRAMISU) + protected void setSystemAudioCaptioningEnabled(boolean isEnabled) { + Settings.Secure.putInt( + getContentResolver(), Settings.Secure.ODI_CAPTIONS_ENABLED, isEnabled ? 1 : 0); } - /** Returns false or the most recent value passed to {@link #setEnabled(boolean)} */ - @Implementation(minSdk = 19) - protected boolean isEnabled() { - return isEnabled; + @Implementation(minSdk = TIRAMISU) + protected void setSystemAudioCaptioningUiEnabled(boolean isEnabled) { + Settings.Secure.putInt( + getContentResolver(), Settings.Secure.ODI_CAPTIONS_VOLUME_UI_ENABLED, isEnabled ? 1 : 0); } - /** Sets the value to be returned by {@link CaptioningManager#isEnabled()} */ - public void setEnabled(boolean isEnabled) { - this.isEnabled = isEnabled; + @Implementation(minSdk = TIRAMISU) + protected boolean isSystemAudioCaptioningUiEnabled() { + return Settings.Secure.getInt( + getContentResolver(), Settings.Secure.ODI_CAPTIONS_VOLUME_UI_ENABLED, 1) + == 1; } - @Implementation(minSdk = 19) - protected void addCaptioningChangeListener(@NonNull CaptioningChangeListener listener) { - listeners.add(listener); + private ContentResolver getContentResolver() { + return reflector(CaptioningManagerReflector.class, realCaptioningManager).getContentResolver(); } - @Implementation(minSdk = 19) - protected void removeCaptioningChangeListener(@NonNull CaptioningChangeListener listener) { - listeners.remove(listener); - } - - /** Returns null or the most recent value passed to {@link #setLocale(Locale)} */ - @Implementation(minSdk = 19) - @Nullable - protected Locale getLocale() { - return locale; - } - - /** Sets the value to be returned by {@link CaptioningManager#getLocale()} */ - public void setLocale(@Nullable Locale locale) { - this.locale = locale; - - for (CaptioningChangeListener captioningChangeListener : listeners) { - captioningChangeListener.onLocaleChanged(locale); - } + @ForType(CaptioningManager.class) + interface CaptioningManagerReflector { + @Accessor("mContentResolver") + ContentResolver getContentResolver(); } } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowCaptureRequestBuilder.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowCaptureRequestBuilder.java index 2d6e162a3..685563fa3 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowCaptureRequestBuilder.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowCaptureRequestBuilder.java @@ -2,7 +2,6 @@ package org.robolectric.shadows; import android.hardware.camera2.CaptureRequest; import android.hardware.camera2.CaptureRequest.Key; -import android.os.Build.VERSION_CODES; import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -10,7 +9,7 @@ import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; /** Shadow class for {@link CaptureRequest.Builder}. */ -@Implements(value = CaptureRequest.Builder.class, minSdk = VERSION_CODES.LOLLIPOP) +@Implements(value = CaptureRequest.Builder.class) public class ShadowCaptureRequestBuilder { private final Map<Key<?>, Object> characteristics = Collections.synchronizedMap(new HashMap<>()); diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowCaptureResult.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowCaptureResult.java index 239a71989..75b3dc0c9 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowCaptureResult.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowCaptureResult.java @@ -3,7 +3,6 @@ package org.robolectric.shadows; import android.annotation.Nullable; import android.hardware.camera2.CaptureResult; import android.hardware.camera2.CaptureResult.Key; -import android.os.Build.VERSION_CODES; import com.google.common.base.Preconditions; import java.util.HashMap; import java.util.Map; @@ -12,7 +11,7 @@ import org.robolectric.annotation.Implements; import org.robolectric.util.ReflectionHelpers; /** Shadow of {@link CaptureResult}. */ -@Implements(value = CaptureResult.class, minSdk = VERSION_CODES.LOLLIPOP) +@Implements(value = CaptureResult.class) public class ShadowCaptureResult { private final Map<Key<?>, Object> resultsKeyToValue = new HashMap<>(); diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowCardEmulation.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowCardEmulation.java index 3f6989a14..7f919e95a 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowCardEmulation.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowCardEmulation.java @@ -8,7 +8,6 @@ import android.content.ComponentName; import android.content.Context; import android.nfc.INfcCardEmulation; import android.nfc.cardemulation.CardEmulation; -import android.os.Build; import android.provider.Settings; import java.util.HashMap; import java.util.Map; @@ -35,13 +34,13 @@ public class ShadowCardEmulation { return service.equals(defaultServiceForCategoryMap.get(category)); } - @Implementation(minSdk = Build.VERSION_CODES.LOLLIPOP) + @Implementation public boolean setPreferredService(Activity activity, ComponentName service) { preferredService = service; return true; } - @Implementation(minSdk = Build.VERSION_CODES.LOLLIPOP) + @Implementation public boolean unsetPreferredService(Activity activity) { preferredService = null; return true; diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowCompatibility.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowCompatibility.java index 1dd630828..5f4baab11 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowCompatibility.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowCompatibility.java @@ -5,6 +5,9 @@ import static org.robolectric.util.reflector.Reflector.reflector; import android.compat.Compatibility; import android.compat.annotation.ChangeId; import android.os.Build.VERSION_CODES; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableMap; +import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; import org.robolectric.annotation.RealObject; @@ -12,25 +15,44 @@ import org.robolectric.util.reflector.Direct; import org.robolectric.util.reflector.ForType; import org.robolectric.util.reflector.Static; -/** - * Robolectric shadow to disable CALL_ACTIVITY_RESULT_BEFORE_RESUME using Compatibility's - * isChangeEnabled. - */ +/** Shadow for {@link Compatability}. */ @Implements(value = Compatibility.class, isInAndroidSdk = false) public class ShadowCompatibility { private static final long CALL_ACTIVITY_RESULT_BEFORE_RESUME = 78294732L; + private static final long ENFORCE_EDGE_TO_EDGE = 309578419L; + + private static final long ENABLE_CHECKING_TELEPHONY_FEATURES_FOR_VCN = 330902016; + + private static final ImmutableMap<Long, Integer> ENABLED_SINCE_TARGET_SDK = + ImmutableMap.of( + ENFORCE_EDGE_TO_EDGE, 35, + ENABLE_CHECKING_TELEPHONY_FEATURES_FOR_VCN, 35); + @RealObject protected static Compatibility realCompatibility; @Implementation(minSdk = VERSION_CODES.S_V2) protected static boolean isChangeEnabled(@ChangeId long changeId) { if (changeId == CALL_ACTIVITY_RESULT_BEFORE_RESUME) { return false; + } else if (ENABLED_SINCE_TARGET_SDK.containsKey(changeId)) { + int targetSdkVersion = getTargetSdkVersion(); + return isEnabled(changeId, targetSdkVersion); } return reflector(CompatibilityReflector.class).isChangeEnabled(changeId); } + @VisibleForTesting + static boolean isEnabled(long flag, int targetSdkVersion) { + int enabledSince = ENABLED_SINCE_TARGET_SDK.get(flag); + return targetSdkVersion >= enabledSince; + } + + private static int getTargetSdkVersion() { + return RuntimeEnvironment.getApplication().getApplicationInfo().targetSdkVersion; + } + /** Reflector interface for {@link Compatibility}'s isChangeEnabled function. */ @ForType(Compatibility.class) private interface CompatibilityReflector { diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowConnectivityManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowConnectivityManager.java index dbb580883..5ee1e305c 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowConnectivityManager.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowConnectivityManager.java @@ -1,11 +1,9 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.LOLLIPOP; import static android.os.Build.VERSION_CODES.M; import static android.os.Build.VERSION_CODES.N; import static android.os.Build.VERSION_CODES.O; import static android.os.Build.VERSION_CODES.S; -import static org.robolectric.RuntimeEnvironment.getApiLevel; import static org.robolectric.Shadows.shadowOf; import android.app.PendingIntent; @@ -66,20 +64,18 @@ public class ShadowConnectivityManager { this.activeNetworkInfo = mobile; - if (getApiLevel() >= LOLLIPOP) { - netIdToNetwork.put(NET_ID_WIFI, ShadowNetwork.newInstance(NET_ID_WIFI)); - netIdToNetwork.put(NET_ID_MOBILE, ShadowNetwork.newInstance(NET_ID_MOBILE)); - netIdToNetworkInfo.put(NET_ID_WIFI, wifi); - netIdToNetworkInfo.put(NET_ID_MOBILE, mobile); + netIdToNetwork.put(NET_ID_WIFI, ShadowNetwork.newInstance(NET_ID_WIFI)); + netIdToNetwork.put(NET_ID_MOBILE, ShadowNetwork.newInstance(NET_ID_MOBILE)); + netIdToNetworkInfo.put(NET_ID_WIFI, wifi); + netIdToNetworkInfo.put(NET_ID_MOBILE, mobile); - NetworkCapabilities wifiNetworkCapabilities = ShadowNetworkCapabilities.newInstance(); - shadowOf(wifiNetworkCapabilities).addTransportType(NetworkCapabilities.TRANSPORT_WIFI); - NetworkCapabilities mobileNetworkCapabilities = ShadowNetworkCapabilities.newInstance(); - shadowOf(mobileNetworkCapabilities).addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR); + NetworkCapabilities wifiNetworkCapabilities = ShadowNetworkCapabilities.newInstance(); + shadowOf(wifiNetworkCapabilities).addTransportType(NetworkCapabilities.TRANSPORT_WIFI); + NetworkCapabilities mobileNetworkCapabilities = ShadowNetworkCapabilities.newInstance(); + shadowOf(mobileNetworkCapabilities).addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR); - networkCapabilitiesMap.put(netIdToNetwork.get(NET_ID_WIFI), wifiNetworkCapabilities); - networkCapabilitiesMap.put(netIdToNetwork.get(NET_ID_MOBILE), mobileNetworkCapabilities); - } + networkCapabilitiesMap.put(netIdToNetwork.get(NET_ID_WIFI), wifiNetworkCapabilities); + networkCapabilitiesMap.put(netIdToNetwork.get(NET_ID_MOBILE), mobileNetworkCapabilities); defaultNetworkActive = true; } @@ -153,6 +149,12 @@ public class ShadowConnectivityManager { networkCallbacks.add(networkCallback); } + @Implementation(minSdk = O) + protected void registerDefaultNetworkCallback( + ConnectivityManager.NetworkCallback networkCallback, Handler handler) { + networkCallbacks.add(networkCallback); + } + @Implementation(minSdk = S) protected void registerBestMatchingNetworkCallback( NetworkRequest request, @@ -302,23 +304,14 @@ public class ShadowConnectivityManager { } public void setActiveNetworkInfo(NetworkInfo info) { - if (getApiLevel() >= LOLLIPOP) { - activeNetworkInfo = info; - if (info != null) { - networkTypeToNetworkInfo.put(info.getType(), info); - netIdToNetwork.put(info.getType(), ShadowNetwork.newInstance(info.getType())); - netIdToNetworkInfo.put(info.getType(), info); - } else { - networkTypeToNetworkInfo.clear(); - netIdToNetwork.clear(); - } + activeNetworkInfo = info; + if (info != null) { + networkTypeToNetworkInfo.put(info.getType(), info); + netIdToNetwork.put(info.getType(), ShadowNetwork.newInstance(info.getType())); + netIdToNetworkInfo.put(info.getType(), info); } else { - activeNetworkInfo = info; - if (info != null) { - networkTypeToNetworkInfo.put(info.getType(), info); - } else { - networkTypeToNetworkInfo.clear(); - } + networkTypeToNetworkInfo.clear(); + netIdToNetwork.clear(); } } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowContextImpl.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowContextImpl.java index af41db2df..69daa7c38 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowContextImpl.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowContextImpl.java @@ -24,7 +24,6 @@ import android.content.ServiceConnection; import android.content.SharedPreferences; import android.content.pm.ActivityInfo; import android.content.res.Configuration; -import android.os.Build; import android.os.Build.VERSION_CODES; import android.os.Bundle; import android.os.Environment; @@ -377,9 +376,7 @@ public class ShadowContextImpl { // This is a private method in ContextImpl so we copy the relevant portions of it here. @Implementation protected void validateServiceIntent(Intent service) { - if (service.getComponent() == null - && service.getPackage() == null - && realContextImpl.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP) { + if (service.getComponent() == null && service.getPackage() == null) { throw new IllegalArgumentException("Service Intent must be explicit: " + service); } } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDeviceConfig.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDeviceConfig.java index 4bca7e9f2..0bc03047a 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDeviceConfig.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDeviceConfig.java @@ -4,20 +4,12 @@ import android.os.Build; import android.provider.DeviceConfig; import java.util.Map; import org.robolectric.RuntimeEnvironment; -import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; import org.robolectric.annotation.Resetter; import org.robolectric.util.ReflectionHelpers; @Implements(value = DeviceConfig.class, isInAndroidSdk = false, minSdk = Build.VERSION_CODES.Q) public class ShadowDeviceConfig { - - @Implementation - protected static String getProperty(String namespace, String name) { - // avoid call to Settings.Config - return null; - } - @Resetter public static void reset() { Object lock = ReflectionHelpers.getStaticField(DeviceConfig.class, "sLock"); diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDisplayManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDisplayManager.java index 876df13d6..bb60781b1 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDisplayManager.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDisplayManager.java @@ -147,9 +147,7 @@ public class ShadowDisplayManager { displayInfo.logicalDensityDpi = displayMetrics.densityDpi; displayInfo.physicalXDpi = displayMetrics.densityDpi; displayInfo.physicalYDpi = displayMetrics.densityDpi; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - displayInfo.state = Display.STATE_ON; - } + displayInfo.state = Display.STATE_ON; return displayInfo; } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowEGL14.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowEGL14.java index 4f488b571..d83733082 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowEGL14.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowEGL14.java @@ -5,14 +5,13 @@ import android.opengl.EGLConfig; import android.opengl.EGLContext; import android.opengl.EGLDisplay; import android.opengl.EGLSurface; -import android.os.Build.VERSION_CODES; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; import org.robolectric.util.ReflectionHelpers; import org.robolectric.util.ReflectionHelpers.ClassParameter; /** Shadow for EGL14. Currently doesn't handle real graphics work, but avoids crashing when run. */ -@Implements(value = EGL14.class, minSdk = VERSION_CODES.LOLLIPOP) +@Implements(value = EGL14.class) public class ShadowEGL14 { private static final long UNUSED_HANDLE_ID = 43L; @@ -37,7 +36,11 @@ public class ShadowEGL14 { int configSize, int[] numConfig, int numConfigOffset) { - configs[configsOffset] = createEglConfig(); + // The configs array here can be null, in which case the numConfig output is supposed to be + // set to the number of matching configs instead of the number of returned configs. + if (configs != null) { + configs[configsOffset] = createEglConfig(); + } numConfig[numConfigOffset] = 1; return true; } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowFontFamily.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowFontFamily.java index 6d46c6240..904cb3e41 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowFontFamily.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowFontFamily.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.LOLLIPOP; import static android.os.Build.VERSION_CODES.O; import android.content.res.AssetManager; @@ -9,7 +8,7 @@ import android.graphics.fonts.FontVariationAxis; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; -@Implements(value = FontFamily.class, minSdk = LOLLIPOP, isInAndroidSdk = false) +@Implements(value = FontFamily.class, isInAndroidSdk = false) public class ShadowFontFamily { @Implementation(minSdk = O) diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowInCallService.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowInCallService.java index c3b4a3e41..e5997eee9 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowInCallService.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowInCallService.java @@ -207,9 +207,9 @@ public class ShadowInCallService extends ShadowService { InCallAdapter adapter = Shadow.newInstanceOf(InCallAdapter.class); Phone phone; if (VERSION.SDK_INT > N_MR1) { - phone = reflector(ReflectorPhone.class, inCallService).newInstance(adapter, "", 0); + phone = reflector(ReflectorPhone.class).newInstance(adapter, "", 0); } else { - phone = reflector(ReflectorPhone.class, inCallService).newInstance(adapter); + phone = reflector(ReflectorPhone.class).newInstance(adapter); } ReflectionHelpers.setField(inCallService, "mPhone", phone); } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowJobScheduler.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowJobScheduler.java index 1ecdd2340..807e9bbfb 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowJobScheduler.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowJobScheduler.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.LOLLIPOP; import static android.os.Build.VERSION_CODES.N; import static android.os.Build.VERSION_CODES.O; import static android.os.Build.VERSION_CODES.S; @@ -23,7 +22,7 @@ import org.robolectric.annotation.HiddenApi; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; -@Implements(value = JobScheduler.class, minSdk = LOLLIPOP) +@Implements(value = JobScheduler.class) public abstract class ShadowJobScheduler { @Implementation @@ -55,7 +54,7 @@ public abstract class ShadowJobScheduler { /** Whether to fail a job if it is set as expedited. */ public abstract void failExpeditedJob(boolean enabled); - @Implements(value = JobSchedulerImpl.class, isInAndroidSdk = false, minSdk = LOLLIPOP) + @Implements(value = JobSchedulerImpl.class, isInAndroidSdk = false) public static class ShadowJobSchedulerImpl extends ShadowJobScheduler { private final Map<Integer, JobInfo> scheduledJobs = diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowJobService.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowJobService.java index c527b5f5b..34e672b8a 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowJobService.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowJobService.java @@ -1,7 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.LOLLIPOP; - import android.app.Notification; import android.app.job.JobParameters; import android.app.job.JobService; @@ -9,7 +7,7 @@ import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; import org.robolectric.versioning.AndroidVersions.U; -@Implements(value = JobService.class, minSdk = LOLLIPOP) +@Implements(value = JobService.class) public class ShadowJobService extends ShadowService { private boolean isJobFinished = false; diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLauncherApps.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLauncherApps.java index d640f23fc..02c3f5dbf 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLauncherApps.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLauncherApps.java @@ -1,7 +1,6 @@ package org.robolectric.shadows; import static android.os.Build.VERSION_CODES.L; -import static android.os.Build.VERSION_CODES.LOLLIPOP; import static android.os.Build.VERSION_CODES.N; import static android.os.Build.VERSION_CODES.N_MR1; import static android.os.Build.VERSION_CODES.O; @@ -45,7 +44,7 @@ import org.robolectric.util.reflector.Accessor; import org.robolectric.util.reflector.ForType; /** Shadow of {@link android.content.pm.LauncherApps}. */ -@Implements(value = LauncherApps.class, minSdk = LOLLIPOP) +@Implements(value = LauncherApps.class) public class ShadowLauncherApps { private List<ShortcutInfo> shortcuts = new ArrayList<>(); private final Multimap<UserHandle, String> enabledPackages = HashMultimap.create(); diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyApkAssets.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyApkAssets.java deleted file mode 100644 index a9679f5eb..000000000 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyApkAssets.java +++ /dev/null @@ -1,33 +0,0 @@ -package org.robolectric.shadows; - -import static android.os.Build.VERSION_CODES.P; -import static android.os.Build.VERSION_CODES.Q; - -import android.content.res.ApkAssets; -import com.android.internal.util.Preconditions; -import java.io.IOException; -import org.robolectric.annotation.Implementation; -import org.robolectric.annotation.Implements; - -// transliterated from -// https://android.googlesource.com/platform/frameworks/base/+/android-9.0.0_r12/core/jni/android_content_res_ApkAssets.cpp - -/** Shadow for {@link ApkAssets} that is used for legacy resources. */ -@Implements(value = ApkAssets.class, minSdk = P, isInAndroidSdk = false) -public class ShadowLegacyApkAssets extends ShadowApkAssets { - - private String assetPath; - - @Implementation(maxSdk = Q) - protected void __constructor__( - String path, boolean system, boolean forceSharedLib, boolean overlay) throws IOException { - Preconditions.checkNotNull(path, "path"); - this.assetPath = path; - } - - - @Implementation - protected String getAssetPath() { - return assetPath; - } -} diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyAssetInputStream.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyAssetInputStream.java deleted file mode 100644 index 09fb27199..000000000 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyAssetInputStream.java +++ /dev/null @@ -1,141 +0,0 @@ -package org.robolectric.shadows; - -import static org.robolectric.util.reflector.Reflector.reflector; - -import android.content.res.AssetManager.AssetInputStream; -import java.io.IOException; -import java.io.InputStream; -import org.robolectric.annotation.Implementation; -import org.robolectric.annotation.Implements; -import org.robolectric.annotation.RealObject; -import org.robolectric.shadows.ShadowAssetInputStream.Picker; -import org.robolectric.util.reflector.Direct; -import org.robolectric.util.reflector.ForType; - -@SuppressWarnings("UnusedDeclaration") -@Implements(value = AssetInputStream.class, shadowPicker = Picker.class) -public class ShadowLegacyAssetInputStream extends ShadowAssetInputStream { - - @RealObject private AssetInputStream realObject; - - private InputStream delegate; - private boolean ninePatch; - - @Override - InputStream getDelegate() { - return delegate; - } - - void setDelegate(InputStream delegate) { - this.delegate = delegate; - } - - @Override - boolean isNinePatch() { - return ninePatch; - } - - void setNinePatch(boolean ninePatch) { - this.ninePatch = ninePatch; - } - - @Implementation - protected int read() throws IOException { - return delegate == null - ? reflector(AssetInputStreamReflector.class, realObject).read() - : delegate.read(); - } - - @Implementation - protected int read(byte[] b) throws IOException { - return delegate == null - ? reflector(AssetInputStreamReflector.class, realObject).read(b) - : delegate.read(b); - } - - @Implementation - protected int read(byte[] b, int off, int len) throws IOException { - return delegate == null - ? reflector(AssetInputStreamReflector.class, realObject).read(b, off, len) - : delegate.read(b, off, len); - } - - @Implementation - protected long skip(long n) throws IOException { - return delegate == null - ? reflector(AssetInputStreamReflector.class, realObject).skip(n) - : delegate.skip(n); - } - - @Implementation - protected int available() throws IOException { - return delegate == null - ? reflector(AssetInputStreamReflector.class, realObject).available() - : delegate.available(); - } - - @Implementation - protected void close() throws IOException { - if (delegate == null) { - reflector(AssetInputStreamReflector.class, realObject).close(); - } else { - delegate.close(); - } - } - - @Implementation - protected void mark(int readlimit) { - if (delegate == null) { - reflector(AssetInputStreamReflector.class, realObject).mark(readlimit); - } else { - delegate.mark(readlimit); - } - } - - @Implementation - protected void reset() throws IOException { - if (delegate == null) { - reflector(AssetInputStreamReflector.class, realObject).reset(); - } else { - delegate.reset(); - } - } - - @Implementation - protected boolean markSupported() { - return delegate == null - ? reflector(AssetInputStreamReflector.class, realObject).markSupported() - : delegate.markSupported(); - } - - @ForType(AssetInputStream.class) - interface AssetInputStreamReflector { - - @Direct - int read(); - - @Direct - int read(byte[] b); - - @Direct - int read(byte[] b, int off, int len); - - @Direct - long skip(long n); - - @Direct - int available(); - - @Direct - void close(); - - @Direct - void mark(int readlimit); - - @Direct - void reset(); - - @Direct - boolean markSupported(); - } -} diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyAssetManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyAssetManager.java deleted file mode 100644 index d3557a403..000000000 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyAssetManager.java +++ /dev/null @@ -1,1432 +0,0 @@ -package org.robolectric.shadows; - -import static android.os.Build.VERSION_CODES.M; -import static android.os.Build.VERSION_CODES.N; -import static android.os.Build.VERSION_CODES.N_MR1; -import static android.os.Build.VERSION_CODES.O; -import static android.os.Build.VERSION_CODES.O_MR1; -import static android.os.Build.VERSION_CODES.P; -import static android.os.Build.VERSION_CODES.TIRAMISU; -import static org.robolectric.shadow.api.Shadow.invokeConstructor; -import static org.robolectric.util.ReflectionHelpers.ClassParameter.from; -import static org.robolectric.util.reflector.Reflector.reflector; - -import android.annotation.SuppressLint; -import android.content.res.ApkAssets; -import android.content.res.AssetFileDescriptor; -import android.content.res.AssetManager; -import android.content.res.Resources; -import android.content.res.TypedArray; -import android.content.res.XmlResourceParser; -import android.os.Build.VERSION_CODES; -import android.os.ParcelFileDescriptor; -import android.util.AttributeSet; -import android.util.SparseArray; -import android.util.TypedValue; -import com.google.common.annotations.VisibleForTesting; -import com.google.common.collect.Ordering; -import dalvik.system.VMRuntime; -import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.UnsupportedEncodingException; -import java.net.MalformedURLException; -import java.net.URL; -import java.net.URLDecoder; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.CopyOnWriteArraySet; -import javax.annotation.Nonnull; -import org.robolectric.RuntimeEnvironment; -import org.robolectric.android.XmlResourceParserImpl; -import org.robolectric.annotation.HiddenApi; -import org.robolectric.annotation.Implementation; -import org.robolectric.annotation.Implements; -import org.robolectric.annotation.RealObject; -import org.robolectric.annotation.Resetter; -import org.robolectric.res.AttrData; -import org.robolectric.res.AttributeResource; -import org.robolectric.res.EmptyStyle; -import org.robolectric.res.FileTypedResource; -import org.robolectric.res.Fs; -import org.robolectric.res.ResName; -import org.robolectric.res.ResType; -import org.robolectric.res.ResourceIds; -import org.robolectric.res.ResourceTable; -import org.robolectric.res.Style; -import org.robolectric.res.StyleData; -import org.robolectric.res.StyleResolver; -import org.robolectric.res.ThemeStyleSet; -import org.robolectric.res.TypedResource; -import org.robolectric.res.android.Asset; -import org.robolectric.res.android.Registries; -import org.robolectric.res.android.ResTable_config; -import org.robolectric.res.builder.XmlBlock; -import org.robolectric.shadow.api.Shadow; -import org.robolectric.shadows.ShadowAssetManager.Picker; -import org.robolectric.util.Logger; -import org.robolectric.util.TempDirectory; -import org.robolectric.util.reflector.Direct; -import org.robolectric.util.reflector.ForType; - -@SuppressLint("NewApi") -@Implements(value = AssetManager.class, /* this one works for P too... maxSdk = VERSION_CODES.O_MR1,*/ - looseSignatures = true, shadowPicker = Picker.class) -public class ShadowLegacyAssetManager extends ShadowAssetManager { - - public static final Ordering<String> ATTRIBUTE_TYPE_PRECIDENCE = - Ordering.explicit( - "reference", - "color", - "boolean", - "integer", - "fraction", - "dimension", - "float", - "enum", - "flag", - "flags", - "string"); - - static boolean strictErrors = false; - - private static final int STYLE_NUM_ENTRIES = 6; - private static final int STYLE_TYPE = 0; - private static final int STYLE_DATA = 1; - private static final int STYLE_ASSET_COOKIE = 2; - private static final int STYLE_RESOURCE_ID = 3; - private static final int STYLE_CHANGING_CONFIGURATIONS = 4; - private static final int STYLE_DENSITY = 5; - - private static long nextInternalThemeId = 1000; - private static final Map<Long, NativeTheme> nativeThemes = new HashMap<>(); - - @RealObject protected AssetManager realObject; - - boolean isSystem = false; - - class NativeTheme { - private ThemeStyleSet themeStyleSet; - - public NativeTheme(ThemeStyleSet themeStyleSet) { - this.themeStyleSet = themeStyleSet; - } - - public ShadowLegacyAssetManager getShadowAssetManager() { - return ShadowLegacyAssetManager.this; - } - } - - ResTable_config config = new ResTable_config(); - private final Set<Path> assetDirs = new CopyOnWriteArraySet<>(); - - private void convertAndFill(AttributeResource attribute, TypedValue outValue, ResTable_config config, boolean resolveRefs) { - if (attribute.isNull()) { - outValue.type = TypedValue.TYPE_NULL; - outValue.data = TypedValue.DATA_NULL_UNDEFINED; - return; - } else if (attribute.isEmpty()) { - outValue.type = TypedValue.TYPE_NULL; - outValue.data = TypedValue.DATA_NULL_EMPTY; - return; - } - - // short-circuit Android caching of loaded resources cuz our string positions don't remain stable... - outValue.assetCookie = Converter.getNextStringCookie(); - outValue.changingConfigurations = 0; - - // TODO: Handle resource and style references - if (attribute.isStyleReference()) { - return; - } - - while (attribute.isResourceReference()) { - Integer resourceId; - ResName resName = attribute.getResourceReference(); - if (attribute.getReferenceResId() != null) { - resourceId = attribute.getReferenceResId(); - } else { - resourceId = getResourceTable().getResourceId(resName); - } - - if (resourceId == null) { - throw new Resources.NotFoundException("unknown resource " + resName); - } - outValue.type = TypedValue.TYPE_REFERENCE; - if (!resolveRefs) { - // Just return the resourceId if resolveRefs is false. - outValue.data = resourceId; - return; - } - - outValue.resourceId = resourceId; - - TypedResource dereferencedRef = getResourceTable().getValue(resName, config); - if (dereferencedRef == null) { - Logger.strict("couldn't resolve %s from %s", resName.getFullyQualifiedName(), attribute); - return; - } else { - if (dereferencedRef.isFile()) { - outValue.type = TypedValue.TYPE_STRING; - outValue.data = 0; - outValue.assetCookie = Converter.getNextStringCookie(); - outValue.string = dereferencedRef.asString(); - return; - } else if (dereferencedRef.getData() instanceof String) { - attribute = new AttributeResource(attribute.resName, dereferencedRef.asString(), resName.packageName); - if (attribute.isResourceReference()) { - continue; - } - if (resolveRefs) { - Converter.getConverter(dereferencedRef.getResType()).fillTypedValue(attribute.value, outValue); - return; - } - } - } - break; - } - - if (attribute.isNull()) { - outValue.type = TypedValue.TYPE_NULL; - return; - } - - TypedResource attrTypeData = getAttrTypeData(attribute.resName); - if (attrTypeData != null) { - AttrData attrData = (AttrData) attrTypeData.getData(); - String format = attrData.getFormat(); - String[] types = format.split("\\|"); - Arrays.sort(types, ATTRIBUTE_TYPE_PRECIDENCE); - for (String type : types) { - if ("reference".equals(type)) continue; // already handled above - Converter converter = Converter.getConverterFor(attrData, type); - - if (converter != null) { - if (converter.fillTypedValue(attribute.value, outValue)) { - return; - } - } - } - } else { - /** - * In cases where the runtime framework doesn't know this attribute, e.g: viewportHeight (added in 21) on a - * KitKat runtine, then infer the attribute type from the value. - * - * TODO: When we are able to pass the SDK resources from the build environment then we can remove this - * and replace the NullResourceLoader with simple ResourceProvider that only parses attribute type information. - */ - ResType resType = ResType.inferFromValue(attribute.value); - Converter.getConverter(resType).fillTypedValue(attribute.value, outValue); - } - } - - - public TypedResource getAttrTypeData(ResName resName) { - return getResourceTable().getValue(resName, config); - } - - @Implementation - protected void __constructor__() { - if (RuntimeEnvironment.getApiLevel() >= P) { - invokeConstructor(AssetManager.class, realObject); - } - - } - - @Implementation - protected void __constructor__(boolean isSystem) { - this.isSystem = isSystem; - if (RuntimeEnvironment.getApiLevel() >= P) { - invokeConstructor(AssetManager.class, realObject, from(boolean.class, isSystem)); - } - - } - - @Implementation(minSdk = P) - protected static long nativeCreate() { - // Return a fake pointer, must not be 0. - return 1; - } - - @HiddenApi - @Implementation(maxSdk = O_MR1) - protected void init(boolean isSystem) { - // no op - } - - protected ResourceTable getResourceTable() { - return isSystem - ? RuntimeEnvironment.getSystemResourceTable() - : RuntimeEnvironment.getAppResourceTable(); - } - - @HiddenApi @Implementation - public CharSequence getResourceText(int ident) { - TypedResource value = getAndResolve(ident, config, true); - if (value == null) return null; - return (CharSequence) value.getData(); - } - - @HiddenApi @Implementation - public CharSequence getResourceBagText(int ident, int bagEntryId) { - throw new UnsupportedOperationException(); // todo - } - - @HiddenApi @Implementation(maxSdk = O_MR1) - protected int getStringBlockCount() { - return 0; - } - - @HiddenApi @Implementation - public String[] getResourceStringArray(final int id) { - CharSequence[] resourceTextArray = getResourceTextArray(id); - if (resourceTextArray == null) return null; - String[] strings = new String[resourceTextArray.length]; - for (int i = 0; i < strings.length; i++) { - strings[i] = resourceTextArray[i].toString(); - } - return strings; - } - - @HiddenApi @Implementation - public int getResourceIdentifier(String name, String defType, String defPackage) { - Integer resourceId = - getResourceTable().getResourceId(ResName.qualifyResName(name, defPackage, defType)); - return resourceId == null ? 0 : resourceId; - } - - @HiddenApi @Implementation - public boolean getResourceValue(int ident, int density, TypedValue outValue, boolean resolveRefs) { - TypedResource value = getAndResolve(ident, config, resolveRefs); - if (value == null) return false; - - getConverter(value).fillTypedValue(value.getData(), outValue); - return true; - } - - private Converter getConverter(TypedResource value) { - if (value instanceof FileTypedResource.Image - || (value instanceof FileTypedResource - && ((FileTypedResource) value).getPath().getFileName().toString().endsWith(".xml"))) { - return new Converter.FromFilePath(); - } - return Converter.getConverter(value.getResType()); - } - - @HiddenApi @Implementation - public CharSequence[] getResourceTextArray(int resId) { - TypedResource value = getAndResolve(resId, config, true); - if (value == null) return null; - List<TypedResource> items = getConverter(value).getItems(value); - CharSequence[] charSequences = new CharSequence[items.size()]; - for (int i = 0; i < items.size(); i++) { - TypedResource typedResource = resolve(items.get(i), config, resId); - charSequences[i] = getConverter(typedResource).asCharSequence(typedResource); - } - return charSequences; - } - - @HiddenApi - @Implementation - public boolean getThemeValue(long themePtr, int ident, TypedValue outValue, boolean resolveRefs) { - ResName resName = getResourceTable().getResName(ident); - - ThemeStyleSet themeStyleSet = getNativeTheme(themePtr).themeStyleSet; - AttributeResource attrValue = themeStyleSet.getAttrValue(resName); - while(attrValue != null && attrValue.isStyleReference()) { - ResName attrResName = attrValue.getStyleReference(); - if (attrValue.resName.equals(attrResName)) { - Logger.info("huh... circular reference for %s?", attrResName.getFullyQualifiedName()); - return false; - } - attrValue = themeStyleSet.getAttrValue(attrResName); - } - if (attrValue != null) { - convertAndFill(attrValue, outValue, config, resolveRefs); - return true; - } - return false; - } - - @HiddenApi @Implementation(maxSdk = O_MR1) - protected Object ensureStringBlocks() { - return null; - } - - @Implementation - protected InputStream open(String fileName) throws IOException { - return Fs.getInputStream(findAssetFile(fileName)); - } - - @Implementation - protected InputStream open(String fileName, int accessMode) throws IOException { - return Fs.getInputStream(findAssetFile(fileName)); - } - - @Implementation - protected AssetFileDescriptor openFd(String fileName) throws IOException { - Path path = findAssetFile(fileName); - if (path.getFileSystem().provider().getScheme().equals("jar")) { - path = getFileFromZip(path); - } - ParcelFileDescriptor parcelFileDescriptor = - ParcelFileDescriptor.open(path.toFile(), ParcelFileDescriptor.MODE_READ_ONLY); - return new AssetFileDescriptor(parcelFileDescriptor, 0, Files.size(path)); - } - - private Path findAssetFile(String fileName) throws IOException { - for (Path assetDir : getAllAssetDirs()) { - Path assetFile = assetDir.resolve(fileName); - if (Files.exists(assetFile)) { - return assetFile; - } - } - - throw new FileNotFoundException("Asset file " + fileName + " not found"); - } - - /** - * Extract an asset from a zipped up assets provided by the build system, this is required because - * there is no way to get a FileDescriptor from a zip entry. This is a temporary measure for Bazel - * which can be removed once binary resources are supported. - */ - private static Path getFileFromZip(Path path) { - byte[] buffer = new byte[1024]; - try { - Path outputDir = new TempDirectory("robolectric_assets").create("fromzip"); - try (InputStream zis = Fs.getInputStream(path)) { - Path fileFromZip = outputDir.resolve(path.getFileName().toString()); - - try (OutputStream fos = Files.newOutputStream(fileFromZip)) { - int len; - while ((len = zis.read(buffer)) > 0) { - fos.write(buffer, 0, len); - } - } - return fileFromZip; - } - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - @Implementation - protected String[] list(String path) throws IOException { - List<String> assetFiles = new ArrayList<>(); - - for (Path assetsDir : getAllAssetDirs()) { - Path file; - if (path.isEmpty()) { - file = assetsDir; - } else { - file = assetsDir.resolve(path); - } - - if (Files.isDirectory(file)) { - Collections.addAll(assetFiles, Fs.listFileNames(file)); - } - } - return assetFiles.toArray(new String[assetFiles.size()]); - } - - @HiddenApi @Implementation(maxSdk = O_MR1) - protected Number openAsset(String fileName, int mode) throws FileNotFoundException { - return 0; - } - - @HiddenApi @Implementation(maxSdk = O_MR1) - protected ParcelFileDescriptor openAssetFd(String fileName, long[] outOffsets) throws IOException { - return null; - } - - @HiddenApi - @Implementation - public InputStream openNonAsset(int cookie, String fileName, int accessMode) throws IOException { - final ResName resName = qualifyFromNonAssetFileName(fileName); - - final FileTypedResource typedResource = - (FileTypedResource) getResourceTable().getValue(resName, config); - - if (typedResource == null) { - throw new IOException("Unable to find resource for " + fileName); - } - - InputStream stream; - if (accessMode == AssetManager.ACCESS_STREAMING) { - stream = Fs.getInputStream(typedResource.getPath()); - } else { - stream = new ByteArrayInputStream(Fs.getBytes(typedResource.getPath())); - } - - if (RuntimeEnvironment.getApiLevel() >= P) { - Asset asset = Asset.newFileAsset(typedResource); - long assetPtr = Registries.NATIVE_ASSET_REGISTRY.register(asset); - // Camouflage the InputStream as an AssetInputStream so subsequent instanceof checks pass. - stream = ShadowAssetInputStream.createAssetInputStream(stream, assetPtr, realObject); - } - - return stream; - } - - @HiddenApi @Implementation(maxSdk = O_MR1) - protected Number openNonAssetNative(int cookie, String fileName, int accessMode) - throws FileNotFoundException { - throw new IllegalStateException(); - } - - private ResName qualifyFromNonAssetFileName(String fileName) { - // Resources from a jar belong to the "android" namespace, except when they come from "resource_files.zip" - // when they are application resources produced by Bazel. - if (fileName.startsWith("jar:") && !fileName.contains("resource_files.zip")) { - // Must remove "jar:" prefix, or else qualifyFromFilePath fails on Windows - if (File.separatorChar == '\\') { - fileName = windowsWorkaround(fileName); - } - return ResName.qualifyFromFilePath("android", fileName.replaceFirst("jar:", "")); - } else { - return ResName.qualifyFromFilePath( - RuntimeEnvironment.getApplication().getPackageName(), fileName); - } - } - - private String windowsWorkaround(String fileWithinJar) { - try { - String path = new URL(new URL(fileWithinJar).getPath()).getPath(); - int bangI = path.indexOf('!'); - String jarPath = path.substring(1, bangI); - return URLDecoder.decode(URLDecoder.decode(jarPath, "UTF-8"), "UTF-8") - + "!" + path.substring(bangI + 1); - } catch (MalformedURLException | UnsupportedEncodingException e) { - throw new RuntimeException(e); - } - } - - @HiddenApi - @Implementation - public AssetFileDescriptor openNonAssetFd(int cookie, String fileName) throws IOException { - throw new IllegalStateException(); - } - - @HiddenApi @Implementation(maxSdk = O_MR1) - protected ParcelFileDescriptor openNonAssetFdNative(int cookie, String fileName, long[] outOffsets) - throws IOException { - throw new IllegalStateException(); - } - - @HiddenApi @Implementation(maxSdk = O_MR1) - protected Number openXmlAssetNative(int cookie, String fileName) throws FileNotFoundException { - throw new IllegalStateException(); - } - - @Implementation - protected XmlResourceParser openXmlResourceParser(int cookie, String fileName) - throws IOException { - XmlBlock xmlBlock = XmlBlock.create(Fs.fromUrl(fileName), getResourceTable().getPackageName()); - if (xmlBlock == null) { - throw new Resources.NotFoundException(fileName); - } - return getXmlResourceParser(getResourceTable(), xmlBlock, getResourceTable().getPackageName()); - } - - @HiddenApi - @Implementation(maxSdk = O_MR1) - protected long seekAsset(long asset, long offset, int whence) { - return 0; - } - - @HiddenApi - @Implementation(maxSdk = O_MR1) - protected long getAssetLength(long asset) { - return 0; - } - - @HiddenApi - @Implementation(maxSdk = O_MR1) - protected long getAssetRemainingLength(long assetHandle) { - return 0; - } - - @HiddenApi - @Implementation(maxSdk = O_MR1) - protected void destroyAsset(long asset) { - // no op - } - - protected XmlResourceParser loadXmlResourceParser(int resId, String type) throws Resources.NotFoundException { - ResName resName = getResName(resId); - ResName resolvedResName = resolveResName(resName, config); - if (resolvedResName == null) { - throw new RuntimeException("couldn't resolve " + resName.getFullyQualifiedName()); - } - resName = resolvedResName; - - XmlBlock block = getResourceTable().getXml(resName, config); - if (block == null) { - throw new Resources.NotFoundException(resName.getFullyQualifiedName()); - } - - ResourceTable resourceProvider = ResourceIds.isFrameworkResource(resId) ? RuntimeEnvironment.getSystemResourceTable() : RuntimeEnvironment.getCompileTimeResourceTable(); - - return getXmlResourceParser(resourceProvider, block, resName.packageName); - } - - private XmlResourceParser getXmlResourceParser(ResourceTable resourceProvider, XmlBlock block, String packageName) { - return new XmlResourceParserImpl( - block.getDocument(), - block.getPath(), - block.getPackageName(), - packageName, - resourceProvider); - } - - @HiddenApi @Implementation - public int addAssetPath(String path) { - assetDirs.add(Fs.fromUrl(path)); - return 1; - } - - @HiddenApi - @Implementation(maxSdk = M) - protected int addAssetPathNative(String path) { - return addAssetPathNative(path, false); - } - - @HiddenApi @Implementation(minSdk = N, maxSdk = O_MR1) - protected int addAssetPathNative(String path, boolean appAsLib) { - return 0; - } - - @HiddenApi @Implementation(minSdk = P) - public void setApkAssets(Object apkAssetsObject, Object invalidateCachesObject) { - ApkAssets[] apkAssets = (ApkAssets[]) apkAssetsObject; - boolean invalidateCaches = (boolean) invalidateCachesObject; - - for (ApkAssets apkAsset : apkAssets) { - assetDirs.add(Fs.fromUrl(apkAsset.getAssetPath())); - } - reflector(AssetManagerReflector.class, realObject).setApkAssets(apkAssets, invalidateCaches); - } - - @HiddenApi @Implementation - public boolean isUpToDate() { - return true; - } - - @HiddenApi @Implementation(maxSdk = M) - public void setLocale(String locale) { - } - - @Implementation - protected String[] getLocales() { - return new String[0]; // todo - } - - @HiddenApi - @Implementation(maxSdk = N_MR1) - public final void setConfiguration( - int mcc, - int mnc, - String locale, - int orientation, - int touchscreen, - int density, - int keyboard, - int keyboardHidden, - int navigation, - int screenWidth, - int screenHeight, - int smallestScreenWidthDp, - int screenWidthDp, - int screenHeightDp, - int screenLayout, - int uiMode, - int sdkVersion) { - setConfiguration( - mcc, - mnc, - locale, - orientation, - touchscreen, - density, - keyboard, - keyboardHidden, - navigation, - screenWidth, - screenHeight, - smallestScreenWidthDp, - screenWidthDp, - screenHeightDp, - screenLayout, - uiMode, - 0, - sdkVersion); - } - - @HiddenApi - @Implementation(minSdk = VERSION_CODES.O, maxSdk = TIRAMISU) - public void setConfiguration( - int mcc, - int mnc, - String locale, - int orientation, - int touchscreen, - int density, - int keyboard, - int keyboardHidden, - int navigation, - int screenWidth, - int screenHeight, - int smallestScreenWidthDp, - int screenWidthDp, - int screenHeightDp, - int screenLayout, - int uiMode, - int colorMode, - int majorVersion) { - // AssetManager* am = assetManagerForJavaObject(env, clazz); - - ResTable_config config = new ResTable_config(); - - // Constants duplicated from Java class android.content.res.Configuration. - final int kScreenLayoutRoundMask = 0x300; - final int kScreenLayoutRoundShift = 8; - - config.mcc = mcc; - config.mnc = mnc; - config.orientation = orientation; - config.touchscreen = touchscreen; - config.density = density; - config.keyboard = keyboard; - config.inputFlags = keyboardHidden; - config.navigation = navigation; - config.screenWidth = screenWidth; - config.screenHeight = screenHeight; - config.smallestScreenWidthDp = smallestScreenWidthDp; - config.screenWidthDp = screenWidthDp; - config.screenHeightDp = screenHeightDp; - config.screenLayout = screenLayout; - config.uiMode = uiMode; - // config.colorMode = colorMode; // todo - config.sdkVersion = majorVersion; - config.minorVersion = 0; - - // In Java, we use a 32bit integer for screenLayout, while we only use an 8bit integer - // in C++. We must extract the round qualifier out of the Java screenLayout and put it - // into screenLayout2. - config.screenLayout2 = - (byte)((screenLayout & kScreenLayoutRoundMask) >> kScreenLayoutRoundShift); - - if (locale != null) { - config.setBcp47Locale(locale); - } - // am->setConfiguration(config, locale8); - - this.config = config; - } - - @HiddenApi @Implementation(maxSdk = O_MR1) - public int[] getArrayIntResource(int resId) { - TypedResource value = getAndResolve(resId, config, true); - if (value == null) return null; - List<TypedResource> items = getConverter(value).getItems(value); - int[] ints = new int[items.size()]; - for (int i = 0; i < items.size(); i++) { - TypedResource typedResource = resolve(items.get(i), config, resId); - ints[i] = getConverter(typedResource).asInt(typedResource); - } - return ints; - } - - @HiddenApi @Implementation(minSdk = P) - protected int[] getResourceIntArray(int resId) { - return getArrayIntResource(resId); - } - - @HiddenApi @Implementation(maxSdk = O_MR1) - protected String[] getArrayStringResource(int arrayResId) { - return new String[0]; - } - - @HiddenApi @Implementation(maxSdk = O_MR1) - protected int[] getArrayStringInfo(int arrayResId) { - return new int[0]; - } - - @HiddenApi @Implementation(maxSdk = O_MR1) - protected Number newTheme() { - return null; - } - - protected TypedArray getTypedArrayResource(Resources resources, int resId) { - TypedResource value = getAndResolve(resId, config, true); - if (value == null) { - return null; - } - List<TypedResource> items = getConverter(value).getItems(value); - return getTypedArray(resources, items, resId); - } - - private TypedArray getTypedArray(Resources resources, List<TypedResource> typedResources, int resId) { - final CharSequence[] stringData = new CharSequence[typedResources.size()]; - final int totalLen = typedResources.size() * STYLE_NUM_ENTRIES; - final int[] data = new int[totalLen]; - - for (int i = 0; i < typedResources.size(); i++) { - final int offset = i * STYLE_NUM_ENTRIES; - TypedResource typedResource = typedResources.get(i); - - // Classify the item. - int type = getResourceType(typedResource); - if (type == -1) { - // This type is unsupported; leave empty. - continue; - } - - final TypedValue typedValue = new TypedValue(); - - if (type == TypedValue.TYPE_REFERENCE) { - final String reference = typedResource.asString(); - ResName refResName = - AttributeResource.getResourceReference( - reference, typedResource.getXmlContext().getPackageName(), null); - typedValue.resourceId = getResourceTable().getResourceId(refResName); - typedValue.data = typedValue.resourceId; - typedResource = resolve(typedResource, config, typedValue.resourceId); - - if (typedResource != null) { - // Reclassify to a non-reference type. - type = getResourceType(typedResource); - if (type == TypedValue.TYPE_ATTRIBUTE) { - type = TypedValue.TYPE_REFERENCE; - } else if (type == -1) { - // This type is unsupported; leave empty. - continue; - } - } - } - - if (type == TypedValue.TYPE_ATTRIBUTE) { - final String reference = typedResource.asString(); - final ResName attrResName = - AttributeResource.getStyleReference( - reference, typedResource.getXmlContext().getPackageName(), "attr"); - typedValue.data = getResourceTable().getResourceId(attrResName); - } - - if (typedResource != null && type != TypedValue.TYPE_NULL && type != TypedValue.TYPE_ATTRIBUTE) { - getConverter(typedResource).fillTypedValue(typedResource.getData(), typedValue); - } - - data[offset + STYLE_TYPE] = type; - data[offset + STYLE_RESOURCE_ID] = typedValue.resourceId; - data[offset + STYLE_DATA] = typedValue.data; - data[offset + STYLE_ASSET_COOKIE] = typedValue.assetCookie; - data[offset + STYLE_CHANGING_CONFIGURATIONS] = typedValue.changingConfigurations; - data[offset + STYLE_DENSITY] = typedValue.density; - stringData[i] = typedResource == null ? null : typedResource.asString(); - } - - int[] indices = new int[typedResources.size() + 1]; /* keep zeroed out */ - return ShadowTypedArray.create(resources, null, data, indices, typedResources.size(), stringData); - } - - private int getResourceType(TypedResource typedResource) { - if (typedResource == null) { - return -1; - } - final ResType resType = typedResource.getResType(); - int type; - if (typedResource.getData() == null || resType == ResType.NULL) { - type = TypedValue.TYPE_NULL; - } else if (typedResource.isReference()) { - type = TypedValue.TYPE_REFERENCE; - } else if (resType == ResType.STYLE) { - type = TypedValue.TYPE_ATTRIBUTE; - } else if (resType == ResType.CHAR_SEQUENCE || resType == ResType.DRAWABLE) { - type = TypedValue.TYPE_STRING; - } else if (resType == ResType.INTEGER) { - type = TypedValue.TYPE_INT_DEC; - } else if (resType == ResType.FLOAT || resType == ResType.FRACTION) { - type = TypedValue.TYPE_FLOAT; - } else if (resType == ResType.BOOLEAN) { - type = TypedValue.TYPE_INT_BOOLEAN; - } else if (resType == ResType.DIMEN) { - type = TypedValue.TYPE_DIMENSION; - } else if (resType == ResType.COLOR) { - type = TypedValue.TYPE_INT_COLOR_ARGB8; - } else if (resType == ResType.TYPED_ARRAY || resType == ResType.CHAR_SEQUENCE_ARRAY) { - type = TypedValue.TYPE_REFERENCE; - } else { - type = -1; - } - return type; - } - - @HiddenApi - @Implementation - public long createTheme() { - synchronized (nativeThemes) { - long nativePtr = nextInternalThemeId++; - nativeThemes.put(nativePtr, new NativeTheme(new ThemeStyleSet())); - return nativePtr; - } - } - - @HiddenApi - @Implementation(maxSdk = O_MR1) - protected static void dumpTheme(long theme, int priority, String tag, String prefix) { - throw new UnsupportedOperationException("not yet implemented"); - } - - - private static NativeTheme getNativeTheme(long themePtr) { - NativeTheme nativeTheme; - synchronized (nativeThemes) { - nativeTheme = nativeThemes.get(themePtr); - } - if (nativeTheme == null) { - throw new RuntimeException("no theme " + themePtr + " found in AssetManager"); - } - return nativeTheme; - } - - @HiddenApi - @Implementation - public void releaseTheme(long themePtr) { - synchronized (nativeThemes) { - nativeThemes.remove(themePtr); - } - } - - @HiddenApi - @Implementation(maxSdk = O_MR1) - protected void deleteTheme(long theme) { - // no op - } - - @HiddenApi - @Implementation(maxSdk = O_MR1) - public static void applyThemeStyle(long themePtr, int styleRes, boolean force) { - NativeTheme nativeTheme = getNativeTheme(themePtr); - Style style = nativeTheme.getShadowAssetManager().resolveStyle(styleRes, null); - nativeTheme.themeStyleSet.apply(style, force); - } - - @HiddenApi - @Implementation(maxSdk = O_MR1) - public static void copyTheme(long destPtr, long sourcePtr) { - NativeTheme destNativeTheme = getNativeTheme(destPtr); - NativeTheme sourceNativeTheme = getNativeTheme(sourcePtr); - destNativeTheme.themeStyleSet = sourceNativeTheme.themeStyleSet.copy(); - } - - @HiddenApi @Implementation(minSdk = P, maxSdk = P) - protected static void nativeThemeCopy(long destPtr, long sourcePtr) { - copyTheme(destPtr, sourcePtr); - } - - @HiddenApi - @Implementation(minSdk = VERSION_CODES.Q) - protected static void nativeThemeCopy( - long dstAssetManagerPtr, long dstThemePtr, long srcAssetManagerPtr, long srcThemePtr) { - copyTheme(dstThemePtr, srcThemePtr); - } - - @HiddenApi - @Implementation(minSdk = O, maxSdk = O_MR1) - protected static void applyStyle( - long themeToken, - int defStyleAttr, - int defStyleRes, - long xmlParserToken, - int[] inAttrs, - int length, - long outValuesAddress, - long outIndicesAddress) { - ShadowVMRuntime shadowVMRuntime = Shadow.extract(VMRuntime.getRuntime()); - int[] outValues = (int[])shadowVMRuntime.getObjectForAddress(outValuesAddress); - int[] outIndices = (int[])shadowVMRuntime.getObjectForAddress(outIndicesAddress); - applyStyle( - themeToken, defStyleAttr, defStyleRes, xmlParserToken, inAttrs, outValues, outIndices); - } - - @HiddenApi @Implementation(minSdk = P) - protected void applyStyleToTheme(long themePtr, int resId, boolean force) { - applyThemeStyle(themePtr, resId, force); - } - - @HiddenApi - @Implementation(maxSdk = N_MR1) - protected static boolean applyStyle( - long themeToken, - int defStyleAttr, - int defStyleRes, - long xmlParserToken, - int[] attrs, - int[] outValues, - int[] outIndices) { - // no-op - return false; - } - - @HiddenApi - @Implementation(maxSdk = O_MR1) - protected static boolean resolveAttrs( - long themeToken, - int defStyleAttr, - int defStyleRes, - int[] inValues, - int[] attrs, - int[] outValues, - int[] outIndices) { - // no-op - return false; - } - - @Implementation(maxSdk = O_MR1) - protected boolean retrieveAttributes( - long xmlParserToken, int[] attrs, int[] outValues, int[] outIndices) { - return false; - } - - @HiddenApi - @Implementation(maxSdk = O_MR1) - protected static int loadThemeAttributeValue( - long themeHandle, int ident, TypedValue outValue, boolean resolve) { - // no-op - return 0; - } - - ///////////////////////// - - Style resolveStyle(int resId, Style themeStyleSet) { - return resolveStyle(getResName(resId), themeStyleSet); - } - - private Style resolveStyle(@Nonnull ResName themeStyleName, Style themeStyleSet) { - TypedResource themeStyleResource = getResourceTable().getValue(themeStyleName, config); - if (themeStyleResource == null) return null; - StyleData themeStyleData = (StyleData) themeStyleResource.getData(); - if (themeStyleSet == null) { - themeStyleSet = new ThemeStyleSet(); - } - return new StyleResolver( - getResourceTable(), - legacyShadowOf(AssetManager.getSystem()).getResourceTable(), - themeStyleData, - themeStyleSet, - themeStyleName, - config); - } - - private TypedResource getAndResolve(int resId, ResTable_config config, boolean resolveRefs) { - TypedResource value = getResourceTable().getValue(resId, config); - if (resolveRefs) { - value = resolve(value, config, resId); - } - return value; - } - - TypedResource resolve(TypedResource value, ResTable_config config, int resId) { - return resolveResourceValue(value, config, resId); - } - - protected ResName resolveResName(ResName resName, ResTable_config config) { - TypedResource value = getResourceTable().getValue(resName, config); - return resolveResource(value, config, resName); - } - - // todo: DRY up #resolveResource vs #resolveResourceValue - private ResName resolveResource(TypedResource value, ResTable_config config, ResName resName) { - while (value != null && value.isReference()) { - String s = value.asString(); - if (AttributeResource.isNull(s) || AttributeResource.isEmpty(s)) { - value = null; - } else { - String refStr = s.substring(1).replace("+", ""); - resName = ResName.qualifyResName(refStr, resName); - value = getResourceTable().getValue(resName, config); - } - } - - return resName; - } - - private TypedResource resolveResourceValue(TypedResource value, ResTable_config config, ResName resName) { - while (value != null && value.isReference()) { - String s = value.asString(); - if (AttributeResource.isNull(s) || AttributeResource.isEmpty(s)) { - value = null; - } else { - String refStr = s.substring(1).replace("+", ""); - resName = ResName.qualifyResName(refStr, resName); - value = getResourceTable().getValue(resName, config); - } - } - - return value; - } - - protected TypedResource resolveResourceValue(TypedResource value, ResTable_config config, int resId) { - ResName resName = getResName(resId); - return resolveResourceValue(value, config, resName); - } - - private TypedValue buildTypedValue(AttributeSet set, int resId, int defStyleAttr, Style themeStyleSet, int defStyleRes) { - /* - * When determining the final value of a particular attribute, there are four inputs that come into play: - * - * 1. Any attribute values in the given AttributeSet. - * 2. The style resource specified in the AttributeSet (named "style"). - * 3. The default style specified by defStyleAttr and defStyleRes - * 4. The base values in this theme. - */ - Style defStyleFromAttr = null; - Style defStyleFromRes = null; - Style styleAttrStyle = null; - - if (defStyleAttr != 0) { - // Load the theme attribute for the default style attributes. E.g., attr/buttonStyle - ResName defStyleName = getResName(defStyleAttr); - - // Load the style for the default style attribute. E.g. "@style/Widget.Robolectric.Button"; - AttributeResource defStyleAttribute = themeStyleSet.getAttrValue(defStyleName); - if (defStyleAttribute != null) { - while (defStyleAttribute.isStyleReference()) { - AttributeResource other = themeStyleSet.getAttrValue(defStyleAttribute.getStyleReference()); - if (other == null) { - throw new RuntimeException("couldn't dereference " + defStyleAttribute); - } - defStyleAttribute = other; - } - - if (defStyleAttribute.isResourceReference()) { - ResName defStyleResName = defStyleAttribute.getResourceReference(); - defStyleFromAttr = resolveStyle(defStyleResName, themeStyleSet); - } - } - } - - if (set != null && set.getStyleAttribute() != 0) { - ResName styleAttributeResName = getResName(set.getStyleAttribute()); - while (styleAttributeResName.type.equals("attr")) { - AttributeResource attrValue = themeStyleSet.getAttrValue(styleAttributeResName); - if (attrValue == null) { - throw new RuntimeException( - "no value for " - + styleAttributeResName.getFullyQualifiedName() - + " in " - + themeStyleSet); - } - if (attrValue.isResourceReference()) { - styleAttributeResName = attrValue.getResourceReference(); - } else if (attrValue.isStyleReference()) { - styleAttributeResName = attrValue.getStyleReference(); - } - } - styleAttrStyle = resolveStyle(styleAttributeResName, themeStyleSet); - } - - if (defStyleRes != 0) { - ResName resName = getResName(defStyleRes); - if (resName.type.equals("attr")) { - // todo: this should be a style resId, not an attr - System.out.println("WARN: " + resName.getFullyQualifiedName() + " should be a style resId"); - // AttributeResource attributeValue = findAttributeValue(defStyleRes, set, styleAttrStyle, defStyleFromAttr, defStyleFromAttr, themeStyleSet); - // if (attributeValue != null) { - // if (attributeValue.isStyleReference()) { - // resName = themeStyleSet.getAttrValue(attributeValue.getStyleReference()).getResourceReference(); - // } else if (attributeValue.isResourceReference()) { - // resName = attributeValue.getResourceReference(); - // } - // } - } else if (resName.type.equals("style")) { - defStyleFromRes = resolveStyle(resName, themeStyleSet); - } - } - - AttributeResource attribute = findAttributeValue(resId, set, styleAttrStyle, defStyleFromAttr, defStyleFromRes, themeStyleSet); - while (attribute != null && attribute.isStyleReference()) { - ResName otherAttrName = attribute.getStyleReference(); - if (attribute.resName.equals(otherAttrName)) { - Logger.info("huh... circular reference for %s?", attribute.resName.getFullyQualifiedName()); - return null; - } - ResName resName = getResourceTable().getResName(resId); - - AttributeResource otherAttr = themeStyleSet.getAttrValue(otherAttrName); - if (otherAttr == null) { - strictError( - "no such attr %s in %s while resolving value for %s", - attribute.value, themeStyleSet, resName.getFullyQualifiedName()); - attribute = null; - } else { - attribute = new AttributeResource(resName, otherAttr.value, otherAttr.contextPackageName); - } - } - - if (attribute == null || attribute.isNull()) { - return null; - } else { - TypedValue typedValue = new TypedValue(); - convertAndFill(attribute, typedValue, config, true); - return typedValue; - } - } - - private void strictError(String message, Object... args) { - if (strictErrors) { - throw new RuntimeException(String.format(message, args)); - } else { - Logger.strict(message, args); - } - } - - TypedArray attrsToTypedArray(Resources resources, AttributeSet set, int[] attrs, int defStyleAttr, long nativeTheme, int defStyleRes) { - CharSequence[] stringData = new CharSequence[attrs.length]; - int[] data = new int[attrs.length * STYLE_NUM_ENTRIES]; - int[] indices = new int[attrs.length + 1]; - int nextIndex = 0; - - Style themeStyleSet = nativeTheme == 0 - ? new EmptyStyle() - : getNativeTheme(nativeTheme).themeStyleSet; - - for (int i = 0; i < attrs.length; i++) { - int offset = i * STYLE_NUM_ENTRIES; - - TypedValue typedValue = buildTypedValue(set, attrs[i], defStyleAttr, themeStyleSet, defStyleRes); - if (typedValue != null) { - //noinspection PointlessArithmeticExpression - data[offset + STYLE_TYPE] = typedValue.type; - data[offset + STYLE_DATA] = typedValue.type == TypedValue.TYPE_STRING ? i : typedValue.data; - data[offset + STYLE_ASSET_COOKIE] = typedValue.assetCookie; - data[offset + STYLE_RESOURCE_ID] = typedValue.resourceId; - data[offset + STYLE_CHANGING_CONFIGURATIONS] = typedValue.changingConfigurations; - data[offset + STYLE_DENSITY] = typedValue.density; - stringData[i] = typedValue.string; - - indices[nextIndex + 1] = i; - nextIndex++; - } - } - - indices[0] = nextIndex; - - TypedArray typedArray = ShadowTypedArray.create(resources, attrs, data, indices, nextIndex, stringData); - if (set != null) { - ShadowTypedArray shadowTypedArray = Shadow.extract(typedArray); - shadowTypedArray.positionDescription = set.getPositionDescription(); - } - return typedArray; - } - - private AttributeResource findAttributeValue(int resId, AttributeSet attributeSet, Style styleAttrStyle, Style defStyleFromAttr, Style defStyleFromRes, @Nonnull Style themeStyleSet) { - if (attributeSet != null) { - for (int i = 0; i < attributeSet.getAttributeCount(); i++) { - if (attributeSet.getAttributeNameResource(i) == resId) { - String attributeValue; - try { - attributeValue = attributeSet.getAttributeValue(i); - } catch (IndexOutOfBoundsException e) { - // type is TypedValue.TYPE_NULL, ignore... - continue; - } - if (attributeValue != null) { - String defaultPackageName = - ResourceIds.isFrameworkResource(resId) - ? "android" - : RuntimeEnvironment.getApplication().getPackageName(); - ResName resName = - ResName.qualifyResName( - attributeSet.getAttributeName(i), defaultPackageName, "attr"); - Integer referenceResId = null; - if (AttributeResource.isResourceReference(attributeValue)) { - referenceResId = attributeSet.getAttributeResourceValue(i, -1); - // binary AttributeSet references have a string value of @resId rather than fully qualified resource name - if (referenceResId != 0) { - ResName refResName = getResourceTable().getResName(referenceResId); - if (refResName != null) { - attributeValue = "@" + refResName.getFullyQualifiedName(); - } - } - } - return new AttributeResource(resName, attributeValue, "fixme!!!", referenceResId); - } - } - } - } - - ResName attrName = getResourceTable().getResName(resId); - if (attrName == null) return null; - - if (styleAttrStyle != null) { - AttributeResource attribute = styleAttrStyle.getAttrValue(attrName); - if (attribute != null) { - return attribute; - } - } - - // else if attr in defStyleFromAttr, use its value - if (defStyleFromAttr != null) { - AttributeResource attribute = defStyleFromAttr.getAttrValue(attrName); - if (attribute != null) { - return attribute; - } - } - - if (defStyleFromRes != null) { - AttributeResource attribute = defStyleFromRes.getAttrValue(attrName); - if (attribute != null) { - return attribute; - } - } - - // else if attr in theme, use its value - return themeStyleSet.getAttrValue(attrName); - } - - @Override - Collection<Path> getAllAssetDirs() { - return assetDirs; - } - - @Nonnull private ResName getResName(int id) { - ResName resName = getResourceTable().getResName(id); - if (resName == null) { - throw new Resources.NotFoundException("Resource ID #0x" + Integer.toHexString(id)); - } - return resName; - } - - @Implementation - protected String getResourceName(int resid) { - return getResName(resid).getFullyQualifiedName(); - } - - @Implementation - protected String getResourcePackageName(int resid) { - return getResName(resid).packageName; - } - - @Implementation - protected String getResourceTypeName(int resid) { - return getResName(resid).type; - } - - @Implementation - protected String getResourceEntryName(int resid) { - return getResName(resid).name; - } - - @Implementation(maxSdk = O_MR1) - protected int getArraySize(int id) { - return 0; - } - - @Implementation(maxSdk = O_MR1) - protected int retrieveArray(int id, int[] outValues) { - return 0; - } - - @Implementation(maxSdk = O_MR1) - protected Number getNativeStringBlock(int block) { - throw new IllegalStateException(); - } - - @Implementation(maxSdk = O_MR1) - protected SparseArray<String> getAssignedPackageIdentifiers() { - return new SparseArray<>(); - } - - @Implementation(maxSdk = O_MR1) - protected int loadResourceValue(int ident, short density, TypedValue outValue, boolean resolve) { - return 0; - } - - @Implementation(maxSdk = O_MR1) - protected int loadResourceBagValue(int ident, int bagEntryId, TypedValue outValue, boolean resolve) { - return 0; - } - - // static void NativeAssetDestroy(JNIEnv* /*env*/, jclass /*clazz*/, jlong asset_ptr) { - @Implementation(minSdk = P) - protected static void nativeAssetDestroy(long asset_ptr) { - ShadowArscAssetManager9.nativeAssetDestroy(asset_ptr); - } - - // static jint NativeAssetReadChar(JNIEnv* /*env*/, jclass /*clazz*/, jlong asset_ptr) { - @Implementation(minSdk = P) - protected static int nativeAssetReadChar(long asset_ptr) { - return ShadowArscAssetManager9.nativeAssetReadChar(asset_ptr); - } - - // static jint NativeAssetRead(JNIEnv* env, jclass /*clazz*/, jlong asset_ptr, jbyteArray java_buffer, -// jint offset, jint len) { - @Implementation(minSdk = P) - protected static int nativeAssetRead(long asset_ptr, byte[] java_buffer, int offset, int len) - throws IOException { - return ShadowArscAssetManager9.nativeAssetRead(asset_ptr, java_buffer, offset, len); - } - - // static jlong NativeAssetSeek(JNIEnv* env, jclass /*clazz*/, jlong asset_ptr, jlong offset, -// jint whence) { - @Implementation(minSdk = P) - protected static long nativeAssetSeek(long asset_ptr, long offset, int whence) { - return ShadowArscAssetManager9.nativeAssetSeek(asset_ptr, offset, whence); - } - - // static jlong NativeAssetGetLength(JNIEnv* /*env*/, jclass /*clazz*/, jlong asset_ptr) { - @Implementation(minSdk = P) - protected static long nativeAssetGetLength(long asset_ptr) { - return ShadowArscAssetManager9.nativeAssetGetLength(asset_ptr); - } - - // static jlong NativeAssetGetRemainingLength(JNIEnv* /*env*/, jclass /*clazz*/, jlong asset_ptr) { - @Implementation(minSdk = P) - protected static long nativeAssetGetRemainingLength(long asset_ptr) { - return ShadowArscAssetManager9.nativeAssetGetRemainingLength(asset_ptr); - } - - @Implementation(minSdk = VERSION_CODES.Q, maxSdk = VERSION_CODES.R) - protected static String[] nativeCreateIdmapsForStaticOverlaysTargetingAndroid() { - return new String[0]; - } - - @Resetter - public static void reset() { - // todo: ShadowPicker doesn't discriminate properly between concrete shadow classes for resetters... - if (useLegacy()) { - if (RuntimeEnvironment.getApiLevel() >= P) { - _AssetManager28_ _assetManagerStatic_ = reflector(_AssetManager28_.class); - _assetManagerStatic_.setSystemApkAssetsSet(null); - _assetManagerStatic_.setSystemApkAssets(null); - } - reflector(_AssetManager_.class).setSystem(null); - } - } - - @VisibleForTesting - @Override - long getNativePtr() { - return 0; - } - - @ForType(AssetManager.class) - interface AssetManagerReflector { - - @Direct - void setApkAssets(ApkAssets[] apkAssetsObject, boolean invalidateCachesObject); - } -} diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyMessageQueue.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyMessageQueue.java index 0d7e20e11..8fe26cfa1 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyMessageQueue.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyMessageQueue.java @@ -1,8 +1,6 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.LOLLIPOP; import static android.os.Build.VERSION_CODES.LOLLIPOP_MR1; -import static org.robolectric.RuntimeEnvironment.getApiLevel; import static org.robolectric.util.ReflectionHelpers.getField; import static org.robolectric.util.ReflectionHelpers.setField; import static org.robolectric.util.reflector.Reflector.reflector; @@ -140,11 +138,7 @@ public class ShadowLegacyMessageQueue extends ShadowMessageQueue { msgProxy.markInUse(); target.dispatchMessage(msg); - if (getApiLevel() >= LOLLIPOP) { - msgProxy.recycleUnchecked(); - } else { - msgProxy.recycle(); - } + msgProxy.recycleUnchecked(); } } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyResourcesImpl.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyResourcesImpl.java deleted file mode 100644 index ecad6fdef..000000000 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyResourcesImpl.java +++ /dev/null @@ -1,195 +0,0 @@ -package org.robolectric.shadows; - -import static android.os.Build.VERSION_CODES.M; -import static android.os.Build.VERSION_CODES.N; -import static android.os.Build.VERSION_CODES.N_MR1; -import static android.os.Build.VERSION_CODES.O; -import static org.robolectric.shadows.ShadowAssetManager.legacyShadowOf; -import static org.robolectric.util.reflector.Reflector.reflector; - -import android.content.res.AssetFileDescriptor; -import android.content.res.AssetManager; -import android.content.res.Resources; -import android.content.res.ResourcesImpl; -import android.content.res.TypedArray; -import android.content.res.XmlResourceParser; -import android.graphics.drawable.Drawable; -import android.os.ParcelFileDescriptor; -import android.util.AttributeSet; -import android.util.TypedValue; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.Locale; -import org.robolectric.RuntimeEnvironment; -import org.robolectric.annotation.HiddenApi; -import org.robolectric.annotation.Implementation; -import org.robolectric.annotation.Implements; -import org.robolectric.annotation.RealObject; -import org.robolectric.annotation.Resetter; -import org.robolectric.res.Plural; -import org.robolectric.res.PluralRules; -import org.robolectric.res.ResName; -import org.robolectric.res.ResType; -import org.robolectric.res.ResourceTable; -import org.robolectric.res.TypedResource; -import org.robolectric.shadows.ShadowResourcesImpl.Picker; -import org.robolectric.util.ReflectionHelpers; -import org.robolectric.util.reflector.Direct; -import org.robolectric.util.reflector.ForType; - -@SuppressWarnings("NewApi") -@Implements( - value = ResourcesImpl.class, - isInAndroidSdk = false, - minSdk = N, - shadowPicker = Picker.class) -public class ShadowLegacyResourcesImpl extends ShadowResourcesImpl { - - @Resetter - public static void reset() { - if (RuntimeEnvironment.useLegacyResources()) { - ShadowResourcesImpl.reset(); - } - } - - @RealObject private ResourcesImpl realResourcesImpl; - - @Implementation(maxSdk = M) - public String getQuantityString(int id, int quantity, Object... formatArgs) throws Resources.NotFoundException { - String raw = getQuantityString(id, quantity); - return String.format(Locale.ENGLISH, raw, formatArgs); - } - - @Implementation(maxSdk = M) - public String getQuantityString(int resId, int quantity) throws Resources.NotFoundException { - ShadowLegacyAssetManager shadowAssetManager = legacyShadowOf(realResourcesImpl.getAssets()); - - TypedResource typedResource = shadowAssetManager.getResourceTable().getValue(resId, shadowAssetManager.config); - if (typedResource != null && typedResource instanceof PluralRules) { - PluralRules pluralRules = (PluralRules) typedResource; - Plural plural = pluralRules.find(quantity); - - if (plural == null) { - return null; - } - - TypedResource<?> resolvedTypedResource = - shadowAssetManager.resolve( - new TypedResource<>( - plural.getString(), ResType.CHAR_SEQUENCE, pluralRules.getXmlContext()), - shadowAssetManager.config, - resId); - return resolvedTypedResource == null ? null : resolvedTypedResource.asString(); - } else { - return null; - } - } - - @Implementation(maxSdk = M) - public InputStream openRawResource(int id) throws Resources.NotFoundException { - ShadowLegacyAssetManager shadowAssetManager = legacyShadowOf(realResourcesImpl.getAssets()); - ResourceTable resourceTable = shadowAssetManager.getResourceTable(); - InputStream inputStream = resourceTable.getRawValue(id, shadowAssetManager.config); - if (inputStream == null) { - throw newNotFoundException(id); - } else { - return inputStream; - } - } - - /** - * Since {@link AssetFileDescriptor}s are not yet supported by Robolectric, {@code null} will - * be returned if the resource is found. If the resource cannot be found, {@link Resources.NotFoundException} will - * be thrown. - */ - @Implementation(maxSdk = M) - public AssetFileDescriptor openRawResourceFd(int id) throws Resources.NotFoundException { - InputStream inputStream = openRawResource(id); - if (!(inputStream instanceof FileInputStream)) { - // todo fixme - return null; - } - - FileInputStream fis = (FileInputStream) inputStream; - try { - return new AssetFileDescriptor(ParcelFileDescriptor.dup(fis.getFD()), 0, fis.getChannel().size()); - } catch (IOException e) { - throw newNotFoundException(id); - } - } - - private Resources.NotFoundException newNotFoundException(int id) { - ResourceTable resourceTable = legacyShadowOf(realResourcesImpl.getAssets()).getResourceTable(); - ResName resName = resourceTable.getResName(id); - if (resName == null) { - return new Resources.NotFoundException("resource ID #0x" + Integer.toHexString(id)); - } else { - return new Resources.NotFoundException(resName.getFullyQualifiedName()); - } - } - - @HiddenApi @Implementation(maxSdk = M) - public XmlResourceParser loadXmlResourceParser(int resId, String type) throws Resources.NotFoundException { - ShadowLegacyAssetManager shadowAssetManager = legacyShadowOf(realResourcesImpl.getAssets()); - return shadowAssetManager.loadXmlResourceParser(resId, type); - } - - @HiddenApi @Implementation - public XmlResourceParser loadXmlResourceParser(String file, int id, int assetCookie, String type) throws Resources.NotFoundException { - return loadXmlResourceParser(id, type); - } - - @Implements( - value = ResourcesImpl.ThemeImpl.class, - minSdk = N, - isInAndroidSdk = false, - shadowPicker = ShadowResourcesImpl.ShadowThemeImpl.Picker.class) - public static class ShadowLegacyThemeImpl extends ShadowThemeImpl { - @RealObject ResourcesImpl.ThemeImpl realThemeImpl; - - @Implementation - public TypedArray obtainStyledAttributes(Resources.Theme wrapper, AttributeSet set, int[] attrs, int defStyleAttr, int defStyleRes) { - Resources resources = wrapper.getResources(); - AssetManager assets = resources.getAssets(); - return legacyShadowOf(assets) - .attrsToTypedArray(resources, set, attrs, defStyleAttr, getNativePtr(), defStyleRes); - } - - public long getNativePtr() { - return ReflectionHelpers.getField(realThemeImpl, "mTheme"); - } - } - - @Implementation(maxSdk = N_MR1) - public Drawable loadDrawable(Resources wrapper, TypedValue value, int id, Resources.Theme theme, boolean useCache) throws Resources.NotFoundException { - Drawable drawable = - reflector(ResourcesImplReflector.class, realResourcesImpl) - .loadDrawable(wrapper, value, id, theme, useCache); - - ShadowResources.setCreatedFromResId(wrapper, id, drawable); - return drawable; - } - - @Implementation(minSdk = O) - public Drawable loadDrawable(Resources wrapper, TypedValue value, int id, int density, Resources.Theme theme) { - Drawable drawable = - reflector(ResourcesImplReflector.class, realResourcesImpl) - .loadDrawable(wrapper, value, id, density, theme); - - ShadowResources.setCreatedFromResId(wrapper, id, drawable); - return drawable; - } - - @ForType(ResourcesImpl.class) - interface ResourcesImplReflector { - - @Direct - Drawable loadDrawable( - Resources wrapper, TypedValue value, int id, Resources.Theme theme, boolean useCache); - - @Direct - Drawable loadDrawable( - Resources wrapper, TypedValue value, int id, int density, Resources.Theme theme); - } -} diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyTypeface.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyTypeface.java index cec7d881a..f1b59d6c1 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyTypeface.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyTypeface.java @@ -1,7 +1,5 @@ package org.robolectric.shadows; - -import static android.os.Build.VERSION_CODES.LOLLIPOP; import static android.os.Build.VERSION_CODES.N_MR1; import static android.os.Build.VERSION_CODES.O; import static android.os.Build.VERSION_CODES.O_MR1; @@ -9,7 +7,6 @@ import static android.os.Build.VERSION_CODES.P; import static android.os.Build.VERSION_CODES.Q; import static android.os.Build.VERSION_CODES.R; import static android.os.Build.VERSION_CODES.S; -import static org.robolectric.RuntimeEnvironment.getApiLevel; import static org.robolectric.Shadows.shadowOf; import android.annotation.SuppressLint; @@ -215,13 +212,8 @@ public class ShadowLegacyTypeface extends ShadowTypeface { protected static Typeface createUnderlyingTypeface(String familyName, int style) { long thisFontId = nextFontId.getAndIncrement(); FONTS.put(thisFontId, new FontDesc(familyName, style)); - if (getApiLevel() >= LOLLIPOP) { - return ReflectionHelpers.callConstructor( - Typeface.class, ClassParameter.from(long.class, thisFontId)); - } else { - return ReflectionHelpers.callConstructor( - Typeface.class, ClassParameter.from(int.class, (int) thisFontId)); - } + return ReflectionHelpers.callConstructor( + Typeface.class, ClassParameter.from(long.class, thisFontId)); } private static synchronized FontDesc findById(long fontId) { diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLocaleData.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLocaleData.java index 668ca53d2..df66e8dfd 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLocaleData.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLocaleData.java @@ -113,7 +113,7 @@ public class ShadowLocaleData { // Lollipop MR1 uses a String localDataReflector.setPercent("%"); } else { - // Upto Lollipop was a char + // Lollipop was a char localDataReflector.setPercent('%'); } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLocationManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLocationManager.java index b27bd96d7..dfad4ac83 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLocationManager.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLocationManager.java @@ -924,9 +924,9 @@ public class ShadowLocationManager { /** * Returns the list of {@link RoboLocationRequest} currently registered under the given provider. - * Since {@link LocationRequest} was not publicly visible prior to S, and did not exist prior to - * Kitkat, {@link RoboLocationRequest} allows querying the location requests prior to those - * platforms, and also implements proper equality comparisons for testing. + * Since {@link LocationRequest} was not publicly visible prior to S, {@link RoboLocationRequest} + * allows querying the location requests prior to those platforms, and also implements proper + * equality comparisons for testing. */ public List<RoboLocationRequest> getLegacyLocationRequests(String provider) { ProviderEntry providerEntry = getProviderEntry(provider); diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMediaCodec.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMediaCodec.java index 4bcc972dd..7ac10173e 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMediaCodec.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMediaCodec.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.LOLLIPOP; import static android.os.Build.VERSION_CODES.N_MR1; import static android.os.Build.VERSION_CODES.O; import static android.os.Build.VERSION_CODES.TIRAMISU; @@ -477,7 +476,7 @@ public class ShadowMediaCodec { protected void freeByteBufferLocked(@Nullable ByteBuffer buffer) {} /** Shadows CodecBuffer to prevent attempting to free non-direct ByteBuffer objects. */ - @Implements(className = "android.media.MediaCodec$BufferMap$CodecBuffer", minSdk = LOLLIPOP) + @Implements(className = "android.media.MediaCodec$BufferMap$CodecBuffer") protected static class ShadowCodecBuffer { // Seems to be required to work. diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMediaCodecList.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMediaCodecList.java index d16fd12e4..ad3634c81 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMediaCodecList.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMediaCodecList.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.LOLLIPOP; import static android.os.Build.VERSION_CODES.M; import static android.os.Build.VERSION_CODES.Q; @@ -23,7 +22,7 @@ import org.robolectric.util.ReflectionHelpers; * <p>Custom {@link MediaCodecInfo} can be created using {@link MediaCodecInfoBuilder} and added to * the list of codecs via {@link #addCodec}. */ -@Implements(value = MediaCodecList.class, minSdk = LOLLIPOP) +@Implements(value = MediaCodecList.class) public class ShadowMediaCodecList { private static final List<MediaCodecInfo> mediaCodecInfos = diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMediaController.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMediaController.java index 252bc427d..1c139b09a 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMediaController.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMediaController.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.LOLLIPOP; import static org.robolectric.util.reflector.Reflector.reflector; import android.annotation.NonNull; @@ -25,7 +24,7 @@ import org.robolectric.util.reflector.Direct; import org.robolectric.util.reflector.ForType; /** Implementation of {@link android.media.session.MediaController}. */ -@Implements(value = MediaController.class, minSdk = LOLLIPOP) +@Implements(value = MediaController.class) public class ShadowMediaController { @RealObject private MediaController realMediaController; private PlaybackState playbackState; diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMediaMuxer.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMediaMuxer.java index ee0bdff35..aa2793097 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMediaMuxer.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMediaMuxer.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.LOLLIPOP; import static android.os.Build.VERSION_CODES.O; import android.annotation.NonNull; @@ -25,7 +24,7 @@ import org.robolectric.util.ReflectionHelpers; * Implementation of {@link android.media.MediaMuxer} which directly passes input bytes to the * specified file, with no modification. */ -@Implements(value = MediaMuxer.class, minSdk = LOLLIPOP) +@Implements(value = MediaMuxer.class) public class ShadowMediaMuxer { // Maps between 'native' ids and corresponding output streams. private static final ConcurrentHashMap<Long, FileOutputStream> outputStreams = diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMediaSession.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMediaSession.java index cbe683f29..9e5ea3546 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMediaSession.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMediaSession.java @@ -1,13 +1,11 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.LOLLIPOP; - import android.content.Context; import android.media.session.MediaSession; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; -@Implements(value = MediaSession.class, minSdk = LOLLIPOP) +@Implements(value = MediaSession.class) public class ShadowMediaSession { @Implementation diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMediaSessionManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMediaSessionManager.java index 1e634d46c..1311d4561 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMediaSessionManager.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMediaSessionManager.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.LOLLIPOP; import static android.os.Build.VERSION_CODES.S; import static org.robolectric.util.ReflectionHelpers.createDeepProxy; import static org.robolectric.util.reflector.Reflector.reflector; @@ -24,7 +23,7 @@ import org.robolectric.util.reflector.Accessor; import org.robolectric.util.reflector.ForType; /** Shadow for {@link MediaSessionManager}. */ -@Implements(value = MediaSessionManager.class, minSdk = LOLLIPOP) +@Implements(value = MediaSessionManager.class) public class ShadowMediaSessionManager { private final List<MediaController> controllers = new CopyOnWriteArrayList<>(); private final Set<OnActiveSessionsChangedListener> listeners = new CopyOnWriteArraySet<>(); diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMemoryMappedFile.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMemoryMappedFile.java index 98421b076..a8f824c29 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMemoryMappedFile.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMemoryMappedFile.java @@ -1,8 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.LOLLIPOP; -import static org.robolectric.RuntimeEnvironment.getApiLevel; - import android.system.ErrnoException; import java.io.IOException; import java.io.InputStream; @@ -33,7 +30,7 @@ public class ShadowMemoryMappedFile { InputStream is = MemoryMappedFile.class.getResourceAsStream(TZ_DATA_2); if (is == null) { throw (Throwable) - exceptionClass().getConstructor(String.class, int.class).newInstance("open", -1); + ErrnoException.class.getConstructor(String.class, int.class).newInstance("open", -1); } try { MemoryMappedFile memoryMappedFile = new MemoryMappedFile(0L, 0L); @@ -42,7 +39,7 @@ public class ShadowMemoryMappedFile { return memoryMappedFile; } catch (IOException e) { throw (Throwable) - exceptionClass() + ErrnoException.class .getConstructor(String.class, int.class, Throwable.class) .newInstance("mmap", -1, e); } @@ -51,18 +48,6 @@ public class ShadowMemoryMappedFile { } } - private static Class exceptionClass() { - if (getApiLevel() >= LOLLIPOP) { - return ErrnoException.class; - } else { - try { - return MemoryMappedFile.class.getClassLoader().loadClass("libcore.io.ErrnoException"); - } catch (ClassNotFoundException e) { - throw new RuntimeException(e); - } - } - } - @Implementation public synchronized void close() throws Exception { bytes = null; diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMotionEvent.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMotionEvent.java index ba220ffe0..f575672a3 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMotionEvent.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMotionEvent.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; - import static android.os.Build.VERSION_CODES.M; import static android.os.Build.VERSION_CODES.P; import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE; @@ -635,12 +634,14 @@ public class ShadowMotionEvent extends ShadowInputEvent { @Implementation(minSdk = V.SDK_INT) @HiddenApi + @InDevelopment protected static float nativeGetRawXOffset(long nativePtr) { return getNativeMotionEvent(nativePtr).getXOffset(); } @Implementation(minSdk = V.SDK_INT) @HiddenApi + @InDevelopment protected static float nativeGetRawYOffset(long nativePtr) { return getNativeMotionEvent(nativePtr).getYOffset(); } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeFont.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeFont.java index 23f5b7f86..c06001499 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeFont.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeFont.java @@ -30,7 +30,6 @@ import org.robolectric.shadows.ShadowNativeFont.Picker; import org.robolectric.util.reflector.Accessor; import org.robolectric.util.reflector.ForType; import org.robolectric.versioning.AndroidVersions.U; -import org.robolectric.versioning.AndroidVersions.V; /** Shadow for {@link Font} that is backed by native code */ @Implements( @@ -40,14 +39,6 @@ import org.robolectric.versioning.AndroidVersions.V; isInAndroidSdk = false, callNativeMethodsByDefault = true) public class ShadowNativeFont { - - /** - * {@link android.graphics.fonts.Font} invokes its own native methods in its static initializer. - * This must be deferred starting in Android V. - */ - @Implementation(minSdk = V.SDK_INT) - protected static void __staticInitializer__() {} - @Implementation(minSdk = S, maxSdk = U.SDK_INT) protected static long nGetMinikinFontPtr(long font) { return FontNatives.nGetMinikinFontPtr(font); diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeFontsFontFamily.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeFontsFontFamily.java index d2174f977..39cb1ae0e 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeFontsFontFamily.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeFontsFontFamily.java @@ -12,7 +12,6 @@ import org.robolectric.nativeruntime.FontFamilyBuilderNatives; import org.robolectric.nativeruntime.FontsFontFamilyNatives; import org.robolectric.shadows.ShadowNativeFontsFontFamily.Picker; import org.robolectric.versioning.AndroidVersions.U; -import org.robolectric.versioning.AndroidVersions.V; /** Shadow for {@link FontFamily} that is backed by native code */ @Implements( @@ -51,10 +50,6 @@ public class ShadowNativeFontsFontFamily { isInAndroidSdk = false, callNativeMethodsByDefault = true) public static class ShadowNativeFontFamilyBuilder { - - @Implementation(minSdk = V.SDK_INT) - protected static void __staticInitializer__() {} - @Implementation(maxSdk = U.SDK_INT) protected static long nInitBuilder() { DefaultNativeRuntimeLoader.injectAndLoad(); diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeLineBreaker.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeLineBreaker.java index ce0b92f7e..96608acd2 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeLineBreaker.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeLineBreaker.java @@ -11,7 +11,6 @@ import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader; import org.robolectric.nativeruntime.LineBreakerNatives; import org.robolectric.shadows.ShadowNativeLineBreaker.Picker; import org.robolectric.versioning.AndroidVersions.U; -import org.robolectric.versioning.AndroidVersions.V; /** Shadow for {@link LineBreaker} that is backed by native code */ @Implements( @@ -20,10 +19,6 @@ import org.robolectric.versioning.AndroidVersions.V; shadowPicker = Picker.class, callNativeMethodsByDefault = true) public class ShadowNativeLineBreaker { - - @Implementation(minSdk = V.SDK_INT) - protected static void __staticInitializer__() {} - @Implementation(maxSdk = U.SDK_INT) protected static long nInit( int breakStrategy, int hyphenationFrequency, boolean isJustified, int[] indents) { diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePositionedGlyphs.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePositionedGlyphs.java index 63f20f28b..9b9d60c29 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePositionedGlyphs.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePositionedGlyphs.java @@ -8,7 +8,6 @@ import org.robolectric.nativeruntime.PositionedGlyphsNatives; import org.robolectric.shadows.ShadowNativePositionedGlyphs.Picker; import org.robolectric.versioning.AndroidVersions.S; import org.robolectric.versioning.AndroidVersions.U; -import org.robolectric.versioning.AndroidVersions.V; /** Shadow for {@link PositionedGlyphs} that is backed by native code */ @Implements( @@ -18,15 +17,6 @@ import org.robolectric.versioning.AndroidVersions.V; isInAndroidSdk = false, callNativeMethodsByDefault = true) public class ShadowNativePositionedGlyphs { - /** - * The {@link PositionedGlyphs} static initializer invokes its own native methods. This has to be - * deferred starting in Android V. - */ - @Implementation(minSdk = V.SDK_INT) - protected static void __staticInitializer__() { - // deferred - } - @Implementation(maxSdk = U.SDK_INT) protected static int nGetGlyphCount(long minikinLayout) { return PositionedGlyphsNatives.nGetGlyphCount(minikinLayout); diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeSurface.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeSurface.java index 3ccae4a45..f7cd7161a 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeSurface.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeSurface.java @@ -28,11 +28,11 @@ import org.robolectric.versioning.AndroidVersions.U; isInAndroidSdk = false, callNativeMethodsByDefault = true) public class ShadowNativeSurface { - @Implementation(maxSdk = U.SDK_INT) + @Implementation protected static long nativeCreateFromSurfaceTexture(SurfaceTexture surfaceTexture) throws OutOfResourcesException { - DefaultNativeRuntimeLoader.injectAndLoad(); - return SurfaceNatives.nativeCreateFromSurfaceTexture(surfaceTexture); + // SurfaceTexture is not available for host. + return 0; } @Implementation(maxSdk = U.SDK_INT) diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNetwork.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNetwork.java index d68b52a0f..6bd6ec733 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNetwork.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNetwork.java @@ -1,25 +1,22 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.LOLLIPOP; import static android.os.Build.VERSION_CODES.LOLLIPOP_MR1; import static android.os.Build.VERSION_CODES.M; import static android.os.Build.VERSION_CODES.R; import android.net.Network; -import com.google.common.base.Preconditions; import java.io.FileDescriptor; import java.net.DatagramSocket; import java.net.Socket; import java.util.HashSet; import java.util.Set; -import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; import org.robolectric.annotation.RealObject; import org.robolectric.shadow.api.Shadow; import org.robolectric.util.ReflectionHelpers; -@Implements(value = Network.class, minSdk = LOLLIPOP) +@Implements(value = Network.class) public class ShadowNetwork { @RealObject private Network realObject; @@ -31,15 +28,10 @@ public class ShadowNetwork { /** * Creates new instance of {@link Network}, because its constructor is hidden. * - * <p>Requires API 21 (Lollipop) and above. - * * @param netId The netId. * @return The Network instance. */ public static Network newInstance(int netId) { - Preconditions.checkState( - RuntimeEnvironment.getApiLevel() >= LOLLIPOP, - "android.net.Network requires API 21 (Lollipop) or above"); return Shadow.newInstance(Network.class, new Class[] {int.class}, new Object[] {netId}); } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNetworkCapabilities.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNetworkCapabilities.java index 4f03aa337..aa02fd957 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNetworkCapabilities.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNetworkCapabilities.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.LOLLIPOP; import static android.os.Build.VERSION_CODES.N; import static android.os.Build.VERSION_CODES.N_MR1; import static android.os.Build.VERSION_CODES.O; @@ -19,7 +18,7 @@ import org.robolectric.util.reflector.Direct; import org.robolectric.util.reflector.ForType; /** Robolectic provides overrides for fetching and updating transport. */ -@Implements(value = NetworkCapabilities.class, minSdk = LOLLIPOP, looseSignatures = true) +@Implements(value = NetworkCapabilities.class, looseSignatures = true) public class ShadowNetworkCapabilities { @RealObject protected NetworkCapabilities realNetworkCapabilities; diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNetworkScoreManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNetworkScoreManager.java index 71ace5e24..27b40e58d 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNetworkScoreManager.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNetworkScoreManager.java @@ -1,16 +1,11 @@ package org.robolectric.shadows; import android.net.NetworkScoreManager; -import android.os.Build; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; /** Provides testing APIs for {@link NetworkScoreManager}. */ -@Implements( - value = NetworkScoreManager.class, - isInAndroidSdk = false, - minSdk = Build.VERSION_CODES.LOLLIPOP -) +@Implements(value = NetworkScoreManager.class, isInAndroidSdk = false) public class ShadowNetworkScoreManager { private String activeScorerPackage; private boolean isScoringEnabled = true; diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNotificationListenerService.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNotificationListenerService.java index 7d0f86aa9..c8cc54c45 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNotificationListenerService.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNotificationListenerService.java @@ -24,7 +24,7 @@ import org.robolectric.util.ReflectionHelpers; import org.robolectric.util.ReflectionHelpers.ClassParameter; /** Shadow implementation of {@link NotificationListenerService}. */ -@Implements(value = NotificationListenerService.class, minSdk = VERSION_CODES.LOLLIPOP) +@Implements(value = NotificationListenerService.class) public class ShadowNotificationListenerService extends ShadowService { private static final AtomicInteger rebindRequestCount = new AtomicInteger(0); diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPackageInstaller.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPackageInstaller.java index 247ac2111..f828000fd 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPackageInstaller.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPackageInstaller.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.LOLLIPOP; import static android.os.Build.VERSION_CODES.O; import static android.os.Build.VERSION_CODES.P; import static android.os.Build.VERSION_CODES.S; @@ -40,11 +39,11 @@ import org.robolectric.annotation.RealObject; import org.robolectric.shadow.api.Shadow; /** Shadow for PackageInstaller. */ -@Implements(value = PackageInstaller.class, minSdk = LOLLIPOP) +@Implements(value = PackageInstaller.class) @SuppressLint("NewApi") public class ShadowPackageInstaller { /** Shadow for PackageInstaller.SessionInfo. */ - @Implements(value = PackageInstaller.SessionInfo.class, minSdk = LOLLIPOP) + @Implements(value = PackageInstaller.SessionInfo.class) public static class ShadowSessionInfo { @RealObject private SessionInfo sessionInfo; @@ -328,7 +327,7 @@ public class ShadowPackageInstaller { } /** Shadow for PackageInstaller.Session. */ - @Implements(value = PackageInstaller.Session.class, minSdk = LOLLIPOP) + @Implements(value = PackageInstaller.Session.class) public static class ShadowSession { private OutputStream outputStream; diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPackageManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPackageManager.java index f753496c7..941f49b65 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPackageManager.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPackageManager.java @@ -204,12 +204,10 @@ public class ShadowPackageManager { * @return existing or newly created activity info. */ public ActivityInfo addActivityIfNotPresent(ComponentName componentName) { + ActivityInfo activityInfo = updateName(componentName, new ActivityInfo()); + activityInfo.flags |= ActivityInfo.FLAG_HARDWARE_ACCELERATED; return addComponent( - activityFilters, - p -> p.activities, - (p, a) -> p.activities = a, - updateName(componentName, new ActivityInfo()), - false); + activityFilters, p -> p.activities, (p, a) -> p.activities = a, activityInfo, false); } /** diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPackageParser.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPackageParser.java index 0f31e379c..3383fd865 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPackageParser.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPackageParser.java @@ -2,7 +2,6 @@ package org.robolectric.shadows; import static android.os.Build.VERSION_CODES.LOLLIPOP; import static android.os.Build.VERSION_CODES.LOLLIPOP_MR1; -import static org.robolectric.util.reflector.Reflector.reflector; import android.content.pm.PackageInfo; import android.content.pm.PackageParser; @@ -18,7 +17,6 @@ import java.util.List; import java.util.Set; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Implements; -import org.robolectric.res.Fs; import org.robolectric.shadows.ShadowLog.LogItem; import org.robolectric.util.ReflectionHelpers; import org.robolectric.util.reflector.Accessor; @@ -35,20 +33,13 @@ public class ShadowPackageParser { PackageParser packageParser = new PackageParser(); try { - Package thePackage; - if (RuntimeEnvironment.getApiLevel() >= Build.VERSION_CODES.LOLLIPOP) { - // TODO(christianw/brettchabot): workaround for NPE from probable bug in Q. - // Can be removed when upstream properly handles a null callback - // PackageParser#setMinAspectRatio(Package) - if (RuntimeEnvironment.getApiLevel() >= Build.VERSION_CODES.Q) { - QHelper.setCallback(packageParser); - } - thePackage = packageParser.parsePackage(apkFile.toFile(), 0); - } else { // JB -> KK - thePackage = - reflector(_PackageParser_.class, packageParser) - .parsePackage(apkFile.toFile(), Fs.externalize(apkFile), new DisplayMetrics(), 0); + // TODO(christianw/brettchabot): workaround for NPE from probable bug in Q. + // Can be removed when upstream properly handles a null callback + // PackageParser#setMinAspectRatio(Package) + if (RuntimeEnvironment.getApiLevel() >= Build.VERSION_CODES.Q) { + QHelper.setCallback(packageParser); } + Package thePackage = packageParser.parsePackage(apkFile.toFile(), 0); if (thePackage == null) { List<LogItem> logItems = ShadowLog.getLogsForTag("PackageParser"); @@ -141,7 +132,7 @@ public class ShadowPackageParser { long lastUpdateTime) { int apiLevel = RuntimeEnvironment.getApiLevel(); - if (apiLevel <= LOLLIPOP) { + if (apiLevel == LOLLIPOP) { return generatePackageInfo( p, gids, diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowParcel.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowParcel.java index 817761991..af1181ea0 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowParcel.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowParcel.java @@ -337,7 +337,6 @@ public class ShadowParcel { } } - // nativeWriteBlob was introduced in lollipop, thus no need for a int nativePtr variant @Implementation protected static void nativeWriteBlob(long nativePtr, byte[] b, int offset, int len) { nativeWriteByteArray(nativePtr, b, offset, len); @@ -378,7 +377,6 @@ public class ShadowParcel { return NATIVE_BYTE_BUFFER_REGISTRY.getNativeObject(nativePtr).createByteArray(); } - // nativeReadBlob was introduced in lollipop, thus no need for a int nativePtr variant @Implementation protected static byte[] nativeReadBlob(long nativePtr) { return nativeCreateByteArray(nativePtr); diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPausedMessage.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPausedMessage.java index a23cae497..dd703bb0d 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPausedMessage.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPausedMessage.java @@ -1,11 +1,9 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.LOLLIPOP; import static org.robolectric.util.reflector.Reflector.reflector; import android.os.Handler; import android.os.Message; -import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; import org.robolectric.annotation.LooperMode; @@ -34,11 +32,7 @@ public class ShadowPausedMessage extends ShadowMessage { @Override @Implementation public void recycleUnchecked() { - if (RuntimeEnvironment.getApiLevel() >= LOLLIPOP) { - reflector(MessageReflector.class, realMessage).recycleUnchecked(); - } else { - reflector(MessageReflector.class, realMessage).recycle(); - } + reflector(MessageReflector.class, realMessage).recycleUnchecked(); } @Override diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPixelCopy.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPixelCopy.java index 9039e885e..f2bea724b 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPixelCopy.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPixelCopy.java @@ -85,16 +85,7 @@ public class ShadowPixelCopy { if (srcRect != null && srcRect.isEmpty()) { throw new IllegalArgumentException("sourceRect is empty"); } - View view = source.getDecorView(); - Rect adjustedSrcRect = null; - if (srcRect != null) { - adjustedSrcRect = new Rect(srcRect); - int[] locationInWindow = new int[2]; - view.getLocationInWindow(locationInWindow); - // offset the srcRect by the decor view's location in the window - adjustedSrcRect.offset(-locationInWindow[0], -locationInWindow[1]); - } - takeScreenshot(view, dest, adjustedSrcRect); + takeScreenshot(source.getDecorView(), dest, srcRect); alertFinished(listener, listenerThread, PixelCopy.SUCCESS); } @@ -168,7 +159,7 @@ public class ShadowPixelCopy { Bitmap bitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888); - if (HardwareRenderingScreenshot.canTakeScreenshot()) { + if (HardwareRenderingScreenshot.canTakeScreenshot(view)) { PerfStatsCollector.getInstance() .measure( "ShadowPixelCopy-Hardware", diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPosix.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPosix.java index c85cf8f9c..8a0af78db 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPosix.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPosix.java @@ -6,10 +6,8 @@ import android.system.StructStat; import java.io.File; import java.io.FileDescriptor; import java.time.Duration; -import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; -import org.robolectric.util.ReflectionHelpers; /** Shadow for {@link libcore.io.Posix} */ @Implements( @@ -33,8 +31,7 @@ public class ShadowPosix { modifiedTime = Duration.ofMillis(file.lastModified()).getSeconds(); } - if (RuntimeEnvironment.getApiLevel() >= Build.VERSION_CODES.LOLLIPOP) { - return new StructStat( + return new StructStat( 1, // st_dev 0, // st_ino mode, // st_mode @@ -49,16 +46,6 @@ public class ShadowPosix { 0, // st_blksize 0 // st_blocks ); - } else { - Object structStat = - ReflectionHelpers.newInstance( - ReflectionHelpers.loadClass( - ShadowPosix.class.getClassLoader(), "libcore.io.StructStat")); - setMode(mode, structStat); - setSize(size, structStat); - setModifiedTime(modifiedTime, structStat); - return structStat; - } } @Implementation @@ -70,16 +57,4 @@ public class ShadowPosix { protected static Object fstat(FileDescriptor fd) throws ErrnoException { return stat(null); } - - private static void setMode(int mode, Object structStat) { - ReflectionHelpers.setField(structStat, "st_mode", mode); - } - - private static void setSize(long size, Object structStat) { - ReflectionHelpers.setField(structStat, "st_size", size); - } - - private static void setModifiedTime(long modifiedTime, Object structStat) { - ReflectionHelpers.setField(structStat, "st_mtime", modifiedTime); - } } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowProcessInitializer.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowProcessInitializer.java new file mode 100644 index 000000000..40c7b8e0a --- /dev/null +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowProcessInitializer.java @@ -0,0 +1,41 @@ +package org.robolectric.shadows; + +import static android.os.Build.VERSION_CODES.O; +import static android.os.Build.VERSION_CODES.P; +import static org.robolectric.util.reflector.Reflector.reflector; + +import org.robolectric.annotation.Implements; +import org.robolectric.annotation.Resetter; +import org.robolectric.util.reflector.Accessor; +import org.robolectric.util.reflector.Constructor; +import org.robolectric.util.reflector.ForType; +import org.robolectric.util.reflector.Static; +import org.robolectric.util.reflector.WithType; + +/** + * Shadow for ThreadedRenderer.ProcessInitializer. This sets up some services for real Android which + * are not used in Robolectric. + */ +@Implements( + className = "android.view.ThreadedRenderer$ProcessInitializer", + isInAndroidSdk = false, + minSdk = O, + maxSdk = P) +public class ShadowProcessInitializer { + @Resetter + public static void reset() { + reflector(ProcessInitializerReflector.class) + .setSInstance(reflector(ProcessInitializerReflector.class).newSInstance()); + } + + @ForType(className = "android.view.ThreadedRenderer$ProcessInitializer") + interface ProcessInitializerReflector { + @Constructor + Object newSInstance(); + + @Accessor("sInstance") + @Static + void setSInstance( + @WithType("android.view.ThreadedRenderer$ProcessInitializer;") Object instance); + } +} diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowRenderNode.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowRenderNode.java index 6c3d62b48..64f85e7e7 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowRenderNode.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowRenderNode.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.LOLLIPOP; import static android.os.Build.VERSION_CODES.P; import android.graphics.Camera; @@ -12,7 +11,6 @@ import org.robolectric.annotation.Implements; @Implements( className = "android.view.RenderNode", isInAndroidSdk = false, - minSdk = LOLLIPOP, maxSdk = P) public class ShadowRenderNode { private static final float NON_ZERO_EPSILON = 0.001f; diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowRenderNodeAnimator.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowRenderNodeAnimator.java index 5e958be4e..cc7c758c1 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowRenderNodeAnimator.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowRenderNodeAnimator.java @@ -55,7 +55,7 @@ public class ShadowRenderNodeAnimator { @Implementation public void doStart() { reflector(RenderNodeAnimatorReflector.class, realObject).doStart(); - if (getApiLevel() <= LOLLIPOP) { + if (getApiLevel() == LOLLIPOP) { schedule(); } } @@ -65,7 +65,7 @@ public class ShadowRenderNodeAnimator { RenderNodeAnimatorReflector renderNodeReflector = reflector(RenderNodeAnimatorReflector.class, realObject); renderNodeReflector.cancel(); - if (getApiLevel() <= LOLLIPOP) { + if (getApiLevel() == LOLLIPOP) { int state = renderNodeReflector.getState(); if (state != STATE_FINISHED) { // In 21, RenderNodeAnimator only calls nEnd, it doesn't call the Java end method. Thus, it diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowResolveInfo.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowResolveInfo.java index 0ff822388..4390ecb12 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowResolveInfo.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowResolveInfo.java @@ -3,7 +3,6 @@ package org.robolectric.shadows; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.ResolveInfo; -import android.os.Build; /** Utilities for {@link ResolveInfo}. */ // TODO: Create a ResolveInfoBuilder in androidx and migrate factory methods there. @@ -46,23 +45,7 @@ public class ShadowResolveInfo { * <p>Note that this is shallow copy as performed by the copy constructor existing in API 17. */ public static ResolveInfo newResolveInfo(ResolveInfo orig) { - ResolveInfo copy; - if (Build.VERSION.SDK_INT >= 17) { - copy = new ResolveInfo(orig); - } else { - copy = new ResolveInfo(); - copy.activityInfo = orig.activityInfo; - copy.serviceInfo = orig.serviceInfo; - copy.filter = orig.filter; - copy.priority = orig.priority; - copy.preferredOrder = orig.preferredOrder; - copy.match = orig.match; - copy.specificIndex = orig.specificIndex; - copy.labelRes = orig.labelRes; - copy.nonLocalizedLabel = orig.nonLocalizedLabel; - copy.icon = orig.icon; - copy.resolvePackageName = orig.resolvePackageName; - } + ResolveInfo copy = new ResolveInfo(orig); // For some reason isDefault field is not copied by the copy constructor. copy.isDefault = orig.isDefault; return copy; diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowResources.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowResources.java index 97d84ad55..bd0d19df2 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowResources.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowResources.java @@ -1,12 +1,7 @@ package org.robolectric.shadows; - -import static android.os.Build.VERSION_CODES.LOLLIPOP; -import static android.os.Build.VERSION_CODES.M; -import static android.os.Build.VERSION_CODES.N; import static android.os.Build.VERSION_CODES.N_MR1; import static android.os.Build.VERSION_CODES.Q; -import static org.robolectric.shadows.ShadowAssetManager.legacyShadowOf; import static org.robolectric.util.reflector.Reflector.reflector; import android.content.res.AssetFileDescriptor; @@ -15,24 +10,19 @@ import android.content.res.CompatibilityInfo; import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.Resources.NotFoundException; -import android.content.res.ResourcesImpl; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; import android.graphics.drawable.Drawable; -import android.os.ParcelFileDescriptor; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.util.LongSparseArray; import android.util.TypedValue; -import java.io.FileInputStream; -import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.HashSet; import java.util.List; -import java.util.Locale; import java.util.Set; import org.robolectric.RuntimeEnvironment; import org.robolectric.android.Bootstrap; @@ -42,14 +32,7 @@ import org.robolectric.annotation.Implements; import org.robolectric.annotation.RealObject; import org.robolectric.annotation.Resetter; import org.robolectric.internal.bytecode.ShadowedObject; -import org.robolectric.res.Plural; -import org.robolectric.res.PluralRules; -import org.robolectric.res.ResName; -import org.robolectric.res.ResType; -import org.robolectric.res.ResourceTable; -import org.robolectric.res.TypedResource; import org.robolectric.shadow.api.Shadow; -import org.robolectric.shadows.ShadowLegacyResourcesImpl.ShadowLegacyThemeImpl; import org.robolectric.util.ReflectionHelpers; import org.robolectric.util.reflector.Direct; import org.robolectric.util.reflector.ForType; @@ -87,149 +70,23 @@ public class ShadowResources { return system; } - @Implementation - protected TypedArray obtainAttributes(AttributeSet set, int[] attrs) { - if (isLegacyAssetManager()) { - return legacyShadowOf(realResources.getAssets()) - .attrsToTypedArray(realResources, set, attrs, 0, 0, 0); - } else { - return reflector(ResourcesReflector.class, realResources).obtainAttributes(set, attrs); - } - } - - @Implementation - protected String getQuantityString(int id, int quantity, Object... formatArgs) - throws Resources.NotFoundException { - if (isLegacyAssetManager()) { - String raw = getQuantityString(id, quantity); - return String.format(Locale.ENGLISH, raw, formatArgs); - } else { - return reflector(ResourcesReflector.class, realResources) - .getQuantityString(id, quantity, formatArgs); - } - } - - @Implementation - protected String getQuantityString(int resId, int quantity) throws Resources.NotFoundException { - if (isLegacyAssetManager()) { - ShadowLegacyAssetManager shadowAssetManager = legacyShadowOf(realResources.getAssets()); - - TypedResource typedResource = - shadowAssetManager.getResourceTable().getValue(resId, shadowAssetManager.config); - if (typedResource != null && typedResource instanceof PluralRules) { - PluralRules pluralRules = (PluralRules) typedResource; - Plural plural = pluralRules.find(quantity); - - if (plural == null) { - return null; - } - - TypedResource<?> resolvedTypedResource = - shadowAssetManager.resolve( - new TypedResource<>( - plural.getString(), ResType.CHAR_SEQUENCE, pluralRules.getXmlContext()), - shadowAssetManager.config, - resId); - return resolvedTypedResource == null ? null : resolvedTypedResource.asString(); - } else { - return null; - } - } else { - return reflector(ResourcesReflector.class, realResources).getQuantityString(resId, quantity); - } - } - - @Implementation - protected InputStream openRawResource(int id) throws Resources.NotFoundException { - if (isLegacyAssetManager()) { - ShadowLegacyAssetManager shadowAssetManager = legacyShadowOf(realResources.getAssets()); - ResourceTable resourceTable = shadowAssetManager.getResourceTable(); - InputStream inputStream = resourceTable.getRawValue(id, shadowAssetManager.config); - if (inputStream == null) { - throw newNotFoundException(id); - } else { - return inputStream; - } - } else { - return reflector(ResourcesReflector.class, realResources).openRawResource(id); - } - } - - /** - * Since {@link AssetFileDescriptor}s are not yet supported by Robolectric, {@code null} will be - * returned if the resource is found. If the resource cannot be found, {@link - * Resources.NotFoundException} will be thrown. - */ - @Implementation - protected AssetFileDescriptor openRawResourceFd(int id) throws Resources.NotFoundException { - if (isLegacyAssetManager()) { - InputStream inputStream = openRawResource(id); - if (!(inputStream instanceof FileInputStream)) { - // todo fixme - return null; - } - - FileInputStream fis = (FileInputStream) inputStream; - try { - return new AssetFileDescriptor( - ParcelFileDescriptor.dup(fis.getFD()), 0, fis.getChannel().size()); - } catch (IOException e) { - throw newNotFoundException(id); - } - } else { - return reflector(ResourcesReflector.class, realResources).openRawResourceFd(id); - } - } - - private Resources.NotFoundException newNotFoundException(int id) { - ResourceTable resourceTable = legacyShadowOf(realResources.getAssets()).getResourceTable(); - ResName resName = resourceTable.getResName(id); - if (resName == null) { - return new Resources.NotFoundException("resource ID #0x" + Integer.toHexString(id)); - } else { - return new Resources.NotFoundException(resName.getFullyQualifiedName()); - } - } - - @Implementation - protected TypedArray obtainTypedArray(int id) throws Resources.NotFoundException { - if (isLegacyAssetManager()) { - ShadowLegacyAssetManager shadowAssetManager = legacyShadowOf(realResources.getAssets()); - TypedArray typedArray = shadowAssetManager.getTypedArrayResource(realResources, id); - if (typedArray != null) { - return typedArray; - } else { - throw newNotFoundException(id); - } - } else { - return reflector(ResourcesReflector.class, realResources).obtainTypedArray(id); - } - } - @HiddenApi @Implementation protected XmlResourceParser loadXmlResourceParser(int resId, String type) throws Resources.NotFoundException { - if (isLegacyAssetManager()) { - ShadowLegacyAssetManager shadowAssetManager = legacyShadowOf(realResources.getAssets()); - return setSourceResourceId(shadowAssetManager.loadXmlResourceParser(resId, type), resId); - } else { - ResourcesReflector relectedResources = reflector(ResourcesReflector.class, realResources); - return setSourceResourceId(relectedResources.loadXmlResourceParser(resId, type), resId); - } + + ResourcesReflector relectedResources = reflector(ResourcesReflector.class, realResources); + return setSourceResourceId(relectedResources.loadXmlResourceParser(resId, type), resId); } @HiddenApi @Implementation protected XmlResourceParser loadXmlResourceParser( String file, int id, int assetCookie, String type) throws Resources.NotFoundException { - if (isLegacyAssetManager()) { - return loadXmlResourceParser(id, type); - } else { - ResourcesReflector relectedResources = reflector(ResourcesReflector.class, realResources); - return setSourceResourceId( - relectedResources.loadXmlResourceParser(file, id, assetCookie, type), id); - } + + ResourcesReflector relectedResources = reflector(ResourcesReflector.class, realResources); + return setSourceResourceId( + relectedResources.loadXmlResourceParser(file, id, assetCookie, type), id); } private static XmlResourceParser setSourceResourceId(XmlResourceParser parser, int resourceId) { @@ -333,63 +190,6 @@ public class ShadowResources { } } - /** Base class for shadows of {@link Resources.Theme}. */ - public abstract static class ShadowTheme { - - /** Shadow picker for {@link ShadowTheme}. */ - public static class Picker extends ResourceModeShadowPicker<ShadowTheme> { - - public Picker() { - super(ShadowLegacyTheme.class, null, null); - } - } - } - - /** Shadow for {@link Resources.Theme}. */ - @Implements(value = Resources.Theme.class, shadowPicker = ShadowTheme.Picker.class) - public static class ShadowLegacyTheme extends ShadowTheme { - @RealObject Resources.Theme realTheme; - - long getNativePtr() { - if (RuntimeEnvironment.getApiLevel() >= N) { - ResourcesImpl.ThemeImpl themeImpl = ReflectionHelpers.getField(realTheme, "mThemeImpl"); - return ((ShadowLegacyThemeImpl) Shadow.extract(themeImpl)).getNativePtr(); - } else { - return ((Number) ReflectionHelpers.getField(realTheme, "mTheme")).longValue(); - } - } - - @Implementation(maxSdk = M) - protected TypedArray obtainStyledAttributes(int[] attrs) { - return obtainStyledAttributes(0, attrs); - } - - @Implementation(maxSdk = M) - protected TypedArray obtainStyledAttributes(int resid, int[] attrs) - throws Resources.NotFoundException { - return obtainStyledAttributes(null, attrs, 0, resid); - } - - @Implementation(maxSdk = M) - protected TypedArray obtainStyledAttributes( - AttributeSet set, int[] attrs, int defStyleAttr, int defStyleRes) { - return getShadowAssetManager() - .attrsToTypedArray( - innerGetResources(), set, attrs, defStyleAttr, getNativePtr(), defStyleRes); - } - - private ShadowLegacyAssetManager getShadowAssetManager() { - return legacyShadowOf(innerGetResources().getAssets()); - } - - private Resources innerGetResources() { - if (RuntimeEnvironment.getApiLevel() >= LOLLIPOP) { - return realTheme.getResources(); - } - return ReflectionHelpers.getField(realTheme, "this$0"); - } - } - static void setCreatedFromResId(Resources resources, int id, Drawable drawable) { // todo: this kinda sucks, find some better way... if (drawable != null && Shadow.extract(drawable) instanceof ShadowDrawable) { @@ -406,10 +206,6 @@ public class ShadowResources { } } - private boolean isLegacyAssetManager() { - return ShadowAssetManager.useLegacy(); - } - /** Shadow for {@link Resources.NotFoundException}. */ @Implements(Resources.NotFoundException.class) public static class ShadowNotFoundException { diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowResourcesImpl.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowResourcesImpl.java index 97f96efe3..7cfe1954d 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowResourcesImpl.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowResourcesImpl.java @@ -6,15 +6,13 @@ import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.List; -import org.robolectric.shadows.ShadowLegacyResourcesImpl.ShadowLegacyThemeImpl; abstract public class ShadowResourcesImpl { public static class Picker extends ResourceModeShadowPicker<ShadowResourcesImpl> { public Picker() { - super(ShadowLegacyResourcesImpl.class, ShadowArscResourcesImpl.class, - ShadowArscResourcesImpl.class); + super(ShadowArscResourcesImpl.class, ShadowArscResourcesImpl.class); } } @@ -47,13 +45,4 @@ abstract public class ShadowResourcesImpl { } return resettableArrays; } - - abstract public static class ShadowThemeImpl { - public static class Picker extends ResourceModeShadowPicker<ShadowThemeImpl> { - - public Picker() { - super(ShadowLegacyThemeImpl.class, null, null); - } - } - } } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowRestrictionsManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowRestrictionsManager.java index 5334dd9ca..2800b8669 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowRestrictionsManager.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowRestrictionsManager.java @@ -1,14 +1,12 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.LOLLIPOP; - import android.content.RestrictionsManager; import android.os.Bundle; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; /** Shadow of {@link android.content.RestrictionsManager}. */ -@Implements(value = RestrictionsManager.class, minSdk=LOLLIPOP) +@Implements(value = RestrictionsManager.class) public class ShadowRestrictionsManager { private Bundle applicationRestrictions; diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowServiceManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowServiceManager.java index fe74b37f1..5602d4c1a 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowServiceManager.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowServiceManager.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.LOLLIPOP; import static android.os.Build.VERSION_CODES.M; import static android.os.Build.VERSION_CODES.N; import static android.os.Build.VERSION_CODES.N_MR1; @@ -189,20 +188,18 @@ public class ShadowServiceManager { addBinderService(binderServices, Context.APP_OPS_SERVICE, IAppOpsService.class); addBinderService(binderServices, "batteryproperties", IBatteryPropertiesRegistrar.class); - if (RuntimeEnvironment.getApiLevel() >= LOLLIPOP) { - addBinderService(binderServices, Context.RESTRICTIONS_SERVICE, IRestrictionsManager.class); - addBinderService(binderServices, Context.TRUST_SERVICE, ITrustManager.class); - addBinderService(binderServices, Context.JOB_SCHEDULER_SERVICE, IJobScheduler.class); - addBinderService(binderServices, Context.NETWORK_SCORE_SERVICE, INetworkScoreService.class); - addBinderService(binderServices, Context.USAGE_STATS_SERVICE, IUsageStatsManager.class); - addBinderService(binderServices, Context.MEDIA_ROUTER_SERVICE, IMediaRouterService.class); - addBinderService(binderServices, Context.MEDIA_SESSION_SERVICE, ISessionManager.class, true); - addBinderService( - binderServices, - Context.VOICE_INTERACTION_MANAGER_SERVICE, - IVoiceInteractionManagerService.class, - true); - } + addBinderService(binderServices, Context.RESTRICTIONS_SERVICE, IRestrictionsManager.class); + addBinderService(binderServices, Context.TRUST_SERVICE, ITrustManager.class); + addBinderService(binderServices, Context.JOB_SCHEDULER_SERVICE, IJobScheduler.class); + addBinderService(binderServices, Context.NETWORK_SCORE_SERVICE, INetworkScoreService.class); + addBinderService(binderServices, Context.USAGE_STATS_SERVICE, IUsageStatsManager.class); + addBinderService(binderServices, Context.MEDIA_ROUTER_SERVICE, IMediaRouterService.class); + addBinderService(binderServices, Context.MEDIA_SESSION_SERVICE, ISessionManager.class, true); + addBinderService( + binderServices, + Context.VOICE_INTERACTION_MANAGER_SERVICE, + IVoiceInteractionManagerService.class, + true); if (RuntimeEnvironment.getApiLevel() >= M) { addBinderService(binderServices, Context.FINGERPRINT_SERVICE, IFingerprintService.class); } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSettings.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSettings.java index e5a7f5676..3df973d66 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSettings.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSettings.java @@ -4,6 +4,7 @@ import static android.os.Build.VERSION_CODES.M; import static android.os.Build.VERSION_CODES.P; import static android.os.Build.VERSION_CODES.Q; import static android.os.Build.VERSION_CODES.R; +import static android.os.Build.VERSION_CODES.TIRAMISU; import static android.provider.Settings.Secure.LOCATION_MODE_OFF; import static org.robolectric.util.reflector.Reflector.reflector; @@ -31,6 +32,7 @@ import org.robolectric.annotation.Implements; import org.robolectric.annotation.Resetter; import org.robolectric.util.reflector.ForType; import org.robolectric.util.reflector.Static; +import org.robolectric.versioning.AndroidVersions.U; @SuppressWarnings({"UnusedDeclaration"}) @Implements(Settings.class) @@ -554,17 +556,102 @@ public class ShadowSettings { * <p>This shadow is primarily to support {@link android.provider.DeviceConfig}, which queries * {@link Settings.Config}. {@link android.provider.DeviceConfig} is pure Java code so it's not * necessary to shadow that directly. - * - * <p>The underlying implementation calls into a system content provider. Starting in Android U, - * the internal logic of Activity is querying DeviceConfig, so to avoid crashes we need to make - * DeviceConfig a no-op. */ - @Implements(value = Settings.Config.class, isInAndroidSdk = false) + @Implements(value = Settings.Config.class, isInAndroidSdk = false, minSdk = Q) public static class ShadowConfig { + + private static final Map<String, String> settings = new ConcurrentHashMap<>(); + + @Implementation(maxSdk = Q) + protected static boolean putString( + ContentResolver cr, String name, String value, boolean makeDefault) { + return put(name, value); + } + + @Implementation(minSdk = R, maxSdk = TIRAMISU) + protected static boolean putString( + ContentResolver cr, String namespace, String name, String value, boolean makeDefault) { + String key = reflector(SettingsConfigReflector.class).createCompositeName(namespace, name); + return put(key, value); + } + + @Implementation(minSdk = U.SDK_INT) + protected static boolean putString( + String namespace, String name, String value, boolean makeDefault) { + String key = reflector(SettingsConfigReflector.class).createCompositeName(namespace, name); + return put(key, value); + } + + @Implementation(maxSdk = TIRAMISU) + protected static String getString(ContentResolver cr, String name) { + return get(name); + } + + @Implementation(minSdk = U.SDK_INT) + protected static String getString(String name) { + return get(name); + } + @Implementation(minSdk = R) protected static Map<String, String> getStrings( ContentResolver resolver, String namespace, List<String> names) { - return ImmutableMap.of(); + + Map<String, String> result = new HashMap<>(); + for (Map.Entry<String, String> entry : settings.entrySet()) { + String key = entry.getKey(); + if (!key.startsWith(namespace + "/")) { + continue; + } + String keyWithoutNamespace = key.substring(namespace.length() + 1); + if (names == null || names.isEmpty()) { + result.put(keyWithoutNamespace, entry.getValue()); + } else if (names.contains(keyWithoutNamespace)) { + result.put(keyWithoutNamespace, entry.getValue()); + } + } + return ImmutableMap.copyOf(result); + } + + private static boolean put(String name, String value) { + settings.put(name, value); + return true; + } + + @Implementation(minSdk = R) + protected static boolean setStrings( + ContentResolver cr, String namespace, Map<String, String> keyValues) { + + synchronized (settings) { + settings.entrySet().removeIf(entry -> entry.getKey().startsWith(namespace + "/")); + for (Map.Entry<String, String> entry : keyValues.entrySet()) { + String key = + reflector(SettingsConfigReflector.class) + .createCompositeName(namespace, entry.getKey()); + put(key, entry.getValue()); + } + } + return true; + } + + @Implementation(minSdk = U.SDK_INT) + protected static boolean deleteString(String namespace, String name) { + String key = reflector(SettingsConfigReflector.class).createCompositeName(namespace, name); + settings.remove(key); + return true; + } + + @Implementation(minSdk = TIRAMISU, maxSdk = TIRAMISU) + protected static boolean deleteString(ContentResolver resolver, String namespace, String name) { + return deleteString(namespace, name); + } + + private static String get(String name) { + return settings.get(name); + } + + @Resetter + public static void reset() { + settings.clear(); } } @@ -578,4 +665,10 @@ public class ShadowSettings { @Static int getLocationModeForUser(ContentResolver cr, int userId); } + + @ForType(Settings.Config.class) + interface SettingsConfigReflector { + @Static + String createCompositeName(String namespace, String name); + } } 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 ec5a63cc4..b7375e557 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSpeechRecognizer.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSpeechRecognizer.java @@ -11,10 +11,13 @@ import android.content.Intent; import android.os.Build.VERSION_CODES; import android.os.Bundle; import android.os.Handler; +import android.os.IBinder; 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 com.google.common.base.Preconditions; import java.util.Queue; @@ -35,34 +38,16 @@ import org.robolectric.versioning.AndroidVersions.U; @Implements(value = SpeechRecognizer.class, looseSignatures = true) public class ShadowSpeechRecognizer { - @RealObject SpeechRecognizer realSpeechRecognizer; - protected static SpeechRecognizer latestSpeechRecognizer; - private Intent recognizerIntent; - private RecognitionListener recognitionListener; - private static boolean isOnDeviceRecognitionAvailable = true; - private boolean isRecognizerDestroyed = false; + @SuppressWarnings("NonFinalStaticField") + private static SpeechRecognizer latestSpeechRecognizer; - 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(Context)} is called. - */ - public static SpeechRecognizer getLatestSpeechRecognizer() { - return latestSpeechRecognizer; - } + @SuppressWarnings("NonFinalStaticField") + private static boolean isOnDeviceRecognitionAvailable = true; - /** Returns the argument passed to the last call to {@link SpeechRecognizer#startListening}. */ - public Intent getLastRecognizerIntent() { - return recognizerIntent; - } + @RealObject SpeechRecognizer realSpeechRecognizer; - /** Returns true iff the destroy method of was invoked for the recognizer. */ - public boolean isDestroyed() { - return isRecognizerDestroyed; - } + // NOTE: Do not manipulate state directly in this class. Call {@link #getState()} instead. + private final ShadowSpeechRecognizerState state = new ShadowSpeechRecognizerState(); @Resetter public static void reset() { @@ -70,10 +55,12 @@ public class ShadowSpeechRecognizer { isOnDeviceRecognitionAvailable = true; } - @Implementation - protected void destroy() { - isRecognizerDestroyed = true; - reflector(SpeechRecognizerReflector.class, realSpeechRecognizer).destroy(); + /** + * Returns the latest SpeechRecognizer. This method can only be called after {@link + * SpeechRecognizer#createSpeechRecognizer(Context)} is called. + */ + public static SpeechRecognizer getLatestSpeechRecognizer() { + return latestSpeechRecognizer; } @Implementation @@ -86,145 +73,264 @@ public class ShadowSpeechRecognizer { return result; } - @Implementation - protected void startListening(Intent recognizerIntent) { - this.recognizerIntent = recognizerIntent; - // from the implementation of {@link SpeechRecognizer#startListening} it seems that it allows - // running the method on an already destroyed object, so we replicate the same by resetting - // isRecognizerDestroyed - isRecognizerDestroyed = false; - // the real implementation connects to a service - // simulate the resulting behavior once the service is connected - Handler mainHandler = new Handler(Looper.getMainLooper()); - // perform the onServiceConnected logic - mainHandler.post( - () -> { - SpeechRecognizerReflector recognizerReflector = - reflector(SpeechRecognizerReflector.class, realSpeechRecognizer); - recognizerReflector.setService( - ReflectionHelpers.createNullProxy(IRecognitionService.class)); - Queue<Message> pendingTasks = recognizerReflector.getPendingTasks(); - while (!pendingTasks.isEmpty()) { - recognizerReflector.getHandler().sendMessage(pendingTasks.poll()); - } - }); + @Implementation(minSdk = VERSION_CODES.TIRAMISU) + protected static SpeechRecognizer createOnDeviceSpeechRecognizer(final Context context) { + SpeechRecognizer result = + reflector(SpeechRecognizerReflector.class).createOnDeviceSpeechRecognizer(context); + latestSpeechRecognizer = result; + return result; + } + + public static void setIsOnDeviceRecognitionAvailable(boolean available) { + isOnDeviceRecognitionAvailable = available; + } + + @Implementation(minSdk = VERSION_CODES.TIRAMISU) + protected static boolean isOnDeviceRecognitionAvailable(final Context context) { + return isOnDeviceRecognitionAvailable; + } + + /** + * Returns the state of this shadow instance. + * + * <p>Subclasses may override this function to customize which state is returned. + */ + protected ShadowSpeechRecognizerState getState() { + return state; } /** - * Handles changing the listener and allows access to the internal listener to trigger events and - * sets the latest SpeechRecognizer. + * Returns the {@link ShadowSpeechRecognizerDirectAccessors} implementation that can handle direct + * access to functions/variables of a real {@link SpeechRecognizer}. + * + * <p>Subclasses may override this function to customize access in case they are shadowing a + * subclass of {@link SpeechRecognizer} that functions differently than the parent class. */ + protected ShadowSpeechRecognizerDirectAccessors getDirectAccessors() { + return reflector(SpeechRecognizerReflector.class, realSpeechRecognizer); + } + + /** Returns true iff the destroy method of was invoked for the recognizer. */ + public boolean isDestroyed() { + return getState().isRecognizerDestroyed; + } + + @Implementation(maxSdk = U.SDK_INT) + protected void destroy() { + getState().isRecognizerDestroyed = true; + getDirectAccessors().destroy(); + } + + /** Returns the argument passed to the last call to {@link SpeechRecognizer#startListening}. */ + public Intent getLastRecognizerIntent() { + return getState().recognizerIntent; + } + + @Implementation(maxSdk = U.SDK_INT) + protected void startListening(Intent recognizerIntent) { + // Record the most recent requested intent. + ShadowSpeechRecognizerState shadowState = getState(); + shadowState.recognizerIntent = recognizerIntent; + + // From the implementation of {@link SpeechRecognizer#startListening} it seems that it allows + // running the method on an already destroyed object, so we replicate the same by resetting + // isRecognizerDestroyed. + shadowState.isRecognizerDestroyed = false; + + // The real implementation connects to a service simulate the resulting behavior once + // the service is connected. + new Handler(Looper.getMainLooper()) + .post( + () -> { + ShadowSpeechRecognizerDirectAccessors directAccessors = getDirectAccessors(); + directAccessors.setService(createFakeSpeechRecognitionService()); + + Handler taskHandler = directAccessors.getHandler(); + Queue<Message> pendingTasks = directAccessors.getPendingTasks(); + while (!pendingTasks.isEmpty()) { + taskHandler.sendMessage(pendingTasks.poll()); + } + }); + } + + /** Handles changing the listener and allows access to the internal listener to trigger events. */ @Implementation(maxSdk = U.SDK_INT) // TODO(hoisie): Update this to support Android V @InDevelopment protected void handleChangeListener(RecognitionListener listener) { - recognitionListener = listener; + getState().recognitionListener = listener; } public void triggerOnEndOfSpeech() { - recognitionListener.onEndOfSpeech(); + getState().recognitionListener.onEndOfSpeech(); } public void triggerOnError(int error) { - recognitionListener.onError(error); + getState().recognitionListener.onError(error); } public void triggerOnReadyForSpeech(Bundle bundle) { - recognitionListener.onReadyForSpeech(bundle); + getState().recognitionListener.onReadyForSpeech(bundle); } public void triggerOnPartialResults(Bundle bundle) { - recognitionListener.onPartialResults(bundle); + getState().recognitionListener.onPartialResults(bundle); } public void triggerOnResults(Bundle bundle) { - recognitionListener.onResults(bundle); + getState().recognitionListener.onResults(bundle); } public void triggerOnRmsChanged(float rmsdB) { - recognitionListener.onRmsChanged(rmsdB); - } - - @Implementation(minSdk = VERSION_CODES.TIRAMISU) - protected static SpeechRecognizer createOnDeviceSpeechRecognizer(final Context context) { - SpeechRecognizer result = - reflector(SpeechRecognizerReflector.class).createOnDeviceSpeechRecognizer(context); - latestSpeechRecognizer = result; - return result; - } - - @Implementation(minSdk = VERSION_CODES.TIRAMISU) - protected static boolean isOnDeviceRecognitionAvailable(final Context context) { - return isOnDeviceRecognitionAvailable; + getState().recognitionListener.onRmsChanged(rmsdB); } @RequiresApi(api = VERSION_CODES.TIRAMISU) - @Implementation(minSdk = VERSION_CODES.TIRAMISU) + @Implementation(minSdk = VERSION_CODES.TIRAMISU, maxSdk = U.SDK_INT) protected void checkRecognitionSupport( @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; + Preconditions.checkArgument(supportListener instanceof RecognitionSupportCallback); + + ShadowSpeechRecognizerState shadowState = getState(); + shadowState.recognitionSupportExecutor = (Executor) executor; + shadowState.recognitionSupportCallback = supportListener; } - @Implementation(minSdk = VERSION_CODES.TIRAMISU) - protected void triggerModelDownload(Intent recognizerIntent) { - latestModelDownloadIntent = recognizerIntent; + @RequiresApi(VERSION_CODES.TIRAMISU) + @Nullable + public Intent getLatestModelDownloadIntent() { + return getState().latestModelDownloadIntent; } - public static void setIsOnDeviceRecognitionAvailable(boolean available) { - isOnDeviceRecognitionAvailable = available; + @Implementation(minSdk = VERSION_CODES.TIRAMISU, maxSdk = U.SDK_INT) + protected void triggerModelDownload(Intent recognizerIntent) { + getState().latestModelDownloadIntent = recognizerIntent; } @RequiresApi(VERSION_CODES.TIRAMISU) public void triggerSupportResult(/*RecognitionSupport*/ Object recognitionSupport) { - Preconditions.checkArgument(recognitionSupport instanceof android.speech.RecognitionSupport); - recognitionSupportExecutor.execute( + Preconditions.checkArgument(recognitionSupport instanceof RecognitionSupport); + + ShadowSpeechRecognizerState shadowState = getState(); + shadowState.recognitionSupportExecutor.execute( () -> - ((android.speech.RecognitionSupportCallback) recognitionSupportCallback) - .onSupportResult((android.speech.RecognitionSupport) recognitionSupport)); + ((RecognitionSupportCallback) shadowState.recognitionSupportCallback) + .onSupportResult((RecognitionSupport) recognitionSupport)); } @RequiresApi(VERSION_CODES.TIRAMISU) public void triggerSupportError(int error) { - recognitionSupportExecutor.execute( - () -> - ((android.speech.RecognitionSupportCallback) recognitionSupportCallback) - .onError(error)); + ShadowSpeechRecognizerState shadowState = getState(); + shadowState.recognitionSupportExecutor.execute( + () -> ((RecognitionSupportCallback) shadowState.recognitionSupportCallback).onError(error)); } - @RequiresApi(VERSION_CODES.TIRAMISU) - @Nullable - public Intent getLatestModelDownloadIntent() { - return latestModelDownloadIntent; + /** + * {@link SpeechRecognizer} implementation now checks if the service's binder is alive whenever + * {@link SpeechRecognizer#checkOpenConnection} is called. This means that we need to return a + * deeper proxy that returns a delegating proxy that always reports the binder as alive. + */ + private static IRecognitionService createFakeSpeechRecognitionService() { + return ReflectionHelpers.createDelegatingProxy( + IRecognitionService.class, new AlwaysAliveSpeechRecognitionServiceDelegate()); + } + + /** + * A proxy delegate for {@link IRecognitionService} that always returns a delegating proxy that + * returns an {@link AlwaysAliveBinderDelegate} when {@link IRecognitionService#asBinder()} is + * called. + * + * @see #createFakeSpeechRecognitionService() for more details + */ + private static class AlwaysAliveSpeechRecognitionServiceDelegate { + public IBinder asBinder() { + return ReflectionHelpers.createDelegatingProxy( + IBinder.class, new AlwaysAliveBinderDelegate()); + } + } + + /** + * A proxy delegate for {@link IBinder} that always returns when {@link IBinder#isBinderAlive()} + * is called. + * + * @see #createFakeSpeechRecognitionService() for more details + */ + private static class AlwaysAliveBinderDelegate { + public boolean isBinderAlive() { + return true; + } + } + + /** + * The state of a specific instance of {@link ShadowSpeechRecognizer}. + * + * <p>NOTE: Not stored as variables in the parent class itself since subclasses may need to return + * a different instance of this class to operate on. + * + * <p>NOTE: This class is public since custom shadows may reside in a different package. + */ + public static class ShadowSpeechRecognizerState { + private boolean isRecognizerDestroyed = false; + private Intent recognizerIntent; + private RecognitionListener recognitionListener; + private Executor recognitionSupportExecutor; + private /*RecognitionSupportCallback*/ Object recognitionSupportCallback; + @Nullable private Intent latestModelDownloadIntent; + } + + /** + * An interface to access direct functions/variables of an instance of {@link SpeechRecognizer}. + * + * <p>Abstracted to allow subclasses to return customized accessors. + */ + protected interface ShadowSpeechRecognizerDirectAccessors { + /** + * Invokes {@link SpeechRecognizer#destroy()} on a real instance of {@link SpeechRecognizer}. + */ + void destroy(); + + /** Sets the {@link IRecognitionService} used by a real {@link SpeechRecognizer}. */ + void setService(IRecognitionService service); + + /** Returns a {@link Queue} of pending async tasks of a real {@link SpeechRecognizer}. */ + Queue<Message> getPendingTasks(); + + /** + * Returns the {@link Handler} of a real {@link SpeechRecognizer} that it uses to process any + * pending async tasks returned by {@link #getPendingTasks()}. + */ + Handler getHandler(); } /** Reflector interface for {@link SpeechRecognizer}'s internals. */ @ForType(SpeechRecognizer.class) - interface SpeechRecognizerReflector { + interface SpeechRecognizerReflector extends ShadowSpeechRecognizerDirectAccessors { @Static @Direct SpeechRecognizer createSpeechRecognizer(Context context, ComponentName serviceComponent); + @Static @Direct + SpeechRecognizer createOnDeviceSpeechRecognizer(Context context); + + @Direct + @Override void destroy(); @Accessor("mService") + @Override void setService(IRecognitionService service); @Accessor("mPendingTasks") + @Override Queue<Message> getPendingTasks(); @Accessor("mHandler") + @Override Handler getHandler(); - - @Static - @Direct - SpeechRecognizer createOnDeviceSpeechRecognizer(Context context); } } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTelecomManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTelecomManager.java index 2a8459609..41d3eb10d 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTelecomManager.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTelecomManager.java @@ -1,7 +1,6 @@ package org.robolectric.shadows; import static android.os.Build.VERSION.SDK_INT; -import static android.os.Build.VERSION_CODES.LOLLIPOP; import static android.os.Build.VERSION_CODES.LOLLIPOP_MR1; import static android.os.Build.VERSION_CODES.M; import static android.os.Build.VERSION_CODES.N; @@ -46,7 +45,7 @@ import org.robolectric.annotation.Implements; import org.robolectric.annotation.RealObject; import org.robolectric.util.ReflectionHelpers; -@Implements(value = TelecomManager.class, minSdk = LOLLIPOP) +@Implements(value = TelecomManager.class) public class ShadowTelecomManager { /** @@ -97,6 +96,7 @@ public class ShadowTelecomManager { private boolean callPhonePermission = true; private boolean handleMmiValue = false; private ConnectionService connectionService; + private boolean isOutgoingCallPermitted = false; public CallRequestMode getCallRequestMode() { return callRequestMode; @@ -119,6 +119,11 @@ public class ShadowTelecomManager { defaultOutgoingPhoneAccounts.remove(uriScheme); } + /** Sets the result of {@link TelecomManager#isOutgoingCallPermitted(PhoneAccountHandle)}. */ + public void setIsOutgoingCallPermitted(boolean isOutgoingCallPermitted) { + this.isOutgoingCallPermitted = isOutgoingCallPermitted; + } + /** * Returns default outgoing phone account set through {@link * #setDefaultOutgoingPhoneAccount(String, PhoneAccountHandle)} for corresponding {@code @@ -730,6 +735,11 @@ public class ShadowTelecomManager { return intent; } + @Implementation(minSdk = O) + protected boolean isOutgoingCallPermitted(PhoneAccountHandle phoneAccountHandle) { + return this.isOutgoingCallPermitted; + } + /** * Details about a call request made via {@link TelecomManager#addNewIncomingCall} or {@link * TelecomManager#addNewUnknownCall}. 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 b9f9a949f..02aa751c3 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTelephonyManager.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTelephonyManager.java @@ -68,6 +68,7 @@ import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicInteger; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.HiddenApi; import org.robolectric.annotation.Implementation; @@ -180,6 +181,7 @@ public class ShadowTelephonyManager { private static Map<Integer, List<EmergencyNumber>> emergencyNumbersList; private static volatile boolean isDataRoamingEnabled; private /*CarrierRestrictionRules*/ Object carrierRestrictionRules; + private final AtomicInteger modemRebootCount = new AtomicInteger(); /** * Should be {@link TelephonyManager.BootstrapAuthenticationCallback} but this object was @@ -680,6 +682,12 @@ public class ShadowTelephonyManager { } } + private void checkModifyPhoneStatePermission() { + if (!checkPermission(permission.MODIFY_PHONE_STATE)) { + throw new SecurityException(); + } + } + static ShadowInstrumentation getShadowInstrumentation() { ActivityThread activityThread = (ActivityThread) RuntimeEnvironment.getActivityThread(); return Shadow.extract(activityThread.getInstrumentation()); @@ -1683,4 +1691,15 @@ public class ShadowTelephonyManager { protected /*CarrierRestrictionRules*/ Object getCarrierRestrictionRules() { return carrierRestrictionRules; } + + /** Implementation for {@link TelephonyManager#rebootModem} */ + @Implementation(minSdk = Build.VERSION_CODES.TIRAMISU) + protected void rebootModem() { + checkModifyPhoneStatePermission(); + modemRebootCount.incrementAndGet(); + } + + public int getModemRebootCount() { + return modemRebootCount.get(); + } } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTextToSpeech.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTextToSpeech.java index d88988322..cfd92eb95 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTextToSpeech.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTextToSpeech.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.LOLLIPOP; import static java.nio.charset.StandardCharsets.UTF_8; import static org.robolectric.util.reflector.Reflector.reflector; @@ -9,7 +8,6 @@ import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.speech.tts.TextToSpeech; -import android.speech.tts.TextToSpeech.Engine; import android.speech.tts.UtteranceProgressListener; import android.speech.tts.Voice; import com.google.common.collect.ImmutableList; @@ -22,7 +20,6 @@ import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Set; -import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; import org.robolectric.annotation.RealObject; @@ -113,11 +110,7 @@ public class ShadowTextToSpeech { @Implementation protected int speak( final String text, final int queueMode, final HashMap<String, String> params) { - if (RuntimeEnvironment.getApiLevel() >= LOLLIPOP) { - return reflector(TextToSpeechReflector.class, tts).speak(text, queueMode, params); - } - return speak( - text, queueMode, null, params == null ? null : params.get(Engine.KEY_PARAM_UTTERANCE_ID)); + return reflector(TextToSpeechReflector.class, tts).speak(text, queueMode, params); } @Implementation diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTotalCaptureResult.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTotalCaptureResult.java index efebb85d2..7d6eafec5 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTotalCaptureResult.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTotalCaptureResult.java @@ -1,12 +1,11 @@ package org.robolectric.shadows; import android.hardware.camera2.TotalCaptureResult; -import android.os.Build.VERSION_CODES; import org.robolectric.annotation.Implements; import org.robolectric.util.ReflectionHelpers; /** Shadow of {@link TotalCaptureResult}. */ -@Implements(value = TotalCaptureResult.class, minSdk = VERSION_CODES.LOLLIPOP) +@Implements(value = TotalCaptureResult.class) public class ShadowTotalCaptureResult extends ShadowCaptureResult { /** Convenience method which returns a new instance of {@link TotalCaptureResult}. */ diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTransportControls.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTransportControls.java index cb5d112ab..878057c08 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTransportControls.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTransportControls.java @@ -13,7 +13,6 @@ import static android.media.session.PlaybackState.ACTION_SKIP_TO_PREVIOUS; import static android.media.session.PlaybackState.ACTION_SKIP_TO_QUEUE_ITEM; import static android.media.session.PlaybackState.ACTION_STOP; import static android.media.session.PlaybackState.STATE_NONE; -import static android.os.Build.VERSION_CODES.LOLLIPOP; import static android.os.Build.VERSION_CODES.M; import static android.os.Build.VERSION_CODES.N; import static org.robolectric.util.reflector.Reflector.reflector; @@ -35,7 +34,7 @@ import org.robolectric.util.reflector.ForType; * <p>TransportControls should always be created by first creating a corresponding MediaController; * *NOT*, for instance, via Shadows.newInstanceOf(TransportControls.class). */ -@Implements(value = TransportControls.class, minSdk = LOLLIPOP) +@Implements(value = TransportControls.class) public class ShadowTransportControls { @RealObject protected TransportControls realTransportControls; diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTypedArray.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTypedArray.java deleted file mode 100644 index 05c050957..000000000 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTypedArray.java +++ /dev/null @@ -1,156 +0,0 @@ -package org.robolectric.shadows; - -import static org.robolectric.res.android.AttributeResolution.STYLE_ASSET_COOKIE; -import static org.robolectric.res.android.AttributeResolution.STYLE_CHANGING_CONFIGURATIONS; -import static org.robolectric.res.android.AttributeResolution.STYLE_DATA; -import static org.robolectric.res.android.AttributeResolution.STYLE_DENSITY; -import static org.robolectric.res.android.AttributeResolution.STYLE_NUM_ENTRIES; -import static org.robolectric.res.android.AttributeResolution.STYLE_RESOURCE_ID; -import static org.robolectric.res.android.AttributeResolution.STYLE_TYPE; -import static org.robolectric.util.reflector.Reflector.reflector; - -import android.annotation.StyleableRes; -import android.content.res.Resources; -import android.content.res.TypedArray; -import android.os.Build; -import android.util.TypedValue; -import com.google.common.base.Strings; -import com.google.common.collect.ImmutableMap; -import org.robolectric.RuntimeEnvironment; -import org.robolectric.annotation.HiddenApi; -import org.robolectric.annotation.Implementation; -import org.robolectric.annotation.Implements; -import org.robolectric.annotation.RealObject; -import org.robolectric.shadow.api.Shadow; -import org.robolectric.util.ReflectionHelpers; -import org.robolectric.util.ReflectionHelpers.ClassParameter; -import org.robolectric.util.reflector.Direct; -import org.robolectric.util.reflector.ForType; - -@SuppressWarnings({"UnusedDeclaration"}) -@Implements(value = TypedArray.class, shadowPicker = ShadowTypedArray.Picker.class) -public class ShadowTypedArray { - public static class Picker extends ResourceModeShadowPicker<ShadowTypedArray> { - public Picker() { - super(ShadowTypedArray.class, null, null); - } - } - - @RealObject private TypedArray realTypedArray; - private CharSequence[] stringData; - public String positionDescription; - - public static TypedArray create(Resources realResources, int[] attrs, int[] data, int[] indices, int len, CharSequence[] stringData) { - TypedArray typedArray; - if (RuntimeEnvironment.getApiLevel() >= Build.VERSION_CODES.O) { - typedArray = - ReflectionHelpers.callConstructor( - TypedArray.class, ClassParameter.from(Resources.class, realResources)); - ReflectionHelpers.setField(typedArray, "mData", data); - ReflectionHelpers.setField(typedArray, "mLength", len); - ReflectionHelpers.setField(typedArray, "mIndices", indices); - } else { - typedArray = - ReflectionHelpers.callConstructor( - TypedArray.class, - ClassParameter.from(Resources.class, realResources), - ClassParameter.from(int[].class, data), - ClassParameter.from(int[].class, indices), - ClassParameter.from(int.class, len)); - } - - ShadowTypedArray shadowTypedArray = Shadow.extract(typedArray); - shadowTypedArray.stringData = stringData; - return typedArray; - } - - @HiddenApi @Implementation - protected CharSequence loadStringValueAt(int index) { - return stringData[index / STYLE_NUM_ENTRIES]; - } - - @Implementation - protected String getNonResourceString(@StyleableRes int index) { - return reflector(TypedArrayReflector.class, realTypedArray).getString(index); - } - - @Implementation - protected String getNonConfigurationString(@StyleableRes int index, int allowedChangingConfigs) { - return reflector(TypedArrayReflector.class, realTypedArray).getString(index); - } - - @Implementation - protected String getPositionDescription() { - return positionDescription; - } - - @SuppressWarnings("NewApi") - public static void dump(TypedArray typedArray) { - int[] data = ReflectionHelpers.getField(typedArray, "mData"); - - StringBuilder result = new StringBuilder(); - for (int index = 0; index < data.length; index+= STYLE_NUM_ENTRIES) { - final int type = data[index+STYLE_TYPE]; - result.append("Index: ").append(index / STYLE_NUM_ENTRIES).append(System.lineSeparator()); - result - .append(Strings.padEnd("Type: ", 25, ' ')) - .append(TYPE_MAP.get(type)) - .append(System.lineSeparator()); - if (type != TypedValue.TYPE_NULL) { - result - .append(Strings.padEnd("Style data: ", 25, ' ')) - .append(data[index + STYLE_DATA]) - .append(System.lineSeparator()); - result - .append(Strings.padEnd("Asset cookie ", 25, ' ')) - .append(data[index + STYLE_ASSET_COOKIE]) - .append(System.lineSeparator()); - result - .append(Strings.padEnd("Style resourceId: ", 25, ' ')) - .append(data[index + STYLE_RESOURCE_ID]) - .append(System.lineSeparator()); - result - .append(Strings.padEnd("Changing configurations ", 25, ' ')) - .append(data[index + STYLE_CHANGING_CONFIGURATIONS]) - .append(System.lineSeparator()); - result - .append(Strings.padEnd("Style density: ", 25, ' ')) - .append(data[index + STYLE_DENSITY]) - .append(System.lineSeparator()); - if (type == TypedValue.TYPE_STRING) { - ShadowTypedArray shadowTypedArray = Shadow.extract(typedArray); - result - .append(Strings.padEnd("Style value: ", 25, ' ')) - .append(shadowTypedArray.loadStringValueAt(index)) - .append(System.lineSeparator()); - } - } - result.append(System.lineSeparator()); - } - System.out.println(result.toString()); - } - - private static final ImmutableMap<Integer, String> TYPE_MAP = ImmutableMap.<Integer, String>builder() - .put(TypedValue.TYPE_NULL, "TYPE_NULL") - .put(TypedValue.TYPE_REFERENCE, "TYPE_REFERENCE") - .put(TypedValue.TYPE_ATTRIBUTE, "TYPE_ATTRIBUTE") - .put(TypedValue.TYPE_STRING, "TYPE_STRING") - .put(TypedValue.TYPE_FLOAT, "TYPE_FLOAT") - .put(TypedValue.TYPE_DIMENSION, "TYPE_DIMENSION") - .put(TypedValue.TYPE_FRACTION, "TYPE_FRACTION") - .put(TypedValue.TYPE_INT_DEC, "TYPE_INT_DEC") - .put(TypedValue.TYPE_INT_HEX, "TYPE_INT_HEX") - .put(TypedValue.TYPE_INT_BOOLEAN, "TYPE_INT_BOOLEAN") - .put(TypedValue.TYPE_INT_COLOR_ARGB8, "TYPE_INT_COLOR_ARGB8") - .put(TypedValue.TYPE_INT_COLOR_RGB8, "TYPE_INT_COLOR_RGB8") - .put(TypedValue.TYPE_INT_COLOR_ARGB4, "TYPE_INT_COLOR_ARGB4") - .put(TypedValue.TYPE_INT_COLOR_RGB4, "TYPE_INT_COLOR_RGB4") - .build(); - - @ForType(TypedArray.class) - interface TypedArrayReflector { - - @Direct - String getString(int index); - } -} diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowUiAutomation.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowUiAutomation.java index bfe39ea95..932b052f0 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowUiAutomation.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowUiAutomation.java @@ -131,7 +131,7 @@ public class ShadowUiAutomation { Bitmap window = Bitmap.createBitmap( rootView.getWidth(), rootView.getHeight(), Bitmap.Config.ARGB_8888); - if (HardwareRenderingScreenshot.canTakeScreenshot()) { + if (HardwareRenderingScreenshot.canTakeScreenshot(rootView)) { HardwareRenderingScreenshot.takeScreenshot(rootView, window); } else { Canvas windowCanvas = new Canvas(window); diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowUsageStatsManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowUsageStatsManager.java index 40dc52690..3f3c5d00b 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowUsageStatsManager.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowUsageStatsManager.java @@ -47,7 +47,6 @@ import org.robolectric.annotation.Resetter; /** Shadow of {@link UsageStatsManager}. */ @Implements( value = UsageStatsManager.class, - minSdk = Build.VERSION_CODES.LOLLIPOP, looseSignatures = true) public class ShadowUsageStatsManager { private static @StandbyBuckets int currentAppStandbyBucket = 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 7accd63a4..904f2349a 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowUserManager.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowUserManager.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.LOLLIPOP; import static android.os.Build.VERSION_CODES.M; import static android.os.Build.VERSION_CODES.N; import static android.os.Build.VERSION_CODES.N_MR1; @@ -322,12 +321,10 @@ public class ShadowUserManager { // use UserHandle id as serial number unless setSerialNumberForUser() is used userManagerState.userSerialNumbers.put(profileUserHandle, (long) profileUserHandle); } - if (RuntimeEnvironment.getApiLevel() >= LOLLIPOP) { - profileUserInfo.profileGroupId = userHandle; - UserInfo parentUserInfo = getUserInfo(userHandle); - if (parentUserInfo != null) { - parentUserInfo.profileGroupId = userHandle; - } + profileUserInfo.profileGroupId = userHandle; + UserInfo parentUserInfo = getUserInfo(userHandle); + if (parentUserInfo != null) { + parentUserInfo.profileGroupId = userHandle; } userManagerState.userInfoMap.put(profileUserHandle, profileUserInfo); // Insert profile to the belonging user's userProfilesList diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowVMRuntime.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowVMRuntime.java index 8bc20c406..b1899caa0 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowVMRuntime.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowVMRuntime.java @@ -1,9 +1,7 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.LOLLIPOP; import static android.os.Build.VERSION_CODES.Q; -import android.annotation.TargetApi; import dalvik.system.VMRuntime; import java.lang.ref.WeakReference; import java.lang.reflect.Array; @@ -63,7 +61,6 @@ public class ShadowVMRuntime { } /** Sets whether the VM is running in 64-bit mode. */ - @TargetApi(LOLLIPOP) public static void setIs64Bit(boolean is64Bit) { ShadowVMRuntime.is64Bit = is64Bit; } @@ -75,7 +72,6 @@ public class ShadowVMRuntime { } /** Sets the instruction set of the current runtime. */ - @TargetApi(LOLLIPOP) public static void setCurrentInstructionSet(@Nullable String currentInstructionSet) { ShadowVMRuntime.currentInstructionSet = currentInstructionSet; } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowVirtualDeviceManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowVirtualDeviceManager.java index f4f15fb79..1c723119d 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowVirtualDeviceManager.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowVirtualDeviceManager.java @@ -4,6 +4,7 @@ import static android.companion.virtual.VirtualDeviceManager.LAUNCH_SUCCESS; import static org.robolectric.util.reflector.Reflector.reflector; import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.PendingIntent; import android.companion.virtual.IVirtualDevice; import android.companion.virtual.IVirtualDeviceManager; @@ -42,6 +43,7 @@ import org.robolectric.util.reflector.Accessor; import org.robolectric.util.reflector.Constructor; import org.robolectric.util.reflector.ForType; import org.robolectric.versioning.AndroidVersions.U; +import org.robolectric.versioning.AndroidVersions.V; /** Shadow for VirtualDeviceManager. */ @Implements( @@ -134,6 +136,7 @@ public class ShadowVirtualDeviceManager { @RealObject VirtualDeviceManager.VirtualDevice realVirtualDevice; private VirtualDeviceParams params; private int deviceId; + private String persistentDeviceId; private PendingIntent pendingIntent; private Integer pendingIntentResultCode = LAUNCH_SUCCESS; private final AtomicBoolean isClosed = new AtomicBoolean(false); @@ -153,6 +156,7 @@ public class ShadowVirtualDeviceManager { ClassParameter.from(VirtualDeviceParams.class, params)); this.params = params; this.deviceId = nextDeviceId.getAndIncrement(); + this.persistentDeviceId = "companion:" + associationId; } @Implementation @@ -160,6 +164,12 @@ public class ShadowVirtualDeviceManager { return deviceId; } + @Implementation(minSdk = V.SDK_INT) + @Nullable + protected String getPersistentDeviceId() { + return persistentDeviceId; + } + /** Prevents a NPE when calling .close() on a VirtualDevice in unit tests. */ @Implementation protected void close() { diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowVoiceInteractionService.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowVoiceInteractionService.java index 6a453d561..9925e328b 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowVoiceInteractionService.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowVoiceInteractionService.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.LOLLIPOP; import static android.os.Build.VERSION_CODES.M; import static android.os.Build.VERSION_CODES.Q; import static org.robolectric.util.reflector.Reflector.reflector; @@ -23,7 +22,7 @@ import org.robolectric.util.reflector.Direct; import org.robolectric.util.reflector.ForType; /** Shadow implementation of {@link android.service.voice.VoiceInteractionService}. */ -@Implements(value = VoiceInteractionService.class, minSdk = LOLLIPOP) +@Implements(value = VoiceInteractionService.class) public class ShadowVoiceInteractionService extends ShadowService { private final List<Bundle> hintBundles = Collections.synchronizedList(new ArrayList<>()); diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowVoiceInteractionSession.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowVoiceInteractionSession.java index c2d73e9fa..4a6f87409 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowVoiceInteractionSession.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowVoiceInteractionSession.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.LOLLIPOP; import static org.robolectric.util.ReflectionHelpers.callConstructor; import android.app.Dialog; @@ -28,7 +27,7 @@ import org.robolectric.util.ReflectionHelpers; import org.robolectric.util.ReflectionHelpers.ClassParameter; /** Shadow implementation of {@link android.service.voice.VoiceInteractionSession}. */ -@Implements(value = VoiceInteractionSession.class, minSdk = LOLLIPOP) +@Implements(value = VoiceInteractionSession.class) public class ShadowVoiceInteractionSession { private final List<Intent> assistantActivityIntents = new ArrayList<>(); diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/SystemFeatureListInitializer.java b/shadows/framework/src/main/java/org/robolectric/shadows/SystemFeatureListInitializer.java index ee6b90348..7e3d1eb1c 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/SystemFeatureListInitializer.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/SystemFeatureListInitializer.java @@ -27,6 +27,12 @@ final class SystemFeatureListInitializer { features.put(PackageManager.FEATURE_WIFI_RTT, true); } + if (apiLevel >= VERSION_CODES.UPSIDE_DOWN_CAKE) { + // Starting in V, FEATURE_TELEPHONY_SUBSCRIPTION is required for some system services, + // such as VcnManager. + features.put(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION, true); + } + return ImmutableMap.copyOf(features); } } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/_Activity_.java b/shadows/framework/src/main/java/org/robolectric/shadows/_Activity_.java index d638630ca..bcc680bd8 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/_Activity_.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/_Activity_.java @@ -164,7 +164,7 @@ public interface _Activity_ { @WithType("android.app.Activity$NonConfigurationInstances") Object lastNonConfigurationInstances) { int apiLevel = RuntimeEnvironment.getApiLevel(); - if (apiLevel <= Build.VERSION_CODES.LOLLIPOP) { + if (apiLevel == Build.VERSION_CODES.LOLLIPOP) { attach( baseContext, activityThread, diff --git a/testapp/build.gradle b/testapp/build.gradle index 0800a09f8..6a8150496 100644 --- a/testapp/build.gradle +++ b/testapp/build.gradle @@ -5,7 +5,7 @@ android { namespace 'org.robolectric.testapp' defaultConfig { - minSdk 19 + minSdk 21 targetSdk 34 versionCode 1 versionName "1.0" diff --git a/testapp/src/main/java/org/robolectric/testapp/AbstractTestActivity.java b/testapp/src/main/java/org/robolectric/testapp/AbstractTestActivity.java new file mode 100644 index 000000000..02ad70562 --- /dev/null +++ b/testapp/src/main/java/org/robolectric/testapp/AbstractTestActivity.java @@ -0,0 +1,6 @@ +package org.robolectric.testapp; + +import android.app.Activity; + +/** Abstract test {@link Activity} for test purpose. */ +public abstract class AbstractTestActivity extends Activity {} |