Merge "Make CarSystemUI use its own custom user switching dialog." into rvc-dev
diff --git a/car-lib/src/android/car/watchdog/CarWatchdogManager.java b/car-lib/src/android/car/watchdog/CarWatchdogManager.java
index fff1283..d8fb7d9 100644
--- a/car-lib/src/android/car/watchdog/CarWatchdogManager.java
+++ b/car-lib/src/android/car/watchdog/CarWatchdogManager.java
@@ -51,7 +51,7 @@
 public final class CarWatchdogManager extends CarManagerBase {
 
     private static final String TAG = CarWatchdogManager.class.getSimpleName();
-    private static final boolean DEBUG = true; // STOPSHIP if true (b/151474489)
+    private static final boolean DEBUG = false; // STOPSHIP if true
     private static final int INVALID_SESSION_ID = -1;
     private static final int NUMBER_OF_CONDITIONS_TO_BE_MET = 2;
     // Message ID representing main thread activeness checking.
diff --git a/evs/apps/default/Android.bp b/evs/apps/default/Android.bp
index 7fd60ef..df6fd0d 100644
--- a/evs/apps/default/Android.bp
+++ b/evs/apps/default/Android.bp
@@ -63,10 +63,6 @@
         "LabeledChecker.png",
     ],
 
-    strip: {
-        keep_symbols: true,
-    },
-
     init_rc: ["evs_app.rc"],
 
     cflags: ["-DLOG_TAG=\"EvsApp\""] + [
diff --git a/evs/apps/default/ConfigManager.h b/evs/apps/default/ConfigManager.h
index 6acd83d..9c6d1a2 100644
--- a/evs/apps/default/ConfigManager.h
+++ b/evs/apps/default/ConfigManager.h
@@ -19,6 +19,8 @@
 #include <vector>
 #include <string>
 
+#include <system/graphics-base.h>
+
 
 class ConfigManager {
 public:
@@ -91,6 +93,12 @@
     const DisplayInfo& getActiveDisplay() const { return mDisplays[mActiveDisplayId]; };
     void  useExternalMemory(bool flag) { mUseExternalMemory = flag; }
     bool  getUseExternalMemory() const { return mUseExternalMemory; }
+    void  setExternalMemoryFormat(android_pixel_format_t format) {
+        mExternalMemoryFormat = format;
+    }
+    android_pixel_format_t getExternalMemoryFormat() const {
+        return mExternalMemoryFormat;
+    }
 
 private:
     // Camera information
@@ -103,6 +111,9 @@
     // Memory management
     bool mUseExternalMemory;
 
+    // Format of external memory
+    android_pixel_format_t mExternalMemoryFormat;
+
     // Car body information (assumes front wheel steering and origin at center of rear axel)
     // Note that units aren't specified and don't matter as long as all length units are consistent
     // within the JSON file from which we parse.  That is, if everything is in meters, that's fine.
diff --git a/evs/apps/default/RenderDirectView.cpp b/evs/apps/default/RenderDirectView.cpp
index 2938521..68b731e 100644
--- a/evs/apps/default/RenderDirectView.cpp
+++ b/evs/apps/default/RenderDirectView.cpp
@@ -116,7 +116,8 @@
                                       mCameraDesc.v1.cameraId.c_str(),
                                       foundCfg ? std::move(targetCfg) : nullptr,
                                       sDisplay,
-                                      mConfig.getUseExternalMemory()));
+                                      mConfig.getUseExternalMemory(),
+                                      mConfig.getExternalMemoryFormat()));
     if (!mTexture) {
         LOG(ERROR) << "Failed to set up video texture for " << mCameraDesc.v1.cameraId;
 // TODO:  For production use, we may actually want to fail in this case, but not yet...
diff --git a/evs/apps/default/StreamHandler.cpp b/evs/apps/default/StreamHandler.cpp
index b1cfd1f..d350af1 100644
--- a/evs/apps/default/StreamHandler.cpp
+++ b/evs/apps/default/StreamHandler.cpp
@@ -30,6 +30,7 @@
 StreamHandler::StreamHandler(android::sp <IEvsCamera> pCamera,
                              uint32_t numBuffers,
                              bool useOwnBuffers,
+                             android_pixel_format_t format,
                              int32_t width,
                              int32_t height)
     : mCamera(pCamera),
@@ -46,7 +47,6 @@
         const auto usage = GRALLOC_USAGE_HW_TEXTURE |
                            GRALLOC_USAGE_SW_READ_RARELY |
                            GRALLOC_USAGE_SW_WRITE_OFTEN;
-        const auto format = HAL_PIXEL_FORMAT_RGBA_8888;
         for (auto i = 0; i < numBuffers; ++i) {
             unsigned pixelsPerLine;
             android::status_t result = alloc.allocate(width,
@@ -64,10 +64,10 @@
                 BufferDesc_1_1 buf;
                 AHardwareBuffer_Desc* pDesc =
                     reinterpret_cast<AHardwareBuffer_Desc *>(&buf.buffer.description);
-                pDesc->width = 640;
-                pDesc->height = 360;
+                pDesc->width = width;
+                pDesc->height = height;
                 pDesc->layers = 1;
-                pDesc->format = HAL_PIXEL_FORMAT_RGBA_8888;
+                pDesc->format = format;
                 pDesc->usage = GRALLOC_USAGE_HW_TEXTURE |
                                GRALLOC_USAGE_SW_READ_RARELY |
                                GRALLOC_USAGE_SW_WRITE_OFTEN;
diff --git a/evs/apps/default/StreamHandler.h b/evs/apps/default/StreamHandler.h
index f877c78..cb22b36 100644
--- a/evs/apps/default/StreamHandler.h
+++ b/evs/apps/default/StreamHandler.h
@@ -51,6 +51,7 @@
     StreamHandler(android::sp <IEvsCamera> pCamera,
                   uint32_t numBuffers = 2,
                   bool useOwnBuffers = false,
+                  android_pixel_format_t format = HAL_PIXEL_FORMAT_RGBA_8888,
                   int32_t width = 640,
                   int32_t height = 360);
     void shutdown();
diff --git a/evs/apps/default/VideoTex.cpp b/evs/apps/default/VideoTex.cpp
index 94e734a..7491dfe 100644
--- a/evs/apps/default/VideoTex.cpp
+++ b/evs/apps/default/VideoTex.cpp
@@ -137,29 +137,39 @@
                              const char* evsCameraId,
                              std::unique_ptr<Stream> streamCfg,
                              EGLDisplay glDisplay,
-                             bool useExternalMemory) {
+                             bool useExternalMemory,
+                             android_pixel_format_t format) {
     // Set up the camera to feed this texture
     sp<IEvsCamera> pCamera = nullptr;
+    sp<StreamHandler> pStreamHandler = nullptr;
     if (streamCfg != nullptr) {
         pCamera = pEnum->openCamera_1_1(evsCameraId, *streamCfg);
+
+        // Initialize the stream that will help us update this texture's contents
+        pStreamHandler = new StreamHandler(pCamera,
+                                           2,     // number of buffers
+                                           useExternalMemory,
+                                           format,
+                                           streamCfg->width,
+                                           streamCfg->height);
     } else {
         pCamera =
             IEvsCamera::castFrom(pEnum->openCamera(evsCameraId))
             .withDefault(nullptr);
+
+        // Initialize the stream with the default resolution
+        pStreamHandler = new StreamHandler(pCamera,
+                                           2,     // number of buffers
+                                           useExternalMemory,
+                                           format);
     }
 
-    if (pCamera.get() == nullptr) {
+    if (pCamera == nullptr) {
         LOG(ERROR) << "Failed to allocate new EVS Camera interface for " << evsCameraId;
         return nullptr;
     }
 
-    // Initialize the stream that will help us update this texture's contents
-    sp<StreamHandler> pStreamHandler = new StreamHandler(pCamera,
-                                                         2,     // number of buffers
-                                                         useExternalMemory,
-                                                         streamCfg->width,
-                                                         streamCfg->height);
-    if (pStreamHandler.get() == nullptr) {
+    if (pStreamHandler == nullptr) {
         LOG(ERROR) << "Failed to allocate FrameHandler";
         return nullptr;
     }
diff --git a/evs/apps/default/VideoTex.h b/evs/apps/default/VideoTex.h
index d884faa..097d086 100644
--- a/evs/apps/default/VideoTex.h
+++ b/evs/apps/default/VideoTex.h
@@ -16,6 +16,9 @@
 #ifndef VIDEOTEX_H
 #define VIDEOTEX_H
 
+#include "StreamHandler.h"
+#include "TexWrapper.h"
+
 #include <EGL/egl.h>
 #include <EGL/eglext.h>
 #include <GLES2/gl2.h>
@@ -25,9 +28,7 @@
 
 #include <android/hardware/automotive/evs/1.1/IEvsEnumerator.h>
 #include <android/hardware/camera/device/3.2/ICameraDevice.h>
-
-#include "TexWrapper.h"
-#include "StreamHandler.h"
+#include <system/graphics-base.h>
 
 using ::android::hardware::camera::device::V3_2::Stream;
 using namespace ::android::hardware::automotive::evs::V1_1;
@@ -38,7 +39,8 @@
                                         const char *evsCameraId,
                                         std::unique_ptr<Stream> streamCfg,
                                         EGLDisplay glDisplay,
-                                        bool useExternalMemory);
+                                        bool useExternalMemory,
+                                        android_pixel_format_t format);
 
 public:
     VideoTex() = delete;
@@ -62,10 +64,13 @@
 };
 
 
+// Creates a video texture to draw the camera preview.  format is effective only
+// when useExternalMemory is true.
 VideoTex* createVideoTexture(sp<IEvsEnumerator> pEnum,
                              const char * deviceName,
                              std::unique_ptr<Stream> streamCfg,
                              EGLDisplay glDisplay,
-                             bool useExternalMemory = false);
+                             bool useExternalMemory = false,
+                             android_pixel_format_t format = HAL_PIXEL_FORMAT_RGBA_8888);
 
 #endif // VIDEOTEX_H
diff --git a/evs/apps/default/evs_app.cpp b/evs/apps/default/evs_app.cpp
index a968990..9f2f2c8 100644
--- a/evs/apps/default/evs_app.cpp
+++ b/evs/apps/default/evs_app.cpp
@@ -14,24 +14,25 @@
  * limitations under the License.
  */
 
+#include "ConfigManager.h"
+#include "EvsStateControl.h"
+#include "EvsVehicleListener.h"
+
 #include <stdio.h>
 
+#include <android/hardware/automotive/evs/1.1/IEvsDisplay.h>
+#include <android/hardware/automotive/evs/1.1/IEvsEnumerator.h>
+#include <android-base/logging.h>
+#include <android-base/macros.h>    // arraysize
+#include <android-base/strings.h>
 #include <hidl/HidlTransportSupport.h>
+#include <hwbinder/ProcessState.h>
 #include <utils/Errors.h>
 #include <utils/StrongPointer.h>
 #include <utils/Log.h>
 
-#include "android-base/macros.h"    // arraysize
-#include "android-base/logging.h"
 
-#include <android/hardware/automotive/evs/1.1/IEvsEnumerator.h>
-#include <android/hardware/automotive/evs/1.1/IEvsDisplay.h>
-
-#include <hwbinder/ProcessState.h>
-
-#include "EvsStateControl.h"
-#include "EvsVehicleListener.h"
-#include "ConfigManager.h"
+using android::base::EqualsIgnoreCase;
 
 // libhidl:
 using android::hardware::configureRpcThreadpool;
@@ -66,6 +67,24 @@
 }
 
 
