Cache monitored tag dump for watched clients when disconnected.

This CL updates the 'watch' command to allow monitoring specific
connected clients, and caches the latest monitored tags dump from
tracked clients when they disconnect.

This allows the 'watch' command to print tag information for short lived
camera sessions.

Specifically, this CL does the following:
- Adds fields to CameraService to track which clients should be watched
and cached
- Adds arguments to 'watch start' to allow passing tag list and client
list
- Renames 'watch live' to 'watch print' and prints cached dump for
disconnected watched clients.

Test: Manually tested
Bug: 199746421
Change-Id: I0931837fffcffc9005f57ad52d6f3cb390eba1d7
diff --git a/services/camera/libcameraservice/CameraService.cpp b/services/camera/libcameraservice/CameraService.cpp
index 88c0bb5..5a892bc 100644
--- a/services/camera/libcameraservice/CameraService.cpp
+++ b/services/camera/libcameraservice/CameraService.cpp
@@ -137,6 +137,7 @@
 static constexpr int32_t kVendorClientState = ActivityManager::PROCESS_STATE_PERSISTENT_UI;
 
 const String8 CameraService::kOfflineDevice("offline-");
+const String16 CameraService::kWatchAllClientsFlag("all");
 
 // Set to keep track of logged service error events.
 static std::set<String8> sServiceErrorEventSet;
@@ -1788,7 +1789,8 @@
         LOG_ALWAYS_FATAL_IF(client.get() == nullptr, "%s: CameraService in invalid state",
                 __FUNCTION__);
 
-        err = client->initialize(mCameraProviderManager, mMonitorTags);
+        String8 monitorTags = isClientWatched(client.get()) ? mMonitorTags : String8("");
+        err = client->initialize(mCameraProviderManager, monitorTags);
         if (err != OK) {
             ALOGE("%s: Could not initialize client from HAL.", __FUNCTION__);
             // Errors could be from the HAL module open call or from AppOpsManager
@@ -1954,7 +1956,8 @@
             return BAD_VALUE;
         }
 
-        auto err = offlineClient->initialize(mCameraProviderManager, mMonitorTags);
+        String8 monitorTags = isClientWatched(offlineClient.get()) ? mMonitorTags : String8("");
+        auto err = offlineClient->initialize(mCameraProviderManager, monitorTags);
         if (err != OK) {
             ALOGE("%s: Could not initialize offline client.", __FUNCTION__);
             return err;
@@ -2620,6 +2623,7 @@
     for (auto& i : mActiveClientManager.getAll()) {
         auto clientSp = i->getValue();
         if (clientSp.get() == client) {
+            cacheClientTagDumpIfNeeded(client->mCameraIdStr, clientSp.get());
             mActiveClientManager.remove(i);
         }
     }
@@ -2696,7 +2700,11 @@
         return sp<BasicClient>{nullptr};
     }
 
-    return clientDescriptorPtr->getValue();
+    sp<BasicClient> client = clientDescriptorPtr->getValue();
+    if (client.get() != nullptr) {
+        cacheClientTagDumpIfNeeded(clientDescriptorPtr->getKey(), client.get());
+    }
+    return client;
 }
 
 void CameraService::doUserSwitch(const std::vector<int32_t>& newUserIds) {
@@ -4245,6 +4253,30 @@
     dprintf(fd, "\n");
 }
 
+void CameraService::cacheClientTagDumpIfNeeded(const char *cameraId, BasicClient* client) {
+    Mutex::Autolock lock(mLogLock);
+    if (!isClientWatchedLocked(client)) { return; }
+
+    std::vector<std::string> dumpVector;
+    client->dumpWatchedEventsToVector(dumpVector);
+
+    if (dumpVector.empty()) { return; }
+
+    std::string dumpString;
+    size_t i = dumpVector.size();
+
+    // Store the string in reverse order (latest last)
+    while (i > 0) {
+         i--;
+         dumpString += cameraId;
+         dumpString += ":";
+         dumpString += dumpVector[i]; // implicitly ends with '\n'
+    }
+
+    const String16 &packageName = client->getPackageName();
+    mWatchedClientsDumpCache[packageName] = dumpString;
+}
+
 void CameraService::handleTorchClientBinderDied(const wp<IBinder> &who) {
     Mutex::Autolock al(mTorchClientMapMutex);
     for (size_t i = 0; i < mTorchClientMap.size(); i++) {
@@ -4697,44 +4729,81 @@
     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);
+status_t CameraService::handleWatchCommand(const Vector<String16>& args, int outFd) {
+    if (args.size() >= 3 && args[1] == String16("start")) {
+        return startWatchingTags(args, outFd);
     } else if (args.size() == 2 && args[1] == String16("dump")) {
-        return camera3::CameraTraces::dump(out);
+        return camera3::CameraTraces::dump(outFd);
     } 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);
+        return stopWatchingTags(outFd);
+    } else if (args.size() >= 2 && args[1] == String16("print")) {
+        return printWatchedTags(args, outFd);
     }
