Merge "adb: factor out socket specification." am: bd7d2e2ec0
am: f95e728431

Change-Id: Ibbed2dea019538491650eab41bd75e4f7546a909
diff --git a/adb/Android.mk b/adb/Android.mk
index 1aa3dc1..0a9c454 100644
--- a/adb/Android.mk
+++ b/adb/Android.mk
@@ -184,6 +184,10 @@
 LOCAL_CFLAGS_darwin := $(LIBADB_darwin_CFLAGS)
 LOCAL_SRC_FILES := \
     $(LIBADB_TEST_SRCS) \
+    adb_client.cpp \
+    bugreport.cpp \
+    bugreport_test.cpp \
+    line_printer.cpp \
     services.cpp \
     shell_service_protocol.cpp \
     shell_service_protocol_test.cpp \
@@ -199,6 +203,7 @@
     libcrypto \
     libcutils \
     libdiagnose_usb \
+    libgmock_host \
 
 # Set entrypoint to wmain from sysdeps_win32.cpp instead of main
 LOCAL_LDFLAGS_windows := -municode
@@ -227,6 +232,7 @@
 
 LOCAL_SRC_FILES := \
     adb_client.cpp \
+    bugreport.cpp \
     client/main.cpp \
     console.cpp \
     commandline.cpp \
diff --git a/adb/adb.h b/adb/adb.h
index 9227eb1..cd6b7bd 100644
--- a/adb/adb.h
+++ b/adb/adb.h
@@ -203,8 +203,6 @@
 int is_adb_interface(int vid, int pid, int usb_class, int usb_subclass, int usb_protocol);
 #endif
 
-int adb_commandline(int argc, const char **argv);
-
 ConnectionState connection_state(atransport *t);
 
 extern const char* adb_device_banner;
diff --git a/adb/adb_client.h b/adb/adb_client.h
index d5cd922..9f9eb1f 100644
--- a/adb/adb_client.h
+++ b/adb/adb_client.h
@@ -18,6 +18,7 @@
 #define _ADB_CLIENT_H_
 
 #include "adb.h"
+#include "sysdeps.h"
 #include "transport.h"
 
 #include <string>