+static bool convertStringToFormat(const char* str, android_pixel_format_t* output) {
+    bool result = true;
+    if (EqualsIgnoreCase(str, "RGBA8888")) {
+        *output = HAL_PIXEL_FORMAT_RGBA_8888;
+    } else if (EqualsIgnoreCase(str, "YV12")) {
+        *output = HAL_PIXEL_FORMAT_YV12;
+    } else if (EqualsIgnoreCase(str, "NV21")) {
+        *output = HAL_PIXEL_FORMAT_YCrCb_420_SP;
+    } else if (EqualsIgnoreCase(str, "YUYV")) {
+        *output = HAL_PIXEL_FORMAT_YCBCR_422_I;
+    } else {
+        result = false;
+    }
+
+    return result;
+}
+
+
 // Main entry point
 int main(int argc, char** argv)
 {
@@ -77,6 +96,7 @@
     const char* evsServiceName = "default";
     int displayId = 1;
     bool useExternalMemory = false;
+    android_pixel_format_t extMemoryFormat = HAL_PIXEL_FORMAT_RGBA_8888;
     for (int i=1; i< argc; i++) {
         if (strcmp(argv[i], "--test") == 0) {
             useVehicleHal = false;
@@ -90,6 +110,19 @@
             displayId = std::stoi(argv[++i]);
         } else if (strcmp(argv[i], "--extmem") == 0) {
             useExternalMemory = true;
+            if (i + 1 >= argc) {
+                // use RGBA8888 by default
+                LOG(INFO) << "External buffer format is not set.  "
+                          << "RGBA8888 will be used.";
+            } else {
+                if (!convertStringToFormat(argv[i + 1], &extMemoryFormat)) {
+                    LOG(WARNING) << "Color format string " << argv[i + 1]
+                                 << " is unknown or not supported.  RGBA8888 will be used.";
+                } else {
+                    // move the index
+                    ++i;
+                }
+            }
         } else {
             printf("Ignoring unrecognized command line arg '%s'\n", argv[i]);
             printHelp = true;
@@ -97,11 +130,23 @@
     }
     if (printHelp) {
         printf("Options include:\n");
-        printf("  --test    Do not talk to Vehicle Hal, but simulate 'reverse' instead\n");
-        printf("  --hw      Bypass EvsManager by connecting directly to EvsEnumeratorHw\n");
-        printf("  --mock    Connect directly to EvsEnumeratorHw-Mock\n");
-        printf("  --display Specify the display to use\n");
-        printf("  --extmem  Application allocates buffers to capture camera frames\n");
+        printf("  --test\n\tDo not talk to Vehicle Hal, but simulate 'reverse' instead\n");
+        printf("  --hw\n\tBypass EvsManager by connecting directly to EvsEnumeratorHw\n");
+        printf("  --mock\n\tConnect directly to EvsEnumeratorHw-Mock\n");
+        printf("  --display\n\tSpecify the display to use\n");
+        printf("  --extmem  <format>\n\t"
+               "Application allocates buffers to capture camera frames.  "
+               "Available format strings are (case insensitive):\n");
+        printf("\t\tRGBA8888: 4x8-bit RGBA format.  This is the default format to be used "
+               "when no format is specified.\n");
+        printf("\t\tYV12: YUV420 planar format with a full resolution Y plane "
+               "followed by a V values, with U values last.\n");
+        printf("\t\tNV21: A biplanar format with a full resolution Y plane "
+               "followed by a single chrome plane with weaved V and U values.\n");
+        printf("\t\tYUYV: Packed format with a half horizontal chrome resolution.  "
+               "Known as YUV4:2:2.\n");
+
+        return EXIT_FAILURE;
     }
 
     // Load our configuration information
@@ -140,6 +185,7 @@
     }
     config.setActiveDisplayId(displayId);
     config.useExternalMemory(useExternalMemory);
+    config.setExternalMemoryFormat(extMemoryFormat);
 
     // Connect to the Vehicle HAL so we can monitor state
     sp<IVehicle> pVnet;
diff --git a/evs/apps/demo_app_evs_support_lib/Android.bp b/evs/apps/demo_app_evs_support_lib/Android.bp
index 5380a4a..ae0073a 100644
--- a/evs/apps/demo_app_evs_support_lib/Android.bp
+++ b/evs/apps/demo_app_evs_support_lib/Android.bp
@@ -30,10 +30,6 @@
 
     include_dirs: ["packages/services/Car/evs/support_library"],
 
-    strip: {
-        keep_symbols: true,
-    },
-
     init_rc: ["evs_app_support_lib.rc"],
 
     cflags: ["-DLOG_TAG=\"EvsAppSupportLib\""] + [
diff --git a/evs/manager/1.0/Android.bp b/evs/manager/1.0/Android.bp
index 260e0b7..82bcdd8 100644
--- a/evs/manager/1.0/Android.bp
+++ b/evs/manager/1.0/Android.bp
@@ -39,10 +39,6 @@
 
     init_rc: ["android.automotive.evs.manager@1.0.rc"],
 
-    strip: {
-        keep_symbols: true,
-    },
-
     cflags: ["-DLOG_TAG=\"EvsManagerV1_0\""] + [
         "-DGL_GLEXT_PROTOTYPES",
         "-DEGL_EGLEXT_PROTOTYPES",
diff --git a/evs/manager/1.1/Android.bp b/evs/manager/1.1/Android.bp
index 694aa06..891d615 100644
--- a/evs/manager/1.1/Android.bp
+++ b/evs/manager/1.1/Android.bp
@@ -20,35 +20,36 @@
     name: "android.automotive.evs.manager@1.1",
 
     srcs: [
-        "service.cpp",
         "Enumerator.cpp",
         "HalCamera.cpp",
-        "VirtualCamera.cpp",
         "HalDisplay.cpp",
+        "VirtualCamera.cpp",
+        "service.cpp",
+        "stats/CameraUsageStats.cpp",
+        "stats/LooperWrapper.cpp",
+        "stats/StatsCollector.cpp",
         "sync/unique_fd.cpp",
         "sync/unique_fence.cpp",
         "sync/unique_timeline.cpp",
     ],
 
     shared_libs: [
-        "libbase",
-        "libcutils",
-        "libutils",
-        "libui",
-        "libsync",
-        "libhidlbase",
-        "libhardware",
-        "libcamera_metadata",
         "android.hardware.automotive.evs@1.0",
         "android.hardware.automotive.evs@1.1",
+        "libbase",
+        "libcamera_metadata",
+        "libcutils",
+        "libhardware",
+        "libhidlbase",
+        "libprocessgroup",
+        "libstatslog",
+        "libsync",
+        "libui",
+        "libutils",
     ],
 
     init_rc: ["android.automotive.evs.manager@1.1.rc"],
 
-    strip: {
-        keep_symbols: true,
-    },
-
     cflags: ["-DLOG_TAG=\"EvsManagerV1_1\""] + [
         "-DGL_GLEXT_PROTOTYPES",
         "-DEGL_EGLEXT_PROTOTYPES",
diff --git a/evs/manager/1.1/Enumerator.cpp b/evs/manager/1.1/Enumerator.cpp
index 8417828..a86202e 100644
--- a/evs/manager/1.1/Enumerator.cpp
+++ b/evs/manager/1.1/Enumerator.cpp
@@ -14,22 +14,50 @@
  * limitations under the License.
  */
 
-#include <android-base/parseint.h>
-#include <android-base/strings.h>
-#include <android-base/logging.h>
-#include <hwbinder/IPCThreadState.h>
-#include <cutils/android_filesystem_config.h>
-
 #include "Enumerator.h"
 #include "HalDisplay.h"
 
+#include <android-base/chrono_utils.h>
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/parseint.h>
+#include <android-base/strings.h>
+#include <android-base/stringprintf.h>
+#include <cutils/android_filesystem_config.h>
+#include <hwbinder/IPCThreadState.h>
+
+namespace {
+
+    const char* kSingleIndent = "\t";
+    const char* kDumpOptionAll = "all";
+    const char* kDumpDeviceCamera = "camera";
+    const char* kDumpDeviceDisplay = "display";
+
+    const char* kDumpCameraCommandCurrent = "--current";
+    const char* kDumpCameraCommandCollected = "--collected";
+    const char* kDumpCameraCommandCustom = "--custom";
+    const char* kDumpCameraCommandCustomStart = "start";
+    const char* kDumpCameraCommandCustomStop = "stop";
+
+    const int kDumpCameraMinNumArgs = 4;
+    const int kOptionDumpDeviceTypeIndex = 1;
+    const int kOptionDumpCameraTypeIndex = 2;
+    const int kOptionDumpCameraCommandIndex = 3;
+    const int kOptionDumpCameraArgsStartIndex = 4;
+
+}
+
 namespace android {
 namespace automotive {
 namespace evs {
 namespace V1_1 {
 namespace implementation {
 
+using ::android::base::Error;
 using ::android::base::EqualsIgnoreCase;
+using ::android::base::StringAppendF;
+using ::android::base::StringPrintf;
+using ::android::base::WriteStringToFd;
 using CameraDesc_1_0 = ::android::hardware::automotive::evs::V1_0::CameraDesc;
 using CameraDesc_1_1 = ::android::hardware::automotive::evs::V1_1::CameraDesc;
 
@@ -43,15 +71,32 @@
         // Get an internal display identifier.
         mHwEnumerator->getDisplayIdList(
             [this](const auto& displayPorts) {
-                if (displayPorts.size() > 0) {
-                    mInternalDisplayPort = displayPorts[0];
-                } else {
+                for (auto& port : displayPorts) {
+                    mDisplayPorts.push_back(port);
+                }
+
+                // The first element is the internal display
+                mInternalDisplayPort = mDisplayPorts.front();
+                if (mDisplayPorts.size() < 1) {
                     LOG(WARNING) << "No display is available to EVS service.";
                 }
             }
         );
     }
 
+    // Starts the statistics collection
+    mMonitorEnabled = false;
+    mClientsMonitor = new StatsCollector();
+    if (mClientsMonitor != nullptr) {
+        auto result = mClientsMonitor->startCollection();
+        if (!result.ok()) {
+            LOG(ERROR) << "Failed to start the usage monitor: "
+                       << result.error();
+        } else {
+            mMonitorEnabled = true;
+        }
+    }
+
     return result;
 }
 
@@ -188,7 +233,10 @@
         if (device == nullptr) {
             LOG(ERROR) << "Failed to open hardware camera " << cameraId;
         } else {
-            hwCamera = new HalCamera(device, cameraId);
+            // Calculates the usage statistics record identifier
+            auto fn = mCameraDevices.hash_function();
+            auto recordId = fn(cameraId) & 0xFF;
+            hwCamera = new HalCamera(device, cameraId, recordId);
             if (hwCamera == nullptr) {
                 LOG(ERROR) << "Failed to allocate camera wrapper object";
                 mHwEnumerator->closeCamera(device);
@@ -239,6 +287,9 @@
             // NOTE:  This should drop our last reference to the camera, resulting in its
             //        destruction.
             mActiveCameras.erase(halCamera->getId());
+            if (mMonitorEnabled) {
+                mClientsMonitor->unregisterClientToMonitor(halCamera->getId());
+            }
         }
     }
 
@@ -277,7 +328,10 @@
                 success = false;
                 break;
             } else {
-                hwCamera = new HalCamera(device, id, streamCfg);
+                // Calculates the usage statistics record identifier
+                auto fn = mCameraDevices.hash_function();
+                auto recordId = fn(id) & 0xFF;
+                hwCamera = new HalCamera(device, id, recordId, streamCfg);
                 if (hwCamera == nullptr) {
                     LOG(ERROR) << "Failed to allocate camera wrapper object";
                     mHwEnumerator->closeCamera(device);
@@ -295,6 +349,10 @@
 
             // Add the hardware camera to our list, which will keep it alive via ref count
             mActiveCameras.try_emplace(id, hwCamera);
+            if (mMonitorEnabled) {
+                mClientsMonitor->registerClientToMonitor(hwCamera);
+            }
+
             sourceCameras.push_back(hwCamera);
         } else {
             if (it->second->getStreamConfig().id != streamCfg.id) {
@@ -392,7 +450,7 @@
     // TODO: Because of b/129284474, an additional class, HalDisplay, has been defined and
     // wraps the IEvsDisplay object the driver returns.  We may want to remove this
     // additional class when it is fixed properly.
-    sp<IEvsDisplay_1_0> pHalDisplay = new HalDisplay(pActiveDisplay);
+    sp<IEvsDisplay_1_0> pHalDisplay = new HalDisplay(pActiveDisplay, mInternalDisplayPort);
     mActiveDisplay = pHalDisplay;
 
     return pHalDisplay;
@@ -444,6 +502,11 @@
         return nullptr;
     }
 
+    if (std::find(mDisplayPorts.begin(), mDisplayPorts.end(), id) == mDisplayPorts.end()) {
+        LOG(ERROR) << "No display is available on the port " << static_cast<int32_t>(id);
+        return nullptr;
+    }
+
     // We simply keep track of the most recently opened display instance.
     // In the underlying layers we expect that a new open will cause the previous
     // object to be destroyed.  This avoids any race conditions associated with
@@ -462,7 +525,7 @@
     // TODO: Because of b/129284474, an additional class, HalDisplay, has been defined and
     // wraps the IEvsDisplay object the driver returns.  We may want to remove this
     // additional class when it is fixed properly.
-    sp<IEvsDisplay_1_1> pHalDisplay = new HalDisplay(pActiveDisplay);
+    sp<IEvsDisplay_1_1> pHalDisplay = new HalDisplay(pActiveDisplay, id);
     mActiveDisplay = pHalDisplay;
 
     return pHalDisplay;
@@ -504,7 +567,7 @@
     if (fd.getNativeHandle() != nullptr && fd->numFds > 0) {
         cmdDump(fd->data[0], options);
     } else {
-        LOG(ERROR) << "Invalid parameters";
+        LOG(ERROR) << "Given file descriptor is not valid.";
     }
 
     return {};
@@ -513,7 +576,8 @@
 
 void Enumerator::cmdDump(int fd, const hidl_vec<hidl_string>& options) {
     if (options.size() == 0) {
-        dprintf(fd, "No option is given");
+        WriteStringToFd("No option is given.\n", fd);
+        cmdHelp(fd);
         return;
     }
 
@@ -525,19 +589,27 @@
     } else if (EqualsIgnoreCase(option, "--dump")) {
         cmdDumpDevice(fd, options);
     } else {
-        dprintf(fd, "Invalid option: %s\n", option.c_str());
+        WriteStringToFd(StringPrintf("Invalid option: %s\n", option.c_str()),
+                        fd);
     }
 }
 
 
 void Enumerator::cmdHelp(int fd) {
-    dprintf(fd, "Usage: \n\n");
-    dprintf(fd, "--help: shows this help.\n");
-    dprintf(fd, "--list [all|camera|display]: list camera or display devices or both "
-                "available to EVS manager.\n");
-    dprintf(fd, "--dump [all|camera|display] <device id>: "
-                "show current status of the target device or all devices "
-                "when no device is given.\n");
+    WriteStringToFd("Usage: \n\n"
+                    "--help: shows this help.\n"
+                    "--list [all|camera|display]: lists camera or display devices or both "
+                    "available to EVS manager.\n"
+                    "--dump camera [all|device_id] --[current|collected|custom] [args]\n"
+                    "\tcurrent: shows the current status\n"
+                    "\tcollected: shows 10 most recent periodically collected camera usage "
+                    "statistics\n"
+                    "\tcustom: starts/stops collecting the camera usage statistics\n"
+                    "\t\tstart [interval] [duration]: starts collecting usage statistics "
+                    "at every [interval] during [duration].  Interval and duration are in "
+                    "milliseconds.\n"
+                    "\t\tstop: stops collecting usage statistics and shows collected records.\n"
+                    "--dump display: shows current status of the display\n", fd);
 }
 
 
@@ -546,18 +618,25 @@
     bool listDisplays = true;
     if (options.size() > 1) {
         const std::string option = options[1];
-        const bool listAll = EqualsIgnoreCase(option, "all");
-        listCameras = listAll || EqualsIgnoreCase(option, "camera");
-        listDisplays = listAll || EqualsIgnoreCase(option, "display");
+        const bool listAll = EqualsIgnoreCase(option, kDumpOptionAll);
+        listCameras = listAll || EqualsIgnoreCase(option, kDumpDeviceCamera);
+        listDisplays = listAll || EqualsIgnoreCase(option, kDumpDeviceDisplay);
         if (!listCameras && !listDisplays) {
-            dprintf(fd, "Unrecognized option, %s, is ignored.\n", option.c_str());
+            WriteStringToFd(StringPrintf("Unrecognized option, %s, is ignored.\n",
+                                         option.c_str()),
+                            fd);
+
+            // Nothing to show, return
+            return;
         }
     }
 
+    std::string buffer;
     if (listCameras) {
-        dprintf(fd, "Camera devices available to EVS service:\n");
+        StringAppendF(&buffer,"Camera devices available to EVS service:\n");
         if (mCameraDevices.size() < 1) {
-            // Camera devices may not be enumerated yet.
+            // Camera devices may not be enumerated yet.  This may fail if the
+            // user is not permitted to use EVS service.
             getCameraList_1_1(
                 [](const auto cameras) {
                     if (cameras.size() < 1) {
@@ -567,62 +646,203 @@
         }
 
         for (auto& [id, desc] : mCameraDevices) {
-            dprintf(fd, "\t%s\n", id.c_str());
+            StringAppendF(&buffer, "%s%s\n", kSingleIndent, id.c_str());
         }
 
-        dprintf(fd, "\nCamera devices currently in use:\n");
+        StringAppendF(&buffer, "%sCamera devices currently in use:\n", kSingleIndent);
         for (auto& [id, ptr] : mActiveCameras) {
-            dprintf(fd, "\t%s\n", id.c_str());
+            StringAppendF(&buffer, "%s%s\n", kSingleIndent, id.c_str());
         }
-        dprintf(fd, "\n");
+        StringAppendF(&buffer, "\n");
     }
 
     if (listDisplays) {
         if (mHwEnumerator != nullptr) {
-            dprintf(fd, "Display devices available to EVS service:\n");
+            StringAppendF(&buffer, "Display devices available to EVS service:\n");
             // Get an internal display identifier.
             mHwEnumerator->getDisplayIdList(
                 [&](const auto& displayPorts) {
-                    for (auto& port : displayPorts) {
-                        dprintf(fd, "\tdisplay port %u\n", (unsigned)port);
+                    for (auto&& port : displayPorts) {
+                        StringAppendF(&buffer, "%sdisplay port %u\n",
+                                               kSingleIndent,
+                                               static_cast<unsigned>(port));
                     }
                 }
             );
+        } else {
+            LOG(WARNING) << "EVS HAL implementation is not available.";
         }
     }
+
+    WriteStringToFd(buffer, fd);
 }
 
 
 void Enumerator::cmdDumpDevice(int fd, const hidl_vec<hidl_string>& options) {
+    // Dumps both cameras and displays if the target device type is not given
     bool dumpCameras = true;
     bool dumpDisplays = true;
-    if (options.size() > 1) {
-        const std::string option = options[1];
-        const bool dumpAll = EqualsIgnoreCase(option, "all");
-        dumpCameras = dumpAll || EqualsIgnoreCase(option, "camera");
-        dumpDisplays = dumpAll || EqualsIgnoreCase(option, "display");
+    const auto numOptions = options.size();
+    if (numOptions > kOptionDumpDeviceTypeIndex) {
+        const std::string target = options[kOptionDumpDeviceTypeIndex];
+        const bool dumpAll = EqualsIgnoreCase(target, kDumpOptionAll);
+        dumpCameras = dumpAll || EqualsIgnoreCase(target, kDumpDeviceCamera);
+        dumpDisplays = dumpAll || EqualsIgnoreCase(target, kDumpDeviceDisplay);
         if (!dumpCameras && !dumpDisplays) {
-            dprintf(fd, "Unrecognized option, %s, is ignored.\n", option.c_str());
+            WriteStringToFd(StringPrintf("Unrecognized option, %s, is ignored.\n",
+                                         target.c_str()),
+                            fd);
+            return;
         }
     }
 
     if (dumpCameras) {
-        const bool dumpAllCameras = options.size() < 3;
-        std::string deviceId = "";
-        if (!dumpAllCameras) {
-            deviceId = options[2];
+        // --dump camera [all|device_id] --[current|collected|custom] [args]
+        if (numOptions < kDumpCameraMinNumArgs) {
+            WriteStringToFd(StringPrintf("Necessary arguments are missing.  "
+                                         "Please check the usages:\n"),
+                            fd);
+            cmdHelp(fd);
+            return;
         }
 
-        for (auto& [id, ptr] : mActiveCameras) {
-            if (!dumpAllCameras && !EqualsIgnoreCase(id, deviceId)) {
-                continue;
-            }
-            ptr->dump(fd);
+        const std::string deviceId = options[kOptionDumpCameraTypeIndex];
+        auto target = mActiveCameras.find(deviceId);
+        const bool dumpAllCameras = EqualsIgnoreCase(deviceId,
+                                                     kDumpOptionAll);
+        if (!dumpAllCameras && target == mActiveCameras.end()) {
+            // Unknown camera identifier
+            WriteStringToFd(StringPrintf("Given camera ID %s is unknown or not active.\n",
+                                         deviceId.c_str()),
+                            fd);
+            return;
         }
+
+        const std::string command = options[kOptionDumpCameraCommandIndex];
+        std::string cameraInfo;
+        if (EqualsIgnoreCase(command, kDumpCameraCommandCurrent)) {
+            // Active stream configuration from each active HalCamera objects
+            if (!dumpAllCameras) {
+                StringAppendF(&cameraInfo, "HalCamera: %s\n%s",
+                                           deviceId.c_str(),
+                                           target->second->toString(kSingleIndent).c_str());
+            } else {
+                for (auto&& [id, handle] : mActiveCameras) {
+                    // Appends the current status
+                    cameraInfo += handle->toString(kSingleIndent);
+                }
+            }
+        } else if (EqualsIgnoreCase(command, kDumpCameraCommandCollected)) {
+            // Reads the usage statistics from active HalCamera objects
+            std::unordered_map<std::string, std::string> usageStrings;
+            if (mMonitorEnabled) {
+                auto result = mClientsMonitor->toString(&usageStrings, kSingleIndent);
+                if (!result.ok()) {
+                    LOG(ERROR) << "Failed to get the monitoring result";
+                    return;
+                }
+
+                if (!dumpAllCameras) {
+                    cameraInfo += usageStrings[deviceId];
+                } else {
+                    for (auto&& [id, stats] : usageStrings) {
+                        cameraInfo += stats;
+                    }
+                }
+            } else {
+                WriteStringToFd(StringPrintf("Client monitor is not available.\n"),
+                                fd);
+                return;
+            }
+        } else if (EqualsIgnoreCase(command, kDumpCameraCommandCustom)) {
+            // Additional arguments are expected for this command:
+            // --dump camera device_id --custom start [interval] [duration]
+            // or, --dump camera device_id --custom stop
+            if (numOptions < kDumpCameraMinNumArgs + 1) {
+                WriteStringToFd(StringPrintf("Necessary arguments are missing. "
+                                             "Please check the usages:\n"),
+                                fd);
+                cmdHelp(fd);
+                return;
+            }
+
+            if (!mMonitorEnabled) {
+                WriteStringToFd(StringPrintf("Client monitor is not available."), fd);
+                return;
+            }
+
+            const std::string subcommand = options[kOptionDumpCameraArgsStartIndex];
+            if (EqualsIgnoreCase(subcommand, kDumpCameraCommandCustomStart)) {
+                using std::chrono::nanoseconds;
+                using std::chrono::milliseconds;
+                using std::chrono::duration_cast;
+                nanoseconds interval = 0ns;
+                nanoseconds duration = 0ns;
+                if (numOptions > kOptionDumpCameraArgsStartIndex + 2) {
+                    duration = duration_cast<nanoseconds>(
+                            milliseconds(
+                                    std::stoi(options[kOptionDumpCameraArgsStartIndex + 2])
+                            ));
+                }
+
+                if (numOptions > kOptionDumpCameraArgsStartIndex + 1) {
+                    interval = duration_cast<nanoseconds>(
+                            milliseconds(
+                                    std::stoi(options[kOptionDumpCameraArgsStartIndex + 1])
+                            ));
+                }
+
+                // Starts a custom collection
+                auto result = mClientsMonitor->startCustomCollection(interval, duration);
+                if (!result) {
+                    LOG(ERROR) << "Failed to start a custom collection.  "
+                               << result.error();
+                    StringAppendF(&cameraInfo, "Failed to start a custom collection. %s\n",
+                                               result.error().message().c_str());
+                }
+            } else if (EqualsIgnoreCase(subcommand, kDumpCameraCommandCustomStop)) {
+                if (!mMonitorEnabled) {
+                    WriteStringToFd(StringPrintf("Client monitor is not available."), fd);
+                    return;
+                }
+
+                auto result = mClientsMonitor->stopCustomCollection(deviceId);
+                if (!result) {
+                    LOG(ERROR) << "Failed to stop a custom collection.  "
+                               << result.error();
+                    StringAppendF(&cameraInfo, "Failed to stop a custom collection. %s\n",
+                                               result.error().message().c_str());
+                } else {
+                    // Pull the custom collection
+                    cameraInfo += *result;
+                }
+            } else {
+                WriteStringToFd(StringPrintf("Unknown argument: %s\n",
+                                             subcommand.c_str()),
+                                fd);
+                cmdHelp(fd);
+                return;
+            }
+        } else {
+            WriteStringToFd(StringPrintf("Unknown command: %s\n"
+                                         "Please check the usages:\n", command.c_str()),
+                            fd);
+            cmdHelp(fd);
+            return;
+        }
+
+        // Outputs the report
+        WriteStringToFd(cameraInfo, fd);
     }
 
     if (dumpDisplays) {
-        dprintf(fd, "Not implemented yet\n");
+        HalDisplay* pDisplay =
+            reinterpret_cast<HalDisplay*>(mActiveDisplay.promote().get());
+        if (!pDisplay) {
+            WriteStringToFd("No active display is found.\n", fd);
+        } else {
+            WriteStringToFd(pDisplay->toString(kSingleIndent), fd);
+        }
     }
 }
 
diff --git a/evs/manager/1.1/Enumerator.h b/evs/manager/1.1/Enumerator.h
index f23871e..7708295 100644
--- a/evs/manager/1.1/Enumerator.h
+++ b/evs/manager/1.1/Enumerator.h
@@ -17,13 +17,14 @@
 #ifndef ANDROID_AUTOMOTIVE_EVS_V1_1_EVSCAMERAENUMERATOR_H
 #define ANDROID_AUTOMOTIVE_EVS_V1_1_EVSCAMERAENUMERATOR_H
 
+#include "HalCamera.h"
+#include "VirtualCamera.h"
+#include "stats/StatsCollector.h"
+
 #include <list>
 #include <unordered_map>
 #include <unordered_set>
 
-#include "HalCamera.h"
-#include "VirtualCamera.h"
-
 #include <android/hardware/automotive/evs/1.1/IEvsEnumerator.h>
 #include <android/hardware/automotive/evs/1.1/IEvsDisplay.h>
 #include <android/hardware/camera/device/3.2/ICameraDevice.h>
@@ -93,9 +94,18 @@
     std::unordered_map<std::string,
                        CameraDesc>    mCameraDevices;
 
+    // List of available physical display devices
+    std::list<uint8_t>                mDisplayPorts;
+
     // Display port the internal display is connected to.
     uint8_t                           mInternalDisplayPort;
 
+    // Collecting camera usage statistics from clients
+    sp<StatsCollector>                mClientsMonitor;
+
+    // Boolean flag to tell whether the camera usages are being monitored or not
+    bool                              mMonitorEnabled;
+
     // LSHAL dump
     void cmdDump(int fd, const hidl_vec<hidl_string>& options);
     void cmdHelp(int fd);
diff --git a/evs/manager/1.1/HalCamera.cpp b/evs/manager/1.1/HalCamera.cpp
index f0ae13d..a90e83b 100644
--- a/evs/manager/1.1/HalCamera.cpp
+++ b/evs/manager/1.1/HalCamera.cpp
@@ -14,11 +14,13 @@
  * limitations under the License.
  */
 
-#include <android-base/logging.h>
-
+#include "Enumerator.h"
 #include "HalCamera.h"
 #include "VirtualCamera.h"
-#include "Enumerator.h"
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/strings.h>
 
 namespace android {
 namespace automotive {
@@ -30,6 +32,15 @@
 // TODO(changyeon):
 // We need to hook up death monitoring to detect stream death so we can attempt a reconnect
 
+using ::android::base::StringAppendF;
+using ::android::base::WriteStringToFd;
+
+HalCamera::~HalCamera() {
+    // Reports the usage statistics before the destruction
+    // EvsUsageStatsReported atom is defined in
+    // frameworks/base/cmds/statsd/src/atoms.proto
+    mUsageStats->writeStats();
+}
 
 sp<VirtualCamera> HalCamera::makeVirtualCamera() {
 
@@ -310,6 +321,9 @@
         if (mFrames[i].refCount <= 0) {
             // Since all our clients are done with this buffer, return it to the device layer
             mHwCamera->doneWithFrame(buffer);
+
+            // Counts a returned buffer
+            mUsageStats->framesReturned();
         }
     }
 
@@ -336,6 +350,9 @@
             returnedBuffers.resize(1);
             returnedBuffers[0] = buffer;
             mHwCamera->doneWithFrame_1_1(returnedBuffers);
+
+            // Counts a returned buffer
+            mUsageStats->framesReturned();
         }
     }
 
@@ -352,6 +369,10 @@
     LOG(INFO) << "A delivered frame from EVS v1.0 HW module is rejected.";
     mHwCamera->doneWithFrame(buffer);
 
+    // Reports a received and returned buffer
+    mUsageStats->framesReceived();
+    mUsageStats->framesReturned();
+
     return Void();
 }
 
@@ -378,7 +399,9 @@
                 // Skip current frame because it arrives too soon.
                 LOG(DEBUG) << "Skips a frame from " << getId();
                 mNextRequests->push_back(req);
-                ++mSyncFrames;
+
+                // Reports a skipped frame
+                mUsageStats->framesSkippedToSync();
             } else if (vCam != nullptr && vCam->deliverFrame(buffer[0])) {
                 // Forward a frame and move a timeline.
                 LOG(DEBUG) << getId() << " forwarded the buffer #" << buffer[0].bufferId;
@@ -387,7 +410,9 @@
             }
         }
     }
-    ++mFramesReceived;
+
+    // Reports the number of received buffers
+    mUsageStats->framesReceived(buffer.size());
 
     // Frames are being forwarded to active v1.0 clients and v1.1 clients if we
     // failed to create a timeline.
@@ -409,8 +434,10 @@
         // right away.
         LOG(INFO) << "Trivially rejecting frame (" << buffer[0].bufferId
                   << ") from " << getId() << " with no acceptance";
-        ++mFramesNotUsed;
         mHwCamera->doneWithFrame_1_1(buffer);
+
+        // Reports a returned buffer
+        mUsageStats->framesReturned();
     } else {
         // Add an entry for this frame in our tracking list.
         unsigned i;
@@ -559,47 +586,68 @@
 }
 
 
-void HalCamera::dump(int fd) const {
-    dprintf(fd, "HalCamera: %s\n", mId.c_str());
-    const auto timeElapsedNano = android::elapsedRealtimeNano() - mTimeCreated;
-    dprintf(fd, "\tCreated: %ld (elapsed %ld ns)\n", (long)mTimeCreated, (long)timeElapsedNano);
-    dprintf(fd, "\tFrames received: %lu (%f fps)\n",
-                (unsigned long)mFramesReceived,
-                (double)mFramesReceived / timeElapsedNano * 1e+9);
-    dprintf(fd, "\tFrames not used: %lu\n", (unsigned long)mFramesNotUsed);
-    dprintf(fd, "\tFrames skipped to sync: %lu\n", (unsigned long)mSyncFrames);
-    dprintf(fd, "\tActive Stream Configuration:\n");
-    dprintf(fd, "\t\tid: %d\n", mStreamConfig.id);
-    dprintf(fd, "\t\twidth: %d\n", mStreamConfig.width);
-    dprintf(fd, "\t\theight: %d\n", mStreamConfig.height);
-    dprintf(fd, "\t\tformat: %d\n", mStreamConfig.width);
-    dprintf(fd, "\t\tusage: 0x%lX\n", (unsigned long)mStreamConfig.usage);
-    dprintf(fd, "\t\trotation: 0x%X\n", mStreamConfig.rotation);
+CameraUsageStatsRecord HalCamera::getStats() const {
+    return mUsageStats->snapshot();
+}
 
-    dprintf(fd, "\tActive clients:\n");
+
+Stream HalCamera::getStreamConfiguration() const {
+    return mStreamConfig;
+}
+
+
+std::string HalCamera::toString(const char* indent) const {
+    std::string buffer;
+
+    const auto timeElapsedMs = android::uptimeMillis() - mTimeCreatedMs;
+    StringAppendF(&buffer, "%sCreated: @%" PRId64 " (elapsed %" PRId64 " ms)\n",
+                           indent, mTimeCreatedMs, timeElapsedMs);
+
+    std::string double_indent(indent);
+    double_indent += indent;
+    buffer += CameraUsageStats::toString(getStats(), double_indent.c_str());
     for (auto&& client : mClients) {
         auto handle = client.promote();
         if (!handle) {
             continue;
         }
 
-        dprintf(fd, "\t\tClient %p\n", handle.get());
-        handle->dump(fd, "\t\t\t");
-        {
-            std::scoped_lock<std::mutex> lock(mFrameMutex);
-            dprintf(fd, "\t\t\tUse a fence-based delivery: %s\n",
-                    mTimelines.find((uint64_t)handle.get()) != mTimelines.end() ? "T" : "F");
-        }
+        StringAppendF(&buffer, "%sClient %p\n",
+                               indent, handle.get());
+        buffer += handle->toString(double_indent.c_str());
     }
 
-    dprintf(fd, "\tMaster client: %p\n", mMaster.promote().get());
-    dprintf(fd, "\tSynchronization support: %s\n", mSyncSupported ? "T" : "F");
+    StringAppendF(&buffer, "%sMaster client: %p\n"
+                           "%sSynchronization support: %s\n",
+                           indent, mMaster.promote().get(),
+                           indent, mSyncSupported ? "T":"F");
+
+    buffer += HalCamera::toString(mStreamConfig, indent);
+
+    return buffer;
 }
 
 
-double HalCamera::getFramerate() const {
-    const auto timeElapsed = android::elapsedRealtimeNano() - mTimeCreated;
-    return static_cast<double>(mFramesReceived) / timeElapsed;
+std::string HalCamera::toString(Stream configuration, const char* indent) {
+    std::string streamInfo;
+    std::string double_indent(indent);
+    double_indent += indent;
+    StringAppendF(&streamInfo, "%sActive Stream Configuration\n"
+                               "%sid: %d\n"
+                               "%swidth: %d\n"
+                               "%sheight: %d\n"
+                               "%sformat: 0x%X\n"
+                               "%susage: 0x%" PRIx64 "\n"
+                               "%srotation: 0x%X\n\n",
+                               indent,
+                               double_indent.c_str(), configuration.id,
+                               double_indent.c_str(), configuration.width,
+                               double_indent.c_str(), configuration.height,
+                               double_indent.c_str(), configuration.format,
+                               double_indent.c_str(), configuration.usage,
+                               double_indent.c_str(), configuration.rotation);
+
+    return streamInfo;
 }
 
 
diff --git a/evs/manager/1.1/HalCamera.h b/evs/manager/1.1/HalCamera.h
index 240737e..7e010a6 100644
--- a/evs/manager/1.1/HalCamera.h
+++ b/evs/manager/1.1/HalCamera.h
@@ -17,20 +17,21 @@
 #ifndef ANDROID_AUTOMOTIVE_EVS_V1_1_HALCAMERA_H
 #define ANDROID_AUTOMOTIVE_EVS_V1_1_HALCAMERA_H
 
-#include <android/hardware/automotive/evs/1.1/types.h>
-#include <android/hardware/automotive/evs/1.1/IEvsCamera.h>
-#include <android/hardware/automotive/evs/1.1/IEvsCameraStream.h>
-#include <utils/Mutex.h>
-#include <utils/SystemClock.h>
+#include "stats/CameraUsageStats.h"
+#include "sync/unique_fd.h"
+#include "sync/unique_fence.h"
+#include "sync/unique_timeline.h"
 
 #include <deque>
 #include <list>
 #include <thread>
 #include <unordered_map>
 
-#include "sync/unique_fd.h"
-#include "sync/unique_fence.h"
-#include "sync/unique_timeline.h"
+#include <android/hardware/automotive/evs/1.1/types.h>
+#include <android/hardware/automotive/evs/1.1/IEvsCamera.h>
+#include <android/hardware/automotive/evs/1.1/IEvsCameraStream.h>
+#include <utils/Mutex.h>
+#include <utils/SystemClock.h>
 
 using namespace ::android::hardware::automotive::evs::V1_1;
 using ::android::hardware::camera::device::V3_2::Stream;
@@ -60,19 +61,22 @@
 // stream from the hardware camera and distribute it to the associated VirtualCamera objects.
 class HalCamera : public IEvsCameraStream_1_1 {
 public:
-    HalCamera(sp<IEvsCamera_1_1> hwCamera, std::string deviceId = "", Stream cfg = {})
+    HalCamera(sp<IEvsCamera_1_1> hwCamera,
+              std::string deviceId = "",
+              int32_t recordId = 0,
+              Stream cfg = {})
         : mHwCamera(hwCamera),
           mId(deviceId),
           mStreamConfig(cfg),
           mSyncSupported(UniqueTimeline::Supported()),
-          mTimeCreated(android::elapsedRealtimeNano()),
-          mFramesReceived(0),
-          mFramesNotUsed(0),
-          mSyncFrames(0) {
+          mTimeCreatedMs(android::uptimeMillis()),
+          mUsageStats(new CameraUsageStats(recordId)) {
         mCurrentRequests = &mFrameRequests[0];
         mNextRequests    = &mFrameRequests[1];
     }
 
+    virtual ~HalCamera();
+
     // Factory methods for client VirtualCameras
     sp<VirtualCamera>     makeVirtualCamera();
     bool                  ownVirtualCamera(sp<VirtualCamera> virtualCamera);
@@ -101,8 +105,17 @@
     Return<EvsResult>   getParameter(CameraParam id, int32_t& value);
     bool                isSyncSupported() const { return mSyncSupported; }
 
-    void                dump(int fd) const;
-    double              getFramerate() const;
+    // Returns a snapshot of collected usage statistics
+    CameraUsageStatsRecord getStats() const;
+
+    // Returns active stream configuration
+    Stream getStreamConfiguration() const;
+
+    // Returns a string showing the current status
+    std::string toString(const char* indent = "") const;
+
+    // Returns a string showing current stream configuration
+    static std::string toString(Stream configuration, const char* indent = "");
 
     // Methods from ::android::hardware::automotive::evs::V1_0::IEvsCameraStream follow.
     Return<void> deliverFrame(const BufferDesc_1_0& buffer) override;
@@ -145,11 +158,11 @@
                        std::unique_ptr<UniqueTimeline>> mTimelines GUARDED_BY(mFrameMutex);
     bool                      mSyncSupported;
 
-    // debugging information
-    int64_t                   mTimeCreated;
-    uint64_t                  mFramesReceived;
-    uint64_t                  mFramesNotUsed;
-    uint64_t                  mSyncFrames;
+    // Time this object was created
+    int64_t mTimeCreatedMs;
+
+    // usage statistics to collect
+    android::sp<CameraUsageStats> mUsageStats;
 };
 
 } // namespace implementation
diff --git a/evs/manager/1.1/HalDisplay.cpp b/evs/manager/1.1/HalDisplay.cpp
index 4f7b4fd..b1e1aa6 100644
--- a/evs/manager/1.1/HalDisplay.cpp
+++ b/evs/manager/1.1/HalDisplay.cpp
@@ -14,17 +14,26 @@
  * limitations under the License.
  */
 
-#include <android-base/logging.h>
 #include "HalDisplay.h"
 
+#include <inttypes.h>
+
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <ui/DisplayConfig.h>
+#include <ui/DisplayState.h>
+
+using android::base::StringAppendF;
+
 namespace android {
 namespace automotive {
 namespace evs {
 namespace V1_1 {
 namespace implementation {
 
-HalDisplay::HalDisplay(sp<IEvsDisplay_1_0> display) :
-  mHwDisplay(display) {
+HalDisplay::HalDisplay(sp<IEvsDisplay_1_0> display, int32_t id) :
+  mHwDisplay(display),
+  mId(id) {
     // nothing to do.
 }
 
@@ -114,6 +123,38 @@
     return Void();
 }
 
+
+std::string HalDisplay::toString(const char* indent) {
+    std::string buffer;
+    android::DisplayConfig displayConfig;
+    android::ui::DisplayState displayState;
+
+    if (mId == std::numeric_limits<int32_t>::min()) {
+        // Display identifier has not set
+        StringAppendF(&buffer, "HalDisplay: Display port is unknown.\n");
+    } else {
+        StringAppendF(&buffer, "HalDisplay: Display port %" PRId32 "\n", mId);
+    }
+
+    getDisplayInfo_1_1([&](auto& config, auto& state) {
+        displayConfig =
+            *(reinterpret_cast<const android::DisplayConfig*>(config.data()));
+        displayState =
+            *(reinterpret_cast<const android::ui::DisplayState*>(state.data()));
+    });
+
+    StringAppendF(&buffer, "%sWidth: %" PRId32 "\n",
+                           indent, displayConfig.resolution.getWidth());
+    StringAppendF(&buffer, "%sHeight: %" PRId32 "\n",
+                           indent, displayConfig.resolution.getHeight());
+    StringAppendF(&buffer, "%sRefresh rate: %f\n",
+                           indent, displayConfig.refreshRate);
+    StringAppendF(&buffer, "%sRotation: %" PRId32 "\n",
+                           indent, static_cast<int32_t>(displayState.orientation));
+
+    return buffer;
+}
+
 } // namespace implementation
 } // namespace V1_1
 } // namespace evs
diff --git a/evs/manager/1.1/HalDisplay.h b/evs/manager/1.1/HalDisplay.h
index de9690b..672ad55 100644
--- a/evs/manager/1.1/HalDisplay.h
+++ b/evs/manager/1.1/HalDisplay.h
@@ -17,6 +17,8 @@
 #ifndef ANDROID_AUTOMOTIVE_EVS_V1_1_DISPLAYPROXY_H
 #define ANDROID_AUTOMOTIVE_EVS_V1_1_DISPLAYPROXY_H
 
+#include <limits>
+
 #include <android/hardware/automotive/evs/1.1/types.h>
 #include <android/hardware/automotive/evs/1.1/IEvsDisplay.h>
 
@@ -42,7 +44,8 @@
 // manager directly to use the IEvsDisplay object the driver provides.
 class HalDisplay : public IEvsDisplay_1_1 {
 public:
-    explicit HalDisplay(sp<IEvsDisplay_1_0> display);
+    explicit HalDisplay(sp<IEvsDisplay_1_0> display,
+                        int32_t port = std::numeric_limits<int32_t>::min());
     virtual ~HalDisplay() override;
 
     inline void         shutdown();
@@ -58,8 +61,12 @@
     // Methods from ::android::hardware::automotive::evs::V1_1::IEvsDisplay follow.
     Return<void>            getDisplayInfo_1_1(getDisplayInfo_1_1_cb _info_cb) override;
 
+    // Returns a string showing the current status
+    std::string toString(const char* indent = "");
+
 private:
     sp<IEvsDisplay_1_0>     mHwDisplay; // The low level display interface that backs this proxy
+    int32_t                 mId; // Display identifier
 };
 
 } // namespace implementation
diff --git a/evs/manager/1.1/VirtualCamera.cpp b/evs/manager/1.1/VirtualCamera.cpp
index 09c6804..8b94a15 100644
--- a/evs/manager/1.1/VirtualCamera.cpp
+++ b/evs/manager/1.1/VirtualCamera.cpp
@@ -14,13 +14,18 @@
  * limitations under the License.
  */
 
-#include <android/hardware_buffer.h>
-#include <android-base/logging.h>
-
 #include "VirtualCamera.h"
 #include "HalCamera.h"
 #include "Enumerator.h"
 
+#include <android/hardware_buffer.h>
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+
+using ::android::base::StringAppendF;
+using ::android::base::StringPrintf;
+using ::android::base::WriteStringToFd;
 using ::android::hardware::automotive::evs::V1_0::DisplayState;
 
 
@@ -893,15 +898,27 @@
 }
 
 
-void VirtualCamera::dump(int fd, const char* prefix) const {
-    dprintf(fd, "%sLogical camera device: %s\n",
-                prefix, mHalCamera.size() > 1 ? "T" : "F");
-    dprintf(fd, "%sFramesAllowed: %u\n", prefix, mFramesAllowed);
-    dprintf(fd, "%sFrames in use:\n", prefix);
+std::string VirtualCamera::toString(const char* indent) const {
+    std::string buffer;
+    StringAppendF(&buffer, "%sLogical camera device: %s\n"
+                           "%sFramesAllowed: %u\n"
+                           "%sFrames in use:\n",
+                           indent, mHalCamera.size() > 1 ? "T" : "F",
+                           indent, mFramesAllowed,
+                           indent);
+
+    std::string next_indent(indent);
+    next_indent += "\t";
     for (auto&& [id, queue] : mFramesHeld) {
-        dprintf(fd, "%s\t%s, %d\n", prefix, id.c_str(), (int)queue.size());
+        StringAppendF(&buffer, "%s%s: %d\n",
+                               next_indent.c_str(),
+                               id.c_str(),
+                               static_cast<int>(queue.size()));
     }
-    dprintf(fd, "%sCurrent stream state: %d\n", prefix, mStreamState);
+    StringAppendF(&buffer, "%sCurrent stream state: %d\n",
+                                 indent, mStreamState);
+
+    return buffer;
 }
 
 
diff --git a/evs/manager/1.1/VirtualCamera.h b/evs/manager/1.1/VirtualCamera.h
index e57ddcf..76a8648 100644
--- a/evs/manager/1.1/VirtualCamera.h
+++ b/evs/manager/1.1/VirtualCamera.h
@@ -108,7 +108,7 @@
                                             importExternalBuffers_cb _hidl_cb) override;
 
     // Dump current status to a given file descriptor
-    void              dump(int fd, const char* prefix = "") const;
+    std::string       toString(const char* indent = "") const;
 
 
 private:
diff --git a/evs/manager/1.1/stats/CameraUsageStats.cpp b/evs/manager/1.1/stats/CameraUsageStats.cpp
new file mode 100644
index 0000000..11f8229
--- /dev/null
+++ b/evs/manager/1.1/stats/CameraUsageStats.cpp
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2020 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 "CameraUsageStats.h"
+
+#include <statslog.h>
+
+namespace android {
+namespace automotive {
+namespace evs {
+namespace V1_1 {
+namespace implementation {
+
+using ::android::base::Result;
+using ::android::base::StringAppendF;
+
+
+void CameraUsageStats::framesReceived(int n) {
+    AutoMutex lock(mMutex);
+    mStats.framesReceived += n;
+}
+
+
+void CameraUsageStats::framesReturned(int n) {
+    AutoMutex lock(mMutex);
+    mStats.framesReturned += n;
+}
+
+
+void CameraUsageStats::framesIgnored(int n) {
+    AutoMutex lock(mMutex);
+    mStats.framesIgnored += n;
+}
+
+
+void CameraUsageStats::framesSkippedToSync(int n) {
+    AutoMutex lock(mMutex);
+    mStats.framesSkippedToSync += n;
+}
+
+
+void CameraUsageStats::eventsReceived() {
+    AutoMutex lock(mMutex);
+    ++mStats.erroneousEventsCount;
+}
+
+
+int64_t CameraUsageStats::getTimeCreated() const {
+    AutoMutex lock(mMutex);
+    return mTimeCreatedMs;
+}
+
+
+int64_t CameraUsageStats::getFramesReceived() const {
+    AutoMutex lock(mMutex);
+    return mStats.framesReceived;
+}
+
+
+int64_t CameraUsageStats::getFramesReturned() const {
+    AutoMutex lock(mMutex);
+    return mStats.framesReturned;
+}
+
+
+CameraUsageStatsRecord CameraUsageStats::snapshot() const {
+    AutoMutex lock(mMutex);
+    return mStats;
+}
+
+
+Result<void> CameraUsageStats::writeStats() const {
+    AutoMutex lock(mMutex);
+    const auto duration = android::uptimeMillis() - mTimeCreatedMs;
+    // TODO(b/156131016): calculates and reports frame roundtrip latencies
+    android::util::stats_write(android::util::EVS_USAGE_STATS_REPORTED,
+                               mId,
+                               mStats.peakClientsCount,
+                               mStats.erroneousEventsCount,
+                               mStats.framesFirstRoundtripLatency,
+                               mStats.framesAvgRoundtripLatency,
+                               mStats.framesPeakRoundtripLatency,
+                               mStats.framesReceived,
+                               mStats.framesIgnored,
+                               mStats.framesSkippedToSync,
+                               duration);
+    return {};
+}
+
+
+std::string CameraUsageStats::toString(const CameraUsageStatsRecord& record, const char* indent) {
+    return record.toString(indent);
+}
+
+} // namespace implementation
+} // namespace V1_1
+} // namespace evs
+} // namespace automotive
+} // namespace android
+
diff --git a/evs/manager/1.1/stats/CameraUsageStats.h b/evs/manager/1.1/stats/CameraUsageStats.h
new file mode 100644
index 0000000..4bc4ea8
--- /dev/null
+++ b/evs/manager/1.1/stats/CameraUsageStats.h
@@ -0,0 +1,152 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_AUTOMOTIVE_EVS_V1_1_CAMERAUSAGESTATS_H
+#define ANDROID_AUTOMOTIVE_EVS_V1_1_CAMERAUSAGESTATS_H
+
+#include <inttypes.h>
+
+#include <android-base/result.h>
+#include <android-base/stringprintf.h>
+#include <utils/Mutex.h>
+#include <utils/RefBase.h>
+#include <utils/SystemClock.h>
+
+namespace android {
+namespace automotive {
+namespace evs {
+namespace V1_1 {
+namespace implementation {
+
+struct CameraUsageStatsRecord {
+public:
+    // Time a snapshot is generated
+    nsecs_t timestamp;
+
+    // Total number of frames received
+    int64_t framesReceived;
+
+    // Total number of frames returned to EVS HAL
+    int64_t framesReturned;
+
+    // Number of frames ignored because no clients are listening
+    int64_t framesIgnored;
+
+    // Number of frames skipped to synchronize camera frames
+    int64_t framesSkippedToSync;
+
+    // Roundtrip latency of the very first frame after the stream started.
+    int64_t framesFirstRoundtripLatency;
+
+    // Peak mFrame roundtrip latency
+    int64_t framesPeakRoundtripLatency;
+
+    // Average mFrame roundtrip latency
+    double  framesAvgRoundtripLatency;
+
+    // Number of the erroneous streaming events
+    int32_t erroneousEventsCount;
+
+    // Peak number of active clients
+    int32_t peakClientsCount;
+
+    // Calculates a delta between two records
+    CameraUsageStatsRecord& operator-=(const CameraUsageStatsRecord& rhs) {
+        // Only calculates differences in the frame statistics
+        framesReceived = framesReceived - rhs.framesReceived;
+        framesReturned = framesReturned - rhs.framesReturned;
+        framesIgnored = framesIgnored - rhs.framesIgnored;
+        framesSkippedToSync = framesSkippedToSync - rhs.framesSkippedToSync;
+        erroneousEventsCount = erroneousEventsCount - rhs.erroneousEventsCount;
+
+        return *this;
+    }
+
+    friend CameraUsageStatsRecord operator-(CameraUsageStatsRecord lhs,
+                                      const CameraUsageStatsRecord& rhs) noexcept {
+        lhs -= rhs; // reuse compound assignment
+        return lhs;
+    }
+
+    // Constructs a string that shows collected statistics
+    std::string toString(const char* indent = "") const {
+        std::string buffer;
+        android::base::StringAppendF(&buffer,
+                "%sTime Collected: @%" PRId64 "ms\n"
+                "%sFrames Received: %" PRId64 "\n"
+                "%sFrames Returned: %" PRId64 "\n"
+                "%sFrames Ignored : %" PRId64 "\n"
+                "%sFrames Skipped To Sync: %" PRId64 "\n\n",
+                indent, ns2ms(timestamp),
+                indent, framesReceived,
+                indent, framesReturned,
+                indent, framesIgnored,
+                indent, framesSkippedToSync);
+
+        return buffer;
+    }
+};
+
+
+class CameraUsageStats : public RefBase {
+public:
+    CameraUsageStats(int32_t id)
+        : mMutex(Mutex()),
+          mId(id),
+          mTimeCreatedMs(android::uptimeMillis()),
+          mStats({}) {}
+
+private:
+    // Mutex to protect a collection record
+    mutable Mutex mMutex;
+
+    // Unique identifier
+    int32_t mId;
+
+    // Time this object was created
+    int64_t mTimeCreatedMs;
+
+    // Usage statistics to collect
+    CameraUsageStatsRecord mStats GUARDED_BY(mMutex);
+
+public:
+    void framesReceived(int n = 1) EXCLUDES(mMutex);
+    void framesReturned(int n = 1) EXCLUDES(mMutex);
+    void framesIgnored(int n = 1) EXCLUDES(mMutex);
+    void framesSkippedToSync(int n = 1) EXCLUDES(mMutex);
+    void eventsReceived() EXCLUDES(mMutex);
+    int64_t getTimeCreated() const EXCLUDES(mMutex);
+    int64_t getFramesReceived() const EXCLUDES(mMutex);
+    int64_t getFramesReturned() const EXCLUDES(mMutex);
+
+    // Returns the statistics collected so far
+    CameraUsageStatsRecord snapshot() const EXCLUDES(mMutex);
+
+    // Reports the usage statistics
+    android::base::Result<void> writeStats() const EXCLUDES(mMutex);
+
+    // Generates a string with current statistics
+    static std::string toString(const CameraUsageStatsRecord& record, const char* indent = "");
+};
+
+
+} // namespace implementation
+} // namespace V1_1
+} // namespace evs
+} // namespace automotive
+} // namespace android
+
+#endif // ANDROID_AUTOMOTIVE_EVS_V1_1_CAMERAUSAGESTATS_H
diff --git a/evs/manager/1.1/stats/LooperWrapper.cpp b/evs/manager/1.1/stats/LooperWrapper.cpp
new file mode 100644
index 0000000..041a723
--- /dev/null
+++ b/evs/manager/1.1/stats/LooperWrapper.cpp
@@ -0,0 +1,82 @@
+/**
+ * Copyright 2020 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 "LooperWrapper.h"
+
+#include <android-base/logging.h>
+
+namespace android {
+namespace automotive {
+namespace evs {
+namespace V1_1 {
+namespace implementation {
+
+using android::sp;
+
+void LooperWrapper::wake() {
+    if (mLooper == nullptr) {
+        LOG(WARNING) << __FUNCTION__ << ": Looper is invalid.";
+        return;
+    }
+
+    return mLooper->wake();
+}
+
+int LooperWrapper::pollAll(int timeoutMillis) {
+    if (mLooper == nullptr) {
+        LOG(WARNING) << __FUNCTION__ << ": Looper is invalid.";
+        return 0;
+    }
+
+    return mLooper->pollAll(timeoutMillis);
+}
+
+void LooperWrapper::sendMessage(const sp<MessageHandler>& handler,
+                                   const Message& message) {
+    if (mLooper == nullptr) {
+        LOG(WARNING) << __FUNCTION__ << ": Looper is invalid.";
+        return;
+    }
+
+    return mLooper->sendMessage(handler, message);
+}
+
+void LooperWrapper::sendMessageAtTime(nsecs_t uptime,
+                                         const sp<MessageHandler>& handler,
+                                         const Message& message) {
+    if (mLooper == nullptr) {
+        LOG(WARNING) << __FUNCTION__ << ": Looper is invalid.";
+        return;
+    }
+
+    return mLooper->sendMessageAtTime(uptime, handler, message);
+}
+
+void LooperWrapper::removeMessages(const sp<MessageHandler>& handler) {
+    if (mLooper == nullptr) {
+        LOG(WARNING) << __FUNCTION__ << ": Looper is invalid.";
+        return;
+    }
+
+    return mLooper->removeMessages(handler);
+}
+
+} // namespace implementation
+} // namespace V1_1
+} // namespace evs
+} // namespace automotive
+} // namespace android
+
diff --git a/evs/manager/1.1/stats/LooperWrapper.h b/evs/manager/1.1/stats/LooperWrapper.h
new file mode 100644
index 0000000..fb9ec12
--- /dev/null
+++ b/evs/manager/1.1/stats/LooperWrapper.h
@@ -0,0 +1,57 @@
+/**
+ * Copyright 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_AUTMOTIVE_EVS_V1_1_EVSLOOPERWRAPPER_H_
+#define ANDROID_AUTMOTIVE_EVS_V1_1_EVSLOOPERWRAPPER_H_
+
+#include <utils/Looper.h>
+#include <utils/RefBase.h>
+#include <utils/Timers.h>
+
+namespace android {
+namespace automotive {
+namespace evs {
+namespace V1_1 {
+namespace implementation {
+
+// This class wraps around android::Looper methods.  Please refer to
+// utils/Looper.h for the details.
+class LooperWrapper : public RefBase {
+public:
+    LooperWrapper() : mLooper(nullptr) {}
+    virtual ~LooperWrapper() {}
+
+    void setLooper(android::sp<Looper> looper) { mLooper = looper; }
+    void wake();
+    virtual nsecs_t now() { return systemTime(SYSTEM_TIME_MONOTONIC); }
+    virtual int pollAll(int timeoutMillis);
+    virtual void sendMessage(const android::sp<MessageHandler>& handler, const Message& message);
+    virtual void sendMessageAtTime(nsecs_t uptime, const android::sp<MessageHandler>& handler,
+                                   const Message& message);
+    virtual void removeMessages(const android::sp<MessageHandler>& handler);
+
+protected:
+    android::sp<Looper> mLooper;
+};
+
+} // namespace implementation
+} // namespace V1_1
+} // namespace evs
+} // namespace automotive
+} // namespace android
+
+#endif  // ANDROID_AUTMOTIVE_EVS_V1_1_EVSLOOPERWRAPPER_H_
+
diff --git a/evs/manager/1.1/stats/StatsCollector.cpp b/evs/manager/1.1/stats/StatsCollector.cpp
new file mode 100644
index 0000000..de85e5a
--- /dev/null
+++ b/evs/manager/1.1/stats/StatsCollector.cpp
@@ -0,0 +1,468 @@
+/*
+ * Copyright 2020 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 "HalCamera.h"
+#include "StatsCollector.h"
+#include "VirtualCamera.h"
+
+#include <processgroup/sched_policy.h>
+#include <pthread.h>
+
+#include <android-base/file.h>
+#include <android-base/strings.h>
+#include <android-base/stringprintf.h>
+#include <utils/SystemClock.h>
+
+namespace {
+
+    const char* kSingleIndent = "\t";
+    const char* kDoubleIndent = "\t\t";
+    const char* kDumpAllDevices = "all";
+
+}
+
+namespace android {
+namespace automotive {
+namespace evs {
+namespace V1_1 {
+namespace implementation {
+
+using android::base::Error;
+using android::base::EqualsIgnoreCase;
+using android::base::Result;
+using android::base::StringAppendF;
+using android::base::StringPrintf;
+using android::base::WriteStringToFd;
+using android::hardware::automotive::evs::V1_1::BufferDesc;
+
+namespace {
+
+const auto kPeriodicCollectionInterval = 10s;
+const auto kPeriodicCollectionCacheSize = 180;
+const auto kMinCollectionInterval = 1s;
+const auto kCustomCollectionMaxDuration = 30min;
+const auto kMaxDumpHistory = 10;
+
+}
+
+void StatsCollector::handleMessage(const Message& message) {
+    const auto received = static_cast<CollectionEvent>(message.what);
+    Result<void> ret;
+    switch (received) {
+        case CollectionEvent::PERIODIC:
+            ret = handleCollectionEvent(received, &mPeriodicCollectionInfo);
+            break;
+
+        case CollectionEvent::CUSTOM_START:
+            ret = handleCollectionEvent(received, &mCustomCollectionInfo);
+            break;
+
+        case CollectionEvent::CUSTOM_END: {
+            AutoMutex lock(mMutex);
+            if (mCurrentCollectionEvent != CollectionEvent::CUSTOM_START) {
+                LOG(WARNING) << "Ignoring a message to end custom collection "
+                             << "as current collection is " << toString(mCurrentCollectionEvent);
+                return;
+            }
+
+            // Starts a periodic collection
+            mLooper->removeMessages(this);
+            mCurrentCollectionEvent = CollectionEvent::PERIODIC;
+            mPeriodicCollectionInfo.lastCollectionTime = mLooper->now();
+            mLooper->sendMessage(this, CollectionEvent::PERIODIC);
+            return;
+        }
+
+        default:
+            LOG(WARNING) << "Unknown event is received: " << received;
+            break;
+    }
+
+    if (!ret.ok()) {
+        Mutex::Autolock lock(mMutex);
+        LOG(ERROR) << "Terminating data collection: "
+                   << ret.error();
+
+        mCurrentCollectionEvent = CollectionEvent::TERMINATED;
+        mLooper->removeMessages(this);
+        mLooper->wake();
+    }
+}
+
+
+Result<void> StatsCollector::handleCollectionEvent(CollectionEvent event,
+                                                   CollectionInfo* info) {
+    AutoMutex lock(mMutex);
+    if (mCurrentCollectionEvent != event) {
+        LOG(WARNING) << "Skipping " << toString(event) << " collection event "
+                     << "on collection event " << toString(mCurrentCollectionEvent);
+        return {};
+    }
+
+    if (info->maxCacheSize < 1) {
+        return Error() << "Maximum cache size must be greater than 0";
+    }
+
+    using std::chrono::duration_cast;
+    using std::chrono::seconds;
+    if (info->interval < kMinCollectionInterval) {
+        LOG(WARNING) << "Collection interval of "
+                     << duration_cast<seconds>(info->interval).count()
+                     << " seconds for " << toString(event)
+                     << " collection cannot be shorter than "
+                     << duration_cast<seconds>(kMinCollectionInterval).count()
+                     << " seconds.";
+        info->interval = kMinCollectionInterval;
+    }
+
+    auto ret = collectLocked(info);
+    if (!ret) {
+        return Error() << toString(event) << " collection failed: "
+                       << ret.error();
+    }
+
+    // Arms a message for next periodic collection
+    info->lastCollectionTime += info->interval.count();
+    mLooper->sendMessageAtTime(info->lastCollectionTime, this, event);
+    return {};
+}
+
+
+Result<void> StatsCollector::collectLocked(CollectionInfo* info) REQUIRES(mMutex) {
+    for (auto&& [id, ptr] : mClientsToMonitor) {
+        auto pClient = ptr.promote();
+        if (!pClient) {
+            LOG(DEBUG) << id << " seems not alive.";
+            continue;
+        }
+
+        // Pulls a snapshot and puts a timestamp
+        auto snapshot = pClient->getStats();
+        snapshot.timestamp = mLooper->now();
+
+        // Removes the oldest record if cache is full
+        if (info->records[id].history.size() > info->maxCacheSize) {
+            info->records[id].history.pop_front();
+        }
+
+        // Stores the latest record and the deltas
+        auto delta = snapshot - info->records[id].latest;
+        info->records[id].history.emplace_back(delta);
+        info->records[id].latest = snapshot;
+    }
+
+    return {};
+}
+
+
+Result<void> StatsCollector::startCollection() {
+    {
+        AutoMutex lock(mMutex);
+        if (mCurrentCollectionEvent != CollectionEvent::INIT ||
+            mCollectionThread.joinable()) {
+            return Error(INVALID_OPERATION)
+                   << "Camera usages collection is already running.";
+        }
+
+        // Create the collection info w/ the default values
+        mPeriodicCollectionInfo = {
+            .interval = kPeriodicCollectionInterval,
+            .maxCacheSize = kPeriodicCollectionCacheSize,
+            .lastCollectionTime = 0,
+        };
+
+    }
+
+    // Starts a background worker thread
+    mCollectionThread = std::thread([&]() {
+        {
+            AutoMutex lock(mMutex);
+            if (mCurrentCollectionEvent != CollectionEvent::INIT) {
+                LOG(ERROR) << "Skipping the statistics collection because "
+                           << "the current collection event is "
+                           << toString(mCurrentCollectionEvent);
+                return;
+            }
+
+            // Staring with a periodic collection
+            mCurrentCollectionEvent = CollectionEvent::PERIODIC;
+        }
+
+        if (set_sched_policy(0, SP_BACKGROUND) != 0) {
+            PLOG(WARNING) << "Failed to set background scheduling prioirty";
+        }
+
+        auto ret = pthread_setname_np(pthread_self(), "EvsCameraUsageCollect");
+        if (ret != 0) {
+            PLOG(WARNING) << "Failed to name a collection thread";
+        }
+
+        // Sets a looper for the communication
+        mLooper->setLooper(Looper::prepare(/*opts=*/0));
+
+        // Starts collecting the usage statistics periodically
+        mLooper->sendMessage(this, CollectionEvent::PERIODIC);
+
+        // Polls the messages until the collection is stopped.
+        bool isActive = true;
+        while (isActive) {
+            mLooper->pollAll(/*timeoutMillis=*/-1);
+            {
+                AutoMutex lock(mMutex);
+                isActive = mCurrentCollectionEvent != CollectionEvent::TERMINATED;
+            }
+        }
+    });
+
+    return {};
+}
+
+
+Result<void> StatsCollector::stopCollection() {
+    {
+        AutoMutex lock(mMutex);
+        if (mCurrentCollectionEvent == CollectionEvent::TERMINATED) {
+            LOG(WARNING) << "Camera usage data collection was stopped already.";
+            return {};
+        }
+
+        LOG(INFO) << "Stopping a camera usage data collection";
+        mCurrentCollectionEvent = CollectionEvent::TERMINATED;
+    }
+
+    // Join a background thread
+    if (mCollectionThread.joinable()) {
+        mLooper->removeMessages(this);
+        mLooper->wake();
+        mCollectionThread.join();
+    }
+
+    return {};
+}
+
+
+Result<void> StatsCollector::startCustomCollection(
+        std::chrono::nanoseconds interval,
+        std::chrono::nanoseconds maxDuration) {
+    using std::chrono::duration_cast;
+    using std::chrono::milliseconds;
+    if (interval < kMinCollectionInterval || maxDuration < kMinCollectionInterval) {
+        return Error(INVALID_OPERATION)
+                << "Collection interval and maximum maxDuration must be >= "
+                << duration_cast<milliseconds>(kMinCollectionInterval).count()
+                << " milliseconds.";
+    }
+
+    if (maxDuration > kCustomCollectionMaxDuration) {
+        return Error(INVALID_OPERATION)
+                << "Collection maximum maxDuration must be less than "
+                << duration_cast<milliseconds>(kCustomCollectionMaxDuration).count()
+                << " milliseconds.";
+    }
+
+    {
+        AutoMutex lock(mMutex);
+        if (mCurrentCollectionEvent != CollectionEvent::PERIODIC) {
+            return Error(INVALID_OPERATION)
+                    << "Cannot start a custom collection when "
+                    << "the current collection event " << toString(mCurrentCollectionEvent)
+                    << " != " << toString(CollectionEvent::PERIODIC) << " collection event";
+        }
+
+        // Notifies the user if a preview custom collection result is
+        // not used yet.
+        if (mCustomCollectionInfo.records.size() > 0) {
+            LOG(WARNING) << "Previous custom collection result, which was done at "
+                         << mCustomCollectionInfo.lastCollectionTime
+                         << " has not pulled yet will be overwritten.";
+        }
+
+        // Programs custom collection configurations
+        mCustomCollectionInfo = {
+                .interval = interval,
+                .maxCacheSize = std::numeric_limits<std::size_t>::max(),
+                .lastCollectionTime = mLooper->now(),
+                .records = {},
+        };
+
+        mLooper->removeMessages(this);
+        nsecs_t uptime = mLooper->now() + maxDuration.count();
+        mLooper->sendMessageAtTime(uptime, this, CollectionEvent::CUSTOM_END);
+        mCurrentCollectionEvent = CollectionEvent::CUSTOM_START;
+        mLooper->sendMessage(this, CollectionEvent::CUSTOM_START);
+    }
+
+    return {};
+}
+
+
+Result<std::string> StatsCollector::stopCustomCollection(std::string targetId) {
+    Mutex::Autolock lock(mMutex);
+    if (mCurrentCollectionEvent == CollectionEvent::CUSTOM_START) {
+        // Stops a running custom collection
+        mLooper->removeMessages(this);
+        mLooper->sendMessage(this, CollectionEvent::CUSTOM_END);
+    }
+
+    auto ret = collectLocked(&mCustomCollectionInfo);
+    if (!ret) {
+        return Error() << toString(mCurrentCollectionEvent) << " collection failed: "
+                       << ret.error();
+    }
+
+    // Prints out the all collected statistics
+    std::string buffer;
+    using std::chrono::duration_cast;
+    using std::chrono::seconds;
+    const intmax_t interval =
+        duration_cast<seconds>(mCustomCollectionInfo.interval).count();
+    if (EqualsIgnoreCase(targetId, kDumpAllDevices)) {
+        for (auto& [id, records] : mCustomCollectionInfo.records) {
+
+            StringAppendF(&buffer, "%s\n"
+                                   "%sNumber of collections: %zu\n"
+                                   "%sCollection interval: %" PRIdMAX " secs\n",
+                                   id.c_str(),
+                                   kSingleIndent, records.history.size(),
+                                   kSingleIndent, interval);
+            auto it = records.history.rbegin();
+            while (it != records.history.rend()) {
+                buffer += it++->toString(kDoubleIndent);
+            }
+        }
+
+        // Clears the collection
+        mCustomCollectionInfo = {};
+    } else {
+        auto it = mCustomCollectionInfo.records.find(targetId);
+        if (it != mCustomCollectionInfo.records.end()) {
+            StringAppendF(&buffer, "%s\n"
+                                   "%sNumber of collections: %zu\n"
+                                   "%sCollection interval: %" PRIdMAX " secs\n",
+                                   targetId.c_str(),
+                                   kSingleIndent, it->second.history.size(),
+                                   kSingleIndent, interval);
+            auto recordIter = it->second.history.rbegin();
+            while (recordIter != it->second.history.rend()) {
+                buffer += recordIter++->toString(kDoubleIndent);
+            }
+
+            // Clears the collection
+            mCustomCollectionInfo = {};
+        } else {
+            // Keeps the collection as the users may want to execute a command
+            // again with a right device id
+            StringAppendF(&buffer, "%s has not been monitored.", targetId.c_str());
+        }
+    }
+
+    return buffer;
+}
+
+
+Result<void> StatsCollector::registerClientToMonitor(android::sp<HalCamera>& camera) {
+    if (!camera) {
+        return Error(BAD_VALUE) << "Given camera client is invalid";
+    }
+
+    AutoMutex lock(mMutex);
+    const auto id = camera->getId();
+    if (mClientsToMonitor.find(id) != mClientsToMonitor.end()) {
+        LOG(WARNING) << id << " is already registered.";
+    } else {
+        mClientsToMonitor.insert_or_assign(id, camera);
+    }
+
+    return {};
+}
+
+
+Result<void> StatsCollector::unregisterClientToMonitor(const std::string& id) {
+    AutoMutex lock(mMutex);
+    auto entry = mClientsToMonitor.find(id);
+    if (entry != mClientsToMonitor.end()) {
+        mClientsToMonitor.erase(entry);
+    } else {
+        LOG(WARNING) << id << " has not been registerd.";
+    }
+
+    return {};
+}
+
+
+std::string StatsCollector::toString(const CollectionEvent& event) const {
+    switch(event) {
+        case CollectionEvent::INIT:
+            return "CollectionEvent::INIT";
+        case CollectionEvent::PERIODIC:
+            return "CollectionEvent::PERIODIC";
+        case CollectionEvent::CUSTOM_START:
+            return "CollectionEvent::CUSTOM_START";
+        case CollectionEvent::CUSTOM_END:
+            return "CollectionEvent::CUSTOM_END";
+        case CollectionEvent::TERMINATED:
+            return "CollectionEvent::TERMINATED";
+
+        default:
+            return "Unknown";
+    }
+}
+
+
+Result<void> StatsCollector::toString(std::unordered_map<std::string, std::string>* usages,
+                                      const char* indent) EXCLUDES(mMutex) {
+    std::string double_indent(indent);
+    double_indent += indent;
+
+    {
+        AutoMutex lock(mMutex);
+        using std::chrono::duration_cast;
+        using std::chrono::seconds;
+        const intmax_t interval =
+            duration_cast<seconds>(mPeriodicCollectionInfo.interval).count();
+
+        for (auto&& [id, records] : mPeriodicCollectionInfo.records) {
+            std::string buffer;
+            StringAppendF(&buffer, "%s\n"
+                                   "%sNumber of collections: %zu\n"
+                                   "%sCollection interval: %" PRIdMAX "secs\n",
+                                   id.c_str(),
+                                   indent, records.history.size(),
+                                   indent, interval);
+
+            // Adding up to kMaxDumpHistory records
+            auto it = records.history.rbegin();
+            auto count = 0;
+            while (it != records.history.rend() && count < kMaxDumpHistory) {
+                buffer += it->toString(double_indent.c_str());
+                ++it;
+                ++count;
+            }
+
+            usages->insert_or_assign(id, std::move(buffer));
+        }
+    }
+
+    return {};
+}
+
+
+} // namespace implementation
+} // namespace V1_1
+} // namespace evs
+} // namespace automotive
+} // namespace android
+
diff --git a/evs/manager/1.1/stats/StatsCollector.h b/evs/manager/1.1/stats/StatsCollector.h
new file mode 100644
index 0000000..031b342
--- /dev/null
+++ b/evs/manager/1.1/stats/StatsCollector.h
@@ -0,0 +1,163 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_AUTOMOTIVE_EVS_V1_1_EVSSTATSCOLLECTOR_H
+#define ANDROID_AUTOMOTIVE_EVS_V1_1_EVSSTATSCOLLECTOR_H
+
+#include "CameraUsageStats.h"
+#include "LooperWrapper.h"
+
+#include <deque>
+#include <thread>
+#include <unordered_map>
+#include <vector>
+
+#include <android/hardware/automotive/evs/1.1/types.h>
+#include <android-base/chrono_utils.h>
+#include <android-base/logging.h>
+#include <android-base/result.h>
+#include <utils/Mutex.h>
+
+namespace android {
+namespace automotive {
+namespace evs {
+namespace V1_1 {
+namespace implementation {
+
+class HalCamera;    // From VirtualCamera.h
+
+enum CollectionEvent {
+    INIT = 0,
+    PERIODIC,
+    CUSTOM_START,
+    CUSTOM_END,
+    TERMINATED,
+
+    LAST_EVENT,
+};
+
+
+struct CollectionRecord {
+    // Latest statistics collection
+    CameraUsageStatsRecord latest = {};
+
+    // History of collected statistics records
+    std::deque<CameraUsageStatsRecord> history;
+};
+
+
+struct CollectionInfo {
+    // Collection interval between two subsequent collections
+    std::chrono::nanoseconds interval = 0ns;
+
+    // The maximum number of records this collection stores
+    size_t maxCacheSize = 0;
+
+    // Time when the latest collection was done
+    nsecs_t lastCollectionTime = 0;
+
+    // Collected statistics records per instances
+    std::unordered_map<std::string, CollectionRecord> records;
+};
+
+
+class StatsCollector : public MessageHandler {
+public:
+    explicit StatsCollector() :
+        mLooper(new LooperWrapper()),
+        mCurrentCollectionEvent(CollectionEvent::INIT),
+        mPeriodicCollectionInfo({}),
+        mCustomCollectionInfo({}) {}
+
+    virtual ~StatsCollector() { stopCollection(); }
+
+    // Starts collecting CameraUsageStats
+    android::base::Result<void> startCollection();
+
+    // Stops collecting the statistics
+    android::base::Result<void> stopCollection();
+
+    // Starts collecting CameraUsageStarts during a given duration at a given
+    // interval.
+    android::base::Result<void> startCustomCollection(
+            std::chrono::nanoseconds interval,
+            std::chrono::nanoseconds duration) EXCLUDES(mMutex);
+
+    // Stops current custom collection and shows the result from the device with
+    // a given unique id.  If this is "all",all results
+    // will be returned.
+    android::base::Result<std::string> stopCustomCollection(
+            std::string id = "") EXCLUDES(mMutex);
+
+    // Registers HalCamera object to monitor
+    android::base::Result<void> registerClientToMonitor(
+            android::sp<HalCamera>& camera) EXCLUDES(mMutex);
+
+    // Unregister HalCamera object
+    android::base::Result<void> unregisterClientToMonitor(
+            const std::string& id) EXCLUDES(mMutex);
+
+    // Returns a string that contains the latest statistics pulled from
+    // currently active clients
+    android::base::Result<void> toString(
+            std::unordered_map<std::string, std::string>* usages,
+            const char* indent = "") EXCLUDES(mMutex);
+
+private:
+    // Mutex to protect records
+    mutable Mutex mMutex;
+
+    // Looper to message the collection thread
+    android::sp<LooperWrapper> mLooper;
+
+    // Background thread to pull stats from the clients
+    std::thread mCollectionThread;
+
+    // Current state of the monitor
+    CollectionEvent mCurrentCollectionEvent GUARDED_BY(mMutex);
+
+    // Periodic collection information
+    CollectionInfo  mPeriodicCollectionInfo GUARDED_BY(mMutex);
+
+    // A collection during the custom period the user sets
+    CollectionInfo  mCustomCollectionInfo GUARDED_BY(mMutex);
+
+    // A list of HalCamera objects to monitor
+    std::unordered_map<std::string,
+                       android::wp<HalCamera>> mClientsToMonitor GUARDED_BY(mMutex);
+
+    // Handles the messages from the looper
+    void handleMessage(const Message& message) override;
+
+    // Handles each CollectionEvent
+    android::base::Result<void> handleCollectionEvent(
+            CollectionEvent event, CollectionInfo* info) EXCLUDES(mMutex);
+
+    // Pulls the statistics from each active HalCamera objects and generates the
+    // records
+    android::base::Result<void> collectLocked(CollectionInfo* info) REQUIRES(mMutex);
+
+    // Returns a string corresponding to a given collection event
+    std::string toString(const CollectionEvent& event) const;
+};
+
+} // namespace implementation
+} // namespace V1_1
+} // namespace evs
+} // namespace automotive
+} // namespace android
+
+#endif // ANDROID_AUTOMOTIVE_EVS_V1_1_EVSSTATSCOLLECTOR_H
diff --git a/evs/sampleDriver/Android.bp b/evs/sampleDriver/Android.bp
index 1722200..1bee713 100644
--- a/evs/sampleDriver/Android.bp
+++ b/evs/sampleDriver/Android.bp
@@ -58,10 +58,6 @@
 
     init_rc: ["android.hardware.automotive.evs@1.1-sample.rc"],
 
-    strip: {
-        keep_symbols: true,
-    },
-
     cflags: ["-DLOG_TAG=\"EvsSampleDriver\""] + [
         "-DGL_GLEXT_PROTOTYPES",
         "-DEGL_EGLEXT_PROTOTYPES",
diff --git a/evs/sampleDriver/EvsEnumerator.cpp b/evs/sampleDriver/EvsEnumerator.cpp
index e108007..ef6009e 100644
--- a/evs/sampleDriver/EvsEnumerator.cpp
+++ b/evs/sampleDriver/EvsEnumerator.cpp
@@ -534,6 +534,12 @@
     }
 
     // Create a new display interface and return it
+    if (sDisplayPortList.find(port) == sDisplayPortList.end()) {
+        LOG(ERROR) << "No display is available on the port "
+                   << static_cast<int32_t>(port);
+        return nullptr;
+    }
+
     pActiveDisplay = new EvsGlDisplay(sDisplayProxy, sDisplayPortList[port]);
     sActiveDisplay = pActiveDisplay;
 
diff --git a/evs/support_library/Android.bp b/evs/support_library/Android.bp
index 6556404..8b77a5d 100644
--- a/evs/support_library/Android.bp
+++ b/evs/support_library/Android.bp
@@ -56,10 +56,6 @@
         "camera_config.json",
     ],
 
