Extract tombstone tracking to a separate service.

In preparation for making tombstones available to apps, extract the
tombstone tracking code from BootReceiver to its own service.

Bug: http://b/159164105
Test: manual
Change-Id: I789f41f12a8be312b6e966226646c4ec4becc1aa
diff --git a/core/java/com/android/server/BootReceiver.java b/core/java/com/android/server/BootReceiver.java
index ac2361d..c46d708 100644
--- a/core/java/com/android/server/BootReceiver.java
+++ b/core/java/com/android/server/BootReceiver.java
@@ -23,7 +23,6 @@
 import android.os.Build;
 import android.os.DropBoxManager;
 import android.os.Environment;
-import android.os.FileObserver;
 import android.os.FileUtils;
 import android.os.RecoverySystem;
 import android.os.RemoteException;
@@ -75,7 +74,6 @@
         SystemProperties.getInt("ro.debuggable", 0) == 1 ? 196608 : 65536;
     private static final int GMSCORE_LASTK_LOG_SIZE = 196608;
 
-    private static final File TOMBSTONE_DIR = new File("/data/tombstones");
     private static final String TAG_TOMBSTONE = "SYSTEM_TOMBSTONE";
 
     // The pre-froyo package and class of the system updater, which
@@ -86,9 +84,6 @@
     private static final String OLD_UPDATER_CLASS =
         "com.google.android.systemupdater.SystemUpdateReceiver";
 
-    // Keep a reference to the observer so the finalizer doesn't disable it.
-    private static FileObserver sTombstoneObserver = null;
-
     private static final String LOG_FILES_FILE = "log-files.xml";
     private static final AtomicFile sFile = new AtomicFile(new File(
             Environment.getDataSystemDirectory(), LOG_FILES_FILE), "log-files");
@@ -154,7 +149,7 @@
         Downloads.removeAllDownloadsByPackage(context, OLD_UPDATER_PACKAGE, OLD_UPDATER_CLASS);
     }
 
