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