aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--gradle/libs.versions.toml2
-rw-r--r--integration_tests/kotlin/build.gradle1
-rw-r--r--integration_tests/kotlin/src/test/kotlin/org/robolectric/integrationtests/kotlin/flow/BluetoothProvisioner.kt79
-rw-r--r--integration_tests/kotlin/src/test/kotlin/org/robolectric/integrationtests/kotlin/flow/BluetoothProvisionerTest.kt83
-rw-r--r--integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/HardwareAcceleratedActivityRenderTest.java7
-rw-r--r--resources/src/main/java/org/robolectric/res/Qualifiers.java18
-rw-r--r--resources/src/main/java/org/robolectric/res/android/CppAssetManager2.java5
-rw-r--r--resources/src/main/java/org/robolectric/res/android/DataType.java4
-rw-r--r--resources/src/main/java/org/robolectric/res/android/DynamicRefTable.java14
-rw-r--r--resources/src/test/java/org/robolectric/res/QualifiersTest.java21
-rw-r--r--resources/src/test/java/org/robolectric/res/android/DataTypeTest.java23
-rw-r--r--resources/src/test/java/org/robolectric/res/android/DynamicRefTableTest.java23
-rw-r--r--robolectric/src/main/java/org/robolectric/Robolectric.java11
-rw-r--r--robolectric/src/main/java/org/robolectric/android/AttributeSetBuilderImpl.java74
-rwxr-xr-xrobolectric/src/main/java/org/robolectric/android/internal/AndroidTestEnvironment.java51
-rw-r--r--robolectric/src/test/java/org/robolectric/QualifiersTest.java4
-rw-r--r--robolectric/src/test/java/org/robolectric/RobolectricTestRunnerSelfTest.java5
-rw-r--r--robolectric/src/test/java/org/robolectric/RuntimeEnvironmentTest.java6
-rw-r--r--robolectric/src/test/java/org/robolectric/android/BootstrapTest.java18
-rw-r--r--robolectric/src/test/java/org/robolectric/android/DeviceConfigTest.java12
-rw-r--r--robolectric/src/test/java/org/robolectric/android/DrawableResourceLoaderTest.java99
-rw-r--r--robolectric/src/test/java/org/robolectric/android/ResourceLoaderTest.java110
-rw-r--r--robolectric/src/test/java/org/robolectric/android/ResourceTableFactoryIntegrationTest.java22
-rw-r--r--robolectric/src/test/java/org/robolectric/android/XmlResourceParserImplTest.java11
-rw-r--r--robolectric/src/test/java/org/robolectric/android/internal/AndroidTestEnvironmentTest.java19
-rw-r--r--robolectric/src/test/java/org/robolectric/res/StyleResourceLoaderTest.java38
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/CompatibilityTest.java11
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowAssetManagerTest.java98
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowCaptioningManagerTest.java155
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowDeviceConfigTest.java571
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowDrawableTest.java7
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowEGL14Test.java14
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowPackageManagerTest.java3
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowResourcesTest.java25
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowSettingsTest.java19
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowSpeechRecognizerTest.java12
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowTelecomManagerTest.java16
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowTelephonyManagerTest.java17
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowThemeTest.java6
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowVirtualDeviceManagerTest.java34
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/XmlPullParserTest.java15
-rw-r--r--shadows/framework/src/main/java/org/robolectric/RuntimeEnvironment.java10
-rw-r--r--shadows/framework/src/main/java/org/robolectric/android/ConfigurationV25.java18
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/HardwareRenderingScreenshot.java19
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ResourceModeShadowPicker.java27
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowApkAssets.java2
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowApplicationPackageManager.java8
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowArscAssetManager.java5
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowArscAssetManager10.java2
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowArscAssetManager9.java2
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowArscResourcesImpl.java76
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowAssetInputStream.java8
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowAssetManager.java33
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothLeAdvertiser.java52
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowCameraManager.java14
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowCaptioningManager.java80
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowCompatibility.java30
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowDeviceConfig.java8
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowEGL14.java6
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowInCallService.java4
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyApkAssets.java33
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyAssetInputStream.java141
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyAssetManager.java1432
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyResourcesImpl.java195
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeFont.java9
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeFontsFontFamily.java5
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeLineBreaker.java5
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePositionedGlyphs.java10
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowPackageManager.java8
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowPixelCopy.java2
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowResources.java217
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowResourcesImpl.java13
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowSettings.java105
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowSpeechRecognizer.java306
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowTelecomManager.java11
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowTelephonyManager.java19
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowTypedArray.java156
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowUiAutomation.java2
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowVirtualDeviceManager.java10
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/SystemFeatureListInitializer.java6
80 files changed, 1608 insertions, 3244 deletions
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 52a90b6aa..ca186969d 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -1,5 +1,5 @@
[versions]
-robolectric-nativeruntime-dist-compat = "1.0.9"
+robolectric-nativeruntime-dist-compat = "1.0.10"
# https://developer.android.com/studio/releases
android-gradle = "8.3.1"
diff --git a/integration_tests/kotlin/build.gradle b/integration_tests/kotlin/build.gradle
index c95d64f3f..37686d981 100644
--- a/integration_tests/kotlin/build.gradle
+++ b/integration_tests/kotlin/build.gradle
@@ -18,6 +18,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..2094dd13b
--- /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) {
+ val unused = 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) {
+ val unused = gatt!!.discoverServices()
+ } else {
+ cancel("Connect Failed", null)
+ }
+ }
+
+ override fun onServicesDiscovered(gatt: BluetoothGatt?, status: Int) {
+ if (status == BluetoothGatt.GATT_SUCCESS) {
+ val unused = 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..181b6a8b9
--- /dev/null
+++ b/integration_tests/kotlin/src/test/kotlin/org/robolectric/integrationtests/kotlin/flow/BluetoothProvisionerTest.kt
@@ -0,0 +1,83 @@
+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 BLUETOOTH_MAC = "00:11:22:33:AA:BB"
+
+ 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()
+ }
+}
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 7f8e26578..f7cf96930 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
@@ -13,7 +13,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.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -57,12 +56,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/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/src/main/java/org/robolectric/Robolectric.java b/robolectric/src/main/java/org/robolectric/Robolectric.java
index 7b694ec82..6d43fc46a 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;
@@ -24,7 +23,6 @@ import android.view.View;
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;
@@ -307,15 +305,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/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/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/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/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/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..459b75b65 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowEGL14Test.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowEGL14Test.java
@@ -27,8 +27,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 +39,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/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/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/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/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/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/ShadowCameraManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowCameraManager.java
index be1e29713..58d92d0c3 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowCameraManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowCameraManager.java
@@ -33,6 +33,7 @@ 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)
@@ -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,
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/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/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/ShadowEGL14.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowEGL14.java
index 4f488b571..547b9f1ff 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowEGL14.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowEGL14.java
@@ -37,7 +37,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/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/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/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/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 5b65023af..3a7e8a136 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(
@@ -17,15 +16,6 @@ import org.robolectric.versioning.AndroidVersions.V;
shadowPicker = Picker.class,
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/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/ShadowPixelCopy.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPixelCopy.java
index 9039e885e..600f8ea64 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPixelCopy.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPixelCopy.java
@@ -168,7 +168,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/ShadowResources.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowResources.java
index 97d84ad55..e9b3f77e5 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,8 @@
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 +11,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 +33,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 +71,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 +191,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 +207,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/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..10c6c35b6 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTelecomManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTelecomManager.java
@@ -97,6 +97,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 +120,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 +736,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/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/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/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);
}
}