Add 1-day sync stats in syncmanager dumpsys.

The stats currently shown in sync manager dumpsys is the total *since wipe* so
they're usually pretty much useless.

So let's add "today" and "yesterday" stats too.

- Also add "# failures" and "# cancels".
- Also split up SERVER to OTHER and FEED (i.e. subscribedfeeds).

Bug: 76035392
Test: Boot, run some sync, then do "cmd content reset-today-stats"
- Also tested with actually setting the clock to 23:59 and wait
- Check the result with "dumpsys content".

Sample output:
=======================================================================
Authority                                           Syncable  Enabled  Stats  Loc  Poll  Per  Feed  User  Othr  Tot  Fail  Can  Time  Last Sync            Backoff
------------------------------------------------------------------------------------------------------------------------------------------------------------------
com.android.calendar                                1         true     Total  0    0     0    1     0     7     8    0     2    8s    FEED SUCCESS
                                                                       Today                  1           5     6               7s    2018-04-12 15:21:24
                                                                       Yestr                                                    0s
com.android.chrome                                  0         false    Total  0    0     0    0     0     0     0    0     0    0s
                                                                       Today                                                    0s

Change-Id: Id0ea42435a9f759e47d4b9490292759270f8e9a5
diff --git a/core/java/android/content/SyncStatusInfo.java b/core/java/android/content/SyncStatusInfo.java
index abf9cc9..ded11cfd 100644
--- a/core/java/android/content/SyncStatusInfo.java
+++ b/core/java/android/content/SyncStatusInfo.java
@@ -21,23 +21,97 @@
 import android.util.Log;
 
 import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
 
 /** @hide */
 public class SyncStatusInfo implements Parcelable {
     private static final String TAG = "Sync";
 
-    static final int VERSION = 4;
+    static final int VERSION = 5;
 
     private static final int MAX_EVENT_COUNT = 10;
 
     public final int authorityId;
-    public long totalElapsedTime;
-    public int numSyncs;
-    public int numSourcePoll;
-    public int numSourceServer;
-    public int numSourceLocal;
-    public int numSourceUser;
-    public int numSourcePeriodic;
+
+    /**
+     * # of syncs for each sync source, etc.
+     */
+    public static class Stats {
+        public long totalElapsedTime;
+        public int numSyncs;
+        public int numSourcePoll;
+        public int numSourceOther;
+        public int numSourceLocal;
+        public int numSourceUser;
+        public int numSourcePeriodic;
+        public int numSourceFeed;
+        public int numFailures;
+        public int numCancels;
+
+        /** Copy all the stats to another instance. */
+        public void copyTo(Stats to) {
+            to.totalElapsedTime = totalElapsedTime;
+            to.numSyncs = numSyncs;
+            to.numSourcePoll = numSourcePoll;
+            to.numSourceOther = numSourceOther;
+            to.numSourceLocal = numSourceLocal;
+            to.numSourceUser = numSourceUser;
+            to.numSourcePeriodic = numSourcePeriodic;
+            to.numSourceFeed = numSourceFeed;
+            to.numFailures = numFailures;
+            to.numCancels = numCancels;
+        }
+
+        /** Clear all the stats. */
+        public void clear() {
+            totalElapsedTime = 0;
+            numSyncs = 0;
+            numSourcePoll = 0;
+            numSourceOther = 0;
+            numSourceLocal = 0;
+            numSourceUser = 0;
+            numSourcePeriodic = 0;
+            numSourceFeed = 0;
+            numFailures = 0;
+            numCancels = 0;
+        }
+
+        /** Write all the stats to a parcel. */
+        public void writeToParcel(Parcel parcel) {
+            parcel.writeLong(totalElapsedTime);
+            parcel.writeInt(numSyncs);
+            parcel.writeInt(numSourcePoll);
+            parcel.writeInt(numSourceOther);
+            parcel.writeInt(numSourceLocal);
+            parcel.writeInt(numSourceUser);
+            parcel.writeInt(numSourcePeriodic);
+            parcel.writeInt(numSourceFeed);
+            parcel.writeInt(numFailures);
+            parcel.writeInt(numCancels);
+        }
+
+        /** Read all the stats from a parcel. */
+        public void readFromParcel(Parcel parcel) {
+            totalElapsedTime = parcel.readLong();
+            numSyncs = parcel.readInt();
+            numSourcePoll = parcel.readInt();
+            numSourceOther = parcel.readInt();
+            numSourceLocal = parcel.readInt();
+            numSourceUser = parcel.readInt();
+            numSourcePeriodic = parcel.readInt();
+            numSourceFeed = parcel.readInt();
+            numFailures = parcel.readInt();
+            numCancels = parcel.readInt();
+        }
+    }
+
+    public long lastTodayResetTime;
+
+    public final Stats totalStats = new Stats();
+    public final Stats todayStats = new Stats();
+    public final Stats yesterdayStats = new Stats();
+
     public long lastSuccessTime;
     public int lastSuccessSource;
     public long lastFailureTime;
@@ -75,12 +149,15 @@
     public void writeToParcel(Parcel parcel, int flags) {
         parcel.writeInt(VERSION);
         parcel.writeInt(authorityId);
-        parcel.writeLong(totalElapsedTime);
-        parcel.writeInt(numSyncs);
-        parcel.writeInt(numSourcePoll);
-        parcel.writeInt(numSourceServer);
-        parcel.writeInt(numSourceLocal);
-        parcel.writeInt(numSourceUser);
+
+        // Note we can't use Stats.writeToParcel() here; see the below constructor for the reason.
+        parcel.writeLong(totalStats.totalElapsedTime);
+        parcel.writeInt(totalStats.numSyncs);
+        parcel.writeInt(totalStats.numSourcePoll);
+        parcel.writeInt(totalStats.numSourceOther);
+        parcel.writeInt(totalStats.numSourceLocal);
+        parcel.writeInt(totalStats.numSourceUser);
+
         parcel.writeLong(lastSuccessTime);
         parcel.writeInt(lastSuccessSource);
         parcel.writeLong(lastFailureTime);
@@ -102,7 +179,18 @@
             parcel.writeLong(mLastEventTimes.get(i));
             parcel.writeString(mLastEvents.get(i));
         }
-        parcel.writeInt(numSourcePeriodic);
+        // Version 4
+        parcel.writeInt(totalStats.numSourcePeriodic);
+
+        // Version 5
+        parcel.writeInt(totalStats.numSourceFeed);
+        parcel.writeInt(totalStats.numFailures);
+        parcel.writeInt(totalStats.numCancels);
+
+        parcel.writeLong(lastTodayResetTime);
+
+        todayStats.writeToParcel(parcel);
+        yesterdayStats.writeToParcel(parcel);
     }
 
     public SyncStatusInfo(Parcel parcel) {
@@ -111,12 +199,15 @@
             Log.w("SyncStatusInfo", "Unknown version: " + version);
         }
         authorityId = parcel.readInt();
