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

Commit 11a3af10 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Create thread to dump shutdown check points to file"

parents bd123a86 ea025e3f
Loading
Loading
Loading
Loading
+100 −0
Original line number Diff line number Diff line
@@ -21,13 +21,20 @@ import android.app.ActivityManager;
import android.app.IActivityManager;
import android.os.Process;
import android.os.RemoteException;
import android.util.AtomicFile;
import android.util.Log;
import android.util.Slog;

import com.android.internal.annotations.VisibleForTesting;

import java.io.File;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
@@ -45,6 +52,7 @@ public final class ShutdownCheckPoints {
    private static final ShutdownCheckPoints INSTANCE = new ShutdownCheckPoints();

    private static final int MAX_CHECK_POINTS = 100;
    private static final int MAX_DUMP_FILES = 20;
    private static final SimpleDateFormat DATE_FORMAT =
            new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS z");

@@ -63,6 +71,11 @@ public final class ShutdownCheckPoints {
                return MAX_CHECK_POINTS;
            }

            @Override
            public int maxDumpFiles() {
                return MAX_DUMP_FILES;
            }

            @Override
            public IActivityManager activityManager() {
                return ActivityManager.getService();
@@ -96,6 +109,15 @@ public final class ShutdownCheckPoints {
        INSTANCE.dumpInternal(printWriter);
    }

    /**
     * Creates a {@link Thread} that calls {@link #dump(PrintWriter)} on a rotating file created
     * from given {@code baseFile} and a timestamp suffix. Older dump files are also deleted by this
     * thread.
     */
    public static Thread newDumpThread(File baseFile) {
        return INSTANCE.newDumpThreadInternal(baseFile);
    }

    @VisibleForTesting
    void recordCheckPointInternal() {
        recordCheckPointInternal(new SystemServerCheckPoint(mInjector));
@@ -138,13 +160,21 @@ public final class ShutdownCheckPoints {
        }
    }

    @VisibleForTesting
    Thread newDumpThreadInternal(File baseFile) {
        return new FileDumperThread(this, baseFile, mInjector.maxDumpFiles());
    }

    /** Injector used by {@link ShutdownCheckPoints} for testing purposes. */
    @VisibleForTesting
    interface Injector {

        long currentTimeMillis();

        int maxCheckPoints();

        int maxDumpFiles();

        IActivityManager activityManager();
    }

@@ -296,4 +326,74 @@ public final class ShutdownCheckPoints {
            printWriter.println(mPackageName);
        }
    }

    /**
     * Thread that writes {@link ShutdownCheckPoints#dumpInternal(PrintWriter)} to a new file and
     * deletes old ones to keep the total number of files down to a given limit.
     */
    private static final class FileDumperThread extends Thread {

        private final ShutdownCheckPoints mInstance;
        private final File mBaseFile;
        private final int mFileCountLimit;

        FileDumperThread(ShutdownCheckPoints instance, File baseFile, int fileCountLimit) {
            mInstance = instance;
            mBaseFile = baseFile;
            mFileCountLimit = fileCountLimit;
        }

        @Override
        public void run() {
            mBaseFile.getParentFile().mkdirs();
            File[] checkPointFiles = listCheckPointsFiles();

            int filesToDelete = checkPointFiles.length - mFileCountLimit + 1;
            for (int i = 0; i < filesToDelete; i++) {
                checkPointFiles[i].delete();
            }

            File nextCheckPointsFile = new File(String.format("%s-%d",
                    mBaseFile.getAbsolutePath(), System.currentTimeMillis()));
            writeCheckpoints(nextCheckPointsFile);
        }

        private File[] listCheckPointsFiles() {
            String filePrefix = mBaseFile.getName() + "-";
            File[] files = mBaseFile.getParentFile().listFiles(new FilenameFilter() {
                @Override
                public boolean accept(File dir, String name) {
                    if (!name.startsWith(filePrefix)) {
                        return false;
                    }
                    try {
                        Long.valueOf(name.substring(filePrefix.length()));
                    } catch (NumberFormatException e) {
                        return false;
                    }
                    return true;
                }
            });
            Arrays.sort(files);
            return files;
        }

        private void writeCheckpoints(File file) {
            AtomicFile tmpFile = new AtomicFile(mBaseFile);
            FileOutputStream fos = null;
            try {
                fos = tmpFile.startWrite();
                PrintWriter pw = new PrintWriter(fos);
                mInstance.dumpInternal(pw);
                pw.flush();
                tmpFile.finishWrite(fos); // This also closes the output stream.
            } catch (IOException e) {
                Log.e(TAG, "Failed to write shutdown checkpoints", e);
                if (fos != null) {
                    tmpFile.failWrite(fos); // This also closes the output stream.
                }
            }
            mBaseFile.renameTo(file);
        }
    }
}
+3 −22
Original line number Diff line number Diff line
@@ -43,7 +43,6 @@ import android.os.UserManager;
import android.os.Vibrator;
import android.telephony.TelephonyManager;
import android.util.ArrayMap;
import android.util.AtomicFile;
import android.util.Log;
import android.util.Slog;
import android.util.TimingsTraceLog;
@@ -57,7 +56,6 @@ import com.android.server.statusbar.StatusBarManagerInternal;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;