-    dprintf(out, "Camera service watch commands:\n"
-                 "  start <comma_separated_tag_list> starts watching the provided tags\n"
-                 "                                   recognizes shorthands like '3a'\n"
+    dprintf(outFd, "Camera service watch commands:\n"
+                 "  start -m <comma_separated_tag_list> [-c <comma_separated_client_list>]\n"
+                 "        starts watching the provided tags for clients with provided package\n"
+                 "        recognizes tag shorthands like '3a'\n"
+                 "        watches all clients if no client is passed, or if 'all' is listed\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");
+                 "  print [-n <refresh_interval_ms>]\n"
+                 "        prints the monitored information in real time\n"
+                 "        Hit Ctrl+C to exit\n");
   return BAD_VALUE;
 }
 
-status_t CameraService::startWatchingTags(const String16 &tags, int outFd) {
+status_t CameraService::startWatchingTags(const Vector<String16> &args, int outFd) {
+    Mutex::Autolock lock(mLogLock);
+    size_t tagsIdx; // index of '-m'
+    String16 tags("");
+    for (tagsIdx = 2; tagsIdx < args.size() && args[tagsIdx] != String16("-m"); tagsIdx++);
+    if (tagsIdx < args.size() - 1) {
+        tags = args[tagsIdx + 1];
+    } else {
+        dprintf(outFd, "No tags provided.\n");
+        return BAD_VALUE;
+    }
+
+    size_t clientsIdx; // index of '-c'
+    String16 clients = kWatchAllClientsFlag; // watch all clients if no clients are provided
+    for (clientsIdx = 2; clientsIdx < args.size() && args[clientsIdx] != String16("-c");
+         clientsIdx++);
+    if (clientsIdx < args.size() - 1) {
+        clients = args[clientsIdx + 1];
+    }
+    parseClientsToWatchLocked(String8(clients));
+
     // track tags to initialize future clients with the monitoring information
     mMonitorTags = String8(tags);
 
+    bool serviceLock = tryLock(mServiceLock);
+    int numWatchedClients = 0;
     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);
+
+        if (isClientWatchedLocked(client.get())) {
+            client->startWatchingTags(mMonitorTags, outFd);
+            numWatchedClients++;
+        }
     }
+    dprintf(outFd, "Started watching %d active clients\n", numWatchedClients);
+
+    if (serviceLock) { mServiceLock.unlock(); }
     return OK;
 }
 
 status_t CameraService::stopWatchingTags(int outFd) {
     // clear mMonitorTags to prevent new clients from monitoring tags at initialization
+    Mutex::Autolock lock(mLogLock);
     mMonitorTags = String8::empty();
 
+    mWatchedClientPackages.clear();
+    mWatchedClientsDumpCache.clear();
+
+    bool serviceLock = tryLock(mServiceLock);
     auto cameraClients = mActiveClientManager.getAll();
     for (const auto &clientDescriptor : cameraClients) {
         if (clientDescriptor == nullptr) { continue; }
@@ -4742,18 +4811,17 @@
         if (client.get() == nullptr) { continue; }
         client->stopWatchingTags(outFd);
     }
+    dprintf(outFd, "Stopped watching all clients.\n");
+    if (serviceLock) { mServiceLock.unlock(); }
     return OK;
 }
 
-status_t CameraService::printWatchedTagsUntilInterrupt(const Vector<String16> &args, int outFd) {
+status_t CameraService::printWatchedTags(const Vector<String16> &args, int outFd) {
+    // Figure outFd refresh interval, if present in args
     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];
+        size_t intervalIdx; // index of '-n'
+        for (intervalIdx = 2; intervalIdx < args.size() && String16("-n") != args[intervalIdx];
              intervalIdx++);
 
         size_t intervalValIdx = intervalIdx + 1;
@@ -4763,6 +4831,43 @@
         }
     }
 
