Skip to content

Commit

Permalink
Merge branch 'google' into 'master'
Browse files Browse the repository at this point in the history
  • Loading branch information
hoisie committed Dec 6, 2024
2 parents b616ede + 0091b8c commit a3b1162
Show file tree
Hide file tree
Showing 7 changed files with 171 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

import android.content.Context;
import android.os.Binder;
import android.os.IBinder.DeathRecipient;
import android.os.Parcel;
import android.os.UserHandle;
import android.os.UserManager;
Expand Down Expand Up @@ -45,6 +46,52 @@ public void transactCallsOnTransact() throws Exception {
assertThat(reply.readString()).isEqualTo("Hello Robolectric");
}

@Test
public void testLinkToDeath() throws Exception {
Binder binder = new Binder();
DeathRecipient recipient = () -> {};
binder.linkToDeath(recipient, 0);
assertThat(shadowOf(binder).getDeathRecipients()).containsExactly(recipient);
}

@Test
public void testLinkToDeath_unlink() throws Exception {
Binder binder = new Binder();
DeathRecipient recipient = () -> {};
// recipient doesn't exist, returns false.
assertThat(binder.unlinkToDeath(recipient, 0)).isFalse();

binder.linkToDeath(recipient, 0);
// recipient exists, return true.
assertThat(binder.unlinkToDeath(recipient, 0)).isTrue();
assertThat(shadowOf(binder).getDeathRecipients()).isEmpty();
}

@Test
public void testLinkToDeath_twice() throws Exception {
Binder binder = new Binder();
DeathRecipient recipient = () -> {};
binder.linkToDeath(recipient, 0);
binder.linkToDeath(recipient, 0);
assertThat(shadowOf(binder).getDeathRecipients()).containsExactly(recipient, recipient);

binder.unlinkToDeath(recipient, 0);
assertThat(shadowOf(binder).getDeathRecipients()).containsExactly(recipient);
}

@Test
public void testLinkToDeath_weakReference() throws Exception {
Binder binder = new Binder();
binder.linkToDeath(
new DeathRecipient() {
@Override
public void binderDied() {}
},
0);
System.gc();
assertThat(shadowOf(binder).getDeathRecipients()).isEmpty();
}

