aboutsummaryrefslogtreecommitdiff
path: root/shadows/framework
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 /shadows/framework
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
Diffstat (limited to 'shadows/framework')
-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
11 files changed, 452 insertions, 50 deletions
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);
+ }
+}