Handle power cycle change in car watchdog daemon

- When the system goes into suspend/shutdown power cycle, car watchdog
daemon stops checking clients' health status.
- Accordingly, when the system resumes, car watchdog daemon resumes the
health check.
- Pinged client check is refactored.
- ICarWatchdog::notifyStateChange's signature is modified not to use
vector or List.
- Add verbose mode to carwatchog_testclient

Bug: 148884065
Test: run carwatchdog_testcliet with verbose, run "dumpsys car_service
resume", and make sure that car watchdog daemon pauses pinging.

Change-Id: I1d357dd5c101fac8728303ac60fb5cc001c9fd70
diff --git a/service/src/com/android/car/watchdog/CarWatchdogService.java b/service/src/com/android/car/watchdog/CarWatchdogService.java
index 21ed269..b511e04 100644
--- a/service/src/com/android/car/watchdog/CarWatchdogService.java
+++ b/service/src/com/android/car/watchdog/CarWatchdogService.java
@@ -26,6 +26,10 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.automotive.watchdog.ICarWatchdogClient;
+import android.automotive.watchdog.PowerCycle;
+import android.automotive.watchdog.StateType;
+import android.car.hardware.power.CarPowerManager.CarPowerStateListener;
+import android.car.hardware.power.ICarPowerStateListener;
 import android.car.watchdog.ICarWatchdogService;
 import android.car.watchdoglib.CarWatchdogDaemonHelper;
 import android.content.Context;
@@ -39,6 +43,8 @@
 
 import androidx.annotation.VisibleForTesting;
 
+import com.android.car.CarLocalServices;
+import com.android.car.CarPowerManagementService;
 import com.android.car.CarServiceBase;
 import com.android.internal.annotations.GuardedBy;
 
@@ -116,6 +122,7 @@
         }
         mCarWatchdogDaemonHelper.addOnConnectionChangeListener(mConnectionListener);
         mCarWatchdogDaemonHelper.connect();
+        subscribePowerCycleChange();
         if (DEBUG) {
             Log.d(TAG, "CarWatchdogService is initialized");
         }
@@ -357,6 +364,54 @@
         }
     }
 
+    private void subscribePowerCycleChange() {
+        CarPowerManagementService powerService =
+                CarLocalServices.getService(CarPowerManagementService.class);
+        if (powerService == null) {
+            Log.w(TAG, "Cannot get CarPowerManagementService");
+            return;
+        }
+        powerService.registerListener(new ICarPowerStateListener.Stub() {
+            @Override
+            public void onStateChanged(int state) {
+                int powerCycle;
+                switch (state) {
+                    // SHUTDOWN_PREPARE covers suspend and shutdown.
+                    case CarPowerStateListener.SHUTDOWN_PREPARE:
+                        powerCycle = PowerCycle.POWER_CYCLE_SUSPEND;
+                        break;
+                    // ON covers resume.
+                    case CarPowerStateListener.ON:
+                        powerCycle = PowerCycle.POWER_CYCLE_RESUME;
+                        // There might be outdated & incorrect info. We should reset them before
+                        // starting to do health check.
+                        prepareHealthCheck();
+                        break;
+                    default:
+                        return;
+                }
+                try {
+                    mCarWatchdogDaemonHelper.notifySystemStateChange(StateType.POWER_CYCLE,
+                            powerCycle, /* arg2= */ -1);
+                } catch (IllegalArgumentException | RemoteException e) {
+                    Log.w(TAG, "Notifying system state change failed: " + e);
+                }
+                if (DEBUG) {
+                    Log.d(TAG, "Notified car watchdog daemon a power cycle(" + powerCycle + ")");
+                }
+            }
+        });
+    }
+
+    private void prepareHealthCheck() {
+        synchronized (mLock) {
+            for (int timeout : ALL_TIMEOUTS) {
+                SparseArray<ClientInfo> pingedClients = mPingedClientMap.get(timeout);
+                pingedClients.clear();
+            }
+        }
+    }
+
     @NonNull
     private int[] toIntArray(@NonNull ArrayList<Integer> list) {
         int size = list.size();
diff --git a/watchdog/aidl/android/automotive/watchdog/ICarWatchdog.aidl b/watchdog/aidl/android/automotive/watchdog/ICarWatchdog.aidl
index ae11b4a..8368a81 100644
--- a/watchdog/aidl/android/automotive/watchdog/ICarWatchdog.aidl
+++ b/watchdog/aidl/android/automotive/watchdog/ICarWatchdog.aidl
@@ -119,11 +119,12 @@
    * The caller should have system UID.
    *
    * @param type                 One of the change types defined in the StateType enum.
-   * @param args                 State change information for the specified type.
+   * @param arg1                 First state change information for the specified type.
+   * @param arg2                 Second state change information for the specified type.
    *
-   * When type is POWER_CYCLE, args should contain the current power cycle of the device.
-   * When type is USER_STATE, args should contain the user ID and current user state.
-   * When type is BOOT_PHASE, args should contain the current boot phase.
+   * When type is POWER_CYCLE, arg1 should contain the current power cycle of the device.
+   * When type is USER_STATE, arg1 and arg2 should contain the user ID and the current user state.
+   * When type is BOOT_PHASE, arg1 should contain the current boot phase.
    */
-  void notifySystemStateChange(in StateType type, in @utf8InCpp List<String> args);
+  void notifySystemStateChange(in StateType type, in int arg1, in int arg2);
 }
