1 package org.robolectric.shadows;
2 
3 import static android.os.Build.VERSION_CODES.Q;
4 import static java.time.ZoneOffset.UTC;
5 import static org.robolectric.shadows.ShadowLooper.assertLooperMode;
6 
7 import android.os.SimpleClock;
8 import android.os.SystemClock;
9 import java.time.DateTimeException;
10 import java.time.Duration;
11 import java.util.concurrent.TimeUnit;
12 import org.robolectric.annotation.ClassName;
13 import org.robolectric.annotation.Implementation;
14 import org.robolectric.annotation.Implements;
15 import org.robolectric.annotation.LooperMode;
16 import org.robolectric.annotation.LooperMode.Mode;
17 
18 /**
19  * The shadow API for {@link SystemClock}.
20  *
21  * <p>The behavior of SystemClock in Robolectric will differ based on the current {@link
22  * LooperMode}. See {@link ShadowLegacySystemClock} and {@link ShadowPausedSystemClock} for more
23  * details.
24  */
25 @Implements(value = SystemClock.class, shadowPicker = ShadowSystemClock.Picker.class)
26 public abstract class ShadowSystemClock {
27   protected static boolean networkTimeAvailable = true;
28   private static boolean gnssTimeAvailable = true;
29 
30   /**
31    * Implements {@link System#currentTimeMillis} through ShadowWrangler.
32    *
33    * @return Current time in millis.
34    */
35   @SuppressWarnings("unused")
currentTimeMillis()36   public static long currentTimeMillis() {
37     return ShadowLegacySystemClock.currentTimeMillis();
38   }
39 
40   /**
41    * Implements {@link System#nanoTime}.
42    *
43    * @return Current time with nanos.
44    * @deprecated Don't call this method directly; instead, use {@link System#nanoTime()}.
45    */
46   @SuppressWarnings("unused")
47   @Deprecated
nanoTime()48   public static long nanoTime() {
49     return ShadowSystem.nanoTime();
50   }
51 
52   /**
53    * Sets the value for {@link System#nanoTime()}.
54    *
55    * <p>May only be used for {@link LooperMode.Mode#LEGACY}. For {@link LooperMode.Mode#PAUSED},
56    * {@param nanoTime} is calculated based on {@link SystemClock#uptimeMillis()} and can't be set
57    * explicitly.
58    */
setNanoTime(long nanoTime)59   public static void setNanoTime(long nanoTime) {
60     assertLooperMode(Mode.LEGACY);
61     ShadowLegacySystemClock.setNanoTime(nanoTime);
62   }
63 
64   /** Sets whether network time is available. */
setNetworkTimeAvailable(boolean available)65   public static void setNetworkTimeAvailable(boolean available) {
66     networkTimeAvailable = available;
67   }
68 
69   /**
70    * An alternate to {@link #advanceBy(Duration)} for older Android code bases where Duration is not
71    * available.
72    */
advanceBy(long time, TimeUnit unit)73   public static void advanceBy(long time, TimeUnit unit) {
74     SystemClock.setCurrentTimeMillis(SystemClock.uptimeMillis() + unit.toMillis(time));
75   }
76 
77   /**
78    * A convenience method for advancing the clock via {@link SystemClock#setCurrentTimeMillis(long)}
79    *
80    * @param duration The interval by which to advance.
81    */
advanceBy(Duration duration)82   public static void advanceBy(Duration duration) {
83     SystemClock.setCurrentTimeMillis(SystemClock.uptimeMillis() + duration.toMillis());
84   }
85 
86   /**
87    * In a deep sleep scenario, {@param elapsedRealtime} is advanced for this duration when in deep
88    * sleep whilst {@param uptime} maintains its original value.
89    *
90    * <p>May only be used for {@link LooperMode.Mode#PAUSED}. For {@link LooperMode.Mode#LEGACY},
91    * {@param elapsedRealtime} is equal to {@param uptime}.
92    */
simulateDeepSleep(Duration duration)93   public static void simulateDeepSleep(Duration duration) {
94     assertLooperMode(Mode.PAUSED);
95     ShadowPausedSystemClock.deepSleep(duration.toMillis());
96   }
97 
98   @Implementation(minSdk = Q)
currentGnssTimeClock()99   protected static @ClassName("java.time.Clock") Object currentGnssTimeClock() {
100     if (gnssTimeAvailable) {
101       return new SimpleClock(UTC) {
102         @Override
103         public long millis() {
104           return SystemClock.uptimeMillis();
105         }
106       };
107     } else {
108       throw new DateTimeException("Gnss based time is not available.");
109     }
110   }
111 
112   /** Sets whether gnss location based time is available. */
113   public static void setGnssTimeAvailable(boolean available) {
114     gnssTimeAvailable = available;
115   }
116 
117   public static void reset() {
118     networkTimeAvailable = true;
119     gnssTimeAvailable = true;
120   }
121 
122   public static class Picker extends LooperShadowPicker<ShadowSystemClock> {
123 
124     public Picker() {
125       super(ShadowLegacySystemClock.class, ShadowPausedSystemClock.class);
126     }
127   }
128 }
129