Loading core/java/android/app/ActivityManagerInternal.java +5 −0 Original line number Diff line number Diff line Loading @@ -294,4 +294,9 @@ public abstract class ActivityManagerInternal { * @param userId The user it is allowed for. */ public abstract void setAllowAppSwitches(@NonNull String type, int uid, int userId); /** * @return true if runtime was restarted, false if it's normal boot */ public abstract boolean isRuntimeRestarted(); } services/core/java/com/android/server/am/ActivityManagerService.java +5 −0 Original line number Diff line number Diff line Loading @@ -24330,6 +24330,11 @@ public class ActivityManagerService extends IActivityManager.Stub } } } @Override public boolean isRuntimeRestarted() { return mSystemServiceManager.isRuntimeRestarted(); } } /** services/core/java/com/android/server/power/batterysaver/BatterySaverController.java +4 −0 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.server.power.batterysaver; import android.Manifest; import android.app.ActivityManagerInternal; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; Loading Loading @@ -109,6 +110,9 @@ public class BatterySaverController implements BatterySaverPolicyListener { final IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_ON); filter.addAction(Intent.ACTION_SCREEN_OFF); mContext.registerReceiver(mReceiver, filter); mFileUpdater.systemReady(LocalServices.getService(ActivityManagerInternal.class) .isRuntimeRestarted()); } private PowerManager getPowerManager() { Loading services/core/java/com/android/server/power/batterysaver/FileUpdater.java +131 −1 Original line number Diff line number Diff line Loading @@ -16,19 +16,34 @@ package com.android.server.power.batterysaver; import android.content.Context; import android.os.Environment; import android.os.Handler; import android.os.Looper; import android.os.SystemProperties; import android.util.ArrayMap; import android.util.AtomicFile; import android.util.Slog; import android.util.Xml; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.XmlUtils; import com.android.server.IoThread; import libcore.io.IoUtils; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.FileWriter; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Map; Loading @@ -47,6 +62,14 @@ public class FileUpdater { private static final boolean DEBUG = BatterySaverController.DEBUG; /** * If this system property is set to 1, it'll skip all file writes. This can be used when * one needs to change max CPU frequency for benchmarking, for example. */ private static final String PROP_SKIP_WRITE = "debug.batterysaver.no_write_files"; private static final String TAG_DEFAULT_ROOT = "defaults"; // Don't do disk access with this lock held. private final Object mLock = new Object(); Loading Loading @@ -93,6 +116,21 @@ public class FileUpdater { RETRY_INTERVAL_MS = retryIntervalMs; } public void systemReady(boolean runtimeRestarted) { synchronized (mLock) { if (runtimeRestarted) { // If it runtime restarted, read the original values from the disk and apply. if (loadDefaultValuesLocked()) { Slog.d(TAG, "Default values loaded after runtime restart; writing them..."); restoreDefault(); } } else { // Delete it, without checking the result. (file-not-exist is not an exception.) injectDefaultValuesFilename().delete(); } } } /** * Write values to files. (Note the actual writes happen ASAP but asynchronously.) */ Loading Loading @@ -241,6 +279,7 @@ public class FileUpdater { } synchronized (mLock) { mDefaultValues.put(file, originalValue); saveDefaultValuesLocked(); } return true; } Loading @@ -252,17 +291,92 @@ public class FileUpdater { @VisibleForTesting void injectWriteToFile(String file, String value) throws IOException { if (injectShouldSkipWrite()) { Slog.i(TAG, "Skipped writing to '" + file + "'"); return; } if (DEBUG) { Slog.d(TAG, "Writing: '" + value + "' to '" + file + "'"); } try (FileWriter out = new FileWriter(file)) { out.write(value); } catch (IOException e) { } catch (IOException | RuntimeException e) { Slog.w(TAG, "Failed writing '" + value + "' to '" + file + "': " + e.getMessage()); throw e; } } private void saveDefaultValuesLocked() { final AtomicFile file = new AtomicFile(injectDefaultValuesFilename()); FileOutputStream outs = null; try { file.getBaseFile().getParentFile().mkdirs(); outs = file.startWrite(); // Write to XML XmlSerializer out = new FastXmlSerializer(); out.setOutput(outs, StandardCharsets.UTF_8.name()); out.startDocument(null, true); out.startTag(null, TAG_DEFAULT_ROOT); XmlUtils.writeMapXml(mDefaultValues, out, null); // Epilogue. out.endTag(null, TAG_DEFAULT_ROOT); out.endDocument(); // Close. file.finishWrite(outs); } catch (IOException | XmlPullParserException | RuntimeException e) { Slog.e(TAG, "Failed to write to file " + file.getBaseFile(), e); file.failWrite(outs); } } @VisibleForTesting boolean loadDefaultValuesLocked() { final AtomicFile file = new AtomicFile(injectDefaultValuesFilename()); if (DEBUG) { Slog.d(TAG, "Loading from " + file.getBaseFile()); } Map<String, String> read = null; try (FileInputStream in = file.openRead()) { XmlPullParser parser = Xml.newPullParser(); parser.setInput(in, StandardCharsets.UTF_8.name()); int type; while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) { if (type != XmlPullParser.START_TAG) { continue; } final int depth = parser.getDepth(); // Check the root tag final String tag = parser.getName(); if (depth == 1) { if (!TAG_DEFAULT_ROOT.equals(tag)) { Slog.e(TAG, "Invalid root tag: " + tag); return false; } continue; } final String[] tagName = new String[1]; read = (ArrayMap<String, String>) XmlUtils.readThisArrayMapXml(parser, TAG_DEFAULT_ROOT, tagName, null); } } catch (FileNotFoundException e) { read = null; } catch (IOException | XmlPullParserException | RuntimeException e) { Slog.e(TAG, "Failed to read file " + file.getBaseFile(), e); } if (read != null) { mDefaultValues.clear(); mDefaultValues.putAll(read); return true; } return false; } private void doWtf(String message) { injectWtf(message, null); } Loading @@ -271,4 +385,20 @@ public class FileUpdater { void injectWtf(String message, Throwable e) { Slog.wtf(TAG, message, e); } File injectDefaultValuesFilename() { final File dir = new File(Environment.getDataSystemDirectory(), "battery-saver"); dir.mkdirs(); return new File(dir, "default-values.xml"); } @VisibleForTesting boolean injectShouldSkipWrite() { return SystemProperties.getBoolean(PROP_SKIP_WRITE, false); } @VisibleForTesting ArrayMap<String, String> getDefaultValuesForTest() { return mDefaultValues; } } services/tests/servicestests/src/com/android/server/power/batterysaver/FileUpdaterTest.java +68 −1 Original line number Diff line number Diff line Loading @@ -15,8 +15,9 @@ */ package com.android.server.power.batterysaver; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; Loading @@ -26,6 +27,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import android.content.Context; import android.hardware.camera2.impl.GetCommand; import android.os.Handler; import android.os.Looper; import android.support.test.InstrumentationRegistry; Loading @@ -40,6 +42,7 @@ import org.mockito.ArgumentMatchers; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.io.File; import java.io.IOException; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; Loading Loading @@ -72,6 +75,17 @@ public class FileUpdaterTest { void injectWtf(String message, Throwable e) { mInjector.injectWtf(message, e); } @Override File injectDefaultValuesFilename() { return new File(InstrumentationRegistry.getContext().getCacheDir() + "/test-default.xml"); } @Override boolean injectShouldSkipWrite() { return false; } } private interface Injector { Loading Loading @@ -334,4 +348,57 @@ public class FileUpdaterTest { reset(mInjector); testMultiWrites(); } @Test public void testWriteReadDefault() throws Exception { doReturn("111").when(mInjector).injectReadFromFileTrimmed("file1"); doReturn("222").when(mInjector).injectReadFromFileTrimmed("file2"); doReturn("333").when(mInjector).injectReadFromFileTrimmed("file3"); // Write final ArrayMap<String, String> values = new ArrayMap<>(); values.put("file1", "11"); values.put("file2", "22"); values.put("file3", "33"); mInstance.writeFiles(values); waitUntilMainHandlerDrain(); verify(mInjector, times(1)).injectWriteToFile("file1", "11"); verify(mInjector, times(1)).injectWriteToFile("file2", "22"); verify(mInjector, times(1)).injectWriteToFile("file3", "33"); // Clear and reload the default. assertEquals(3, mInstance.getDefaultValuesForTest().size()); mInstance.getDefaultValuesForTest().clear(); assertEquals(0, mInstance.getDefaultValuesForTest().size()); mInstance.systemReady(/*runtimeRestarted=*/ true); assertEquals(3, mInstance.getDefaultValuesForTest().size()); // Reset to default mInstance.restoreDefault(); waitUntilMainHandlerDrain(); verify(mInjector, times(1)).injectWriteToFile("file1", "111"); verify(mInjector, times(1)).injectWriteToFile("file2", "222"); verify(mInjector, times(1)).injectWriteToFile("file3", "333"); // Make sure the default file still exists. assertTrue(mInstance.injectDefaultValuesFilename().exists()); // Simulate a clean boot. mInstance.getDefaultValuesForTest().clear(); assertEquals(0, mInstance.getDefaultValuesForTest().size()); mInstance.systemReady(/*runtimeRestarted=*/ false); // Default is empty, and the file is gone. assertEquals(0, mInstance.getDefaultValuesForTest().size()); assertFalse(mInstance.injectDefaultValuesFilename().exists()); // No WTF should have happened. veriryWtf(0); } } Loading
core/java/android/app/ActivityManagerInternal.java +5 −0 Original line number Diff line number Diff line Loading @@ -294,4 +294,9 @@ public abstract class ActivityManagerInternal { * @param userId The user it is allowed for. */ public abstract void setAllowAppSwitches(@NonNull String type, int uid, int userId); /** * @return true if runtime was restarted, false if it's normal boot */ public abstract boolean isRuntimeRestarted(); }
services/core/java/com/android/server/am/ActivityManagerService.java +5 −0 Original line number Diff line number Diff line Loading @@ -24330,6 +24330,11 @@ public class ActivityManagerService extends IActivityManager.Stub } } } @Override public boolean isRuntimeRestarted() { return mSystemServiceManager.isRuntimeRestarted(); } } /**
services/core/java/com/android/server/power/batterysaver/BatterySaverController.java +4 −0 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.server.power.batterysaver; import android.Manifest; import android.app.ActivityManagerInternal; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; Loading Loading @@ -109,6 +110,9 @@ public class BatterySaverController implements BatterySaverPolicyListener { final IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_ON); filter.addAction(Intent.ACTION_SCREEN_OFF); mContext.registerReceiver(mReceiver, filter); mFileUpdater.systemReady(LocalServices.getService(ActivityManagerInternal.class) .isRuntimeRestarted()); } private PowerManager getPowerManager() { Loading
services/core/java/com/android/server/power/batterysaver/FileUpdater.java +131 −1 Original line number Diff line number Diff line Loading @@ -16,19 +16,34 @@ package com.android.server.power.batterysaver; import android.content.Context; import android.os.Environment; import android.os.Handler; import android.os.Looper; import android.os.SystemProperties; import android.util.ArrayMap; import android.util.AtomicFile; import android.util.Slog; import android.util.Xml; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.XmlUtils; import com.android.server.IoThread; import libcore.io.IoUtils; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.FileWriter; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Map; Loading @@ -47,6 +62,14 @@ public class FileUpdater { private static final boolean DEBUG = BatterySaverController.DEBUG; /** * If this system property is set to 1, it'll skip all file writes. This can be used when * one needs to change max CPU frequency for benchmarking, for example. */ private static final String PROP_SKIP_WRITE = "debug.batterysaver.no_write_files"; private static final String TAG_DEFAULT_ROOT = "defaults"; // Don't do disk access with this lock held. private final Object mLock = new Object(); Loading Loading @@ -93,6 +116,21 @@ public class FileUpdater { RETRY_INTERVAL_MS = retryIntervalMs; } public void systemReady(boolean runtimeRestarted) { synchronized (mLock) { if (runtimeRestarted) { // If it runtime restarted, read the original values from the disk and apply. if (loadDefaultValuesLocked()) { Slog.d(TAG, "Default values loaded after runtime restart; writing them..."); restoreDefault(); } } else { // Delete it, without checking the result. (file-not-exist is not an exception.) injectDefaultValuesFilename().delete(); } } } /** * Write values to files. (Note the actual writes happen ASAP but asynchronously.) */ Loading Loading @@ -241,6 +279,7 @@ public class FileUpdater { } synchronized (mLock) { mDefaultValues.put(file, originalValue); saveDefaultValuesLocked(); } return true; } Loading @@ -252,17 +291,92 @@ public class FileUpdater { @VisibleForTesting void injectWriteToFile(String file, String value) throws IOException { if (injectShouldSkipWrite()) { Slog.i(TAG, "Skipped writing to '" + file + "'"); return; } if (DEBUG) { Slog.d(TAG, "Writing: '" + value + "' to '" + file + "'"); } try (FileWriter out = new FileWriter(file)) { out.write(value); } catch (IOException e) { } catch (IOException | RuntimeException e) { Slog.w(TAG, "Failed writing '" + value + "' to '" + file + "': " + e.getMessage()); throw e; } } private void saveDefaultValuesLocked() { final AtomicFile file = new AtomicFile(injectDefaultValuesFilename()); FileOutputStream outs = null; try { file.getBaseFile().getParentFile().mkdirs(); outs = file.startWrite(); // Write to XML XmlSerializer out = new FastXmlSerializer(); out.setOutput(outs, StandardCharsets.UTF_8.name()); out.startDocument(null, true); out.startTag(null, TAG_DEFAULT_ROOT); XmlUtils.writeMapXml(mDefaultValues, out, null); // Epilogue. out.endTag(null, TAG_DEFAULT_ROOT); out.endDocument(); // Close. file.finishWrite(outs); } catch (IOException | XmlPullParserException | RuntimeException e) { Slog.e(TAG, "Failed to write to file " + file.getBaseFile(), e); file.failWrite(outs); } } @VisibleForTesting boolean loadDefaultValuesLocked() { final AtomicFile file = new AtomicFile(injectDefaultValuesFilename()); if (DEBUG) { Slog.d(TAG, "Loading from " + file.getBaseFile()); } Map<String, String> read = null; try (FileInputStream in = file.openRead()) { XmlPullParser parser = Xml.newPullParser(); parser.setInput(in, StandardCharsets.UTF_8.name()); int type; while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) { if (type != XmlPullParser.START_TAG) { continue; } final int depth = parser.getDepth(); // Check the root tag final String tag = parser.getName(); if (depth == 1) { if (!TAG_DEFAULT_ROOT.equals(tag)) { Slog.e(TAG, "Invalid root tag: " + tag); return false; } continue; } final String[] tagName = new String[1]; read = (ArrayMap<String, String>) XmlUtils.readThisArrayMapXml(parser, TAG_DEFAULT_ROOT, tagName, null); } } catch (FileNotFoundException e) { read = null; } catch (IOException | XmlPullParserException | RuntimeException e) { Slog.e(TAG, "Failed to read file " + file.getBaseFile(), e); } if (read != null) { mDefaultValues.clear(); mDefaultValues.putAll(read); return true; } return false; } private void doWtf(String message) { injectWtf(message, null); } Loading @@ -271,4 +385,20 @@ public class FileUpdater { void injectWtf(String message, Throwable e) { Slog.wtf(TAG, message, e); } File injectDefaultValuesFilename() { final File dir = new File(Environment.getDataSystemDirectory(), "battery-saver"); dir.mkdirs(); return new File(dir, "default-values.xml"); } @VisibleForTesting boolean injectShouldSkipWrite() { return SystemProperties.getBoolean(PROP_SKIP_WRITE, false); } @VisibleForTesting ArrayMap<String, String> getDefaultValuesForTest() { return mDefaultValues; } }
services/tests/servicestests/src/com/android/server/power/batterysaver/FileUpdaterTest.java +68 −1 Original line number Diff line number Diff line Loading @@ -15,8 +15,9 @@ */ package com.android.server.power.batterysaver; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; Loading @@ -26,6 +27,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import android.content.Context; import android.hardware.camera2.impl.GetCommand; import android.os.Handler; import android.os.Looper; import android.support.test.InstrumentationRegistry; Loading @@ -40,6 +42,7 @@ import org.mockito.ArgumentMatchers; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.io.File; import java.io.IOException; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; Loading Loading @@ -72,6 +75,17 @@ public class FileUpdaterTest { void injectWtf(String message, Throwable e) { mInjector.injectWtf(message, e); } @Override File injectDefaultValuesFilename() { return new File(InstrumentationRegistry.getContext().getCacheDir() + "/test-default.xml"); } @Override boolean injectShouldSkipWrite() { return false; } } private interface Injector { Loading Loading @@ -334,4 +348,57 @@ public class FileUpdaterTest { reset(mInjector); testMultiWrites(); } @Test public void testWriteReadDefault() throws Exception { doReturn("111").when(mInjector).injectReadFromFileTrimmed("file1"); doReturn("222").when(mInjector).injectReadFromFileTrimmed("file2"); doReturn("333").when(mInjector).injectReadFromFileTrimmed("file3"); // Write final ArrayMap<String, String> values = new ArrayMap<>(); values.put("file1", "11"); values.put("file2", "22"); values.put("file3", "33"); mInstance.writeFiles(values); waitUntilMainHandlerDrain(); verify(mInjector, times(1)).injectWriteToFile("file1", "11"); verify(mInjector, times(1)).injectWriteToFile("file2", "22"); verify(mInjector, times(1)).injectWriteToFile("file3", "33"); // Clear and reload the default. assertEquals(3, mInstance.getDefaultValuesForTest().size()); mInstance.getDefaultValuesForTest().clear(); assertEquals(0, mInstance.getDefaultValuesForTest().size()); mInstance.systemReady(/*runtimeRestarted=*/ true); assertEquals(3, mInstance.getDefaultValuesForTest().size()); // Reset to default mInstance.restoreDefault(); waitUntilMainHandlerDrain(); verify(mInjector, times(1)).injectWriteToFile("file1", "111"); verify(mInjector, times(1)).injectWriteToFile("file2", "222"); verify(mInjector, times(1)).injectWriteToFile("file3", "333"); // Make sure the default file still exists. assertTrue(mInstance.injectDefaultValuesFilename().exists()); // Simulate a clean boot. mInstance.getDefaultValuesForTest().clear(); assertEquals(0, mInstance.getDefaultValuesForTest().size()); mInstance.systemReady(/*runtimeRestarted=*/ false); // Default is empty, and the file is gone. assertEquals(0, mInstance.getDefaultValuesForTest().size()); assertFalse(mInstance.injectDefaultValuesFilename().exists()); // No WTF should have happened. veriryWtf(0); } }