diff --git a/adb/bugreport.cpp b/adb/bugreport.cpp
new file mode 100644
index 0000000..c348dd5
--- /dev/null
+++ b/adb/bugreport.cpp
@@ -0,0 +1,278 @@
+/*
+ * Copyright (C) 2016 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 TRACE_TAG ADB
+
+#include "bugreport.h"
+
+#include <string>
+#include <vector>
+
+#include <android-base/strings.h>
+
+#include "sysdeps.h"
+#include "adb_utils.h"
+#include "file_sync_service.h"
+
+static constexpr char BUGZ_BEGIN_PREFIX[] = "BEGIN:";
+static constexpr char BUGZ_PROGRESS_PREFIX[] = "PROGRESS:";
+static constexpr char BUGZ_PROGRESS_SEPARATOR[] = "/";
+static constexpr char BUGZ_OK_PREFIX[] = "OK:";
+static constexpr char BUGZ_FAIL_PREFIX[] = "FAIL:";
+
+// Custom callback used to handle the output of zipped bugreports.
+class BugreportStandardStreamsCallback : public StandardStreamsCallbackInterface {
+  public:
+    BugreportStandardStreamsCallback(const std::string& dest_dir, const std::string& dest_file,
+                                     bool show_progress, Bugreport* br)
+        : br_(br),
+          src_file_(),
+          dest_dir_(dest_dir),
+          dest_file_(dest_file),
+          line_message_(),
+          invalid_lines_(),
+          show_progress_(show_progress),
+          status_(0),
+          line_() {
+        SetLineMessage("generating");
+    }
+
+    void OnStdout(const char* buffer, int length) {
+        for (int i = 0; i < length; i++) {
+            char c = buffer[i];
+            if (c == '\n') {
+                ProcessLine(line_);
+                line_.clear();
+            } else {
+                line_.append(1, c);
+            }
+        }
+    }
+
+    void OnStderr(const char* buffer, int length) {
+        OnStream(nullptr, stderr, buffer, length);
+    }
+    int Done(int unused_) {
+        // Process remaining line, if any.
+        ProcessLine(line_);
+
+        // Warn about invalid lines, if any.
+        if (!invalid_lines_.empty()) {
+            fprintf(stderr,
+                    "WARNING: bugreportz generated %zu line(s) with unknown commands, "
+                    "device might not support zipped bugreports:\n",
+                    invalid_lines_.size());
+            for (const auto& line : invalid_lines_) {
+                fprintf(stderr, "\t%s\n", line.c_str());
+            }
+            fprintf(stderr,
+                    "If the zipped bugreport was not generated, try 'adb bugreport' instead.\n");
+        }
+
+        // Pull the generated bug report.
+        if (status_ == 0) {
+            if (src_file_.empty()) {
+                fprintf(stderr, "bugreportz did not return a '%s' or '%s' line\n", BUGZ_OK_PREFIX,
+                        BUGZ_FAIL_PREFIX);
+                return -1;
+            }
+            std::string destination;
+            if (dest_dir_.empty()) {
+                destination = dest_file_;
+            } else {
+                destination = android::base::StringPrintf("%s%c%s", dest_dir_.c_str(),
+                                                          OS_PATH_SEPARATOR, dest_file_.c_str());
+            }
+            std::vector<const char*> srcs{src_file_.c_str()};
+            SetLineMessage("pulling");
+            status_ =
+                br_->DoSyncPull(srcs, destination.c_str(), true, line_message_.c_str()) ? 0 : 1;
+            if (status_ != 0) {
+                fprintf(stderr,
+                        "Bug report finished but could not be copied to '%s'.\n"
+                        "Try to run 'adb pull %s <directory>'\n"
+                        "to copy it to a directory that can be written.\n",
+                        destination.c_str(), src_file_.c_str());
+            }
+        }
+        return status_;
+    }
+
+  private:
+    void SetLineMessage(const std::string& action) {
+        line_message_ = action + " " + adb_basename(dest_file_);
+    }
+
+    void SetSrcFile(const std::string path) {
+        src_file_ = path;
+        if (!dest_dir_.empty()) {
+            // Only uses device-provided name when user passed a directory.
+            dest_file_ = adb_basename(path);
+            SetLineMessage("generating");
+        }
+    }
+
+    void ProcessLine(const std::string& line) {
+        if (line.empty()) return;
+
+        if (android::base::StartsWith(line, BUGZ_BEGIN_PREFIX)) {
+            SetSrcFile(&line[strlen(BUGZ_BEGIN_PREFIX)]);
+        } else if (android::base::StartsWith(line, BUGZ_OK_PREFIX)) {
+            SetSrcFile(&line[strlen(BUGZ_OK_PREFIX)]);
+        } else if (android::base::StartsWith(line, BUGZ_FAIL_PREFIX)) {
+            const char* error_message = &line[strlen(BUGZ_FAIL_PREFIX)];
+            fprintf(stderr, "Device failed to take a zipped bugreport: %s\n", error_message);
+            status_ = -1;
+        } else if (show_progress_ && android::base::StartsWith(line, BUGZ_PROGRESS_PREFIX)) {
+            // progress_line should have the following format:
+            //
+            // BUGZ_PROGRESS_PREFIX:PROGRESS/TOTAL
+            //
+            size_t idx1 = line.rfind(BUGZ_PROGRESS_PREFIX) + strlen(BUGZ_PROGRESS_PREFIX);
+            size_t idx2 = line.rfind(BUGZ_PROGRESS_SEPARATOR);
+            int progress = std::stoi(line.substr(idx1, (idx2 - idx1)));
+            int total = std::stoi(line.substr(idx2 + 1));
+            br_->UpdateProgress(line_message_, progress, total);
+        } else {
+            invalid_lines_.push_back(line);
+        }
+    }
+
+    Bugreport* br_;
+
+    // Path of bugreport on device.
+    std::string src_file_;
+
+    // Bugreport destination on host, depending on argument passed on constructor:
+    // - if argument is a directory, dest_dir_ is set with it and dest_file_ will be the name
+    //   of the bugreport reported by the device.
+    // - if argument is empty, dest_dir is set as the current directory and dest_file_ will be the
+    //   name of the bugreport reported by the device.
+    // - otherwise, dest_dir_ is not set and dest_file_ is set with the value passed on constructor.
+    std::string dest_dir_, dest_file_;
+
+    // Message displayed on LinePrinter, it's updated every time the destination above change.
+    std::string line_message_;
+
+    // Lines sent by bugreportz that contain invalid commands; will be displayed at the end.
+    std::vector<std::string> invalid_lines_;
+
+    // Whether PROGRESS_LINES should be interpreted as progress.
+    bool show_progress_;
+
+    // Overall process of the operation, as returned by Done().
+    int status_;
+
+    // Temporary buffer containing the characters read since the last newline (\n).
+    std::string line_;
+
+    DISALLOW_COPY_AND_ASSIGN(BugreportStandardStreamsCallback);
+};
+
+// Implemented in commandline.cpp
+int usage();
+
+int Bugreport::DoIt(TransportType transport_type, const char* serial, int argc, const char** argv) {
+    if (argc > 2) return usage();
+
+    // Gets bugreportz version.
+    std::string bugz_stdout, bugz_stderr;
+    DefaultStandardStreamsCallback version_callback(&bugz_stdout, &bugz_stderr);
+    int status = SendShellCommand(transport_type, serial, "bugreportz -v", false, &version_callback);
+    std::string bugz_version = android::base::Trim(bugz_stderr);
+    std::string bugz_output = android::base::Trim(bugz_stdout);
+
+    if (status != 0 || bugz_version.empty()) {
+        D("'bugreportz' -v results: status=%d, stdout='%s', stderr='%s'", status,
+          bugz_output.c_str(), bugz_version.c_str());
+        if (argc == 1) {
+            // Device does not support bugreportz: if called as 'adb bugreport', just falls out to
+            // the flat-file version.
+            fprintf(stderr,
+                    "Failed to get bugreportz version, which is only available on devices "
+                    "running Android 7.0 or later.\nTrying a plain-text bug report instead.\n");
+            return SendShellCommand(transport_type, serial, "bugreport", false);
+        }
+
+        // But if user explicitly asked for a zipped bug report, fails instead (otherwise calling
+        // 'bugreport' would generate a lot of output the user might not be prepared to handle).
+        fprintf(stderr,
+                "Failed to get bugreportz version: 'bugreportz -v' returned '%s' (code %d).\n"
+                "If the device does not run Android 7.0 or above, try 'adb bugreport' instead.\n",
+                bugz_output.c_str(), status);
+        return status != 0 ? status : -1;
+    }
+
+    std::string dest_file, dest_dir;
+
+    if (argc == 1) {
+        // No args - use current directory
+        if (!getcwd(&dest_dir)) {
+            perror("adb: getcwd failed");
+            return 1;
+        }
+    } else {
+        // Check whether argument is a directory or file
+        if (directory_exists(argv[1])) {
+            dest_dir = argv[1];
+        } else {
+            dest_file = argv[1];
+        }
+    }
+
+    if (dest_file.empty()) {
+        // Uses a default value until device provides the proper name
+        dest_file = "bugreport.zip";
+    } else {
+        if (!android::base::EndsWith(dest_file, ".zip")) {
+            // TODO: use a case-insensitive comparison (like EndsWithIgnoreCase
+            dest_file += ".zip";
+        }
+    }
+
+    bool show_progress = true;
+    std::string bugz_command = "bugreportz -p";
+    if (bugz_version == "1.0") {
+        // 1.0 does not support progress notifications, so print a disclaimer
+        // message instead.
+        fprintf(stderr,
+                "Bugreport is in progress and it could take minutes to complete.\n"
+                "Please be patient and do not cancel or disconnect your device "
+                "until it completes.\n");
+        show_progress = false;
+        bugz_command = "bugreportz";
+    }
+    BugreportStandardStreamsCallback bugz_callback(dest_dir, dest_file, show_progress, this);
+    return SendShellCommand(transport_type, serial, bugz_command, false, &bugz_callback);
+}
+
+void Bugreport::UpdateProgress(const std::string& message, int progress, int total) {
+    int progress_percentage = (progress * 100 / total);
+    line_printer_.Print(
+        android::base::StringPrintf("[%3d%%] %s", progress_percentage, message.c_str()),
+        LinePrinter::INFO);
+}
+
+int Bugreport::SendShellCommand(TransportType transport_type, const char* serial,
+                                const std::string& command, bool disable_shell_protocol,
+                                StandardStreamsCallbackInterface* callback) {
+    return send_shell_command(transport_type, serial, command, disable_shell_protocol, callback);
+}
+
+bool Bugreport::DoSyncPull(const std::vector<const char*>& srcs, const char* dst, bool copy_attrs,
+                           const char* name) {
+    return do_sync_pull(srcs, dst, copy_attrs, name);
+}
diff --git a/adb/bugreport.h b/adb/bugreport.h
new file mode 100644
index 0000000..ee99cbc
--- /dev/null
+++ b/adb/bugreport.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2016 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 BUGREPORT_H
+#define BUGREPORT_H
+
+#include <vector>
+
+#include "adb.h"
+#include "commandline.h"
+#include "line_printer.h"
+
+class Bugreport {
+    friend class BugreportStandardStreamsCallback;
+
+  public:
+    Bugreport() : line_printer_() {
+    }
+    int DoIt(TransportType transport_type, const char* serial, int argc, const char** argv);
+
+  protected:
+    // Functions below are abstractions of external functions so they can be
+    // mocked on tests.
+    virtual int SendShellCommand(
+        TransportType transport_type, const char* serial, const std::string& command,
+        bool disable_shell_protocol,
+        StandardStreamsCallbackInterface* callback = &DEFAULT_STANDARD_STREAMS_CALLBACK);
+
+    virtual bool DoSyncPull(const std::vector<const char*>& srcs, const char* dst, bool copy_attrs,
+                            const char* name);
+
+  private:
+    virtual void UpdateProgress(const std::string& file_name, int progress, int total);
+    LinePrinter line_printer_;
+    DISALLOW_COPY_AND_ASSIGN(Bugreport);
+};
+
+#endif  // BUGREPORT_H
diff --git a/adb/bugreport_test.cpp b/adb/bugreport_test.cpp
new file mode 100644
index 0000000..1129285
--- /dev/null
+++ b/adb/bugreport_test.cpp
@@ -0,0 +1,418 @@
+/*
+ * Copyright (C) 2016 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 "bugreport.h"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <android-base/strings.h>
+#include <android-base/test_utils.h>
+
+#include "sysdeps.h"
+#include "adb_utils.h"
+
+using ::testing::_;
+using ::testing::Action;
+using ::testing::ActionInterface;
+using ::testing::DoAll;
+using ::testing::ElementsAre;
+using ::testing::HasSubstr;
+using ::testing::MakeAction;
+using ::testing::Return;
+using ::testing::StrEq;
+using ::testing::WithArg;
+using ::testing::internal::CaptureStderr;
+using ::testing::internal::CaptureStdout;
+using ::testing::internal::GetCapturedStderr;
+using ::testing::internal::GetCapturedStdout;
+
+// Empty function so tests don't need to be linked against file_sync_service.cpp, which requires
+// SELinux and its transitive dependencies...
+bool do_sync_pull(const std::vector<const char*>& srcs, const char* dst, bool copy_attrs,
+                  const char* name) {
+    ADD_FAILURE() << "do_sync_pull() should have been mocked";
+    return false;
+}
+
+// Empty functions so tests don't need to be linked against commandline.cpp
+DefaultStandardStreamsCallback DEFAULT_STANDARD_STREAMS_CALLBACK(nullptr, nullptr);
+int usage() {
+    return -42;
+}
+int send_shell_command(TransportType transport_type, const char* serial, const std::string& command,
+                       bool disable_shell_protocol, StandardStreamsCallbackInterface* callback) {
+    ADD_FAILURE() << "send_shell_command() should have been mocked";
+    return -42;
+}
+
+enum StreamType {
+    kStreamStdout,
+    kStreamStderr,
+};
+
+// gmock black magic to provide a WithArg<4>(WriteOnStdout(output)) matcher
+typedef void OnStandardStreamsCallbackFunction(StandardStreamsCallbackInterface*);
+
+class OnStandardStreamsCallbackAction : public ActionInterface<OnStandardStreamsCallbackFunction> {
+  public:
+    explicit OnStandardStreamsCallbackAction(StreamType type, const std::string& output)
+        : type_(type), output_(output) {
+    }
+    virtual Result Perform(const ArgumentTuple& args) {
+        if (type_ == kStreamStdout) {
+            ::std::tr1::get<0>(args)->OnStdout(output_.c_str(), output_.size());
+        }
+        if (type_ == kStreamStderr) {
+            ::std::tr1::get<0>(args)->OnStderr(output_.c_str(), output_.size());
+        }
+    }
+
+  private:
+    StreamType type_;
+    std::string output_;
+};
+
+// Matcher used to emulated StandardStreamsCallbackInterface.OnStdout(buffer,
+// length)
+Action<OnStandardStreamsCallbackFunction> WriteOnStdout(const std::string& output) {
+    return MakeAction(new OnStandardStreamsCallbackAction(kStreamStdout, output));
+}
+
+// Matcher used to emulated StandardStreamsCallbackInterface.OnStderr(buffer,
+// length)
+Action<OnStandardStreamsCallbackFunction> WriteOnStderr(const std::string& output) {
+    return MakeAction(new OnStandardStreamsCallbackAction(kStreamStderr, output));
+}
+
+typedef int CallbackDoneFunction(StandardStreamsCallbackInterface*);
+
+class CallbackDoneAction : public ActionInterface<CallbackDoneFunction> {
+  public:
+    explicit CallbackDoneAction(int status) : status_(status) {
+    }
+    virtual Result Perform(const ArgumentTuple& args) {
+        int status = ::std::tr1::get<0>(args)->Done(status_);
+        return status;
+    }
+
+  private:
+    int status_;
+};
+
+// Matcher used to emulated StandardStreamsCallbackInterface.Done(status)
+Action<CallbackDoneFunction> ReturnCallbackDone(int status = -1337) {
+    return MakeAction(new CallbackDoneAction(status));
+}
+
+class BugreportMock : public Bugreport {
+  public:
+    MOCK_METHOD5(SendShellCommand,
+                 int(TransportType transport_type, const char* serial, const std::string& command,
+                     bool disable_shell_protocol, StandardStreamsCallbackInterface* callback));
+    MOCK_METHOD4(DoSyncPull, bool(const std::vector<const char*>& srcs, const char* dst,
+                                  bool copy_attrs, const char* name));
+    MOCK_METHOD3(UpdateProgress, void(const std::string&, int, int));
+};
+
+class BugreportTest : public ::testing::Test {
+  public:
+    void SetUp() {
+        if (!getcwd(&cwd_)) {
+            ADD_FAILURE() << "getcwd failed: " << strerror(errno);
+            return;
+        }
+    }
+
+    void ExpectBugreportzVersion(const std::string& version) {
+        EXPECT_CALL(br_,
+                    SendShellCommand(kTransportLocal, "HannibalLecter", "bugreportz -v", false, _))
+            .WillOnce(DoAll(WithArg<4>(WriteOnStderr(version.c_str())),
+                            WithArg<4>(ReturnCallbackDone(0))));
+    }
+
+    void ExpectProgress(int progress, int total, const std::string& file = "file.zip") {
+        EXPECT_CALL(br_, UpdateProgress(StrEq("generating " + file), progress, total));
+    }
+
+    BugreportMock br_;
+    std::string cwd_;  // TODO: make it static
+};
+
+// Tests when called with invalid number of arguments
+TEST_F(BugreportTest, InvalidNumberArgs) {
+    const char* args[] = {"bugreport", "to", "principal"};
+    ASSERT_EQ(-42, br_.DoIt(kTransportLocal, "HannibalLecter", 3, args));
+}
+
+// Tests the 'adb bugreport' option when the device does not support 'bugreportz' - it falls back
+// to the flat-file format ('bugreport' binary on device)
+TEST_F(BugreportTest, NoArgumentsPreNDevice) {
+    // clang-format off
+    EXPECT_CALL(br_, SendShellCommand(kTransportLocal, "HannibalLecter", "bugreportz -v", false, _))
+        .WillOnce(DoAll(WithArg<4>(WriteOnStderr("")),
+                        // Write some bogus output on stdout to make sure it's ignored
+                        WithArg<4>(WriteOnStdout("Dude, where is my bugreportz?")),
+                        WithArg<4>(ReturnCallbackDone(0))));
+    // clang-format on
+    std::string bugreport = "Reported the bug was.";
+    CaptureStdout();
+    EXPECT_CALL(br_, SendShellCommand(kTransportLocal, "HannibalLecter", "bugreport", false, _))
+        .WillOnce(DoAll(WithArg<4>(WriteOnStdout(bugreport)), Return(0)));
+
+    const char* args[] = {"bugreport"};
+    ASSERT_EQ(0, br_.DoIt(kTransportLocal, "HannibalLecter", 1, args));
+    ASSERT_THAT(GetCapturedStdout(), StrEq(bugreport));
+}
+
+// Tests the 'adb bugreport' option when the device supports 'bugreportz' version 1.0 - it will
+// save the bugreport in the current directory with the name provided by the device.
+TEST_F(BugreportTest, NoArgumentsNDevice) {
+    ExpectBugreportzVersion("1.0");
+
+    std::string dest_file =
+        android::base::StringPrintf("%s%cda_bugreport.zip", cwd_.c_str(), OS_PATH_SEPARATOR);
+    EXPECT_CALL(br_, SendShellCommand(kTransportLocal, "HannibalLecter", "bugreportz", false, _))
+        .WillOnce(DoAll(WithArg<4>(WriteOnStdout("OK:/device/da_bugreport.zip")),
+                        WithArg<4>(ReturnCallbackDone())));
+    EXPECT_CALL(br_, DoSyncPull(ElementsAre(StrEq("/device/da_bugreport.zip")), StrEq(dest_file),
+                                true, StrEq("pulling da_bugreport.zip")))
+        .WillOnce(Return(true));
+
+    const char* args[] = {"bugreport"};
+    ASSERT_EQ(0, br_.DoIt(kTransportLocal, "HannibalLecter", 1, args));
+}
+
+// Tests the 'adb bugreport' option when the device supports 'bugreportz' version 1.1 - it will
+// save the bugreport in the current directory with the name provided by the device.
+TEST_F(BugreportTest, NoArgumentsPostNDevice) {
+    ExpectBugreportzVersion("1.1");
+    std::string dest_file =
+        android::base::StringPrintf("%s%cda_bugreport.zip", cwd_.c_str(), OS_PATH_SEPARATOR);
+    ExpectProgress(50, 100, "da_bugreport.zip");
+    EXPECT_CALL(br_, SendShellCommand(kTransportLocal, "HannibalLecter", "bugreportz -p", false, _))
+        .WillOnce(DoAll(WithArg<4>(WriteOnStdout("BEGIN:/device/da_bugreport.zip\n")),
+                        WithArg<4>(WriteOnStdout("PROGRESS:50/100\n")),
+                        WithArg<4>(WriteOnStdout("OK:/device/da_bugreport.zip\n")),
+                        WithArg<4>(ReturnCallbackDone())));
+    EXPECT_CALL(br_, DoSyncPull(ElementsAre(StrEq("/device/da_bugreport.zip")), StrEq(dest_file),
+                                true, StrEq("pulling da_bugreport.zip")))
+        .WillOnce(Return(true));
+
+    const char* args[] = {"bugreport"};
+    ASSERT_EQ(0, br_.DoIt(kTransportLocal, "HannibalLecter", 1, args));
+}
+
+// Tests 'adb bugreport file.zip' when it succeeds and device does not support progress.
+TEST_F(BugreportTest, OkNDevice) {
+    ExpectBugreportzVersion("1.0");
+    EXPECT_CALL(br_, SendShellCommand(kTransportLocal, "HannibalLecter", "bugreportz", false, _))
+        .WillOnce(DoAll(WithArg<4>(WriteOnStdout("OK:/device/bugreport.zip")),
+                        WithArg<4>(ReturnCallbackDone())));
+    EXPECT_CALL(br_, DoSyncPull(ElementsAre(StrEq("/device/bugreport.zip")), StrEq("file.zip"),
+                                true, StrEq("pulling file.zip")))
+        .WillOnce(Return(true));
+
+    const char* args[] = {"bugreport", "file.zip"};
+    ASSERT_EQ(0, br_.DoIt(kTransportLocal, "HannibalLecter", 2, args));
+}
+
+// Tests 'adb bugreport file.zip' when it succeeds but response was sent in
+// multiple buffer writers and without progress updates.
+TEST_F(BugreportTest, OkNDeviceSplitBuffer) {
+    ExpectBugreportzVersion("1.0");
+    EXPECT_CALL(br_, SendShellCommand(kTransportLocal, "HannibalLecter", "bugreportz", false, _))
+        .WillOnce(DoAll(WithArg<4>(WriteOnStdout("OK:/device")),
+                        WithArg<4>(WriteOnStdout("/bugreport.zip")),
+                        WithArg<4>(ReturnCallbackDone())));
+    EXPECT_CALL(br_, DoSyncPull(ElementsAre(StrEq("/device/bugreport.zip")), StrEq("file.zip"),
+                                true, StrEq("pulling file.zip")))
+        .WillOnce(Return(true));
+
+    const char* args[] = {"bugreport", "file.zip"};
+    ASSERT_EQ(0, br_.DoIt(kTransportLocal, "HannibalLecter", 2, args));
+}
+
+// Tests 'adb bugreport file.zip' when it succeeds and displays progress.
+TEST_F(BugreportTest, OkProgress) {
+    ExpectBugreportzVersion("1.1");
+    ExpectProgress(1, 100);
+    ExpectProgress(10, 100);
+    ExpectProgress(50, 100);
+    ExpectProgress(99, 100);
+    // clang-format off
+    EXPECT_CALL(br_, SendShellCommand(kTransportLocal, "HannibalLecter", "bugreportz -p", false, _))
+        // NOTE: DoAll accepts at most 10 arguments, and we're almost reached that limit...
+        .WillOnce(DoAll(
+            // Name might change on OK, so make sure the right one is picked.
+            WithArg<4>(WriteOnStdout("BEGIN:/device/bugreport___NOT.zip\n")),
+            // Progress line in one write
+            WithArg<4>(WriteOnStdout("PROGRESS:1/100\n")),
+            // Add some bogus lines
+            WithArg<4>(WriteOnStdout("\nDUDE:SWEET\n\nBLA\n\nBLA\nBLA\n\n")),
+            // Multiple progress lines in one write
+            WithArg<4>(WriteOnStdout("PROGRESS:10/100\nPROGRESS:50/100\n")),
+            // Progress line in multiple writes
+            WithArg<4>(WriteOnStdout("PROG")),
+            WithArg<4>(WriteOnStdout("RESS:99")),
+            WithArg<4>(WriteOnStdout("/100\n")),
+            // Split last message as well, just in case
+            WithArg<4>(WriteOnStdout("OK:/device/bugreport")),
+            WithArg<4>(WriteOnStdout(".zip")),
+            WithArg<4>(ReturnCallbackDone())));
+    // clang-format on
+    EXPECT_CALL(br_, DoSyncPull(ElementsAre(StrEq("/device/bugreport.zip")), StrEq("file.zip"),
+                                true, StrEq("pulling file.zip")))
+        .WillOnce(Return(true));
+
+    const char* args[] = {"bugreport", "file.zip"};
+    ASSERT_EQ(0, br_.DoIt(kTransportLocal, "HannibalLecter", 2, args));
+}
+
+// Tests 'adb bugreport dir' when it succeeds and destination is a directory.
+TEST_F(BugreportTest, OkDirectory) {
+    ExpectBugreportzVersion("1.1");
+    TemporaryDir td;
+    std::string dest_file =
+        android::base::StringPrintf("%s%cda_bugreport.zip", td.path, OS_PATH_SEPARATOR);
+
+    EXPECT_CALL(br_, SendShellCommand(kTransportLocal, "HannibalLecter", "bugreportz -p", false, _))
+        .WillOnce(DoAll(WithArg<4>(WriteOnStdout("BEGIN:/device/da_bugreport.zip\n")),
+                        WithArg<4>(WriteOnStdout("OK:/device/da_bugreport.zip")),
+                        WithArg<4>(ReturnCallbackDone())));
+    EXPECT_CALL(br_, DoSyncPull(ElementsAre(StrEq("/device/da_bugreport.zip")), StrEq(dest_file),
+                                true, StrEq("pulling da_bugreport.zip")))
+        .WillOnce(Return(true));
+
+    const char* args[] = {"bugreport", td.path};
+    ASSERT_EQ(0, br_.DoIt(kTransportLocal, "HannibalLecter", 2, args));
+}
+
+// Tests 'adb bugreport file' when it succeeds
+TEST_F(BugreportTest, OkNoExtension) {
+    ExpectBugreportzVersion("1.1");
+    EXPECT_CALL(br_, SendShellCommand(kTransportLocal, "HannibalLecter", "bugreportz -p", false, _))
+        .WillOnce(DoAll(WithArg<4>(WriteOnStdout("OK:/device/bugreport.zip\n")),
+                        WithArg<4>(ReturnCallbackDone())));
+    EXPECT_CALL(br_, DoSyncPull(ElementsAre(StrEq("/device/bugreport.zip")), StrEq("file.zip"),
+                                true, StrEq("pulling file.zip")))
+        .WillOnce(Return(true));
+
+    const char* args[] = {"bugreport", "file"};
+    ASSERT_EQ(0, br_.DoIt(kTransportLocal, "HannibalLecter", 2, args));
+}
+
+// Tests 'adb bugreport dir' when it succeeds and destination is a directory and device runs N.
+TEST_F(BugreportTest, OkNDeviceDirectory) {
+    ExpectBugreportzVersion("1.0");
+    TemporaryDir td;
+    std::string dest_file =
+        android::base::StringPrintf("%s%cda_bugreport.zip", td.path, OS_PATH_SEPARATOR);
+
+    EXPECT_CALL(br_, SendShellCommand(kTransportLocal, "HannibalLecter", "bugreportz", false, _))
+        .WillOnce(DoAll(WithArg<4>(WriteOnStdout("BEGIN:/device/da_bugreport.zip\n")),
+                        WithArg<4>(WriteOnStdout("OK:/device/da_bugreport.zip")),
+                        WithArg<4>(ReturnCallbackDone())));
+    EXPECT_CALL(br_, DoSyncPull(ElementsAre(StrEq("/device/da_bugreport.zip")), StrEq(dest_file),
+                                true, StrEq("pulling da_bugreport.zip")))
+        .WillOnce(Return(true));
+
+    const char* args[] = {"bugreport", td.path};
+    ASSERT_EQ(0, br_.DoIt(kTransportLocal, "HannibalLecter", 2, args));
+}
+
+// Tests 'adb bugreport file.zip' when the bugreport itself failed
+TEST_F(BugreportTest, BugreportzReturnedFail) {
+    ExpectBugreportzVersion("1.1");
+    EXPECT_CALL(br_, SendShellCommand(kTransportLocal, "HannibalLecter", "bugreportz -p", false, _))
+        .WillOnce(
+            DoAll(WithArg<4>(WriteOnStdout("FAIL:D'OH!\n")), WithArg<4>(ReturnCallbackDone())));
+
+    CaptureStderr();
+    const char* args[] = {"bugreport", "file.zip"};
+    ASSERT_EQ(-1, br_.DoIt(kTransportLocal, "HannibalLecter", 2, args));
+    ASSERT_THAT(GetCapturedStderr(), HasSubstr("D'OH!"));
+}
+
+// Tests 'adb bugreport file.zip' when the bugreport itself failed but response
+// was sent in
+// multiple buffer writes
+TEST_F(BugreportTest, BugreportzReturnedFailSplitBuffer) {
+    ExpectBugreportzVersion("1.1");
+    EXPECT_CALL(br_, SendShellCommand(kTransportLocal, "HannibalLecter", "bugreportz -p", false, _))
+        .WillOnce(DoAll(WithArg<4>(WriteOnStdout("FAIL")), WithArg<4>(WriteOnStdout(":D'OH!\n")),
+                        WithArg<4>(ReturnCallbackDone())));
+
+    CaptureStderr();
+    const char* args[] = {"bugreport", "file.zip"};
+    ASSERT_EQ(-1, br_.DoIt(kTransportLocal, "HannibalLecter", 2, args));
+    ASSERT_THAT(GetCapturedStderr(), HasSubstr("D'OH!"));
+}
+
+// Tests 'adb bugreport file.zip' when the bugreportz returned an unsupported
+// response.
+TEST_F(BugreportTest, BugreportzReturnedUnsupported) {
+    ExpectBugreportzVersion("1.1");
+    EXPECT_CALL(br_, SendShellCommand(kTransportLocal, "HannibalLecter", "bugreportz -p", false, _))
+        .WillOnce(DoAll(WithArg<4>(WriteOnStdout("bugreportz? What am I, a zombie?")),
+                        WithArg<4>(ReturnCallbackDone())));
+
+    CaptureStderr();
+    const char* args[] = {"bugreport", "file.zip"};
+    ASSERT_EQ(-1, br_.DoIt(kTransportLocal, "HannibalLecter", 2, args));
+    ASSERT_THAT(GetCapturedStderr(), HasSubstr("bugreportz? What am I, a zombie?"));
+}
+
+// Tests 'adb bugreport file.zip' when the bugreportz -v command failed
+TEST_F(BugreportTest, BugreportzVersionFailed) {
+    EXPECT_CALL(br_, SendShellCommand(kTransportLocal, "HannibalLecter", "bugreportz -v", false, _))
+        .WillOnce(Return(666));
+
+    const char* args[] = {"bugreport", "file.zip"};
+    ASSERT_EQ(666, br_.DoIt(kTransportLocal, "HannibalLecter", 2, args));
+}
+
+// Tests 'adb bugreport file.zip' when the bugreportz -v returns status 0 but with no output.
+TEST_F(BugreportTest, BugreportzVersionEmpty) {
+    ExpectBugreportzVersion("");
+
+    const char* args[] = {"bugreport", "file.zip"};
+    ASSERT_EQ(-1, br_.DoIt(kTransportLocal, "HannibalLecter", 2, args));
+}
+
+// Tests 'adb bugreport file.zip' when the main bugreportz command failed
+TEST_F(BugreportTest, BugreportzFailed) {
+    ExpectBugreportzVersion("1.1");
+    EXPECT_CALL(br_, SendShellCommand(kTransportLocal, "HannibalLecter", "bugreportz -p", false, _))
+        .WillOnce(Return(666));
+
+    const char* args[] = {"bugreport", "file.zip"};
+    ASSERT_EQ(666, br_.DoIt(kTransportLocal, "HannibalLecter", 2, args));
+}
+
+// Tests 'adb bugreport file.zip' when the bugreport could not be pulled
+TEST_F(BugreportTest, PullFails) {
+    ExpectBugreportzVersion("1.1");
+    EXPECT_CALL(br_, SendShellCommand(kTransportLocal, "HannibalLecter", "bugreportz -p", false, _))
+        .WillOnce(DoAll(WithArg<4>(WriteOnStdout("OK:/device/bugreport.zip")),
+                        WithArg<4>(ReturnCallbackDone())));
+    EXPECT_CALL(br_, DoSyncPull(ElementsAre(StrEq("/device/bugreport.zip")), StrEq("file.zip"),
+                                true, HasSubstr("file.zip")))
+        .WillOnce(Return(false));
+
+    const char* args[] = {"bugreport", "file.zip"};
+    ASSERT_EQ(1, br_.DoIt(kTransportLocal, "HannibalLecter", 2, args));
+}
diff --git a/adb/client/main.cpp b/adb/client/main.cpp
index e160169..0c85fe5 100644
--- a/adb/client/main.cpp
+++ b/adb/client/main.cpp
@@ -32,6 +32,7 @@
 #include "adb_auth.h"
 #include "adb_listeners.h"
 #include "adb_utils.h"
+#include "commandline.h"
 #include "transport.h"
 
 static std::string GetLogFilePath() {
diff --git a/adb/commandline.cpp b/adb/commandline.cpp
index 8aab389..f8a5d13 100644
--- a/adb/commandline.cpp
+++ b/adb/commandline.cpp
@@ -52,10 +52,11 @@
 #include "adb_client.h"
 #include "adb_io.h"
 #include "adb_utils.h"
+#include "bugreport.h"
+#include "commandline.h"
 #include "file_sync_service.h"
 #include "services.h"
 #include "shell_service.h"
-#include "transport.h"
 
 static int install_app(TransportType t, const char* serial, int argc, const char** argv);
 static int install_multiple_app(TransportType t, const char* serial, int argc, const char** argv);
@@ -66,8 +67,7 @@
 static auto& gProductOutPath = *new std::string();
 extern int gListenAll;
 
-static constexpr char BUGZ_OK_PREFIX[] = "OK:";
-static constexpr char BUGZ_FAIL_PREFIX[] = "FAIL:";
+DefaultStandardStreamsCallback DEFAULT_STANDARD_STREAMS_CALLBACK(nullptr, nullptr);
 
 static std::string product_file(const char *extra) {
     if (gProductOutPath.empty()) {
@@ -82,6 +82,7 @@
 
 static void help() {
     fprintf(stderr, "%s\n", adb_version().c_str());
+    // clang-format off
     fprintf(stderr,
         " -a                            - directs adb to listen on all interfaces for a connection\n"
         " -d                            - directs command to the only connected USB device\n"
@@ -174,9 +175,11 @@
         "                                 (-g: grant all runtime permissions)\n"
         "  adb uninstall [-k] <package> - remove this app package from the device\n"
         "                                 ('-k' means keep the data and cache directories)\n"
-        "  adb bugreport [<zip_file>]   - return all information from the device\n"
-        "                                 that should be included in a bug report.\n"
-        "\n"
+        "  adb bugreport [<path>]       - return all information from the device that should be included in a zipped bug report.\n"
+        "                                 If <path> is a file, the bug report will be saved as that file.\n"
+        "                                 If <path> is a directory, the bug report will be saved in that directory with the name provided by the device.\n"
+        "                                 If <path> is omitted, the bug report will be saved in the current directory with the name provided by the device.\n"
+        "                                 NOTE: if the device does not support zipped bug reports, the bug report will be output on stdout.\n"
         "  adb backup [-f <file>] [-apk|-noapk] [-obb|-noobb] [-shared|-noshared] [-all] [-system|-nosystem] [<packages...>]\n"
         "                               - write an archive of the device's data to <file>.\n"
         "                                 If no -f option is supplied then the data is written\n"
@@ -250,11 +253,11 @@
         "  ADB_TRACE                    - Print debug information. A comma separated list of the following values\n"
         "                                 1 or all, adb, sockets, packets, rwx, usb, sync, sysdeps, transport, jdwp\n"
         "  ANDROID_SERIAL               - The serial number to connect to. -s takes priority over this if given.\n"
-        "  ANDROID_LOG_TAGS             - When used with the logcat option, only these debug tags are printed.\n"
-        );
+        "  ANDROID_LOG_TAGS             - When used with the logcat option, only these debug tags are printed.\n");
+    // clang-format on
 }
 
-static int usage() {
+int usage() {
     help();
     return 1;
 }
@@ -292,17 +295,14 @@
 // this expects that incoming data will use the shell protocol, in which case
 // stdout/stderr are routed independently and the remote exit code will be
 // returned.
-// if |output| is non-null, stdout will be appended to it instead.
-// if |err| is non-null, stderr will be appended to it instead.
-static int read_and_dump(int fd, bool use_shell_protocol=false, std::string* output=nullptr,
-                         std::string* err=nullptr) {
+// if |callback| is non-null, stdout/stderr output will be handled by it.
+int read_and_dump(int fd, bool use_shell_protocol = false,
+                  StandardStreamsCallbackInterface* callback = &DEFAULT_STANDARD_STREAMS_CALLBACK) {
     int exit_code = 0;
     if (fd < 0) return exit_code;
 
     std::unique_ptr<ShellProtocol> protocol;
     int length = 0;
-    FILE* outfile = stdout;
-    std::string* outstring = output;
 
     char raw_buffer[BUFSIZ];
     char* buffer_ptr = raw_buffer;
@@ -320,14 +320,13 @@
             if (!protocol->Read()) {
                 break;
             }
+            length = protocol->data_length();
             switch (protocol->id()) {
                 case ShellProtocol::kIdStdout:
-                    outfile = stdout;
-                    outstring = output;
+                    callback->OnStdout(buffer_ptr, length);
                     break;
                 case ShellProtocol::kIdStderr:
-                    outfile = stderr;
-                    outstring = err;
+                    callback->OnStderr(buffer_ptr, length);
                     break;
                 case ShellProtocol::kIdExit:
                     exit_code = protocol->data()[0];
@@ -343,17 +342,11 @@
             if (length <= 0) {
                 break;
             }
-        }
-
-        if (outstring == nullptr) {
-            fwrite(buffer_ptr, 1, length, outfile);
-            fflush(outfile);
-        } else {
-            outstring->append(buffer_ptr, length);
+            callback->OnStdout(buffer_ptr, length);
         }
     }
 
-    return exit_code;
+    return callback->Done(exit_code);
 }
 
 static void read_status_line(int fd, char* buf, size_t count)
@@ -1113,20 +1106,16 @@
     return true;
 }
 
-// Connects to the device "shell" service with |command| and prints the
-// resulting output.
-static int send_shell_command(TransportType transport_type, const char* serial,
-                              const std::string& command,
-                              bool disable_shell_protocol,
-                              std::string* output=nullptr,
-                              std::string* err=nullptr) {
+int send_shell_command(TransportType transport_type, const char* serial, const std::string& command,
+                       bool disable_shell_protocol, StandardStreamsCallbackInterface* callback) {
     int fd;
     bool use_shell_protocol = false;
 
     while (true) {
         bool attempt_connection = true;
 
-        // Use shell protocol if it's supported and the caller doesn't explicitly disable it.
+        // Use shell protocol if it's supported and the caller doesn't explicitly
+        // disable it.
         if (!disable_shell_protocol) {
             FeatureSet features;
             std::string error;
@@ -1148,13 +1137,13 @@
             }
         }
 
-        fprintf(stderr,"- waiting for device -\n");
+        fprintf(stderr, "- waiting for device -\n");
         if (!wait_for_device("wait-for-device", transport_type, serial)) {
             return 1;
         }
     }
 
-    int exit_code = read_and_dump(fd, use_shell_protocol, output, err);
+    int exit_code = read_and_dump(fd, use_shell_protocol, callback);
 
     if (adb_close(fd) < 0) {
         PLOG(ERROR) << "failure closing FD " << fd;
@@ -1163,45 +1152,6 @@
     return exit_code;
 }
 
-static int bugreport(TransportType transport_type, const char* serial, int argc,
-                     const char** argv) {
-    if (argc == 1) return send_shell_command(transport_type, serial, "bugreport", false);
-    if (argc != 2) return usage();
-
-    // Zipped bugreport option - will call 'bugreportz', which prints the location of the generated
-    // file, then pull it to the destination file provided by the user.
-    std::string dest_file = argv[1];
-    if (!android::base::EndsWith(argv[1], ".zip")) {
-        // TODO: use a case-insensitive comparison (like EndsWithIgnoreCase
-        dest_file += ".zip";
-    }
-    std::string output;
-
-    fprintf(stderr, "Bugreport is in progress and it could take minutes to complete.\n"
-            "Please be patient and do not cancel or disconnect your device until it completes.\n");
-    int status = send_shell_command(transport_type, serial, "bugreportz", false, &output, nullptr);
-    if (status != 0 || output.empty()) return status;
-    output = android::base::Trim(output);
-
-    if (android::base::StartsWith(output, BUGZ_OK_PREFIX)) {
-        const char* zip_file = &output[strlen(BUGZ_OK_PREFIX)];
-        std::vector<const char*> srcs{zip_file};
-        status = do_sync_pull(srcs, dest_file.c_str(), true, dest_file.c_str()) ? 0 : 1;
-        if (status != 0) {
-            fprintf(stderr, "Could not copy file '%s' to '%s'\n", zip_file, dest_file.c_str());
-        }
-        return status;
-    }
-    if (android::base::StartsWith(output, BUGZ_FAIL_PREFIX)) {
-        const char* error_message = &output[strlen(BUGZ_FAIL_PREFIX)];
-        fprintf(stderr, "Device failed to take a zipped bugreport: %s\n", error_message);
-        return -1;
-    }
-    fprintf(stderr, "Unexpected string (%s) returned by bugreportz, "
-            "device probably does not support -z option\n", output.c_str());
-    return -1;
-}
-
 static int logcat(TransportType transport, const char* serial, int argc, const char** argv) {
     char* log_tags = getenv("ANDROID_LOG_TAGS");
     std::string quoted = escape_arg(log_tags == nullptr ? "" : log_tags);
@@ -1337,7 +1287,7 @@
     if (hint.find_first_of(OS_PATH_SEPARATORS) != std::string::npos) {  // NOLINT
         std::string cwd;
         if (!getcwd(&cwd)) {
-            fprintf(stderr, "adb: getcwd failed: %s\n", strerror(errno));
+            perror("adb: getcwd failed");
             return "";
         }
         return android::base::StringPrintf("%s%c%s", cwd.c_str(), OS_PATH_SEPARATOR, hint.c_str());
@@ -1437,6 +1387,16 @@
 #endif
 }
 
+static bool _use_legacy_install() {
+    FeatureSet features;
+    std::string error;
+    if (!adb_get_feature_set(&features, &error)) {
+        fprintf(stderr, "error: %s\n", error.c_str());
+        return true;
+    }
+    return !CanUseFeature(features, kFeatureCmd);
+}
+
 int adb_commandline(int argc, const char **argv) {
     int no_daemon = 0;
     int is_daemon = 0;
@@ -1737,7 +1697,8 @@
     } else if (!strcmp(argv[0], "root") || !strcmp(argv[0], "unroot")) {
         return adb_root(argv[0]) ? 0 : 1;
     } else if (!strcmp(argv[0], "bugreport")) {
-        return bugreport(transport_type, serial, argc, argv);
+        Bugreport bugreport;
+        return bugreport.DoIt(transport_type, serial, argc, argv);
     } else if (!strcmp(argv[0], "forward") || !strcmp(argv[0], "reverse")) {
         bool reverse = !strcmp(argv[0], "reverse");
         ++argv;
@@ -1831,17 +1792,10 @@
     }
     else if (!strcmp(argv[0], "install")) {
         if (argc < 2) return usage();
-        FeatureSet features;
-        std::string error;
-        if (!adb_get_feature_set(&features, &error)) {
-            fprintf(stderr, "error: %s\n", error.c_str());
-            return 1;
+        if (_use_legacy_install()) {
+            return install_app_legacy(transport_type, serial, argc, argv);
         }
-
-        if (CanUseFeature(features, kFeatureCmd)) {
-            return install_app(transport_type, serial, argc, argv);
-        }
-        return install_app_legacy(transport_type, serial, argc, argv);
+        return install_app(transport_type, serial, argc, argv);
     }
     else if (!strcmp(argv[0], "install-multiple")) {
         if (argc < 2) return usage();
@@ -1849,17 +1803,10 @@
     }
     else if (!strcmp(argv[0], "uninstall")) {
         if (argc < 2) return usage();
-        FeatureSet features;
-        std::string error;
-        if (!adb_get_feature_set(&features, &error)) {
-            fprintf(stderr, "error: %s\n", error.c_str());
-            return 1;
+        if (_use_legacy_install()) {
+            return uninstall_app_legacy(transport_type, serial, argc, argv);
         }
-
-        if (CanUseFeature(features, kFeatureCmd)) {
-            return uninstall_app(transport_type, serial, argc, argv);
-        }
-        return uninstall_app_legacy(transport_type, serial, argc, argv);
+        return uninstall_app(transport_type, serial, argc, argv);
     }
     else if (!strcmp(argv[0], "sync")) {
         std::string src;
@@ -2073,7 +2020,6 @@
     int i;
     struct stat sb;
     uint64_t total_size = 0;
-
     // Find all APK arguments starting at end.
     // All other arguments passed through verbatim.
     int first_apk = -1;
@@ -2098,7 +2044,14 @@
         return 1;
     }
 
-    std::string cmd = android::base::StringPrintf("exec:pm install-create -S %" PRIu64, total_size);
+    std::string install_cmd;
+    if (_use_legacy_install()) {
+        install_cmd = "exec:pm";
+    } else {
+        install_cmd = "exec:cmd package";
+    }
+
+    std::string cmd = android::base::StringPrintf("%s install-create -S %" PRIu64, install_cmd.c_str(), total_size);
     for (i = 1; i < first_apk; i++) {
         cmd += " " + escape_arg(argv[i]);
     }
@@ -2140,8 +2093,8 @@
         }
 
         std::string cmd = android::base::StringPrintf(
-                "exec:pm install-write -S %" PRIu64 " %d %d_%s -",
-                static_cast<uint64_t>(sb.st_size), session_id, i, adb_basename(file).c_str());
+                "%s install-write -S %" PRIu64 " %d %d_%s -",
+                install_cmd.c_str(), static_cast<uint64_t>(sb.st_size), session_id, i, adb_basename(file).c_str());
 
         int localFd = adb_open(file, O_RDONLY);
         if (localFd < 0) {
@@ -2176,8 +2129,8 @@
 finalize_session:
     // Commit session if we streamed everything okay; otherwise abandon
     std::string service =
-            android::base::StringPrintf("exec:pm install-%s %d",
-                                        success ? "commit" : "abandon", session_id);
+            android::base::StringPrintf("%s install-%s %d",
+                                        install_cmd.c_str(), success ? "commit" : "abandon", session_id);
     fd = adb_connect(service, &error);
     if (fd < 0) {
         fprintf(stderr, "Connect error for finalize: %s\n", error.c_str());
diff --git a/adb/commandline.h b/adb/commandline.h
new file mode 100644
index 0000000..0cf655c
--- /dev/null
+++ b/adb/commandline.h
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2016 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 COMMANDLINE_H
+#define COMMANDLINE_H
+
+#include "adb.h"
+
+// Callback used to handle the standard streams (stdout and stderr) sent by the
+// device's upon receiving a command.
+//
+class StandardStreamsCallbackInterface {
+  public:
+    StandardStreamsCallbackInterface() {
+    }
+    // Handles the stdout output from devices supporting the Shell protocol.
+    virtual void OnStdout(const char* buffer, int length) = 0;
+
+    // Handles the stderr output from devices supporting the Shell protocol.
+    virtual void OnStderr(const char* buffer, int length) = 0;
+
+    // Indicates the communication is finished and returns the appropriate error
+    // code.
+    //
+    // |status| has the status code returning by the underlying communication
+    // channels
+    virtual int Done(int status) = 0;
+
+  protected:
+    static void OnStream(std::string* string, FILE* stream, const char* buffer, int length) {
+        if (string != nullptr) {
+            string->append(buffer, length);
+        } else {
+            fwrite(buffer, 1, length, stream);
+            fflush(stream);
+        }
+    }
+
+  private:
+    DISALLOW_COPY_AND_ASSIGN(StandardStreamsCallbackInterface);
+};
+
+// Default implementation that redirects the streams to the equilavent host
+// stream or to a string
+// passed to the constructor.
+class DefaultStandardStreamsCallback : public StandardStreamsCallbackInterface {
+  public:
+    // If |stdout_str| is non-null, OnStdout will append to it.
+    // If |stderr_str| is non-null, OnStderr will append to it.
+    DefaultStandardStreamsCallback(std::string* stdout_str, std::string* stderr_str)
+        : stdout_str_(stdout_str), stderr_str_(stderr_str) {
+    }
+
+    void OnStdout(const char* buffer, int length) {
+        OnStream(stdout_str_, stdout, buffer, length);
+    }
+
+    void OnStderr(const char* buffer, int length) {
+        OnStream(stderr_str_, stderr, buffer, length);
+    }
+
+    int Done(int status) {
+        return status;
+    }
+
+  private:
+    std::string* stdout_str_;
+    std::string* stderr_str_;
+
+    DISALLOW_COPY_AND_ASSIGN(DefaultStandardStreamsCallback);
+};
+
+// Singleton.
+extern DefaultStandardStreamsCallback DEFAULT_STANDARD_STREAMS_CALLBACK;
+
+int adb_commandline(int argc, const char** argv);
+int usage();
+
+// Connects to the device "shell" service with |command| and prints the
+// resulting output.
+// if |callback| is non-null, stdout/stderr output will be handled by it.
+int send_shell_command(TransportType transport_type, const char* serial, const std::string& command,
+                       bool disable_shell_protocol, StandardStreamsCallbackInterface* callback =
+                                                        &DEFAULT_STANDARD_STREAMS_CALLBACK);
+
+#endif  // COMMANDLINE_H
diff --git a/debuggerd/debuggerd.cpp b/debuggerd/debuggerd.cpp
index 8834209..8f08c08 100644
--- a/debuggerd/debuggerd.cpp
+++ b/debuggerd/debuggerd.cpp
@@ -183,6 +183,16 @@
    return allowed;
 }
 
+static bool pid_contains_tid(pid_t pid, pid_t tid) {
+  char task_path[PATH_MAX];
+  if (snprintf(task_path, PATH_MAX, "/proc/%d/task/%d", pid, tid) >= PATH_MAX) {
+    ALOGE("debuggerd: task path overflow (pid = %d, tid = %d)\n", pid, tid);
+    exit(1);
+  }
+
+  return access(task_path, F_OK) == 0;
+}
+
 static int read_request(int fd, debugger_request_t* out_request) {
   ucred cr;
   socklen_t len = sizeof(cr);
@@ -224,16 +234,13 @@
 
   if (msg.action == DEBUGGER_ACTION_CRASH) {
     // Ensure that the tid reported by the crashing process is valid.
-    char buf[64];
-    struct stat s;
-    snprintf(buf, sizeof buf, "/proc/%d/task/%d", out_request->pid, out_request->tid);
-    if (stat(buf, &s)) {
-      ALOGE("tid %d does not exist in pid %d. ignoring debug request\n",
-          out_request->tid, out_request->pid);
+    // This check needs to happen again after ptracing the requested thread to prevent a race.
+    if (!pid_contains_tid(out_request->pid, out_request->tid)) {
+      ALOGE("tid %d does not exist in pid %d. ignoring debug request\n", out_request->tid,
+            out_request->pid);
       return -1;
     }
-  } else if (cr.uid == 0
-            || (cr.uid == AID_SYSTEM && msg.action == DEBUGGER_ACTION_DUMP_BACKTRACE)) {
+  } else if (cr.uid == 0 || (cr.uid == AID_SYSTEM && msg.action == DEBUGGER_ACTION_DUMP_BACKTRACE)) {
     // Only root or system can ask us to attach to any process and dump it explicitly.
     // However, system is only allowed to collect backtraces but cannot dump tombstones.
     status = get_process_info(out_request->tid, &out_request->pid,
@@ -410,10 +417,31 @@
 }
 #endif
 
-static void ptrace_siblings(pid_t pid, pid_t main_tid, std::set<pid_t>& tids) {
-  char task_path[64];
+// Attach to a thread, and verify that it's still a member of the given process
+static bool ptrace_attach_thread(pid_t pid, pid_t tid) {
+  if (ptrace(PTRACE_ATTACH, tid, 0, 0) != 0) {
+    return false;
+  }
 
-  snprintf(task_path, sizeof(task_path), "/proc/%d/task", pid);
+  // Make sure that the task we attached to is actually part of the pid we're dumping.
+  if (!pid_contains_tid(pid, tid)) {
+    if (ptrace(PTRACE_DETACH, tid, 0, 0) != 0) {
+      ALOGE("debuggerd: failed to detach from thread '%d'", tid);
+      exit(1);
+    }
+    return false;
+  }
+
+  return true;
+}
+
+static void ptrace_siblings(pid_t pid, pid_t main_tid, std::set<pid_t>& tids) {
+  char task_path[PATH_MAX];
+
+  if (snprintf(task_path, PATH_MAX, "/proc/%d/task", pid) >= PATH_MAX) {
+    ALOGE("debuggerd: task path overflow (pid = %d)\n", pid);
+    abort();
+  }
 
   std::unique_ptr<DIR, int (*)(DIR*)> d(opendir(task_path), closedir);
 
@@ -440,7 +468,7 @@
       continue;
     }
 
-    if (ptrace(PTRACE_ATTACH, tid, 0, 0) < 0) {
+    if (!ptrace_attach_thread(pid, tid)) {
       ALOGE("debuggerd: ptrace attach to %d failed: %s", tid, strerror(errno));
       continue;
     }
@@ -566,11 +594,33 @@
   // debugger_signal_handler().
 
   // Attach to the target process.
-  if (ptrace(PTRACE_ATTACH, request.tid, 0, 0) != 0) {
+  if (!ptrace_attach_thread(request.pid, request.tid)) {
     ALOGE("debuggerd: ptrace attach failed: %s", strerror(errno));
     exit(1);
   }
 
+  // DEBUGGER_ACTION_CRASH requests can come from arbitrary processes and the tid field in the
+  // request is sent from the other side. If an attacker can cause a process to be spawned with the
+  // pid of their process, they could trick debuggerd into dumping that process by exiting after
+  // sending the request. Validate the trusted request.uid/gid to defend against this.
+  if (request.action == DEBUGGER_ACTION_CRASH) {
+    pid_t pid;
+    uid_t uid;
+    gid_t gid;
+    if (get_process_info(request.tid, &pid, &uid, &gid) != 0) {
+      ALOGE("debuggerd: failed to get process info for tid '%d'", request.tid);
+      exit(1);
+    }
+
+    if (pid != request.pid || uid != request.uid || gid != request.gid) {
+      ALOGE(
+        "debuggerd: attached task %d does not match request: "
+        "expected pid=%d,uid=%d,gid=%d, actual pid=%d,uid=%d,gid=%d",
+        request.tid, request.pid, request.uid, request.gid, pid, uid, gid);
+      exit(1);
+    }
+  }
+
   // Don't attach to the sibling threads if we want to attach gdb.
   // Supposedly, it makes the process less reliable.
   bool attach_gdb = should_attach_gdb(request);
diff --git a/include/sysutils/FrameworkListener.h b/include/sysutils/FrameworkListener.h
index 18049cd..2137069 100644
--- a/include/sysutils/FrameworkListener.h
+++ b/include/sysutils/FrameworkListener.h
@@ -32,6 +32,7 @@
     int mCommandCount;
     bool mWithSeq;
     FrameworkCommandCollection *mCommands;
+    bool mSkipToNextNullByte;
 
 public:
     FrameworkListener(const char *socketName);
diff --git a/libsysutils/src/FrameworkListener.cpp b/libsysutils/src/FrameworkListener.cpp
index e7b3dd6..579ead9 100644
--- a/libsysutils/src/FrameworkListener.cpp
+++ b/libsysutils/src/FrameworkListener.cpp
@@ -49,6 +49,7 @@
     errorRate = 0;
     mCommandCount = 0;
     mWithSeq = withSeq;
+    mSkipToNextNullByte = false;
 }
 
 bool FrameworkListener::onDataAvailable(SocketClient *c) {
@@ -59,10 +60,15 @@
     if (len < 0) {
         SLOGE("read() failed (%s)", strerror(errno));
         return false;
-    } else if (!len)
+    } else if (!len) {
         return false;
-   if(buffer[len-1] != '\0')
+    } else if (buffer[len-1] != '\0') {
         SLOGW("String is not zero-terminated");
+        android_errorWriteLog(0x534e4554, "29831647");
+        c->sendMsg(500, "Command too large for buffer", false);
+        mSkipToNextNullByte = true;
+        return false;
+    }
 
     int offset = 0;
     int i;
@@ -70,11 +76,16 @@
     for (i = 0; i < len; i++) {
         if (buffer[i] == '\0') {
             /* IMPORTANT: dispatchCommand() expects a zero-terminated string */
-            dispatchCommand(c, buffer + offset);
+            if (mSkipToNextNullByte) {
+                mSkipToNextNullByte = false;
+            } else {
+                dispatchCommand(c, buffer + offset);
+            }
             offset = i + 1;
         }
     }
 
+    mSkipToNextNullByte = false;
     return true;
 }