Rename Shadow*SystemClock classes.

ShadowBaseSystemClock -> ShadowSystemClock
ShadowRealisticSystemClock -> ShadowPausedSystemClock
ShadowSystemClock -> ShadowLegacySystemClock

ShadowSystemClock now should (only) expose the public API.

Shadows explicitly enabled using @Config(shadows=...) will now have their shadowPicker honored.

PiperOrigin-RevId: 242194000
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowDateUtilsTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowDateUtilsTest.java
index 6f1ee65..50d8751 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowDateUtilsTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowDateUtilsTest.java
@@ -8,6 +8,7 @@
 import static com.google.common.truth.TruthJUnit.assume;
 
 import android.app.Application;
+import android.os.SystemClock;
 import android.text.format.DateUtils;
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -77,7 +78,7 @@
   public void isToday_shouldReturnFalseForNotToday() {
     assume().that(ShadowBaseLooper.useRealisticLooper()).isFalse();
     long today = java.util.Calendar.getInstance().getTimeInMillis();
-    ShadowSystemClock.setCurrentTimeMillis(today);
+    SystemClock.setCurrentTimeMillis(today);
 
     assertThat(DateUtils.isToday(today)).isTrue();
     assertThat(DateUtils.isToday(today + (86400 * 1000)  /* 24 hours */)).isFalse();
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowSystemClockTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowLegacySystemClockTest.java
similarity index 88%
rename from robolectric/src/test/java/org/robolectric/shadows/ShadowSystemClockTest.java
rename to robolectric/src/test/java/org/robolectric/shadows/ShadowLegacySystemClockTest.java
index 3d6ec9e..acea84f 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowSystemClockTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowLegacySystemClockTest.java
@@ -3,28 +3,24 @@
 import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
 import static android.os.Build.VERSION_CODES.P;
 import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.TruthJUnit.assume;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.robolectric.annotation.LooperMode.Mode.LEGACY;
 
 import android.os.SystemClock;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import java.time.DateTimeException;
-import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.robolectric.Robolectric;
 import org.robolectric.annotation.Config;
+import org.robolectric.annotation.LooperMode;
 import org.robolectric.internal.bytecode.RobolectricInternals;
 
 @RunWith(AndroidJUnit4.class)
-public class ShadowSystemClockTest {
-
-  @Before
-  public void setUp() {
-    assume().that(ShadowRealisticLooper.useRealisticLooper()).isFalse();
-  }
+@LooperMode(LEGACY)
+public class ShadowLegacySystemClockTest {
 
   @Test
   public void shouldAllowForFakingOfTime() throws Exception {
@@ -43,11 +39,11 @@
   @Test
   public void testSetCurrentTime() {
     Robolectric.getForegroundThreadScheduler().advanceTo(1000);
-    assertThat(ShadowSystemClock.now()).isEqualTo(1000);
+    assertThat(ShadowLegacySystemClock.now()).isEqualTo(1000);
     assertTrue(SystemClock.setCurrentTimeMillis(1034));
-    assertThat(ShadowSystemClock.now()).isEqualTo(1034);
+    assertThat(ShadowLegacySystemClock.now()).isEqualTo(1034);
     assertFalse(SystemClock.setCurrentTimeMillis(1000));
-    assertThat(ShadowSystemClock.now()).isEqualTo(1034);
+    assertThat(ShadowLegacySystemClock.now()).isEqualTo(1034);
   }
 
   @Test
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowRealisticSystemClockTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowPausedSystemClockTest.java
similarity index 89%
rename from robolectric/src/test/java/org/robolectric/shadows/ShadowRealisticSystemClockTest.java
rename to robolectric/src/test/java/org/robolectric/shadows/ShadowPausedSystemClockTest.java
index df57eab..f27ae8c 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowRealisticSystemClockTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowPausedSystemClockTest.java
@@ -3,29 +3,24 @@
 import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
 import static android.os.Build.VERSION_CODES.P;
 import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.TruthJUnit.assume;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.robolectric.annotation.LooperMode.Mode.PAUSED;
 
 import android.os.SystemClock;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import java.time.DateTimeException;
 import java.util.concurrent.TimeUnit;
-import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.robolectric.annotation.Config;
+import org.robolectric.annotation.LooperMode;
 import org.robolectric.internal.bytecode.RobolectricInternals;
 
 @RunWith(AndroidJUnit4.class)
-public class ShadowRealisticSystemClockTest {
-
-  @Before
-  public void assertSimplifiedLooper() {
-    assume().that(ShadowBaseLooper.useRealisticLooper()).isTrue();
-  }
+@LooperMode(PAUSED)
+public class ShadowPausedSystemClockTest {
 
   @Test
   public void sleep() {
@@ -83,7 +78,7 @@
   @Test
   @Config(minSdk = P)
   public void currentNetworkTimeMillis_networkTimeNotAvailable_shouldThrowDateTimeException() {
-    ShadowRealisticSystemClock.setNetworkTimeAvailable(false);
+    ShadowSystemClock.setNetworkTimeAvailable(false);
     try {
       SystemClock.currentNetworkTimeMillis();
       fail("Trying to get currentNetworkTimeMillis without network time should throw");
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowRealisticMessageQueueTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowRealisticMessageQueueTest.java
index 0fd0750..6c14899 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowRealisticMessageQueueTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowRealisticMessageQueueTest.java
@@ -9,6 +9,7 @@
 import android.os.Message;
 import android.os.MessageQueue;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
+import java.time.Duration;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 import org.junit.After;
@@ -75,7 +76,7 @@
     msg.setTarget(new Handler());
     shadowQueue.doEnqueueMessage(msg, TimeUnit.MINUTES.toMillis(10));
     NextThread t = NextThread.startSync(shadowQueue);
-    ShadowRealisticSystemClock.advanceBy(10, TimeUnit.MINUTES);
+    ShadowSystemClock.advanceBy(Duration.ofMinutes(10));
     t.join();
   }
 
diff --git a/sandbox/src/main/java/org/robolectric/internal/bytecode/ShadowMap.java b/sandbox/src/main/java/org/robolectric/internal/bytecode/ShadowMap.java
index 840a800..f62b74a 100644
--- a/sandbox/src/main/java/org/robolectric/internal/bytecode/ShadowMap.java
+++ b/sandbox/src/main/java/org/robolectric/internal/bytecode/ShadowMap.java
@@ -62,6 +62,8 @@
     ShadowInfo shadowInfo = overriddenShadows.get(instrumentedClassName);
     if (shadowInfo == null) {
       shadowInfo = checkShadowPickers(instrumentedClassName, clazz);
+    } else if (shadowInfo.hasShadowPicker()) {
+      shadowInfo = pickShadow(instrumentedClassName, clazz, shadowInfo);
     }
 
     if (shadowInfo == null && clazz.getClassLoader() != null) {
@@ -94,10 +96,15 @@
       return null;
     }
 
-    ClassLoader classLoader = clazz.getClassLoader();
+    return pickShadow(instrumentedClassName, clazz, shadowPickerClassName);
+  }
+
+  private ShadowInfo pickShadow(String instrumentedClassName, Class<?> clazz,
+      String shadowPickerClassName) {
+    ClassLoader sandboxClassLoader = clazz.getClassLoader();
     try {
       Class<? extends ShadowPicker<?>> shadowPickerClass =
-          (Class<? extends ShadowPicker<?>>) classLoader.loadClass(shadowPickerClassName);
+          (Class<? extends ShadowPicker<?>>) sandboxClassLoader.loadClass(shadowPickerClassName);
       ShadowPicker<?> shadowPicker = shadowPickerClass.getDeclaredConstructor().newInstance();
       Class<?> selectedShadowClass = shadowPicker.pickShadowClass();
       if (selectedShadowClass == null) {
@@ -119,6 +126,11 @@
     }
   }
 
+  private ShadowInfo pickShadow(
+      String instrumentedClassName, Class<?> clazz, ShadowInfo shadowInfo) {
+    return pickShadow(instrumentedClassName, clazz, shadowInfo.getShadowPickerClass().getName());
+  }
+
   public static ShadowInfo obtainShadowInfo(Class<?> clazz) {
     return obtainShadowInfo(clazz, false);
   }
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBaseSystemClock.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBaseSystemClock.java
deleted file mode 100644
index 481d166..0000000
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBaseSystemClock.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package org.robolectric.shadows;
-
-abstract class ShadowBaseSystemClock {
-
-  public static class Picker extends LooperShadowPicker<ShadowBaseSystemClock> {
-
-    public Picker() {
-      super(ShadowSystemClock.class, ShadowRealisticSystemClock.class);
-    }
-  }
-}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDisplayEventReceiver.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDisplayEventReceiver.java
index e4d8b3c..6c0d8b1 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDisplayEventReceiver.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDisplayEventReceiver.java
@@ -7,13 +7,12 @@
 import static android.os.Build.VERSION_CODES.M;
 import static android.os.Build.VERSION_CODES.N_MR1;
 import static android.os.Build.VERSION_CODES.O;
-import static android.os.Build.VERSION_CODES.P;
 import static android.os.Build.VERSION_CODES.Q;
 
 import android.os.MessageQueue;
 import android.view.DisplayEventReceiver;
 import java.lang.ref.WeakReference;
-import java.util.concurrent.TimeUnit;
+import java.time.Duration;
 import org.robolectric.RuntimeEnvironment;
 import org.robolectric.annotation.Implementation;
 import org.robolectric.annotation.Implements;
@@ -34,7 +33,7 @@
 
   protected @RealObject DisplayEventReceiver receiver;
 
-  private static final long VSYNC_DELAY_MS = 1;
+  private static final Duration VSYNC_DELAY = Duration.ofMillis(1);
 
   @Implementation(minSdk = O)
   protected static long nativeInit(
@@ -114,7 +113,7 @@
     public void scheduleVsync() {
       // simulate an immediate callback
       DisplayEventReceiver receiver = receiverRef.get();
-      ShadowRealisticSystemClock.advanceBy(VSYNC_DELAY_MS, TimeUnit.MILLISECONDS);
+      ShadowSystemClock.advanceBy(VSYNC_DELAY);
       if (receiver != null) {
         ShadowDisplayEventReceiver shadowReceiver = Shadow.extract(receiver);
         shadowReceiver.onVsync();
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacySystemClock.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacySystemClock.java
new file mode 100644
index 0000000..5c3c76e
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacySystemClock.java
@@ -0,0 +1,131 @@
+package org.robolectric.shadows;
+
+import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
+import static android.os.Build.VERSION_CODES.P;
+
+import android.os.SystemClock;
+import java.time.DateTimeException;
+import org.robolectric.annotation.HiddenApi;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.Resetter;
+
+/**
+ * A shadow SystemClock for {@link LooperMode.Mode.LEGACY}
+ *
+ * <p>In LEGACY LooperMode, Robolectric's concept of current time is base on the current time of the
+ * UI Scheduler for consistency with previous implementations. This is not ideal, since both
+ * schedulers (background and foreground), can see different values for the current time.
+ */
+@Implements(
+    value = SystemClock.class,
+    shadowPicker = ShadowSystemClock.Picker.class,
+    // turn off shadowOf generation
+    isInAndroidSdk = false)
+public class ShadowLegacySystemClock extends ShadowSystemClock {
+  private static long bootedAt = 0;
+  private static long nanoTime = 0;
+  private static final int MILLIS_PER_NANO = 1000000;
+
+  static long now() {
+    if (ShadowApplication.getInstance() == null) {
+      return 0;
+    }
+    return ShadowApplication.getInstance().getForegroundThreadScheduler().getCurrentTime();
+  }
+
+  @Implementation
+  protected static void sleep(long millis) {
+    if (ShadowApplication.getInstance() == null) {
+      return;
+    }
+
+    nanoTime = millis * MILLIS_PER_NANO;
+    ShadowApplication.getInstance().getForegroundThreadScheduler().advanceBy(millis);
+  }
+
+  @Implementation
+  protected static boolean setCurrentTimeMillis(long millis) {
+    if (ShadowApplication.getInstance() == null) {
+      return false;
+    }
+
+    if (now() > millis) {
+      return false;
+    }
+    nanoTime = millis * MILLIS_PER_NANO;
+    ShadowApplication.getInstance().getForegroundThreadScheduler().advanceTo(millis);
+    return true;
+  }
+
+  @Implementation
+  protected static long uptimeMillis() {
+    return now() - bootedAt;
+  }
+
+  @Implementation
+  protected static long elapsedRealtime() {
+    return uptimeMillis();
+  }
+
+  @Implementation(minSdk = JELLY_BEAN_MR1)
+  protected static long elapsedRealtimeNanos() {
+    return elapsedRealtime() * MILLIS_PER_NANO;
+  }
+
+  @Implementation
+  protected static long currentThreadTimeMillis() {
+    return uptimeMillis();
+  }
+
+  @HiddenApi
+  @Implementation
+  public static long currentThreadTimeMicro() {
+    return uptimeMillis() * 1000;
+  }
+
+  @HiddenApi
+  @Implementation
+  public static long currentTimeMicro() {
+    return now() * 1000;
+  }
+
+  /**
+   * Implements {@link System#currentTimeMillis} through ShadowWrangler.
+   *
+   * @return Current time in millis.
+   */
+  @SuppressWarnings("unused")
+  public static long currentTimeMillis() {
+    return nanoTime / MILLIS_PER_NANO;
+  }
+
+  /**
+   * Implements {@link System#nanoTime} through ShadowWrangler.
+   *
+   * @return Current time with nanos.
+   */
+  @SuppressWarnings("unused")
+  public static long nanoTime() {
+    return nanoTime;
+  }
+
+  public static void setNanoTime(long nanoTime) {
+    ShadowLegacySystemClock.nanoTime = nanoTime;
+  }
+
+  @Implementation(minSdk = P)
+  @HiddenApi
+  protected static long currentNetworkTimeMillis() {
+    if (networkTimeAvailable) {
+      return currentTimeMillis();
+    } else {
+      throw new DateTimeException("Network time not available");
+    }
+  }
+
+  @Resetter
+  public static void reset() {
+    ShadowSystemClock.reset();
+  }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLooper.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLooper.java
index 556e58b..d1e8f3b 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLooper.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLooper.java
@@ -16,8 +16,10 @@
 import org.robolectric.RuntimeEnvironment;
 import org.robolectric.annotation.Implementation;
 import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.LooperMode;
 import org.robolectric.annotation.RealObject;
 import org.robolectric.annotation.Resetter;
+import org.robolectric.config.ConfigurationRegistry;
 import org.robolectric.shadow.api.Shadow;
 import org.robolectric.util.Scheduler;
 
@@ -435,4 +437,11 @@
   private static ShadowMessageQueue shadowOf(MessageQueue mq) {
     return Shadow.extract(mq);
   }
+
+  static void assertLooperMode(LooperMode.Mode expectedMode) {
+    LooperMode.Mode looperMode = ConfigurationRegistry.get(LooperMode.Mode.class);
+    if (looperMode != expectedMode) {
+      throw new IllegalStateException("this action is not supported in " + looperMode + " mode.");
+    }
+  }
 }
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowRealisticSystemClock.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPausedSystemClock.java
similarity index 75%
rename from shadows/framework/src/main/java/org/robolectric/shadows/ShadowRealisticSystemClock.java
rename to shadows/framework/src/main/java/org/robolectric/shadows/ShadowPausedSystemClock.java
index 4947261..4c086e6 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowRealisticSystemClock.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPausedSystemClock.java
@@ -4,19 +4,16 @@
 import static android.os.Build.VERSION_CODES.P;
 
 import android.os.SystemClock;
-import androidx.test.annotation.Beta;
 import java.time.DateTimeException;
 import java.util.List;
 import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.concurrent.TimeUnit;
 import org.robolectric.annotation.HiddenApi;
 import org.robolectric.annotation.Implementation;
 import org.robolectric.annotation.Implements;
 import org.robolectric.annotation.Resetter;
 
 /**
- * A new version of a shadow SystemClock used when {@link ShadowBaseLooper#useRealisticLooper()} is
- * active.
+ * A shadow SystemClock used when {@link LooperMode.Mode.PAUSED} is active.
  *
  * <p>In this variant, there is just one global system time controlled by this class. The current
  * time is fixed in place, and manually advanced by calling {@link
@@ -25,18 +22,16 @@
  * <p>{@link SystemClock#uptimeMillis()} and {@link SystemClock#currentThreadTimeMillis()} are
  * identical.
  *
- *  This is beta API, and will very likely be renamed in a future Robolectric release.
+ * <p>This class should not be referenced directly. Use ShadowSystemClock instead.
  */
 @Implements(
     value = SystemClock.class,
     isInAndroidSdk = false,
-    shadowPicker = ShadowBaseSystemClock.Picker.class)
-@Beta
-public class ShadowRealisticSystemClock extends ShadowBaseSystemClock {
+    shadowPicker = ShadowSystemClock.Picker.class)
+public class ShadowPausedSystemClock extends ShadowSystemClock {
   private static final long INITIAL_TIME = 100;
   private static final int MILLIS_PER_NANO = 1000000;;
   private static long currentTimeMillis = INITIAL_TIME;
-  private static boolean networkTimeAvailable = true;
   private static List<Listener> listeners = new CopyOnWriteArrayList<>();
 
   /**
@@ -102,13 +97,13 @@
 
   @HiddenApi
   @Implementation
-  public static long currentThreadTimeMicro() {
+  protected static long currentThreadTimeMicro() {
     return uptimeMillis() * 1000;
   }
 
   @HiddenApi
   @Implementation
-  public static long currentTimeMicro() {
+  protected static long currentTimeMicro() {
     return currentThreadTimeMicro();
   }
 
@@ -122,24 +117,9 @@
     }
   }
 
-  /** Sets whether network time is available. */
-  public static void setNetworkTimeAvailable(boolean available) {
-    networkTimeAvailable = available;
-  }
-
-  /**
-   * Convenience method for calling {@link setCurrentTimeMillis()} to a
-   *
-   */
-  public static void advanceBy(long timeValue, TimeUnit timeUnit) {
-    setCurrentTimeMillis(currentTimeMillis + timeUnit.toMillis(timeValue));
-  }
-
   @Resetter
   public static void reset() {
     currentTimeMillis = INITIAL_TIME;
-    networkTimeAvailable = true;
+    ShadowSystemClock.reset();
   }
-
-
 }
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowRealisticLooper.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowRealisticLooper.java
index ac5aed7..578a42a 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowRealisticLooper.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowRealisticLooper.java
@@ -78,7 +78,7 @@
 
   @Override
   public void idleFor(long time, TimeUnit timeUnit) {
-    ShadowRealisticSystemClock.advanceBy(time, timeUnit);
+    ShadowSystemClock.advanceBy(Duration.ofMillis(timeUnit.toMillis(time)));
     idle();
   }
 
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowRealisticMessageQueue.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowRealisticMessageQueue.java
index b38e407..fe495b3 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowRealisticMessageQueue.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowRealisticMessageQueue.java
@@ -49,7 +49,7 @@
   private static NativeObjRegistry<ShadowRealisticMessageQueue> nativeQueueRegistry =
       new NativeObjRegistry<ShadowRealisticMessageQueue>(ShadowRealisticMessageQueue.class);
   private boolean isPolling = false;
-  private ShadowRealisticSystemClock.Listener clockListener;
+  private ShadowPausedSystemClock.Listener clockListener;
 
   // shadow constructor instead of nativeInit because nativeInit signature has changed across SDK
   // versions
@@ -60,7 +60,7 @@
     reflector(ReflectorMessageQueue.class, realQueue).setPtr(ptr);
     clockListener =
         newCurrentTimeMillis -> nativeWake(ptr);
-    ShadowRealisticSystemClock.addListener(clockListener);
+    ShadowPausedSystemClock.addListener(clockListener);
   }
 
   @Implementation(maxSdk = JELLY_BEAN_MR1)
@@ -76,7 +76,7 @@
   @Implementation(minSdk = KITKAT_WATCH)
   protected static void nativeDestroy(long ptr) {
     ShadowRealisticMessageQueue q = nativeQueueRegistry.unregister(ptr);
-    ShadowRealisticSystemClock.removeListener(q.clockListener);
+    ShadowPausedSystemClock.removeListener(q.clockListener);
   }
 
   @Implementation(maxSdk = JELLY_BEAN_MR1)
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSystem.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSystem.java
index 50be796..2ff3d4f 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSystem.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSystem.java
@@ -15,7 +15,7 @@
     if (ShadowBaseLooper.useRealisticLooper()) {
       return TimeUnit.MILLISECONDS.toNanos(SystemClock.uptimeMillis());
     } else {
-      return ShadowSystemClock.nanoTime();
+      return ShadowLegacySystemClock.nanoTime();
     }
   }
 
@@ -29,7 +29,7 @@
     if (ShadowBaseLooper.useRealisticLooper()) {
       return SystemClock.uptimeMillis();
     } else {
-      return ShadowSystemClock.currentTimeMillis();
+      return ShadowLegacySystemClock.currentTimeMillis();
     }
   }
 }
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSystemClock.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSystemClock.java
index 35534bb..a4153de 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSystemClock.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSystemClock.java
@@ -1,89 +1,23 @@
 package org.robolectric.shadows;
 
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
-import static android.os.Build.VERSION_CODES.P;
+import static org.robolectric.shadows.ShadowLooper.assertLooperMode;
 
 import android.os.SystemClock;
-import java.time.DateTimeException;
-import org.robolectric.annotation.HiddenApi;
-import org.robolectric.annotation.Implementation;
+import java.time.Duration;
 import org.robolectric.annotation.Implements;
-import org.robolectric.annotation.Resetter;
+import org.robolectric.annotation.LooperMode;
+import org.robolectric.annotation.LooperMode.Mode;
 
 /**
- * Robolectric's concept of current time is base on the current time of the UI Scheduler for
- * consistency with previous implementations. This is not ideal, since both schedulers (background
- * and foreground), can see different values for the current time.
+ * The shadow API for {@link SystemClock}.
+ *
+ * The behavior of SystemClock in Robolectric will differ based on the current {@link
+ * LooperMode}. See {@link ShadowLegacySystemClock} and {@link ShadowPausedSystemClock} for more
+ * details.
  */
-@Implements(value = SystemClock.class, shadowPicker = ShadowBaseSystemClock.Picker.class)
-public class ShadowSystemClock extends ShadowBaseSystemClock {
-  private static long bootedAt = 0;
-  private static long nanoTime = 0;
-  private static final int MILLIS_PER_NANO = 1000000;
-  private static boolean networkTimeAvailable = true;
-
-  static long now() {
-    if (ShadowApplication.getInstance() == null) {
-      return 0;
-    }
-    return ShadowApplication.getInstance().getForegroundThreadScheduler().getCurrentTime();
-  }
-
-  @Implementation
-  protected static void sleep(long millis) {
-    if (ShadowApplication.getInstance() == null) {
-      return;
-    }
-
-    nanoTime = millis * MILLIS_PER_NANO;
-    ShadowApplication.getInstance().getForegroundThreadScheduler().advanceBy(millis);
-  }
-
-  @Implementation
-  protected static boolean setCurrentTimeMillis(long millis) {
-    if (ShadowApplication.getInstance() == null) {
-      return false;
-    }
-
-    if (now() > millis) {
-      return false;
-    }
-    nanoTime = millis * MILLIS_PER_NANO;
-    ShadowApplication.getInstance().getForegroundThreadScheduler().advanceTo(millis);
-    return true;
-  }
-
-  @Implementation
-  protected static long uptimeMillis() {
-    return now() - bootedAt;
-  }
-
-  @Implementation
-  protected static long elapsedRealtime() {
-    return uptimeMillis();
-  }
-
-  @Implementation(minSdk = JELLY_BEAN_MR1)
-  protected static long elapsedRealtimeNanos() {
-    return elapsedRealtime() * MILLIS_PER_NANO;
-  }
-
-  @Implementation
-  protected static long currentThreadTimeMillis() {
-    return uptimeMillis();
-  }
-
-  @HiddenApi
-  @Implementation
-  public static long currentThreadTimeMicro() {
-    return uptimeMillis() * 1000;
-  }
-
-  @HiddenApi
-  @Implementation
-  public static long currentTimeMicro() {
-    return now() * 1000;
-  }
+@Implements(value = SystemClock.class, shadowPicker = ShadowSystemClock.Picker.class)
+public abstract class ShadowSystemClock {
+  protected static boolean networkTimeAvailable = true;
 
   /**
    * Implements {@link System#currentTimeMillis} through ShadowWrangler.
@@ -92,31 +26,31 @@
    */
   @SuppressWarnings("unused")
   public static long currentTimeMillis() {
-    return nanoTime / MILLIS_PER_NANO;
+    return ShadowLegacySystemClock.currentTimeMillis();
   }
 
   /**
-   * Implements {@link System#nanoTime} through ShadowWrangler.
+   * Implements {@link System#nanoTime}.
    *
    * @return Current time with nanos.
+   * @deprecated Don't call this method directly; instead, use {@link System#nanoTime()}.
    */
   @SuppressWarnings("unused")
+  @Deprecated
   public static long nanoTime() {
-    return nanoTime;
+    return ShadowSystem.nanoTime();
   }
 
+  /**
+   * Sets the value for {@link System#nanoTime()}.
+   *
+   * May only be used for {@link LooperMode.Mode.LEGACY}. For {@link LooperMode.Mode.PAUSED},
+   * `nanoTime` is calculated based on {@link SystemClock#uptimeMillis()} and can't be set
+   * explicitly.
+   */
   public static void setNanoTime(long nanoTime) {
-    ShadowSystemClock.nanoTime = nanoTime;
-  }
-
-  @Implementation(minSdk = P)
-  @HiddenApi
-  protected static long currentNetworkTimeMillis() {
-    if (networkTimeAvailable) {
-      return currentTimeMillis();
-    } else {
-      throw new DateTimeException("Network time not available");
-    }
+    assertLooperMode(Mode.LEGACY);
+    ShadowLegacySystemClock.setNanoTime(nanoTime);
   }
 
   /** Sets whether network time is available. */
@@ -124,8 +58,23 @@
     networkTimeAvailable = available;
   }
 
-  @Resetter
+  /**
+   * A convenience method for advancing the clock via {@link SystemClock#setCurrentTimeMillis(long)}
+   *
+   * @param duration The interval by which to advance.
+   */
+  public static void advanceBy(Duration duration) {
+    SystemClock.setCurrentTimeMillis(SystemClock.uptimeMillis() + duration.toMillis());
+  }
+
   public static void reset() {
     networkTimeAvailable = true;
   }
+
+  public static class Picker extends LooperShadowPicker<ShadowSystemClock> {
+
+    public Picker() {
+      super(ShadowLegacySystemClock.class, ShadowPausedSystemClock.class);
+    }
+  }
 }