Initial Process and Binder support, with CTS.

The Ravenwood environment doesn't support IPC, but much of the code
that developers intend to test (such as AIDL stubs) still relies on
baseline same-process behavior to be intact.

This change adds UID and PID support to Process and Binder, but we
carefully communicate that it's only available when the test author
has configured it via a RavenwoodRule.  This ensures that tests
don't accidentally rely on unexpected defaults, as some tests will
want AID_SYSTEM, and others want a normal app UID.

Also bring along SystemClock, PatternMatcher, and ParcelUuid
along with relevant tests.

Finally, now that we have an IgnoreUnderRavenwood annotation, we
can pivot back our default failure to be a RuntimeException instead
of an AssumptionViolatedException.

Bug: 292141694
Test: atest-dev CtsOsTestCasesRavenwood CtsOsTestCases
Change-Id: I5f54c3179b2d305b9ab9144c43fd063c6b756e44
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
index 9956220..0ccc485 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -30,9 +30,11 @@
 import com.android.internal.os.BinderCallHeavyHitterWatcher.BinderCallHeavyHitterListener;
 import com.android.internal.os.BinderInternal;
 import com.android.internal.os.BinderInternal.CallSession;
+import com.android.internal.os.SomeArgs;
 import com.android.internal.util.FastPrintWriter;
 import com.android.internal.util.FunctionalUtils.ThrowingRunnable;
 import com.android.internal.util.FunctionalUtils.ThrowingSupplier;
+import com.android.internal.util.Preconditions;
 
 import dalvik.annotation.optimization.CriticalNative;
 
@@ -46,6 +48,7 @@
 import java.io.PrintWriter;
 import java.lang.reflect.Modifier;
 import java.util.concurrent.atomic.AtomicReferenceArray;
+import java.util.function.Supplier;
 
 /**
  * Base class for a remotable object, the core part of a lightweight
@@ -289,6 +292,33 @@
         sWarnOnBlockingOnCurrentThread.set(sWarnOnBlocking);
     }
 
+    private static ThreadLocal<SomeArgs> sIdentity$ravenwood;
+
+    @android.ravenwood.annotation.RavenwoodKeepWholeClass
+    private static class IdentitySupplier implements Supplier<SomeArgs> {
+        @Override
+        public SomeArgs get() {
+            final SomeArgs args = SomeArgs.obtain();
+            // Match IPCThreadState behavior
+            args.arg1 = Boolean.FALSE;
+            args.argi1 = android.os.Process.myUid();
+            args.argi2 = android.os.Process.myPid();
+            return args;
+        }
+    }
+
+    /** @hide */
+    @android.ravenwood.annotation.RavenwoodKeep
+    public static void init$ravenwood() {
+        sIdentity$ravenwood = ThreadLocal.withInitial(new IdentitySupplier());
+    }
+
+    /** @hide */
+    @android.ravenwood.annotation.RavenwoodKeep
+    public static void reset$ravenwood() {
+        sIdentity$ravenwood = null;
+    }
+
     /**
      * Raw native pointer to JavaBBinderHolder object. Owned by this Java object. Not null.
      */
@@ -312,8 +342,14 @@
      * Warning: oneway transactions do not receive PID.
      */
     @CriticalNative
+    @android.ravenwood.annotation.RavenwoodReplace
     public static final native int getCallingPid();
 
+    /** @hide */
+    public static final int getCallingPid$ravenwood() {
+        return Preconditions.requireNonNullViaRavenwoodRule(sIdentity$ravenwood).get().argi2;
+    }
+
     /**
      * Return the Linux UID assigned to the process that sent you the
      * current transaction that is being processed. This UID can be used with
@@ -322,8 +358,14 @@
      * incoming transaction, then its own UID is returned.
      */
     @CriticalNative
+    @android.ravenwood.annotation.RavenwoodReplace
     public static final native int getCallingUid();
 
+    /** @hide */
+    public static final int getCallingUid$ravenwood() {
+        return Preconditions.requireNonNullViaRavenwoodRule(sIdentity$ravenwood).get().argi1;
+    }
+
     /**
      * Returns {@code true} if the current thread is currently executing an
      * incoming transaction.
@@ -331,6 +373,7 @@
      * @hide
      */
     @CriticalNative
