• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package org.robolectric.shadows;
2 
3 import static android.os.Build.VERSION_CODES.P;
4 import static android.os.Build.VERSION_CODES.S;
5 
6 import android.os.SystemClock;
7 import java.time.DateTimeException;
8 import java.time.Duration;
9 import java.util.List;
10 import java.util.concurrent.CopyOnWriteArrayList;
11 import javax.annotation.concurrent.GuardedBy;
12 import org.robolectric.annotation.HiddenApi;
13 import org.robolectric.annotation.Implementation;
14 import org.robolectric.annotation.Implements;
15 import org.robolectric.annotation.Resetter;
16 
17 /**
18  * A shadow SystemClock used when {@link LooperMode.Mode#PAUSED} is active.
19  *
20  * <p>In this variant, System times (both elapsed realtime and uptime) are controlled by this class.
21  * The current times are fixed in place. You can manually advance both by calling {@link
22  * SystemClock#setCurrentTimeMillis(long)} or just advance elapsed realtime only by calling {@link
23  * deepSleep(long)}.
24  *
25  * <p>{@link SystemClock#uptimeMillis()} and {@link SystemClock#currentThreadTimeMillis()} are
26  * identical.
27  *
28  * <p>This class should not be referenced directly. Use ShadowSystemClock instead.
29  */
30 @Implements(
31     value = SystemClock.class,
32     isInAndroidSdk = false,
33     shadowPicker = ShadowSystemClock.Picker.class)
34 public class ShadowPausedSystemClock extends ShadowSystemClock {
35   static final int MILLIS_PER_NANO = 1_000_000;
36   private static final int MILLIS_PER_MICRO = 1_000;
37   private static final long INITIAL_TIME_NS = 100 * MILLIS_PER_NANO;
38 
39   @SuppressWarnings("NonFinalStaticField")
40   @GuardedBy("ShadowPausedSystemClock.class")
41   private static long currentUptimeNs = INITIAL_TIME_NS;
42 
43   @SuppressWarnings("NonFinalStaticField")
44   @GuardedBy("ShadowPausedSystemClock.class")
45   private static long currentRealtimeNs = INITIAL_TIME_NS;
46 
47   private static final List<Listener> listeners = new CopyOnWriteArrayList<>();
48   // hopefully temporary list of clock listeners that are NOT cleared between tests
49   // This is needed to accommodate Loopers which are not reset between tests
50   private static final List<Listener> staticListeners = new CopyOnWriteArrayList<>();
51 
52   /** Callback for clock updates */
53   interface Listener {
onClockAdvanced()54     void onClockAdvanced();
55   }
56 
addListener(Listener listener)57   static void addListener(Listener listener) {
58     listeners.add(listener);
59   }
60 
removeListener(Listener listener)61   static void removeListener(Listener listener) {
62     listeners.remove(listener);
63     staticListeners.remove(listener);
64   }
65 
addStaticListener(Listener listener)66   static void addStaticListener(Listener listener) {
67     staticListeners.add(listener);
68   }
69 
70   /**
71    * Advances the current time (both elapsed realtime and uptime) by given millis, without sleeping
72    * the current thread.
73    */
74   @Implementation
sleep(long millis)75   protected static void sleep(long millis) {
76     synchronized (ShadowPausedSystemClock.class) {
77       currentUptimeNs += (millis * MILLIS_PER_NANO);
78       currentRealtimeNs += (millis * MILLIS_PER_NANO);
79     }
80     informListeners();
81   }
82 
83   /**
84    * Advances the current time (elapsed realtime only) by given millis, without sleeping the current
85    * thread.
86    *
87    * <p>This is to simulate scenarios like suspend-to-RAM, where only elapsed realtime is
88    * incremented when the device is in deep sleep.
89    */
deepSleep(long millis)90   protected static void deepSleep(long millis) {
91     synchronized (ShadowPausedSystemClock.class) {
92       currentRealtimeNs += (millis * MILLIS_PER_NANO);
93     }
94     informListeners();
95   }
96 
informListeners()97   private static void informListeners() {
98     for (Listener listener : listeners) {
99       listener.onClockAdvanced();
100     }
101     for (Listener listener : staticListeners) {
102       listener.onClockAdvanced();
103     }
104   }
105 
106   /**
107    * Sets the current wall time (both elapsed realtime and uptime).
108    *
109    * <p>This API sets both of the elapsed realtime and uptime to the specified value.
110    *
111    * <p>Use of this method is discouraged. It currently has the following inconsistent behavior:
112    *
113    * <ol>
114    *   <li>>It doesn't check permissions. In real android this method is protected by the
115    *       signature/privileged SET_TIME permission, thus it is uncallable by most apps
116    *   <li>It doesn't actually change the value of System.currentTimeMillis for non-instrumented
117    *       code aka nearly all user tests and apps It only allows advancing the current time, not
118    *       moving it backwards
119    *   <li>It incorrectly changes the value of SystemClock.uptime, elapsedRealtime, and for
120    *       instrumented code System.nanoTime. In real android these are all independent clocks
121    * </ol>
122    *
123    * <p>It is recommended to use ShadowSystemClock.advanceBy instead to advance
124    * SystemClock.uptimeMillis and SystemClock.elapsedRealTime
125    *
126    * @return false if specified time is less than current uptime.
127    */
128   @Implementation
setCurrentTimeMillis(long millis)129   protected static boolean setCurrentTimeMillis(long millis) {
130     long newTimeNs = millis * MILLIS_PER_NANO;
131     synchronized (ShadowPausedSystemClock.class) {
132       if (currentUptimeNs > newTimeNs) {
133         return false;
134       } else if (currentUptimeNs == newTimeNs) {
135         return true;
136       } else {
137         currentUptimeNs = newTimeNs;
138         currentRealtimeNs = newTimeNs;
139       }
140     }
141     informListeners();
142     return true;
143   }
144 
145   @Implementation
uptimeMillis()146   protected static long uptimeMillis() {
147     return uptimeNanos() / MILLIS_PER_NANO;
148   }
149 
150   @Implementation(minSdk = S)
uptimeNanos()151   protected static synchronized long uptimeNanos() {
152     return currentUptimeNs;
153   }
154 
155   @Implementation
elapsedRealtime()156   protected static long elapsedRealtime() {
157     return elapsedRealtimeNanos() / MILLIS_PER_NANO;
158   }
159 
160   @Implementation
elapsedRealtimeNanos()161   protected static synchronized long elapsedRealtimeNanos() {
162     return currentRealtimeNs;
163   }
164 
165   @Implementation
currentThreadTimeMillis()166   protected static long currentThreadTimeMillis() {
167     return uptimeMillis();
168   }
169 
170   @HiddenApi
171   @Implementation
currentThreadTimeMicro()172   protected static long currentThreadTimeMicro() {
173     return uptimeNanos() / MILLIS_PER_MICRO;
174   }
175 
176   @HiddenApi
177   @Implementation
currentTimeMicro()178   protected static long currentTimeMicro() {
179     return currentThreadTimeMicro();
180   }
181 
182   @Implementation(minSdk = P)
183   @HiddenApi
currentNetworkTimeMillis()184   protected static synchronized long currentNetworkTimeMillis() {
185     if (networkTimeAvailable) {
186       return uptimeMillis();
187     } else {
188       throw new DateTimeException("Network time not available");
189     }
190   }
191 
internalAdvanceBy(Duration duration)192   static void internalAdvanceBy(Duration duration) {
193     if (duration.toNanos() <= 0) {
194       // ignore
195       return;
196     }
197     synchronized (ShadowPausedSystemClock.class) {
198       currentUptimeNs += duration.toNanos();
199       currentRealtimeNs += duration.toNanos();
200     }
201     informListeners();
202   }
203 
204   @Resetter
reset()205   public static synchronized void reset() {
206     currentUptimeNs = INITIAL_TIME_NS;
207     currentRealtimeNs = INITIAL_TIME_NS;
208     ShadowSystemClock.reset();
209     listeners.clear();
210   }
211 }
212