Snap for 5652689 from 4139744db0a1c14bf22982e5e8e0288ce334e046 to qt-c2f2-release

Change-Id: I34a24d2ec4bc8055ab8b5a683844ac66b747ea05
diff --git a/car-bugreportd/Android.bp b/car-bugreportd/Android.bp
index e026d0b..36420f0 100644
--- a/car-bugreportd/Android.bp
+++ b/car-bugreportd/Android.bp
@@ -29,7 +29,11 @@
     ],
     shared_libs: [
         "libbase",
-        "liblog",
         "libcutils",
+        "libgui",
+        "libhwui",
+        "liblog",
+        "libui",
+        "libziparchive",
     ],
 }
diff --git a/car-bugreportd/car-bugreportd.rc b/car-bugreportd/car-bugreportd.rc
index 4c405e1..0935a60 100644
--- a/car-bugreportd/car-bugreportd.rc
+++ b/car-bugreportd/car-bugreportd.rc
@@ -1,6 +1,7 @@
 service car-bugreportd /system/bin/car-bugreportd
     socket car_br_progress_socket stream 0660 shell log
     socket car_br_output_socket stream 0660 shell log
+    socket car_br_extra_output_socket stream 0660 shell log
     class core
     user shell
     group log
diff --git a/car-bugreportd/main.cpp b/car-bugreportd/main.cpp
index 055d35a..f2ef87c 100644
--- a/car-bugreportd/main.cpp
+++ b/car-bugreportd/main.cpp
@@ -27,25 +27,34 @@
 #include <cutils/sockets.h>
 #include <errno.h>
 #include <fcntl.h>
+#include <ftw.h>
+#include <gui/SurfaceComposerClient.h>
 #include <log/log_main.h>
 #include <private/android_filesystem_config.h>
 #include <stdio.h>
 #include <stdlib.h>
+#include <sys/prctl.h>
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <sys/wait.h>
 #include <time.h>
 #include <unistd.h>
+#include <ziparchive/zip_writer.h>
 
 #include <chrono>
 #include <string>
 #include <vector>
 
 namespace {
+// Directory used for keeping temporary files
+constexpr const char* kTempDirectory = "/data/user_de/0/com.android.shell/temp_bugreport_files";
 // Socket to write the progress information.
 constexpr const char* kCarBrProgressSocket = "car_br_progress_socket";
 // Socket to write the zipped bugreport file.
 constexpr const char* kCarBrOutputSocket = "car_br_output_socket";
+// Socket to write the extra bugreport zip file. This zip file contains data that does not exist
+// in bugreport file generated by dumpstate.
+constexpr const char* kCarBrExtraOutputSocket = "car_br_extra_output_socket";
 // The prefix used by bugreportz protocol to indicate bugreport finished successfully.
 constexpr const char* kOkPrefix = "OK:";
 // Number of connect attempts to dumpstate socket
@@ -55,6 +64,13 @@
 // Wait time for dumpstate. No timeout in dumpstate is longer than 60 seconds. Choose
 // a value that is twice longer.
 constexpr const int kDumpstateTimeoutInSec = 120;
+// The prefix for screenshot filename in the generated zip file.
+constexpr const char* kScreenshotPrefix = "/screenshot";
+
+using android::OK;
+using android::PhysicalDisplayId;
+using android::status_t;
+using android::SurfaceComposerClient;
 
 // Returns a valid socket descriptor or -1 on failure.
 int openSocket(const char* service) {
@@ -96,6 +112,66 @@
     return;
 }
 
+// Sends the contents of the zip fileto |outfd|.
+// Returns true if success
+void zipFilesToFd(const std::vector<std::string>& extra_files, int outfd) {
+    // pass fclose as Deleter to close the file when unique_ptr is destroyed.
+    std::unique_ptr<FILE, decltype(fclose)*> outfile = {fdopen(outfd, "wb"), fclose};
+    if (outfile == nullptr) {
+        ALOGE("Failed to open output descriptor");
+        return;
+    }
+    auto writer = std::make_unique<ZipWriter>(outfile.get());
+
+    int error = 0;
+    for (const auto& filepath : extra_files) {
+        const auto name = android::base::Basename(filepath);
+
+        error = writer->StartEntry(name.c_str(), 0);
+        if (error) {
+            ALOGE("Failed to start entry %s", writer->ErrorCodeString(error));
+            return;
+        }
+        android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(filepath.c_str(), O_RDONLY)));
+        if (fd == -1) {
+            return;
+        }
+        while (1) {
+            char buffer[65536];
+
+            ssize_t bytes_read = TEMP_FAILURE_RETRY(read(fd, buffer, sizeof(buffer)));
+            if (bytes_read == 0) {
+                break;
+            }
+            if (bytes_read == -1) {
+                if (errno == EAGAIN) {
+                    ALOGE("timed out while reading %s", name.c_str());
+                } else {
+                    ALOGE("read terminated abnormally (%s)", strerror(errno));
+                }
+                // fail immediately
+                return;
+            }
+            error = writer->WriteBytes(buffer, bytes_read);
+            if (error) {
+                ALOGE("WriteBytes() failed %s", ZipWriter::ErrorCodeString(error));
+                // fail immediately
+                return;
+            }
+        }
+
+        error = writer->FinishEntry();
+        if (error) {
+            ALOGE("failed to finish entry %s", writer->ErrorCodeString(error));
+            continue;
+        }
+    }
+    error = writer->Finish();
+    if (error) {
+        ALOGE("failed to finish zip writer %s", writer->ErrorCodeString(error));
+    }
+}
+
 int copyTo(int fd_in, int fd_out, void* buffer, size_t buffer_len) {
     ssize_t bytes_read = TEMP_FAILURE_RETRY(read(fd_in, buffer, buffer_len));
     if (bytes_read == 0) {
@@ -182,6 +258,7 @@
                 line.append(1, c);
             }
         }
+        *out_bytes_written += bytes_read;
     }
     s.reset();
     // Process final line, in case it didn't finish with newline.
@@ -194,6 +271,168 @@
     return true;
 }
 
