Add a global setting to turn on/off the proc state cpu times tracking.

Bug: 66953194
Test: atest core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
Test: atest hostsidetests/incident/src/com/android/server/cts/BatteryStatsValidationTest.java
Test: atest core/tests/coretests/src/com/android/internal/os/BstatsCpuTimesValidationTest.java
Change-Id: Id26476ad77c95994f358d8bd59b6c2e6513c4c54
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 009fc39..36ad625 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -9782,6 +9782,22 @@
         public static final String TEXT_CLASSIFIER_CONSTANTS = "text_classifier_constants";
 
         /**
+         * BatteryStats specific settings.
+         * This is encoded as a key=value list, separated by commas. Ex: "foo=1,bar=true"
+         *
+         * The following keys are supported:
+         * <pre>
+         * track_cpu_times_by_proc_state (boolean)
+         * </pre>
+         *
+         * <p>
+         * Type: string
+         * @hide
+         * see also com.android.internal.os.BatteryStatsImpl.Constants
+         */
+        public static final String BATTERY_STATS_CONSTANTS = "battery_stats_constants";
+
+        /**
          * Whether or not App Standby feature is enabled. This controls throttling of apps
          * based on usage patterns and predictions.
          * Type: int (0 for false, 1 for true)
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 9015cbb..b8ff9e4 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -21,10 +21,13 @@
 import android.app.ActivityManager;
 import android.bluetooth.BluetoothActivityEnergyInfo;
 import android.bluetooth.UidTraffic;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
+import android.database.ContentObserver;
 import android.net.ConnectivityManager;
 import android.net.NetworkStats;
+import android.net.Uri;
 import android.net.wifi.WifiActivityEnergyInfo;
 import android.net.wifi.WifiManager;
 import android.os.BatteryManager;
@@ -46,6 +49,7 @@
 import android.os.UserHandle;
 import android.os.WorkSource;
 import android.os.WorkSource.WorkChain;
+import android.provider.Settings;
 import android.telephony.DataConnectionRealTimeInfo;
 import android.telephony.ModemActivityInfo;
 import android.telephony.ServiceState;
@@ -54,6 +58,7 @@
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.IntArray;
+import android.util.KeyValueListParser;
 import android.util.Log;
 import android.util.LogWriter;
 import android.util.LongSparseArray;
@@ -292,9 +297,18 @@
     public void updateProcStateCpuTimes(boolean onBattery, boolean onBatteryScreenOff) {
         final SparseIntArray uidStates;
         synchronized (BatteryStatsImpl.this) {
+            if (!mConstants.TRACK_CPU_TIMES_BY_PROC_STATE) {
+                return;
+            }
             if(!initKernelSingleUidTimeReaderLocked()) {
                 return;
             }
+            // If the KernelSingleUidTimeReader has stale cpu times, then we shouldn't try to
+            // compute deltas since it might result in mis-attributing cpu times to wrong states.
+            if (mKernelSingleUidTimeReader.hasStaleData()) {
+                mPendingUids.clear();
+                return;
+            }
 
             if (mPendingUids.size() == 0) {
                 return;
@@ -355,12 +369,23 @@
      */
     public void copyFromAllUidsCpuTimes(boolean onBattery, boolean onBatteryScreenOff) {
         synchronized (BatteryStatsImpl.this) {
+            if (!mConstants.TRACK_CPU_TIMES_BY_PROC_STATE) {
+                return;
+            }
             if(!initKernelSingleUidTimeReaderLocked()) {
                 return;
             }
 
             final SparseArray<long[]> allUidCpuFreqTimesMs =
                     mKernelUidCpuFreqTimeReader.getAllUidCpuFreqTimeMs();
+            // If the KernelSingleUidTimeReader has stale cpu times, then we shouldn't try to
+            // compute deltas since it might result in mis-attributing cpu times to wrong states.
+            if (mKernelSingleUidTimeReader.hasStaleData()) {
+                mKernelSingleUidTimeReader.setAllUidsCpuTimesMs(allUidCpuFreqTimesMs);
+                mKernelSingleUidTimeReader.markDataAsStale(false);
+                mPendingUids.clear();
+                return;
+            }
             for (int i = allUidCpuFreqTimesMs.size() - 1; i >= 0; --i) {
                 final int uid = allUidCpuFreqTimesMs.keyAt(i);
                 final Uid u = getAvailableUidStatsLocked(mapUid(uid));
@@ -450,6 +475,7 @@
         Future<?> scheduleCpuSyncDueToRemovedUid(int uid);
         Future<?> scheduleReadProcStateCpuTimes(boolean onBattery, boolean onBatteryScreenOff);
         Future<?> scheduleCopyFromAllUidsCpuTimes(boolean onBattery, boolean onBatteryScreenOff);
+        Future<?> scheduleCpuSyncDueToSettingChange();
     }
 
     public Handler mHandler;
@@ -812,6 +838,9 @@
     @VisibleForTesting
     protected PowerProfile mPowerProfile;
 
+    @GuardedBy("this")
+    private final Constants mConstants;
+
     /*
      * Holds a SamplingTimer associated with each Resource Power Manager state and voter,
      * recording their times when on-battery (regardless of screen state).
@@ -900,6 +929,7 @@
         mHandler = null;
         mPlatformIdleStateCallback = null;
         mUserInfoProvider = null;
+        mConstants = new Constants(mHandler);
         clearHistoryLocked();
     }
 
@@ -9411,7 +9441,7 @@
                 if (mProcessState != ActivityManager.PROCESS_STATE_NONEXISTENT) {
                     mProcessStateTimer[mProcessState].stopRunningLocked(elapsedRealtimeMs);
 
-                    if (mBsi.mPerProcStateCpuTimesAvailable) {
+                    if (mBsi.trackPerProcStateCpuTimes()) {
                         if (mBsi.mPendingUids.size() == 0) {
                             mBsi.mExternalSync.scheduleReadProcStateCpuTimes(
                                     mBsi.mOnBatteryTimeBase.isRunning(),
@@ -9765,6 +9795,7 @@
         mCheckinFile = new AtomicFile(new File(systemDir, "batterystats-checkin.bin"));
         mDailyFile = new AtomicFile(new File(systemDir, "batterystats-daily.xml"));
         mHandler = new MyHandler(handler.getLooper());
+        mConstants = new Constants(mHandler);
         mStartCount++;
         mScreenOnTimer = new StopwatchTimer(mClocks, null, -1, null, mOnBatteryTimeBase);
         mScreenDozeTimer = new StopwatchTimer(mClocks, null, -1, null, mOnBatteryTimeBase);
@@ -9860,6 +9891,7 @@
         mDailyFile = null;
         mHandler = null;
         mExternalSync = null;
+        mConstants = new Constants(mHandler);
         clearHistoryLocked();
         readFromParcel(p);
         mPlatformIdleStateCallback = null;
@@ -12613,6 +12645,78 @@
         mShuttingDown = true;
     }
 
+    public boolean trackPerProcStateCpuTimes() {
+        return mConstants.TRACK_CPU_TIMES_BY_PROC_STATE && mPerProcStateCpuTimesAvailable;
+    }
+
+    public void systemServicesReady(Context context) {
+        mConstants.startObserving(context.getContentResolver());
+    }
+
+    @VisibleForTesting
+    public final class Constants extends ContentObserver {
+        public static final String KEY_TRACK_CPU_TIMES_BY_PROC_STATE
+                = "track_cpu_times_by_proc_state";
+
+        private static final boolean DEFAULT_TRACK_CPU_TIMES_BY_PROC_STATE = true;
+
+        public boolean TRACK_CPU_TIMES_BY_PROC_STATE = DEFAULT_TRACK_CPU_TIMES_BY_PROC_STATE;
+
+        private ContentResolver mResolver;
+        private final KeyValueListParser mParser = new KeyValueListParser(',');
+
+        public Constants(Handler handler) {
+            super(handler);
+        }
+
+        public void startObserving(ContentResolver resolver) {
+            mResolver = resolver;
+            mResolver.registerContentObserver(
+                    Settings.Global.getUriFor(Settings.Global.BATTERY_STATS_CONSTANTS),
+                    false /* notifyForDescendants */, this);
+            updateConstants();
+        }
+
+        @Override
+        public void onChange(boolean selfChange, Uri uri) {
+            updateConstants();
+        }
+
+        private void updateConstants() {
+            synchronized (BatteryStatsImpl.this) {
+                try {
+                    mParser.setString(Settings.Global.getString(mResolver,
+                            Settings.Global.BATTERY_STATS_CONSTANTS));
+                } catch (IllegalArgumentException e) {
+                    // Failed to parse the settings string, log this and move on
+                    // with defaults.
+                    Slog.e(TAG, "Bad batterystats settings", e);
+                }
+
+                updateTrackCpuTimesByProcStateLocked(TRACK_CPU_TIMES_BY_PROC_STATE,
+                        mParser.getBoolean(KEY_TRACK_CPU_TIMES_BY_PROC_STATE,
+                                DEFAULT_TRACK_CPU_TIMES_BY_PROC_STATE));
+            }
+        }
+
+        private void updateTrackCpuTimesByProcStateLocked(boolean wasEnabled, boolean isEnabled) {
+            TRACK_CPU_TIMES_BY_PROC_STATE = isEnabled;
+            if (isEnabled && !wasEnabled) {
+                mKernelSingleUidTimeReader.markDataAsStale(true);
+                mExternalSync.scheduleCpuSyncDueToSettingChange();
+            }
+        }
+
+        public void dumpLocked(PrintWriter pw) {
+            pw.print(KEY_TRACK_CPU_TIMES_BY_PROC_STATE); pw.print("=");
+            pw.println(TRACK_CPU_TIMES_BY_PROC_STATE);
+        }
+    }
+
+    public void dumpConstantsLocked(PrintWriter pw) {
+        mConstants.dumpLocked(pw);
+    }
+
     Parcel mPendingWrite = null;
     final ReentrantLock mWriteLock = new ReentrantLock();
 
