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

Commit ea025e3f authored by Lais Andrade's avatar Lais Andrade
Browse files

Create thread to dump shutdown check points to file

Create new method ShutdownCheckPoints#createDumpThread that
creates a thread that calls ShutdownCheckPoints#dump on a local file and
also limits the total number of files created by deleting the oldest
ones.

Bug: 113147108
Test: atest FrameworksServicesTests:ShutdownCheckPointsTest
Change-Id: I62c8eedba2ff4c10ad66636c57de07fd1370c116
parent ea76d7e3
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;
        }
    }
}