+bool waitpid_with_timeout(pid_t pid, int timeout_secs, int* status) {
+    sigset_t child_mask, old_mask;
+    sigemptyset(&child_mask);
+    sigaddset(&child_mask, SIGCHLD);
+
+    if (sigprocmask(SIG_BLOCK, &child_mask, &old_mask) == -1) {
+        ALOGE("*** sigprocmask failed: %s\n", strerror(errno));
+        return false;
+    }
+
+    timespec ts = {.tv_sec = timeout_secs, .tv_nsec = 0};
+    int ret = TEMP_FAILURE_RETRY(sigtimedwait(&child_mask, nullptr, &ts));
+    int saved_errno = errno;
+
+    // Set the signals back the way they were.
+    if (sigprocmask(SIG_SETMASK, &old_mask, nullptr) == -1) {
+        ALOGE("*** sigprocmask failed: %s\n", strerror(errno));
+        if (ret == 0) {
+            return false;
+        }
+    }
+    if (ret == -1) {
+        errno = saved_errno;
+        if (errno == EAGAIN) {
+            errno = ETIMEDOUT;
+        } else {
+            ALOGE("*** sigtimedwait failed: %s\n", strerror(errno));
+        }
+        return false;
+    }
+
+    pid_t child_pid = waitpid(pid, status, WNOHANG);
+    if (child_pid != pid) {
+        if (child_pid != -1) {
+            ALOGE("*** Waiting for pid %d, got pid %d instead\n", pid, child_pid);
+        } else {
+            ALOGE("*** waitpid failed: %s\n", strerror(errno));
+        }
+        return false;
+    }
+    return true;
+}
+
+// Runs the given command. Kills the command if it does not finish by timeout.
+int runCommand(int timeout_secs, const char* file, std::vector<const char*> args) {
+    pid_t pid = fork();
+
+    // handle error case
+    if (pid < 0) {
+        ALOGE("fork failed %s", strerror(errno));
+        return pid;
+    }
+
+    // handle child case
+    if (pid == 0) {
+        /* make sure the child dies when parent dies */
+        prctl(PR_SET_PDEATHSIG, SIGKILL);
+
+        /* just ignore SIGPIPE, will go down with parent's */
+        struct sigaction sigact;
+        memset(&sigact, 0, sizeof(sigact));
+        sigact.sa_handler = SIG_IGN;
+        sigaction(SIGPIPE, &sigact, nullptr);
+
+        execvp(file, (char**)args.data());
+        // execvp's result will be handled after waitpid_with_timeout() below, but
+        // if it failed, it's safer to exit dumpstate.
+        ALOGE("execvp on command %s failed (error: %s)", file, strerror(errno));
+        _exit(EXIT_FAILURE);
+    }
+
+    // handle parent case
+    int status;
+    bool ret = waitpid_with_timeout(pid, timeout_secs, &status);
+
+    if (!ret) {
+        if (errno == ETIMEDOUT) {
+            ALOGE("command %s timed out (killing pid %d)", file, pid);
+        } else {
+            ALOGE("command %s: Error (killing pid %d)\n", file, pid);
+        }
+        kill(pid, SIGTERM);
+        if (!waitpid_with_timeout(pid, 5, nullptr)) {
+            kill(pid, SIGKILL);
+            if (!waitpid_with_timeout(pid, 5, nullptr)) {
+                ALOGE("could not kill command '%s' (pid %d) even with SIGKILL.\n", file, pid);
+            }
+        }
+        return -1;
+    }
+
+    if (WIFSIGNALED(status)) {
+        ALOGE("command '%s' failed: killed by signal %d\n", file, WTERMSIG(status));
+    } else if (WIFEXITED(status) && WEXITSTATUS(status) > 0) {
+        status = WEXITSTATUS(status);
+        ALOGE("command '%s' failed: exit code %d\n", file, status);
+    }
+
+    return status;
+}
+
+void takeScreenshotForDisplayId(int id, const char* tmp_dir,
+        std::vector<std::string>* extra_files) {
+    std::string id_as_string = std::to_string(id);
+    std::string filename = std::string(tmp_dir) + kScreenshotPrefix + id_as_string + ".png";
+    std::vector<const char*> args { "-p", "-d", id_as_string.c_str(), filename.c_str(), nullptr };
+    ALOGI("capturing screen for display (%d) as %s", id, filename.c_str());
+    int status = runCommand(10, "/system/bin/screencap", args);
+    if (status == 0) {
+        LOG(INFO) << "Screenshot saved for display:" << id_as_string;
+    }
+    // add the file regardless of the exit status of the screencap util.
+    extra_files->push_back(filename);
+
+    LOG(ERROR) << "Failed to take screenshot for display:" << id_as_string;
+}
+
+void takeScreenshot(const char* tmp_dir, std::vector<std::string>* extra_files) {
+    // Now send the screencaptures
+    std::vector<PhysicalDisplayId> ids = SurfaceComposerClient::getPhysicalDisplayIds();
+
+    for (PhysicalDisplayId display_id : ids) {
+        takeScreenshotForDisplayId(display_id, tmp_dir, extra_files);
+    }
+}
+
+bool recursiveRemoveDir(const std::string& path) {
+    auto callback = [](const char* child, const struct stat*, int file_type, struct FTW*) -> int {
+        if (file_type == FTW_DP) {
+            if (rmdir(child) == -1) {
+                ALOGE("rmdir(%s): %s", child, strerror(errno));
+                return -1;
+            }
+        } else if (file_type == FTW_F) {
+            if (unlink(child) == -1) {
+                ALOGE("unlink(%s): %s", child, strerror(errno));
+                return -1;
+            }
+        }
+        return 0;
+    };
+    // do a file tree walk with a sufficiently large depth.
+    return nftw(path.c_str(), callback, 128, FTW_DEPTH) == 0;
+}
+
+status_t createTempDir(const char* dir) {
+    struct stat sb;
+    if (TEMP_FAILURE_RETRY(stat(dir, &sb)) == 0) {
+        if (!recursiveRemoveDir(dir)) {
+            return -errno;
+        }
+    } else if (errno != ENOENT) {
+        ALOGE("Failed to stat %s ", dir);
+        return -errno;
+    }
+    if (TEMP_FAILURE_RETRY(mkdir(dir, 0700)) == -1) {
+        ALOGE("Failed to mkdir %s", dir);
+        return -errno;
+    }
+    return OK;
+}
+
 // Removes bugreport
 void cleanupBugreportFile(const std::string& zip_path) {
     if (unlink(zip_path.c_str()) != 0) {
@@ -204,10 +443,16 @@
 }  // namespace
 
 int main(void) {
-    ALOGE("Starting bugreport collecting service");
+    ALOGI("Starting bugreport collecting service");
 
     auto t0 = std::chrono::steady_clock::now();
 
+    std::vector<std::string> extra_files;
+    if (createTempDir(kTempDirectory) == OK) {
+        // take screenshots of the physical displays as early as possible
+        takeScreenshot(kTempDirectory, &extra_files);
+    }
+
     // Start the dumpstatez service.
     android::base::SetProperty("ctl.start", "car-dumpstatez");
 
@@ -231,6 +476,14 @@
         close(output_socket);
     }
 
+    int extra_output_socket = openSocket(kCarBrExtraOutputSocket);
+    if (extra_output_socket != -1 && ret_val) {
+        zipFilesToFd(extra_files, extra_output_socket);
+    }
+    if (extra_output_socket != -1) {
+        close(extra_output_socket);
+    }
+
     auto delta = std::chrono::duration_cast<std::chrono::duration<double>>(
                      std::chrono::steady_clock::now() - t0)
                      .count();
@@ -239,6 +492,8 @@
     ALOGI("bugreport %s in %.02fs, %zu bytes written", result.c_str(), delta, bytes_written);
     cleanupBugreportFile(zip_path);
 
+    recursiveRemoveDir(kTempDirectory);
+
     // No matter how doBugreport() finished, let's try to explicitly stop
     // car-dumpstatez in case it stalled.
     android::base::SetProperty("ctl.stop", "car-dumpstatez");
diff --git a/car-lib/src/android/car/CarBugreportManager.java b/car-lib/src/android/car/CarBugreportManager.java
index b0f7a03..f5d3b1d 100644
--- a/car-lib/src/android/car/CarBugreportManager.java
+++ b/car-lib/src/android/car/CarBugreportManager.java
@@ -159,26 +159,39 @@
     }
 
     /**
-     * Request a bug report. A zipped bugreport is generated in the background.
+     * Request a bug report. A zipped (i.e. legacy) bugreport is generated in the background
+     * using dumpstate. This API also generates extra files that does not exist in the legacy
+     * bugreport and makes them available through a extra output file. Currently the extra
+     * output contains the screenshots for all the physical displays.
      *
      * <p>The file descriptor is closed when bugreport is written or if an exception happens.
      *
+     * <p>This method is enabled only for one bug reporting app. It can be configured using
+     * {@code config_car_bugreport_application} string that is defined in
+     * {@code packages/services/Car/service/res/values/config.xml}. To learn more please
+     * see {@code packages/services/Car/tests/BugReportApp/README.md}.
+     *
      * @param output the zipped bugreport file
+     * @param extraOutput a zip file that contains extra files generated for automotive.
      * @param callback  the callback for reporting dump status
      */
     @RequiresPermission(android.Manifest.permission.DUMP)