-    strip: {
-        keep_symbols: true,
-    },
-
     cflags: ["-DLOG_TAG=\"libevssupport\""] + [
         "-DGL_GLEXT_PROTOTYPES",
         "-DEGL_EGLEXT_PROTOTYPES",
diff --git a/service/src/com/android/car/vms/VmsBrokerService.java b/service/src/com/android/car/vms/VmsBrokerService.java
index aed7e69..b2dbaa1 100644
--- a/service/src/com/android/car/vms/VmsBrokerService.java
+++ b/service/src/com/android/car/vms/VmsBrokerService.java
@@ -53,6 +53,7 @@
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -111,7 +112,17 @@
 
     @Override
     public void dump(PrintWriter writer) {
-        // TODO(b/149125079): Implement dumpsys
+        writer.println("*" + TAG + "*");
+        synchronized (mLock) {
+            writer.println("mAvailableLayers: " + mAvailableLayers.getAvailableLayers());
+            writer.println();
+            writer.println("mSubscriptionState: " + mSubscriptionState);
+            writer.println();
+            writer.println("mClientMap:");
+            mClientMap.values().stream()
+                    .sorted(Comparator.comparingInt(VmsClientInfo::getUid))
+                    .forEach(client -> client.dump(writer, "  "));
+        }
     }
 
     @Override
diff --git a/service/src/com/android/car/vms/VmsClientInfo.java b/service/src/com/android/car/vms/VmsClientInfo.java
index b1ad912..d64b6b7 100644
--- a/service/src/com/android/car/vms/VmsClientInfo.java
+++ b/service/src/com/android/car/vms/VmsClientInfo.java
@@ -28,6 +28,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 
+import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -180,6 +181,52 @@
         }
     }
 
