Add bugreport section progress reporter

- Allow dumpstatelisteners to monitor section size and duration and errors while the
bugreport is generated. Data will be used to write smoke tests for bugreport.
- Refactor main function to allow test to call dumpstate main function. Required until
bugreport api is completed
- Restore stdout and stderr fds before exiting dumpstate

Bug: 70154685
Test: mmm -j56 frameworks/native/cmds/dumpstate && \
      adb sync data && \
      adb shell /data/nativetest64/dumpstate_test/dumpstate_test && \
      printf "\n\n#### ALL TESTS PASSED ####\n"

Change-Id: I7e0938baf6e055f14dce2348d0fe99f261870bf1
diff --git a/cmds/dumpstate/Android.bp b/cmds/dumpstate/Android.bp
index ce3a6aa..35cff5f 100644
--- a/cmds/dumpstate/Android.bp
+++ b/cmds/dumpstate/Android.bp
@@ -83,7 +83,9 @@
     ],
     srcs: [
         "DumpstateInternal.cpp",
+        "DumpstateSectionReporter.cpp",
         "DumpstateService.cpp",
+        "main.cpp",
         "utils.cpp",
         "dumpstate.cpp",
     ],
diff --git a/cmds/dumpstate/DumpstateSectionReporter.cpp b/cmds/dumpstate/DumpstateSectionReporter.cpp
new file mode 100644
index 0000000..f814bde
--- /dev/null
+++ b/cmds/dumpstate/DumpstateSectionReporter.cpp
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+
+#define LOG_TAG "dumpstate"
+
+#include "DumpstateSectionReporter.h"
+
+namespace android {
+namespace os {
+namespace dumpstate {
+
+DumpstateSectionReporter::DumpstateSectionReporter(const std::string& title,
+                                                   sp<android::os::IDumpstateListener> listener,
+                                                   bool sendReport)
+    : title_(title), listener_(listener), sendReport_(sendReport), status_(OK), size_(-1) {
+    started_ = std::chrono::steady_clock::now();
+}
+
+DumpstateSectionReporter::~DumpstateSectionReporter() {
+    if ((listener_ != nullptr) && (sendReport_)) {
+        auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
+            std::chrono::steady_clock::now() - started_);
+        listener_->onSectionComplete(title_, status_, size_, (int32_t)elapsed.count());
+    }
+}
+
+}  // namespace dumpstate
+}  // namespace os
+}  // namespace android
diff --git a/cmds/dumpstate/DumpstateSectionReporter.h b/cmds/dumpstate/DumpstateSectionReporter.h
new file mode 100644
index 0000000..e971de8
--- /dev/null
+++ b/cmds/dumpstate/DumpstateSectionReporter.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+
+#ifndef ANDROID_OS_DUMPSTATESECTIONREPORTER_H_
+#define ANDROID_OS_DUMPSTATESECTIONREPORTER_H_
+
+#include <android/os/IDumpstateListener.h>
+#include <utils/StrongPointer.h>
+
+namespace android {
+namespace os {
+namespace dumpstate {
+
+
+/*
+ * Helper class used to report per section details to a listener.
+ *
+ * Typical usage:
+ *
+ *    DumpstateSectionReporter sectionReporter(title, listener, sendReport);
+ *    sectionReporter.setSize(5000);
+ *
+ */
+class DumpstateSectionReporter {
+  public:
+    DumpstateSectionReporter(const std::string& title, sp<android::os::IDumpstateListener> listener,
+                             bool sendReport);
+
+    ~DumpstateSectionReporter();
+
+    void setStatus(status_t status) {
+        status_ = status;
+    }
+
+    void setSize(int size) {
+        size_ = size;
+    }
+
+  private:
+    std::string title_;
+    android::sp<android::os::IDumpstateListener> listener_;
+    bool sendReport_;
+    status_t status_;
+    int size_;
+    std::chrono::time_point<std::chrono::steady_clock> started_;
+};
+
+}  // namespace dumpstate
+}  // namespace os
+}  // namespace android
+
+#endif  // ANDROID_OS_DUMPSTATESECTIONREPORTER_H_
diff --git a/cmds/dumpstate/DumpstateService.cpp b/cmds/dumpstate/DumpstateService.cpp
index efe0466..49a78e7 100644
--- a/cmds/dumpstate/DumpstateService.cpp
+++ b/cmds/dumpstate/DumpstateService.cpp
@@ -52,6 +52,7 @@
 
 binder::Status DumpstateService::setListener(const std::string& name,
                                              const sp<IDumpstateListener>& listener,
+                                             bool getSectionDetails,
                                              sp<IDumpstateToken>* returned_token) {
     *returned_token = nullptr;
     if (name.empty()) {
@@ -70,6 +71,7 @@
 
     ds_.listener_name_ = name;
     ds_.listener_ = listener;
+    ds_.report_section_ = getSectionDetails;
     *returned_token = new DumpstateToken();
 
     return binder::Status::ok();
diff --git a/cmds/dumpstate/DumpstateService.h b/cmds/dumpstate/DumpstateService.h
index 4352d3d..7bca24a 100644
--- a/cmds/dumpstate/DumpstateService.h
+++ b/cmds/dumpstate/DumpstateService.h
@@ -38,6 +38,7 @@
 
     status_t dump(int fd, const Vector<String16>& args) override;
     binder::Status setListener(const std::string& name, const sp<IDumpstateListener>& listener,
+                               bool getSectionDetails,
                                sp<IDumpstateToken>* returned_token) override;
 
   private:
diff --git a/cmds/dumpstate/binder/android/os/IDumpstate.aidl b/cmds/dumpstate/binder/android/os/IDumpstate.aidl
index 4becccf..9b11b96 100644
--- a/cmds/dumpstate/binder/android/os/IDumpstate.aidl
+++ b/cmds/dumpstate/binder/android/os/IDumpstate.aidl
@@ -30,6 +30,9 @@
      *
      * Returns a token used to monitor dumpstate death, or `nullptr` if the listener was already
      * set (the listener behaves like a Highlander: There Can be Only One).
+     * Set {@code getSectionDetails} to true in order to receive callbacks with per section
+     * progress details
      */
-    IDumpstateToken setListener(@utf8InCpp String name, IDumpstateListener listener);
+    IDumpstateToken setListener(@utf8InCpp String name, IDumpstateListener listener,
+                                boolean getSectionDetails);
 }
diff --git a/cmds/dumpstate/binder/android/os/IDumpstateListener.aidl b/cmds/dumpstate/binder/android/os/IDumpstateListener.aidl
index 32717f4..030d69d 100644
--- a/cmds/dumpstate/binder/android/os/IDumpstateListener.aidl
+++ b/cmds/dumpstate/binder/android/os/IDumpstateListener.aidl
@@ -24,4 +24,16 @@
 interface IDumpstateListener {
     void onProgressUpdated(int progress);
     void onMaxProgressUpdated(int maxProgress);
+
+    /**
+    * Called after every section is complete.
+    * @param  name          section name
+    * @param  status        values from status_t
+    *                       {@code OK} section completed successfully
+    *                       {@code TIMEOUT} dump timed out
+    *                       {@code != OK} error
+    * @param  size          size in bytes, may be invalid if status != OK
+    * @param  durationMs    duration in ms
+    */
+    void onSectionComplete(@utf8InCpp String name, int status, int size, int durationMs);
 }
diff --git a/cmds/dumpstate/dumpstate.cpp b/cmds/dumpstate/dumpstate.cpp
index 143192e..eb9079b 100644
--- a/cmds/dumpstate/dumpstate.cpp
+++ b/cmds/dumpstate/dumpstate.cpp
@@ -1545,7 +1545,8 @@
     // clang-format on
 }
 