-    public void requestZippedBugreport(
-            @NonNull ParcelFileDescriptor output, @NonNull CarBugreportManagerCallback callback) {
+    public void requestBugreport(
+            @NonNull ParcelFileDescriptor output,
+            @NonNull ParcelFileDescriptor extraOutput,
+            @NonNull CarBugreportManagerCallback callback) {
         Preconditions.checkNotNull(output);
+        Preconditions.checkNotNull(extraOutput);
         Preconditions.checkNotNull(callback);
         try {
             CarBugreportManagerCallbackWrapper wrapper =
                     new CarBugreportManagerCallbackWrapper(callback, mHandler);
-            mService.requestZippedBugreport(output, wrapper);
+            mService.requestBugreport(output, extraOutput, wrapper);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         } finally {
             IoUtils.closeQuietly(output);
+            IoUtils.closeQuietly(extraOutput);
         }
     }
 
diff --git a/car-lib/src/android/car/ICarBugreportService.aidl b/car-lib/src/android/car/ICarBugreportService.aidl
index 1ec4e91..2673c80 100644
--- a/car-lib/src/android/car/ICarBugreportService.aidl
+++ b/car-lib/src/android/car/ICarBugreportService.aidl
@@ -28,7 +28,10 @@
     /**
      * Starts bugreport service to capture a zipped bugreport. The caller needs to provide
      * two file descriptors. The "output" file descriptor will be used to provide the actual
-     * zip file. The file descriptor is written by the service and will be read by the client.
+     * zip file. The "extra_output" file descriptor will be provided to add files that does not
+     * exist in the original file.
+     * The file descriptor is written by the service and will be read by the client.
      */
-    void requestZippedBugreport(in ParcelFileDescriptor output, ICarBugreportCallback callback) = 1;
+    void requestBugreport(in ParcelFileDescriptor output,
+            in ParcelFileDescriptor extraOutput, ICarBugreportCallback callback) = 1;
  }
diff --git a/car-lib/src/android/car/vms/VmsPublisherClientService.java b/car-lib/src/android/car/vms/VmsPublisherClientService.java
index b89c558..b4dcc68 100644
--- a/car-lib/src/android/car/vms/VmsPublisherClientService.java
+++ b/car-lib/src/android/car/vms/VmsPublisherClientService.java
@@ -21,11 +21,14 @@
 import android.annotation.SystemApi;
 import android.app.Service;
 import android.content.Intent;
+import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
+import android.os.Process;
 import android.os.RemoteException;
+import android.os.UserHandle;
 import android.util.Log;
 
 import com.android.internal.annotations.GuardedBy;
@@ -219,8 +222,9 @@
         }
 
         @Override
