1 package org.robolectric.shadows;
2 
3 import static android.os.Build.VERSION_CODES.P;
4 
5 import android.os.SystemClock;
6 import java.time.DateTimeException;
7 import java.util.List;
8 import java.util.concurrent.CopyOnWriteArrayList;
9 import javax.annotation.concurrent.GuardedBy;
10 import org.robolectric.annotation.HiddenApi;
11 import org.robolectric.annotation.Implementation;
12 import org.robolectric.annotation.Implements;
13 import org.robolectric.annotation.Resetter;
14 
15 /**
16  * A shadow SystemClock used when {@link LooperMode.Mode#PAUSED} is active.
17  *
18  * <p>In this variant, System times (both elapsed realtime and uptime) are controlled by this class.
19  * The current times are fixed in place. You can manually advance both by calling {@link
20  * SystemClock#setCurrentTimeMillis(long)} or just advance elapsed realtime only by calling {@link
21  * deepSleep(long)}.
22  *
23  * <p>{@link SystemClock#uptimeMillis()} and {@link SystemClock#currentThreadTimeMillis()} are
24  * identical.
25  *
26  * <p>This class should not be referenced directly. Use ShadowSystemClock instead.
27  */
28 @Implements(
29     value = SystemClock.class,
30     isInAndroidSdk = false,
31     shadowPicker = ShadowSystemClock.Picker.class)
32 public class ShadowPausedSystemClock extends ShadowSystemClock {
33   private static final long INITIAL_TIME = 100;
34   private static final int MILLIS_PER_NANO = 1000000;
35 
36   @SuppressWarnings("NonFinalStaticField")
37   @GuardedBy("ShadowPausedSystemClock.class")
38   private static long currentUptimeMillis = INITIAL_TIME;
39 
40   @SuppressWarnings("NonFinalStaticField")
41   @GuardedBy("ShadowPausedSystemClock.class")
42   private static long currentRealtimeMillis = INITIAL_TIME;
43 
44   private static final List<Listener> listeners = new CopyOnWriteArrayList<>();
45   // hopefully temporary list of clock listeners that are NOT cleared between tests
46   // This is needed to accomodate Loopers which are not reset between tests
47   private static final List<Listener> staticListeners = new CopyOnWriteArrayList<>();
48 
49   /** Callback for clock updates */
50   interface Listener {
onClockAdvanced()51     void onClockAdvanced();
52   }
53 
addListener(Listener listener)54   static void addListener(Listener listener) {
55     listeners.add(listener);
56   }
57 
removeListener(Listener listener)58   static void removeListener(Listener listener) {
59     listeners.remove(listener);
60     staticListeners.remove(listener);
61   }
62 
addStaticListener(Listener listener)63   static void addStaticListener(Listener listener) {
64     staticListeners.add(listener);
65   }
66 
67   /**
68    * Advances the current time (both elapsed realtime and uptime) by given millis, without sleeping
69    * the current thread.
70    */
71   @Implementation
sleep(long millis)72   protected static void sleep(long millis) {
73     synchronized (ShadowPausedSystemClock.class) {
74       currentUptimeMillis += millis;
75       currentRealtimeMillis += millis;
76     }
77     informListeners();
78   }
79 
80   /**
81    * Advances the current time (elapsed realtime only) by given millis, without sleeping the current
82    * thread.
83    *
84    * <p>This is to simulate scenarios like suspend-to-RAM, where only elapsed realtime is
85    * incremented when the device is in deep sleep.
86    */
deepSleep(long millis)87   protected static void deepSleep(long millis) {
88     synchronized (ShadowPausedSystemClock.class) {
89       currentRealtimeMillis += millis;
90     }
91     informListeners();
92   }
93 
informListeners()94   private static void informListeners() {
95     for (Listener listener : listeners) {
96       listener.onClockAdvanced();
97     }
98     for (Listener listener : staticListeners) {
99       listener.onClockAdvanced();
100     }
101   }
102 
103   /**
104    * Sets the current wall time (both elapsed realtime and uptime).
105    *
106    * <p>This API sets both of the elapsed realtime and uptime to the specified value.
107    *
108    * <p>Currently does not perform any permission checks.
109    *
110    * @return false if specified time is less than current uptime.
111    */
112   @Implementation
setCurrentTimeMillis(long millis)113   protected static boolean setCurrentTimeMillis(long millis) {
114     synchronized (ShadowPausedSystemClock.class) {
115       if (currentUptimeMillis > millis) {
116         return false;
117       } else if (currentUptimeMillis == millis) {
118         return true;
119       } else {
120         currentUptimeMillis = millis;
121         currentRealtimeMillis = millis;
122       }
123     }
124     informListeners();
125     return true;
126   }
127 
128   @Implementation
uptimeMillis()129   protected static synchronized long uptimeMillis() {
130     return currentUptimeMillis;
131   }
132 
133   @Implementation
elapsedRealtime()134   protected static synchronized long elapsedRealtime() {
135     return currentRealtimeMillis;
136   }
137 
138   @Implementation
elapsedRealtimeNanos()139   protected static long elapsedRealtimeNanos() {
140     return elapsedRealtime() * MILLIS_PER_NANO;
141   }
142 
143   @Implementation
currentThreadTimeMillis()144   protected static long currentThreadTimeMillis() {
145     return uptimeMillis();
146   }
147 
148   @HiddenApi
149   @Implementation
currentThreadTimeMicro()150   protected static long currentThreadTimeMicro() {
151     return uptimeMillis() * 1000;
152   }
153 
154   @HiddenApi
155   @Implementation
currentTimeMicro()156   protected static long currentTimeMicro() {
157     return currentThreadTimeMicro();
158   }
159 
160   @Implementation(minSdk = P)
161   @HiddenApi
currentNetworkTimeMillis()162   protected static synchronized long currentNetworkTimeMillis() {
163     if (networkTimeAvailable) {
164       return currentUptimeMillis;
165     } else {
166       throw new DateTimeException("Network time not available");
167     }
168   }
169 
170   @Resetter
reset()171   public static synchronized void reset() {
172     currentUptimeMillis = INITIAL_TIME;
173     currentRealtimeMillis = INITIAL_TIME;
174     ShadowSystemClock.reset();
175     listeners.clear();
176   }
177 }
178