Add a privileged API for capturing and consuming bugreports

The API is mostly implemented; except for hooking up the listener
and handling an already running bugreport.

BugreportManager is the handle to the new API exposed to apps.

Generating bugreports requires root privileges. To limit the footprint
of the root access, the actual bugreport generation in Dumpstate binary,
is accessed as a oneshot service and dies after it finishes
running.

System server accesses Dumpstate via a binder interface since it does
not have root privileges.

Starting a oneshot service is done via setting a system property, which
needs to be done from system server. BugreportManagerService is the
new system server service that does this. BugreportManager calls into
BugreportManagerService via a binder interface, since the former is in
the app's process.

Both app to system server as well as system server to native service
calls are via implementations of IDumpstate binder interface.

Bug: 111441001
Test: builds. Flashed & verified it boots.
Test: wrote a test client (not included) and verified invoking
startBugreport works.

Change-Id: I4abeb753388c055c36ae0dd916af1ec8d40b7bf0
Merged-In: I4abeb753388c055c36ae0dd916af1ec8d40b7bf0
diff --git a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
new file mode 100644
index 0000000..faa4714
--- /dev/null
+++ b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2019 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.annotation.RequiresPermission;
+import android.content.Context;
+import android.os.BugreportParams;
+import android.os.IDumpstate;
+import android.os.IDumpstateListener;
+import android.os.IDumpstateToken;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.util.Slog;
+
+import java.io.FileDescriptor;
+
+// TODO(b/111441001):
+// 1. Handle the case where another bugreport is in progress
+// 2. Make everything threadsafe
+// 3. Pass validation & other errors on listener
+
+/**
+ * Implementation of the service that provides a privileged API to capture and consume bugreports.
+ *
+ * <p>Delegates the actualy generation to a native implementation of {@code Dumpstate}.
+ */
+class BugreportManagerServiceImpl extends IDumpstate.Stub {
+    private static final String TAG = "BugreportManagerService";
+    private static final long DEFAULT_BUGREPORT_SERVICE_TIMEOUT_MILLIS = 30 * 1000;
+
+    private IDumpstate mDs = null;
+    private final Context mContext;
+
+    BugreportManagerServiceImpl(Context context) {
+        mContext = context;
+    }
+
+    @Override
+    @RequiresPermission(android.Manifest.permission.DUMP)
+    public IDumpstateToken setListener(String name, IDumpstateListener listener,
+            boolean getSectionDetails) throws RemoteException {
+        // TODO(b/111441001): Figure out if lazy setting of listener should be allowed
+        // and if so how to handle it.
+        throw new UnsupportedOperationException("setListener is not allowed on this service");
+    }
+
+
+    @Override
+    @RequiresPermission(android.Manifest.permission.DUMP)
+    public void startBugreport(FileDescriptor bugreportFd, FileDescriptor screenshotFd,
+            int bugreportMode, IDumpstateListener listener) throws RemoteException {
+
+        validate(bugreportMode);
+
+        mDs = getDumpstateService();
+        if (mDs == null) {
+            Slog.w(TAG, "Unable to get bugreport service");
+            // TODO(b/111441001): pass error on listener
+            return;
+        }
+        mDs.startBugreport(bugreportFd, screenshotFd, bugreportMode, listener);
+    }
+
+    private boolean validate(@BugreportParams.BugreportMode int mode) {
+        if (mode != BugreportParams.BUGREPORT_MODE_FULL
+                && mode != BugreportParams.BUGREPORT_MODE_INTERACTIVE
+                && mode != BugreportParams.BUGREPORT_MODE_REMOTE
+                && mode != BugreportParams.BUGREPORT_MODE_WEAR
+                && mode != BugreportParams.BUGREPORT_MODE_TELEPHONY
+                && mode != BugreportParams.BUGREPORT_MODE_WIFI) {
+            Slog.w(TAG, "Unknown bugreport mode: " + mode);
+            return false;
+        }
+        return true;
+    }
+
+    /*
+     * Start and get a handle to the native implementation of {@code IDumpstate} which does the
+     * actual bugreport generation.
+     *
+     * <p>Generating bugreports requires root privileges. To limit the footprint
+     * of the root access, the actual generation in Dumpstate binary is accessed as a
+     * oneshot service 'bugreport'.
+     */
+    private IDumpstate getDumpstateService() {
+        // Start bugreport service.
+        SystemProperties.set("ctl.start", "bugreport");
+
+        IDumpstate ds = null;
+        boolean timedOut = false;
+        int totalTimeWaitedMillis = 0;
+        int seedWaitTimeMillis = 500;
+        while (!timedOut) {
+            // Note that the binder service on the native side is "dumpstate".
+            ds = IDumpstate.Stub.asInterface(ServiceManager.getService("dumpstate"));
+            if (ds != null) {
+                Slog.i(TAG, "Got bugreport service handle.");
+                break;
+            }
+            SystemClock.sleep(seedWaitTimeMillis);
+            Slog.i(TAG,
+                    "Waiting to get dumpstate service handle (" + totalTimeWaitedMillis + "ms)");
+            totalTimeWaitedMillis += seedWaitTimeMillis;
+            seedWaitTimeMillis *= 2;
+            timedOut = totalTimeWaitedMillis > DEFAULT_BUGREPORT_SERVICE_TIMEOUT_MILLIS;
+        }
+        if (timedOut) {
+            Slog.w(TAG,
+                    "Timed out waiting to get dumpstate service handle ("
+                    + totalTimeWaitedMillis + "ms)");
+        }
+        return ds;
+    }
+}