Creates canonical ShadowDropboxManager.

Supports adding entries with data and specifying all entries should throw an exception.

PiperOrigin-RevId: 202501378
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowDropBoxManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowDropBoxManagerTest.java
new file mode 100644
index 0000000..bab467c
--- /dev/null
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowDropBoxManagerTest.java
@@ -0,0 +1,94 @@
+package org.robolectric.shadows;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.robolectric.Shadows.shadowOf;
+
+import android.content.Context;
+import android.os.DropBoxManager;
+import android.os.DropBoxManager.Entry;
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+/** Unit tests for {@see ShadowDropboxManager}. */
+@RunWith(RobolectricTestRunner.class)
+public class ShadowDropBoxManagerTest {
+
+  private static final String TAG = "TAG";
+  private static final String ANOTHER_TAG = "ANOTHER_TAG";
+  private static final byte[] DATA = "HELLO WORLD".getBytes();
+
+  private DropBoxManager manager;
+  private ShadowDropBoxManager shadowDropBoxManager;
+
+  @Before
+  public void setup() {
+    manager =
+        (DropBoxManager) RuntimeEnvironment.application.getSystemService(Context.DROPBOX_SERVICE);
+    shadowDropBoxManager = shadowOf(manager);
+  }
+
+  @Test
+  public void emptyDropbox() {
+    assertThat(manager.getNextEntry(null, 0)).isNull();
+  }
+
+  @Test
+  public void dataExpected() throws Exception {
+    shadowDropBoxManager.addData(TAG, 1, DATA);
+
+    Entry entry = manager.getNextEntry(null, 0);
+    assertThat(entry).isNotNull();
+    assertThat(entry.getTag()).isEqualTo(TAG);
+    assertThat(entry.getTimeMillis()).isEqualTo(1);
+    assertThat(new BufferedReader(new InputStreamReader(entry.getInputStream())).readLine())
+        .isEqualTo(new String(DATA));
+    assertThat(entry.getText(100)).isEqualTo(new String(DATA));
+  }
+
+  /** Checks that we retrieve the first entry <em>after</em> the specified time. */
+  @Test
+  public void dataNotExpected_timestampSameAsEntry() throws Exception {
+    shadowDropBoxManager.addData(TAG, 1, DATA);
+
+    assertThat(manager.getNextEntry(null, 1)).isNull();
+  }
+
+  @Test
+  public void dataNotExpected_timestampAfterEntry() throws Exception {
+    shadowDropBoxManager.addData(TAG, 1, DATA);
+
+    assertThat(manager.getNextEntry(null, 2)).isNull();
+  }
+
+  @Test
+  public void dataNotExpected_wrongTag() throws Exception {
+    shadowDropBoxManager.addData(TAG, 1, DATA);
+
+    assertThat(manager.getNextEntry(ANOTHER_TAG, 0)).isNull();
+  }
+
+  @Test
+  public void dataExpectedWithSort() throws Exception {
+    shadowDropBoxManager.addData(TAG, 3, DATA);
+    shadowDropBoxManager.addData(TAG, 1, new byte[] {(byte) 0x0});
+
+    Entry entry = manager.getNextEntry(null, 2);
+    assertThat(entry).isNotNull();
+    assertThat(entry.getText(100)).isEqualTo(new String(DATA));
+    assertThat(entry.getTimeMillis()).isEqualTo(3);
+  }
+
+  @Test()
+  public void resetClearsData() throws Exception {
+    shadowDropBoxManager.addData(TAG, 1, DATA);
+
+    shadowDropBoxManager.reset();
+
+    assertThat(manager.getNextEntry(null, 0)).isNull();
+  }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDropBoxManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDropBoxManager.java
new file mode 100644
index 0000000..57bfd98
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDropBoxManager.java
@@ -0,0 +1,55 @@
+package org.robolectric.shadows;
+
+import android.os.DropBoxManager;
+import android.os.DropBoxManager.Entry;
+import java.util.SortedMap;
+import java.util.TreeMap;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+
+/** Fake dropbox manager that starts with no entries. */
+@Implements(value = DropBoxManager.class)
+public class ShadowDropBoxManager {
+  private final SortedMap<Long, Entry> entries = new TreeMap<>();
+
+  public ShadowDropBoxManager() {
+    reset();
+  }
+
+  /**
+   * Adds entry to the DropboxManager with the flag indicating data is text.
+   *
+   * <p>The existing {@link DropBoxManager#addData}, {@link DropBoxManager#addFile}, and {@link
+   * DropBoxManager#addText} methods in DropBoxManager are not shadowed. This method is a
+   * convenience for quickly adding multiple historical entries. The entries can be added in any
+   * order since this shadow will sort the entries by the specified timestamp.
+   *
+   * <p>The flag will be set to {@link DropBoxManager#IS_TEXT} so that {@link
+   * DropBoxManager.Entry#getText} can be used.
+   *
+   * @param tag can be any arbitrary string
+   * @param timestamp is an arbitrary timestamp that must be unique from all other entries
+   * @param data must not be null
+   */
+  void addData(String tag, long timestamp, byte[] data) {
+    entries.put(timestamp, new DropBoxManager.Entry(tag, timestamp, data, DropBoxManager.IS_TEXT));
+  }
+
+  /**
+   * Clears all entries.
+   */
+  public void reset() {
+    entries.clear();
+  }
+
+  @Implementation
+  protected DropBoxManager.Entry getNextEntry(String tag, long millis) {
+    for (DropBoxManager.Entry entry : entries.tailMap(millis).values()) {
+      if ((tag != null && !entry.getTag().equals(tag)) || entry.getTimeMillis() <= millis) {
+        continue;
+      }
+      return entry;
+    }
+    return null;
+  }
+}