summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--services/core/java/com/android/server/power/PreRebootLogger.java133
-rw-r--r--services/core/java/com/android/server/power/ShutdownThread.java10
-rw-r--r--services/tests/servicestests/src/com/android/server/power/PreRebootLoggerTest.java97
3 files changed, 240 insertions, 0 deletions
diff --git a/services/core/java/com/android/server/power/PreRebootLogger.java b/services/core/java/com/android/server/power/PreRebootLogger.java
new file mode 100644
index 000000000000..cda00b404c0b
--- /dev/null
+++ b/services/core/java/com/android/server/power/PreRebootLogger.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.power;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.os.Environment;
+import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.provider.Settings;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileWriter;
+import java.io.IOException;
+
+/**
+ * Provides utils to dump/wipe pre-reboot information.
+ */
+final class PreRebootLogger {
+ private static final String TAG = "PreRebootLogger";
+ private static final String PREREBOOT_DIR = "prereboot";
+
+ private static final String[] BUFFERS_TO_DUMP = {"system"};
+ private static final String[] SERVICES_TO_DUMP = {Context.ROLLBACK_SERVICE, "package"};
+
+ private static final Object sLock = new Object();
+
+ /**
+ * Process pre-reboot information. Dump pre-reboot information to {@link #PREREBOOT_DIR} if
+ * enabled {@link Settings.Global#ADB_ENABLED}; wipe dumped information otherwise.
+ */
+ static void log(Context context) {
+ log(context, getDumpDir());
+ }
+
+ @VisibleForTesting
+ static void log(Context context, @NonNull File dumpDir) {
+ if (Settings.Global.getInt(
+ context.getContentResolver(), Settings.Global.ADB_ENABLED, 0) == 1) {
+ Slog.d(TAG, "Dumping pre-reboot information...");
+ dump(dumpDir);
+ } else {
+ Slog.d(TAG, "Wiping pre-reboot information...");
+ wipe(dumpDir);
+ }
+ }
+
+ private static void dump(@NonNull File dumpDir) {
+ synchronized (sLock) {
+ for (String buffer : BUFFERS_TO_DUMP) {
+ dumpLogsLocked(dumpDir, buffer);
+ }
+ for (String service : SERVICES_TO_DUMP) {
+ dumpServiceLocked(dumpDir, service);
+ }
+ }
+ }
+
+ private static void wipe(@NonNull File dumpDir) {
+ synchronized (sLock) {
+ for (File file : dumpDir.listFiles()) {
+ file.delete();
+ }
+ }
+ }
+
+ private static File getDumpDir() {
+ final File dumpDir = new File(Environment.getDataMiscDirectory(), PREREBOOT_DIR);
+ if (!dumpDir.exists() || !dumpDir.isDirectory()) {
+ throw new UnsupportedOperationException("Pre-reboot dump directory not found");
+ }
+ return dumpDir;
+ }
+
+ @GuardedBy("sLock")
+ private static void dumpLogsLocked(@NonNull File dumpDir, @NonNull String buffer) {
+ try {
+ final File dumpFile = new File(dumpDir, buffer);
+ if (dumpFile.createNewFile()) {
+ dumpFile.setWritable(true /* writable */, true /* ownerOnly */);
+ } else {
+ // Wipes dumped information in existing file before recording new information.
+ new FileWriter(dumpFile, false).flush();
+ }
+
+ final String[] cmdline =
+ {"logcat", "-d", "-b", buffer, "-f", dumpFile.getAbsolutePath()};
+ Runtime.getRuntime().exec(cmdline).waitFor();
+ } catch (IOException | InterruptedException e) {
+ Slog.d(TAG, "Dump system log buffer before reboot fail", e);
+ }
+ }
+
+ @GuardedBy("sLock")
+ private static void dumpServiceLocked(@NonNull File dumpDir, @NonNull String serviceName) {
+ final IBinder binder = ServiceManager.checkService(serviceName);
+ if (binder == null) {
+ return;
+ }
+
+ try {
+ final File dumpFile = new File(dumpDir, serviceName);
+ final ParcelFileDescriptor fd = ParcelFileDescriptor.open(dumpFile,
+ ParcelFileDescriptor.MODE_CREATE | ParcelFileDescriptor.MODE_TRUNCATE
+ | ParcelFileDescriptor.MODE_WRITE_ONLY);
+ binder.dump(fd.getFileDescriptor(), ArrayUtils.emptyArray(String.class));
+ } catch (FileNotFoundException | RemoteException e) {
+ Slog.d(TAG, String.format("Dump %s service before reboot fail", serviceName), e);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/power/ShutdownThread.java b/services/core/java/com/android/server/power/ShutdownThread.java
index cc1cddd3a111..bc722f181650 100644
--- a/services/core/java/com/android/server/power/ShutdownThread.java
+++ b/services/core/java/com/android/server/power/ShutdownThread.java
@@ -44,6 +44,7 @@ import android.os.Vibrator;
import android.telephony.TelephonyManager;
import android.util.ArrayMap;
import android.util.Log;
+import android.util.Slog;
import android.util.TimingsTraceLog;
import android.view.WindowManager;
@@ -446,6 +447,15 @@ public final class ShutdownThread extends Thread {
SystemProperties.set(REBOOT_SAFEMODE_PROPERTY, "1");
}
+ shutdownTimingLog.traceBegin("DumpPreRebootInfo");
+ try {
+ Slog.i(TAG, "Logging pre-reboot information...");
+ PreRebootLogger.log(mContext);
+ } catch (Exception e) {
+ Slog.e(TAG, "Failed to log pre-reboot information", e);
+ }
+ shutdownTimingLog.traceEnd(); // DumpPreRebootInfo
+
metricStarted(METRIC_SEND_BROADCAST);
shutdownTimingLog.traceBegin("SendShutdownBroadcast");
Log.i(TAG, "Sending shutdown broadcast...");
diff --git a/services/tests/servicestests/src/com/android/server/power/PreRebootLoggerTest.java b/services/tests/servicestests/src/com/android/server/power/PreRebootLoggerTest.java
new file mode 100644
index 000000000000..a13823441665
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/power/PreRebootLoggerTest.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.power;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.provider.Settings;
+import android.test.mock.MockContentResolver;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.util.test.FakeSettingsProvider;
+
+import com.google.common.io.Files;
+
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.File;
+
+/**
+ * Tests for {@link PreRebootLogger}
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class PreRebootLoggerTest {
+ @Mock Context mContext;
+ private MockContentResolver mContentResolver;
+ private File mDumpDir;
+
+ @BeforeClass
+ public static void setupOnce() {
+ FakeSettingsProvider.clearSettingsProvider();
+ }
+
+ @AfterClass
+ public static void tearDownOnce() {
+ FakeSettingsProvider.clearSettingsProvider();
+ }
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mContentResolver = new MockContentResolver(getInstrumentation().getTargetContext());
+ when(mContext.getContentResolver()).thenReturn(mContentResolver);
+ mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
+
+ mDumpDir = Files.createTempDir();
+ mDumpDir.mkdir();
+ mDumpDir.deleteOnExit();
+ }
+
+ @Test
+ public void log_adbEnabled_dumpsInformationProperly() {
+ Settings.Global.putInt(mContentResolver, Settings.Global.ADB_ENABLED, 1);
+
+ PreRebootLogger.log(mContext, mDumpDir);
+
+ assertThat(mDumpDir.list()).asList().containsExactly("system", "package", "rollback");
+ }
+
+ @Test
+ public void log_adbDisabled_wipesDumpedInformation() {
+ Settings.Global.putInt(mContentResolver, Settings.Global.ADB_ENABLED, 1);
+ PreRebootLogger.log(mContext, mDumpDir);
+ Settings.Global.putInt(mContentResolver, Settings.Global.ADB_ENABLED, 0);
+
+ PreRebootLogger.log(mContext, mDumpDir);
+
+ assertThat(mDumpDir.listFiles()).isEmpty();
+ }
+}