-int main(int argc, char *argv[]) {
+/** Main entry point for dumpstate. */
+int run_main(int argc, char* argv[]) {
     int do_add_date = 0;
     int do_zip_file = 0;
     int do_vibrate = 1;
@@ -1558,6 +1559,8 @@
     bool show_header_only = false;
     bool do_start_service = false;
     bool telephony_only = false;
+    int dup_stdout_fd;
+    int dup_stderr_fd;
 
     /* set as high priority, and protect from OOM killer */
     setpriority(PRIO_PROCESS, 0, -20);
@@ -1829,11 +1832,13 @@
     }
 
     if (is_redirecting) {
+        TEMP_FAILURE_RETRY(dup_stderr_fd = dup(fileno(stderr)));
         redirect_to_file(stderr, const_cast<char*>(ds.log_path_.c_str()));
         if (chown(ds.log_path_.c_str(), AID_SHELL, AID_SHELL)) {
             MYLOGE("Unable to change ownership of dumpstate log file %s: %s\n",
                    ds.log_path_.c_str(), strerror(errno));
         }
+        TEMP_FAILURE_RETRY(dup_stdout_fd = dup(fileno(stdout)));
         /* TODO: rather than generating a text file now and zipping it later,
            it would be more efficient to redirect stdout to the zip entry
            directly, but the libziparchive doesn't support that option yet. */
@@ -1907,7 +1912,7 @@
 
     /* close output if needed */
     if (is_redirecting) {
-        fclose(stdout);
+        TEMP_FAILURE_RETRY(dup2(dup_stdout_fd, fileno(stdout)));
     }
 
     /* rename or zip the (now complete) .tmp file to its final location */
@@ -2038,7 +2043,7 @@
     MYLOGI("done (id %d)\n", ds.id_);
 
     if (is_redirecting) {
-        fclose(stderr);
+        TEMP_FAILURE_RETRY(dup2(dup_stderr_fd, fileno(stderr)));
     }
 
     if (use_control_socket && ds.control_socket_fd_ != -1) {
diff --git a/cmds/dumpstate/dumpstate.h b/cmds/dumpstate/dumpstate.h
index 8db23a9..843c545 100644
--- a/cmds/dumpstate/dumpstate.h
+++ b/cmds/dumpstate/dumpstate.h
@@ -341,9 +341,10 @@
     // Pointer to the zip structure.
     std::unique_ptr<ZipWriter> zip_writer_;
 
-    // Binder object listing to progress.
+    // Binder object listening to progress.
     android::sp<android::os::IDumpstateListener> listener_;
     std::string listener_name_;
+    bool report_section_;
 
     // Notification title and description
     std::string notification_title;
@@ -433,6 +434,9 @@
 /** Gets command-line arguments. */
 void format_args(int argc, const char *argv[], std::string *args);
 
+/** Main entry point for dumpstate. */
+int run_main(int argc, char* argv[]);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/cmds/dumpstate/main.cpp b/cmds/dumpstate/main.cpp
new file mode 100644
index 0000000..78aad11
--- /dev/null
+++ b/cmds/dumpstate/main.cpp
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+
+#include "dumpstate.h"
+
+int main(int argc, char* argv[]) {
+    return run_main(argc, argv);
+}
diff --git a/cmds/dumpstate/tests/dumpstate_test.cpp b/cmds/dumpstate/tests/dumpstate_test.cpp
index a2e9453..838b385 100644
--- a/cmds/dumpstate/tests/dumpstate_test.cpp
+++ b/cmds/dumpstate/tests/dumpstate_test.cpp
@@ -58,6 +58,8 @@
   public:
     MOCK_METHOD1(onProgressUpdated, binder::Status(int32_t progress));
     MOCK_METHOD1(onMaxProgressUpdated, binder::Status(int32_t max_progress));
+    MOCK_METHOD4(onSectionComplete, binder::Status(const ::std::string& name, int32_t status,
+                                                   int32_t size, int32_t durationMs));
 
   protected:
     MOCK_METHOD0(onAsBinder, IBinder*());
@@ -601,27 +603,43 @@
 TEST_F(DumpstateServiceTest, SetListenerNoName) {
     sp<DumpstateListenerMock> listener(new DumpstateListenerMock());
     sp<IDumpstateToken> token;
-    EXPECT_TRUE(dss.setListener("", listener, &token).isOk());
+    EXPECT_TRUE(dss.setListener("", listener, /* getSectionDetails = */ false, &token).isOk());
     ASSERT_THAT(token, IsNull());
 }
 
 TEST_F(DumpstateServiceTest, SetListenerNoPointer) {
     sp<IDumpstateToken> token;
-    EXPECT_TRUE(dss.setListener("whatever", nullptr, &token).isOk());
+    EXPECT_TRUE(
+        dss.setListener("whatever", nullptr, /* getSectionDetails = */ false, &token).isOk());
     ASSERT_THAT(token, IsNull());
 }
 
 TEST_F(DumpstateServiceTest, SetListenerTwice) {
     sp<DumpstateListenerMock> listener(new DumpstateListenerMock());
     sp<IDumpstateToken> token;
-    EXPECT_TRUE(dss.setListener("whatever", listener, &token).isOk());
+    EXPECT_TRUE(
+        dss.setListener("whatever", listener, /* getSectionDetails = */ false, &token).isOk());
     ASSERT_THAT(token, NotNull());
     EXPECT_THAT(Dumpstate::GetInstance().listener_name_, StrEq("whatever"));
+    EXPECT_FALSE(Dumpstate::GetInstance().report_section_);
 
     token.clear();
-    EXPECT_TRUE(dss.setListener("whatsoever", listener, &token).isOk());
+    EXPECT_TRUE(
+        dss.setListener("whatsoever", listener, /* getSectionDetails = */ false, &token).isOk());
     ASSERT_THAT(token, IsNull());
     EXPECT_THAT(Dumpstate::GetInstance().listener_name_, StrEq("whatever"));
+    EXPECT_FALSE(Dumpstate::GetInstance().report_section_);
+}
+
+TEST_F(DumpstateServiceTest, SetListenerWithSectionDetails) {
+    sp<DumpstateListenerMock> listener(new DumpstateListenerMock());
+    sp<IDumpstateToken> token;
+    Dumpstate::GetInstance().listener_ = nullptr;
+    EXPECT_TRUE(
+        dss.setListener("whatever", listener, /* getSectionDetails = */ true, &token).isOk());
+    ASSERT_THAT(token, NotNull());
+    EXPECT_THAT(Dumpstate::GetInstance().listener_name_, StrEq("whatever"));
+    EXPECT_TRUE(Dumpstate::GetInstance().report_section_);
 }
 
 class ProgressTest : public DumpstateBaseTest {