+    mLogLock.lock();
+    bool serviceLock = tryLock(mServiceLock);
+    // get all watched clients that are currently connected
+    std::set<String16> connectedMoniterdClients;
+    for (const auto &clientDescriptor: mActiveClientManager.getAll()) {
+        if (clientDescriptor == nullptr) { continue; }
+
+        sp<BasicClient> client = clientDescriptor->getValue();
+        if (client.get() == nullptr) { continue; }
+        if (!isClientWatchedLocked(client.get())) { continue; }
+
+        connectedMoniterdClients.emplace(client->getPackageName());
+    }
+    if (serviceLock) { mServiceLock.unlock(); }
+
+    // Print entries in mWatchedClientsDumpCache for clients that are not connected
+    for (const auto &kv: mWatchedClientsDumpCache) {
+        const String16 &package = kv.first;
+        if (connectedMoniterdClients.find(package) != connectedMoniterdClients.end()) {
+            continue;
+        }
+
+        dprintf(outFd, "Client: %s\n", String8(package).string());
+        dprintf(outFd, "%s\n", kv.second.c_str());
+    }
+    mLogLock.unlock();
+
+    if (connectedMoniterdClients.empty()) {
+        dprintf(outFd, "No watched client active.\n");
+        return OK;
+    }
+
+    // For connected watched clients, print monitored tags live
+    return printWatchedTagsUntilInterrupt(refreshTimeoutMs  * 1000, outFd);
+}
+
+status_t CameraService::printWatchedTagsUntilInterrupt(useconds_t refreshMicros, int outFd) {
     std::unordered_map<std::string, std::string> cameraToLastEvent;
     auto cameraClients = mActiveClientManager.getAll();
 
@@ -4774,6 +4879,7 @@
     dprintf(outFd, "Press Ctrl + C to exit...\n\n");
     while (true) {
         for (const auto& clientDescriptor : cameraClients) {
+            Mutex::Autolock lock(mLogLock);
             if (clientDescriptor == nullptr) { continue; }
             const char* cameraId = clientDescriptor->getKey().string();
 
@@ -4782,6 +4888,7 @@
 
             sp<BasicClient> client = clientDescriptor->getValue();
             if (client.get() == nullptr) { continue; }
+            if (!isClientWatchedLocked(client.get())) { continue; }
 
             std::vector<std::string> latestEvents;
             client->dumpWatchedEventsToVector(latestEvents);
@@ -4795,7 +4902,7 @@
                 cameraToLastEvent[cameraId] = latestEvents[0];
             }
         }
-        usleep(refreshTimeoutMs * 1000);  // convert ms to us
+        usleep(refreshMicros);  // convert ms to us
     }
     return OK;
 }
@@ -4825,6 +4932,30 @@
     } while (idxToPrint != 0);
 }
 
+void CameraService::parseClientsToWatchLocked(String8 clients) {
+    mWatchedClientPackages.clear();
+
+    const char *allSentinel = String8(kWatchAllClientsFlag).string();
+
+    char *tokenized = clients.lockBuffer(clients.size());
+    char *savePtr;
+    char *nextClient = strtok_r(tokenized, ",", &savePtr);
+
+    while (nextClient != nullptr) {
+        if (strcmp(nextClient, allSentinel) == 0) {
+            // Don't need to track any other package if 'all' is present
+            mWatchedClientPackages.clear();
+            mWatchedClientPackages.emplace(kWatchAllClientsFlag);
+            break;
+        }
+
+        // track package names
+        mWatchedClientPackages.emplace(nextClient);
+        nextClient = strtok_r(nullptr, ",", &savePtr);
+    }
+    clients.unlockBuffer();
+}
+
 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"
@@ -4841,6 +4972,16 @@
         "  help print this message\n");
 }
 