diff --git a/watchdog/car-watchdog-lib/src/android/car/watchdoglib/CarWatchdogDaemonHelper.java b/watchdog/car-watchdog-lib/src/android/car/watchdoglib/CarWatchdogDaemonHelper.java
index 73cb766..45bd8ff 100644
--- a/watchdog/car-watchdog-lib/src/android/car/watchdoglib/CarWatchdogDaemonHelper.java
+++ b/watchdog/car-watchdog-lib/src/android/car/watchdoglib/CarWatchdogDaemonHelper.java
@@ -33,7 +33,6 @@
 
 import com.android.internal.annotations.GuardedBy;
 
-import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.CopyOnWriteArrayList;
 
@@ -260,14 +259,16 @@
     /**
      * Tells car watchdog daemon that system state has been changed for the specified StateType.
      *
-     * @param StateType Either PowerCycle, UserState, or BootPhase
-     * @param args Args explaining the state change for the specified state type.
+     * @param type Either PowerCycle, UserState, or BootPhase
+     * @param arg1 First state change information for the specified state type.
+     * @param arg2 Second state change information for the specified state type.
      * @throws IllegalArgumentException If the args don't match the state type. Refer to the aidl
-     * interface for more information on the args.
+     *                                  interface for more information on the args.
      * @throws RemoteException
      */
-    public void notifySystemStateChange(int type, List<String> args) throws RemoteException {
-      invokeDaemonMethod((daemon) -> daemon.notifySystemStateChange(type, args));
+    public void notifySystemStateChange(int type, int arg1, int arg2)
+            throws IllegalArgumentException, RemoteException {
+        invokeDaemonMethod((daemon) -> daemon.notifySystemStateChange(type, arg1, arg2));
     }
 
     private void invokeDaemonMethod(Invokable r) throws IllegalArgumentException, RemoteException {
diff --git a/watchdog/server/src/WatchdogBinderMediator.cpp b/watchdog/server/src/WatchdogBinderMediator.cpp
index aedd27c..68c944a 100644
--- a/watchdog/server/src/WatchdogBinderMediator.cpp
+++ b/watchdog/server/src/WatchdogBinderMediator.cpp
@@ -140,28 +140,14 @@
     return mWatchdogProcessService->unregisterMonitor(monitor);
 }
 
-Status WatchdogBinderMediator::notifySystemStateChange(StateType type,
-                                                       const std::vector<std::string>& args) {
+Status WatchdogBinderMediator::notifySystemStateChange(StateType type, int32_t arg1, int32_t arg2) {
     Status status = checkSystemPermission();
     if (!status.isOk()) {
         return status;
     }
     switch (type) {
         case StateType::POWER_CYCLE: {
-            if (args.size() != 1) {
-                return fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT,
-                                         StringPrintf("Expected exactly one argument for %s "
-                                                      "change, got %zu",
-                                                      toString(StateType::POWER_CYCLE).c_str(),
-                                                      args.size()));
-            }
-            uint32_t powerCycleArg = 0;
-            if (!ParseUint(args[0], &powerCycleArg)) {
-                return fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT,
-                                         StringPrintf("Failed to parse power cycle argument %s",
-                                                      args[0].c_str()));
-            }
-            auto powerCycle = static_cast<PowerCycle>(powerCycleArg);
+            PowerCycle powerCycle = static_cast<PowerCycle>(static_cast<uint32_t>(arg1));
             if (powerCycle >= PowerCycle::NUM_POWER_CYLES) {
                 return fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT,
                                          StringPrintf("Invalid power cycle %d", powerCycle));
@@ -169,26 +155,8 @@
             return mWatchdogProcessService->notifyPowerCycleChange(powerCycle);
         }
         case StateType::USER_STATE: {
-            if (args.size() != 2) {
-                return fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT,
-                                         StringPrintf("Expected exactly two arguments for %s "
-                                                      "change, got %zu",
-                                                      toString(StateType::USER_STATE).c_str(),
-                                                      args.size()));
-            }
-            userid_t userId = 0;
-            if (!ParseUint(args[0], &userId)) {
-                return fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT,
-                                         StringPrintf("Failed to parse user ID argument %s",
-                                                      args[0].c_str()));
-            }
-            uint32_t userStateArg = 0;
-            if (!ParseUint(args[1], &userStateArg)) {
-                return fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT,
-                                         StringPrintf("Failed to parse user state argument %s",
-                                                      args[0].c_str()));
-            }
-            auto userState = static_cast<UserState>(userStateArg);
+            userid_t userId = static_cast<userid_t>(arg1);
+            UserState userState = static_cast<UserState>(static_cast<uint32_t>(arg2));
             if (userState >= UserState::NUM_USER_STATES) {
                 return fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT,
                                          StringPrintf("Invalid user state %d", userState));
