summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/app/ActivityManagerInternal.java5
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java5
-rw-r--r--services/core/java/com/android/server/power/batterysaver/BatterySaverController.java4
-rw-r--r--services/core/java/com/android/server/power/batterysaver/FileUpdater.java132
-rw-r--r--services/tests/servicestests/src/com/android/server/power/batterysaver/FileUpdaterTest.java69
5 files changed, 213 insertions, 2 deletions
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 0266131a6c0d..d7efa91fb28b 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -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();
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 3c8f112cfab0..9ac241e0dc55 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -24330,6 +24330,11 @@ public class ActivityManagerService extends IActivityManager.Stub
}
}
}
+
+ @Override
+ public boolean isRuntimeRestarted() {
+ return mSystemServiceManager.isRuntimeRestarted();
+ }
}
/**
diff --git a/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java b/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java
index 483d5181f9c9..80bc935959a2 100644
--- a/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java
+++ b/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java
@@ -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;
@@ -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() {
diff --git a/services/core/java/com/android/server/power/batterysaver/FileUpdater.java b/services/core/java/com/android/server/power/batterysaver/FileUpdater.java
index cc1b540e669b..e0ab9e93a8a6 100644
--- a/services/core/java/com/android/server/power/batterysaver/FileUpdater.java
+++ b/services/core/java/com/android/server/power/batterysaver/FileUpdater.java
@@ -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;
@@ -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();
@@ -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.)
*/
@@ -241,6 +279,7 @@ public class FileUpdater {
}
synchronized (mLock) {
mDefaultValues.put(file, originalValue);
+ saveDefaultValuesLocked();
}
return true;
}
@@ -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);
}
@@ -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;
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/power/batterysaver/FileUpdaterTest.java b/services/tests/servicestests/src/com/android/server/power/batterysaver/FileUpdaterTest.java
index 7e2a7d221c22..7324fe6de9d8 100644
--- a/services/tests/servicestests/src/com/android/server/power/batterysaver/FileUpdaterTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/batterysaver/FileUpdaterTest.java
@@ -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;
@@ -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;
@@ -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;
@@ -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 {
@@ -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);
+ }
}