Merge "Add 1-day sync stats in syncmanager dumpsys." into pi-dev
diff --git a/core/java/android/content/IContentService.aidl b/core/java/android/content/IContentService.aidl
index c500116..dc17666 100644
--- a/core/java/android/content/IContentService.aidl
+++ b/core/java/android/content/IContentService.aidl
@@ -183,4 +183,6 @@
 
     void putCache(in String packageName, in Uri key, in Bundle value, int userId);
     Bundle getCache(in String packageName, in Uri key, int userId);
+
+    void resetTodayStats();
 }
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
diff --git a/services/core/java/com/android/server/content/ContentService.java b/services/core/java/com/android/server/content/ContentService.java
index e3d0bdd..e840a29 100644
--- a/services/core/java/com/android/server/content/ContentService.java
+++ b/services/core/java/com/android/server/content/ContentService.java
@@ -51,6 +51,8 @@
 import android.os.Parcel;
 import android.os.Process;
 import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.os.ShellCallback;
 import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.ArrayMap;
@@ -1577,4 +1579,32 @@
             }
         }
     }
+
+    private void enforceShell(String method) {
+        final int callingUid = Binder.getCallingUid();
+        if (callingUid != Process.SHELL_UID && callingUid != Process.ROOT_UID) {
+            throw new SecurityException("Non-shell user attempted to call " + method);
+        }
+    }
+
+    @Override
+    public void resetTodayStats() {
+        enforceShell("resetTodayStats");
+
+        if (mSyncManager != null) {
+            final long token = Binder.clearCallingIdentity();
+            try {
+                mSyncManager.resetTodayStats();
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+    }
+
+    @Override
+    public void onShellCommand(FileDescriptor in, FileDescriptor out,
+            FileDescriptor err, String[] args, ShellCallback callback,
+            ResultReceiver resultReceiver) {
+        (new ContentShellCommand(this)).exec(this, in, out, err, args, callback, resultReceiver);
+    }
 }
diff --git a/services/core/java/com/android/server/content/ContentShellCommand.java b/services/core/java/com/android/server/content/ContentShellCommand.java
new file mode 100644
index 0000000..6c2991f
--- /dev/null
+++ b/services/core/java/com/android/server/content/ContentShellCommand.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.content;
+
+import android.content.IContentService;
+import android.content.Intent;
+import android.os.RemoteException;
+import android.os.ShellCommand;
+
+import java.io.PrintWriter;
+
+public class ContentShellCommand extends ShellCommand {
+    final IContentService mInterface;
+
+    ContentShellCommand(IContentService service) {
+        mInterface = service;
+    }
+
+    @Override
+    public int onCommand(String cmd) {
+        if (cmd == null) {
+            return handleDefaultCommands(cmd);
+        }
+
+        final PrintWriter pw = getOutPrintWriter();
+        try {
+            switch(cmd) {
+                case "reset-today-stats":
+                    return runResetTodayStats();
+                default:
+                    return handleDefaultCommands(cmd);
+            }
+        } catch (RemoteException e) {
+            pw.println("Remote exception: " + e);
+        }
+        return -1;
+    }
+
+    private int runResetTodayStats() throws RemoteException {
+        mInterface.resetTodayStats();
+        return 0;
+    }
+
+    @Override
+    public void onHelp() {
+        final PrintWriter pw = getOutPrintWriter();
+        pw.println("Content service commands:");
+        pw.println("  help");
+        pw.println("    Print this help text.");
+        pw.println("");
+        pw.println("  reset-today-stats");
+        pw.println("    Reset 1-day sync stats.");
+        pw.println();
+    }
+}
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index a312fe1..decae18 100644
--- a/services/core/java/com/android/server/content/SyncManager.java
+++ b/services/core/java/com/android/server/content/SyncManager.java
@@ -48,6 +48,7 @@
 import android.content.SyncInfo;
 import android.content.SyncResult;
 import android.content.SyncStatusInfo;
+import android.content.SyncStatusInfo.Stats;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
@@ -95,6 +96,7 @@
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.IndentingPrintWriter;
+import com.android.internal.util.function.QuadConsumer;
 import com.android.server.DeviceIdleController;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
@@ -122,6 +124,7 @@
 import java.util.Objects;
 import java.util.Random;
 import java.util.Set;
+import java.util.function.Function;
 import java.util.function.Predicate;
 
 /**
@@ -242,7 +245,7 @@
     /** Track whether the device has already been provisioned. */
     private volatile boolean mProvisioned;
 
-    protected SyncAdaptersCache mSyncAdapters;
+    protected final SyncAdaptersCache mSyncAdapters;
 
     private final Random mRand;
 
@@ -423,6 +426,17 @@
                 }
             };
 