public final class ShutdownThread extends Thread {
@@ -428,7 +426,9 @@ public final class ShutdownThread extends Thread {
        metricStarted(METRIC_SYSTEM_SERVER);

        // Start dumping check points for this shutdown in a separate thread.
        Thread dumpCheckPointsThread = startCheckPointsDump();
        Thread dumpCheckPointsThread = ShutdownCheckPoints.newDumpThread(
                new File(CHECK_POINTS_FILE_BASENAME));
        dumpCheckPointsThread.start();

        BroadcastReceiver br = new BroadcastReceiver() {
            @Override public void onReceive(Context context, Intent intent) {
@@ -728,25 +728,6 @@ public final class ShutdownThread extends Thread {
        }
    }

    private Thread startCheckPointsDump() {
        Thread thread = new Thread() {
            public void run() {
                // Writes to a file with .new suffix and then renames it to final file name.
                // The final file won't be left with partial data if this thread is interrupted.
                AtomicFile file = new AtomicFile(new File(CHECK_POINTS_FILE_BASENAME));
                try (FileOutputStream fos = file.startWrite()) {
                    PrintWriter pw = new PrintWriter(fos);
                    ShutdownCheckPoints.dump(pw);
                    file.finishWrite(fos);
                } catch (IOException e) {
                    Log.e(TAG, "Cannot save shutdown checkpoints", e);
                }
            }
        };
        thread.start();
        return thread;
    }

    private void uncrypt() {
        Log.i(TAG, "Calling uncrypt and monitoring the progress...");

+107 −7
Original line number Diff line number Diff line
@@ -33,9 +33,15 @@ import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;

import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.TimeZone;
@@ -59,7 +65,7 @@ public class ShutdownCheckPointsTest {
    public void setUp() {
        Locale.setDefault(Locale.UK);
        TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
        mTestInjector = new TestInjector(0, 100, mActivityManager);
        mTestInjector = new TestInjector(mActivityManager);
        mInstance = new ShutdownCheckPoints(mTestInjector);
    }

@@ -207,6 +213,67 @@ public class ShutdownCheckPointsTest {
                dumpToString(limitedInstance));
    }

    @Test
    public void testDumpToFile() throws Exception {
        File tempDir = createTempDir();
        File baseFile = new File(tempDir, "checkpoints");

        mTestInjector.setCurrentTime(1000);
        mInstance.recordCheckPointInternal("first.intent", "first.app");
        dumpToFile(baseFile);

        mTestInjector.setCurrentTime(2000);
        mInstance.recordCheckPointInternal("second.intent", "second.app");
        dumpToFile(baseFile);

        File[] dumpFiles = tempDir.listFiles();
        Arrays.sort(dumpFiles);

        assertEquals(2, dumpFiles.length);
        assertEquals(
                "Shutdown request from INTENT at 1970-01-01 00:00:01.000 UTC (epoch=1000)\n"
                        + "Intent: first.intent\n"
                        + "Package: first.app\n\n",
                readFileAsString(dumpFiles[0].getAbsolutePath()));
        assertEquals(
                "Shutdown request from INTENT at 1970-01-01 00:00:01.000 UTC (epoch=1000)\n"
                        + "Intent: first.intent\n"
                        + "Package: first.app\n\n"
                        + "Shutdown request from INTENT at 1970-01-01 00:00:02.000 UTC (epoch=2000)"
                        + "\n"
                        + "Intent: second.intent\n"
                        + "Package: second.app\n\n",
                readFileAsString(dumpFiles[1].getAbsolutePath()));
    }

    @Test
    public void testTooManyFilesDropsOlderOnes() throws Exception {
        mTestInjector.setDumpFilesLimit(1);
        ShutdownCheckPoints instance = new ShutdownCheckPoints(mTestInjector);
        File tempDir = createTempDir();
        File baseFile = new File(tempDir, "checkpoints");

        mTestInjector.setCurrentTime(1000);
        instance.recordCheckPointInternal("first.intent", "first.app");
        dumpToFile(instance, baseFile);

        mTestInjector.setCurrentTime(2000);
        instance.recordCheckPointInternal("second.intent", "second.app");
        dumpToFile(instance, baseFile);

        File[] dumpFiles = tempDir.listFiles();
        assertEquals(1, dumpFiles.length);
        assertEquals(
                "Shutdown request from INTENT at 1970-01-01 00:00:01.000 UTC (epoch=1000)\n"
                        + "Intent: first.intent\n"
                        + "Package: first.app\n\n"
                        + "Shutdown request from INTENT at 1970-01-01 00:00:02.000 UTC (epoch=2000)"
                        + "\n"
                        + "Intent: second.intent\n"
                        + "Package: second.app\n\n",
                readFileAsString(dumpFiles[0].getAbsolutePath()));
    }

    private String dumpToString() {
        return dumpToString(mInstance);
    }
@@ -218,15 +285,39 @@ public class ShutdownCheckPointsTest {
        return sw.toString();
    }

    private void dumpToFile(File baseFile) throws InterruptedException {
        dumpToFile(mInstance, baseFile);
    }

    private void dumpToFile(ShutdownCheckPoints instance, File baseFile)
            throws InterruptedException {
        Thread dumpThread = instance.newDumpThreadInternal(baseFile);
        dumpThread.start();
        dumpThread.join();
    }

    private String readFileAsString(String absolutePath) throws IOException {
        return new String(Files.readAllBytes(Paths.get(absolutePath)), StandardCharsets.UTF_8);
    }

    private File createTempDir() throws IOException {
        File tempDir = File.createTempFile("checkpoints", "out");
        tempDir.delete();
        tempDir.mkdir();
        return tempDir;
    }

    /** Fake system dependencies for testing. */
    private final class TestInjector implements ShutdownCheckPoints.Injector {
        private long mNow;
        private int mLimit;
        private int mCheckPointsLimit;
        private int mDumpFilesLimit;
        private IActivityManager mActivityManager;

        TestInjector(long now, int limit, IActivityManager activityManager) {
            mNow = now;
            mLimit = limit;
        TestInjector(IActivityManager activityManager) {
            mNow = 0;
            mCheckPointsLimit = 100;
            mDumpFilesLimit = 2;
            mActivityManager = activityManager;
        }

@@ -237,7 +328,12 @@ public class ShutdownCheckPointsTest {

        @Override
        public int maxCheckPoints() {
            return mLimit;
            return mCheckPointsLimit;
        }

        @Override
        public int maxDumpFiles() {
            return mDumpFilesLimit;
        }

        @Override
@@ -250,7 +346,11 @@ public class ShutdownCheckPointsTest {
        }

        void setCheckPointsLimit(int limit) {
            mLimit = limit;
            mCheckPointsLimit = limit;
        }

        void setDumpFilesLimit(int dumpFilesLimit) {
            mDumpFilesLimit = dumpFilesLimit;
        }
    }
}