@@ -196,20 +164,8 @@
             return mWatchdogProcessService->notifyUserStateChange(userId, userState);
         }
         case StateType::BOOT_PHASE: {
-            if (args.size() != 1) {
-                return fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT,
-                                         StringPrintf("Expacted exactly one argument for %s "
-                                                      "change, got %zu",
-                                                      toString(StateType::BOOT_PHASE).c_str(),
-                                                      args.size()));
-            }
-            uint32_t phase = 0;
-            if (!ParseUint(args[0], &phase)) {
-                return fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT,
-                                         StringPrintf("Failed to parse boot phase argument %s",
-                                                      args[0].c_str()));
-            }
-            if (static_cast<BootPhase>(phase) >= BootPhase::BOOT_COMPLETED) {
+            BootPhase phase = static_cast<BootPhase>(static_cast<uint32_t>(arg1));
+            if (phase >= BootPhase::BOOT_COMPLETED) {
                 /*auto ret = mIoPerfCollection->onBootFinished();
                 if (!ret.ok()) {
                     return fromExceptionCode(ret.error().code(), ret.error().message());
diff --git a/watchdog/server/src/WatchdogBinderMediator.h b/watchdog/server/src/WatchdogBinderMediator.h
index 1041c8e..a3b19a2 100644
--- a/watchdog/server/src/WatchdogBinderMediator.h
+++ b/watchdog/server/src/WatchdogBinderMediator.h
@@ -69,8 +69,7 @@
                                     int32_t pid) override {
         return mWatchdogProcessService->tellDumpFinished(monitor, pid);
     }
-    binder::Status notifySystemStateChange(StateType type,
-                                           const std::vector<std::string>& args) override;
+    binder::Status notifySystemStateChange(StateType type, int32_t arg1, int32_t arg2) override;
 
 protected:
     android::base::Result<void> init(android::sp<WatchdogProcessService> watchdogProcessService,
diff --git a/watchdog/server/src/WatchdogProcessService.cpp b/watchdog/server/src/WatchdogProcessService.cpp
index bbb08c5..51d1f07 100644
--- a/watchdog/server/src/WatchdogProcessService.cpp
+++ b/watchdog/server/src/WatchdogProcessService.cpp
@@ -21,6 +21,7 @@
 
 #include <android-base/chrono_utils.h>
 #include <android-base/file.h>
+#include <android-base/properties.h>
 #include <android-base/stringprintf.h>
 #include <binder/IPCThreadState.h>
 
@@ -30,6 +31,7 @@
 
 using std::literals::chrono_literals::operator""s;
 using android::base::Error;
+using android::base::GetProperty;
 using android::base::Result;
 using android::base::StringAppendF;
 using android::base::StringPrintf;
@@ -67,14 +69,22 @@
     return buffer;
 }
 
+bool isSystemShuttingDown() {
+    std::string sysPowerCtl;
+    std::istringstream tokenStream(GetProperty("sys.powerctl", ""));
+    std::getline(tokenStream, sysPowerCtl, ',');
+    return sysPowerCtl == "reboot" || sysPowerCtl == "shutdown";
+}
+
 }  // namespace
 
 WatchdogProcessService::WatchdogProcessService(const sp<Looper>& handlerLooper) :
       mHandlerLooper(handlerLooper), mLastSessionId(0) {
     mMessageHandler = new MessageHandlerImpl(this);
+    mWatchdogEnabled = true;
     for (const auto& timeout : kTimeouts) {
         mClients.insert(std::make_pair(timeout, std::vector<ClientInfo>()));
-        mPingedClients.insert(std::make_pair(timeout, PingedClientSet()));
+        mPingedClients.insert(std::make_pair(timeout, PingedClientMap()));
     }
 }
 
@@ -187,8 +197,35 @@
 }
 
 Status WatchdogProcessService::notifyPowerCycleChange(PowerCycle cycle) {
-    // TODO(b/148884065): implement this method.
-    (void)cycle;
+    std::string buffer;
+    Mutex::Autolock lock(mMutex);
+    bool oldStatus = mWatchdogEnabled;
+    switch (cycle) {
+        case PowerCycle::POWER_CYCLE_SHUTDOWN:
+            mWatchdogEnabled = false;
+            buffer = "SHUTDOWN power cycle";
+            break;
+        case PowerCycle::POWER_CYCLE_SUSPEND:
+            mWatchdogEnabled = false;
+            buffer = "SUSPEND power cycle";
+            break;
+        case PowerCycle::POWER_CYCLE_RESUME:
+            mWatchdogEnabled = true;
+            for (const auto& timeout : kTimeouts) {
+                startHealthCheckingLocked(timeout);
+            }
+            buffer = "RESUME power cycle";
+            break;
+        default:
+            ALOGW("Unsupported power cycle: %d", cycle);
+            return Status::fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT,
+                                             "The monitor is not registered or an invalid monitor "
+                                             "is given");
+    }
+    ALOGI("Received %s", buffer.c_str());
+    if (oldStatus != mWatchdogEnabled) {
+        ALOGI("Car watchdog is %s", mWatchdogEnabled ? "enabled" : "disabled");
+    }
     return Status::ok();
 }
 
@@ -201,55 +238,65 @@
 
 Result<void> WatchdogProcessService::dump(int fd, const Vector<String16>& /*args*/) {
     Mutex::Autolock lock(mMutex);
+    const char* indent = "  ";
+    const char* doubleIndent = "    ";
     std::string buffer;
     WriteStringToFd("CAR WATCHDOG PROCESS SERVICE\n", fd);
-    WriteStringToFd("  Registered clients\n", fd);
+    WriteStringToFd(StringPrintf("%sWatchdog enabled: %s\n", indent,
+                                 mWatchdogEnabled ? "true" : "false"),
+                    fd);
+    WriteStringToFd(StringPrintf("%sRegistered clients\n", indent), fd);
     int count = 1;
     for (const auto& timeout : kTimeouts) {
         std::vector<ClientInfo>& clients = mClients[timeout];
         for (auto it = clients.begin(); it != clients.end(); it++, count++) {
-            WriteStringToFd(StringPrintf("    Client #%d: %s\n", count, it->toString().c_str()),
+            WriteStringToFd(StringPrintf("%sClient #%d: %s\n", doubleIndent, count,
+                                         it->toString().c_str()),
                             fd);
         }
     }
-    WriteStringToFd(StringPrintf("\n  Monitor registered: %s\n",
+    WriteStringToFd(StringPrintf("\n%sMonitor registered: %s\n", indent,
                                  mMonitor == nullptr ? "false" : "true"),
                     fd);
+    WriteStringToFd(StringPrintf("%sisSystemShuttingDown: %s\n", indent,
+                                 isSystemShuttingDown() ? "true" : "false"),
+                    fd);
     return {};
 }
 
 void WatchdogProcessService::doHealthCheck(int what) {
     mHandlerLooper->removeMessages(mMessageHandler, what);
+    if (!isWatchdogEnabled()) {
+        return;
+    }
     const TimeoutLength timeout = static_cast<TimeoutLength>(what);
-    std::vector<ClientInfo> clientsToCheck;
-    PingedClientSet& pingedClients = mPingedClients[timeout];
-
     dumpAndKillClientsIfNotResponding(timeout);
 
     /* Generates a temporary/local vector containing clients.
      * Using a local copy may send unnecessary ping messages to clients after they are unregistered.
      * Clients should be able to handle them.
      */
+    std::vector<ClientInfo> clientsToCheck;
+    PingedClientMap& pingedClients = mPingedClients[timeout];
     {
         Mutex::Autolock lock(mMutex);
-        clientsToCheck = mClients[timeout];
         pingedClients.clear();
+        clientsToCheck = mClients[timeout];
+        for (auto& clientInfo : clientsToCheck) {
+            int sessionId = getNewSessionId();
+            clientInfo.sessionId = sessionId;
+            pingedClients.insert(std::make_pair(sessionId, clientInfo));
+        }
     }
 
     for (const auto& clientInfo : clientsToCheck) {
-        int32_t sessionId = getNewSessionId();
-        PingedClient targetClient(clientInfo.client, sessionId);
-        {
-            Mutex::Autolock lock(mMutex);
-            pingedClients.insert(targetClient);
-        }
-        Status status = clientInfo.client->checkIfAlive(sessionId, timeout);
+        Status status = clientInfo.client->checkIfAlive(clientInfo.sessionId, timeout);
         if (!status.isOk()) {
             ALOGW("Sending a ping message to client(pid: %d) failed: %s", clientInfo.pid,
                   status.exceptionMessage().c_str());
             {
                 Mutex::Autolock lock(mMutex);
-                pingedClients.erase(targetClient);
+                pingedClients.erase(clientInfo.sessionId);
             }
         }
     }
@@ -318,7 +365,7 @@
 
     // If the client array becomes non-empty, start health checking.
     if (clients.size() == 1) {
-        startHealthChecking(timeout);
+        startHealthCheckingLocked(timeout);
     }
     if (DEBUG) {
         ALOGD("Car watchdog %s(pid: %d, timeout: %d) is registered", clientName, callingPid,
@@ -350,11 +397,11 @@
 
 Status WatchdogProcessService::tellClientAliveLocked(const sp<ICarWatchdogClient>& client,
                                                      int32_t sessionId) {
+    const sp<IBinder> binder = BnCarWatchdog::asBinder(client);
     for (const auto& timeout : kTimeouts) {
-        PingedClientSet& clients = mPingedClients[timeout];
-        PingedClient respondingClient(client, sessionId);
-        PingedClientSet::const_iterator it = clients.find(respondingClient);
-        if (it == clients.cend()) {
+        PingedClientMap& clients = mPingedClients[timeout];
+        PingedClientMap::const_iterator it = clients.find(sessionId);
+        if (it == clients.cend() || binder != BnCarWatchdog::asBinder(it->second.client)) {
             continue;
         }
         clients.erase(it);
@@ -382,7 +429,9 @@
     return false;
 }
 
-Result<void> WatchdogProcessService::startHealthChecking(TimeoutLength timeout) {
+Result<void> WatchdogProcessService::startHealthCheckingLocked(TimeoutLength timeout) {
+    PingedClientMap& clients = mPingedClients[timeout];
+    clients.clear();
     int what = static_cast<int>(timeout);
     auto durationNs = timeoutToDurationNs(timeout);
     mHandlerLooper->sendMessageDelayed(durationNs.count(), mMessageHandler, Message(what));
@@ -393,10 +442,10 @@
     std::vector<int32_t> processIds;
     {
         Mutex::Autolock lock(mMutex);
-        PingedClientSet& clients = mPingedClients[timeout];
-        for (PingedClientSet::const_iterator it = clients.cbegin(); it != clients.cend(); it++) {
+        PingedClientMap& clients = mPingedClients[timeout];
+        for (PingedClientMap::const_iterator it = clients.cbegin(); it != clients.cend(); it++) {
             pid_t pid = -1;
-            sp<IBinder> binder = BnCarWatchdog::asBinder((*it).client);
+            sp<IBinder> binder = BnCarWatchdog::asBinder(it->second.client);
             std::vector<TimeoutLength> timeouts = {timeout};
             // Unhealthy clients are eventually removed from the list through binderDied when they
             // are killed.
@@ -419,11 +468,11 @@
     if (size == 0) {
         return {};
     }
+    std::string pidString = pidArrayToString(processesNotResponding);
     sp<ICarWatchdogMonitor> monitor;
     {
         Mutex::Autolock lock(mMutex);
         if (mMonitor == nullptr) {
-            std::string pidString = pidArrayToString(processesNotResponding);
             std::string errorMsg =
                     StringPrintf("Cannot dump and kill processes(pid = %s): Monitor is not set",
                                  pidString.c_str());
@@ -432,9 +481,13 @@
         }
         monitor = mMonitor;
     }
+    if (isSystemShuttingDown()) {
+        ALOGI("Skip dumping and killing processes(%s): The system is shutting down",
+              pidString.c_str());
+        return {};
+    }
     monitor->onClientsNotResponding(processesNotResponding);
     if (DEBUG) {
-        std::string pidString = pidArrayToString(processesNotResponding);
         ALOGD("Dumping and killing processes is requested: %s", pidString.c_str());
     }
     return {};
@@ -448,6 +501,11 @@
     return mLastSessionId;
 }
 
+bool WatchdogProcessService::isWatchdogEnabled() {
+    Mutex::Autolock lock(mMutex);
+    return mWatchdogEnabled;
+}
+
 std::string WatchdogProcessService::ClientInfo::toString() {
     std::string buffer;
     StringAppendF(&buffer, "pid = %d, type = %s", pid, type == Regular ? "Regular" : "Mediator");
diff --git a/watchdog/server/src/WatchdogProcessService.h b/watchdog/server/src/WatchdogProcessService.h
index c76936e..d5e389a 100644
--- a/watchdog/server/src/WatchdogProcessService.h
+++ b/watchdog/server/src/WatchdogProcessService.h
@@ -77,26 +77,11 @@
 
         android::sp<ICarWatchdogClient> client;
         pid_t pid;
+        int sessionId;
         ClientType type;
     };
 
-    struct PingedClient {
-        PingedClient(const android::sp<ICarWatchdogClient>& client, int32_t sessionId) :
-              client(client), sessionId(sessionId) {}
-
-        bool operator==(const PingedClient& other) const { return sessionId == other.sessionId; }
-
-        android::sp<ICarWatchdogClient> client;
-        int32_t sessionId;
-    };
-
-    struct PingedClientHash {
-        std::size_t operator()(const PingedClient& pingedClient) const {
-            return pingedClient.sessionId;
-        }
-    };
-
-    typedef std::unordered_set<PingedClient, PingedClientHash> PingedClientSet;
+    typedef std::unordered_map<int, ClientInfo> PingedClientMap;
 
     class MessageHandlerImpl : public MessageHandler {
     public:
@@ -116,10 +101,11 @@
     bool isRegisteredLocked(const android::sp<ICarWatchdogClient>& client);
     binder::Status tellClientAliveLocked(const android::sp<ICarWatchdogClient>& client,
                                          int32_t sessionId);
-    base::Result<void> startHealthChecking(TimeoutLength timeout);
+    base::Result<void> startHealthCheckingLocked(TimeoutLength timeout);
     base::Result<void> dumpAndKillClientsIfNotResponding(TimeoutLength timeout);
     base::Result<void> dumpAndKillAllProcesses(const std::vector<int32_t>& processesNotResponding);
     int32_t getNewSessionId();
+    bool isWatchdogEnabled();
 
     using Processor =
             std::function<void(std::vector<ClientInfo>&, std::vector<ClientInfo>::const_iterator)>;
@@ -127,12 +113,13 @@
                                     const android::sp<IBinder> binder, const Processor& processor);
 
 private:
-    Mutex mMutex;
     sp<Looper> mHandlerLooper;
     android::sp<MessageHandlerImpl> mMessageHandler;
+    Mutex mMutex;
     std::unordered_map<TimeoutLength, std::vector<ClientInfo>> mClients GUARDED_BY(mMutex);
-    std::unordered_map<TimeoutLength, PingedClientSet> mPingedClients GUARDED_BY(mMutex);
+    std::unordered_map<TimeoutLength, PingedClientMap> mPingedClients GUARDED_BY(mMutex);
     android::sp<ICarWatchdogMonitor> mMonitor GUARDED_BY(mMutex);
+    bool mWatchdogEnabled GUARDED_BY(mMutex);
     // mLastSessionId is accessed only within main thread. No need for mutual-exclusion.
     int32_t mLastSessionId;
 };
diff --git a/watchdog/server/tests/WatchdogBinderMediatorTest.cpp b/watchdog/server/tests/WatchdogBinderMediatorTest.cpp
index b2155c9..cb533a2 100644
--- a/watchdog/server/tests/WatchdogBinderMediatorTest.cpp
+++ b/watchdog/server/tests/WatchdogBinderMediatorTest.cpp
@@ -286,102 +286,61 @@
 
 TEST_F(WatchdogBinderMediatorTest, TestErrorOnNotifyStateChangeWithNonSystemCallingUid) {
     StateType type = StateType::POWER_CYCLE;
-    std::vector<std::string> args = {
-            std::to_string(static_cast<int32_t>(PowerCycle::POWER_CYCLE_SUSPEND))};
     EXPECT_CALL(*mMockWatchdogProcessService, notifyPowerCycleChange(_)).Times(0);
-    Status status = mWatchdogBinderMediator->notifySystemStateChange(type, args);
+    Status status =
+            mWatchdogBinderMediator
+                    ->notifySystemStateChange(type,
+                                              static_cast<int32_t>(PowerCycle::POWER_CYCLE_SUSPEND),
+                                              -1);
     ASSERT_FALSE(status.isOk()) << status;
 }
 
 TEST_F(WatchdogBinderMediatorTest, TestNotifyPowerCycleChange) {
     setSystemCallingUid();
     StateType type = StateType::POWER_CYCLE;
-    std::vector<std::string> args = {
-            std::to_string(static_cast<int32_t>(PowerCycle::POWER_CYCLE_SUSPEND))};
     EXPECT_CALL(*mMockWatchdogProcessService,
                 notifyPowerCycleChange(PowerCycle::POWER_CYCLE_SUSPEND))
             .WillOnce(Return(Status::ok()));
-    Status status = mWatchdogBinderMediator->notifySystemStateChange(type, args);
+    Status status =
+            mWatchdogBinderMediator
+                    ->notifySystemStateChange(type,
+                                              static_cast<int32_t>(PowerCycle::POWER_CYCLE_SUSPEND),
+                                              -1);
     ASSERT_TRUE(status.isOk()) << status;
 }
 
 TEST_F(WatchdogBinderMediatorTest, TestErrorOnNotifyPowerCycleChangeWithInvalidArgs) {
     EXPECT_CALL(*mMockWatchdogProcessService, notifyPowerCycleChange(_)).Times(0);
     StateType type = StateType::POWER_CYCLE;
-    std::vector<std::string> args = {std::to_string(
-                                             static_cast<int32_t>(PowerCycle::POWER_CYCLE_SUSPEND)),
-                                     std::to_string(
-                                             static_cast<int32_t>(PowerCycle::POWER_CYCLE_RESUME))};
-    Status status = mWatchdogBinderMediator->notifySystemStateChange(type, args);
+
+    Status status = mWatchdogBinderMediator->notifySystemStateChange(type, -1, -1);
     ASSERT_FALSE(status.isOk()) << status;
 
-    args.clear();
-    args.push_back("-1");
-    status = mWatchdogBinderMediator->notifySystemStateChange(type, args);
-    ASSERT_FALSE(status.isOk()) << status;
-
-    args.clear();
-    args.push_back("3000");
-    status = mWatchdogBinderMediator->notifySystemStateChange(type, args);
-    ASSERT_FALSE(status.isOk()) << status;
-
-    args.clear();
-    args.push_back("invalid_power_cycle");
-    status = mWatchdogBinderMediator->notifySystemStateChange(type, args);
+    status = mWatchdogBinderMediator->notifySystemStateChange(type, 3000, -1);
     ASSERT_FALSE(status.isOk()) << status;
 }
 
 TEST_F(WatchdogBinderMediatorTest, TestNotifyUserStateChange) {
     setSystemCallingUid();
     StateType type = StateType::USER_STATE;
-    std::vector<std::string> args = {"234567",
-                                     std::to_string(
-                                             static_cast<int32_t>(UserState::USER_STATE_STOPPED))};
     EXPECT_CALL(*mMockWatchdogProcessService,
                 notifyUserStateChange(234567, UserState::USER_STATE_STOPPED))
             .WillOnce(Return(Status::ok()));
-    Status status = mWatchdogBinderMediator->notifySystemStateChange(type, args);
+    Status status =
+            mWatchdogBinderMediator
+                    ->notifySystemStateChange(type, 234567,
+                                              static_cast<int32_t>(UserState::USER_STATE_STOPPED));
     ASSERT_TRUE(status.isOk()) << status;
 }
 
 TEST_F(WatchdogBinderMediatorTest, TestErrorOnNotifyUserStateChangeWithInvalidArgs) {
     EXPECT_CALL(*mMockWatchdogProcessService, notifyUserStateChange(_, _)).Times(0);
-    StateType type = StateType::POWER_CYCLE;
-    std::vector<std::string> args = {"234567",
-                                     std::to_string(
-                                             static_cast<int32_t>(UserState::USER_STATE_STOPPED)),
-                                     "extra_arg"};
-    Status status = mWatchdogBinderMediator->notifySystemStateChange(type, args);
+    StateType type = StateType::USER_STATE;
+
+    Status status = mWatchdogBinderMediator->notifySystemStateChange(type, 234567, -1);
     ASSERT_FALSE(status.isOk()) << status;
 
-    args.clear();
-    args.push_back("-1");
-    args.push_back(std::to_string(static_cast<int32_t>(UserState::USER_STATE_STOPPED)));
-    status = mWatchdogBinderMediator->notifySystemStateChange(type, args);
-    ASSERT_FALSE(status.isOk()) << status;
-
-    args.clear();
-    args.push_back("invalid_user_id");
-    args.push_back(std::to_string(static_cast<int32_t>(UserState::USER_STATE_STOPPED)));
-    status = mWatchdogBinderMediator->notifySystemStateChange(type, args);
-    ASSERT_FALSE(status.isOk()) << status;
-
-    args.clear();
-    args.push_back("234567");
-    args.push_back("-1");
-    status = mWatchdogBinderMediator->notifySystemStateChange(type, args);
-    ASSERT_FALSE(status.isOk()) << status;
-
-    args.clear();
-    args.push_back("234567");
-    args.push_back("3000");
-    status = mWatchdogBinderMediator->notifySystemStateChange(type, args);
-    ASSERT_FALSE(status.isOk()) << status;
-
-    args.clear();
-    args.push_back("234567");
-    args.push_back("invalid_user_state");
-    status = mWatchdogBinderMediator->notifySystemStateChange(type, args);
+    status = mWatchdogBinderMediator->notifySystemStateChange(type, 234567, 3000);
     ASSERT_FALSE(status.isOk()) << status;
 }
 
diff --git a/watchdog/testclient/src/WatchdogClient.cpp b/watchdog/testclient/src/WatchdogClient.cpp
index abc10ef..41a2b26 100644
--- a/watchdog/testclient/src/WatchdogClient.cpp
+++ b/watchdog/testclient/src/WatchdogClient.cpp
@@ -49,6 +49,9 @@
 }
 
 ndk::ScopedAStatus WatchdogClient::checkIfAlive(int32_t sessionId, TimeoutLength timeout) {
+    if (mVerbose) {
+        ALOGI("Pinged by car watchdog daemon: session id = %d", sessionId);
+    }
     Mutex::Autolock lock(mMutex);
     mHandlerLooper->removeMessages(mMessageHandler, WHAT_CHECK_ALIVE);
     mSession = HealthCheckSession(sessionId, timeout);
@@ -74,6 +77,7 @@
         mIsClientActive = true;
     }
     mForcedKill = param.forcedKill;
+    mVerbose = param.verbose;
     registerClient(param.timeout);
 
     if (param.inactiveAfterInSec >= 0) {
@@ -115,11 +119,17 @@
         ALOGE("Failed to call binder interface: %d", status.getStatus());
         return;
     }
+    if (mVerbose) {
+        ALOGI("Sent response to car watchdog daemon: session id = %d", sessionId);
+    }
 }
 
 void WatchdogClient::becomeInactive() {
     Mutex::Autolock lock(mMutex);
     mIsClientActive = false;
+    if (mVerbose) {
+        ALOGI("Became inactive");
+    }
 }
 
 void WatchdogClient::terminateProcess() {
diff --git a/watchdog/testclient/src/WatchdogClient.h b/watchdog/testclient/src/WatchdogClient.h
index 2073598..95f11ca 100644
--- a/watchdog/testclient/src/WatchdogClient.h
+++ b/watchdog/testclient/src/WatchdogClient.h
@@ -33,6 +33,7 @@
     int inactiveAfterInSec;
     int terminateAfterInSec;
     bool forcedKill;
+    bool verbose;
 };
 
 struct HealthCheckSession {
@@ -75,6 +76,7 @@
     ::android::sp<::android::Looper> mHandlerLooper;
     ::android::sp<MessageHandlerImpl> mMessageHandler;
     bool mForcedKill;
+    bool mVerbose;
     ::android::Mutex mMutex;
     std::shared_ptr<ICarWatchdog> mWatchdogServer GUARDED_BY(mMutex);
     std::shared_ptr<ICarWatchdogClient> mTestClient GUARDED_BY(mMutex);
diff --git a/watchdog/testclient/src/main.cpp b/watchdog/testclient/src/main.cpp
index 5b24173..440069d 100644
--- a/watchdog/testclient/src/main.cpp
+++ b/watchdog/testclient/src/main.cpp
@@ -34,7 +34,7 @@
 
 Result<CommandParam> checkArgument(int argc, char** argv) {
     CommandParam param;
-    if (argc != 4 && argc != 5) {
+    if (argc < 4) {
         return Error() << "Invalid syntax";
     }
     if (strcmp(argv[1], "critical") && strcmp(argv[1], "moderate") && strcmp(argv[1], "normal")) {
@@ -49,22 +49,27 @@
     if (!ParseInt(strValue, &param.terminateAfterInSec)) {
         return Error() << "Invalid terminate after time";
     }
-    if (argc == 5) {
-        if (strcmp(argv[4], "--forcedkill")) {
+    param.forcedKill = false;
+    param.verbose = false;
+    for (int i = 4; i < argc; i++) {
+        if (!strcmp(argv[i], "--forcedkill")) {
+            param.forcedKill = true;
+        } else if (!strcmp(argv[i], "--verbose")) {
+            param.verbose = true;
+        } else {
             return Error() << "Invalid option";
         }
-        param.forcedKill = true;
-    } else {
-        param.forcedKill = false;
     }
     return param;
 }
 /**
  * Usage: carwatchdog_testclient [timeout] [inactive_after] [terminate_after] [--forcedkill]
+ *                               [--verbose]
  * timeout: critical|moderate|normal
  * inactive_after: number in seconds. -1 for never being inactive.
  * terminate_after: number in seconds. -1 for running forever.
  * --forcedkill: terminate without unregistering from car watchdog daemon.
+ * --verbose: output verbose logs.
  */
 int main(int argc, char** argv) {
     sp<Looper> looper(Looper::prepare(/*opts=*/0));
@@ -81,7 +86,8 @@
         ALOGE("timeout: critical|moderate|normal");
         ALOGE("inactive_after: number in seconds (-1 for never being inactive)");
         ALOGE("terminate_after: number in seconds (-1 for running forever)");
-        ALOGE("--forcedkill: terminate without unregistering from car watchdog daemone");
+        ALOGE("--forcedkill: terminate without unregistering from car watchdog daemon");
+        ALOGE("--verbose: output verbose logs");
         return 1;
     }
     if (!service->initialize(*param)) {