-        totalElapsedTime = parcel.readLong();
-        numSyncs = parcel.readInt();
-        numSourcePoll = parcel.readInt();
-        numSourceServer = parcel.readInt();
-        numSourceLocal = parcel.readInt();
-        numSourceUser = parcel.readInt();
+
+        // Note we can't use Stats.writeToParcel() here because the data is persisted and we need
+        // to be able to read from the old format too.
+        totalStats.totalElapsedTime = parcel.readLong();
+        totalStats.numSyncs = parcel.readInt();
+        totalStats.numSourcePoll = parcel.readInt();
+        totalStats.numSourceOther = parcel.readInt();
+        totalStats.numSourceLocal = parcel.readInt();
+        totalStats.numSourceUser = parcel.readInt();
         lastSuccessTime = parcel.readLong();
         lastSuccessSource = parcel.readInt();
         lastFailureTime = parcel.readLong();
@@ -149,25 +240,37 @@
         }
         if (version < 4) {
             // Before version 4, numSourcePeriodic wasn't persisted.
-            numSourcePeriodic = numSyncs - numSourceLocal - numSourcePoll - numSourceServer
-                    - numSourceUser;
-            if (numSourcePeriodic < 0) { // Sanity check.
-                numSourcePeriodic = 0;
+            totalStats.numSourcePeriodic =
+                    totalStats.numSyncs - totalStats.numSourceLocal - totalStats.numSourcePoll
+                            - totalStats.numSourceOther
+                            - totalStats.numSourceUser;
+            if (totalStats.numSourcePeriodic < 0) { // Sanity check.
+                totalStats.numSourcePeriodic = 0;
             }
         } else {
-            numSourcePeriodic = parcel.readInt();
+            totalStats.numSourcePeriodic = parcel.readInt();
+        }
+        if (version >= 5) {
+            totalStats.numSourceFeed = parcel.readInt();
+            totalStats.numFailures = parcel.readInt();
+            totalStats.numCancels = parcel.readInt();
+
+            lastTodayResetTime = parcel.readLong();
+
+            todayStats.readFromParcel(parcel);
+            yesterdayStats.readFromParcel(parcel);
         }
     }
 
     public SyncStatusInfo(SyncStatusInfo other) {
         authorityId = other.authorityId;
-        totalElapsedTime = other.totalElapsedTime;
-        numSyncs = other.numSyncs;
-        numSourcePoll = other.numSourcePoll;
-        numSourceServer = other.numSourceServer;
-        numSourceLocal = other.numSourceLocal;
-        numSourceUser = other.numSourceUser;
-        numSourcePeriodic = other.numSourcePeriodic;
+
+        other.totalStats.copyTo(totalStats);
+        other.todayStats.copyTo(todayStats);
+        other.yesterdayStats.copyTo(yesterdayStats);
+
+        lastTodayResetTime = other.lastTodayResetTime;
+
         lastSuccessTime = other.lastSuccessTime;
         lastSuccessSource = other.lastSuccessSource;
         lastFailureTime = other.lastFailureTime;
@@ -251,4 +354,41 @@
             }
         }
     }
+
+    /**
+     * If the last reset was not not today, move today's stats to yesterday's and clear today's.
+     */
+    public void maybeResetTodayStats(boolean clockValid, boolean force) {
+        final long now = System.currentTimeMillis();
+
+        if (!force) {
+            // Last reset was the same day, nothing to do.
+            if (areSameDates(now, lastTodayResetTime)) {
+                return;
+            }
+
+            // Hack -- on devices with no RTC, until the NTP kicks in, the device won't have the
+            // correct time. So if the time goes back, don't reset, unless we're sure the current
+            // time is correct.
+            if (now < lastTodayResetTime && !clockValid) {
+                return;
+            }
+        }
+
+        lastTodayResetTime = now;
+
+        todayStats.copyTo(yesterdayStats);
+        todayStats.clear();
+    }
+
+    private static boolean areSameDates(long time1, long time2) {
+        final Calendar c1 = new GregorianCalendar();
+        final Calendar c2 = new GregorianCalendar();
+
+        c1.setTimeInMillis(time1);
+        c2.setTimeInMillis(time2);
+
+        return c1.get(Calendar.YEAR) == c2.get(Calendar.YEAR)
+                && c1.get(Calendar.DAY_OF_YEAR) == c2.get(Calendar.DAY_OF_YEAR);
+    }
 }
\ No newline at end of file