diff --git a/core/java/com/android/internal/os/KernelSingleUidTimeReader.java b/core/java/com/android/internal/os/KernelSingleUidTimeReader.java
index ca635a4..ebeb24c4 100644
--- a/core/java/com/android/internal/os/KernelSingleUidTimeReader.java
+++ b/core/java/com/android/internal/os/KernelSingleUidTimeReader.java
@@ -46,12 +46,14 @@
     private final int mCpuFreqsCount;
 
     @GuardedBy("this")
-    private final SparseArray<long[]> mLastUidCpuTimeMs = new SparseArray<>();
+    private SparseArray<long[]> mLastUidCpuTimeMs = new SparseArray<>();
 
     @GuardedBy("this")
     private int mReadErrorCounter;
     @GuardedBy("this")
     private boolean mSingleUidCpuTimesAvailable = true;
+    @GuardedBy("this")
+    private boolean mHasStaleData;
 
     private final Injector mInjector;
 
@@ -166,6 +168,30 @@
         return deltaTimesMs;
     }
 
+    public void markDataAsStale(boolean hasStaleData) {
+        synchronized (this) {
+            mHasStaleData = hasStaleData;
+        }
+    }
+
+    public boolean hasStaleData() {
+        synchronized (this) {
+            return mHasStaleData;
+        }
+    }
+
+    public void setAllUidsCpuTimesMs(SparseArray<long[]> allUidsCpuTimesMs) {
+        synchronized (this) {
+            mLastUidCpuTimeMs.clear();
+            for (int i = allUidsCpuTimesMs.size() - 1; i >= 0; --i) {
+                final long[] cpuTimesMs = allUidsCpuTimesMs.valueAt(i);
+                if (cpuTimesMs != null) {
+                    mLastUidCpuTimeMs.put(allUidsCpuTimesMs.keyAt(i), cpuTimesMs.clone());
+                }
+            }
+        }
+    }
+
     public void removeUid(int uid) {
         synchronized (this) {
             mLastUidCpuTimeMs.delete(uid);
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index fc86500..91782e9 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -112,6 +112,7 @@
                     Settings.Global.BATTERY_DISCHARGE_DURATION_THRESHOLD,
                     Settings.Global.BATTERY_DISCHARGE_THRESHOLD,
                     Settings.Global.BATTERY_SAVER_DEVICE_SPECIFIC_CONSTANTS,
+                    Settings.Global.BATTERY_STATS_CONSTANTS,
                     Settings.Global.BLE_SCAN_ALWAYS_AVAILABLE,
                     Settings.Global.BLUETOOTH_A2DP_SINK_PRIORITY_PREFIX,
                     Settings.Global.BLUETOOTH_A2DP_SRC_PRIORITY_PREFIX,
diff --git a/core/tests/coretests/src/com/android/internal/os/BstatsCpuTimesValidationTest.java b/core/tests/coretests/src/com/android/internal/os/BstatsCpuTimesValidationTest.java
index e54fe7d..4d34721 100644
--- a/core/tests/coretests/src/com/android/internal/os/BstatsCpuTimesValidationTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BstatsCpuTimesValidationTest.java
@@ -25,6 +25,8 @@
 import static android.os.BatteryStats.Uid.PROCESS_STATE_TOP_SLEEPING;
 import static android.os.BatteryStats.Uid.UID_PROCESS_TYPES;
 
+import static com.android.internal.os.BatteryStatsImpl.Constants.KEY_TRACK_CPU_TIMES_BY_PROC_STATE;
+
 import static junit.framework.Assert.assertNotNull;
 import static junit.framework.Assert.assertNull;
 import static junit.framework.Assert.assertTrue;
@@ -48,15 +50,19 @@
 import android.os.PowerManager;
 import android.os.Process;
 import android.os.SystemClock;
+import android.provider.Settings;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.LargeTest;
 import android.support.test.runner.AndroidJUnit4;
 import android.support.test.uiautomator.UiDevice;
+import android.text.TextUtils;
 import android.util.DebugUtils;
 import android.util.Log;
 
 import org.junit.BeforeClass;
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.TestName;
 import org.junit.runner.RunWith;
 
 import java.util.Arrays;
@@ -86,6 +92,9 @@
     private static final int START_SERVICE_TIMEOUT_MS = 2000;
     private static final int START_ISOLATED_SERVICE_TIMEOUT_MS = 2000;
 
+    private static final int SETTING_UPDATE_TIMEOUT_MS = 2000;
+    private static final int SETTING_UPDATE_CHECK_INTERVAL_MS = 200;
+
     private static final int GENERAL_TIMEOUT_MS = 4000;
     private static final int GENERAL_INTERVAL_MS = 200;
 
@@ -97,6 +106,8 @@
     private static boolean sCpuFreqTimesAvailable;
     private static boolean sPerProcStateTimesAvailable;
 
+    @Rule public TestName testName = new TestName();
+
     @BeforeClass
     public static void setupOnce() throws Exception {
         sContext = InstrumentationRegistry.getContext();
@@ -123,6 +134,9 @@
     @Test
     public void testCpuFreqTimes() throws Exception {
         if (!sCpuFreqTimesAvailable) {
+            Log.w(TAG, "Skipping " + testName.getMethodName()
+                    + "; freqTimesAvailable=" + sCpuFreqTimesAvailable
+                    + ", procStateTimesAvailable=" + sPerProcStateTimesAvailable);
             return;
         }
 
@@ -148,6 +162,9 @@
     @Test
     public void testCpuFreqTimes_screenOff() throws Exception {
         if (!sCpuFreqTimesAvailable) {
+            Log.w(TAG, "Skipping " + testName.getMethodName()
+                    + "; freqTimesAvailable=" + sCpuFreqTimesAvailable
+                    + ", procStateTimesAvailable=" + sPerProcStateTimesAvailable);
             return;
         }
 
@@ -179,6 +196,9 @@
     @Test
     public void testCpuFreqTimes_isolatedProcess() throws Exception {
         if (!sCpuFreqTimesAvailable) {
+            Log.w(TAG, "Skipping " + testName.getMethodName()
+                    + "; freqTimesAvailable=" + sCpuFreqTimesAvailable
+                    + ", procStateTimesAvailable=" + sPerProcStateTimesAvailable);
             return;
         }
 
@@ -204,6 +224,9 @@
     @Test
     public void testCpuFreqTimes_stateTop() throws Exception {
         if (!sCpuFreqTimesAvailable || !sPerProcStateTimesAvailable) {
+            Log.w(TAG, "Skipping " + testName.getMethodName()
+                    + "; freqTimesAvailable=" + sCpuFreqTimesAvailable
+                    + ", procStateTimesAvailable=" + sPerProcStateTimesAvailable);
             return;
         }
 
@@ -234,6 +257,9 @@
     @Test
     public void testIsolatedCpuFreqTimes_stateService() throws Exception {
         if (!sCpuFreqTimesAvailable || !sPerProcStateTimesAvailable) {
+            Log.w(TAG, "Skipping " + testName.getMethodName()
+                    + "; freqTimesAvailable=" + sCpuFreqTimesAvailable
+                    + ", procStateTimesAvailable=" + sPerProcStateTimesAvailable);
             return;
         }
 
@@ -272,6 +298,9 @@
     @Test
     public void testCpuFreqTimes_stateTopSleeping() throws Exception {
         if (!sCpuFreqTimesAvailable || !sPerProcStateTimesAvailable) {
+            Log.w(TAG, "Skipping " + testName.getMethodName()
+                    + "; freqTimesAvailable=" + sCpuFreqTimesAvailable
+                    + ", procStateTimesAvailable=" + sPerProcStateTimesAvailable);
             return;
         }
 
@@ -302,6 +331,9 @@
     @Test
     public void testCpuFreqTimes_stateFgService() throws Exception {
         if (!sCpuFreqTimesAvailable || !sPerProcStateTimesAvailable) {
+            Log.w(TAG, "Skipping " + testName.getMethodName()
+                    + "; freqTimesAvailable=" + sCpuFreqTimesAvailable
+                    + ", procStateTimesAvailable=" + sPerProcStateTimesAvailable);
             return;
         }
 
@@ -332,6 +364,9 @@
     @Test
     public void testCpuFreqTimes_stateFg() throws Exception {
         if (!sCpuFreqTimesAvailable || !sPerProcStateTimesAvailable) {
+            Log.w(TAG, "Skipping " + testName.getMethodName()
+                    + "; freqTimesAvailable=" + sCpuFreqTimesAvailable
+                    + ", procStateTimesAvailable=" + sPerProcStateTimesAvailable);
             return;
         }
 
@@ -362,6 +397,9 @@
     @Test
     public void testCpuFreqTimes_stateBg() throws Exception {
         if (!sCpuFreqTimesAvailable || !sPerProcStateTimesAvailable) {
+            Log.w(TAG, "Skipping " + testName.getMethodName()
+                    + "; freqTimesAvailable=" + sCpuFreqTimesAvailable
+                    + ", procStateTimesAvailable=" + sPerProcStateTimesAvailable);
             return;
         }
 
@@ -392,6 +430,9 @@
     @Test
     public void testCpuFreqTimes_stateCached() throws Exception {
         if (!sCpuFreqTimesAvailable || !sPerProcStateTimesAvailable) {
+            Log.w(TAG, "Skipping " + testName.getMethodName()
+                    + "; freqTimesAvailable=" + sCpuFreqTimesAvailable
+                    + ", procStateTimesAvailable=" + sPerProcStateTimesAvailable);
             return;
         }
 
@@ -419,6 +460,124 @@
         batteryOffScreenOn();
     }
 
+    @Test
+    public void testCpuFreqTimes_trackingDisabled() throws Exception {
+        if (!sCpuFreqTimesAvailable || !sPerProcStateTimesAvailable) {
+            Log.w(TAG, "Skipping " + testName.getMethodName()
+                    + "; freqTimesAvailable=" + sCpuFreqTimesAvailable
+                    + ", procStateTimesAvailable=" + sPerProcStateTimesAvailable);
+            return;
+        }
+
+        final String bstatsConstants = Settings.Global.getString(sContext.getContentResolver(),
+                Settings.Global.BATTERY_STATS_CONSTANTS);
+        try {
+            batteryOnScreenOn();
+            forceStop();
+            resetBatteryStats();
+            final long[] initialSnapshot = getAllCpuFreqTimes(sTestPkgUid);
+            assertNull("Initial snapshot should be null, initial="
+                    + Arrays.toString(initialSnapshot), initialSnapshot);
+            assertNull("Initial top state snapshot should be null",
+                    getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_TOP));
+
+            doSomeWork(PROCESS_STATE_TOP);
+            forceStop();
+
+            final long[] cpuTimesMs = getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_TOP);
+            final String msgCpuTimes = getAllCpuTimesMsg();
+            assertCpuTimesValid(cpuTimesMs);
+            long actualCpuTimeMs = 0;
+            for (int i = 0; i < cpuTimesMs.length / 2; ++i) {
+                actualCpuTimeMs += cpuTimesMs[i];
+            }
+            assertApproximateValue("Incorrect total cpu time, " + msgCpuTimes,
+                    WORK_DURATION_MS, actualCpuTimeMs);
+
+            updateTrackPerProcStateCpuTimesSetting(bstatsConstants, false);
+
+            doSomeWork(PROCESS_STATE_TOP);
+            forceStop();
+
+            final long[] cpuTimesMs2 = getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_TOP);
+            assertCpuTimesValid(cpuTimesMs2);
+            assertCpuTimesEqual(cpuTimesMs2, cpuTimesMs, 20,
+                    "Unexpected cpu times with tracking off");
+
+            updateTrackPerProcStateCpuTimesSetting(bstatsConstants, true);
+
+            final long[] cpuTimesMs3 = getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_TOP);
+            assertCpuTimesValid(cpuTimesMs3);
+            assertCpuTimesEqual(cpuTimesMs3, cpuTimesMs, 20,
+                    "Unexpected cpu times after turning on tracking");
+
+            doSomeWork(PROCESS_STATE_TOP);
+            forceStop();
+
+            final long[] cpuTimesMs4 = getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_TOP);
+            assertCpuTimesValid(cpuTimesMs4);
+            actualCpuTimeMs = 0;
+            for (int i = 0; i < cpuTimesMs.length / 2; ++i) {
+                actualCpuTimeMs += cpuTimesMs[i];
+            }
+            assertApproximateValue("Incorrect total cpu time, " + msgCpuTimes,
+                    2 * WORK_DURATION_MS, actualCpuTimeMs);
+
+            batteryOffScreenOn();
+        } finally {
+            Settings.Global.putString(sContext.getContentResolver(),
+                    Settings.Global.BATTERY_STATS_CONSTANTS, bstatsConstants);
+        }
+    }
+
+    private void assertCpuTimesEqual(long[] actual, long[] expected, long delta, String errMsg) {
+        for (int i = actual.length - 1; i >= 0; --i) {
+            if (actual[i] > expected[i] + delta || actual[i] < expected[i]) {
+                fail(errMsg + ", actual=" + actual + ", expected=" + expected + ", delta=" + delta);
+            }
+        }
+    }
+
+    private void updateTrackPerProcStateCpuTimesSetting(String originalConstants, boolean enabled)
+            throws Exception {
+        final String newConstants;
+        final String setting = KEY_TRACK_CPU_TIMES_BY_PROC_STATE + "=" + enabled;
+        if (originalConstants == null || "null".equals(originalConstants)) {
+            newConstants = setting;
+        } else if (originalConstants.contains(KEY_TRACK_CPU_TIMES_BY_PROC_STATE)) {
+            newConstants = originalConstants.replaceAll(
+                    KEY_TRACK_CPU_TIMES_BY_PROC_STATE + "=(true|false)", setting);
+        } else {
+            newConstants = originalConstants + "," + setting;
+        }
+        Settings.Global.putString(sContext.getContentResolver(),
+                Settings.Global.BATTERY_STATS_CONSTANTS, newConstants);
+        assertTrackPerProcStateCpuTimesSetting(enabled);
+    }
+
+    private void assertTrackPerProcStateCpuTimesSetting(boolean enabled) throws Exception {
+        final String expectedValue = Boolean.toString(enabled);
+        assertDelayedCondition("Unexpected value for " + KEY_TRACK_CPU_TIMES_BY_PROC_STATE, () -> {
+            final String actualValue = getSettingValueFromDump(KEY_TRACK_CPU_TIMES_BY_PROC_STATE);
+            return expectedValue.equals(actualValue)
+                    ? null : "expected=" + expectedValue + ", actual=" + actualValue;
+        }, SETTING_UPDATE_TIMEOUT_MS, SETTING_UPDATE_CHECK_INTERVAL_MS);
+    }
+
+    private String getSettingValueFromDump(String key) throws Exception {
+        final String settingsDump = executeCmdSilent("dumpsys batterystats --settings");
+        final TextUtils.SimpleStringSplitter splitter = new TextUtils.SimpleStringSplitter('\n');
+        splitter.setString(settingsDump);
+        String next;
+        while (splitter.hasNext()) {
+            next = splitter.next();
+            if (next.startsWith(key)) {
+                return next.split("=")[1];
+            }
+        }
+        return null;
+    }
+
     private void assertCpuTimesValid(long[] cpuTimes) {
         assertNotNull(cpuTimes);
         for (int i = 0; i < cpuTimes.length; ++i) {
@@ -750,13 +909,18 @@
     }
 
     private void assertDelayedCondition(String errMsgPrefix, ExpectedCondition condition)
-            throws Exception {
-        final long endTime = SystemClock.uptimeMillis() + GENERAL_TIMEOUT_MS;
+        throws Exception {
+        assertDelayedCondition(errMsgPrefix, condition, GENERAL_TIMEOUT_MS, GENERAL_INTERVAL_MS);
+    }
+
+    private void assertDelayedCondition(String errMsgPrefix, ExpectedCondition condition,
+            long timeoutMs, long checkIntervalMs) throws Exception {
+        final long endTime = SystemClock.uptimeMillis() + timeoutMs;
         while (SystemClock.uptimeMillis() <= endTime) {
             if (condition.getErrIfNotTrue() == null) {
                 return;
             }
-            SystemClock.sleep(GENERAL_INTERVAL_MS);
+            SystemClock.sleep(checkIntervalMs);
         }
         final String errMsg = condition.getErrIfNotTrue();
         if (errMsg != null) {
diff --git a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
index 43b41a0..6c5a2aa 100644
--- a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
+++ b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
@@ -139,6 +139,11 @@
         }
 
         @Override
+        public Future<?> scheduleCpuSyncDueToSettingChange() {
+            return null;
+        }
+
+        @Override
         public Future<?> scheduleReadProcStateCpuTimes(
                 boolean onBattery, boolean onBatteryScreenOff) {
             return null;
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 81e34df..2da1c2b 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -2695,6 +2695,13 @@
         }
 
         @Override
+        public void onBootPhase(int phase) {
+            if (phase == PHASE_SYSTEM_SERVICES_READY) {
+                mService.mBatteryStatsService.systemServicesReady();
+            }
+        }
+
+        @Override
         public void onCleanupUser(int userId) {
             mService.mBatteryStatsService.onCleanupUser(userId);
         }
diff --git a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
index 28b8edf..1fcaeef 100644
--- a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
+++ b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
@@ -118,9 +118,14 @@
     }
 
     @Override
+    public synchronized Future<?> scheduleCpuSyncDueToSettingChange() {
+        return scheduleSyncLocked("setting-change", UPDATE_CPU);
+    }
+
+    @Override
     public Future<?> scheduleReadProcStateCpuTimes(boolean onBattery, boolean onBatteryScreenOff) {
         synchronized (mStats) {
-            if (!mStats.mPerProcStateCpuTimesAvailable) {
+            if (!mStats.trackPerProcStateCpuTimes()) {
                 return null;
             }
         }
@@ -138,7 +143,7 @@
     public Future<?> scheduleCopyFromAllUidsCpuTimes(
             boolean onBattery, boolean onBatteryScreenOff) {
         synchronized (mStats) {
-            if (!mStats.mPerProcStateCpuTimesAvailable) {
+            if (!mStats.trackPerProcStateCpuTimes()) {
                 return null;
             }
         }
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 813e617..c9aa9a2 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -185,6 +185,10 @@
         ServiceManager.addService(BatteryStats.SERVICE_NAME, asBinder());
     }
 
+    public void systemServicesReady() {
+        mStats.systemServicesReady(mContext);
+    }
+
     private final class LocalService extends BatteryStatsInternal {
         @Override
         public String[] getWifiIfaces() {
@@ -1185,6 +1189,7 @@
         pw.println("  --write: force write current collected stats to disk.");
         pw.println("  --new-daily: immediately create and write new daily stats record.");
         pw.println("  --read-daily: read-load last written daily stats.");
+        pw.println("  --settings: dump the settings key/values related to batterystats");
         pw.println("  <package.name>: optional name of package to filter output by.");
         pw.println("  -h: print this help text.");
         pw.println("Battery stats (batterystats) commands:");
@@ -1197,6 +1202,12 @@
         pw.println("      pretend-screen-off: pretend the screen is off, even if screen state changes");
     }
 
+    private void dumpSettings(PrintWriter pw) {
+        synchronized (mStats) {
+            mStats.dumpConstantsLocked(pw);
+        }
+    }
+
     private int doEnableOrDisable(PrintWriter pw, int i, String[] args, boolean enable) {
         i++;
         if (i >= args.length) {
@@ -1307,6 +1318,9 @@
                 } else if ("-h".equals(arg)) {
                     dumpHelp(pw);
                     return;
+                } else if ("--settings".equals(arg)) {
+                    dumpSettings(pw);
+                    return;
                 } else if ("-a".equals(arg)) {
                     flags |= BatteryStats.DUMP_VERBOSE;
                 } else if (arg.length() > 0 && arg.charAt(0) == '-'){