Collect background stats: scheduled jobs

Added background times and counts for an app's scheduled job usage.
Changes DualTimer to be a subclass of Timer so that background timers
can be easily accessed from the main timer (which is convenient for
jobs, whose ArrayMap of Timers is directly accessed outside of
BatteryStatsImpl).

Bug: 35669746
Test: runtest -x
frameworks/base/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java

Change-Id: Ic1d85db34346ebda94ed39f134fc0794a5877815
(cherry picked from commit d253f537a0cc15851fb0308f3ea287e6bd93dfe6)
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index dd11f68..8349510 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -182,7 +182,7 @@
      * New in version 19:
      *   - Wakelock data (wl) gets current and max times.
      * New in version 20:
-     *   - Sensor, BluetoothScan, WifiScan get background timers and counter.
+     *   - Background timers and counters for: Sensor, BluetoothScan, WifiScan, Jobs.
      */
     static final String CHECKIN_VERSION = "20";
 
@@ -392,6 +392,16 @@
         }
 
         /**
+         * Returns the secondary Timer held by the Timer, if one exists. This secondary timer may be
+         * used, for example, for tracking background usage. Secondary timers are never pooled.
+         *
+         * Not all Timer subclasses have a secondary timer; those that don't return null.
+         */
+        public Timer getSubTimer() {
+            return null;
+        }
+
+        /**
          * Returns whether the timer is currently running.  Some types of timers
          * (e.g. BatchTimers) don't know whether the event is currently active,
          * and report false.
@@ -3393,9 +3403,13 @@
                 // Convert from microseconds to milliseconds with rounding
                 final long totalTime = (timer.getTotalTimeLocked(rawRealtime, which) + 500) / 1000;
                 final int count = timer.getCountLocked(which);
+                final Timer bgTimer = timer.getSubTimer();
+                final long bgTime = bgTimer != null ?
+                        (bgTimer.getTotalTimeLocked(rawRealtime, which) + 500) / 1000 : -1;
+                final int bgCount = bgTimer != null ? bgTimer.getCountLocked(which) : -1;
                 if (totalTime != 0) {
                     dumpLine(pw, uid, category, JOB_DATA, "\"" + jobs.keyAt(ij) + "\"",
-                            totalTime, count);
+                            totalTime, count, bgTime, bgCount);
                 }
             }
 
@@ -4616,6 +4630,10 @@
                 // Convert from microseconds to milliseconds with rounding
                 final long totalTime = (timer.getTotalTimeLocked(rawRealtime, which) + 500) / 1000;
                 final int count = timer.getCountLocked(which);
+                final Timer bgTimer = timer.getSubTimer();
+                final long bgTime = bgTimer != null ?
+                        (bgTimer.getTotalTimeLocked(rawRealtime, which) + 500) / 1000 : -1;
+                final int bgCount = bgTimer != null ? bgTimer.getCountLocked(which) : -1;
                 sb.setLength(0);
                 sb.append(prefix);
                 sb.append("    Job ");
@@ -4626,6 +4644,13 @@
                     sb.append("realtime (");
                     sb.append(count);
                     sb.append(" times)");
+                    if (bgTime > 0) {
+                        sb.append(", ");
+                        formatTimeMs(sb, bgTime);
+                        sb.append("background (");
+                        sb.append(bgCount);
+                        sb.append(" times)");
+                    }
                 } else {
                     sb.append("(not used)");
                 }