+    void dump(PrintWriter writer, String indent) {
+        synchronized (mLock) {
+            String prefix = indent;
+            writer.println(prefix + "VmsClient [" + mPackageName + "]");
+
+            prefix = indent + "  ";
+            writer.println(prefix + "UID: " + mUid);
+            writer.println(prefix + "Legacy Client: " + mLegacyClient);
+            writer.println(prefix + "Monitoring: " + mMonitoringEnabled);
+
+            if (mProviderIds.size() > 0) {
+                writer.println(prefix + "Offerings:");
+                for (int i = 0; i < mProviderIds.size(); i++) {
+                    prefix = indent + "    ";
+                    int providerId = mProviderIds.keyAt(i);
+                    writer.println(prefix + "Provider [" + providerId + "]");
+
+                    for (VmsLayerDependency layerOffering : mOfferings.get(
+                            providerId, Collections.emptySet())) {
+                        prefix = indent + "      ";
+                        writer.println(prefix + layerOffering.getLayer());
+                        if (!layerOffering.getDependencies().isEmpty()) {
+                            prefix = indent + "        ";
+                            writer.println(prefix + "Dependencies: "
+                                    + layerOffering.getDependencies());
+                        }
+                    }
+                }
+            }
+
+            if (!mLayerSubscriptions.isEmpty() || !mLayerAndProviderSubscriptions.isEmpty()) {
+                prefix = indent + "  ";
+                writer.println(prefix + "Subscriptions:");
+
+                prefix = indent + "    ";
+                for (VmsLayer layer : mLayerSubscriptions) {
+                    writer.println(prefix + layer);
+                }
+                for (Map.Entry<VmsLayer, Set<Integer>> layerEntry :
+                        mLayerAndProviderSubscriptions.entrySet()) {
+                    writer.println(prefix + layerEntry.getKey() + ": " + layerEntry.getValue());
+                }
+            }
+        }
+    }
+
     private static <K, V> Map<K, Set<V>> deepCopy(Map<K, Set<V>> original) {
         return original.entrySet().stream().collect(Collectors.toMap(
                 Map.Entry::getKey,
diff --git a/service/src/com/android/car/watchdog/CarWatchdogService.java b/service/src/com/android/car/watchdog/CarWatchdogService.java
index 041ba29..8a1ebc7 100644
--- a/service/src/com/android/car/watchdog/CarWatchdogService.java
+++ b/service/src/com/android/car/watchdog/CarWatchdogService.java
@@ -68,7 +68,7 @@
  */
 public final class CarWatchdogService extends ICarWatchdogService.Stub implements CarServiceBase {
 
-    private static final boolean DEBUG = true; // STOPSHIP if true (b/151474489)
+    private static final boolean DEBUG = false; // STOPSHIP if true
     private static final String TAG = TAG_WATCHDOG;
     private static final int[] ALL_TIMEOUTS =
             { TIMEOUT_CRITICAL, TIMEOUT_MODERATE, TIMEOUT_NORMAL };
diff --git a/tests/android_car_api_test/src/android/car/apitest/CarActivityViewDisplayIdTest.java b/tests/android_car_api_test/src/android/car/apitest/CarActivityViewDisplayIdTest.java
index 93f2530..ee6dbbb 100644
--- a/tests/android_car_api_test/src/android/car/apitest/CarActivityViewDisplayIdTest.java
+++ b/tests/android_car_api_test/src/android/car/apitest/CarActivityViewDisplayIdTest.java
@@ -24,7 +24,6 @@
 import static org.junit.Assume.assumeTrue;
 import static org.testng.Assert.assertThrows;
 
-import androidx.test.filters.FlakyTest;
 import androidx.test.filters.MediumTest;
 
 import org.junit.Test;
@@ -42,7 +41,6 @@
     private static final int NONEXISTENT_DISPLAY_ID = Integer.MAX_VALUE;
 
     @Test
-    @FlakyTest
     public void testSingleActivityView() throws Exception {
         ActivityViewTestActivity activity = startActivityViewTestActivity(DEFAULT_DISPLAY);
         activity.waitForActivityViewReady();
@@ -65,7 +63,6 @@
     }
 
     @Test
-    @FlakyTest
     public void testDoubleActivityView() throws Exception {
         ActivityViewTestActivity activity1 = startActivityViewTestActivity(DEFAULT_DISPLAY);
         activity1.waitForActivityViewReady();
@@ -100,7 +97,6 @@
     }
 
     @Test
-    @FlakyTest
     public void testThrowsExceptionOnReportingNonExistingDisplay() throws Exception {
         ActivityViewTestActivity activity = startActivityViewTestActivity(DEFAULT_DISPLAY);
         activity.waitForActivityViewReady();
@@ -125,7 +121,6 @@
 
     // TODO(b/143353546): Make the following tests not to rely on CarLauncher.
     @Test
-    @FlakyTest
     public void testThrowsExceptionOnReportingNonOwningDisplay() throws Exception {
         int displayIdOfCarLauncher = waitForActivityViewDisplayReady(CAR_LAUNCHER_PKG_NAME);
         assumeTrue(INVALID_DISPLAY != displayIdOfCarLauncher);
diff --git a/tests/android_car_api_test/src/android/car/apitest/CarUserManagerTest.java b/tests/android_car_api_test/src/android/car/apitest/CarUserManagerTest.java
index e18a200..cb58d3f 100644
--- a/tests/android_car_api_test/src/android/car/apitest/CarUserManagerTest.java
+++ b/tests/android_car_api_test/src/android/car/apitest/CarUserManagerTest.java
@@ -65,7 +65,7 @@
      * Stopping the user takes a while, even when calling force stop - change it to false if this
      * test becomes flaky.
      */
-    private static final boolean TEST_STOP = true;
+    private static final boolean TEST_STOP = false;
 
     private static final UserManager sUserManager = UserManager.get(sContext);
 
@@ -165,10 +165,10 @@
         // Switch back to the previous user
         switchUser(oldUserId);
 
-        // Must force stop the user, otherwise it can take minutes for its process to finish
-        forceStopUser(newUserId);
-
         if (TEST_STOP) {
+            // Must force stop the user, otherwise it can take minutes for its process to finish
+            forceStopUser(newUserId);
+
             // waitForEvents() will also return events for previous user...
             List<UserLifecycleEvent> allEvents = stopListener.waitForEvents();
             Log.d(TAG, "All received events on stopListener: " + allEvents);
@@ -183,6 +183,8 @@
                 assertWithMessage("wrong userHandle on %s", event)
                     .that(event.getUserHandle().getIdentifier()).isEqualTo(newUserId);
             }
+        } else {
+            Log.w(TAG, "NOT testing user stop events");
         }
 
         // Make sure unregistered listener din't receive any more events
diff --git a/watchdog/server/src/WatchdogProcessService.cpp b/watchdog/server/src/WatchdogProcessService.cpp
index 749ed30..8d4ec06 100644
--- a/watchdog/server/src/WatchdogProcessService.cpp
+++ b/watchdog/server/src/WatchdogProcessService.cpp
@@ -15,7 +15,7 @@
  */
 
 #define LOG_TAG "carwatchdogd"
-#define DEBUG true  // TODO(b/151474489): stop ship if true.
+#define DEBUG false  // STOPSHIP if true.
 
 #include "WatchdogProcessService.h"