static class TestBinder extends Binder {
int code;
Parcel data;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import static android.os.Build.VERSION_CODES.P;
import static android.os.Build.VERSION_CODES.Q;
import static android.os.Build.VERSION_CODES.S;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
Expand Down Expand Up @@ -167,6 +168,22 @@ public void advanceTimeBy_shouldAdvanceBothElapsedRealtimeAndUptimeMillis() {
assertThat(SystemClock.currentThreadTimeMillis()).isEqualTo(1100);
}

@Test
@Config(minSdk = S)
public void advanceTimeBy_shouldAdvanceNanoTime() {
long startUptimeNanos = SystemClock.uptimeNanos();
long startUptimeMs = SystemClock.uptimeMillis();
ShadowSystemClock.advanceBy(Duration.ofNanos(100));

assertThat(SystemClock.uptimeNanos()).isEqualTo(startUptimeNanos + 100);
assertThat(SystemClock.uptimeMillis()).isEqualTo(startUptimeMs);

ShadowSystemClock.advanceBy(Duration.ofMillis(1));
assertThat(SystemClock.uptimeMillis()).isEqualTo(startUptimeMs + 1);
assertThat(SystemClock.uptimeNanos())
.isEqualTo(startUptimeNanos + 100 + ShadowPausedSystemClock.MILLIS_PER_NANO);
}

@Test
public void simulateDeepSleep_shouldOnlyAdvanceElapsedRealtime() {
SystemClock.setCurrentTimeMillis(1000);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,15 @@
import static android.os.Build.VERSION_CODES.Q;

import android.os.Binder;
import android.os.IBinder.DeathRecipient;
import android.os.Parcel;
import android.os.RemoteException;
import android.os.UserHandle;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.RealObject;
Expand All @@ -19,6 +25,8 @@ public class ShadowBinder {
private static Integer callingPid;
private static UserHandle callingUserHandle;

private final List<WeakReference<DeathRecipient>> deathRecipients = new ArrayList<>();

@Implementation
protected boolean transact(int code, Parcel data, Parcel reply, int flags)
throws RemoteException {
Expand All @@ -44,6 +52,30 @@ protected boolean transact(int code, Parcel data, Parcel reply, int flags)
return result;
}

@Implementation
protected void linkToDeath(DeathRecipient deathRecipient, int flags) {
// The caller must hold a strong reference, the binder does not.
deathRecipients.add(new WeakReference<>(deathRecipient));
}

@Implementation
protected boolean unlinkToDeath(DeathRecipient deathRecipient, int flags) {
WeakReference<DeathRecipient> itemToRemove = null;
for (WeakReference<DeathRecipient> item : deathRecipients) {
// If the same recipient is registered twice, it must be unregistered twice as well.
if (item.get() == deathRecipient) {
itemToRemove = item;
break;
}
}
if (itemToRemove != null) {
deathRecipients.remove(itemToRemove);
return true;
} else {
return false;
}
}

@Implementation
protected static int getCallingPid() {
if (callingPid != null) {
Expand Down Expand Up @@ -86,6 +118,14 @@ protected static UserHandle getCallingUserHandle() {
return android.os.Process.myUserHandle();
}

public List<DeathRecipient> getDeathRecipients() {
return deathRecipients.stream()
.map(Reference::get)
// References that have been collected will be null.
.filter(Objects::nonNull)
.toList();
}

public static void setCallingPid(int pid) {
ShadowBinder.callingPid = pid;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package org.robolectric.shadows;

import static android.os.Build.VERSION_CODES.P;
import static android.os.Build.VERSION_CODES.S;

import android.os.SystemClock;
import java.time.DateTimeException;
import java.time.Duration;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.annotation.concurrent.GuardedBy;
Expand All @@ -30,20 +32,21 @@
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;
static final int MILLIS_PER_NANO = 1_000_000;
private static final int MILLIS_PER_MICRO = 1_000;
private static final long INITIAL_TIME_NS = 100 * MILLIS_PER_NANO;

@SuppressWarnings("NonFinalStaticField")
@GuardedBy("ShadowPausedSystemClock.class")
private static long currentUptimeMillis = INITIAL_TIME;
private static long currentUptimeNs = INITIAL_TIME_NS;

@SuppressWarnings("NonFinalStaticField")
@GuardedBy("ShadowPausedSystemClock.class")
private static long currentRealtimeMillis = INITIAL_TIME;
private static long currentRealtimeNs = INITIAL_TIME_NS;

private static final List<Listener> listeners = new CopyOnWriteArrayList<>();
// hopefully temporary list of clock listeners that are NOT cleared between tests
// This is needed to accomodate Loopers which are not reset between tests
// This is needed to accommodate Loopers which are not reset between tests
private static final List<Listener> staticListeners = new CopyOnWriteArrayList<>();

/** Callback for clock updates */
Expand Down Expand Up @@ -71,8 +74,8 @@ static void addStaticListener(Listener listener) {
@Implementation
protected static void sleep(long millis) {
synchronized (ShadowPausedSystemClock.class) {
currentUptimeMillis += millis;
currentRealtimeMillis += millis;
currentUptimeNs += (millis * MILLIS_PER_NANO);
currentRealtimeNs += (millis * MILLIS_PER_NANO);
}
informListeners();
}
Expand All @@ -86,7 +89,7 @@ protected static void sleep(long millis) {
*/
protected static void deepSleep(long millis) {
synchronized (ShadowPausedSystemClock.class) {
currentRealtimeMillis += millis;
currentRealtimeNs += (millis * MILLIS_PER_NANO);
}
informListeners();
}
Expand All @@ -111,33 +114,39 @@ private static void informListeners() {
*/
@Implementation
protected static boolean setCurrentTimeMillis(long millis) {
long newTimeNs = millis * MILLIS_PER_NANO;
synchronized (ShadowPausedSystemClock.class) {
if (currentUptimeMillis > millis) {
if (currentUptimeNs > newTimeNs) {
return false;
} else if (currentUptimeMillis == millis) {
} else if (currentUptimeNs == newTimeNs) {
return true;
} else {
currentUptimeMillis = millis;
currentRealtimeMillis = millis;
currentUptimeNs = newTimeNs;
currentRealtimeNs = newTimeNs;
}
}
informListeners();
return true;
}

@Implementation
protected static synchronized long uptimeMillis() {
return currentUptimeMillis;
protected static long uptimeMillis() {
return uptimeNanos() / MILLIS_PER_NANO;
}

@Implementation(minSdk = S)
protected static synchronized long uptimeNanos() {
return currentUptimeNs;
}

@Implementation
protected static synchronized long elapsedRealtime() {
return currentRealtimeMillis;
protected static long elapsedRealtime() {
return elapsedRealtimeNanos() / MILLIS_PER_NANO;
}

@Implementation
protected static long elapsedRealtimeNanos() {
return elapsedRealtime() * MILLIS_PER_NANO;
protected static synchronized long elapsedRealtimeNanos() {
return currentRealtimeNs;
}

@Implementation
Expand All @@ -148,7 +157,7 @@ protected static long currentThreadTimeMillis() {
@HiddenApi
@Implementation
protected static long currentThreadTimeMicro() {
return uptimeMillis() * 1000;
return uptimeNanos() / MILLIS_PER_MICRO;
}

@HiddenApi
Expand All @@ -161,16 +170,24 @@ protected static long currentTimeMicro() {
@HiddenApi
protected static synchronized long currentNetworkTimeMillis() {
if (networkTimeAvailable) {
return currentUptimeMillis;
return uptimeMillis();
} else {
throw new DateTimeException("Network time not available");
}
}

static void internalAdvanceBy(Duration duration) {
synchronized (ShadowPausedSystemClock.class) {
currentUptimeNs += duration.toNanos();
currentRealtimeNs += duration.toNanos();
}
informListeners();
}

@Resetter
public static synchronized void reset() {
currentUptimeMillis = INITIAL_TIME;
currentRealtimeMillis = INITIAL_TIME;
currentUptimeNs = INITIAL_TIME_NS;
currentRealtimeNs = INITIAL_TIME_NS;
ShadowSystemClock.reset();
listeners.clear();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package org.robolectric.shadows;

import android.os.SystemClock;
import java.util.concurrent.TimeUnit;
import org.robolectric.annotation.LooperMode;

public class ShadowSystem {
Expand All @@ -16,7 +15,7 @@ public static long nanoTime() {
if (ShadowLooper.looperMode() == LooperMode.Mode.LEGACY) {
return ShadowLegacySystemClock.nanoTime();
} else {
return TimeUnit.MILLISECONDS.toNanos(SystemClock.uptimeMillis());
return ShadowPausedSystemClock.uptimeNanos();
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import static android.os.Build.VERSION_CODES.Q;
import static java.time.ZoneOffset.UTC;
import static org.robolectric.shadows.ShadowLooper.assertLooperMode;
import static org.robolectric.shadows.ShadowLooper.looperMode;

import android.os.SimpleClock;
import android.os.SystemClock;
Expand Down Expand Up @@ -71,7 +72,7 @@ public static void setNetworkTimeAvailable(boolean available) {
* available.
*/
public static void advanceBy(long time, TimeUnit unit) {
SystemClock.setCurrentTimeMillis(SystemClock.uptimeMillis() + unit.toMillis(time));
advanceBy(Duration.of(time, unit.toChronoUnit()));
}

/**
Expand All @@ -80,7 +81,11 @@ public static void advanceBy(long time, TimeUnit unit) {
* @param duration The interval by which to advance.
*/
public static void advanceBy(Duration duration) {
SystemClock.setCurrentTimeMillis(SystemClock.uptimeMillis() + duration.toMillis());
if (looperMode() == Mode.LEGACY) {
SystemClock.setCurrentTimeMillis(SystemClock.uptimeMillis() + duration.toMillis());
} else {
ShadowPausedSystemClock.internalAdvanceBy(duration);
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
package org.robolectric.shadows;

import static org.robolectric.util.reflector.Reflector.reflector;

import android.text.TextPaint;
import android.text.TextUtils;
import android.text.TextUtils.TruncateAt;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.util.reflector.Direct;
import org.robolectric.util.reflector.ForType;
import org.robolectric.util.reflector.Static;

/** Implement {@link TextUtils#ellipsize} by truncating the text. */
@SuppressWarnings({"UnusedDeclaration"})
Expand All @@ -14,6 +19,9 @@ public class ShadowTextUtils {
@Implementation
protected static CharSequence ellipsize(
CharSequence text, TextPaint p, float avail, TruncateAt where) {
if (useRealEllipsize()) {
return reflector(TextUtilsReflector.class).ellipsize(text, p, avail, where);
}
// This shadow follows the convention of ShadowPaint#measureText where each
// characters width is 1.0.
if (avail <= 0) {
Expand All @@ -24,4 +32,16 @@ protected static CharSequence ellipsize(
return text.subSequence(0, (int) avail);
}
}

private static boolean useRealEllipsize() {
return ShadowView.useRealGraphics()
&& Boolean.parseBoolean(System.getProperty("robolectric.useRealEllipsize", "false"));
}

@ForType(TextUtils.class)
interface TextUtilsReflector {
@Direct
@Static
CharSequence ellipsize(CharSequence text, TextPaint p, float avail, TruncateAt where);
}
}

0 comments on commit a3b1162

Please sign in to comment.