Loading services/core/java/com/android/server/power/ShutdownCheckPoints.java +100 −0 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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"); Loading @@ -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(); Loading Loading @@ -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)); Loading Loading @@ -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(); } Loading Loading @@ -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); } } } services/core/java/com/android/server/power/ShutdownThread.java +3 −22 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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 { Loading Loading @@ -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) { Loading Loading @@ -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..."); Loading services/tests/servicestests/src/com/android/server/power/ShutdownCheckPointsTest.java +107 −7 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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); } Loading Loading @@ -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); } Loading @@ -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; } Loading @@ -237,7 +328,12 @@ public class ShutdownCheckPointsTest { @Override public int maxCheckPoints() { return mLimit; return mCheckPointsLimit; } @Override public int maxDumpFiles() { return mDumpFilesLimit; } @Override Loading @@ -250,7 +346,11 @@ public class ShutdownCheckPointsTest { } void setCheckPointsLimit(int limit) { mLimit = limit; mCheckPointsLimit = limit; } void setDumpFilesLimit(int dumpFilesLimit) { mDumpFilesLimit = dumpFilesLimit; } } } Loading
services/core/java/com/android/server/power/ShutdownCheckPoints.java +100 −0 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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"); Loading @@ -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(); Loading Loading @@ -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)); Loading Loading @@ -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(); } Loading Loading @@ -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); } } }
services/core/java/com/android/server/power/ShutdownThread.java +3 −22 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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 { Loading Loading @@ -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) { Loading Loading @@ -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..."); Loading
services/tests/servicestests/src/com/android/server/power/ShutdownCheckPointsTest.java +107 −7 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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); } Loading Loading @@ -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); } Loading @@ -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; } Loading @@ -237,7 +328,12 @@ public class ShutdownCheckPointsTest { @Override public int maxCheckPoints() { return mLimit; return mCheckPointsLimit; } @Override public int maxDumpFiles() { return mDumpFilesLimit; } @Override Loading @@ -250,7 +346,11 @@ public class ShutdownCheckPointsTest { } void setCheckPointsLimit(int limit) { mLimit = limit; mCheckPointsLimit = limit; } void setDumpFilesLimit(int dumpFilesLimit) { mDumpFilesLimit = dumpFilesLimit; } } }