diff options
author | Treehugger Robot <android-test-infra-autosubmit@system.gserviceaccount.com> | 2024-04-15 04:27:31 +0000 |
---|---|---|
committer | Gerrit Code Review <noreply-gerritcodereview@google.com> | 2024-04-15 04:27:31 +0000 |
commit | b3b17db94476c25225589165fbf53ec938f5b6dc (patch) | |
tree | f11775470d93f8e96cf44f5466f7a7c10a0edc9c | |
parent | 300f6ab8d044ea63c3fbe4b53fc0ca726f8cf573 (diff) | |
parent | acf14fc6bfb8c1c9a7cbb748bf841c4a2613258f (diff) | |
download | robolectric-b3b17db94476c25225589165fbf53ec938f5b6dc.tar.gz |
Merge changes from topic "merge" into main
* changes:
Cleanup vs upstream.
Merge branch 'upstream-google' of sso://android.googlesource.com/platform/external/robolectric into update
21 files changed, 768 insertions, 87 deletions
diff --git a/integration_tests/dependency-on-stubs/build.gradle b/integration_tests/dependency-on-stubs/build.gradle index 683de182d..1757653eb 100644 --- a/integration_tests/dependency-on-stubs/build.gradle +++ b/integration_tests/dependency-on-stubs/build.gradle @@ -1,18 +1,29 @@ -import org.robolectric.gradle.RoboJavaModulePlugin +import org.robolectric.gradle.AndroidProjectConfigPlugin -apply plugin: RoboJavaModulePlugin +apply plugin: 'com.android.library' +apply plugin: AndroidProjectConfigPlugin // test with a project that depends on the stubs jar, not org.robolectric:android-all -dependencies { - api project(":robolectric") - api libs.junit4 +android { + compileSdk 33 + namespace 'org.robolectric' + + defaultConfig { + minSdk 19 + targetSdk 33 + } - testImplementation files("${System.getenv("ANDROID_HOME")}/platforms/android-29/android.jar") + compileOptions { + sourceCompatibility = '1.8' + targetCompatibility = '1.8' + } - testCompileOnly AndroidSdk.MAX_SDK.coordinates // compile against latest Android SDK - testRuntimeOnly AndroidSdk.MAX_SDK.coordinates + testOptions.unitTests.includeAndroidResources true +} + +dependencies { + testImplementation project(":robolectric") + testImplementation libs.junit4 testImplementation libs.truth - testImplementation libs.mockito - testImplementation libs.hamcrest.junit } diff --git a/integration_tests/dependency-on-stubs/src/test/java/org/robolectric/LoadWeirdClassesTest.java b/integration_tests/dependency-on-stubs/src/test/java/org/robolectric/LoadWeirdClassesTest.java index 0d4093ff5..b3e4f14d7 100644 --- a/integration_tests/dependency-on-stubs/src/test/java/org/robolectric/LoadWeirdClassesTest.java +++ b/integration_tests/dependency-on-stubs/src/test/java/org/robolectric/LoadWeirdClassesTest.java @@ -1,15 +1,11 @@ package org.robolectric; import static android.os.Build.VERSION_CODES.KITKAT; -import static org.junit.Assume.assumeThat; import static org.robolectric.Shadows.shadowOf; -import android.app.Activity; import android.content.pm.PackageInfo; import android.os.Build; import android.view.Display; -import java.io.File; -import org.hamcrest.Matchers; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.annotation.Config; @@ -33,14 +29,6 @@ public class LoadWeirdClassesTest { } @Test - public void shadowOf_shouldCompile() { - assumeThat("Windows is an affront to decency.", - File.separator, Matchers.equalTo("/")); - - shadowOf(Robolectric.setupActivity(Activity.class)); - } - - @Test public void packageManager() { PackageInfo packageInfo = new PackageInfo(); packageInfo.packageName = "test.package"; diff --git a/robolectric/src/test/java/org/robolectric/shadows/InformationElementBuilderTest.java b/robolectric/src/test/java/org/robolectric/shadows/InformationElementBuilderTest.java new file mode 100644 index 000000000..4dd791392 --- /dev/null +++ b/robolectric/src/test/java/org/robolectric/shadows/InformationElementBuilderTest.java @@ -0,0 +1,28 @@ +package org.robolectric.shadows; + +import static com.google.common.truth.Truth.assertThat; + +import android.net.wifi.ScanResult.InformationElement; +import android.os.Build.VERSION_CODES; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import java.nio.ByteBuffer; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.annotation.Config; + +/** Test for {@link InformationElementBuilder} */ +@RunWith(AndroidJUnit4.class) +@Config(minSdk = VERSION_CODES.R) +public class InformationElementBuilderTest { + @Test + public void build_informationElement() { + byte[] bytes = new byte[] {1, 2, 3, 4}; + InformationElement informationElement = + InformationElementBuilder.newBuilder().setId(1).setIdExt(2).setBytes(bytes).build(); + + assertThat(informationElement).isNotNull(); + assertThat(informationElement.getId()).isEqualTo(1); + assertThat(informationElement.getIdExt()).isEqualTo(2); + assertThat(informationElement.getBytes()).isEqualTo(ByteBuffer.wrap(bytes)); + } +} diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowFileIntegrityManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowFileIntegrityManagerTest.java new file mode 100644 index 000000000..35a1c2e40 --- /dev/null +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowFileIntegrityManagerTest.java @@ -0,0 +1,38 @@ +package org.robolectric.shadows; + +import static android.os.Build.VERSION_CODES.R; +import static com.google.common.truth.Truth.assertThat; +import static org.robolectric.Shadows.shadowOf; + +import android.security.FileIntegrityManager; +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.annotation.Config; + +@RunWith(AndroidJUnit4.class) +@Config(minSdk = R) +public final class ShadowFileIntegrityManagerTest { + + private FileIntegrityManager fileIntegrityManager; + + @Before + public void setUp() { + fileIntegrityManager = + ApplicationProvider.getApplicationContext().getSystemService(FileIntegrityManager.class); + } + + @Test + public void isApkVeritySupported_returnsTrue() { + assertThat(fileIntegrityManager.isApkVeritySupported()).isTrue(); + } + + @Test + public void isApkVeritySupported_setFalse_returnsFalse() { + shadowOf(fileIntegrityManager).setIsApkVeritySupported(false); + + assertThat(fileIntegrityManager.isApkVeritySupported()).isFalse(); + } +} diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowTelephonyManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowTelephonyManagerTest.java index d560d60ea..6da61d037 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowTelephonyManagerTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowTelephonyManagerTest.java @@ -867,6 +867,15 @@ public class ShadowTelephonyManagerTest { } @Test + @Config(minSdk = Q) + public void shouldGetSimSpecificCarrierId() { + int expectedCarrierId = 132; + shadowOf(telephonyManager).setSimSpecificCarrierId(expectedCarrierId); + + assertThat(telephonyManager.getSimSpecificCarrierId()).isEqualTo(expectedCarrierId); + } + + @Test @Config(minSdk = P) public void shouldGetSimCarrierIdName() { String expectedCarrierIdName = "Fi"; @@ -1494,4 +1503,13 @@ public class ShadowTelephonyManagerTest { assertThat(tm.getSubscriptionId()).isEqualTo(123); } + + @Test + @Config(minSdk = Q) + public void setDataRoamingEnabledChangesIsDataRoamingEnabled() { + shadowOf(telephonyManager).setDataRoamingEnabled(false); + assertThat(telephonyManager.isDataRoamingEnabled()).isFalse(); + shadowOf(telephonyManager).setDataRoamingEnabled(true); + assertThat(telephonyManager.isDataRoamingEnabled()).isTrue(); + } } diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowTranslationManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowTranslationManagerTest.java index 0dfbb5cb4..acbef389b 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowTranslationManagerTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowTranslationManagerTest.java @@ -1,7 +1,6 @@ package org.robolectric.shadows; import static com.google.common.truth.Truth.assertThat; -import static org.robolectric.shadow.api.Shadow.extract; import android.icu.util.ULocale; import android.os.Build.VERSION_CODES; @@ -11,27 +10,21 @@ import android.view.translation.TranslationSpec; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.common.collect.ImmutableSet; -import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.annotation.Config; +import org.robolectric.shadow.api.Shadow; @RunWith(AndroidJUnit4.class) @Config(minSdk = VERSION_CODES.S) public class ShadowTranslationManagerTest { - private ShadowTranslationManager instance; - - @Before - public void setUp() { - instance = - extract( - ApplicationProvider.getApplicationContext().getSystemService(TranslationManager.class)); - } + private final TranslationManager translationManager = + ApplicationProvider.getApplicationContext().getSystemService(TranslationManager.class); @Test public void getOnDeviceTranslationCapabilities_noCapabilitiesSet_returnsEmpty() { assertThat( - instance.getOnDeviceTranslationCapabilities( + translationManager.getOnDeviceTranslationCapabilities( TranslationSpec.DATA_FORMAT_TEXT, TranslationSpec.DATA_FORMAT_TEXT)) .isEmpty(); } @@ -52,11 +45,12 @@ public class ShadowTranslationManagerTest { new TranslationSpec(ULocale.FRENCH, TranslationSpec.DATA_FORMAT_TEXT), /* uiTranslationEnabled= */ true, /* supportedTranslationFlags= */ 0)); - instance.setOnDeviceTranslationCapabilities( - TranslationSpec.DATA_FORMAT_TEXT, TranslationSpec.DATA_FORMAT_TEXT, capabilities); + ((ShadowTranslationManager) Shadow.extract(translationManager)) + .setOnDeviceTranslationCapabilities( + TranslationSpec.DATA_FORMAT_TEXT, TranslationSpec.DATA_FORMAT_TEXT, capabilities); assertThat( - instance.getOnDeviceTranslationCapabilities( + translationManager.getOnDeviceTranslationCapabilities( TranslationSpec.DATA_FORMAT_TEXT, TranslationSpec.DATA_FORMAT_TEXT)) .isEqualTo(capabilities); } diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowWindowManagerGlobalTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowWindowManagerGlobalTest.java index 8bc4a049d..28185849a 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowWindowManagerGlobalTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowWindowManagerGlobalTest.java @@ -2,22 +2,37 @@ package org.robolectric.shadows; import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; import android.app.Activity; import android.content.ClipData; +import android.graphics.Rect; import android.os.Build.VERSION_CODES; import android.os.Bundle; import android.os.Looper; +import android.view.Display; import android.view.MotionEvent; import android.view.View; import android.view.View.DragShadowBuilder; +import android.view.View.OnTouchListener; +import android.view.ViewConfiguration; +import android.window.BackEvent; +import android.window.OnBackAnimationCallback; +import android.window.OnBackInvokedDispatcher; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; +import java.util.ArrayList; +import java.util.List; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.Robolectric; import org.robolectric.RuntimeEnvironment; +import org.robolectric.android.controller.ActivityController; import org.robolectric.annotation.Config; @RunWith(AndroidJUnit4.class) @@ -60,6 +75,158 @@ public class ShadowWindowManagerGlobalTest { assertThat(decorView.getWindowVisibility()).isEqualTo(View.VISIBLE); } + @SuppressWarnings("MemberName") // In lieu of parameterization. + private void startPredictiveBackGesture_callsBackCallbackMethods(@BackEvent.SwipeEdge int edge) { + ShadowApplication.setEnableOnBackInvokedCallback(true); + float touchSlop = + ViewConfiguration.get(ApplicationProvider.getApplicationContext()).getScaledTouchSlop(); + try (ActivityController<ActivityWithBackCallback> controller = + Robolectric.buildActivity(ActivityWithBackCallback.class)) { + Activity activity = controller.setup().get(); + TestBackAnimationCallback backInvokedCallback = new TestBackAnimationCallback(); + activity + .getOnBackInvokedDispatcher() + .registerOnBackInvokedCallback( + OnBackInvokedDispatcher.PRIORITY_DEFAULT, backInvokedCallback); + + float moveByX = (edge == BackEvent.EDGE_LEFT ? 1 : -1) * touchSlop * 2; + try (ShadowWindowManagerGlobal.PredictiveBackGesture backGesture = + ShadowWindowManagerGlobal.startPredictiveBackGesture(edge)) { + backGesture.moveBy(moveByX, 0f); + } + + assertThat(backInvokedCallback.onBackStarted).isNotNull(); + assertThat(backInvokedCallback.onBackProgressed).isNotEmpty(); + assertThat(Iterables.getLast(backInvokedCallback.onBackProgressed).getTouchX()) + .isEqualTo(backInvokedCallback.onBackStarted.getTouchX() + moveByX); + assertThat(Iterables.getLast(backInvokedCallback.onBackProgressed).getProgress()) + .isGreaterThan(0); + assertThat(backInvokedCallback.onBackInvokedCalled).isTrue(); + } + } + + @Test + @Config(minSdk = VERSION_CODES.UPSIDE_DOWN_CAKE) + public void startPredictiveBackGesture_leftEdge_callsBackCallbackMethods() { + startPredictiveBackGesture_callsBackCallbackMethods(BackEvent.EDGE_LEFT); + } + + @Test + @Config(minSdk = VERSION_CODES.UPSIDE_DOWN_CAKE) + public void startPredictiveBackGesture_rightEdge_callsBackCallbackMethods() { + startPredictiveBackGesture_callsBackCallbackMethods(BackEvent.EDGE_RIGHT); + } + + @Test + @Config(minSdk = VERSION_CODES.UPSIDE_DOWN_CAKE) + public void startPredictiveBackGesture_cancel_callbackIsCancelled() { + ShadowApplication.setEnableOnBackInvokedCallback(true); + try (ActivityController<ActivityWithBackCallback> controller = + Robolectric.buildActivity(ActivityWithBackCallback.class)) { + Activity activity = controller.setup().get(); + TestBackAnimationCallback backInvokedCallback = new TestBackAnimationCallback(); + activity + .getOnBackInvokedDispatcher() + .registerOnBackInvokedCallback( + OnBackInvokedDispatcher.PRIORITY_DEFAULT, backInvokedCallback); + + try (ShadowWindowManagerGlobal.PredictiveBackGesture backGesture = + ShadowWindowManagerGlobal.startPredictiveBackGesture(BackEvent.EDGE_LEFT)) { + backGesture.cancel(); + } + + assertThat(backInvokedCallback.onBackStarted).isNotNull(); + assertThat(backInvokedCallback.onBackCancelledCalled).isTrue(); + } + } + + @Test + @Config(minSdk = VERSION_CODES.UPSIDE_DOWN_CAKE) + public void startPredictiveBackGesture_withExclusion_isNotCalled() { + ShadowApplication.setEnableOnBackInvokedCallback(true); + Display display = ShadowDisplay.getDefaultDisplay(); + try (ActivityController<ActivityWithBackCallback> controller = + Robolectric.buildActivity(ActivityWithBackCallback.class)) { + Activity activity = controller.setup().get(); + TestBackAnimationCallback backInvokedCallback = new TestBackAnimationCallback(); + activity + .getOnBackInvokedDispatcher() + .registerOnBackInvokedCallback( + OnBackInvokedDispatcher.PRIORITY_DEFAULT, backInvokedCallback); + // Exclude the entire display. + activity + .findViewById(android.R.id.content) + .setSystemGestureExclusionRects( + ImmutableList.of(new Rect(0, 0, display.getWidth(), display.getHeight()))); + + ShadowWindowManagerGlobal.PredictiveBackGesture backGesture = + ShadowWindowManagerGlobal.startPredictiveBackGesture(BackEvent.EDGE_LEFT); + + assertThat(backGesture).isNull(); + assertThat(backInvokedCallback.onBackStarted).isNull(); + } + } + + @Test + @Config(minSdk = VERSION_CODES.UPSIDE_DOWN_CAKE) + public void startPredictiveBackGesture_cancelledTouchEventsDispatchedToWindow() { + ShadowApplication.setEnableOnBackInvokedCallback(true); + try (ActivityController<ActivityWithBackCallback> controller = + Robolectric.buildActivity(ActivityWithBackCallback.class)) { + Activity activity = controller.setup().get(); + List<MotionEvent> touchEvents = new ArrayList<>(); + activity + .getOnBackInvokedDispatcher() + .registerOnBackInvokedCallback( + OnBackInvokedDispatcher.PRIORITY_DEFAULT, new TestBackAnimationCallback()); + activity + .findViewById(android.R.id.content) + .setOnTouchListener( + new OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + touchEvents.add(event); + return true; + } + }); + + ShadowWindowManagerGlobal.startPredictiveBackGesture(BackEvent.EDGE_LEFT).close(); + + assertThat(touchEvents).isNotEmpty(); + assertThat(touchEvents.get(0).getAction()).isEqualTo(MotionEvent.ACTION_DOWN); + assertThat(Iterables.getLast(touchEvents).getAction()).isEqualTo(MotionEvent.ACTION_CANCEL); + } + } + + @Test + @Config(minSdk = VERSION_CODES.UPSIDE_DOWN_CAKE) + public void startPredictiveBackGesture_invalidPosition_throwsIllegalArgumentException() { + ShadowApplication.setEnableOnBackInvokedCallback(true); + assertThrows( + IllegalArgumentException.class, + () -> ShadowWindowManagerGlobal.startPredictiveBackGesture(BackEvent.EDGE_LEFT, -1f)); + } + + @Test + @Config(minSdk = VERSION_CODES.UPSIDE_DOWN_CAKE) + public void startPredictiveBackGesture_alreadyOngoing_throwsIllegalStateException() { + ShadowApplication.setEnableOnBackInvokedCallback(true); + try (ActivityController<ActivityWithBackCallback> controller = + Robolectric.buildActivity(ActivityWithBackCallback.class)) { + Activity activity = controller.setup().get(); + activity + .getOnBackInvokedDispatcher() + .registerOnBackInvokedCallback( + OnBackInvokedDispatcher.PRIORITY_DEFAULT, new TestBackAnimationCallback()); + + ShadowWindowManagerGlobal.startPredictiveBackGesture(BackEvent.EDGE_LEFT); + + assertThrows( + IllegalStateException.class, + () -> ShadowWindowManagerGlobal.startPredictiveBackGesture(BackEvent.EDGE_LEFT)); + } + } + static final class DragActivity extends Activity { @Override protected void onCreate(@Nullable Bundle bundle) { @@ -81,4 +248,33 @@ public class ShadowWindowManagerGlobalTest { setContentView(contentView); } } + + public static final class ActivityWithBackCallback extends Activity {} + + private static final class TestBackAnimationCallback implements OnBackAnimationCallback { + @Nullable public BackEvent onBackStarted; + public List<BackEvent> onBackProgressed = new ArrayList<>(); + public boolean onBackInvokedCalled = false; + public boolean onBackCancelledCalled = false; + + @Override + public void onBackStarted(@NonNull BackEvent backEvent) { + onBackStarted = backEvent; + } + + @Override + public void onBackProgressed(@NonNull BackEvent backEvent) { + onBackProgressed.add(backEvent); + } + + @Override + public void onBackInvoked() { + onBackInvokedCalled = true; + } + + @Override + public void onBackCancelled() { + onBackCancelledCalled = true; + } + } } diff --git a/robolectric/src/test/resources/AndroidManifest.xml b/robolectric/src/test/resources/AndroidManifest.xml index b677954f2..27a3dc9b3 100644 --- a/robolectric/src/test/resources/AndroidManifest.xml +++ b/robolectric/src/test/resources/AndroidManifest.xml @@ -81,6 +81,9 @@ <activity android:name="org.robolectric.shadows.ShadowPackageManagerTest$ActivityWithConfigChanges" android:configChanges="screenLayout|orientation"/> + <activity android:name="org.robolectric.shadows.ShadowWindowManagerGlobalTest$ActivityWithBackCallback" + android:enableOnBackInvokedCallback="true" /> + <activity android:name="org.robolectric.shadows.ShadowActivityTest$LabelTestActivity1" /> <activity android:name="org.robolectric.shadows.ShadowActivityTest$LabelTestActivity2" android:label="@string/activity_name"/> diff --git a/shadowapi/src/main/java/org/robolectric/util/ReflectionHelpers.java b/shadowapi/src/main/java/org/robolectric/util/ReflectionHelpers.java index 4226e788a..1c6071f27 100644 --- a/shadowapi/src/main/java/org/robolectric/util/ReflectionHelpers.java +++ b/shadowapi/src/main/java/org/robolectric/util/ReflectionHelpers.java @@ -540,4 +540,3 @@ public class ReflectionHelpers { } } } - diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/InformationElementBuilder.java b/shadows/framework/src/main/java/org/robolectric/shadows/InformationElementBuilder.java new file mode 100644 index 000000000..493244daa --- /dev/null +++ b/shadows/framework/src/main/java/org/robolectric/shadows/InformationElementBuilder.java @@ -0,0 +1,50 @@ +package org.robolectric.shadows; + +import static android.os.Build.VERSION_CODES.R; + +import android.net.wifi.ScanResult.InformationElement; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import org.robolectric.RuntimeEnvironment; + +/** Builder for {@link InformationElement}. */ +public class InformationElementBuilder { + private int id; + private int idExt; + private byte[] bytes; + + private InformationElementBuilder() {} + + public static InformationElementBuilder newBuilder() { + return new InformationElementBuilder(); + } + + @CanIgnoreReturnValue + public InformationElementBuilder setId(int id) { + this.id = id; + return this; + } + + @CanIgnoreReturnValue + public InformationElementBuilder setIdExt(int idExt) { + this.idExt = idExt; + return this; + } + + @CanIgnoreReturnValue + public InformationElementBuilder setBytes(byte[] bytes) { + this.bytes = bytes; + return this; + } + + public InformationElement build() { + if (RuntimeEnvironment.getApiLevel() > R) { + return new InformationElement(this.id, this.idExt, this.bytes); + } else { + InformationElement info = new InformationElement(); + info.id = this.id; + info.idExt = this.idExt; + info.bytes = this.bytes; + return info; + } + } +} diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowApplication.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowApplication.java index b726386c8..950193699 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowApplication.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowApplication.java @@ -16,6 +16,7 @@ import android.content.ContextWrapper; import android.content.Intent; import android.content.IntentFilter; import android.content.ServiceConnection; +import android.os.Build; import android.os.Handler; import android.os.IBinder; import android.os.PowerManager; @@ -391,4 +392,19 @@ public class ShadowApplication extends ShadowContextWrapper { ShadowContextImpl shadowContext = Shadow.extract(realApplication.getBaseContext()); shadowContext.setSystemService(key, service); } + + /** + * Enables or disables predictive back for the current application. + * + * <p>This is the equivalent of specifying {code android:enableOnBackInvokedCallback} on the + * {@code <application>} tag in the Android manifest. + */ + public static void setEnableOnBackInvokedCallback(boolean isEnabled) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { + ShadowWindowOnBackInvokedDispatcher.setEnablePredictiveBack(isEnabled); + RuntimeEnvironment.getApplication() + .getApplicationInfo() + .setEnableOnBackInvokedCallback(isEnabled); + } + } } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowFileIntegrityManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowFileIntegrityManager.java new file mode 100644 index 000000000..f00259319 --- /dev/null +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowFileIntegrityManager.java @@ -0,0 +1,28 @@ +package org.robolectric.shadows; + +import static android.os.Build.VERSION_CODES.R; + +import android.security.FileIntegrityManager; +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; + +/** Shadow for {@link FileIntegrityManager}. */ +@Implements(value = FileIntegrityManager.class, minSdk = R) +public class ShadowFileIntegrityManager { + + private boolean isApkVeritySupported = true; + + /** Sets the value of {@link #isApkVeritySupported}. */ + public void setIsApkVeritySupported(boolean isApkVeritySupported) { + this.isApkVeritySupported = isApkVeritySupported; + } + + /** + * Returns {@code true} by default, or can be changed by {@link + * #setIsApkVeritySupported(boolean)}. + */ + @Implementation + protected boolean isApkVeritySupported() { + return isApkVeritySupported; + } +} diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowInformationElement.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowInformationElement.java deleted file mode 100644 index 76fc9a172..000000000 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowInformationElement.java +++ /dev/null @@ -1,40 +0,0 @@ -package org.robolectric.shadows; - -import android.net.wifi.ScanResult; -import android.os.Build.VERSION_CODES; -import org.robolectric.annotation.Implements; - -/** Shadow for {@link android.net.wifi.ScanResult.InformationElement}. */ -@Implements(value = ScanResult.InformationElement.class, minSdk = VERSION_CODES.R) -public class ShadowInformationElement { - /** - * A builder for creating ShadowInformationElement objects. Use build() to return the - * InformationElement object. - */ - public static class Builder { - private final ScanResult.InformationElement informationElement; - - public Builder() { - informationElement = new ScanResult.InformationElement(); - } - - public Builder setId(int id) { - informationElement.id = id; - return this; - } - - public Builder setIdExt(int idExt) { - informationElement.idExt = idExt; - return this; - } - - public Builder setBytes(byte[] bytes) { - informationElement.bytes = bytes; - return this; - } - - public ScanResult.InformationElement build() { - return informationElement; - } - } -} 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 e33bc335b..fe74b37f1 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowServiceManager.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowServiceManager.java @@ -82,6 +82,7 @@ import android.os.storage.IStorageManager; import android.permission.ILegacyPermissionManager; import android.permission.IPermissionManager; import android.safetycenter.ISafetyCenterManager; +import android.security.IFileIntegrityService; import android.speech.IRecognitionServiceManager; import android.uwb.IUwbAdapter; import android.view.IWindowManager; @@ -242,6 +243,7 @@ public class ShadowServiceManager { addBinderService(binderServices, Context.TETHERING_SERVICE, ITetheringConnector.class); addBinderService(binderServices, "telephony.registry", ITelephonyRegistry.class); addBinderService(binderServices, Context.PLATFORM_COMPAT_SERVICE, IPlatformCompat.class); + addBinderService(binderServices, Context.FILE_INTEGRITY_SERVICE, IFileIntegrityService.class); } if (RuntimeEnvironment.getApiLevel() >= S) { addBinderService(binderServices, "permissionmgr", IPermissionManager.class); 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 4c8c369fa..bdf1d08bd 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTelephonyManager.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTelephonyManager.java @@ -153,6 +153,7 @@ public class ShadowTelephonyManager { private static final Map<Integer, String> simCountryIsoMap = Collections.synchronizedMap(new LinkedHashMap<>()); private int simCarrierId; + private int simSpecificCarrierId; private CharSequence simCarrierIdName; private int carrierIdFromSimMccMnc; private String subscriberId; @@ -177,6 +178,7 @@ public class ShadowTelephonyManager { private VisualVoicemailSmsFilterSettings visualVoicemailSmsFilterSettings; private static volatile boolean emergencyCallbackMode; private static Map<Integer, List<EmergencyNumber>> emergencyNumbersList; + private static volatile boolean isDataRoamingEnabled; /** * Should be {@link TelephonyManager.BootstrapAuthenticationCallback} but this object was @@ -1226,6 +1228,16 @@ public class ShadowTelephonyManager { this.simCarrierId = simCarrierId; } + @Implementation(minSdk = Q) + protected int getSimSpecificCarrierId() { + return simSpecificCarrierId; + } + + /** Sets the value to be returned by {@link #getSimSpecificCarrierId()}. */ + public void setSimSpecificCarrierId(int simSpecificCarrierId) { + this.simSpecificCarrierId = simSpecificCarrierId; + } + @Implementation(minSdk = P) protected CharSequence getSimCarrierIdName() { return simCarrierIdName; @@ -1633,4 +1645,21 @@ public class ShadowTelephonyManager { } return ImmutableMap.of(); } + + /** + * Implementation for {@link TelephonyManager#isDataRoamingEnabled}. + * + * @return False by default, unless set with {@link #setDataRoamingEnabled(boolean)}. + */ + @Implementation(minSdk = Q) + protected boolean isDataRoamingEnabled() { + checkReadPhoneStatePermission(); + return isDataRoamingEnabled; + } + + /** Sets the value to be returned by {@link #isDataRoamingEnabled()} */ + @Implementation(minSdk = Q) + protected void setDataRoamingEnabled(boolean isDataRoamingEnabled) { + ShadowTelephonyManager.isDataRoamingEnabled = isDataRoamingEnabled; + } } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTranslationManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTranslationManager.java index ed33b5928..9cd316944 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTranslationManager.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTranslationManager.java @@ -11,7 +11,11 @@ import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; /** Shadow for {@link TranslationManager}. */ -@Implements(value = TranslationManager.class, minSdk = VERSION_CODES.S) +@Implements( + value = TranslationManager.class, + minSdk = VERSION_CODES.S, + isInAndroidSdk = false // turn off shadowOf generation + ) public class ShadowTranslationManager { private final Table<Integer, Integer, ImmutableSet<TranslationCapability>> onDeviceTranslationCapabilities = HashBasedTable.create(); diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowUserManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowUserManager.java index 78a96ec31..1fef4d09d 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowUserManager.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowUserManager.java @@ -76,6 +76,7 @@ public class ShadowUserManager { public static final int FLAG_PROFILE = UserInfo.FLAG_PROFILE; public static final int FLAG_FULL = UserInfo.FLAG_FULL; public static final int FLAG_SYSTEM = UserInfo.FLAG_SYSTEM; + public static final int FLAG_MAIN = UserInfo.FLAG_MAIN; private static int maxSupportedUsers = DEFAULT_MAX_SUPPORTED_USERS; private static boolean isMultiUserSupported = false; @@ -131,13 +132,10 @@ public class ShadowUserManager { private int nextUserId = DEFAULT_SECONDARY_USER_ID; - // TODO: use UserInfo.FLAG_MAIN when available - private static final int FLAG_MAIN = 0x00004000; - public UserManagerState() { int id = UserHandle.USER_SYSTEM; String name = "system_user"; - int flags = UserInfo.FLAG_PRIMARY | UserInfo.FLAG_ADMIN | FLAG_MAIN; + int flags = UserInfo.FLAG_PRIMARY | UserInfo.FLAG_ADMIN | UserInfo.FLAG_MAIN; userSerialNumbers.put(id, (long) id); // Start the user as shut down. 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 20749faea..86c2bd02e 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowViewRootImpl.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowViewRootImpl.java @@ -520,4 +520,3 @@ public class ShadowViewRootImpl { WindowInsets getWindowInsets(boolean forceConstruct); } } - diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWindowManagerGlobal.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWindowManagerGlobal.java index 4f988c39f..456609dea 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWindowManagerGlobal.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWindowManagerGlobal.java @@ -2,22 +2,38 @@ package org.robolectric.shadows; import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; import static android.os.Build.VERSION_CODES.P; +import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE; +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkState; +import static java.lang.Math.max; +import static java.lang.Math.round; import static org.robolectric.shadows.ShadowView.useRealGraphics; import static org.robolectric.util.reflector.Reflector.reflector; +import android.annotation.FloatRange; import android.annotation.Nullable; import android.app.Instrumentation; import android.content.ClipData; import android.content.Context; +import android.graphics.Rect; import android.os.Binder; import android.os.IBinder; import android.os.Looper; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.SystemClock; +import android.util.Log; import android.view.IWindowManager; import android.view.IWindowSession; +import android.view.MotionEvent; +import android.view.RemoteAnimationTarget; import android.view.View; +import android.view.ViewConfiguration; import android.view.WindowManagerGlobal; +import android.window.BackEvent; +import android.window.BackMotionEvent; +import android.window.OnBackInvokedCallbackInfo; +import java.io.Closeable; import java.lang.reflect.Proxy; import java.util.List; import org.robolectric.RuntimeEnvironment; @@ -26,15 +42,13 @@ import org.robolectric.annotation.Implements; import org.robolectric.annotation.Resetter; import org.robolectric.util.ReflectionHelpers; import org.robolectric.util.reflector.Accessor; +import org.robolectric.util.reflector.Constructor; import org.robolectric.util.reflector.ForType; import org.robolectric.util.reflector.Static; /** Shadow for {@link WindowManagerGlobal}. */ @SuppressWarnings("unused") // Unused params are implementations of Android SDK methods. -@Implements( - value = WindowManagerGlobal.class, - isInAndroidSdk = false, - looseSignatures = true) +@Implements(value = WindowManagerGlobal.class, isInAndroidSdk = false, looseSignatures = true) public class ShadowWindowManagerGlobal { private static WindowSessionDelegate windowSessionDelegate = new WindowSessionDelegate(); private static IWindowSession windowSession; @@ -72,6 +86,199 @@ public class ShadowWindowManagerGlobal { windowSessionDelegate.lastDragClipData = null; } + /** + * Ongoing predictive back gesture. + * + * <p>Start a predictive back gesture by calling {@link + * ShadowWindowManagerGlobal#startPredictiveBackGesture}. One or more drag progress events can be + * dispatched by calling {@link #moveBy}. The gesture must be ended by either calling {@link + * #cancel()} or {@link #close()}, if {@link #cancel()} is called a subsequent call to {@link + * close()} will do nothing to allow using the gesture in a try with resources statement: + * + * <pre> + * try (PredictiveBackGesture backGesture = + * ShadowWindowManagerGlobal.startPredictiveBackGesture(BackEvent.EDGE_LEFT)) { + * backGesture.moveBy(10, 10); + * } + * </pre> + */ + public static final class PredictiveBackGesture implements Closeable { + @BackEvent.SwipeEdge private final int edge; + private final int displayWidth; + private final float startTouchX; + private final float progressThreshold; + private float touchX; + private float touchY; + private boolean isCancelled; + private boolean isFinished; + + private PredictiveBackGesture( + @BackEvent.SwipeEdge int edge, int displayWidth, float touchX, float touchY) { + this.edge = edge; + this.displayWidth = displayWidth; + this.progressThreshold = + ViewConfiguration.get(RuntimeEnvironment.getApplication()).getScaledTouchSlop(); + this.startTouchX = touchX; + this.touchX = touchX; + this.touchY = touchY; + } + + /** Dispatches drag progress for a predictive back gesture. */ + public void moveBy(float dx, float dy) { + checkState(!isCancelled && !isFinished); + try { + touchX += dx; + touchY += dy; + ShadowWindowManagerGlobal.windowSessionDelegate + .onBackInvokedCallbackInfo + .getCallback() + .onBackProgressed( + BackMotionEvents.newBackMotionEvent(edge, touchX, touchY, caclulateProgress())); + ShadowLooper.idleMainLooper(); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** Cancels the back gesture. */ + public void cancel() { + checkState(!isCancelled && !isFinished); + isCancelled = true; + try { + ShadowWindowManagerGlobal.windowSessionDelegate + .onBackInvokedCallbackInfo + .getCallback() + .onBackCancelled(); + ShadowWindowManagerGlobal.windowSessionDelegate.currentPredictiveBackGesture = null; + ShadowLooper.idleMainLooper(); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** + * Ends the back gesture. If the back gesture has not been cancelled by calling {@link + * #cancel()} then the back handler is invoked. + * + * <p>Callers should always call either {@link #cancel()} or {@link #close()}. It is recommended + * to use the result of {@link ShadowWindowManagerGlobal#startPredictiveBackGesture} in a try + * with resources. + */ + @Override + public void close() { + checkState(!isFinished); + isFinished = true; + if (!isCancelled) { + try { + ShadowWindowManagerGlobal.windowSessionDelegate + .onBackInvokedCallbackInfo + .getCallback() + .onBackInvoked(); + ShadowWindowManagerGlobal.windowSessionDelegate.currentPredictiveBackGesture = null; + ShadowLooper.idleMainLooper(); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + } + + private float caclulateProgress() { + // The real implementation anchors the progress on the start x and resets it each time the + // threshold is lost, it also calculates a linear and non linear progress area. This + // implementation is much simpler. + int direction = (edge == BackEvent.EDGE_LEFT ? 1 : -1); + float draggableWidth = + (edge == BackEvent.EDGE_LEFT ? displayWidth - startTouchX : startTouchX) + - progressThreshold; + return max((((touchX - startTouchX) * direction) - progressThreshold) / draggableWidth, 0f); + } + } + + /** + * Starts a predictive back gesture in the center of the edge. See {@link + * #startPredictiveBackGesture(int, float)}. + */ + @Nullable + public static PredictiveBackGesture startPredictiveBackGesture(@BackEvent.SwipeEdge int edge) { + return startPredictiveBackGesture(edge, 0.5f); + } + + /** + * Starts a predictive back gesture. + * + * <p>If no active activity with a back pressed callback that supports animations is registered + * then null will be returned. See {@link PredictiveBackGesture}. + * + * <p>See {@link ShadowApplication#setEnableOnBackInvokedCallback}. + * + * @param position The position on edge of the window + */ + @Nullable + public static PredictiveBackGesture startPredictiveBackGesture( + @BackEvent.SwipeEdge int edge, @FloatRange(from = 0f, to = 1f) float position) { + checkArgument(position >= 0f && position <= 1f, "Invalid position: %s.", position); + checkState( + windowSessionDelegate.currentPredictiveBackGesture == null, + "Current predictive back gesture in progress."); + if (windowSessionDelegate.onBackInvokedCallbackInfo == null + || !windowSessionDelegate.onBackInvokedCallbackInfo.isAnimationCallback()) { + return null; + } else { + try { + // Exclusion rects are sent to the window session by posting so idle the looper first. + ShadowLooper.idleMainLooper(); + int touchSlop = + ViewConfiguration.get(RuntimeEnvironment.getApplication()).getScaledTouchSlop(); + int displayWidth = ShadowDisplay.getDefaultDisplay().getWidth(); + float deltaX = (edge == BackEvent.EDGE_LEFT ? 1 : -1) * touchSlop / 2f; + float downX = (edge == BackEvent.EDGE_LEFT ? 0 : displayWidth) + deltaX; + float downY = ShadowDisplay.getDefaultDisplay().getHeight() * position; + if (windowSessionDelegate.systemGestureExclusionRects != null) { + // TODO: The rects should be offset based on the window's position in the display, most + // windows should be full screen which makes this naive logic work ok. + for (Rect rect : windowSessionDelegate.systemGestureExclusionRects) { + if (rect.contains(round(downX), round(downY))) { + return null; + } + } + } + // A predictive back gesture starts as a user swipe which the window will receive the start + // of the gesture before it gets intercepted by the window manager. + MotionEvent downEvent = + MotionEvent.obtain( + /* downTime= */ SystemClock.uptimeMillis(), + /* eventTime= */ SystemClock.uptimeMillis(), + MotionEvent.ACTION_DOWN, + downX, + downY, + /* metaState= */ 0); + MotionEvent moveEvent = MotionEvent.obtain(downEvent); + moveEvent.setAction(MotionEvent.ACTION_MOVE); + moveEvent.offsetLocation(deltaX, 0); + MotionEvent cancelEvent = MotionEvent.obtain(moveEvent); + cancelEvent.setAction(MotionEvent.ACTION_CANCEL); + ShadowUiAutomation.injectInputEvent(downEvent); + ShadowUiAutomation.injectInputEvent(moveEvent); + ShadowUiAutomation.injectInputEvent(cancelEvent); + windowSessionDelegate + .onBackInvokedCallbackInfo + .getCallback() + .onBackStarted( + BackMotionEvents.newBackMotionEvent( + edge, downX + 2 * deltaX, downY, /* progress= */ 0)); + ShadowLooper.idleMainLooper(); + PredictiveBackGesture backGesture = + new PredictiveBackGesture(edge, displayWidth, downX + 2 * deltaX, downY); + windowSessionDelegate.currentPredictiveBackGesture = backGesture; + return backGesture; + } catch (RemoteException e) { + Log.e("ShadowWindowManagerGlobal", "Failed to start back gesture", e); + return null; + } + } + } + + @SuppressWarnings("unchecked") // Cast args to IWindowSession methods @Implementation protected static synchronized IWindowSession getWindowSession() { if (windowSession == null) { @@ -99,6 +306,13 @@ public class ShadowWindowManagerGlobal { case "setInTouchMode": windowSessionDelegate.setInTouchMode((boolean) args[0]); return null; + case "setOnBackInvokedCallbackInfo": + windowSessionDelegate.onBackInvokedCallbackInfo = + (OnBackInvokedCallbackInfo) args[1]; + return null; + case "reportSystemGestureExclusionChanged": + windowSessionDelegate.systemGestureExclusionRects = (List<Rect>) args[1]; + return null; default: return ReflectionHelpers.defaultValueForType( method.getReturnType().getName()); @@ -155,6 +369,9 @@ public class ShadowWindowManagerGlobal { // TODO: Default to touch mode always. private boolean inTouchMode = useRealGraphics(); @Nullable protected ClipData lastDragClipData; + @Nullable private OnBackInvokedCallbackInfo onBackInvokedCallbackInfo; + @Nullable private List<Rect> systemGestureExclusionRects; + @Nullable private PredictiveBackGesture currentPredictiveBackGesture; protected int getAddFlags() { int res = 0; @@ -196,4 +413,67 @@ public class ShadowWindowManagerGlobal { throw new AssertionError("Missing ClipData param"); } } + + @ForType(BackMotionEvent.class) + interface BackMotionEventReflector { + @Constructor + BackMotionEvent newBackMotionEvent( + float touchX, + float touchY, + float progress, + float velocityX, + float velocityY, + int swipeEdge, + RemoteAnimationTarget departingAnimationTarget); + + @Constructor + BackMotionEvent newBackMotionEventV( + float touchX, + float touchY, + float progress, + float velocityX, + float velocityY, + boolean triggerBack, + int swipeEdge, + RemoteAnimationTarget departingAnimationTarget); + } + + private static class BackMotionEvents { + private BackMotionEvents() {} + + static BackMotionEvent newBackMotionEvent( + @BackEvent.SwipeEdge int edge, float touchX, float touchY, float progress) { + if (RuntimeEnvironment.getApiLevel() >= UPSIDE_DOWN_CAKE) { + try { + return reflector(BackMotionEventReflector.class) + .newBackMotionEventV( + touchX, + touchY, + progress, + 0f, // velocity x + 0f, // velocity y + Boolean.FALSE, // trigger back + edge, // swipe edge + null); + } catch (Throwable t) { + if (NoSuchMethodException.class.isInstance(t) || AssertionError.class.isInstance(t)) { + // fall through, assuming (perhaps falsely?) this exception is thrown by reflector(), + // and not the method reflected in to. + } else { + if (RuntimeException.class.isInstance(t)) { + throw (RuntimeException) t; + } else { + throw new RuntimeException(t); + } + } + } + } + return reflector(BackMotionEventReflector.class) + .newBackMotionEvent( + touchX, touchY, progress, 0f, // velocity x + 0f, // velocity y + edge, // swipe edge + null); + } + } } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWindowOnBackInvokedDispatcher.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWindowOnBackInvokedDispatcher.java new file mode 100644 index 000000000..98b199d1f --- /dev/null +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWindowOnBackInvokedDispatcher.java @@ -0,0 +1,36 @@ +package org.robolectric.shadows; + +import android.os.Build; +import android.window.WindowOnBackInvokedDispatcher; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Implements; +import org.robolectric.annotation.Resetter; +import org.robolectric.util.ReflectionHelpers; + +/** Shadow for {@link WindowOnBackInvokedDispatcher}. */ +@Implements( + value = WindowOnBackInvokedDispatcher.class, + minSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, + isInAndroidSdk = false) +public class ShadowWindowOnBackInvokedDispatcher { + private static final boolean ENABLE_PREDICTIVE_BACK_DEFAULT = + RuntimeEnvironment.getApiLevel() >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE + ? ReflectionHelpers.getStaticField( + WindowOnBackInvokedDispatcher.class, "ENABLE_PREDICTIVE_BACK") + : false; + + static void setEnablePredictiveBack(boolean isEnabled) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { + ReflectionHelpers.setStaticField( + WindowOnBackInvokedDispatcher.class, "ENABLE_PREDICTIVE_BACK", isEnabled); + } + } + + @Resetter + public static void reset() { + ReflectionHelpers.setStaticField( + WindowOnBackInvokedDispatcher.class, + "ENABLE_PREDICTIVE_BACK", + ENABLE_PREDICTIVE_BACK_DEFAULT); + } +} diff --git a/utils/src/main/java/org/robolectric/util/Scheduler.java b/utils/src/main/java/org/robolectric/util/Scheduler.java index c34565679..b203884b0 100644 --- a/utils/src/main/java/org/robolectric/util/Scheduler.java +++ b/utils/src/main/java/org/robolectric/util/Scheduler.java @@ -29,7 +29,11 @@ import java.util.concurrent.TimeUnit; * Scheduler will continue looping through posted events (including future events), advancing * its clock as it goes. * </ul> + * + * @deprecated Scheduler APIs only function when using LooperMode.LEGACY. Switch to + * LooperMode.PAUSED and use {@link ShadowLooper} APIs instead. */ +@Deprecated public class Scheduler { /** |