-    private String getPreviousBootHeaders() {
+    private static String getPreviousBootHeaders() {
         try {
             return FileUtils.readTextFile(lastHeaderFile, 0, null);
         } catch (IOException e) {
@@ -162,7 +157,7 @@
         }
     }
 
-    private String getCurrentBootHeaders() throws IOException {
+    private static String getCurrentBootHeaders() throws IOException {
         return new StringBuilder(512)
             .append("Build: ").append(Build.FINGERPRINT).append("\n")
             .append("Hardware: ").append(Build.BOARD).append("\n")
@@ -176,7 +171,7 @@
     }
 
 
-    private String getBootHeadersToLogAndUpdate() throws IOException {
+    private static String getBootHeadersToLogAndUpdate() throws IOException {
         final String oldHeaders = getPreviousBootHeaders();
         final String newHeaders = getCurrentBootHeaders();
 
@@ -248,38 +243,27 @@
         logFsMountTime();
         addFsckErrorsToDropBoxAndLogFsStat(db, timestamps, headers, -LOG_SIZE, "SYSTEM_FSCK");
         logSystemServerShutdownTimeMetrics();
-
-        // Scan existing tombstones (in case any new ones appeared)
-        File[] tombstoneFiles = TOMBSTONE_DIR.listFiles();
-        for (int i = 0; tombstoneFiles != null && i < tombstoneFiles.length; i++) {
-            if (tombstoneFiles[i].isFile()) {
-                addFileToDropBox(db, timestamps, headers, tombstoneFiles[i].getPath(),
-                        LOG_SIZE, "SYSTEM_TOMBSTONE");
-            }
-        }
-
         writeTimestamps(timestamps);
+    }
 
-        // Start watching for new tombstone files; will record them as they occur.
-        // This gets registered with the singleton file observer thread.
-        sTombstoneObserver = new FileObserver(TOMBSTONE_DIR.getPath(), FileObserver.CREATE) {
-            @Override
-            public void onEvent(int event, String path) {
-                HashMap<String, Long> timestamps = readTimestamps();
-                try {
-                    File file = new File(TOMBSTONE_DIR, path);
-                    if (file.isFile() && file.getName().startsWith("tombstone_")) {
-                        addFileToDropBox(db, timestamps, headers, file.getPath(), LOG_SIZE,
-                                TAG_TOMBSTONE);
-                    }
-                } catch (IOException e) {
-                    Slog.e(TAG, "Can't log tombstone", e);
-                }
-                writeTimestamps(timestamps);
-            }
-        };
-
-        sTombstoneObserver.startWatching();
+    /**
+     * Add a tombstone to the DropBox.
+     *
+     * @param ctx Context
+     * @param tombstone path to the tombstone
+     */
+    public static void addTombstoneToDropBox(Context ctx, File tombstone) {
+        final DropBoxManager db = ctx.getSystemService(DropBoxManager.class);
+        final String bootReason = SystemProperties.get("ro.boot.bootreason", null);
+        HashMap<String, Long> timestamps = readTimestamps();
+        try {
+            final String headers = getBootHeadersToLogAndUpdate();
+            addFileToDropBox(db, timestamps, headers, tombstone.getPath(), LOG_SIZE,
+                    TAG_TOMBSTONE);
+        } catch (IOException e) {
+            Slog.e(TAG, "Can't log tombstone", e);
+        }
+        writeTimestamps(timestamps);
     }
 
     private static void addLastkToDropBox(
@@ -764,7 +748,7 @@
         }
     }
 
-    private void writeTimestamps(HashMap<String, Long> timestamps) {
+    private static void writeTimestamps(HashMap<String, Long> timestamps) {
         synchronized (sFile) {
             final FileOutputStream stream;
             try {
diff --git a/services/core/java/com/android/server/os/NativeTombstoneManager.java b/services/core/java/com/android/server/os/NativeTombstoneManager.java
new file mode 100644
index 0000000..110666f
--- /dev/null
+++ b/services/core/java/com/android/server/os/NativeTombstoneManager.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2021 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.os;
+
+import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.os.FileObserver;
+import android.os.Handler;
+
+import com.android.server.BootReceiver;
+import com.android.server.ServiceThread;
+
+import java.io.File;
+
+/**
+ * A class to manage native tombstones.
+ */
+public final class NativeTombstoneManager {
+    private static final String TAG = NativeTombstoneManager.class.getSimpleName();
+
+    private static final File TOMBSTONE_DIR = new File("/data/tombstones");
+
+    private final Context mContext;
+    private final Handler mHandler;
+    private final TombstoneWatcher mWatcher;
+
+    NativeTombstoneManager(Context context) {
+        mContext = context;
+
+        final ServiceThread thread = new ServiceThread(TAG + ":tombstoneWatcher",
+                THREAD_PRIORITY_BACKGROUND, true /* allowIo */);
+        thread.start();
+        mHandler = thread.getThreadHandler();
+
+        mWatcher = new TombstoneWatcher();
+        mWatcher.startWatching();
+    }
+
+    void onSystemReady() {
+        // Scan existing tombstones.
+        mHandler.post(() -> {
+            final File[] tombstoneFiles = TOMBSTONE_DIR.listFiles();
+            for (int i = 0; tombstoneFiles != null && i < tombstoneFiles.length; i++) {
+                if (tombstoneFiles[i].isFile()) {
+                    handleTombstone(tombstoneFiles[i]);
+                }
+            }
+        });
+    }
+
+    private void handleTombstone(File path) {
+        final String filename = path.getName();
+        if (!filename.startsWith("tombstone_")) {
+            return;
+        }
+
+        BootReceiver.addTombstoneToDropBox(mContext, path);
+    }
+
+    class TombstoneWatcher extends FileObserver {
+        TombstoneWatcher() {
+            // Tombstones can be created either by linking an O_TMPFILE temporary file (CREATE),
+            // or by moving a named temporary file in the same directory on kernels where O_TMPFILE
+            // isn't supported (MOVED_TO).
+            super(TOMBSTONE_DIR, FileObserver.CREATE | FileObserver.MOVED_TO);
+        }
+
+        @Override
+        public void onEvent(int event, @Nullable String path) {
+            mHandler.post(() -> {
+                handleTombstone(new File(TOMBSTONE_DIR, path));
+            });
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/os/NativeTombstoneManagerService.java b/services/core/java/com/android/server/os/NativeTombstoneManagerService.java
new file mode 100644
index 0000000..cb3c7ff0
--- /dev/null
+++ b/services/core/java/com/android/server/os/NativeTombstoneManagerService.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2021 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.os;
+
+import android.content.Context;
+
+import com.android.server.LocalServices;
+import com.android.server.SystemService;
+
+/**
+ * Service that tracks and manages native tombstones.
+ *
+ * @hide
+ */
+public class NativeTombstoneManagerService extends SystemService {
+    private static final String TAG = "NativeTombstoneManagerService";
+
+    private NativeTombstoneManager mManager;
+
+    public NativeTombstoneManagerService(Context context) {
+        super(context);
+    }
+
+    @Override
+    public void onStart() {
+        mManager = new NativeTombstoneManager(getContext());
+        LocalServices.addService(NativeTombstoneManager.class, mManager);
+    }
+
+    @Override
+    public void onBootPhase(int phase) {
+        if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
+            mManager.onSystemReady();
+        }
+    }
+}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 203de9d..c355b2f 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -139,6 +139,7 @@
 import com.android.server.om.OverlayManagerService;
 import com.android.server.os.BugreportManagerService;
 import com.android.server.os.DeviceIdentifiersPolicyService;
+import com.android.server.os.NativeTombstoneManagerService;
 import com.android.server.os.SchedulingPolicyService;
 import com.android.server.people.PeopleService;
 import com.android.server.pm.BackgroundDexOptService;
@@ -1072,6 +1073,11 @@
         mSystemServiceManager.startService(ROLLBACK_MANAGER_SERVICE_CLASS);
         t.traceEnd();
 
+        // Tracks native tombstones.
+        t.traceBegin("StartNativeTombstoneManagerService");
+        mSystemServiceManager.startService(NativeTombstoneManagerService.class);
+        t.traceEnd();
+
         // Service to capture bugreports.
         t.traceBegin("StartBugreportManagerService");
         mSystemServiceManager.startService(BugreportManagerService.class);