+bool CameraService::isClientWatched(const BasicClient *client) {
+    Mutex::Autolock lock(mLogLock);
+    return isClientWatchedLocked(client);
+}
+
+bool CameraService::isClientWatchedLocked(const BasicClient *client) {
+    return mWatchedClientPackages.find(kWatchAllClientsFlag) != mWatchedClientPackages.end() ||
+           mWatchedClientPackages.find(client->getPackageName()) != mWatchedClientPackages.end();
+}
+
 int32_t CameraService::updateAudioRestriction() {
     Mutex::Autolock lock(mServiceLock);
     return updateAudioRestrictionLocked();
diff --git a/services/camera/libcameraservice/CameraService.h b/services/camera/libcameraservice/CameraService.h
index f701d30..c5ab7cb 100644
--- a/services/camera/libcameraservice/CameraService.h
+++ b/services/camera/libcameraservice/CameraService.h
@@ -829,6 +829,14 @@
     RingBuffer<String8> mEventLog;
     Mutex mLogLock;
 
+    // set of client package names to watch. if this set contains 'all', then all clients will
+    // be watched. Access should be guarded by mLogLock
+    std::set<String16> mWatchedClientPackages;
+    // cache of last monitored tags dump immediately before the client disconnects. If a client
+    // re-connects, its entry is not updated until it disconnects again. Access should be guarded
+    // by mLogLock
+    std::map<String16, std::string> mWatchedClientsDumpCache;
+
     // The last monitored tags set by client
     String8 mMonitorTags;
 
@@ -961,6 +969,8 @@
      */
     void dumpEventLog(int fd);
 
+    void cacheClientTagDumpIfNeeded(const char *cameraId, BasicClient *client);
+
     /**
      * This method will acquire mServiceLock
      */
@@ -1154,17 +1164,21 @@
     status_t handleSetCameraMute(const Vector<String16>& args);
 
     // Handle 'watch' command as passed through 'cmd'
-    status_t handleWatchCommand(const Vector<String16> &args, int out);
+    status_t handleWatchCommand(const Vector<String16> &args, int outFd);
 
-    // Enable tag monitoring of the given tags in all attached clients
-    status_t startWatchingTags(const String16 &tags, int outFd);
+    // Enable tag monitoring of the given tags in provided clients
+    status_t startWatchingTags(const Vector<String16> &args, int outFd);
 
-    // Disable tag monitoring in all attached clients
+    // Disable tag monitoring
     status_t stopWatchingTags(int outFd);
 
-    // Print events of monitored tags in all attached devices as they are captured
+    // Print events of monitored tags in all cached and attached clients
+    status_t printWatchedTags(const Vector<String16> &args, int outFd);
+
+    // Print events of monitored tags in all attached clients as they are captured. New events are
+    // fetched every `refreshMicros` us
     // NOTE: This function does not terminate unless interrupted.
-    status_t printWatchedTagsUntilInterrupt(const Vector<String16> &args, int outFd);
+    status_t printWatchedTagsUntilInterrupt(useconds_t refreshMicros, int outFd);
 
     // Print all events in vector `events' that came after lastPrintedEvent
     static void printNewWatchedEvents(int outFd,
@@ -1173,9 +1187,23 @@
                                       const std::vector<std::string> &events,
                                       const std::string &lastPrintedEvent);
 
+    // Parses comma separated clients list and adds them to mWatchedClientPackages.
+    // Does not acquire mLogLock before modifying mWatchedClientPackages. It is the caller's
+    // responsibility to acquire mLogLock before calling this function.
+    void parseClientsToWatchLocked(String8 clients);
+
     // Prints the shell command help
     status_t printHelp(int out);
 
+    // Returns true if client should monitor tags based on the contents of mWatchedClientPackages.
+    // Acquires mLogLock before querying mWatchedClientPackages.
+    bool isClientWatched(const BasicClient *client);
+
+    // Returns true if client should monitor tags based on the contents of mWatchedClientPackages.
+    // Does not acquire mLogLock before querying mWatchedClientPackages. It is the caller's
+    // responsibility to acquire mLogLock before calling this functions.
+    bool isClientWatchedLocked(const BasicClient *client);
+
     /**
      * Get the current system time as a formatted string.
      */
@@ -1206,6 +1234,10 @@
     // Use separate keys for offline devices.
     static const String8 kOfflineDevice;
 
+    // Sentinel value to be stored in `mWatchedClientsPackages` to indicate that all clients should
+    // be watched.
+    static const String16 kWatchAllClientsFlag;
+
     // TODO: right now each BasicClient holds one AppOpsManager instance.
     // We can refactor the code so all of clients share this instance
     AppOpsManager mAppOps;