blob: 9d583e3bf9759d19d2bbc579f44e0179baf8d5b0 [file] [log] [blame]
package org.robolectric.shadows;
import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
import static android.os.Build.VERSION_CODES.P;
import android.os.SystemClock;
import java.time.DateTimeException;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.annotation.concurrent.GuardedBy;
import org.robolectric.annotation.HiddenApi;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.LooperMode;
import org.robolectric.annotation.Resetter;
/**
* A shadow SystemClock used when {@link LooperMode.Mode#PAUSED} is active.
*
* <p>In this variant, there is just one global system time controlled by this class. The current
* time is fixed in place, and manually advanced by calling {@link
* SystemClock#setCurrentTimeMillis(long)}
*
* <p>{@link SystemClock#uptimeMillis()} and {@link SystemClock#currentThreadTimeMillis()} are
* identical.
*
* <p>This class should not be referenced directly. Use ShadowSystemClock instead.
*/
@Implements(
value = SystemClock.class,
isInAndroidSdk = false,
shadowPicker = ShadowSystemClock.Picker.class)
public class ShadowPausedSystemClock extends ShadowSystemClock {
private static final long INITIAL_TIME = 100;
private static final int MILLIS_PER_NANO = 1000000;
@GuardedBy("ShadowPausedSystemClock.class")
private static long currentTimeMillis = INITIAL_TIME;
private static final List<Listener> listeners = new CopyOnWriteArrayList<>();
/**
* Callback for clock updates
*/
interface Listener {
void onClockAdvanced();
}
static void addListener(Listener listener) {
listeners.add(listener);
}
static void removeListener(Listener listener) {
listeners.remove(listener);
}
/** Advances the current time by given millis, without sleeping the current thread/ */
@Implementation
protected static void sleep(long millis) {
synchronized (ShadowPausedSystemClock.class) {
currentTimeMillis += millis;
}
for (Listener listener : listeners) {
listener.onClockAdvanced();
}
}
/**
* Sets the current wall time.
*
* <p>Currently does not perform any permission checks.
*
* @return false if specified time is less than current time.
*/
@Implementation
protected static boolean setCurrentTimeMillis(long millis) {
synchronized (ShadowPausedSystemClock.class) {
if (currentTimeMillis > millis) {
return false;
} else if (currentTimeMillis == millis) {
return true;
} else {
currentTimeMillis = millis;
}
}
for (Listener listener : listeners) {
listener.onClockAdvanced();
}
return true;
}
@Implementation
protected static synchronized long uptimeMillis() {
return currentTimeMillis;
}
@Implementation
protected static long elapsedRealtime() {
return uptimeMillis();
}
@Implementation(minSdk = JELLY_BEAN_MR1)
protected static long elapsedRealtimeNanos() {
return elapsedRealtime() * MILLIS_PER_NANO;
}
@Implementation
protected static long currentThreadTimeMillis() {
return uptimeMillis();
}
@HiddenApi
@Implementation
protected static long currentThreadTimeMicro() {
return uptimeMillis() * 1000;
}
@HiddenApi
@Implementation
protected static long currentTimeMicro() {
return currentThreadTimeMicro();
}
@Implementation(minSdk = P)
@HiddenApi
protected static synchronized long currentNetworkTimeMillis() {
if (networkTimeAvailable) {
return currentTimeMillis;
} else {
throw new DateTimeException("Network time not available");
}
}
@Resetter
public static synchronized void reset() {
currentTimeMillis = INITIAL_TIME;
ShadowSystemClock.reset();
listeners.clear();
}
}