diff options
author | Ram Peri <ramperi@google.com> | 2023-01-21 01:34:34 +0000 |
---|---|---|
committer | Gerrit Code Review <noreply-gerritcodereview@google.com> | 2023-01-21 01:34:34 +0000 |
commit | 99b266f538c792b5c37825d8d695d1748a99bd09 (patch) | |
tree | b1829a2dca2591bcec782fc13a20a9928c97f138 | |
parent | d51cb97e9934f408a401b6f945d8bb12b0c3d281 (diff) | |
parent | 11d21fe78d6becf9f02451c8a86a884140226bfd (diff) | |
download | robolectric-main-16k-with-phones.tar.gz |
Merge "Periodic pull of Robolectric changes from github. Test: atest -c MyRoboTests Bug: 263483214"main-16k-with-phones
23 files changed, 424 insertions, 37 deletions
diff --git a/integration_tests/androidx_test/src/main/res/layout/espresso_activity.xml b/integration_tests/androidx_test/src/main/res/layout/espresso_activity.xml index 1cbc1979d..c412b90f0 100644 --- a/integration_tests/androidx_test/src/main/res/layout/espresso_activity.xml +++ b/integration_tests/androidx_test/src/main/res/layout/espresso_activity.xml @@ -50,4 +50,10 @@ android:layout_height="wrap_content" android:inputType="phone" /> + <EditText + android:id="@+id/edit_text_number" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:inputType="number" /> + </LinearLayout> diff --git a/integration_tests/androidx_test/src/sharedTest/java/org/robolectric/integrationtests/axt/EspressoTest.java b/integration_tests/androidx_test/src/sharedTest/java/org/robolectric/integrationtests/axt/EspressoTest.java index 494ce82c8..0c0df8b19 100644 --- a/integration_tests/androidx_test/src/sharedTest/java/org/robolectric/integrationtests/axt/EspressoTest.java +++ b/integration_tests/androidx_test/src/sharedTest/java/org/robolectric/integrationtests/axt/EspressoTest.java @@ -25,6 +25,7 @@ import androidx.test.ext.junit.rules.ActivityScenarioRule; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SdkSuppress; import java.util.concurrent.atomic.AtomicReference; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -129,6 +130,15 @@ public final class EspressoTest { onView(withId(R.id.edit_text_phone)).check(matches(withText("411"))); } + /** use typeText with a inputType number */ + @Test + @Ignore // TODO(#5110): fails + public void typeText_number() { + onView(withId(R.id.edit_text_number)).perform(typeText("411")); + + onView(withId(R.id.edit_text_number)).check(matches(withText("411"))); + } + @Test public void textView() { onView(withText("Text View")) diff --git a/nativeruntime/src/main/java/org/robolectric/nativeruntime/DefaultNativeRuntimeLoader.java b/nativeruntime/src/main/java/org/robolectric/nativeruntime/DefaultNativeRuntimeLoader.java index 5221a4b8a..3892453dd 100644 --- a/nativeruntime/src/main/java/org/robolectric/nativeruntime/DefaultNativeRuntimeLoader.java +++ b/nativeruntime/src/main/java/org/robolectric/nativeruntime/DefaultNativeRuntimeLoader.java @@ -11,6 +11,7 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableMap; import com.google.common.io.Files; import com.google.common.io.Resources; +import java.io.File; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; @@ -137,7 +138,9 @@ public class DefaultNativeRuntimeLoader implements NativeRuntimeLoader { } } System.setProperty( - "robolectric.nativeruntime.fontdir", fontsOutputPath.toAbsolutePath().toString()); + "robolectric.nativeruntime.fontdir", + // Android's FontListParser expects a trailing slash for the base font directory. + fontsOutputPath.toAbsolutePath() + File.separator); if (zipfs != null) { zipfs.close(); } diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowContentResolverTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowContentResolverTest.java index c77bb1e14..31e0f3243 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowContentResolverTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowContentResolverTest.java @@ -488,8 +488,25 @@ public class ShadowContentResolverTest { } @Test - public void openOutputStream_shouldReturnAnOutputStream() throws Exception { - assertThat(contentResolver.openOutputStream(uri21)).isInstanceOf(OutputStream.class); + public void openOutputStream_withNoRealOrRegisteredProvider_doesNotThrow() throws Exception { + Uri uri = Uri.parse("content://invalidauthority/test/1"); + assertThat(contentResolver.openOutputStream(uri)).isNotNull(); + } + + @Test + public void openOutputStream_withRealContentProvider_canReadBytesWrittenToOutputStream() + throws IOException, RemoteException { + Robolectric.setupContentProvider(MyContentProvider.class, AUTHORITY); + Uri uri = Uri.parse("content://" + AUTHORITY + "/test/1"); + + // Write content through given outputstream + try (OutputStream outputStream = contentResolver.openOutputStream(uri)) { + outputStream.write("foo".getBytes(UTF_8)); + } + + // Verify written content can be read back + InputStream inputStream = contentResolver.openInputStream(uri); + assertThat(new String(inputStream.readAllBytes(), UTF_8)).isEqualTo("foo"); } @Test @@ -1177,7 +1194,7 @@ public class ShadowContentResolverTest { } catch (IOException e) { throw new RuntimeException("error creating new file", e); } - return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY); + return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_WRITE); } } } diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowContextWrapperTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowContextWrapperTest.java index 02b149da7..4d71cbfb4 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowContextWrapperTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowContextWrapperTest.java @@ -658,16 +658,25 @@ public class ShadowContextWrapperTest { } @Test - public void checkPermissionUidPid() { - assertThat(contextWrapper.checkPermission("MY_PERMISSON", 1, 1)) + public void checkPermission_denied() { + assertThat(contextWrapper.checkPermission("MY_PERMISSON", /* pid= */ 1, /* uid= */ 1)) + .isEqualTo(PackageManager.PERMISSION_DENIED); + + assertThat(contextWrapper.checkPermission("MY_PERMISSON", /* pid= */ -1, /* uid= */ 1)) .isEqualTo(PackageManager.PERMISSION_DENIED); + } + @Test + public void checkPermission_granted() { shadowContextWrapper.grantPermissions(1, 1, "MY_PERMISSON"); - assertThat(contextWrapper.checkPermission("MY_PERMISSON", 2, 1)) + assertThat(contextWrapper.checkPermission("MY_PERMISSON", /* pid= */ 1, /* uid= */ 1)) + .isEqualTo(PackageManager.PERMISSION_GRANTED); + + assertThat(contextWrapper.checkPermission("MY_PERMISSON", /* pid= */ 2, /* uid= */ 1)) .isEqualTo(PackageManager.PERMISSION_DENIED); - assertThat(contextWrapper.checkPermission("MY_PERMISSON", 1, 1)) + assertThat(contextWrapper.checkPermission("MY_PERMISSON", /* pid= */ -1, /* uid= */ 1)) .isEqualTo(PackageManager.PERMISSION_GRANTED); } diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowDevicePolicyManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowDevicePolicyManagerTest.java index 7d36f356a..03d824753 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowDevicePolicyManagerTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowDevicePolicyManagerTest.java @@ -818,6 +818,43 @@ public final class ShadowDevicePolicyManagerTest { } @Test + @Config(minSdk = R) + public void getAutoTimeEnabledShouldWorkAsIntendedForDeviceOwner() { + // GIVEN the caller is the device owner + shadowOf(devicePolicyManager).setDeviceOwner(testComponent); + + // WHEN setAutoTimeEnabled is called with true + devicePolicyManager.setAutoTimeEnabled(testComponent, true); + + // THEN getAutoTimeEnabled should return true + assertThat(devicePolicyManager.getAutoTimeEnabled(testComponent)).isTrue(); + } + + @Test + @Config(minSdk = R) + public void getAutoTimeEnabledShouldWorkAsIntendedForProfileOwner() { + // GIVEN the caller is the profile owner + shadowOf(devicePolicyManager).setProfileOwner(testComponent); + + // WHEN setAutoTimeEnabled is called with false + devicePolicyManager.setAutoTimeEnabled(testComponent, false); + + // THEN getAutoTimeEnabled should return false + assertThat(devicePolicyManager.getAutoTimeEnabled(testComponent)).isFalse(); + } + + @Test + @Config(minSdk = R) + public void getAutoTimeEnabledShouldReturnFalseIfNotSet() { + // GIVEN the caller is the device owner + shadowOf(devicePolicyManager).setDeviceOwner(testComponent); + + // WHEN setAutoTimeEnabled has not been called + // THEN getAutoTimeEnabled should return false + assertThat(devicePolicyManager.getAutoTimeEnabled(testComponent)).isFalse(); + } + + @Test @Config(minSdk = LOLLIPOP) public void getAutoTimeRequiredShouldWorkAsIntendedForDeviceOwner() { // GIVEN the caller is the device owner diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowEGL14Test.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowEGL14Test.java index e5d5c9dbf..4da4963cd 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowEGL14Test.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowEGL14Test.java @@ -17,6 +17,11 @@ import org.robolectric.annotation.Config; @Config(minSdk = VERSION_CODES.LOLLIPOP) public final class ShadowEGL14Test { @Test + public void eglGetCurrentContext() { + assertThat(EGL14.eglGetCurrentContext()).isNotNull(); + } + + @Test public void eglGetDisplay() { assertThat(EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY)).isNotNull(); } diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowPackageManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowPackageManagerTest.java index e3242fadd..abb13702e 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowPackageManagerTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowPackageManagerTest.java @@ -4326,6 +4326,25 @@ public class ShadowPackageManagerTest { assertThat(packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA)).isFalse(); } + @Test + @Config(minSdk = S) + public void getProperty() throws NameNotFoundException { + assertThrows( + NameNotFoundException.class, + () -> + packageManager.getProperty( + "myproperty", RuntimeEnvironment.getApplication().getPackageName())); + } + + @Test + @Config(minSdk = S) + public void getProperty_component() throws NameNotFoundException { + final ComponentName componentName = + new ComponentName(RuntimeEnvironment.getApplication().getPackageName(), "mycomponentname"); + assertThrows( + NameNotFoundException.class, () -> packageManager.getProperty("myproperty", componentName)); + } + public String[] setPackagesSuspended( String[] packageNames, boolean suspended, diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowScrollViewTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowScrollViewTest.java index 1b0970be5..536e6dc21 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowScrollViewTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowScrollViewTest.java @@ -36,7 +36,7 @@ public class ShadowScrollViewTest { @Test public void realCode_shouldSmoothScrollTo() { try { - System.setProperty("robolectric.nativeruntime.enableGraphics", "true"); + System.setProperty("robolectric.useRealScrolling", "true"); Activity activity = Robolectric.setupActivity(Activity.class); ScrollView scrollView = new ScrollView(activity); View view = new View(activity); @@ -51,14 +51,14 @@ public class ShadowScrollViewTest { assertEquals(7, scrollView.getScrollX()); assertEquals(6, scrollView.getScrollY()); } finally { - System.clearProperty("robolectric.nativeruntime.enableGraphics"); + System.clearProperty("robolectric.useRealScrolling"); } } @Test public void realCode_shouldSmoothScrollBy() { try { - System.setProperty("robolectric.nativeruntime.enableGraphics", "true"); + System.setProperty("robolectric.useRealScrolling", "true"); Activity activity = Robolectric.setupActivity(Activity.class); ScrollView scrollView = new ScrollView(activity); View view = new View(activity); @@ -74,7 +74,7 @@ public class ShadowScrollViewTest { assertEquals(17, scrollView.getScrollX()); assertEquals(26, scrollView.getScrollY()); } finally { - System.clearProperty("robolectric.nativeruntime.enableGraphics"); + System.clearProperty("robolectric.useRealScrolling"); } } } diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowServiceManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowServiceManagerTest.java index cf1bf5b9c..68778c584 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowServiceManagerTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowServiceManagerTest.java @@ -4,6 +4,7 @@ import static com.google.common.truth.Truth.assertThat; import static java.util.concurrent.TimeUnit.SECONDS; import android.content.Context; +import android.os.Build.VERSION_CODES; import android.os.IBinder; import android.os.ServiceManager; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -13,17 +14,24 @@ import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicReference; import org.junit.Test; import org.junit.runner.RunWith; +import org.robolectric.annotation.Config; /** Tests for {@link ShadowServiceManager}. */ @RunWith(AndroidJUnit4.class) public final class ShadowServiceManagerTest { - + @Test public void getService_available_shouldReturnNonNull() { assertThat(ServiceManager.getService(Context.INPUT_METHOD_SERVICE)).isNotNull(); } @Test + @Config(sdk = VERSION_CODES.S) + public void getSensorPrivacyService_notNull() { + assertThat(ServiceManager.getService(Context.SENSOR_PRIVACY_SERVICE)).isNotNull(); + } + + @Test public void getService_unavailableService_shouldReturnNull() { ShadowServiceManager.setServiceAvailability(Context.INPUT_METHOD_SERVICE, false); assertThat(ServiceManager.getService(Context.INPUT_METHOD_SERVICE)).isNull(); diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowVibratorTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowVibratorTest.java index b8b57851c..bc93daac0 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowVibratorTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowVibratorTest.java @@ -172,6 +172,48 @@ public class ShadowVibratorTest { new PrimitiveSegment(EFFECT_CLICK, /* scale= */ 1f, /* delay= */ 2150))); } + @Config(minSdk = S) + @Test + public void getPrimitiveSegmentsInPrimitiveEffects_composeOnce_shouldReturnSameFragment() { + vibrator.vibrate( + VibrationEffect.startComposition() + .addPrimitive(EFFECT_CLICK, /* scale= */ 0.5f, /* delay= */ 20) + .addPrimitive(EFFECT_CLICK, /* scale= */ 0.7f, /* delay= */ 50) + .addPrimitive(EFFECT_CLICK, /* scale= */ 0.9f, /* delay= */ 150) + .compose()); + + assertThat(shadowOf(vibrator).getPrimitiveSegmentsInPrimitiveEffects()) + .isEqualTo( + ImmutableList.of( + new PrimitiveEffect(EFFECT_CLICK, /* scale= */ 0.5f, /* delay= */ 20), + new PrimitiveEffect(EFFECT_CLICK, /* scale= */ 0.7f, /* delay= */ 50), + new PrimitiveEffect(EFFECT_CLICK, /* scale= */ 0.9f, /* delay= */ 150))); + } + + @Config(minSdk = S) + @Test + public void getPrimitiveSegmentsInPrimitiveEffects_composeTwice_shouldReturnTheLastComposition() { + vibrator.vibrate( + VibrationEffect.startComposition() + .addPrimitive(EFFECT_CLICK, /* scale= */ 0.5f, /* delay= */ 20) + .addPrimitive(EFFECT_CLICK, /* scale= */ 0.7f, /* delay= */ 50) + .addPrimitive(EFFECT_CLICK, /* scale= */ 0.9f, /* delay= */ 150) + .compose()); + vibrator.vibrate( + VibrationEffect.startComposition() + .addPrimitive(EFFECT_CLICK, /* scale= */ 0.4f, /* delay= */ 120) + .addPrimitive(EFFECT_CLICK, /* scale= */ 0.9f, /* delay= */ 150) + .addPrimitive(EFFECT_CLICK, /* scale= */ 1f, /* delay= */ 2150) + .compose()); + + assertThat(shadowOf(vibrator).getPrimitiveSegmentsInPrimitiveEffects()) + .isEqualTo( + ImmutableList.of( + new PrimitiveEffect(EFFECT_CLICK, /* scale= */ 0.4f, /* delay= */ 120), + new PrimitiveEffect(EFFECT_CLICK, /* scale= */ 0.9f, /* delay= */ 150), + new PrimitiveEffect(EFFECT_CLICK, /* scale= */ 1f, /* delay= */ 2150))); + } + @Config(minSdk = R) @Test public void areAllPrimitivesSupported_oneSupportedPrimitive_shouldReturnTrue() { diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowWifiManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowWifiManagerTest.java index 299a85333..945c713b2 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowWifiManagerTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowWifiManagerTest.java @@ -7,6 +7,7 @@ import static android.os.Build.VERSION_CODES.R; import static android.os.Build.VERSION_CODES.S; import static com.google.common.truth.Truth.assertThat; import static com.google.common.util.concurrent.MoreExecutors.directExecutor; +import static org.junit.Assert.assertThrows; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; @@ -14,6 +15,9 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.robolectric.Shadows.shadowOf; +import android.app.admin.DeviceAdminService; +import android.app.admin.DevicePolicyManager; +import android.content.ComponentName; import android.content.Context; import android.net.ConnectivityManager; import android.net.DhcpInfo; @@ -250,6 +254,69 @@ public class ShadowWifiManagerTest { } @Test + @Config(minSdk = S) + public void + getCallerConfiguredNetworks_noAccessWifiStatePermission_shouldThrowSecurityException() { + shadowOf(wifiManager).setAccessWifiStatePermission(false); + + assertThrows(SecurityException.class, () -> wifiManager.getCallerConfiguredNetworks()); + } + + @Test + @Config(minSdk = S) + public void getCallerConfiguredNetworks_noNetworksConfigured_returnsEmptyList() { + assertThat(wifiManager.getCallerConfiguredNetworks()).isEmpty(); + } + + @Test + @Config(minSdk = S) + public void getCallerConfiguredNetworks_networksAddedAndRemoved_returnsConfiguredNetworks() { + WifiConfiguration wifiConfiguration = new WifiConfiguration(); + wifiManager.addNetwork(wifiConfiguration); + + assertThat(wifiManager.getCallerConfiguredNetworks()).hasSize(1); + + wifiManager.removeNetwork(0); + + assertThat(wifiManager.getCallerConfiguredNetworks()).isEmpty(); + } + + @Test + @Config(minSdk = S) + public void + removeNonCallerConfiguredNetworks_noChangeWifiStatePermission_shouldThrowSecurityException() { + setDeviceOwner(); + shadowOf(wifiManager).setChangeWifiStatePermission(false); + + assertThrows(SecurityException.class, () -> wifiManager.removeNonCallerConfiguredNetworks()); + } + + @Test + @Config(minSdk = S) + public void removeNonCallerConfiguredNetworks_notDeviceOwner_shouldThrowSecurityException() { + assertThrows(SecurityException.class, () -> wifiManager.removeNonCallerConfiguredNetworks()); + } + + @Test + @Config(minSdk = S) + public void removeNonCallerConfiguredNetworks_noConfiguredNetworks_returnsFalse() { + setDeviceOwner(); + + assertThat(wifiManager.removeNonCallerConfiguredNetworks()).isFalse(); + } + + @Test + @Config(minSdk = S) + public void removeNonCallerConfiguredNetworks_hasConfiguredNetworks_removesConfiguredNetworks() { + setDeviceOwner(); + wifiManager.addNetwork(new WifiConfiguration()); + wifiManager.addNetwork(new WifiConfiguration()); + + assertThat(wifiManager.removeNonCallerConfiguredNetworks()).isTrue(); + assertThat(wifiManager.getConfiguredNetworks()).isEmpty(); + } + + @Test @Config(minSdk = Build.VERSION_CODES.LOLLIPOP) public void getPrivilegedConfiguredNetworks_shouldReturnConfiguredNetworks() { WifiConfiguration wifiConfiguration = new WifiConfiguration(); @@ -764,4 +831,14 @@ public class ShadowWifiManagerTest { assertThat(shadowOf(wifiManager).getWifiApConfiguration().SSID).isEqualTo("foo"); } + + private void setDeviceOwner() { + shadowOf( + (DevicePolicyManager) + ApplicationProvider.getApplicationContext() + .getSystemService(Context.DEVICE_POLICY_SERVICE)) + .setDeviceOwner( + new ComponentName( + ApplicationProvider.getApplicationContext(), DeviceAdminService.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 ae8dfb894..8c3193381 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowApplicationPackageManager.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowApplicationPackageManager.java @@ -2236,6 +2236,22 @@ public class ShadowApplicationPackageManager extends ShadowPackageManager { return reflector(ReflectorApplicationPackageManager.class, realObject).getContext(); } + /** Stub that will always throw. */ + @Implementation(minSdk = S) + protected Object /* PackageManager.Property */ getProperty( + String propertyName, String packageName) throws NameNotFoundException { + // TODO: in future read this value from parsed manifest + throw new NameNotFoundException("unsupported"); + } + + /** Stub that will always throw. */ + @Implementation(minSdk = S) + protected Object /* PackageManager.Property */ getProperty( + String propertyName, ComponentName name) throws NameNotFoundException { + // TODO: in future read this value from parsed manifest + throw new NameNotFoundException("unsupported"); + } + /** Reflector interface for {@link ApplicationPackageManager}'s internals. */ @ForType(ApplicationPackageManager.class) private interface ReflectorApplicationPackageManager { diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowContentResolver.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowContentResolver.java index 0b86844e6..69b2dae92 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowContentResolver.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowContentResolver.java @@ -201,7 +201,7 @@ public class ShadowContentResolver { } @Implementation - protected final OutputStream openOutputStream(final Uri uri) { + protected final OutputStream openOutputStream(final Uri uri) throws FileNotFoundException { Supplier<OutputStream> supplier = outputStreamMap.get(uri); if (supplier != null) { OutputStream outputStream = supplier.get(); @@ -209,15 +209,23 @@ public class ShadowContentResolver { return outputStream; } } - return new OutputStream() { - @Override - public void write(int arg0) throws IOException {} - - @Override - public String toString() { - return "outputstream for " + uri; - } - }; + try { + return reflector( + org.robolectric.shadows.ShadowContentResolver.ContentResolverReflector.class, + realContentResolver) + .openOutputStream(uri); + } catch (SecurityException | FileNotFoundException e) { + // This is legacy behavior is only supported because existing users require it. + return new OutputStream() { + @Override + public void write(int arg0) throws IOException {} + + @Override + public String toString() { + return "outputstream for " + uri; + } + }; + } } /** diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDevicePolicyManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDevicePolicyManager.java index dbaa35301..422f96f3b 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDevicePolicyManager.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDevicePolicyManager.java @@ -89,6 +89,7 @@ public class ShadowDevicePolicyManager { private Map<String, Bundle> applicationRestrictionsMap = new HashMap<>(); private CharSequence organizationName; private int organizationColor; + private boolean isAutoTimeEnabled; private boolean isAutoTimeRequired; private boolean isAutoTimeZoneEnabled; private String timeZone; @@ -602,6 +603,18 @@ public class ShadowDevicePolicyManager { return organizationColor; } + @Implementation(minSdk = R) + protected void setAutoTimeEnabled(ComponentName admin, boolean enabled) { + enforceDeviceOwnerOrProfileOwner(admin); + isAutoTimeEnabled = enabled; + } + + @Implementation(minSdk = R) + protected boolean getAutoTimeEnabled(ComponentName admin) { + enforceDeviceOwnerOrProfileOwner(admin); + return isAutoTimeEnabled; + } + @Implementation(minSdk = LOLLIPOP) protected void setAutoTimeRequired(ComponentName admin, boolean required) { enforceDeviceOwnerOrProfileOwner(admin); 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 475607a74..4f488b571 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowEGL14.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowEGL14.java @@ -84,6 +84,11 @@ public class ShadowEGL14 { } @Implementation + protected static EGLContext eglGetCurrentContext() { + return createEglContext(3); + } + + @Implementation protected static boolean eglMakeCurrent( EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx) { return true; diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowInstrumentation.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowInstrumentation.java index e76b7973c..da5070a7a 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowInstrumentation.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowInstrumentation.java @@ -1011,10 +1011,20 @@ public class ShadowInstrumentation { } int checkPermission(String permission, int pid, int uid) { - Set<String> grantedPermissionsForPidUid = grantedPermissionsMap.get(new Pair(pid, uid)); - return grantedPermissionsForPidUid != null && grantedPermissionsForPidUid.contains(permission) - ? PERMISSION_GRANTED - : PERMISSION_DENIED; + if (pid == -1) { + for (Map.Entry<Pair<Integer, Integer>, Set<String>> entry : + grantedPermissionsMap.entrySet()) { + if (entry.getKey().second == uid && entry.getValue().contains(permission)) { + return PERMISSION_GRANTED; + } + } + return PERMISSION_DENIED; + } else { + Set<String> grantedPermissionsForPidUid = grantedPermissionsMap.get(new Pair(pid, uid)); + return grantedPermissionsForPidUid != null && grantedPermissionsForPidUid.contains(permission) + ? PERMISSION_GRANTED + : PERMISSION_DENIED; + } } void grantPermissions(String... permissionNames) { diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowScrollView.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowScrollView.java index a96c00ecd..d7126d2e8 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowScrollView.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowScrollView.java @@ -16,7 +16,7 @@ public class ShadowScrollView extends ShadowViewGroup { @Implementation protected void smoothScrollTo(int x, int y) { - if (useRealGraphics()) { + if (useRealScrolling()) { reflector(ScrollViewReflector.class, realScrollView).smoothScrollTo(x, y); } else { scrollTo(x, y); @@ -25,7 +25,7 @@ public class ShadowScrollView extends ShadowViewGroup { @Implementation protected void smoothScrollBy(int x, int y) { - if (useRealGraphics()) { + if (useRealScrolling()) { reflector(ScrollViewReflector.class, realScrollView).smoothScrollBy(x, y); } else { scrollBy(x, y); diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowServiceManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowServiceManager.java index aa51c52b8..5c9c05fa0 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowServiceManager.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowServiceManager.java @@ -42,6 +42,7 @@ import android.content.integrity.IAppIntegrityManager; import android.content.pm.ICrossProfileApps; import android.content.pm.IShortcutService; import android.content.rollback.IRollbackManager; +import android.hardware.ISensorPrivacyManager; import android.hardware.biometrics.IAuthService; import android.hardware.biometrics.IBiometricService; import android.hardware.fingerprint.IFingerprintService; @@ -205,6 +206,7 @@ public class ShadowServiceManager { addBinderService(Context.UWB_SERVICE, IUwbAdapter.class); addBinderService(Context.VCN_MANAGEMENT_SERVICE, IVcnManagementService.class); addBinderService(Context.TRANSLATION_MANAGER_SERVICE, ITranslationManager.class); + addBinderService(Context.SENSOR_PRIVACY_SERVICE, ISensorPrivacyManager.class); } if (RuntimeEnvironment.getApiLevel() >= TIRAMISU) { addBinderService(Context.AMBIENT_CONTEXT_SERVICE, IAmbientContextManager.class); diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowVibrator.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowVibrator.java index b66a0a414..df299c795 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowVibrator.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowVibrator.java @@ -6,15 +6,18 @@ import android.media.AudioAttributes; import android.os.VibrationAttributes; import android.os.VibrationEffect; import android.os.Vibrator; +import android.os.vibrator.PrimitiveSegment; import android.os.vibrator.VibrationEffectSegment; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Objects; +import java.util.stream.Collectors; import javax.annotation.Nullable; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; import org.robolectric.annotation.Resetter; +import org.robolectric.util.ReflectionHelpers; @Implements(Vibrator.class) public class ShadowVibrator { @@ -86,6 +89,20 @@ public class ShadowVibrator { return vibrationEffectSegments; } + /** Returns the last list of {@link PrimitiveSegment} vibrations in {@link PrimitiveEffect}. */ + @SuppressWarnings("JdkCollectors") // toImmutableList is only supported in Java 8+. + public List<PrimitiveEffect> getPrimitiveSegmentsInPrimitiveEffects() { + return vibrationEffectSegments.stream() + .filter(segment -> segment instanceof PrimitiveSegment) + .map( + segment -> + new PrimitiveEffect( + ReflectionHelpers.getField(segment, "mPrimitiveId"), + ReflectionHelpers.getField(segment, "mScale"), + ReflectionHelpers.getField(segment, "mDelay"))) + .collect(Collectors.toList()); + } + /** Returns the last list of {@link PrimitiveEffect}. */ @Nullable public List<PrimitiveEffect> getPrimitiveEffects() { diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowView.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowView.java index 70cb36999..1473eb853 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowView.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowView.java @@ -575,7 +575,7 @@ public class ShadowView { @Implementation protected void scrollTo(int x, int y) { - if (useRealGraphics()) { + if (useRealScrolling()) { reflector(_View_.class, realView).scrollTo(x, y); } else { reflector(_View_.class, realView) @@ -588,7 +588,7 @@ public class ShadowView { @Implementation protected void scrollBy(int x, int y) { - if (useRealGraphics()) { + if (useRealScrolling()) { reflector(_View_.class, realView).scrollBy(x, y); } else { scrollTo(getScrollX() + x, getScrollY() + y); @@ -597,7 +597,7 @@ public class ShadowView { @Implementation protected int getScrollX() { - if (useRealGraphics()) { + if (useRealScrolling()) { return reflector(_View_.class, realView).getScrollX(); } else { return scrollToCoordinates != null ? scrollToCoordinates.x : 0; @@ -606,7 +606,7 @@ public class ShadowView { @Implementation protected int getScrollY() { - if (useRealGraphics()) { + if (useRealScrolling()) { return reflector(_View_.class, realView).getScrollY(); } else { return scrollToCoordinates != null ? scrollToCoordinates.y : 0; @@ -615,7 +615,7 @@ public class ShadowView { @Implementation protected void setScrollX(int scrollX) { - if (useRealGraphics()) { + if (useRealScrolling()) { reflector(_View_.class, realView).setScrollX(scrollX); } else { scrollTo(scrollX, scrollToCoordinates.y); @@ -624,7 +624,7 @@ public class ShadowView { @Implementation protected void setScrollY(int scrollY) { - if (useRealGraphics()) { + if (useRealScrolling()) { reflector(_View_.class, realView).setScrollY(scrollY); } else { scrollTo(scrollToCoordinates.x, scrollY); @@ -1057,4 +1057,15 @@ public class ShadowView { static boolean useRealGraphics() { return Boolean.getBoolean("robolectric.nativeruntime.enableGraphics"); } + + /** + * Currently the default View scrolling implementation is broken and low-fidelty. For instance, + * even if a View has no children, Robolectric will still happily set the scroll position of a + * View. Long-term we want to eliminate this broken behavior, but in the mean time the real + * scrolling behavior is enabled when native graphics are enabled, or when a system property is + * set. + */ + static boolean useRealScrolling() { + return useRealGraphics() || Boolean.getBoolean("robolectric.useRealScrolling"); + } } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowViewRootImpl.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowViewRootImpl.java index cfe2bc7b6..2dd2e9a9e 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowViewRootImpl.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowViewRootImpl.java @@ -123,7 +123,30 @@ public class ShadowViewRootImpl { } public void callDispatchResized() { - if (RuntimeEnvironment.getApiLevel() > Build.VERSION_CODES.S_V2) { + if (RuntimeEnvironment.getApiLevel() > VERSION_CODES.TIRAMISU) { + Display display = getDisplay(); + Rect frame = new Rect(); + display.getRectSize(frame); + + ClientWindowFrames frames = new ClientWindowFrames(); + // set the final field + ReflectionHelpers.setField(frames, "frame", frame); + + ReflectionHelpers.callInstanceMethod( + ViewRootImpl.class, + realObject, + "dispatchResized", + ClassParameter.from(ClientWindowFrames.class, frames), + ClassParameter.from(boolean.class, true), /* reportDraw */ + ClassParameter.from( + MergedConfiguration.class, new MergedConfiguration()), /* mergedConfiguration */ + ClassParameter.from(InsetsState.class, new InsetsState()), /* insetsState */ + ClassParameter.from(boolean.class, false), /* forceLayout */ + ClassParameter.from(boolean.class, false), /* alwaysConsumeSystemBars */ + ClassParameter.from(int.class, 0), /* displayId */ + ClassParameter.from(int.class, 0), /* syncSeqId */ + ClassParameter.from(boolean.class, false) /* dragResizing */); + } else if (RuntimeEnvironment.getApiLevel() > Build.VERSION_CODES.S_V2) { Display display = getDisplay(); Rect frame = new Rect(); display.getRectSize(frame); diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWifiManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWifiManager.java index 561ed907f..78de3d1d7 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWifiManager.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWifiManager.java @@ -5,7 +5,9 @@ import static android.os.Build.VERSION_CODES.KITKAT; import static android.os.Build.VERSION_CODES.LOLLIPOP; import static android.os.Build.VERSION_CODES.Q; import static android.os.Build.VERSION_CODES.R; +import static android.os.Build.VERSION_CODES.S; +import android.app.admin.DevicePolicyManager; import android.content.Context; import android.content.Intent; import android.net.ConnectivityManager; @@ -49,6 +51,7 @@ public class ShadowWifiManager { private static float sSignalLevelInPercent = 1f; private boolean accessWifiStatePermission = true; + private boolean changeWifiStatePermission = true; private int wifiState = WifiManager.WIFI_STATE_ENABLED; private boolean wasSaved = false; private WifiInfo wifiInfo; @@ -140,6 +143,19 @@ public class ShadowWifiManager { return scanResults; } + /** + * The original implementation allows this to be called by the Device Owner (DO), Profile Owner + * (PO), callers with carrier privilege and system apps, but this shadow can be called by all apps + * carrying the ACCESS_WIFI_STATE permission. + * + * <p>This shadow is a wrapper for getConfiguredNetworks() and does not actually check the caller. + */ + @Implementation(minSdk = S) + protected List<WifiConfiguration> getCallerConfiguredNetworks() { + checkAccessWifiStatePermission(); + return getConfiguredNetworks(); + } + @Implementation protected List<WifiConfiguration> getConfiguredNetworks() { final ArrayList<WifiConfiguration> wifiConfigurations = new ArrayList<>(); @@ -172,6 +188,21 @@ public class ShadowWifiManager { } /** + * Removes all configured networks regardless of the app that created the network. Can only be + * called by a Device Owner (DO) app. + * + * @return {@code true} if at least one network is removed, {@code false} otherwise + */ + @Implementation(minSdk = S) + protected boolean removeNonCallerConfiguredNetworks() { + checkChangeWifiStatePermission(); + checkDeviceOwner(); + int previousSize = networkIdToConfiguredNetworks.size(); + networkIdToConfiguredNetworks.clear(); + return networkIdToConfiguredNetworks.size() < previousSize; + } + + /** * Adds or updates a network which can later be retrieved with {@link #getWifiConfiguration(int)} * method. A null {@param config}, or one with a networkId less than 0, or a networkId that had * its updatePermission removed using the {@link #setUpdateNetworkPermission(int, boolean)} will @@ -353,6 +384,10 @@ public class ShadowWifiManager { this.accessWifiStatePermission = accessWifiStatePermission; } + public void setChangeWifiStatePermission(boolean changeWifiStatePermission) { + this.changeWifiStatePermission = changeWifiStatePermission; + } + /** * Prevents a networkId from being updated using the {@link updateNetwork(WifiConfiguration)} * method. This is to simulate the case where a separate application creates a network, and the @@ -397,7 +432,21 @@ public class ShadowWifiManager { private void checkAccessWifiStatePermission() { if (!accessWifiStatePermission) { - throw new SecurityException(); + throw new SecurityException("Caller does not hold ACCESS_WIFI_STATE permission"); + } + } + + private void checkChangeWifiStatePermission() { + if (!changeWifiStatePermission) { + throw new SecurityException("Caller does not hold CHANGE_WIFI_STATE permission"); + } + } + + private void checkDeviceOwner() { + if (!getContext() + .getSystemService(DevicePolicyManager.class) + .isDeviceOwnerApp(getContext().getPackageName())) { + throw new SecurityException("Caller is not device owner"); } } |