-        public void setVmsPublisherService(IBinder token, IVmsPublisherService service)
-                throws RemoteException {
+        public void setVmsPublisherService(IBinder token, IVmsPublisherService service) {
+            assertSystemOrSelf();
+
             VmsPublisherClientService vmsPublisherClientService = mVmsPublisherClientService.get();
             if (vmsPublisherClientService == null) return;
             if (DBG) {
@@ -233,8 +237,9 @@
         }
 
         @Override
-        public void onVmsSubscriptionChange(VmsSubscriptionState subscriptionState)
-                throws RemoteException {
+        public void onVmsSubscriptionChange(VmsSubscriptionState subscriptionState) {
+            assertSystemOrSelf();
+
             VmsPublisherClientService vmsPublisherClientService = mVmsPublisherClientService.get();
             if (vmsPublisherClientService == null) return;
             if (DBG) {
@@ -255,6 +260,13 @@
                     handler.obtainMessage(VmsEventHandler.ON_SUBSCRIPTION_CHANGE_EVENT,
                             subscriptionState));
         }
+
+        private void assertSystemOrSelf() {
+            if (!(Binder.getCallingUid() == UserHandle.USER_SYSTEM
+                    || Binder.getCallingPid() == Process.myPid())) {
+                throw new SecurityException("Caller must be system user or same process");
+            }
+        }
     }
 
     /**
diff --git a/car_product/sepolicy/private/file_contexts b/car_product/sepolicy/private/file_contexts
index 908c853..9666f70 100644
--- a/car_product/sepolicy/private/file_contexts
+++ b/car_product/sepolicy/private/file_contexts
@@ -6,3 +6,4 @@
 /system/bin/car-bugreportd  u:object_r:dumpstate_exec:s0
 /dev/socket/car_br_progress_socket  u:object_r:dumpstate_socket:s0
 /dev/socket/car_br_output_socket  u:object_r:dumpstate_socket:s0
+/dev/socket/car_br_extra_output_socket u:object_r:dumpstate_socket:s0
diff --git a/service/res/values/config.xml b/service/res/values/config.xml
index 4aef4a5..ef75d63 100644
--- a/service/res/values/config.xml
+++ b/service/res/values/config.xml
@@ -155,12 +155,14 @@
          Every item in this array contains a flatten component name of a service that needs to be
          started and a list of parameters after hashtag symbol. Here's the format:
 
-         <item>com.bar.foo/.Service#bind={true|false},user={all|system|foreground},trigger={asap,userUnlocked}</item>
+         <item>com.bar.foo/.Service#bind={bind|start|startForeground},user={all|system|foreground},
+         trigger={asap,userUnlocked}</item>
 
-         bind: indicates whether service needs to be bound or started, if the value is 'true'
-               the service will be bound by calling Context#bindService(...), otherwise it will
-               be started using Context#startService. If service was bound it will be restarted
-               unless it is constantly crashing. The default value is 'false'
+         bind: bind - start service with Context#bindService
+               start - start service with Context#startService
+               startForeground - start service with Context#startForegroundService
+               If service was bound it will be restarted unless it is constantly crashing.
+               The default value is 'start'
          user: all - the service will be bound/started for system and all foreground users
                system - the service will be started/bound only for system user (u0)
                foreground - the service will be bound/started only for foreground users
@@ -175,7 +177,7 @@
          is no longer foreground.
      -->
     <string-array translatable="false" name="config_earlyStartupServices">
-        <!-- list your services here -->
+        <item>com.android.car.messenger/.MessengerService#bind=startForeground,user=foreground,trigger=userUnlocked</item>
     </string-array>
 
     <string name="config_TetheredProjectionAccessPointSsid" translatable="false">CarAP</string>
@@ -207,4 +209,8 @@
 
     <string name="token_handle_shared_preferences" translatable="false">com.android.car.trust.TOKEN_HANDLE</string>
 
+    <!-- The package name of the default bugreport application that can use CarBugreportServiceManager.
+         There is no default bugreporting app.-->
+    <string name="config_car_bugreport_application" translatable="false"></string>
+
 </resources>
diff --git a/service/res/values/strings.xml b/service/res/values/strings.xml
index 3d8a402..ad6149a 100644
--- a/service/res/values/strings.xml
+++ b/service/res/values/strings.xml
@@ -261,8 +261,5 @@
 
     <!-- The package name of the media application that will be selected as the default [CHAR LIMIT=NONE] -->
     <string name="default_media_application" translatable="false">com.android.bluetooth</string>
-    <!-- The package name of the default bugreport application that can use CarBugreportServiceManager.
-         There is no default bugreporting app.-->
-    <string name="default_car_bugreport_application" translatable="false"></string>
 
 </resources>
diff --git a/service/src/com/android/car/CarBugreportManagerService.java b/service/src/com/android/car/CarBugreportManagerService.java
index 572f17d..b63822f 100644
--- a/service/src/com/android/car/CarBugreportManagerService.java
+++ b/service/src/com/android/car/CarBugreportManagerService.java
@@ -52,7 +52,7 @@
 import java.io.PrintWriter;
 
 /**
- * Bugreport service for cars. Should *only* be used on userdebug or eng builds.
+ * Bugreport service for cars.
  */
 public class CarBugreportManagerService extends ICarBugreportService.Stub implements
         CarServiceBase {
@@ -75,8 +75,10 @@
     // definition.
     private static final String BUGREPORT_PROGRESS_SOCKET = "car_br_progress_socket";
     private static final String BUGREPORT_OUTPUT_SOCKET = "car_br_output_socket";
+    private static final String BUGREPORT_EXTRA_OUTPUT_SOCKET = "car_br_extra_output_socket";
 
     private static final int SOCKET_CONNECTION_MAX_RETRY = 10;
+    private static final int SOCKET_CONNECTION_RETRY_DELAY_IN_MS = 5000;
 
     private final Context mContext;
     private final Object mLock = new Object();
@@ -108,7 +110,8 @@
 
     @Override
     @RequiresPermission(android.Manifest.permission.DUMP)
-    public void requestZippedBugreport(ParcelFileDescriptor data, ICarBugreportCallback callback) {
+    public void requestBugreport(ParcelFileDescriptor output, ParcelFileDescriptor extraOutput,
+            ICarBugreportCallback callback) {
 
         // Check the caller has proper permission
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP,
@@ -120,8 +123,8 @@
             throw new SecurityException("Caller " + pm.getNameForUid(callingUid)
                             + " does not have the right signature");
         }
-        // Check the caller is the default designated bugreport app
-        String defaultAppPkgName = mContext.getString(R.string.default_car_bugreport_application);
+        // Check if the caller is the designated bugreport app
+        String defaultAppPkgName = mContext.getString(R.string.config_car_bugreport_application);
         String[] packageNamesForCallerUid = pm.getPackagesForUid(callingUid);
         boolean found = false;
         if (packageNamesForCallerUid != null) {
@@ -138,23 +141,24 @@
         }
 
         synchronized (mLock) {
-            requestZippedBugReportLocked(data, callback);
+            requestBugReportLocked(output, extraOutput, callback);
         }
     }
 
     @GuardedBy("mLock")
-    private void requestZippedBugReportLocked(
-            ParcelFileDescriptor data, ICarBugreportCallback callback) {
+    private void requestBugReportLocked(ParcelFileDescriptor output,
+            ParcelFileDescriptor extraOutput, ICarBugreportCallback callback) {
         if (mIsServiceRunning) {
             Slog.w(TAG, "Bugreport Service already running");
             reportError(callback, CarBugreportManagerCallback.CAR_BUGREPORT_IN_PROGRESS);
             return;
         }
         mIsServiceRunning = true;
-        mHandler.post(() -> startBugreportd(data, callback));
+        mHandler.post(() -> startBugreportd(output, extraOutput, callback));
     }
 
-    private void startBugreportd(ParcelFileDescriptor data, ICarBugreportCallback callback) {
+    private void startBugreportd(ParcelFileDescriptor output, ParcelFileDescriptor extraOutput,
+            ICarBugreportCallback callback) {
         Slog.i(TAG, "Starting " + BUGREPORTD_SERVICE);
         try {
             SystemProperties.set("ctl.start", BUGREPORTD_SERVICE);
@@ -163,7 +167,7 @@
             reportError(callback, CAR_BUGREPORT_DUMPSTATE_FAILED);
             return;
         }
-        processBugreportSockets(data, callback);
+        processBugreportSockets(output, extraOutput, callback);
         synchronized (mLock) {
             mIsServiceRunning = false;
         }
@@ -196,9 +200,14 @@
         }
     }
 
-    private void handleFinished(ParcelFileDescriptor output, ICarBugreportCallback callback) {
+    private void handleFinished(ParcelFileDescriptor output, ParcelFileDescriptor extraOutput,
+            ICarBugreportCallback callback) {
         Slog.i(TAG, "Finished reading bugreport");
-        if (!copySocketToPfd(output, callback)) {
+        // copysockettopfd calls callback.onError on error
+        if (!copySocketToPfd(output, BUGREPORT_OUTPUT_SOCKET, callback)) {
+            return;
+        }
+        if (!copySocketToPfd(extraOutput, BUGREPORT_EXTRA_OUTPUT_SOCKET, callback)) {
             return;
         }
         try {
@@ -216,7 +225,8 @@
      * {@code FAIL:message} accordingly.
      */
     private void processBugreportSockets(
-            ParcelFileDescriptor output, ICarBugreportCallback callback) {
+            ParcelFileDescriptor output, ParcelFileDescriptor extraOutput,
+            ICarBugreportCallback callback) {
         LocalSocket localSocket = connectSocket(BUGREPORT_PROGRESS_SOCKET);
         if (localSocket == null) {
             reportError(callback, CAR_BUGREPORT_DUMPSTATE_CONNECTION_FAILED);
@@ -235,7 +245,7 @@
                     reportError(callback, CAR_BUGREPORT_DUMPSTATE_FAILED);
                     return;
                 } else if (line.startsWith(OK_PREFIX)) {
-                    handleFinished(output, callback);
+                    handleFinished(output, extraOutput, callback);
                     return;
                 } else if (!line.startsWith(BEGIN_PREFIX)) {
                     Slog.w(TAG, "Received unknown progress line from dumpstate: " + line);
@@ -250,8 +260,8 @@
     }
 
     private boolean copySocketToPfd(
-            ParcelFileDescriptor pfd, ICarBugreportCallback callback) {
-        LocalSocket localSocket = connectSocket(BUGREPORT_OUTPUT_SOCKET);
+            ParcelFileDescriptor pfd, String remoteSocket, ICarBugreportCallback callback) {
+        LocalSocket localSocket = connectSocket(remoteSocket);
         if (localSocket == null) {
             reportError(callback, CAR_BUGREPORT_DUMPSTATE_CONNECTION_FAILED);
             return false;
@@ -292,8 +302,15 @@
         // keep retrying until success or reaching timeout.
         int retryCount = 0;
         while (true) {
-            // First connection always fails, so we wait 1 second before trying to connect.
-            SystemClock.sleep(/* ms= */ 1000);
+            // There are a few factors impacting the socket delay:
+            // 1. potential system slowness
+            // 2. car-bugreportd takes the screenshots early (before starting dumpstate). This
+            //    should be taken into account as the socket opens after screenshots are
+            //    captured.
+            // Therefore we are generous in setting the timeout. Most cases should not even
+            // come close to the timeouts, but since bugreports are taken when there is a
+            // system issue, it is hard to guess.
+            SystemClock.sleep(SOCKET_CONNECTION_RETRY_DELAY_IN_MS);
             try {
                 socket.connect(new LocalSocketAddress(socketName,
                         LocalSocketAddress.Namespace.RESERVED));
@@ -304,7 +321,7 @@
                             + " after " + retryCount + " retries", e);
                     return null;
                 }
-                Log.i(TAG, "Failed to connect to" + socketName + ". Will try again "
+                Log.i(TAG, "Failed to connect to " + socketName + ". Will try again "
                         + e.getMessage());
             }
         }
diff --git a/service/src/com/android/car/VmsPublisherService.java b/service/src/com/android/car/VmsPublisherService.java
index 3384e3a..11cef1f 100644
--- a/service/src/com/android/car/VmsPublisherService.java
+++ b/service/src/com/android/car/VmsPublisherService.java
@@ -119,16 +119,14 @@
         mContext = context;
         mClientManager = clientManager;
         mBrokerService = brokerService;
-    }
-
-    @Override
-    public void init() {
         mClientManager.registerConnectionListener(this);
     }
 
     @Override
+    public void init() {}
+
+    @Override
     public void release() {
-        mClientManager.unregisterConnectionListener(this);
         mPublisherProxies.values().forEach(PublisherProxy::unregister);
         mPublisherProxies.clear();
     }
@@ -172,7 +170,7 @@
         publisherProxy.register();
         try {
             publisherClient.setVmsPublisherService(publisherToken, publisherProxy);
-        } catch (RemoteException e) {
+        } catch (Throwable e) {
             Log.e(TAG, "unable to configure publisher: " + publisherName, e);
             return;
         }
@@ -298,7 +296,7 @@
         public void onSubscriptionChange(VmsSubscriptionState subscriptionState) {
             try {
                 mPublisherClient.onVmsSubscriptionChange(subscriptionState);
-            } catch (RemoteException e) {
+            } catch (Throwable e) {
                 Log.e(TAG, String.format("Unable to send subscription state to: %s", mName), e);
             }
         }
diff --git a/service/src/com/android/car/pm/VendorServiceController.java b/service/src/com/android/car/pm/VendorServiceController.java
index 0c8eae3..189370e 100644
--- a/service/src/com/android/car/pm/VendorServiceController.java
+++ b/service/src/com/android/car/pm/VendorServiceController.java
@@ -300,6 +300,9 @@
             Intent intent = mVendorServiceInfo.getIntent();
             if (mVendorServiceInfo.shouldBeBound()) {
                 return mContext.bindServiceAsUser(intent, this, BIND_AUTO_CREATE, mHandler, mUser);
+            } else if (mVendorServiceInfo.shouldBeStartedInForeground()) {
+                mStarted = mContext.startForegroundServiceAsUser(intent, mUser) != null;
+                return mStarted;
             } else {
                 mStarted = mContext.startServiceAsUser(intent, mUser) != null;
                 return mStarted;
diff --git a/service/src/com/android/car/pm/VendorServiceInfo.java b/service/src/com/android/car/pm/VendorServiceInfo.java
index 94a50a0..ba79978 100644
--- a/service/src/com/android/car/pm/VendorServiceInfo.java
+++ b/service/src/com/android/car/pm/VendorServiceInfo.java
@@ -53,17 +53,28 @@
     })
     @interface Trigger {}
 
-    private final boolean mBind;
+    private static final int BIND = 0;
+    private static final int START = 1;
+    private static final int START_FOREGROUND = 2;
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({
+            BIND,
+            START,
+            START_FOREGROUND,
+    })
+    @interface Bind {}
+
+    private final @Bind int mBind;
     private final @UserScope int mUserScope;
     private final @Trigger int mTrigger;
     private final ComponentName mComponentName;
 
-    private VendorServiceInfo(ComponentName componentName, boolean bind, @UserScope int userScope,
+    private VendorServiceInfo(ComponentName componentName, @Bind int bind, @UserScope int userScope,
             @Trigger int trigger) {
         mComponentName = componentName;
-        mBind = bind;
         mUserScope = userScope;
         mTrigger = trigger;
+        mBind = bind;
     }
 
     boolean isSystemUserService() {
@@ -83,7 +94,11 @@
     }
 
     boolean shouldBeBound() {
-        return mBind;
+        return mBind == BIND;
+    }
+
+    boolean shouldBeStartedInForeground() {
+        return mBind == START_FOREGROUND;
     }
 
     Intent getIntent() {
@@ -102,11 +117,11 @@
 
         final ComponentName cn = ComponentName.unflattenFromString(serviceParamTokens[0]);
         if (cn == null) {
-            throw new IllegalArgumentException("Failed to parse service info: "
-                    + rawServiceInfo + ", expected a single '#' symbol");
+            throw new IllegalArgumentException("Failed to unflatten component name from: "
+                    + rawServiceInfo);
         }
 
-        boolean bind = false;
+        int bind = START;
         int userScope = USER_SCOPE_ALL;
         int trigger = TRIGGER_UNLOCKED;
 
@@ -121,7 +136,15 @@
 
                 switch (key) {
                     case KEY_BIND:
-                        bind = TextUtils.equals("true", val.toLowerCase());
+                        if ("bind".equals(val)) {
+                            bind = BIND;
+                        } else if ("start".equals(val)) {
+                            bind = START;
+                        } else if ("startForeground".equals(val)) {
+                            bind = START_FOREGROUND;
+                        } else {
+                            throw new IllegalArgumentException("Unexpected bind option: " + val);
+                        }
                         break;
                     case KEY_USER_SCOPE:
                         if ("all".equals(val)) {
diff --git a/service/src/com/android/car/trust/CarTrustAgentBleManager.java b/service/src/com/android/car/trust/CarTrustAgentBleManager.java
index 718bd22..022477e 100644
--- a/service/src/com/android/car/trust/CarTrustAgentBleManager.java
+++ b/service/src/com/android/car/trust/CarTrustAgentBleManager.java
@@ -85,7 +85,7 @@
     private CarTrustAgentUnlockService mCarTrustAgentUnlockService;
     private String mOriginalBluetoothName;
     private byte[] mUniqueId;
-    private String mRandomName;
+    private String mEnrollmentDeviceName;
     private int mMtuSize = 20;
 
     // Enrollment Service and Characteristic UUIDs
@@ -247,15 +247,15 @@
     }
 
     @Nullable
-    private String getRandomName() {
-        if (mRandomName != null) {
-            return mRandomName;
+    private String getEnrollmentDeviceName() {
+        if (mEnrollmentDeviceName != null) {
+            return mEnrollmentDeviceName;
         }
 
         if (getTrustedDeviceService() != null) {
-            mRandomName = getTrustedDeviceService().getRandomName();
+            mEnrollmentDeviceName = getTrustedDeviceService().getEnrollmentDeviceName();
         }
-        return mRandomName;
+        return mEnrollmentDeviceName;
     }
 
     private void resolveBLEVersion(BluetoothDevice device, byte[] value,
@@ -374,17 +374,17 @@
     void startEnrollmentAdvertising() {
         mCurrentTrustedDeviceOperation = TRUSTED_DEVICE_OPERATION_ENROLLMENT;
         // Replace name to ensure it is small enough to be advertised
-        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
-        String name = getRandomName();
-        if (mOriginalBluetoothName == null) {
-            mOriginalBluetoothName = adapter.getName();
-        }
+        String name = getEnrollmentDeviceName();
         if (name != null) {
+            BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+            if (mOriginalBluetoothName == null) {
+                mOriginalBluetoothName = adapter.getName();
+            }
             adapter.setName(name);
-        }
-        if (Log.isLoggable(TAG, Log.DEBUG)) {
-            Log.d(TAG, "Changing bluetooth adapter name from "
-                    + mOriginalBluetoothName + " to " + name);
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "Changing bluetooth adapter name from "
+                        + mOriginalBluetoothName + " to " + name);
+            }
         }
         startAdvertising(mEnrollmentGattService,
                 new AdvertiseData.Builder()
diff --git a/service/src/com/android/car/trust/CarTrustedDeviceService.java b/service/src/com/android/car/trust/CarTrustedDeviceService.java
index cc79eff..d7e3321 100644
--- a/service/src/com/android/car/trust/CarTrustedDeviceService.java
+++ b/service/src/com/android/car/trust/CarTrustedDeviceService.java
@@ -24,6 +24,7 @@
 import android.content.SharedPreferences;
 import android.security.keystore.KeyGenParameterSpec;
 import android.security.keystore.KeyProperties;
+import android.sysprop.CarProperties;
 import android.util.Base64;
 import android.util.Log;
 
@@ -71,20 +72,31 @@
     private static final String KEYSTORE_PROVIDER = "AndroidKeyStore";
     private static final String IV_SPEC_SEPARATOR = ";";
 
+    // Device name length is limited by available bytes in BLE advertisement data packet.
+    //
+    // BLE advertisement limits data packet length to 31
+    // Currently we send:
+    // - 18 bytes for 16 chars UUID: 16 bytes + 2 bytes for header;
+    // - 3 bytes for advertisement being connectable;
+    // which leaves 10 bytes.
+    // Subtracting 2 bytes used by header, we have 8 bytes for device name.
+    private static final int DEVICE_NAME_LENGTH_LIMIT = 8;
+    // Limit prefix to 4 chars and fill the rest with randomly generated name. Use random name
+    // to improve uniqueness in paired device name.
+    private static final int DEVICE_NAME_PREFIX_LIMIT = 4;
+
     // The length of the authentication tag for a cipher in GCM mode. The GCM specification states
     // that this length can only have the values {128, 120, 112, 104, 96}. Using the highest
     // possible value.
     private static final int GCM_AUTHENTICATION_TAG_LENGTH = 128;
 
-    private static final int RANDOM_NAME_LENGTH = 6;
-
     private final Context mContext;
     private CarTrustAgentEnrollmentService mCarTrustAgentEnrollmentService;
     private CarTrustAgentUnlockService mCarTrustAgentUnlockService;
     private CarTrustAgentBleManager mCarTrustAgentBleManager;
     private SharedPreferences mTrustAgentTokenPreferences;
     private UUID mUniqueId;
-    private String mRandomName;
+    private String mEnrollmentDeviceName;
 
     public CarTrustedDeviceService(Context context) {
         mContext = context;
@@ -286,17 +298,22 @@
     }
 
     /**
-     * Get generated random name for enrollment
+     * Returns the name that should be used for the device during enrollment of a trusted device.
      *
-     * @return a random name for enrollment
+     * <p>The returned name will be a combination of a prefix sysprop and randomized digits.
      */
-    String getRandomName() {
-        if (mRandomName == null) {
-            // Create random RANDOM_NAME_LENGTH digit number for name
-            mRandomName = Utils.generateRandomNumberString(RANDOM_NAME_LENGTH);
-        }
+    String getEnrollmentDeviceName() {
+        if (mEnrollmentDeviceName == null) {
+            String deviceNamePrefix =
+                    CarProperties.trusted_device_device_name_prefix().orElse("");
+            deviceNamePrefix = deviceNamePrefix.substring(
+                    0, Math.min(deviceNamePrefix.length(), DEVICE_NAME_PREFIX_LIMIT));
 
-        return mRandomName;
+            int randomNameLength = DEVICE_NAME_LENGTH_LIMIT - deviceNamePrefix.length();
+            String randomName = Utils.generateRandomNumberString(randomNameLength);
+            mEnrollmentDeviceName = deviceNamePrefix + randomName;
+        }
+        return mEnrollmentDeviceName;
     }
 
     /**
diff --git a/tests/BugReportApp/AndroidManifest.xml b/tests/BugReportApp/AndroidManifest.xml
index ac27207..3356151 100644
--- a/tests/BugReportApp/AndroidManifest.xml
+++ b/tests/BugReportApp/AndroidManifest.xml
@@ -16,8 +16,8 @@
 -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="com.google.android.car.bugreport"
-          android:versionCode="6"
-          android:versionName="1.5.0">
+          android:versionCode="7"
+          android:versionName="1.5.1">
 
     <uses-permission android:name="android.permission.INTERNET"/>
     <uses-permission android:name="android.permission.READ_LOGS"/>
diff --git a/tests/BugReportApp/README.md b/tests/BugReportApp/README.md
index cd0245d..b93126c 100644
--- a/tests/BugReportApp/README.md
+++ b/tests/BugReportApp/README.md
@@ -21,7 +21,17 @@
 Bug reports are zipped and uploaded to GCS. GCS enables creating Pub/Sub
 notifications that can be used to track when new  bug reports are uploaded.
 
-## Configuration
+## System configuration
+
+BugReport app uses `CarBugreportServiceManager` to collect bug reports and
+screenshots. `CarBugreportServiceManager` allows only one bug report app to
+use it's APIs, by default it's none.
+
+To allow AAE BugReport app to access the API, you need to overlay
+`config_car_bugreport_application` in `packages/services/Car/service/res/values/config.xml`
+with value `com.google.android.car.bugreport`.
+
+## App Configuration
 
 UI and upload configs are located in `res/` directory. Resources can be
 [overlayed](https://source.android.com/setup/develop/new-device#use-resource-overlays)
diff --git a/tests/BugReportApp/src/com/google/android/car/bugreport/BugReportService.java b/tests/BugReportApp/src/com/google/android/car/bugreport/BugReportService.java
index cce327d..981faab 100644
--- a/tests/BugReportApp/src/com/google/android/car/bugreport/BugReportService.java
+++ b/tests/BugReportApp/src/com/google/android/car/bugreport/BugReportService.java
@@ -18,7 +18,6 @@
 import static com.google.android.car.bugreport.PackageUtils.getPackageVersion;
 
 import android.annotation.FloatRange;
-import android.annotation.Nullable;
 import android.annotation.StringRes;
 import android.app.Notification;
 import android.app.NotificationChannel;
@@ -28,15 +27,14 @@
 import android.car.CarBugreportManager;
 import android.car.CarNotConnectedException;
 import android.content.Intent;
-import android.hardware.display.DisplayManager;
 import android.os.Binder;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Message;
 import android.os.ParcelFileDescriptor;
 import android.util.Log;
-import android.view.Display;
 import android.widget.Toast;
 
 import com.google.common.util.concurrent.AtomicDouble;
@@ -52,9 +50,7 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
-import java.util.ArrayList;
 import java.util.Enumeration;
-import java.util.List;
 import java.util.concurrent.Executors;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.TimeUnit;
@@ -90,9 +86,8 @@
     private static final String NOTIFICATION_STATUS_CHANNEL_ID = "BUGREPORT_STATUS_CHANNEL_ID";
     private static final int BUGREPORT_IN_PROGRESS_NOTIF_ID = 1;
 
-    // http://cs/android/frameworks/base/core/java/android/app/ActivityView.java
-    private static final String ACTIVITY_VIEW_VIRTUAL_DISPLAY = "ActivityViewVirtualDisplay";
     private static final String OUTPUT_ZIP_FILE = "output_file.zip";
+    private static final String EXTRA_OUTPUT_ZIP_FILE = "extra_output_file.zip";
 
     private static final String MESSAGE_FAILURE_DUMPSTATE = "Failed to grab dumpstate";
     private static final String MESSAGE_FAILURE_ZIP = "Failed to zip files";
@@ -232,61 +227,12 @@
     }
 
     private void collectBugReport() {
-        // Order is important when capturing. Screenshot should be first
-        mSingleThreadExecutor.schedule(
-                this::takeAllScreenshots, ACTIVITY_FINISH_DELAY, TimeUnit.MILLISECONDS);
-        mSingleThreadExecutor.schedule(
-                this::grabBtSnoopLog, ACTIVITY_FINISH_DELAY, TimeUnit.MILLISECONDS);
-        mSingleThreadExecutor.schedule(
-                this::dumpStateToFile, ACTIVITY_FINISH_DELAY, TimeUnit.MILLISECONDS);
-    }
-
-    private void takeAllScreenshots() {
-        for (int displayId : getAvailableDisplayIds()) {
-            takeScreenshot(displayId);
+        if (Build.IS_USERDEBUG || Build.IS_ENG) {
+            mSingleThreadExecutor.schedule(
+                    this::grabBtSnoopLog, ACTIVITY_FINISH_DELAY, TimeUnit.MILLISECONDS);
         }
-    }
-
-    @Nullable
-    private File takeScreenshot(int displayId) {
-        Log.i(TAG, String.format("takeScreenshot displayId=%d", displayId));
-        File result = FileUtils.getFileWithSuffix(this, mMetaBugReport.getTimestamp(),
-                "-" + displayId + "-screenshot.png");
-        try {
-            if (DEBUG) {
-                Log.d(TAG, "Screen output: " + result.getName());
-            }
-
-            java.lang.Process process = Runtime.getRuntime()
-                    .exec("/system/bin/screencap -d " + displayId + " -p "
-                            + result.getAbsolutePath());
-
-            // Waits for the command to finish.
-            int err = process.waitFor();
-            if (DEBUG) {
-                Log.d(TAG, "screencap process finished: " + err);
-            }
-            return result;
-        } catch (IOException | InterruptedException e) {
-            Log.e(TAG, "screencap process failed: ", e);
-            showToast(R.string.toast_status_screencap_failed);
-        }
-        return null;
-    }
-
-    private List<Integer> getAvailableDisplayIds() {
-        DisplayManager displayManager = getSystemService(DisplayManager.class);
-        ArrayList<Integer> displayIds = new ArrayList<>();
-        for (Display d : displayManager.getDisplays()) {
-            Log.v(TAG,
-                    "getAvailableDisplayIds: d.Name=" + d.getName() + ", d.id=" + d.getDisplayId());
-            // We skip virtual displays as they are not captured by screencap.
-            if (d.getName().contains(ACTIVITY_VIEW_VIRTUAL_DISPLAY)) {
-                continue;
-            }
-            displayIds.add(d.getDisplayId());
-        }
-        return displayIds;
+        mSingleThreadExecutor.schedule(
+                this::saveBugReport, ACTIVITY_FINISH_DELAY, TimeUnit.MILLISECONDS);
     }
 
     private void grabBtSnoopLog() {
@@ -302,13 +248,16 @@
         }
     }
 
-    private void dumpStateToFile() {
+    private void saveBugReport() {
         Log.i(TAG, "Dumpstate to file");
         File outputFile = FileUtils.getFile(this, mMetaBugReport.getTimestamp(), OUTPUT_ZIP_FILE);
-
+        File extraOutputFile = FileUtils.getFile(this, mMetaBugReport.getTimestamp(),
+                EXTRA_OUTPUT_ZIP_FILE);
         try (ParcelFileDescriptor outFd = ParcelFileDescriptor.open(outputFile,
+                ParcelFileDescriptor.MODE_CREATE | ParcelFileDescriptor.MODE_READ_WRITE);
+             ParcelFileDescriptor extraOutFd = ParcelFileDescriptor.open(extraOutputFile,
                 ParcelFileDescriptor.MODE_CREATE | ParcelFileDescriptor.MODE_READ_WRITE)) {
-            requestBugReport(outFd);
+            requestBugReport(outFd, extraOutFd);
         } catch (IOException | RuntimeException e) {
             Log.e(TAG, "Failed to grab dump state", e);
             BugStorageUtils.setBugReportStatus(this, mMetaBugReport, Status.STATUS_WRITE_FAILED,
@@ -324,7 +273,7 @@
         mHandler.sendMessage(message);
     }
 
-    private void requestBugReport(ParcelFileDescriptor outFd) {
+    private void requestBugReport(ParcelFileDescriptor outFd, ParcelFileDescriptor extraOutFd) {
         if (DEBUG) {
             Log.d(TAG, "Requesting a bug report from CarBugReportManager.");
         }
@@ -355,7 +304,7 @@
                 sendProgressEventToHandler(MAX_PROGRESS_VALUE);
             }
         };
-        mBugreportManager.requestZippedBugreport(outFd, mCallback);
+        mBugreportManager.requestBugreport(outFd, extraOutFd, mCallback);
     }
 
     private void scheduleZipTask() {
@@ -433,8 +382,9 @@
                     continue;
                 }
                 String filename = file.getName();
-                // only for the OUTPUT_FILE, we add invidiual entries to zip file
-                if (filename.equals(OUTPUT_ZIP_FILE)) {
+
+                // only for the zipped output file, we add invidiual entries to zip file
+                if (filename.equals(OUTPUT_ZIP_FILE) || filename.equals(EXTRA_OUTPUT_ZIP_FILE)) {
                     extractZippedFileToOutputStream(file, zipStream);
                 } else {
                     FileInputStream reader = new FileInputStream(file);
diff --git a/tests/BugReportApp/src/com/google/android/car/bugreport/SystemUtils.java b/tests/BugReportApp/src/com/google/android/car/bugreport/SystemUtils.java
deleted file mode 100644
index 6a47298..0000000
--- a/tests/BugReportApp/src/com/google/android/car/bugreport/SystemUtils.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * 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.google.android.car.bugreport;
-
-import android.os.Build;
-import android.util.Log;
-
-import java.io.BufferedReader;
-import java.io.InputStreamReader;
-
-/**
- * Android System utility class.
- */
-final class SystemUtils {
-    private static final String TAG = SystemUtils.class.getSimpleName();
-
-    private static final String ENFORCING = "Enforcing";
-    private static final String PERMISSIVE = "Permissive";
-
-    private static final String VOLVO_FINGERPRINT_PREFIX = "VolvoCars";
-
-    /**
-     * Returns {@code true} if SELinux is {@code Enforcing}. If it can't verify it returns {@code
-     * true} because most likely SELinux is not allowing running {@code getenforce}.
-     *
-     * <p>Returns {@code false} only when SELinux is {@code Permissive}.
-     */
-    static boolean isSeLinuxEnforcing() {
-        StringBuilder result = new StringBuilder();
-        Process p;
-        try {
-            p = Runtime.getRuntime().exec("getenforce");
-            p.waitFor();
-            BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream()));
-            String line;
-            while ((line = reader.readLine()) != null) {
-                result.append(line);
-            }
-        } catch (Exception e) {
-            Log.e(TAG, "OS does not support getenforce", e);
-            // If getenforce is not available to the device, assume the device is not enforcing
-            return false;
-        }
-        String response = result.toString();
-        if (ENFORCING.equals(response)) {
-            return true;
-        } else if (PERMISSIVE.equals(response)) {
-            return false;
-        } else {
-            Log.e(TAG, "getenforce returned unexpected value, assuming selinux is enforcing!");
-            // If getenforce doesn't return anything, most likely SELinux is in enforcing mode.
-            return true;
-        }
-    }
-
-    /** Returns {@code true} if the app is running on Volvo cars. */
-    static boolean isVolvo() {
-        return Build.FINGERPRINT.startsWith(VOLVO_FINGERPRINT_PREFIX);
-    }
-
-    /** Returns {@code true} if the app is running on Android P. */
-    static boolean isAndroidP() {
-        return Build.VERSION.SDK_INT == Build.VERSION_CODES.P;
-    }
-
-    private SystemUtils() {
-    }
-}
diff --git a/tests/carservice_unit_test/src/com/android/car/VmsPublisherServiceTest.java b/tests/carservice_unit_test/src/com/android/car/VmsPublisherServiceTest.java
index 2c63ef5..d65bc12 100644
--- a/tests/carservice_unit_test/src/com/android/car/VmsPublisherServiceTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/VmsPublisherServiceTest.java
@@ -108,6 +108,8 @@
     @Before
     public void setUp() {
         mPublisherService = new VmsPublisherService(mContext, mBrokerService, mClientManager);
+        verify(mClientManager).registerConnectionListener(mPublisherService);
+
         mPublisherClient = new MockPublisherClient();
         mPublisherClient2 = new MockPublisherClient();
         when(mBrokerService.getSubscribersForLayerFromPublisher(LAYER, PUBLISHER_ID))
@@ -118,7 +120,6 @@
     @Test
     public void testInit() {
         mPublisherService.init();
-        verify(mClientManager).registerConnectionListener(mPublisherService);
     }
 
     @Test
@@ -637,7 +638,6 @@
     @Test
     public void testRelease() {
         mPublisherService.release();
-        verify(mClientManager).unregisterConnectionListener(mPublisherService);
     }
 
     private class MockPublisherClient extends IVmsPublisherClient.Stub {
diff --git a/tests/carservice_unit_test/src/com/android/car/pm/VendorServiceInfoTest.java b/tests/carservice_unit_test/src/com/android/car/pm/VendorServiceInfoTest.java
index 3045276..8174add 100644
--- a/tests/carservice_unit_test/src/com/android/car/pm/VendorServiceInfoTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/pm/VendorServiceInfoTest.java
@@ -43,7 +43,7 @@
     @Test
     public void multipleHashTags() {
         exception.expect(IllegalArgumentException.class);
-        VendorServiceInfo.parse(SERVICE_NAME + "#user=system#bind=true");
+        VendorServiceInfo.parse(SERVICE_NAME + "#user=system#bind=bind");
     }
 
     @Test
@@ -64,6 +64,7 @@
         assertThat(info.getIntent().getComponent())
                 .isEqualTo(ComponentName.unflattenFromString(SERVICE_NAME));
         assertThat(info.shouldBeBound()).isFalse();
+        assertThat(info.shouldBeStartedInForeground()).isFalse();
         assertThat(info.isSystemUserService()).isTrue();
         assertThat(info.isForegroundUserService()).isTrue();
         assertThat(info.shouldStartOnUnlock()).isTrue();
@@ -71,16 +72,25 @@
 
     @Test
     public void startService() {
-        VendorServiceInfo info = VendorServiceInfo.parse(SERVICE_NAME + "#bind=false");
+        VendorServiceInfo info = VendorServiceInfo.parse(SERVICE_NAME + "#bind=start");
         assertThat(info.shouldBeBound()).isFalse();
+        assertThat(info.shouldBeStartedInForeground()).isFalse();
         assertThat(info.getIntent().getComponent())
                 .isEqualTo(ComponentName.unflattenFromString(SERVICE_NAME));
     }
 
     @Test
     public void bindService() {
-        VendorServiceInfo info = VendorServiceInfo.parse(SERVICE_NAME + "#bind=true");
+        VendorServiceInfo info = VendorServiceInfo.parse(SERVICE_NAME + "#bind=bind");
         assertThat(info.shouldBeBound()).isTrue();
+        assertThat(info.shouldBeStartedInForeground()).isFalse();
+    }
+
+    @Test
+    public void startServiceInForeground() {
+        VendorServiceInfo info = VendorServiceInfo.parse(SERVICE_NAME + "#bind=startForeground");
+        assertThat(info.shouldBeBound()).isFalse();
+        assertThat(info.shouldBeStartedInForeground()).isTrue();
     }
 
     @Test
@@ -131,7 +141,7 @@
     @Test
     public void allArgs() {
         VendorServiceInfo info = VendorServiceInfo.parse(SERVICE_NAME
-                + "#bind=true,user=foreground,trigger=userUnlocked");
+                + "#bind=bind,user=foreground,trigger=userUnlocked");
         assertThat(info.getIntent().getComponent())
                 .isEqualTo(ComponentName.unflattenFromString(SERVICE_NAME));
         assertThat(info.shouldBeBound()).isTrue();