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