Merge "Add some useful methods to OperationScheduler to inquire into the history, in case you want to second-guess its scheduling."
diff --git a/common/java/com/android/common/OperationScheduler.java b/common/java/com/android/common/OperationScheduler.java
index c7b12d3..0c7ca83 100644
--- a/common/java/com/android/common/OperationScheduler.java
+++ b/common/java/com/android/common/OperationScheduler.java
@@ -158,12 +158,35 @@
             time = Math.max(time, moratoriumTimeMillis);
         }
         time = Math.max(time, lastSuccessTimeMillis + options.minTriggerMillis);
-        time = Math.max(time, lastErrorTimeMillis + options.backoffFixedMillis +
-                options.backoffIncrementalMillis * errorCount);
+        if (errorCount > 0) {
+            time = Math.max(time, lastErrorTimeMillis + options.backoffFixedMillis +
+                    options.backoffIncrementalMillis * errorCount);
+        }
         return time;
     }
 
     /**
+     * Return the last time the operation completed.  Does not modify any state.
+     *
+     * @return the wall clock time when {@link #onSuccess()} was last called.
+     */
+    public long getLastSuccessTimeMillis() {
+        return mStorage.getLong(PREFIX + "lastSuccessTimeMillis", 0);
+    }
+
+    /**
+     * Return the last time the operation was attempted.  Does not modify any state.
+     *
+     * @return the wall clock time when {@link #onSuccess()} or {@link
+     * #onTransientError()} was last called.
+     */
+    public long getLastAttemptTimeMillis() {
+        return Math.max(
+                mStorage.getLong(PREFIX + "lastSuccessTimeMillis", 0),
+                mStorage.getLong(PREFIX + "lastErrorTimeMillis", 0));
+    }
+
+    /**
      * Fetch a {@link SharedPreferences} property, but force it to be before
      * a certain time, updating the value if necessary.  This is to recover
      * gracefully from clock rollbacks which could otherwise strand our timers.
@@ -273,9 +296,7 @@
      * where there is reason to hope things might start working better.
      */
     public void resetTransientError() {
-        mStorage.edit()
-                .remove(PREFIX + "lastErrorTimeMillis")
-                .remove(PREFIX + "errorCount").commit();
+        mStorage.edit().remove(PREFIX + "errorCount").commit();
     }
 
     /**
diff --git a/common/tests/src/com/android/common/OperationSchedulerTest.java b/common/tests/src/com/android/common/OperationSchedulerTest.java
index 28178b5..f728eea 100644
--- a/common/tests/src/com/android/common/OperationSchedulerTest.java
+++ b/common/tests/src/com/android/common/OperationSchedulerTest.java
@@ -28,6 +28,8 @@
         OperationScheduler scheduler = new OperationScheduler(storage);
         OperationScheduler.Options options = new OperationScheduler.Options();
         assertEquals(Long.MAX_VALUE, scheduler.getNextTimeMillis(options));
+        assertEquals(0, scheduler.getLastSuccessTimeMillis());
+        assertEquals(0, scheduler.getLastAttemptTimeMillis());
 
         long beforeTrigger = System.currentTimeMillis();
         scheduler.setTriggerTimeMillis(beforeTrigger + 1000000);
@@ -49,6 +51,9 @@
         long beforeError = System.currentTimeMillis();
         scheduler.onTransientError();
         long afterError = System.currentTimeMillis();
+        assertEquals(0, scheduler.getLastSuccessTimeMillis());
+        assertTrue(beforeError <= scheduler.getLastAttemptTimeMillis());
+        assertTrue(afterError >= scheduler.getLastAttemptTimeMillis());
         assertEquals(beforeTrigger + 1500000, scheduler.getNextTimeMillis(options));
         options.backoffFixedMillis = 1000000;
         options.backoffIncrementalMillis = 500000;
@@ -59,9 +64,18 @@
         beforeError = System.currentTimeMillis();
         scheduler.onTransientError();
         afterError = System.currentTimeMillis();
+        assertTrue(beforeError <= scheduler.getLastAttemptTimeMillis());
+        assertTrue(afterError >= scheduler.getLastAttemptTimeMillis());
         assertTrue(beforeError + 2000000 <= scheduler.getNextTimeMillis(options));
         assertTrue(afterError + 2000000 >= scheduler.getNextTimeMillis(options));
 
+        // Reset transient error: no backoff interval
+        scheduler.resetTransientError();
+        assertEquals(0, scheduler.getLastSuccessTimeMillis());
+        assertEquals(beforeTrigger + 1500000, scheduler.getNextTimeMillis(options));
+        assertTrue(beforeError <= scheduler.getLastAttemptTimeMillis());
+        assertTrue(afterError >= scheduler.getLastAttemptTimeMillis());
+
         // Permanent error holds true even if transient errors are reset
         // However, we remember that the transient error was reset...
         scheduler.onPermanentError();
@@ -75,6 +89,10 @@
         long beforeSuccess = System.currentTimeMillis();
         scheduler.onSuccess();
         long afterSuccess = System.currentTimeMillis();
+        assertTrue(beforeSuccess <= scheduler.getLastAttemptTimeMillis());
+        assertTrue(afterSuccess >= scheduler.getLastAttemptTimeMillis());
+        assertTrue(beforeSuccess <= scheduler.getLastSuccessTimeMillis());
+        assertTrue(afterSuccess >= scheduler.getLastSuccessTimeMillis());
         assertEquals(Long.MAX_VALUE, scheduler.getNextTimeMillis(options));
 
         // The moratorium is not reset by success!