+    private final BroadcastReceiver mOtherIntentsReceiver =
+            new BroadcastReceiver() {
+                @Override
+                public void onReceive(Context context, Intent intent) {
+                    if (Intent.ACTION_TIME_CHANGED.equals(intent.getAction())) {
+                        mSyncStorageEngine.setClockValid();
+                        return;
+                    }
+                }
+            };
+
     private BroadcastReceiver mUserIntentReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -631,6 +645,9 @@
         mContext.registerReceiverAsUser(
                 mUserIntentReceiver, UserHandle.ALL, intentFilter, null, null);
 
+        intentFilter = new IntentFilter(Intent.ACTION_TIME_CHANGED);
+        context.registerReceiver(mOtherIntentsReceiver, intentFilter);
+
         if (!factoryTest) {
             mNotificationMgr = (NotificationManager)
                     context.getSystemService(Context.NOTIFICATION_SERVICE);
@@ -947,9 +964,13 @@
         } else if (requestedAuthority == null) {
             source = SyncStorageEngine.SOURCE_POLL;
         } else {
-            // This isn't strictly server, since arbitrary callers can (and do) request
-            // a non-forced two-way sync on a specific url.
-            source = SyncStorageEngine.SOURCE_SERVER;
+            if (extras.containsKey("feed")) {
+                source = SyncStorageEngine.SOURCE_FEED;
+            } else{
+                // This isn't strictly server, since arbitrary callers can (and do) request
+                // a non-forced two-way sync on a specific url.
+                source = SyncStorageEngine.SOURCE_OTHER;
+            }
         }
 
         for (AccountAndUser account : accounts) {
@@ -2134,6 +2155,7 @@
         pw.print("Memory low: "); pw.println(mStorageIsLow);
         pw.print("Device idle: "); pw.println(mDeviceIsIdle);
         pw.print("Reported active: "); pw.println(mReportedSyncActive);
+        pw.print("Clock valid: "); pw.println(mSyncStorageEngine.isClockValid());
 
         final AccountAndUser[] accounts = AccountManagerService.getSingleton().getAllAccounts();
 
@@ -2181,26 +2203,35 @@
 
         final ArrayList<Pair<EndPoint, SyncStatusInfo>> statuses = new ArrayList<>();
 
+        mSyncStorageEngine.resetTodayStats(/* force=*/ false);
+
         for (AccountAndUser account : accounts) {
             pw.printf("Account %s u%d %s\n",
                     account.account.name, account.userId, account.account.type);
 
             pw.println("=======================================================================");
-            final PrintTable table = new PrintTable(13);
+            final PrintTable table = new PrintTable(16);
             table.set(0, 0,
                     "Authority", // 0
                     "Syncable",  // 1
                     "Enabled",   // 2
-                    "Delay",     // 3
-                    "Loc",       // 4
-                    "Poll",      // 5
-                    "Per",       // 6
-                    "Serv",      // 7
-                    "User",      // 8
-                    "Tot",       // 9
-                    "Time",      // 10
-                    "Last Sync", // 11
-                    "Backoff"    // 12
+
+                    "Stats",     // 3 "Total", "Today" or "Yesterday".
+
+                    "Loc",       // 4 # of syncs with local sources. (including failures/cancels. )
+                    "Poll",      // 5 "poll" syncs.
+                    "Per",       // 6 Periodic syncs.
+                    "Feed",      // 7 Syncs with a "feed" extra. (subscribedfeeds?)
+                    "User",      // 8 User-initiated
+                    "Othr",      // 9 Other sources.
+
+                    "Tot",       // 10 Total syncs (including failures / cancels)
+                    "Fail",      // 11 (Failure)
+                    "Can",       // 12 (Cancel)
+
+                    "Time",      // 13 Total time
+                    "Last Sync", // 14
+                    "Backoff"    // 15
             );
 
             final List<RegisteredServicesCache.ServiceInfo<SyncAdapterType>> sorted =
@@ -2234,37 +2265,50 @@
                 }
                 table.set(row, 0, authority, settings.syncable, settings.enabled);
 
-                sb.setLength(0);
-                table.set(row, 4,
-                        status.numSourceLocal,
-                        status.numSourcePoll,
-                        status.numSourcePeriodic,
-                        status.numSourceServer,
-                        status.numSourceUser,
-                        status.numSyncs,
-                        formatDurationHMS(sb, status.totalElapsedTime));
+                QuadConsumer<String, Stats, Function<Integer, String>, Integer> c =
+                        (label, stats, filter, r) -> {
+                    sb.setLength(0);
+                    table.set(r, 3,
+                            label,
+                            filter.apply(stats.numSourceLocal),
+                            filter.apply(stats.numSourcePoll),
+                            filter.apply(stats.numSourcePeriodic),
+                            filter.apply(stats.numSourceFeed),
+                            filter.apply(stats.numSourceUser),
+                            filter.apply(stats.numSourceOther),
+                            filter.apply(stats.numSyncs),
+                            filter.apply(stats.numFailures),
+                            filter.apply(stats.numCancels),
+                            formatDurationHMS(sb, stats.totalElapsedTime));
+                };
+                c.accept("Total", status.totalStats, (i) -> Integer.toString(i), row);
+                c.accept("Today", status.todayStats, this::zeroToEmpty, row + 1);
+                c.accept("Yestr", status.yesterdayStats, this::zeroToEmpty, row + 2);
+
+                final int LAST_SYNC = 14;
+                final int BACKOFF = LAST_SYNC + 1;
 
                 int row1 = row;
                 if (settings.delayUntil > now) {
-                    table.set(row1++, 12, "D: " + (settings.delayUntil - now) / 1000);
+                    table.set(row1++, BACKOFF, "D: " + (settings.delayUntil - now) / 1000);
                     if (settings.backoffTime > now) {
-                        table.set(row1++, 12, "B: " + (settings.backoffTime - now) / 1000);
-                        table.set(row1++, 12, settings.backoffDelay / 1000);
+                        table.set(row1++, BACKOFF, "B: " + (settings.backoffTime - now) / 1000);
+                        table.set(row1++, BACKOFF, settings.backoffDelay / 1000);
                     }
                 }
 
                 row1 = row;
                 if (status.lastSuccessTime != 0) {
-                    table.set(row1++, 11, SyncStorageEngine.SOURCES[status.lastSuccessSource]
+                    table.set(row1++, LAST_SYNC, SyncStorageEngine.SOURCES[status.lastSuccessSource]
                             + " " + "SUCCESS");
-                    table.set(row1++, 11, formatTime(status.lastSuccessTime));
+                    table.set(row1++, LAST_SYNC, formatTime(status.lastSuccessTime));
                 }
                 if (status.lastFailureTime != 0) {
-                    table.set(row1++, 11, SyncStorageEngine.SOURCES[status.lastFailureSource]
+                    table.set(row1++, LAST_SYNC, SyncStorageEngine.SOURCES[status.lastFailureSource]
                             + " " + "FAILURE");
-                    table.set(row1++, 11, formatTime(status.lastFailureTime));
+                    table.set(row1++, LAST_SYNC, formatTime(status.lastFailureTime));
                     //noinspection UnusedAssignment
-                    table.set(row1++, 11, status.lastFailureMesg);
+                    table.set(row1++, LAST_SYNC, status.lastFailureMesg);
                 }
             }
             table.writeTo(pw);
@@ -2274,6 +2318,7 @@
 
         pw.println();
         pw.println("Per Adapter History");
+        pw.println("(SERVER is now split up to FEED and OTHER)");
 
         for (int i = 0; i < statuses.size(); i++) {
             final Pair<EndPoint, SyncStatusInfo> event = statuses.get(i);
@@ -2299,6 +2344,10 @@
         }
     }
 
+    private String zeroToEmpty(int value) {
+        return (value != 0) ? Integer.toString(value) : "";
+    }
+
     private void dumpTimeSec(PrintWriter pw, long time) {
         pw.print(time/1000); pw.print('.'); pw.print((time/100)%10);
         pw.print('s');
@@ -2459,6 +2508,7 @@
 
             pw.println();
             pw.println("Recent Sync History");
+            pw.println("(SERVER is now split up to FEED and OTHER)");
             final String format = "  %-" + maxAccount + "s  %-" + maxAuthority + "s %s\n";
             final Map<String, Long> lastTimeMap = Maps.newHashMap();
             final PackageManager pm = mContext.getPackageManager();
@@ -2525,6 +2575,7 @@
             }
             pw.println();
             pw.println("Recent Sync History Extras");
+            pw.println("(SERVER is now split up to FEED and OTHER)");
             for (int i = 0; i < N; i++) {
                 final SyncStorageEngine.SyncHistoryItem item = items.get(i);
                 final Bundle extras = item.extras;
@@ -3104,6 +3155,10 @@
             final boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
             if (isLoggable) Slog.v(TAG, op.toString());
 
+            // At this point, we know the device has been connected to the server, so
+            // assume the clock is correct.
+            mSyncStorageEngine.setClockValid();
+
             mSyncJobService.markSyncStarted(op.jobId);
 
             if (mStorageIsLow) {
@@ -4015,4 +4070,8 @@
         Slog.wtf(TAG, message);
         mLogger.log("WTF: ", message);
     }
+
+    public void resetTodayStats() {
+        mSyncStorageEngine.resetTodayStats(/*force=*/ true);
+    }
 }
diff --git a/services/core/java/com/android/server/content/SyncStorageEngine.java b/services/core/java/com/android/server/content/SyncStorageEngine.java
index 8dd229c..f54a9a0 100644
--- a/services/core/java/com/android/server/content/SyncStorageEngine.java
+++ b/services/core/java/com/android/server/content/SyncStorageEngine.java
@@ -108,11 +108,12 @@
     /** Enum value for a sync stop event. */
     public static final int EVENT_STOP = 1;
 
-    /** Enum value for a server-initiated sync. */
-    public static final int SOURCE_SERVER = 0;
+    /** Enum value for a sync with other sources. */
+    public static final int SOURCE_OTHER = 0;
 
     /** Enum value for a local-initiated sync. */
     public static final int SOURCE_LOCAL = 1;
+
     /** Enum value for a poll-based sync (e.g., upon connection to network) */
     public static final int SOURCE_POLL = 2;
 
@@ -122,16 +123,18 @@
     /** Enum value for a periodic sync. */
     public static final int SOURCE_PERIODIC = 4;
 
+    /** Enum a sync with a "feed" extra */
+    public static final int SOURCE_FEED = 5;
+
     public static final long NOT_IN_BACKOFF_MODE = -1;
 
-    // TODO: i18n -- grab these out of resources.
     /** String names for the sync source types. */
-    public static final String[] SOURCES = { "SERVER",
+    public static final String[] SOURCES = { "OTHER",
             "LOCAL",
             "POLL",
             "USER",
             "PERIODIC",
-            "SERVICE"};
+            "FEED"};
 
     // The MESG column will contain one of these or one of the Error types.
     public static final String MESG_SUCCESS = "success";
@@ -153,6 +156,8 @@
     private static HashMap<String, String> sAuthorityRenames;
     private static PeriodicSyncAddedListener mPeriodicSyncAddedListener;
 
+    private volatile boolean mIsClockValid;
+
     static {
         sAuthorityRenames = new HashMap<String, String>();
         sAuthorityRenames.put("contacts", "com.android.contacts");
@@ -1174,23 +1179,36 @@
 
             SyncStatusInfo status = getOrCreateSyncStatusLocked(item.authorityId);
 
-            status.numSyncs++;
-            status.totalElapsedTime += elapsedTime;
+            status.maybeResetTodayStats(isClockValid(), /*force=*/ false);
+
+            status.totalStats.numSyncs++;
+            status.todayStats.numSyncs++;
+            status.totalStats.totalElapsedTime += elapsedTime;
+            status.todayStats.totalElapsedTime += elapsedTime;
             switch (item.source) {
                 case SOURCE_LOCAL:
-                    status.numSourceLocal++;
+                    status.totalStats.numSourceLocal++;
+                    status.todayStats.numSourceLocal++;
                     break;
                 case SOURCE_POLL:
-                    status.numSourcePoll++;
+                    status.totalStats.numSourcePoll++;
+                    status.todayStats.numSourcePoll++;
                     break;
                 case SOURCE_USER:
-                    status.numSourceUser++;
+                    status.totalStats.numSourceUser++;
+                    status.todayStats.numSourceUser++;
                     break;
-                case SOURCE_SERVER:
-                    status.numSourceServer++;
+                case SOURCE_OTHER:
+                    status.totalStats.numSourceOther++;
+                    status.todayStats.numSourceOther++;
                     break;
                 case SOURCE_PERIODIC:
-                    status.numSourcePeriodic++;
+                    status.totalStats.numSourcePeriodic++;
+                    status.todayStats.numSourcePeriodic++;
+                    break;
+                case SOURCE_FEED:
+                    status.totalStats.numSourceFeed++;
+                    status.todayStats.numSourceFeed++;
                     break;
             }
 
@@ -1225,6 +1243,9 @@
                 if (status.lastFailureTime == 0) {
                     writeStatusNow = true;
                 }
+                status.totalStats.numFailures++;
+                status.todayStats.numFailures++;
+
                 status.lastFailureTime = lastSyncTime;
                 status.lastFailureSource = item.source;
                 status.lastFailureMesg = resultMessage;
@@ -1233,6 +1254,11 @@
                 }
                 ds.failureCount++;
                 ds.failureTime += elapsedTime;
+            } else {
+                // Cancel
+                status.totalStats.numCancels++;
+                status.todayStats.numCancels++;
+                writeStatusNow = true;
             }
             final StringBuilder event = new StringBuilder();
             event.append("" + resultMessage + " Source=" + SyncStorageEngine.SOURCES[item.source]
@@ -1969,9 +1995,8 @@
     }
 
     /**
-     * Load sync engine state from the old syncmanager database, and then
-     * erase it.  Note that we don't deal with pending operations, active
-     * sync, or history.
+     * TODO Remove it. It's super old code that was used to migrate the information from a sqlite
+     * database that we used a long time ago, and is no longer relevant.
      */
     private void readAndDeleteLegacyAccountInfoLocked() {
         // Look for old database to initialize from.
@@ -2049,13 +2074,13 @@
                         st = new SyncStatusInfo(authority.ident);
                         mSyncStatus.put(authority.ident, st);
                     }
