Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 3c1d0cae authored by Makoto Onuki's avatar Makoto Onuki Committed by Android (Google) Code Review
Browse files

Merge "Extreme battery saver: Tweak to file saver"

parents 733ce637 e098b759
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -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();
}
+5 −0
Original line number Diff line number Diff line
@@ -24330,6 +24330,11 @@ public class ActivityManagerService extends IActivityManager.Stub
                }
            }
        }
        @Override
        public boolean isRuntimeRestarted() {
            return mSystemServiceManager.isRuntimeRestarted();
        }
    }
    /**
+4 −0
Original line number Diff line number Diff line
@@ -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() {
+131 −1
Original line number Diff line number Diff line
@@ -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;
    }
}
+68 −1
Original line number Diff line number Diff line
@@ -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);
   }
}