Loading services/core/java/com/android/server/power/BatterySaverPolicy.java +20 −33 Original line number Diff line number Diff line Loading @@ -32,6 +32,7 @@ import android.util.Slog; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.server.power.batterysaver.CpuFrequencies; import java.io.PrintWriter; import java.util.ArrayList; Loading @@ -40,15 +41,14 @@ import java.util.List; /** * Class to decide whether to turn on battery saver mode for specific service * * TODO: We should probably make {@link #mFilesForInteractive} and {@link #mFilesForNoninteractive} * less flexible and just take a list of "CPU number - frequency" pairs. Being able to write * anything under /sys/ and /proc/ is too loose. * * Test: atest BatterySaverPolicyTest * Test: atest ${ANDROID_BUILD_TOP}/frameworks/base/services/tests/servicestests/src/com/android/server/power/BatterySaverPolicyTest.java */ public class BatterySaverPolicy extends ContentObserver { private static final String TAG = "BatterySaverPolicy"; public static final boolean DEBUG = false; // DO NOT SUBMIT WITH TRUE. // Value of batterySaverGpsMode such that GPS isn't affected by battery saver mode. public static final int GPS_MODE_NO_CHANGE = 0; // Value of batterySaverGpsMode such that GPS is disabled when battery saver mode Loading @@ -70,8 +70,8 @@ public class BatterySaverPolicy extends ContentObserver { private static final String KEY_FORCE_ALL_APPS_STANDBY = "force_all_apps_standby"; private static final String KEY_OPTIONAL_SENSORS_DISABLED = "optional_sensors_disabled"; private static final String KEY_FILE_FOR_INTERACTIVE_PREFIX = "file-on:"; private static final String KEY_FILE_FOR_NONINTERACTIVE_PREFIX = "file-off:"; private static final String KEY_CPU_FREQ_INTERACTIVE = "cpufreq-i"; private static final String KEY_CPU_FREQ_NONINTERACTIVE = "cpufreq-n"; private static String mSettings; private static String mDeviceSpecificSettings; Loading Loading @@ -273,6 +273,11 @@ public class BatterySaverPolicy extends ContentObserver { mSettings = setting; mDeviceSpecificSettings = deviceSpecificSetting; if (DEBUG) { Slog.i(TAG, "mSettings=" + mSettings); Slog.i(TAG, "mDeviceSpecificSettings=" + mDeviceSpecificSettings); } final KeyValueListParser parser = new KeyValueListParser(','); // Non-device-specific parameters. Loading Loading @@ -307,29 +312,11 @@ public class BatterySaverPolicy extends ContentObserver { + deviceSpecificSetting); } mFilesForInteractive = collectParams(parser, KEY_FILE_FOR_INTERACTIVE_PREFIX); mFilesForNoninteractive = collectParams(parser, KEY_FILE_FOR_NONINTERACTIVE_PREFIX); } private static ArrayMap<String, String> collectParams( KeyValueListParser parser, String prefix) { final ArrayMap<String, String> ret = new ArrayMap<>(); mFilesForInteractive = (new CpuFrequencies()).parseString( parser.getString(KEY_CPU_FREQ_INTERACTIVE, "")).toSysFileMap(); for (int i = parser.size() - 1; i >= 0; i--) { final String key = parser.keyAt(i); if (!key.startsWith(prefix)) { continue; } final String path = key.substring(prefix.length()); if (!(path.startsWith("/sys/") || path.startsWith("/proc/"))) { Slog.wtf(TAG, "Invalid path: " + path); continue; } ret.put(path, parser.getString(key, "")); } return ret; mFilesForNoninteractive = (new CpuFrequencies()).parseString( parser.getString(KEY_CPU_FREQ_NONINTERACTIVE, "")).toSysFileMap(); } /** Loading Loading @@ -399,9 +386,9 @@ public class BatterySaverPolicy extends ContentObserver { synchronized (mLock) { pw.println(); pw.println("Battery saver policy"); pw.println(" Settings " + Settings.Global.BATTERY_SAVER_CONSTANTS); pw.println(" Settings: " + Settings.Global.BATTERY_SAVER_CONSTANTS); pw.println(" value: " + mSettings); pw.println(" Settings " + mDeviceSpecificSettingsSource); pw.println(" Settings: " + mDeviceSpecificSettingsSource); pw.println(" value: " + mDeviceSpecificSettings); pw.println(); Loading services/core/java/com/android/server/power/batterysaver/BatterySaverController.java +1 −1 Original line number Diff line number Diff line Loading @@ -49,7 +49,7 @@ import java.util.ArrayList; public class BatterySaverController implements BatterySaverPolicyListener { static final String TAG = "BatterySaverController"; static final boolean DEBUG = false; // DO NOT MERGE WITH TRUE static final boolean DEBUG = BatterySaverPolicy.DEBUG; private final Object mLock = new Object(); private final Context mContext; Loading services/core/java/com/android/server/power/batterysaver/CpuFrequencies.java 0 → 100644 +101 −0 Original line number Diff line number Diff line /* * Copyright (C) 2017 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.power.batterysaver; import android.util.ArrayMap; import android.util.Slog; import com.android.internal.annotations.GuardedBy; import java.util.Map; /** * Helper to parse a list of "core-number:frequency" pairs concatenated with / as a separator, * and convert them into a map of "filename -> value" that should be written to * /sys/.../scaling_max_freq. * * Example input: "0:1900800/4:2500000", which will be converted into: * "/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq" "1900800" * "/sys/devices/system/cpu/cpu4/cpufreq/scaling_max_freq" "2500000" * * Test: atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/power/batterysaver/CpuFrequenciesTest.java */ public class CpuFrequencies { private static final String TAG = "CpuFrequencies"; private final Object mLock = new Object(); @GuardedBy("mLock") private final ArrayMap<Integer, Long> mCoreAndFrequencies = new ArrayMap<>(); public CpuFrequencies() { } /** * Parse a string. */ public CpuFrequencies parseString(String cpuNumberAndFrequencies) { synchronized (mLock) { mCoreAndFrequencies.clear(); try { for (String pair : cpuNumberAndFrequencies.split("/")) { final String[] coreAndFreq = pair.split(":", 2); if (coreAndFreq.length != 2) { throw new IllegalArgumentException("Wrong format"); } final int core = Integer.parseInt(coreAndFreq[0]); final long freq = Long.parseLong(coreAndFreq[1]); mCoreAndFrequencies.put(core, freq); } } catch (IllegalArgumentException e) { Slog.wtf(TAG, "Invalid configuration: " + cpuNumberAndFrequencies, e); } } return this; } /** * Return a new map containing the filename-value pairs. */ public ArrayMap<String, String> toSysFileMap() { final ArrayMap<String, String> map = new ArrayMap<>(); addToSysFileMap(map); return map; } /** * Add the filename-value pairs to an existing map. */ public void addToSysFileMap(Map<String, String> map) { synchronized (mLock) { final int size = mCoreAndFrequencies.size(); for (int i = 0; i < size; i++) { final int core = mCoreAndFrequencies.keyAt(i); final long freq = mCoreAndFrequencies.valueAt(i); final String file = "/sys/devices/system/cpu/cpu" + Integer.toString(core) + "/cpufreq/scaling_max_freq"; map.put(file, Long.toString(freq)); } } } } services/core/java/com/android/server/power/batterysaver/FileUpdater.java +227 −8 Original line number Diff line number Diff line Loading @@ -16,40 +16,259 @@ package com.android.server.power.batterysaver; import android.content.Context; import android.os.Handler; import android.os.Looper; import android.util.ArrayMap; import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.server.IoThread; import libcore.io.IoUtils; import java.io.FileWriter; import java.io.IOException; import java.util.ArrayList; import java.util.Map; /** * Used by {@link BatterySaverController} to write values to /sys/ (and possibly /proc/ too) files * with retry and to restore the original values. * with retries. It also support restoring to the file original values. * * TODO Implement it * Retries are needed because writing to "/sys/.../scaling_max_freq" returns EIO when the current * frequency happens to be above the new max frequency. * * Test: atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/power/batterysaver/FileUpdaterTest.java */ public class FileUpdater { private static final String TAG = BatterySaverController.TAG; private static final boolean DEBUG = BatterySaverController.DEBUG; // Don't do disk access with this lock held. private final Object mLock = new Object(); private final Context mContext; private final Handler mHandler; /** * Filename -> value map that holds pending writes. */ @GuardedBy("mLock") private final ArrayMap<String, String> mPendingWrites = new ArrayMap<>(); /** * Filename -> value that holds the original value of each file. */ @GuardedBy("mLock") private final ArrayMap<String, String> mDefaultValues = new ArrayMap<>(); /** Number of retries. We give up on writing after {@link #MAX_RETRIES} retries. */ @GuardedBy("mLock") private int mRetries = 0; private final int MAX_RETRIES; private final long RETRY_INTERVAL_MS; /** * "Official" constructor. Don't use the other constructor in the production code. */ public FileUpdater(Context context) { this(context, IoThread.get().getLooper(), 10, 5000); } /** * Constructor for test. */ @VisibleForTesting FileUpdater(Context context, Looper looper, int maxRetries, int retryIntervalMs) { mContext = context; mHandler = new Handler(looper); MAX_RETRIES = maxRetries; RETRY_INTERVAL_MS = retryIntervalMs; } /** * Write values to files. (Note the actual writes happen ASAP but asynchronously.) */ public void writeFiles(ArrayMap<String, String> fileValues) { synchronized (mLock) { for (int i = fileValues.size() - 1; i >= 0; i--) { final String file = fileValues.keyAt(i); final String value = fileValues.valueAt(i); if (DEBUG) { final int size = fileValues.size(); for (int i = 0; i < size; i++) { Slog.d(TAG, "Writing '" + fileValues.valueAt(i) + "' to '" + fileValues.keyAt(i) + "'"); Slog.d(TAG, "Scheduling write: '" + value + "' to '" + file + "'"); } mPendingWrites.put(file, value); } mRetries = 0; mHandler.removeCallbacks(mHandleWriteOnHandlerRunnable); mHandler.post(mHandleWriteOnHandlerRunnable); } } /** * Restore the default values. */ public void restoreDefault() { synchronized (mLock) { if (DEBUG) { Slog.d(TAG, "Resetting file default values."); } mPendingWrites.clear(); writeFiles(mDefaultValues); } } private Runnable mHandleWriteOnHandlerRunnable = () -> handleWriteOnHandler(); /** Convert map keys into a single string for debug messages. */ private String getKeysString(Map<String, String> source) { return new ArrayList<>(source.keySet()).toString(); } /** Clone an ArrayMap. */ private ArrayMap<String, String> cloneMap(ArrayMap<String, String> source) { return new ArrayMap<>(source); } /** * Called on the handler and writes {@link #mPendingWrites} to the disk. * * When it about to write to each file for the first time, it'll read the file and store * the original value in {@link #mDefaultValues}. */ private void handleWriteOnHandler() { // We don't want to access the disk with the lock held, so copy the pending writes to // a local map. final ArrayMap<String, String> writes; synchronized (mLock) { if (mPendingWrites.size() == 0) { return; } if (DEBUG) { Slog.d(TAG, "Writing files: (# retries=" + mRetries + ") " + getKeysString(mPendingWrites)); } writes = cloneMap(mPendingWrites); } // Then write. boolean needRetry = false; final int size = writes.size(); for (int i = 0; i < size; i++) { final String file = writes.keyAt(i); final String value = writes.valueAt(i); // Make sure the default value is loaded. if (!ensureDefaultLoaded(file)) { continue; } // Write to the file. When succeeded, remove it from the pending list. // Otherwise, schedule a retry. try { injectWriteToFile(file, value); removePendingWrite(file); } catch (IOException e) { needRetry = true; } } if (needRetry) { scheduleRetry(); } } private void removePendingWrite(String file) { synchronized (mLock) { mPendingWrites.remove(file); } } private void scheduleRetry() { synchronized (mLock) { if (mPendingWrites.size() == 0) { return; // Shouldn't happen but just in case. } mRetries++; if (mRetries > MAX_RETRIES) { doWtf("Gave up writing files: " + getKeysString(mPendingWrites)); return; } mHandler.removeCallbacks(mHandleWriteOnHandlerRunnable); mHandler.postDelayed(mHandleWriteOnHandlerRunnable, RETRY_INTERVAL_MS); } } /** * Make sure {@link #mDefaultValues} has the default value loaded for {@code file}. * * @return true if the default value is loaded. false if the file cannot be read. */ private boolean ensureDefaultLoaded(String file) { // Has the default already? synchronized (mLock) { if (mDefaultValues.containsKey(file)) { return true; } } final String originalValue; try { originalValue = injectReadFromFileTrimmed(file); } catch (IOException e) { // If the file is not readable, assume can't write too. injectWtf("Unable to read from file", e); removePendingWrite(file); return false; } synchronized (mLock) { mDefaultValues.put(file, originalValue); } return true; } @VisibleForTesting String injectReadFromFileTrimmed(String file) throws IOException { return IoUtils.readFileAsString(file).trim(); } @VisibleForTesting void injectWriteToFile(String file, String value) throws IOException { if (DEBUG) { Slog.d(TAG, "Resetting file default values"); Slog.d(TAG, "Writing: '" + value + "' to '" + file + "'"); } try (FileWriter out = new FileWriter(file)) { out.write(value); } catch (IOException e) { Slog.w(TAG, "Failed writing '" + value + "' to '" + file + "': " + e.getMessage()); throw e; } } private void doWtf(String message) { injectWtf(message, null); } @VisibleForTesting void injectWtf(String message, Throwable e) { Slog.wtf(TAG, message, e); } } services/tests/servicestests/res/values/strings.xml +2 −2 Original line number Diff line number Diff line Loading @@ -30,6 +30,6 @@ <string name="test_account_type2">com.android.server.accounts.account_manager_service_test.account.type2</string> <string name="config_batterySaverDeviceSpecificConfig_1"></string> <string name="config_batterySaverDeviceSpecificConfig_2">file-off:/sys/a=1,file-off:/sys/b=2</string> <string name="config_batterySaverDeviceSpecificConfig_3">file-off:/sys/a=3,file-on:/proc/c=4,/abc=3</string> <string name="config_batterySaverDeviceSpecificConfig_2">cpufreq-n=1:123/2:456</string> <string name="config_batterySaverDeviceSpecificConfig_3">cpufreq-n=2:222,cpufreq-i=3:333/4:444</string> </resources> Loading
services/core/java/com/android/server/power/BatterySaverPolicy.java +20 −33 Original line number Diff line number Diff line Loading @@ -32,6 +32,7 @@ import android.util.Slog; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.server.power.batterysaver.CpuFrequencies; import java.io.PrintWriter; import java.util.ArrayList; Loading @@ -40,15 +41,14 @@ import java.util.List; /** * Class to decide whether to turn on battery saver mode for specific service * * TODO: We should probably make {@link #mFilesForInteractive} and {@link #mFilesForNoninteractive} * less flexible and just take a list of "CPU number - frequency" pairs. Being able to write * anything under /sys/ and /proc/ is too loose. * * Test: atest BatterySaverPolicyTest * Test: atest ${ANDROID_BUILD_TOP}/frameworks/base/services/tests/servicestests/src/com/android/server/power/BatterySaverPolicyTest.java */ public class BatterySaverPolicy extends ContentObserver { private static final String TAG = "BatterySaverPolicy"; public static final boolean DEBUG = false; // DO NOT SUBMIT WITH TRUE. // Value of batterySaverGpsMode such that GPS isn't affected by battery saver mode. public static final int GPS_MODE_NO_CHANGE = 0; // Value of batterySaverGpsMode such that GPS is disabled when battery saver mode Loading @@ -70,8 +70,8 @@ public class BatterySaverPolicy extends ContentObserver { private static final String KEY_FORCE_ALL_APPS_STANDBY = "force_all_apps_standby"; private static final String KEY_OPTIONAL_SENSORS_DISABLED = "optional_sensors_disabled"; private static final String KEY_FILE_FOR_INTERACTIVE_PREFIX = "file-on:"; private static final String KEY_FILE_FOR_NONINTERACTIVE_PREFIX = "file-off:"; private static final String KEY_CPU_FREQ_INTERACTIVE = "cpufreq-i"; private static final String KEY_CPU_FREQ_NONINTERACTIVE = "cpufreq-n"; private static String mSettings; private static String mDeviceSpecificSettings; Loading Loading @@ -273,6 +273,11 @@ public class BatterySaverPolicy extends ContentObserver { mSettings = setting; mDeviceSpecificSettings = deviceSpecificSetting; if (DEBUG) { Slog.i(TAG, "mSettings=" + mSettings); Slog.i(TAG, "mDeviceSpecificSettings=" + mDeviceSpecificSettings); } final KeyValueListParser parser = new KeyValueListParser(','); // Non-device-specific parameters. Loading Loading @@ -307,29 +312,11 @@ public class BatterySaverPolicy extends ContentObserver { + deviceSpecificSetting); } mFilesForInteractive = collectParams(parser, KEY_FILE_FOR_INTERACTIVE_PREFIX); mFilesForNoninteractive = collectParams(parser, KEY_FILE_FOR_NONINTERACTIVE_PREFIX); } private static ArrayMap<String, String> collectParams( KeyValueListParser parser, String prefix) { final ArrayMap<String, String> ret = new ArrayMap<>(); mFilesForInteractive = (new CpuFrequencies()).parseString( parser.getString(KEY_CPU_FREQ_INTERACTIVE, "")).toSysFileMap(); for (int i = parser.size() - 1; i >= 0; i--) { final String key = parser.keyAt(i); if (!key.startsWith(prefix)) { continue; } final String path = key.substring(prefix.length()); if (!(path.startsWith("/sys/") || path.startsWith("/proc/"))) { Slog.wtf(TAG, "Invalid path: " + path); continue; } ret.put(path, parser.getString(key, "")); } return ret; mFilesForNoninteractive = (new CpuFrequencies()).parseString( parser.getString(KEY_CPU_FREQ_NONINTERACTIVE, "")).toSysFileMap(); } /** Loading Loading @@ -399,9 +386,9 @@ public class BatterySaverPolicy extends ContentObserver { synchronized (mLock) { pw.println(); pw.println("Battery saver policy"); pw.println(" Settings " + Settings.Global.BATTERY_SAVER_CONSTANTS); pw.println(" Settings: " + Settings.Global.BATTERY_SAVER_CONSTANTS); pw.println(" value: " + mSettings); pw.println(" Settings " + mDeviceSpecificSettingsSource); pw.println(" Settings: " + mDeviceSpecificSettingsSource); pw.println(" value: " + mDeviceSpecificSettings); pw.println(); Loading
services/core/java/com/android/server/power/batterysaver/BatterySaverController.java +1 −1 Original line number Diff line number Diff line Loading @@ -49,7 +49,7 @@ import java.util.ArrayList; public class BatterySaverController implements BatterySaverPolicyListener { static final String TAG = "BatterySaverController"; static final boolean DEBUG = false; // DO NOT MERGE WITH TRUE static final boolean DEBUG = BatterySaverPolicy.DEBUG; private final Object mLock = new Object(); private final Context mContext; Loading
services/core/java/com/android/server/power/batterysaver/CpuFrequencies.java 0 → 100644 +101 −0 Original line number Diff line number Diff line /* * Copyright (C) 2017 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.power.batterysaver; import android.util.ArrayMap; import android.util.Slog; import com.android.internal.annotations.GuardedBy; import java.util.Map; /** * Helper to parse a list of "core-number:frequency" pairs concatenated with / as a separator, * and convert them into a map of "filename -> value" that should be written to * /sys/.../scaling_max_freq. * * Example input: "0:1900800/4:2500000", which will be converted into: * "/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq" "1900800" * "/sys/devices/system/cpu/cpu4/cpufreq/scaling_max_freq" "2500000" * * Test: atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/power/batterysaver/CpuFrequenciesTest.java */ public class CpuFrequencies { private static final String TAG = "CpuFrequencies"; private final Object mLock = new Object(); @GuardedBy("mLock") private final ArrayMap<Integer, Long> mCoreAndFrequencies = new ArrayMap<>(); public CpuFrequencies() { } /** * Parse a string. */ public CpuFrequencies parseString(String cpuNumberAndFrequencies) { synchronized (mLock) { mCoreAndFrequencies.clear(); try { for (String pair : cpuNumberAndFrequencies.split("/")) { final String[] coreAndFreq = pair.split(":", 2); if (coreAndFreq.length != 2) { throw new IllegalArgumentException("Wrong format"); } final int core = Integer.parseInt(coreAndFreq[0]); final long freq = Long.parseLong(coreAndFreq[1]); mCoreAndFrequencies.put(core, freq); } } catch (IllegalArgumentException e) { Slog.wtf(TAG, "Invalid configuration: " + cpuNumberAndFrequencies, e); } } return this; } /** * Return a new map containing the filename-value pairs. */ public ArrayMap<String, String> toSysFileMap() { final ArrayMap<String, String> map = new ArrayMap<>(); addToSysFileMap(map); return map; } /** * Add the filename-value pairs to an existing map. */ public void addToSysFileMap(Map<String, String> map) { synchronized (mLock) { final int size = mCoreAndFrequencies.size(); for (int i = 0; i < size; i++) { final int core = mCoreAndFrequencies.keyAt(i); final long freq = mCoreAndFrequencies.valueAt(i); final String file = "/sys/devices/system/cpu/cpu" + Integer.toString(core) + "/cpufreq/scaling_max_freq"; map.put(file, Long.toString(freq)); } } } }
services/core/java/com/android/server/power/batterysaver/FileUpdater.java +227 −8 Original line number Diff line number Diff line Loading @@ -16,40 +16,259 @@ package com.android.server.power.batterysaver; import android.content.Context; import android.os.Handler; import android.os.Looper; import android.util.ArrayMap; import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.server.IoThread; import libcore.io.IoUtils; import java.io.FileWriter; import java.io.IOException; import java.util.ArrayList; import java.util.Map; /** * Used by {@link BatterySaverController} to write values to /sys/ (and possibly /proc/ too) files * with retry and to restore the original values. * with retries. It also support restoring to the file original values. * * TODO Implement it * Retries are needed because writing to "/sys/.../scaling_max_freq" returns EIO when the current * frequency happens to be above the new max frequency. * * Test: atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/power/batterysaver/FileUpdaterTest.java */ public class FileUpdater { private static final String TAG = BatterySaverController.TAG; private static final boolean DEBUG = BatterySaverController.DEBUG; // Don't do disk access with this lock held. private final Object mLock = new Object(); private final Context mContext; private final Handler mHandler; /** * Filename -> value map that holds pending writes. */ @GuardedBy("mLock") private final ArrayMap<String, String> mPendingWrites = new ArrayMap<>(); /** * Filename -> value that holds the original value of each file. */ @GuardedBy("mLock") private final ArrayMap<String, String> mDefaultValues = new ArrayMap<>(); /** Number of retries. We give up on writing after {@link #MAX_RETRIES} retries. */ @GuardedBy("mLock") private int mRetries = 0; private final int MAX_RETRIES; private final long RETRY_INTERVAL_MS; /** * "Official" constructor. Don't use the other constructor in the production code. */ public FileUpdater(Context context) { this(context, IoThread.get().getLooper(), 10, 5000); } /** * Constructor for test. */ @VisibleForTesting FileUpdater(Context context, Looper looper, int maxRetries, int retryIntervalMs) { mContext = context; mHandler = new Handler(looper); MAX_RETRIES = maxRetries; RETRY_INTERVAL_MS = retryIntervalMs; } /** * Write values to files. (Note the actual writes happen ASAP but asynchronously.) */ public void writeFiles(ArrayMap<String, String> fileValues) { synchronized (mLock) { for (int i = fileValues.size() - 1; i >= 0; i--) { final String file = fileValues.keyAt(i); final String value = fileValues.valueAt(i); if (DEBUG) { final int size = fileValues.size(); for (int i = 0; i < size; i++) { Slog.d(TAG, "Writing '" + fileValues.valueAt(i) + "' to '" + fileValues.keyAt(i) + "'"); Slog.d(TAG, "Scheduling write: '" + value + "' to '" + file + "'"); } mPendingWrites.put(file, value); } mRetries = 0; mHandler.removeCallbacks(mHandleWriteOnHandlerRunnable); mHandler.post(mHandleWriteOnHandlerRunnable); } } /** * Restore the default values. */ public void restoreDefault() { synchronized (mLock) { if (DEBUG) { Slog.d(TAG, "Resetting file default values."); } mPendingWrites.clear(); writeFiles(mDefaultValues); } } private Runnable mHandleWriteOnHandlerRunnable = () -> handleWriteOnHandler(); /** Convert map keys into a single string for debug messages. */ private String getKeysString(Map<String, String> source) { return new ArrayList<>(source.keySet()).toString(); } /** Clone an ArrayMap. */ private ArrayMap<String, String> cloneMap(ArrayMap<String, String> source) { return new ArrayMap<>(source); } /** * Called on the handler and writes {@link #mPendingWrites} to the disk. * * When it about to write to each file for the first time, it'll read the file and store * the original value in {@link #mDefaultValues}. */ private void handleWriteOnHandler() { // We don't want to access the disk with the lock held, so copy the pending writes to // a local map. final ArrayMap<String, String> writes; synchronized (mLock) { if (mPendingWrites.size() == 0) { return; } if (DEBUG) { Slog.d(TAG, "Writing files: (# retries=" + mRetries + ") " + getKeysString(mPendingWrites)); } writes = cloneMap(mPendingWrites); } // Then write. boolean needRetry = false; final int size = writes.size(); for (int i = 0; i < size; i++) { final String file = writes.keyAt(i); final String value = writes.valueAt(i); // Make sure the default value is loaded. if (!ensureDefaultLoaded(file)) { continue; } // Write to the file. When succeeded, remove it from the pending list. // Otherwise, schedule a retry. try { injectWriteToFile(file, value); removePendingWrite(file); } catch (IOException e) { needRetry = true; } } if (needRetry) { scheduleRetry(); } } private void removePendingWrite(String file) { synchronized (mLock) { mPendingWrites.remove(file); } } private void scheduleRetry() { synchronized (mLock) { if (mPendingWrites.size() == 0) { return; // Shouldn't happen but just in case. } mRetries++; if (mRetries > MAX_RETRIES) { doWtf("Gave up writing files: " + getKeysString(mPendingWrites)); return; } mHandler.removeCallbacks(mHandleWriteOnHandlerRunnable); mHandler.postDelayed(mHandleWriteOnHandlerRunnable, RETRY_INTERVAL_MS); } } /** * Make sure {@link #mDefaultValues} has the default value loaded for {@code file}. * * @return true if the default value is loaded. false if the file cannot be read. */ private boolean ensureDefaultLoaded(String file) { // Has the default already? synchronized (mLock) { if (mDefaultValues.containsKey(file)) { return true; } } final String originalValue; try { originalValue = injectReadFromFileTrimmed(file); } catch (IOException e) { // If the file is not readable, assume can't write too. injectWtf("Unable to read from file", e); removePendingWrite(file); return false; } synchronized (mLock) { mDefaultValues.put(file, originalValue); } return true; } @VisibleForTesting String injectReadFromFileTrimmed(String file) throws IOException { return IoUtils.readFileAsString(file).trim(); } @VisibleForTesting void injectWriteToFile(String file, String value) throws IOException { if (DEBUG) { Slog.d(TAG, "Resetting file default values"); Slog.d(TAG, "Writing: '" + value + "' to '" + file + "'"); } try (FileWriter out = new FileWriter(file)) { out.write(value); } catch (IOException e) { Slog.w(TAG, "Failed writing '" + value + "' to '" + file + "': " + e.getMessage()); throw e; } } private void doWtf(String message) { injectWtf(message, null); } @VisibleForTesting void injectWtf(String message, Throwable e) { Slog.wtf(TAG, message, e); } }
services/tests/servicestests/res/values/strings.xml +2 −2 Original line number Diff line number Diff line Loading @@ -30,6 +30,6 @@ <string name="test_account_type2">com.android.server.accounts.account_manager_service_test.account.type2</string> <string name="config_batterySaverDeviceSpecificConfig_1"></string> <string name="config_batterySaverDeviceSpecificConfig_2">file-off:/sys/a=1,file-off:/sys/b=2</string> <string name="config_batterySaverDeviceSpecificConfig_3">file-off:/sys/a=3,file-on:/proc/c=4,/abc=3</string> <string name="config_batterySaverDeviceSpecificConfig_2">cpufreq-n=1:123/2:456</string> <string name="config_batterySaverDeviceSpecificConfig_3">cpufreq-n=2:222,cpufreq-i=3:333/4:444</string> </resources>