Add 'watch' command to CameraService

Currently there is no way to monitor tags and dump per frame data from
Camera devices without calling dumpsys, which in turn dumps a lot of
unnecessary information.

This CL adds a 'watch' command to CameraService which can be called
through `cmd`. 'watch' provides a path to manage tag monitoring without
dumpsys. 'watch' currently has four functions:
1. start <taglist>: starts mornitoring the provided tags in attached
                    devices.
2. stop: stop monitoring all tags
3. live: prints monitored information to the terminal live
4. dump: dumps any collected camera traces

This CL adds a function to camera device each for the three tag
monitoring function listed above.

Test: n/a
Bug: 199746421
Change-Id: Ia5ce13f6e50c82085afcc676309cdbffef915a41
diff --git a/services/camera/libcameraservice/CameraService.cpp b/services/camera/libcameraservice/CameraService.cpp
index 49d5cd8..88c0bb5 100644
--- a/services/camera/libcameraservice/CameraService.cpp
+++ b/services/camera/libcameraservice/CameraService.cpp
@@ -3115,6 +3115,21 @@
     return OK;
 }
 
+status_t CameraService::BasicClient::startWatchingTags(const String8&, int) {
+    // Can't watch tags directly, must go through CameraService::startWatchingTags
+    return OK;
+}
+
+status_t CameraService::BasicClient::stopWatchingTags(int) {
+    // Can't watch tags directly, must go through CameraService::stopWatchingTags
+    return OK;
+}
+
+status_t CameraService::BasicClient::dumpWatchedEventsToVector(std::vector<std::string> &) {
+    // Can't watch tags directly, must go through CameraService::dumpWatchedEventsToVector
+    return OK;
+}
+
 String16 CameraService::BasicClient::getPackageName() const {
     return mClientPackageName;
 }
@@ -4136,7 +4151,7 @@
 
     // Dump camera traces if there were any
     dprintf(fd, "\n");
-    camera3::CameraTraces::dump(fd, args);
+    camera3::CameraTraces::dump(fd);
 
     // Process dump arguments, if any
     int n = args.size();
@@ -4534,9 +4549,11 @@
         return handleGetImageDumpMask(out);
     } else if (args.size() >= 2 && args[0] == String16("set-camera-mute")) {
         return handleSetCameraMute(args);
+    } else if (args.size() >= 2 && args[0] == String16("watch")) {
+        return handleWatchCommand(args, out);
     } else if (args.size() == 1 && args[0] == String16("help")) {
         printHelp(out);
-        return NO_ERROR;
+        return OK;
     }
     printHelp(err);
     return BAD_VALUE;
@@ -4680,6 +4697,134 @@
     return OK;
 }
 
