aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTreehugger Robot <android-test-infra-autosubmit@system.gserviceaccount.com>2024-04-15 04:27:31 +0000
committerGerrit Code Review <noreply-gerritcodereview@google.com>2024-04-15 04:27:31 +0000
commitb3b17db94476c25225589165fbf53ec938f5b6dc (patch)
treef11775470d93f8e96cf44f5466f7a7c10a0edc9c
parent300f6ab8d044ea63c3fbe4b53fc0ca726f8cf573 (diff)
parentacf14fc6bfb8c1c9a7cbb748bf841c4a2613258f (diff)
downloadrobolectric-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
-rw-r--r--integration_tests/dependency-on-stubs/build.gradle31
-rw-r--r--integration_tests/dependency-on-stubs/src/test/java/org/robolectric/LoadWeirdClassesTest.java12
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/InformationElementBuilderTest.java28
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowFileIntegrityManagerTest.java38
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowTelephonyManagerTest.java18
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowTranslationManagerTest.java22
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowWindowManagerGlobalTest.java196
-rw-r--r--robolectric/src/test/resources/AndroidManifest.xml3
-rw-r--r--shadowapi/src/main/java/org/robolectric/util/ReflectionHelpers.java1
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/InformationElementBuilder.java50
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowApplication.java16
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowFileIntegrityManager.java28
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowInformationElement.java40
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowServiceManager.java2
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowTelephonyManager.java29
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowTranslationManager.java6
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowUserManager.java6
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowViewRootImpl.java1
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowWindowManagerGlobal.java288
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowWindowOnBackInvokedDispatcher.java36
-rw-r--r--utils/src/main/java/org/robolectric/util/Scheduler.java4
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 {
/**