-                    st.totalElapsedTime = getLongColumn(c, "totalElapsedTime");
-                    st.numSyncs = getIntColumn(c, "numSyncs");
-                    st.numSourceLocal = getIntColumn(c, "numSourceLocal");
-                    st.numSourcePoll = getIntColumn(c, "numSourcePoll");
-                    st.numSourceServer = getIntColumn(c, "numSourceServer");
-                    st.numSourceUser = getIntColumn(c, "numSourceUser");
-                    st.numSourcePeriodic = 0;
+                    st.totalStats.totalElapsedTime = getLongColumn(c, "totalElapsedTime");
+                    st.totalStats.numSyncs = getIntColumn(c, "numSyncs");
+                    st.totalStats.numSourceLocal = getIntColumn(c, "numSourceLocal");
+                    st.totalStats.numSourcePoll = getIntColumn(c, "numSourcePoll");
+                    st.totalStats.numSourceOther = getIntColumn(c, "numSourceServer");
+                    st.totalStats.numSourceUser = getIntColumn(c, "numSourceUser");
+                    st.totalStats.numSourcePeriodic = 0;
                     st.lastSuccessSource = getIntColumn(c, "lastSuccessSource");
                     st.lastSuccessTime = getLongColumn(c, "lastSuccessTime");
                     st.lastFailureSource = getIntColumn(c, "lastFailureSource");
@@ -2296,4 +2321,29 @@
     public void queueBackup() {
         BackupManager.dataChanged("android");
     }
+
+    public void setClockValid() {
+        if (!mIsClockValid) {
+            mIsClockValid = true;
+            Slog.w(TAG, "Clock is valid now.");
+        }
+    }
+
+    public boolean isClockValid() {
+        return mIsClockValid;
+    }
+
+    public void resetTodayStats(boolean force) {
+        if (force) {
+            Log.w(TAG, "Force resetting today stats.");
+        }
+        synchronized (mAuthorities) {
+            final int N = mSyncStatus.size();
+            for (int i = 0; i < N; i++) {
+                SyncStatusInfo cur = mSyncStatus.valueAt(i);
+                cur.maybeResetTodayStats(isClockValid(), force);
+            }
+            writeStatusLocked();
+        }
+    }
 }