+status_t CameraService::handleWatchCommand(const Vector<String16>& args, int out) {
+    if (args.size() == 3 && args[1] == String16("start")) {
+        return startWatchingTags(args[2], out);
+    } else if (args.size() == 2 && args[1] == String16("dump")) {
+        return camera3::CameraTraces::dump(out);
+    } else if (args.size() == 2 && args[1] == String16("stop")) {
+        return stopWatchingTags(out);
+    } else if (args.size() >= 2 && args[1] == String16("live")) {
+        return printWatchedTagsUntilInterrupt(args, out);
+    }
+    dprintf(out, "Camera service watch commands:\n"
+                 "  start <comma_separated_tag_list> starts watching the provided tags\n"
+                 "                                   recognizes shorthands like '3a'\n"
+                 "  dump dumps camera trace\n"
+                 "  stop stops watching all tags\n"
+                 "  live prints the monitored information in real time\n"
+                 "        Hit Ctrl+C to stop.\n");
+  return BAD_VALUE;
+}
+
+status_t CameraService::startWatchingTags(const String16 &tags, int outFd) {
+    // track tags to initialize future clients with the monitoring information
+    mMonitorTags = String8(tags);
+
+    auto cameraClients = mActiveClientManager.getAll();
+    for (const auto &clientDescriptor: cameraClients) {
+        if (clientDescriptor == nullptr) { continue; }
+        sp<BasicClient> client = clientDescriptor->getValue();
+        if (client.get() == nullptr) { continue; }
+        client->startWatchingTags(mMonitorTags, outFd);
+    }
+    return OK;
+}
+
+status_t CameraService::stopWatchingTags(int outFd) {
+    // clear mMonitorTags to prevent new clients from monitoring tags at initialization
+    mMonitorTags = String8::empty();
+
+    auto cameraClients = mActiveClientManager.getAll();
+    for (const auto &clientDescriptor : cameraClients) {
+        if (clientDescriptor == nullptr) { continue; }
+        sp<BasicClient> client = clientDescriptor->getValue();
+        if (client.get() == nullptr) { continue; }
+        client->stopWatchingTags(outFd);
+    }
+    return OK;
+}
+
+status_t CameraService::printWatchedTagsUntilInterrupt(const Vector<String16> &args, int outFd) {
+    useconds_t refreshTimeoutMs = 1000; // refresh every 1s by default
+
+    if (args.size() > 2) {
+        size_t intervalIdx; // index of refresh interval argument
+        for (intervalIdx = 2;
+             intervalIdx < args.size()
+                && String16("-n") != args[intervalIdx]
+                && String16("--interval") != args[intervalIdx];
+             intervalIdx++);
+
+        size_t intervalValIdx = intervalIdx + 1;
+        if (intervalValIdx < args.size()) {
+            refreshTimeoutMs = strtol(String8(args[intervalValIdx].string()), nullptr, 10);
+            if (errno) { return BAD_VALUE; }
+        }
+    }
+
+    std::unordered_map<std::string, std::string> cameraToLastEvent;
+    auto cameraClients = mActiveClientManager.getAll();
+
+    if (cameraClients.empty()) {
+        dprintf(outFd, "No clients connected.\n");
+        return OK;
+    }
+
+    dprintf(outFd, "Press Ctrl + C to exit...\n\n");
+    while (true) {
+        for (const auto& clientDescriptor : cameraClients) {
+            if (clientDescriptor == nullptr) { continue; }
+            const char* cameraId = clientDescriptor->getKey().string();
+
+            // This also initializes the map entries with an empty string
+            const std::string& lastPrintedEvent = cameraToLastEvent[cameraId];
+
+            sp<BasicClient> client = clientDescriptor->getValue();
+            if (client.get() == nullptr) { continue; }
+
+            std::vector<std::string> latestEvents;
+            client->dumpWatchedEventsToVector(latestEvents);
+
+            if (!latestEvents.empty()) {
+                printNewWatchedEvents(outFd,
+                                      cameraId,
+                                      client->getPackageName(),
+                                      latestEvents,
+                                      lastPrintedEvent);
+                cameraToLastEvent[cameraId] = latestEvents[0];
+            }
+        }
+        usleep(refreshTimeoutMs * 1000);  // convert ms to us
+    }
+    return OK;
+}
+
+void CameraService::printNewWatchedEvents(int outFd,
+                                          const char *cameraId,
+                                          const String16 &packageName,
+                                          const std::vector<std::string> &events,
+                                          const std::string &lastPrintedEvent) {
+    if (events.empty()) { return; }
+
+    // index of lastPrintedEvent in events.
+    // lastPrintedIdx = events.size() if lastPrintedEvent is not in events
+    size_t lastPrintedIdx;
+    for (lastPrintedIdx = 0;
+         lastPrintedIdx < events.size() && lastPrintedEvent != events[lastPrintedIdx];
+         lastPrintedIdx++);
+
+    if (lastPrintedIdx == 0) { return; } // early exit if no new event in `events`
+
+    const char *printPackageName = String8(packageName).string();
+    // print events in chronological order (latest event last)
+    size_t idxToPrint = lastPrintedIdx;
+    do {
+        idxToPrint--;
+        dprintf(outFd, "%s:%s  %s", cameraId, printPackageName, events[idxToPrint].c_str());
+    } while (idxToPrint != 0);
+}
+
 status_t CameraService::printHelp(int out) {
     return dprintf(out, "Camera service commands:\n"
         "  get-uid-state <PACKAGE> [--user USER_ID] gets the uid state\n"
@@ -4692,6 +4837,7 @@
         "      Valid values 0=OFF, 1=ON for JPEG\n"
         "  get-image-dump-mask returns the current image-dump-mask value\n"
         "  set-camera-mute <0/1> enable or disable camera muting\n"
+        "  watch <start|stop|dump|live> manages tag monitoring in connected clients\n"
         "  help print this message\n");
 }