+    @android.ravenwood.annotation.RavenwoodReplace
     public static final native boolean isDirectlyHandlingTransactionNative();
 
     /** @hide */
@@ -344,6 +387,7 @@
     /**
      * @hide
      */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static final boolean isDirectlyHandlingTransaction() {
         return sIsHandlingBinderTransaction || isDirectlyHandlingTransactionNative();
     }
@@ -363,8 +407,15 @@
     * @hide
     */
     @CriticalNative
+    @android.ravenwood.annotation.RavenwoodReplace
     private static native boolean hasExplicitIdentity();
 
+    /** @hide */
+    private static boolean hasExplicitIdentity$ravenwood() {
+        return Preconditions.requireNonNullViaRavenwoodRule(sIdentity$ravenwood).get().arg1
+                == Boolean.TRUE;
+    }
+
     /**
      * Return the Linux UID assigned to the process that sent the transaction
      * currently being processed.
@@ -373,6 +424,7 @@
      * executing an incoming transaction and the calling identity has not been
      * explicitly set with {@link #clearCallingIdentity()}
      */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static final int getCallingUidOrThrow() {
         if (!isDirectlyHandlingTransaction() && !hasExplicitIdentity()) {
             throw new IllegalStateException(
@@ -434,8 +486,26 @@
      * @see #restoreCallingIdentity(long)
      */
     @CriticalNative
+    @android.ravenwood.annotation.RavenwoodReplace
     public static final native long clearCallingIdentity();
 
+    /** @hide */
+    public static final long clearCallingIdentity$ravenwood() {
+        final SomeArgs args = Preconditions.requireNonNullViaRavenwoodRule(
+                sIdentity$ravenwood).get();
+        long res = ((long) args.argi1 << 32) | args.argi2;
+        if (args.arg1 == Boolean.TRUE) {
+            res |= (0x1 << 30);
+        } else {
+            res &= ~(0x1 << 30);
+        }
+        // Match IPCThreadState behavior
+        args.arg1 = Boolean.TRUE;
+        args.argi1 = android.os.Process.myUid();
+        args.argi2 = android.os.Process.myPid();
+        return res;
+    }
+
     /**
      * Restore the identity of the incoming IPC on the current thread
      * back to a previously identity that was returned by {@link
@@ -447,8 +517,18 @@
      * @see #clearCallingIdentity
      */
     @CriticalNative
+    @android.ravenwood.annotation.RavenwoodReplace
     public static final native void restoreCallingIdentity(long token);
 
+    /** @hide */
+    public static final void restoreCallingIdentity$ravenwood(long token) {
+        final SomeArgs args = Preconditions.requireNonNullViaRavenwoodRule(
+                sIdentity$ravenwood).get();
+        args.arg1 = ((token & (0x1 << 30)) != 0) ? Boolean.TRUE : Boolean.FALSE;
+        args.argi1 = (int) (token >> 32);
+        args.argi2 = (int) (token & ~(0x1 << 30));
+    }
+
     /**
      * Convenience method for running the provided action enclosed in
      * {@link #clearCallingIdentity}/{@link #restoreCallingIdentity}.
@@ -644,8 +724,14 @@
      * in order to prevent the process from holding on to objects longer than
      * it needs to.
      */
+    @android.ravenwood.annotation.RavenwoodReplace
     public static final native void flushPendingCommands();
 
+    /** @hide */
+    public static final void flushPendingCommands$ravenwood() {
+        // Ravenwood doesn't support IPC; ignored
+    }
+
     /**
      * Add the calling thread to the IPC thread pool. This function does
      * not return until the current process is exiting.
@@ -703,6 +789,7 @@
      * <p>If you're creating a Binder token (a Binder object without an attached interface),
      * you should use {@link #Binder(String)} instead.
      */
+    @android.ravenwood.annotation.RavenwoodKeep
     public Binder() {
         this(null);
     }
@@ -719,6 +806,7 @@
      * Instead of creating multiple tokens with the same descriptor, consider adding a suffix to
      * help identify them.
      */
+    @android.ravenwood.annotation.RavenwoodKeep
     public Binder(@Nullable String descriptor) {
         mObject = getNativeBBinderHolder();
         if (mObject != 0L) {
@@ -742,6 +830,7 @@
      * will be implemented for you to return the given owner IInterface when
      * the corresponding descriptor is requested.
      */
+    @android.ravenwood.annotation.RavenwoodKeep
     public void attachInterface(@Nullable IInterface owner, @Nullable String descriptor) {
         mOwner = owner;
         mDescriptor = descriptor;
@@ -750,6 +839,7 @@
     /**
      * Default implementation returns an empty interface name.
      */
+    @android.ravenwood.annotation.RavenwoodKeep
     public @Nullable String getInterfaceDescriptor() {
         return mDescriptor;
     }
@@ -758,6 +848,7 @@
      * Default implementation always returns true -- if you got here,
      * the object is alive.
      */
+    @android.ravenwood.annotation.RavenwoodKeep
     public boolean pingBinder() {
         return true;
     }
@@ -768,6 +859,7 @@
      * Note that if you're calling on a local binder, this always returns true
      * because your process is alive if you're calling it.
      */
+    @android.ravenwood.annotation.RavenwoodKeep
     public boolean isBinderAlive() {
         return true;
     }
@@ -777,6 +869,7 @@
      * to return the associated {@link IInterface} if it matches the requested
      * descriptor.
      */
+    @android.ravenwood.annotation.RavenwoodKeep
     public @Nullable IInterface queryLocalInterface(@NonNull String descriptor) {
         if (mDescriptor != null && mDescriptor.equals(descriptor)) {
             return mOwner;
@@ -1250,12 +1343,14 @@
     /**
      * Local implementation is a no-op.
      */
+    @android.ravenwood.annotation.RavenwoodKeep
     public void linkToDeath(@NonNull DeathRecipient recipient, int flags) {
     }
 
     /**
      * Local implementation is a no-op.
      */
+    @android.ravenwood.annotation.RavenwoodKeep
     public boolean unlinkToDeath(@NonNull DeathRecipient recipient, int flags) {
         return true;
     }
@@ -1283,6 +1378,7 @@
         }
     }
 
+    @android.ravenwood.annotation.RavenwoodReplace
     private static native long getNativeBBinderHolder();
 
     private static long getNativeBBinderHolder$ravenwood() {
diff --git a/core/java/android/os/IBinder.java b/core/java/android/os/IBinder.java
index 90e4b17..91c2965 100644
--- a/core/java/android/os/IBinder.java
+++ b/core/java/android/os/IBinder.java
@@ -194,6 +194,7 @@
      * Limit that should be placed on IPC sizes, in bytes, to keep them safely under the transaction
      * buffer limit.
      */
+    @android.ravenwood.annotation.RavenwoodKeep
     static int getSuggestedMaxIpcSizeBytes() {
         return MAX_IPC_SIZE;
     }
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 677143a..daec1721 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -34,6 +34,9 @@
 import android.util.Pair;
 import android.webkit.WebViewZygote;
 
+import com.android.internal.os.SomeArgs;
+import com.android.internal.util.Preconditions;
+
 import dalvik.system.VMRuntime;
 
 import libcore.io.IoUtils;
@@ -833,14 +836,37 @@
         return VMRuntime.getRuntime().is64Bit();
     }
 
+    private static SomeArgs sIdentity$ravenwood;
+
+    /** @hide */
+    @android.ravenwood.annotation.RavenwoodKeep
+    public static void init$ravenwood(int uid, int pid) {
+        final SomeArgs args = SomeArgs.obtain();
+        args.argi1 = uid;
+        args.argi2 = pid;
+        sIdentity$ravenwood = args;
+    }
+
+    /** @hide */
+    @android.ravenwood.annotation.RavenwoodKeep
+    public static void reset$ravenwood() {
+        sIdentity$ravenwood = null;
+    }
+
     /**
      * Returns the identifier of this process, which can be used with
      * {@link #killProcess} and {@link #sendSignal}.
      */
+    @android.ravenwood.annotation.RavenwoodReplace
     public static final int myPid() {
         return Os.getpid();
     }
 
+    /** @hide */
+    public static final int myPid$ravenwood() {
+        return Preconditions.requireNonNullViaRavenwoodRule(sIdentity$ravenwood).argi2;
+    }
+
     /**
      * Returns the identifier of this process' parent.
      * @hide
@@ -864,10 +890,16 @@
      * app-specific sandbox.  It is different from {@link #myUserHandle} in that
      * a uid identifies a specific app sandbox in a specific user.
      */
+    @android.ravenwood.annotation.RavenwoodReplace
     public static final int myUid() {
         return Os.getuid();
     }
 
+    /** @hide */
+    public static final int myUid$ravenwood() {
+        return Preconditions.requireNonNullViaRavenwoodRule(sIdentity$ravenwood).argi1;
+    }
+
     /**
      * Returns this process's user handle.  This is the
      * user the process is running under.  It is distinct from
diff --git a/core/java/android/os/SystemClock.java b/core/java/android/os/SystemClock.java
index 831ca86..49a0bd3 100644
--- a/core/java/android/os/SystemClock.java
+++ b/core/java/android/os/SystemClock.java
@@ -24,6 +24,7 @@
 import android.content.Context;
 import android.location.ILocationManager;
 import android.location.LocationTime;
+import android.text.format.DateUtils;
 import android.util.Slog;
 
 import dalvik.annotation.optimization.CriticalNative;
@@ -125,6 +126,7 @@
      *
      * @param ms to sleep before returning, in milliseconds of uptime.
      */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static void sleep(long ms)
     {
         long start = uptimeMillis();
@@ -186,8 +188,16 @@
      * @return milliseconds of non-sleep uptime since boot.
      */
     @CriticalNative
+    @android.ravenwood.annotation.RavenwoodReplace
     native public static long uptimeMillis();
 
+    /** @hide */
+    public static long uptimeMillis$ravenwood() {
+        // Ravenwood booted in Jan 2023, and has been in deep sleep for one week
+        return System.currentTimeMillis() - (1672556400L * 1_000)
+                - (DateUtils.WEEK_IN_MILLIS * 1_000);
+    }
+
     /**
      * Returns nanoseconds since boot, not counting time spent in deep sleep.
      *
@@ -195,8 +205,16 @@
      * @hide
      */
     @CriticalNative
+    @android.ravenwood.annotation.RavenwoodReplace
     public static native long uptimeNanos();
 
+    /** @hide */
+    public static long uptimeNanos$ravenwood() {
+        // Ravenwood booted in Jan 2023, and has been in deep sleep for one week
+        return System.nanoTime() - (1672556400L * 1_000_000_000)
+                - (DateUtils.WEEK_IN_MILLIS * 1_000_000_000);
+    }
+
     /**
      * Return {@link Clock} that starts at system boot, not counting time spent
      * in deep sleep.
@@ -218,8 +236,15 @@
      * @return elapsed milliseconds since boot.
      */
     @CriticalNative
+    @android.ravenwood.annotation.RavenwoodReplace
     native public static long elapsedRealtime();
 
+    /** @hide */
+    public static long elapsedRealtime$ravenwood() {
+        // Ravenwood booted in Jan 2023, and has been in deep sleep for one week
+        return System.currentTimeMillis() - (1672556400L * 1_000);
+    }
+
     /**
      * Return {@link Clock} that starts at system boot, including time spent in
      * sleep.
@@ -241,8 +266,15 @@
      * @return elapsed nanoseconds since boot.
      */
     @CriticalNative
+    @android.ravenwood.annotation.RavenwoodReplace
     public static native long elapsedRealtimeNanos();
 
+    /** @hide */
+    public static long elapsedRealtimeNanos$ravenwood() {
+        // Ravenwood booted in Jan 2023, and has been in deep sleep for one week
+        return System.nanoTime() - (1672556400L * 1_000_000_000);
+    }
+
     /**
      * Returns milliseconds running in the current thread.
      *
@@ -271,8 +303,15 @@
      */
     @UnsupportedAppUsage
     @CriticalNative
+    @android.ravenwood.annotation.RavenwoodReplace
     public static native long currentTimeMicro();
 
+    /** @hide */
+    public static long currentTimeMicro$ravenwood() {
+        // Ravenwood booted in Jan 2023, and has been in deep sleep for one week
+        return System.nanoTime() / 1000L;
+    }
+
     /**
      * Returns milliseconds since January 1, 1970 00:00:00.0 UTC, synchronized
      * using a remote network source outside the device.
diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp
index ec12d21..fc4ed1d 100644
--- a/ravenwood/Android.bp
+++ b/ravenwood/Android.bp
@@ -28,9 +28,8 @@
     name: "ravenwood-junit",
     srcs: ["junit-src/**/*.java"],
     libs: [
+        "framework-minus-apex.ravenwood",
         "junit",
     ],
-    sdk_version: "core_current",
-    host_supported: true,
     visibility: ["//visibility:public"],
 }
diff --git a/ravenwood/framework-minus-apex-ravenwood-policies.txt b/ravenwood/framework-minus-apex-ravenwood-policies.txt
index 48d5722..692d598 100644
--- a/ravenwood/framework-minus-apex-ravenwood-policies.txt
+++ b/ravenwood/framework-minus-apex-ravenwood-policies.txt
@@ -81,6 +81,8 @@
 class com.android.internal.util.Preconditions stubclass
 class com.android.internal.util.StringPool stubclass
 
+class com.android.internal.os.SomeArgs stubclass
+
 # Parcel
 class android.os.Parcel stubclass
     method writeException (Ljava/lang/Exception;)V @writeException$ravenwood
@@ -92,14 +94,16 @@
 class android.os.BadParcelableException stubclass
 class android.os.BadTypeParcelableException stubclass
 
-# Binder: just enough to construct, no further functionality
-class android.os.Binder stub
-    method <init> ()V stub
-    method <init> (Ljava/lang/String;)V stub
-    method isDirectlyHandlingTransaction ()Z stub
-    method isDirectlyHandlingTransactionNative ()Z @isDirectlyHandlingTransactionNative$ravenwood
-    method getNativeBBinderHolder ()J @getNativeBBinderHolder$ravenwood
+# Binder
+class android.os.DeadObjectException stubclass
+class android.os.DeadSystemException stubclass
+class android.os.RemoteException stubclass
+class android.os.TransactionTooLargeException stubclass
 
 # Containers
 class android.os.BaseBundle stubclass
 class android.os.Bundle stubclass
+
+# Misc
+class android.os.PatternMatcher stubclass
+class android.os.ParcelUuid stubclass
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
index a6b3f66..bffd0cd 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
@@ -16,6 +16,7 @@
 
 package android.platform.test.ravenwood;
 
+import android.os.Process;
 import android.platform.test.annotations.IgnoreUnderRavenwood;
 
 import org.junit.Assume;
@@ -23,6 +24,8 @@
 import org.junit.runner.Description;
 import org.junit.runners.model.Statement;
 
+import java.util.concurrent.atomic.AtomicInteger;
+
 /**
  * THIS RULE IS EXPERIMENTAL. REACH OUT TO g/ravenwood BEFORE USING IT, OR YOU HAVE ANY
  * QUESTIONS ABOUT IT.
@@ -30,20 +33,84 @@
  * @hide
  */
 public class RavenwoodRule implements TestRule {
+    private static AtomicInteger sNextPid = new AtomicInteger(100);
+
+    /**
+     * Unless the test author requests differently, run as "nobody", and give each collection of
+     * tests its own unique PID.
+     */
+    private int mUid = android.os.Process.NOBODY_UID;
+    private int mPid = sNextPid.getAndIncrement();
+
+    public RavenwoodRule() {
+    }
+
+    public static class Builder {
+        private RavenwoodRule mRule = new RavenwoodRule();
+
+        public Builder() {
+        }
+
+        /**
+         * Configure the identity of this process to be the system UID for the duration of the
+         * test. Has no effect under non-Ravenwood environments.
+         */
+        public Builder setProcessSystem() {
+            mRule.mUid = android.os.Process.SYSTEM_UID;
+            return this;
+        }
+
+        /**
+         * Configure the identity of this process to be an app UID for the duration of the
+         * test. Has no effect under non-Ravenwood environments.
+         */
+        public Builder setProcessApp() {
+            mRule.mUid = android.os.Process.FIRST_APPLICATION_UID;
+            return this;
+        }
+
+        public RavenwoodRule build() {
+            return mRule;
+        }
+    }
+
+    /**
+     * Return if the current process is running under a Ravenwood test environment.
+     */
     public boolean isUnderRavenwood() {
         // TODO: give ourselves a better environment signal
         return System.getProperty("java.class.path").contains("ravenwood");
     }
 
+    private void init() {
+        android.os.Process.init$ravenwood(mUid, mPid);
+        android.os.Binder.init$ravenwood();
+    }
+
+    private void reset() {
+        android.os.Process.reset$ravenwood();
+        android.os.Binder.reset$ravenwood();
+    }
+
     @Override
     public Statement apply(Statement base, Description description) {
         return new Statement() {
             @Override
             public void evaluate() throws Throwable {
+                final boolean isUnderRavenwood = isUnderRavenwood();
                 if (description.getAnnotation(IgnoreUnderRavenwood.class) != null) {
-                    Assume.assumeFalse(isUnderRavenwood());
+                    Assume.assumeFalse(isUnderRavenwood);
                 }
-                base.evaluate();
+                if (isUnderRavenwood) {
+                    init();
+                }
+                try {
+                    base.evaluate();
+                } finally {
+                    if (isUnderRavenwood) {
+                        reset();
+                    }
+                }
             }
         };
     }
diff --git a/ravenwood/ravenwood-annotation-allowed-classes.txt b/ravenwood/ravenwood-annotation-allowed-classes.txt
index c6dd6e3..776a19a 100644
--- a/ravenwood/ravenwood-annotation-allowed-classes.txt
+++ b/ravenwood/ravenwood-annotation-allowed-classes.txt
@@ -1,3 +1,9 @@
 # Only classes listed here can use the Ravenwood annotations.
 
 com.android.internal.util.ArrayUtils
+
+android.os.Binder
+android.os.Binder$IdentitySupplier
+android.os.IBinder
+android.os.Process
+android.os.SystemClock
diff --git a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java b/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java
index 25abbac..c770b9c 100644
--- a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java
+++ b/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java
@@ -65,7 +65,7 @@
      */
     public static void onThrowMethodCalled() {
         // TODO: Maybe add call tracking?
-        throw new AssumptionViolatedException("This method is not supported on the host side");
+        throw new RuntimeException("This method is not supported on the host side");
     }
 
     /**
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AnnotationBasedFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AnnotationBasedFilter.kt
index ee67f0f..5a7d78a 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AnnotationBasedFilter.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AnnotationBasedFilter.kt
@@ -387,13 +387,17 @@
                     }
 
                     // This mn has "SubstituteWith". This means,
-                        // 1. Re move the "rename-to" method, so add it to substitutedMethods.
+                    // 1. Re move the "rename-to" method, so add it to substitutedMethods.
                     policiesFromSubstitution[MethodKey(renameTo, mn.desc)] =
                             FilterPolicy.Remove.withReason("substitute-to")
 
+                    // If the policy is "stub", use "stub".
+                    // Otherwise, it must be "keep" or "throw", but there's no point in using
+                    // "throw", so let's use "keep".
+                    val newPolicy = if (policy.needsInStub) policy else FilterPolicy.Keep
                     // 2. We also keep the from-to in the map.
                     policiesFromSubstitution[MethodKey(renameFrom, mn.desc)] =
-                            policy.withReason("substitute-from")
+                            newPolicy.withReason("substitute-from")
                     substituteToMethods[MethodKey(renameFrom, mn.desc)] = renameTo
 
                     log.v("Substitution found: %s%s -> %s", renameFrom, mn.desc, renameTo)