Merge "CarService detects recurring overuse" into sc-v2-dev
diff --git a/cpp/watchdog/aidl/android/automotive/watchdog/internal/UserState.aidl b/cpp/watchdog/aidl/android/automotive/watchdog/internal/UserState.aidl
index 515fb41..95461eb 100644
--- a/cpp/watchdog/aidl/android/automotive/watchdog/internal/UserState.aidl
+++ b/cpp/watchdog/aidl/android/automotive/watchdog/internal/UserState.aidl
@@ -32,6 +32,11 @@
   USER_STATE_STOPPED,
 
   /**
+   * The user is removed.
+   */
+  USER_STATE_REMOVED,
+
+  /**
    * Number of available user states.
    */
   NUM_USER_STATES,
diff --git a/cpp/watchdog/server/src/IoOveruseMonitor.cpp b/cpp/watchdog/server/src/IoOveruseMonitor.cpp
index 0ebe0d4..edf30d8 100644
--- a/cpp/watchdog/server/src/IoOveruseMonitor.cpp
+++ b/cpp/watchdog/server/src/IoOveruseMonitor.cpp
@@ -23,11 +23,11 @@
 
 #include <WatchdogProperties.sysprop.h>
 #include <android-base/file.h>
+#include <android-base/strings.h>
 #include <android/automotive/watchdog/internal/PackageIdentifier.h>
 #include <android/automotive/watchdog/internal/UidType.h>
 #include <binder/IPCThreadState.h>
 #include <binder/Status.h>
-#include <cutils/multiuser.h>
 #include <log/log.h>
 #include <processgroup/sched_policy.h>
 
@@ -53,8 +53,10 @@
 using ::android::automotive::watchdog::internal::ResourceOveruseConfiguration;
 using ::android::automotive::watchdog::internal::UidType;
 using ::android::automotive::watchdog::internal::UserPackageIoUsageStats;
+using ::android::base::EndsWith;
 using ::android::base::Error;
 using ::android::base::Result;
+using ::android::base::StringPrintf;
 using ::android::base::WriteStringToFd;
 using ::android::binder::Status;
 
@@ -598,6 +600,7 @@
     std::unordered_set<std::string> uniquePackageNames;
     std::copy(packageNames.begin(), packageNames.end(),
               std::inserter(uniquePackageNames, uniquePackageNames.end()));
+    std::unique_lock writeLock(mRwMutex);
     for (auto& [key, usage] : mUserPackageDailyIoUsageById) {
         if (uniquePackageNames.find(usage.packageInfo.packageIdentifier.name) !=
             uniquePackageNames.end()) {
@@ -607,6 +610,37 @@
     return {};
 }
 
+void IoOveruseMonitor::removeStatsForUser(userid_t userId) {
+    std::unique_lock writeLock(mRwMutex);
+    for (auto it = mUserPackageDailyIoUsageById.begin();
+         it != mUserPackageDailyIoUsageById.end();) {
+        if (multiuser_get_user_id(it->second.packageInfo.packageIdentifier.uid) == userId) {
+            it = mUserPackageDailyIoUsageById.erase(it);
+        } else {
+            ++it;
+        }
+    }
+    // |mPrevBootIoUsageStatsById| keys are constructed using |uniquePackageIdStr| method. Thus, the
+    // key suffix would contain the userId. The value in this map is |IoUsageStats|, which doesn't
+    // contain the userId, so this is the only way to delete cached previous boot stats for
+    // the removed user.
+    std::string keySuffix = StringPrintf(":%" PRId32, userId);
+    for (auto it = mPrevBootIoUsageStatsById.begin(); it != mPrevBootIoUsageStatsById.end();) {
+        if (EndsWith(it->first, keySuffix)) {
+            it = mPrevBootIoUsageStatsById.erase(it);
+        } else {
+            ++it;
+        }
+    }
+    for (auto it = mLatestIoOveruseStats.begin(); it != mLatestIoOveruseStats.end();) {
+        if (multiuser_get_user_id(it->uid) == userId) {
+            it = mLatestIoOveruseStats.erase(it);
+        } else {
+            ++it;
+        }
+    }
+}
+
 void IoOveruseMonitor::handleBinderDeath(const wp<IBinder>& who) {
     std::unique_lock writeLock(mRwMutex);
     IBinder* binder = who.unsafe_get();
diff --git a/cpp/watchdog/server/src/IoOveruseMonitor.h b/cpp/watchdog/server/src/IoOveruseMonitor.h
index 5fa91aa..d6c2616 100644
--- a/cpp/watchdog/server/src/IoOveruseMonitor.h
+++ b/cpp/watchdog/server/src/IoOveruseMonitor.h
@@ -31,6 +31,7 @@
 #include <android/automotive/watchdog/internal/IoOveruseConfiguration.h>
 #include <android/automotive/watchdog/internal/PackageInfo.h>
 #include <android/automotive/watchdog/internal/PackageIoOveruseStats.h>
+#include <cutils/multiuser.h>
 #include <utils/Mutex.h>
 
 #include <time.h>
@@ -92,6 +93,9 @@
 
     virtual android::base::Result<void> resetIoOveruseStats(
             const std::vector<std::string>& packageNames) = 0;
+
+    // Removes stats for the given user from the internal cache.
+    virtual void removeStatsForUser(userid_t userId) = 0;
 };
 
 class IoOveruseMonitor final : public IIoOveruseMonitor {
@@ -161,6 +165,8 @@
     android::base::Result<void> resetIoOveruseStats(
             const std::vector<std::string>& packageName) override;
 
+    void removeStatsForUser(userid_t userId) override;
+
 protected:
     android::base::Result<void> init();
 
diff --git a/cpp/watchdog/server/src/WatchdogInternalHandler.cpp b/cpp/watchdog/server/src/WatchdogInternalHandler.cpp
index 2441ccf..d28b322 100644
--- a/cpp/watchdog/server/src/WatchdogInternalHandler.cpp
+++ b/cpp/watchdog/server/src/WatchdogInternalHandler.cpp
@@ -186,7 +186,7 @@
                 return fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT,
                                          StringPrintf("Invalid user state %d", userState));
             }
-            return mWatchdogProcessService->notifyUserStateChange(userId, userState);
+            return handleUserStateChange(userId, userState);
         }
         case aawi::StateType::BOOT_PHASE: {
             aawi::BootPhase phase = static_cast<aawi::BootPhase>(static_cast<uint32_t>(arg1));
@@ -225,6 +225,29 @@
     return Status::ok();
 }
 
+Status WatchdogInternalHandler::handleUserStateChange(userid_t userId, aawi::UserState userState) {
+    std::string stateDesc;
+    switch (userState) {
+        case aawi::UserState::USER_STATE_STARTED:
+            stateDesc = "started";
+            mWatchdogProcessService->notifyUserStateChange(userId, /*isStarted=*/true);
+            break;
+        case aawi::UserState::USER_STATE_STOPPED:
+            stateDesc = "stopped";
+            mWatchdogProcessService->notifyUserStateChange(userId, /*isStarted=*/false);
+            break;
+        case aawi::UserState::USER_STATE_REMOVED:
+            stateDesc = "removed";
+            mIoOveruseMonitor->removeStatsForUser(userId);
+            break;
+        default:
+            ALOGW("Unsupported user state: %d", userState);
+            return Status::fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT, "Unsupported user state");
+    }
+    ALOGI("Received user state change: user(%" PRId32 ") is %s", userId, stateDesc.c_str());
+    return Status::ok();
+}
+
 Status WatchdogInternalHandler::updateResourceOveruseConfigurations(
         const std::vector<ResourceOveruseConfiguration>& configs) {
     Status status = checkSystemUser();
diff --git a/cpp/watchdog/server/src/WatchdogInternalHandler.h b/cpp/watchdog/server/src/WatchdogInternalHandler.h
index 1000c8e..6634d5b 100644
--- a/cpp/watchdog/server/src/WatchdogInternalHandler.h
+++ b/cpp/watchdog/server/src/WatchdogInternalHandler.h
@@ -105,6 +105,9 @@
     android::binder::Status handlePowerCycleChange(
             android::automotive::watchdog::internal::PowerCycle powerCycle);
 
+    android::binder::Status handleUserStateChange(
+            userid_t userId, android::automotive::watchdog::internal::UserState userState);
+
     android::sp<WatchdogBinderMediator> mBinderMediator;
     android::sp<IWatchdogServiceHelper> mWatchdogServiceHelper;
     android::sp<WatchdogProcessService> mWatchdogProcessService;
diff --git a/cpp/watchdog/server/src/WatchdogProcessService.cpp b/cpp/watchdog/server/src/WatchdogProcessService.cpp
index 891c6e2..3eca5ec 100644
--- a/cpp/watchdog/server/src/WatchdogProcessService.cpp
+++ b/cpp/watchdog/server/src/WatchdogProcessService.cpp
@@ -289,24 +289,14 @@
     }
 }
 
-Status WatchdogProcessService::notifyUserStateChange(userid_t userId, aawi::UserState state) {
+void WatchdogProcessService::notifyUserStateChange(userid_t userId, bool isStarted) {
     std::string buffer;
     Mutex::Autolock lock(mMutex);
-    switch (state) {
-        case aawi::UserState::USER_STATE_STARTED:
-            mStoppedUserIds.erase(userId);
-            buffer = StringPrintf("user(%d) is started", userId);
-            break;
-        case aawi::UserState::USER_STATE_STOPPED:
-            mStoppedUserIds.insert(userId);
-            buffer = StringPrintf("user(%d) is stopped", userId);
-            break;
-        default:
-            ALOGW("Unsupported user state: %d", state);
-            return Status::fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT, "Unsupported user state");
+    if (isStarted) {
+        mStoppedUserIds.erase(userId);
+    } else {
+        mStoppedUserIds.insert(userId);
     }
-    ALOGI("Received user state change: %s", buffer.c_str());
-    return Status::ok();
 }
 
 Result<void> WatchdogProcessService::dump(int fd, const Vector<String16>& /*args*/) {
diff --git a/cpp/watchdog/server/src/WatchdogProcessService.h b/cpp/watchdog/server/src/WatchdogProcessService.h
index 46d3ef4..d66a420 100644
--- a/cpp/watchdog/server/src/WatchdogProcessService.h
+++ b/cpp/watchdog/server/src/WatchdogProcessService.h
@@ -80,8 +80,7 @@
                     monitor,
             int32_t pid);
     virtual void setEnabled(bool isEnabled);
-    virtual android::binder::Status notifyUserStateChange(
-            userid_t userId, android::automotive::watchdog::internal::UserState state);
+    virtual void notifyUserStateChange(userid_t userId, bool isStarted);
 
 private:
     enum ClientType {
diff --git a/cpp/watchdog/server/tests/IoOveruseMonitorTest.cpp b/cpp/watchdog/server/tests/IoOveruseMonitorTest.cpp
index c1dd4f7..752be74 100644
--- a/cpp/watchdog/server/tests/IoOveruseMonitorTest.cpp
+++ b/cpp/watchdog/server/tests/IoOveruseMonitorTest.cpp
@@ -1064,6 +1064,85 @@
     ASSERT_FALSE(mIoOveruseMonitor->updateResourceOveruseConfigurations({}).ok());
 }
 
+TEST_F(IoOveruseMonitorTest, TestRemoveUser) {
+    std::vector<UserPackageIoUsageStats> todayIoUsageStats =
+            {constructUserPackageIoUsageStats(
+                     /*userId=*/11, "com.android.google.package",
+                     /*writtenBytes=*/constructPerStateBytes(100'000, 85'000, 120'000),
+                     /*forgivenWriteBytes=*/constructPerStateBytes(70'000, 60'000, 100'000),
+                     /*totalOveruses=*/3),
+             constructUserPackageIoUsageStats(
+                     /*userId=*/12, "com.android.kitchensink",
+                     /*writtenBytes=*/constructPerStateBytes(50'000, 40'000, 35'000),
+                     /*forgivenWriteBytes=*/constructPerStateBytes(30'000, 30'000, 30'000),
+                     /*totalOveruses=*/6)};
+    EXPECT_CALL(*mMockWatchdogServiceHelper, getTodayIoUsageStats(_))
+            .WillOnce(DoAll(SetArgPointee<0>(todayIoUsageStats), Return(Status::ok())));
+
+    EXPECT_CALL(*mMockUidStatsCollector, deltaStats())
+            .WillOnce(Return(
+                    constructUidStats({{1001000, {/*fgWrBytes=*/70'000, /*bgWrBytes=*/20'000}},
+                                       {1112345, {/*fgWrBytes=*/35'000, /*bgWrBytes=*/15'000}}})));
+
+    std::vector<PackageIoOveruseStats> actualIoOveruseStats;
+    EXPECT_CALL(*mMockWatchdogServiceHelper, latestIoOveruseStats(_))
+            .WillOnce(DoAll(SaveArg<0>(&actualIoOveruseStats), Return(Status::ok())));
+
+    time_t currentTime = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
+    const auto [startTime, durationInSeconds] = calculateStartAndDuration(currentTime);
+
+    ASSERT_RESULT_OK(mIoOveruseMonitor->onPeriodicCollection(currentTime, SystemState::NORMAL_MODE,
+                                                             mMockUidStatsCollector, nullptr));
+
+    std::vector<PackageIoOveruseStats> expectedIoOveruseStats =
+            {constructPackageIoOveruseStats(
+                     /*uid*=*/1001000, /*shouldNotify=*/false, /*isKillable=*/false,
+                     /*remaining=*/constructPerStateBytes(10'000, 20'000, 100'000),
+                     /*written=*/constructPerStateBytes(70'000, 20'000, 0),
+                     /*forgiven=*/constructPerStateBytes(0, 0, 0),
+                     /*totalOveruses=*/0, startTime, durationInSeconds),
+             constructPackageIoOveruseStats(
+                     /*uid*=*/1112345, /*shouldNotify=*/true, /*isKillable=*/true,
+                     /*remaining=*/constructPerStateBytes(5'000, 0, 80'000),
+                     /*written=*/constructPerStateBytes(135'000, 100'000, 120'000),
+                     /*forgiven=*/constructPerStateBytes(70'000, 90'000, 100'000),
+                     /*totalOveruses=*/4, startTime, durationInSeconds)};
+    EXPECT_THAT(actualIoOveruseStats, UnorderedElementsAreArray(expectedIoOveruseStats))
+            << "Expected: " << toString(expectedIoOveruseStats)
+            << "\nActual: " << toString(actualIoOveruseStats);
+
+    mIoOveruseMonitor->removeStatsForUser(/*userId=*/11);
+    mIoOveruseMonitor->removeStatsForUser(/*userId=*/12);
+
+    EXPECT_CALL(*mMockUidStatsCollector, deltaStats())
+            .WillOnce(Return(
+                    constructUidStats({{1112345, {/*fgWrBytes=*/70'000, /*bgWrBytes=*/40'000}},
+                                       {1245678, {/*fgWrBytes=*/30'000, /*bgWrBytes=*/10'000}}})));
+
+    actualIoOveruseStats.clear();
+    EXPECT_CALL(*mMockWatchdogServiceHelper, latestIoOveruseStats(_))
+            .WillOnce(DoAll(SaveArg<0>(&actualIoOveruseStats), Return(Status::ok())));
+
+    ASSERT_RESULT_OK(mIoOveruseMonitor->onPeriodicCollection(currentTime, SystemState::GARAGE_MODE,
+                                                             mMockUidStatsCollector, nullptr));
+
+    expectedIoOveruseStats = {constructPackageIoOveruseStats(
+                                      /*uid*=*/1112345, /*shouldNotify=*/true, /*isKillable=*/true,
+                                      /*remaining=*/constructPerStateBytes(70'000, 30'000, 0),
+                                      /*written=*/constructPerStateBytes(0, 0, 110'000),
+                                      /*forgiven=*/constructPerStateBytes(0, 0, 100'000),
+                                      /*totalOveruses=*/1, startTime, durationInSeconds),
+                              constructPackageIoOveruseStats(
+                                      /*uid*=*/1245678, /*shouldNotify=*/true, /*isKillable=*/true,
+                                      /*remaining=*/constructPerStateBytes(30'000, 15'000, 0),
+                                      /*written=*/constructPerStateBytes(0, 0, 40'000),
+                                      /*forgiven=*/constructPerStateBytes(0, 0, 40'000),
+                                      /*totalOveruses=*/4, startTime, durationInSeconds)};
+    EXPECT_THAT(actualIoOveruseStats, UnorderedElementsAreArray(expectedIoOveruseStats))
+            << "Expected: " << toString(expectedIoOveruseStats)
+            << "\nActual: " << toString(actualIoOveruseStats);
+}
+
 }  // namespace watchdog
 }  // namespace automotive
 }  // namespace android
diff --git a/cpp/watchdog/server/tests/MockIoOveruseMonitor.h b/cpp/watchdog/server/tests/MockIoOveruseMonitor.h
index 294276b..0e3f4c4 100644
--- a/cpp/watchdog/server/tests/MockIoOveruseMonitor.h
+++ b/cpp/watchdog/server/tests/MockIoOveruseMonitor.h
@@ -53,6 +53,7 @@
                 (const, override));
     MOCK_METHOD(android::base::Result<void>, resetIoOveruseStats, (const std::vector<std::string>&),
                 (override));
+    MOCK_METHOD(void, removeStatsForUser, (userid_t), (override));
 };
 
 }  // namespace watchdog
diff --git a/cpp/watchdog/server/tests/MockWatchdogProcessService.h b/cpp/watchdog/server/tests/MockWatchdogProcessService.h
index 44f3377..304a2a4 100644
--- a/cpp/watchdog/server/tests/MockWatchdogProcessService.h
+++ b/cpp/watchdog/server/tests/MockWatchdogProcessService.h
@@ -40,42 +40,37 @@
 class MockWatchdogProcessService : public WatchdogProcessService {
 public:
     MockWatchdogProcessService() : WatchdogProcessService(nullptr) {}
-    MOCK_METHOD(android::base::Result<void>, dump, (int fd, const Vector<android::String16>& args),
+    MOCK_METHOD(android::base::Result<void>, dump, (int fd, const Vector<android::String16>&),
                 (override));
     MOCK_METHOD(android::base::Result<void>, registerWatchdogServiceHelper,
-                (const android::sp<IWatchdogServiceHelper>& helper), (override));
+                (const android::sp<IWatchdogServiceHelper>&), (override));
 
     MOCK_METHOD(android::binder::Status, registerClient,
-                (const sp<ICarWatchdogClient>& client, TimeoutLength timeout), (override));
-    MOCK_METHOD(android::binder::Status, unregisterClient, (const sp<ICarWatchdogClient>& client),
+                (const sp<ICarWatchdogClient>&, TimeoutLength), (override));
+    MOCK_METHOD(android::binder::Status, unregisterClient, (const sp<ICarWatchdogClient>&),
                 (override));
-    MOCK_METHOD(android::binder::Status, registerCarWatchdogService,
-                (const android::sp<IBinder>& binder), (override));
-    MOCK_METHOD(void, unregisterCarWatchdogService, (const android::sp<IBinder>& binder),
+    MOCK_METHOD(android::binder::Status, registerCarWatchdogService, (const android::sp<IBinder>&),
                 (override));
+    MOCK_METHOD(void, unregisterCarWatchdogService, (const android::sp<IBinder>&), (override));
     MOCK_METHOD(android::binder::Status, registerMonitor,
-                (const sp<android::automotive::watchdog::internal::ICarWatchdogMonitor>& monitor),
+                (const sp<android::automotive::watchdog::internal::ICarWatchdogMonitor>&),
                 (override));
     MOCK_METHOD(android::binder::Status, unregisterMonitor,
-                (const sp<android::automotive::watchdog::internal::ICarWatchdogMonitor>& monitor),
+                (const sp<android::automotive::watchdog::internal::ICarWatchdogMonitor>&),
                 (override));
-    MOCK_METHOD(android::binder::Status, tellClientAlive,
-                (const sp<ICarWatchdogClient>& client, int32_t sessionId), (override));
+    MOCK_METHOD(android::binder::Status, tellClientAlive, (const sp<ICarWatchdogClient>&, int32_t),
+                (override));
     MOCK_METHOD(android::binder::Status, tellCarWatchdogServiceAlive,
                 (const android::sp<
-                         android::automotive::watchdog::internal::ICarWatchdogServiceForSystem>&
-                         service,
-                 const std::vector<int32_t>& clientsNotResponding, int32_t sessionId),
+                         android::automotive::watchdog::internal::ICarWatchdogServiceForSystem>&,
+                 const std::vector<int32_t>&, int32_t),
                 (override));
     MOCK_METHOD(android::binder::Status, tellDumpFinished,
-                (const android::sp<android::automotive::watchdog::internal::ICarWatchdogMonitor>&
-                         monitor,
-                 int32_t pid),
+                (const android::sp<android::automotive::watchdog::internal::ICarWatchdogMonitor>&,
+                 int32_t),
                 (override));
     MOCK_METHOD(void, setEnabled, (bool), (override));
-    MOCK_METHOD(android::binder::Status, notifyUserStateChange,
-                (userid_t userId, android::automotive::watchdog::internal::UserState state),
-                (override));
+    MOCK_METHOD(void, notifyUserStateChange, (userid_t, bool), (override));
 };
 
 }  // namespace watchdog
diff --git a/cpp/watchdog/server/tests/WatchdogInternalHandlerTest.cpp b/cpp/watchdog/server/tests/WatchdogInternalHandlerTest.cpp
index a5abbc4..3d95bc6 100644
--- a/cpp/watchdog/server/tests/WatchdogInternalHandlerTest.cpp
+++ b/cpp/watchdog/server/tests/WatchdogInternalHandlerTest.cpp
@@ -362,12 +362,21 @@
     ASSERT_TRUE(status.isOk()) << status;
 }
 
-TEST_F(WatchdogInternalHandlerTest, TestNotifyUserStateChange) {
+TEST_F(WatchdogInternalHandlerTest, TestNotifyUserStateChangeWithStartedUser) {
     setSystemCallingUid();
     aawi::StateType type = aawi::StateType::USER_STATE;
-    EXPECT_CALL(*mMockWatchdogProcessService,
-                notifyUserStateChange(234567, aawi::UserState::USER_STATE_STOPPED))
-            .WillOnce(Return(Status::ok()));
+    EXPECT_CALL(*mMockWatchdogProcessService, notifyUserStateChange(234567, /*isStarted=*/true));
+    Status status = mWatchdogInternalHandler
+                            ->notifySystemStateChange(type, 234567,
+                                                      static_cast<int32_t>(
+                                                              aawi::UserState::USER_STATE_STARTED));
+    ASSERT_TRUE(status.isOk()) << status;
+}
+
+TEST_F(WatchdogInternalHandlerTest, TestNotifyUserStateChangeWithStoppedUser) {
+    setSystemCallingUid();
+    aawi::StateType type = aawi::StateType::USER_STATE;
+    EXPECT_CALL(*mMockWatchdogProcessService, notifyUserStateChange(234567, /*isStarted=*/false));
     Status status = mWatchdogInternalHandler
                             ->notifySystemStateChange(type, 234567,
                                                       static_cast<int32_t>(
@@ -375,6 +384,17 @@
     ASSERT_TRUE(status.isOk()) << status;
 }
 
+TEST_F(WatchdogInternalHandlerTest, TestNotifyUserStateChangeWithRemovedUser) {
+    setSystemCallingUid();
+    aawi::StateType type = aawi::StateType::USER_STATE;
+    EXPECT_CALL(*mMockIoOveruseMonitor, removeStatsForUser(/*userId=*/234567));
+    Status status = mWatchdogInternalHandler
+                            ->notifySystemStateChange(type, 234567,
+                                                      static_cast<int32_t>(
+                                                              aawi::UserState::USER_STATE_REMOVED));
+    ASSERT_TRUE(status.isOk()) << status;
+}
+
 TEST_F(WatchdogInternalHandlerTest, TestErrorOnNotifyUserStateChangeWithInvalidArgs) {
     EXPECT_CALL(*mMockWatchdogProcessService, notifyUserStateChange(_, _)).Times(0);
     aawi::StateType type = aawi::StateType::USER_STATE;
diff --git a/service/src/com/android/car/watchdog/CarWatchdogService.java b/service/src/com/android/car/watchdog/CarWatchdogService.java
index eab0e4b..ae9897d 100644
--- a/service/src/com/android/car/watchdog/CarWatchdogService.java
+++ b/service/src/com/android/car/watchdog/CarWatchdogService.java
@@ -91,6 +91,8 @@
             "com.android.car.watchdog.ACTION_DISMISS_RESOURCE_OVERUSE_NOTIFICATION";
     static final String ACTION_RESOURCE_OVERUSE_DISABLE_APP =
             "com.android.car.watchdog.ACTION_RESOURCE_OVERUSE_DISABLE_APP";
+
+    @VisibleForTesting
     static final int MISSING_ARG_VALUE = -1;
 
     private static final String FALLBACK_DATA_SYSTEM_CAR_DIR_PATH = "/data/system/car";
@@ -124,7 +126,7 @@
     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
-            final String action = intent.getAction();
+            String action = intent.getAction();
             switch (action) {
                 case ACTION_DISMISS_RESOURCE_OVERUSE_NOTIFICATION:
                 case ACTION_LAUNCH_APP_SETTINGS:
@@ -145,8 +147,20 @@
                     notifyGarageModeChange(garageMode);
                     return;
                 case ACTION_USER_REMOVED:
-                    UserHandle user = intent.getParcelableExtra(Intent.EXTRA_USER);
-                    mWatchdogPerfHandler.deleteUser(user.getIdentifier());
+                    UserHandle userHandle = intent.getParcelableExtra(Intent.EXTRA_USER);
+                    int userId = userHandle.getIdentifier();
+                    try {
+                        mCarWatchdogDaemonHelper.notifySystemStateChange(StateType.USER_STATE,
+                                userId, UserState.USER_STATE_REMOVED);
+                        if (DEBUG) {
+                            Slogf.d(TAG, "Notified car watchdog daemon of removed user %d",
+                                    userId);
+                        }
+                    } catch (RemoteException e) {
+                        Slogf.w(TAG, e, "Failed to notify car watchdog daemon of removed user %d",
+                                userId);
+                    }
+                    mWatchdogPerfHandler.deleteUser(userId);
                     return;
             }
         }
@@ -482,7 +496,9 @@
                 Slogf.d(TAG, "Notified car watchdog daemon of user states");
             }
         } catch (RemoteException | RuntimeException e) {
-            Slogf.w(TAG, "Notifying latest user states failed: %s", e);
+            // When car watchdog daemon is not connected, the {@link mCarWatchdogDaemonHelper}
+            // throws IllegalStateException. Catch the exception to avoid crashing the process.
+            Slogf.w(TAG, e, "Notifying latest user states failed");
         }
     }
 
@@ -498,6 +514,8 @@
                 Slogf.d(TAG, "Notified car watchdog daemon of power cycle(%d)", powerCycle);
             }
         } catch (RemoteException | RuntimeException e) {
+            // When car watchdog daemon is not connected, the {@link mCarWatchdogDaemonHelper}
+            // throws IllegalStateException. Catch the exception to avoid crashing the process.
             Slogf.w(TAG, e, "Notifying power cycle change to %d failed", powerCycle);
         }
     }
@@ -510,6 +528,8 @@
                 Slogf.d(TAG, "Notified car watchdog daemon of garage mode(%d)", garageMode);
             }
         } catch (RemoteException | RuntimeException e) {
+            // When car watchdog daemon is not connected, the {@link mCarWatchdogDaemonHelper}
+            // throws IllegalStateException. Catch the exception to avoid crashing the process.
             Slogf.w(TAG, e, "Notifying garage mode change to %d failed", garageMode);
         }
     }
@@ -535,7 +555,9 @@
                 Slogf.d(TAG, "CarWatchdogService registers to car watchdog daemon");
             }
         } catch (RemoteException | RuntimeException e) {
-            Slogf.w(TAG, "Cannot register to car watchdog daemon: %s", e);
+            // When car watchdog daemon is not connected, the {@link mCarWatchdogDaemonHelper}
+            // throws IllegalStateException. Catch the exception to avoid crashing the process.
+            Slogf.w(TAG, e, "Cannot register to car watchdog daemon");
         }
         notifyAllUserStates();
         CarPowerManagementService powerService =
@@ -567,7 +589,9 @@
                 Slogf.d(TAG, "CarWatchdogService unregisters from car watchdog daemon");
             }
         } catch (RemoteException | RuntimeException e) {
-            Slogf.w(TAG, "Cannot unregister from car watchdog daemon: %s", e);
+            // When car watchdog daemon is not connected, the {@link mCarWatchdogDaemonHelper}
+            // throws IllegalStateException. Catch the exception to avoid crashing the process.
+            Slogf.w(TAG, e, "Cannot unregister from car watchdog daemon");
         }
     }
 
@@ -627,7 +651,9 @@
                             userId, userStateDesc);
                 }
             } catch (RemoteException | RuntimeException e) {
-                Slogf.w(TAG, "Notifying user state change failed: %s", e);
+                // When car watchdog daemon is not connected, the {@link mCarWatchdogDaemonHelper}
+                // throws IllegalStateException. Catch the exception to avoid crashing the process.
+                Slogf.w(TAG, e, "Notifying user state change failed");
             }
         });
     }
diff --git a/service/src/com/android/car/watchdog/WatchdogPerfHandler.java b/service/src/com/android/car/watchdog/WatchdogPerfHandler.java
index 9d479c6..d39ec88 100644
--- a/service/src/com/android/car/watchdog/WatchdogPerfHandler.java
+++ b/service/src/com/android/car/watchdog/WatchdogPerfHandler.java
@@ -155,7 +155,8 @@
     public static final String INTERNAL_APPLICATION_CATEGORY_TYPE_MEDIA = "MEDIA";
     public static final String INTERNAL_APPLICATION_CATEGORY_TYPE_UNKNOWN = "UNKNOWN";
     public static final int UID_IO_USAGE_SUMMARY_TOP_COUNT = 10;
-    public static final int UID_IO_USAGE_SUMMARY_MIN_WEEKLY_WRITTEN_BYTES = 500 * 1024 * 1024;
+    public static final int UID_IO_USAGE_SUMMARY_MIN_SYSTEM_TOTAL_WEEKLY_WRITTEN_BYTES =
+            500 * 1024 * 1024;
 
     static final String INTENT_EXTRA_ID = "notification_id";
 
@@ -1847,7 +1848,7 @@
         // for some user packages, the fetched summaries will still contain enough entries to pull.
         List<WatchdogStorage.UserPackageDailySummaries> topUsersDailyIoUsageSummaries =
                 mWatchdogStorage.getTopUsersDailyIoUsageSummaries(numTopUsers * 2,
-                        UID_IO_USAGE_SUMMARY_MIN_WEEKLY_WRITTEN_BYTES,
+                        UID_IO_USAGE_SUMMARY_MIN_SYSTEM_TOTAL_WEEKLY_WRITTEN_BYTES,
                         period.first.toEpochSecond(), period.second.toEpochSecond());
         if (topUsersDailyIoUsageSummaries == null) {
             Slogf.i(TAG, "No top users' I/O usage summary stats available to pull");
diff --git a/service/src/com/android/car/watchdog/WatchdogStorage.java b/service/src/com/android/car/watchdog/WatchdogStorage.java
index f4ae576..57baf06 100644
--- a/service/src/com/android/car/watchdog/WatchdogStorage.java
+++ b/service/src/com/android/car/watchdog/WatchdogStorage.java
@@ -228,13 +228,17 @@
      * when summaries are not available.
      */
     public @Nullable List<UserPackageDailySummaries> getTopUsersDailyIoUsageSummaries(
-            int numTopUsers, long minTotalWrittenBytes, long includingStartEpochSeconds,
+            int numTopUsers, long minSystemTotalWrittenBytes, long includingStartEpochSeconds,
             long excludingEndEpochSeconds) {
         ArrayMap<String, List<AtomsProto.CarWatchdogDailyIoUsageSummary>> summariesById;
         try (SQLiteDatabase db = mDbHelper.getReadableDatabase()) {
+            long systemTotalWrittenBytes = IoUsageStatsTable.querySystemTotalWrittenBytes(db,
+                    includingStartEpochSeconds, excludingEndEpochSeconds);
+            if (systemTotalWrittenBytes < minSystemTotalWrittenBytes) {
+                return null;
+            }
             summariesById = IoUsageStatsTable.queryTopUsersDailyIoUsageSummaries(db,
-                    numTopUsers, minTotalWrittenBytes, includingStartEpochSeconds,
-                    excludingEndEpochSeconds);
+                    numTopUsers, includingStartEpochSeconds, excludingEndEpochSeconds);
         }
         if (summariesById == null) {
             return null;
@@ -933,8 +937,6 @@
                     .append(COLUMN_WRITTEN_GARAGE_MODE_BYTES).append(") > 0 ")
                     .append("ORDER BY stats_date_epoch ASC");
 
-            Slogf.e(TAG, "Query: %s", queryBuilder.toString());
-
             String[] selectionArgs = new String[]{String.valueOf(includingStartEpochSeconds),
                     String.valueOf(excludingEndEpochSeconds)};
             List<AtomsProto.CarWatchdogDailyIoUsageSummary> summaries = new ArrayList<>();
@@ -955,10 +957,30 @@
             return summaries;
         }
 
+        public static long querySystemTotalWrittenBytes(SQLiteDatabase db,
+                long includingStartEpochSeconds, long excludingEndEpochSeconds) {
+            StringBuilder queryBuilder = new StringBuilder();
+            queryBuilder.append("SELECT SUM(").append(COLUMN_WRITTEN_FOREGROUND_BYTES).append(" + ")
+                    .append(COLUMN_WRITTEN_BACKGROUND_BYTES).append(" + ")
+                    .append(COLUMN_WRITTEN_GARAGE_MODE_BYTES).append(") ")
+                    .append("FROM ").append(TABLE_NAME).append(" WHERE ")
+                    .append(COLUMN_DATE_EPOCH).append(" >= ? and ")
+                    .append(COLUMN_DATE_EPOCH).append(" < ? ");
+
+            String[] selectionArgs = new String[]{String.valueOf(includingStartEpochSeconds),
+                    String.valueOf(excludingEndEpochSeconds)};
+            long totalWrittenBytes = 0;
+            try (Cursor cursor = db.rawQuery(queryBuilder.toString(), selectionArgs)) {
+                while (cursor.moveToNext()) {
+                    totalWrittenBytes += cursor.getLong(0);
+                }
+            }
+            return totalWrittenBytes;
+        }
+
         public static @Nullable ArrayMap<String, List<AtomsProto.CarWatchdogDailyIoUsageSummary>>
                 queryTopUsersDailyIoUsageSummaries(SQLiteDatabase db, int numTopUsers,
-                long minTotalWrittenBytes, long includingStartEpochSeconds,
-                long excludingEndEpochSeconds) {
+                long includingStartEpochSeconds, long excludingEndEpochSeconds) {
             StringBuilder innerQueryBuilder = new StringBuilder();
             innerQueryBuilder.append("SELECT ").append(COLUMN_USER_PACKAGE_ID)
                     .append(" FROM (SELECT ").append(COLUMN_USER_PACKAGE_ID).append(", ")
@@ -969,8 +991,8 @@
                     .append(COLUMN_DATE_EPOCH).append(" >= ? and ")
                     .append(COLUMN_DATE_EPOCH).append(" < ?")
                     .append(" GROUP BY ").append(COLUMN_USER_PACKAGE_ID)
-                    .append(" HAVING total_written_bytes >= ").append(minTotalWrittenBytes)
-                    .append(" ORDER BY total_written_bytes LIMIT ").append(numTopUsers).append(')');
+                    .append(" ORDER BY total_written_bytes DESC LIMIT ").append(numTopUsers)
+                    .append(')');
 
             StringBuilder queryBuilder = new StringBuilder();
             queryBuilder.append("SELECT ").append(COLUMN_USER_PACKAGE_ID).append(", ")
diff --git a/tests/carservice_unit_test/src/com/android/car/watchdog/CarWatchdogServiceUnitTest.java b/tests/carservice_unit_test/src/com/android/car/watchdog/CarWatchdogServiceUnitTest.java
index c446c05..5a745e9 100644
--- a/tests/carservice_unit_test/src/com/android/car/watchdog/CarWatchdogServiceUnitTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/watchdog/CarWatchdogServiceUnitTest.java
@@ -39,9 +39,10 @@
 import static com.android.car.watchdog.CarWatchdogService.ACTION_DISMISS_RESOURCE_OVERUSE_NOTIFICATION;
 import static com.android.car.watchdog.CarWatchdogService.ACTION_LAUNCH_APP_SETTINGS;
 import static com.android.car.watchdog.CarWatchdogService.ACTION_RESOURCE_OVERUSE_DISABLE_APP;
+import static com.android.car.watchdog.CarWatchdogService.MISSING_ARG_VALUE;
 import static com.android.car.watchdog.TimeSource.ZONE_OFFSET;
 import static com.android.car.watchdog.WatchdogPerfHandler.INTENT_EXTRA_ID;
-import static com.android.car.watchdog.WatchdogPerfHandler.UID_IO_USAGE_SUMMARY_MIN_WEEKLY_WRITTEN_BYTES;
+import static com.android.car.watchdog.WatchdogPerfHandler.UID_IO_USAGE_SUMMARY_MIN_SYSTEM_TOTAL_WEEKLY_WRITTEN_BYTES;
 import static com.android.car.watchdog.WatchdogStorage.RETENTION_PERIOD;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -373,9 +374,8 @@
     public void testGarageModeStateChangeToOn() throws Exception {
         mBroadcastReceiver.onReceive(mMockContext,
                 new Intent().setAction(CarWatchdogService.ACTION_GARAGE_MODE_ON));
-        verify(mMockCarWatchdogDaemon)
-                .notifySystemStateChange(
-                        eq(StateType.GARAGE_MODE), eq(GarageMode.GARAGE_MODE_ON), eq(-1));
+        verify(mMockCarWatchdogDaemon).notifySystemStateChange(StateType.GARAGE_MODE,
+                GarageMode.GARAGE_MODE_ON, MISSING_ARG_VALUE);
         verify(mMockWatchdogStorage).shrinkDatabase();
     }
 
@@ -385,9 +385,8 @@
                 new Intent().setAction(CarWatchdogService.ACTION_GARAGE_MODE_OFF));
         // GARAGE_MODE_OFF is notified twice: Once during the initial daemon connect and once when
         // the ACTION_GARAGE_MODE_OFF intent is received.
-        verify(mMockCarWatchdogDaemon, times(2))
-                .notifySystemStateChange(
-                        eq(StateType.GARAGE_MODE), eq(GarageMode.GARAGE_MODE_OFF), eq(-1));
+        verify(mMockCarWatchdogDaemon, times(2)).notifySystemStateChange(StateType.GARAGE_MODE,
+                GarageMode.GARAGE_MODE_OFF, MISSING_ARG_VALUE);
         verify(mMockWatchdogStorage, never()).shrinkDatabase();
     }
 
@@ -404,14 +403,14 @@
 
         restartWatchdogDaemonAndAwait();
 
-        verify(mMockCarWatchdogDaemon, times(1)).notifySystemStateChange(
-                eq(StateType.USER_STATE), eq(101), eq(UserState.USER_STATE_STOPPED));
-        verify(mMockCarWatchdogDaemon, times(1)).notifySystemStateChange(
-                eq(StateType.USER_STATE), eq(102), eq(UserState.USER_STATE_STARTED));
-        verify(mMockCarWatchdogDaemon, times(1)).notifySystemStateChange(
-                eq(StateType.POWER_CYCLE), eq(PowerCycle.POWER_CYCLE_SHUTDOWN_ENTER), eq(-1));
-        verify(mMockCarWatchdogDaemon, times(1)).notifySystemStateChange(
-                eq(StateType.GARAGE_MODE), eq(GarageMode.GARAGE_MODE_ON), eq(-1));
+        verify(mMockCarWatchdogDaemon).notifySystemStateChange(StateType.USER_STATE, 101,
+                UserState.USER_STATE_STOPPED);
+        verify(mMockCarWatchdogDaemon).notifySystemStateChange(StateType.USER_STATE, 102,
+                UserState.USER_STATE_STARTED);
+        verify(mMockCarWatchdogDaemon).notifySystemStateChange(StateType.POWER_CYCLE,
+                PowerCycle.POWER_CYCLE_SHUTDOWN_ENTER, MISSING_ARG_VALUE);
+        verify(mMockCarWatchdogDaemon).notifySystemStateChange(StateType.GARAGE_MODE,
+                GarageMode.GARAGE_MODE_ON, MISSING_ARG_VALUE);
     }
 
     @Test
@@ -420,6 +419,8 @@
         mBroadcastReceiver.onReceive(mMockContext,
                 new Intent().setAction(Intent.ACTION_USER_REMOVED)
                         .putExtra(Intent.EXTRA_USER, UserHandle.of(100)));
+        verify(mMockCarWatchdogDaemon).notifySystemStateChange(StateType.USER_STATE, 100,
+                UserState.USER_STATE_REMOVED);
         verify(mMockWatchdogStorage).syncUsers(new int[] {101, 102});
     }
 
@@ -3689,7 +3690,7 @@
                 .that(mWatchdogServiceForSystemImpl).isNotNull();
 
         verify(mMockCarWatchdogDaemon, atLeastOnce()).notifySystemStateChange(
-                eq(StateType.GARAGE_MODE), eq(GarageMode.GARAGE_MODE_OFF), eq(-1));
+                StateType.GARAGE_MODE, GarageMode.GARAGE_MODE_OFF, MISSING_ARG_VALUE);
 
         // Once registration with daemon completes, the service post a new message on the main
         // thread to fetch and sync resource overuse configs.
@@ -4470,7 +4471,7 @@
             long startEpochSecond = beginWeekStartDate.toEpochSecond();
             verify(mMockWatchdogStorage).getTopUsersDailyIoUsageSummaries(
                     UID_IO_USAGE_SUMMARY_TOP_COUNT * 2,
-                    UID_IO_USAGE_SUMMARY_MIN_WEEKLY_WRITTEN_BYTES, startEpochSecond,
+                    UID_IO_USAGE_SUMMARY_MIN_SYSTEM_TOTAL_WEEKLY_WRITTEN_BYTES, startEpochSecond,
                     beginWeekStartDate.plusWeeks(1).toEpochSecond());
             for (Integer uid : expectUids) {
                 expectedSummaries.add(AtomsProto.CarWatchdogUidIoUsageSummary.newBuilder()
diff --git a/tests/carservice_unit_test/src/com/android/car/watchdog/WatchdogStorageUnitTest.java b/tests/carservice_unit_test/src/com/android/car/watchdog/WatchdogStorageUnitTest.java
index 614809f..84b8672 100644
--- a/tests/carservice_unit_test/src/com/android/car/watchdog/WatchdogStorageUnitTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/watchdog/WatchdogStorageUnitTest.java
@@ -346,7 +346,7 @@
 
         List<WatchdogStorage.UserPackageDailySummaries> actual =
                 mService.getTopUsersDailyIoUsageSummaries(/* numTopUsers= */ 3,
-                        /* minTotalWrittenBytes= */ 600_000,
+                        /* minSystemTotalWrittenBytes= */ 600_000,
                         /* includingStartEpochSeconds= */ currentDate.minusDays(15).toEpochSecond(),
                         /* excludingEndEpochSeconds= */ currentDate.minusDays(7).toEpochSecond());
 
@@ -354,6 +354,8 @@
                 new ArrayList<>();
         List<AtomsProto.CarWatchdogDailyIoUsageSummary> user100VendorPkgSummaries =
                 new ArrayList<>();
+        List<AtomsProto.CarWatchdogDailyIoUsageSummary> user101SystemPkgSummaries =
+                new ArrayList<>();
         for (int i = 15; i > 7; --i) {
             user101VendorPkgSummaries.add(CarWatchdogServiceUnitTest
                     .constructCarWatchdogDailyIoUsageSummary(/* fgWrBytes= */ 4101L * i,
@@ -363,17 +365,47 @@
                     .constructCarWatchdogDailyIoUsageSummary(/* fgWrBytes= */ 4100L * i,
                             /* bgWrBytes= */ 5100L * i, /* gmWrBytes= */ 6100L * i,
                             /* overuseCount= */ 1));
+            user101SystemPkgSummaries.add(CarWatchdogServiceUnitTest
+                    .constructCarWatchdogDailyIoUsageSummary(/* fgWrBytes= */ 1101L * i,
+                            /* bgWrBytes= */ 2101L * i, /* gmWrBytes= */ 3101L * i,
+                            /* overuseCount= */ 2));
         }
         List<WatchdogStorage.UserPackageDailySummaries> expected = Arrays.asList(
                 new WatchdogStorage.UserPackageDailySummaries(/* userId= */ 101,
                         /* packageName= */ "vendor_package.critical.C", user101VendorPkgSummaries),
                 new WatchdogStorage.UserPackageDailySummaries(/* userId= */ 100,
-                        /* packageName= */ "vendor_package.critical.C", user100VendorPkgSummaries));
+                        /* packageName= */ "vendor_package.critical.C", user100VendorPkgSummaries),
+                new WatchdogStorage.UserPackageDailySummaries(/* userId= */ 101,
+                        /* packageName= */ "system_package.non_critical.A",
+                        user101SystemPkgSummaries));
 
         assertWithMessage("Top users daily I/O usage summaries").that(actual).isEqualTo(expected);
     }
 
     @Test
+    public void testGetTopUsersDailyIoUsageSummariesWithLowSystemTotalWrittenBytes()
+            throws Exception {
+        injectSampleUserPackageSettings();
+        List<WatchdogStorage.IoUsageStatsEntry> entries = new ArrayList<>();
+        for (int i = 1; i <= 30; ++i) {
+            entries.addAll(sampleStatsBetweenDates(/* includingStartDaysAgo= */ i,
+                    /* excludingEndDaysAgo= */ i + 1, /* writtenBytesMultiplier= */ i));
+        }
+
+        assertWithMessage("Save I/O usage stats").that(mService.saveIoUsageStats(entries)).isTrue();
+
+        ZonedDateTime currentDate = mTimeSource.getCurrentDate();
+
+        List<WatchdogStorage.UserPackageDailySummaries> actual =
+                mService.getTopUsersDailyIoUsageSummaries(/* numTopUsers= */ 3,
+                        /* minSystemTotalWrittenBytes= */ 4_000_000,
+                        /* includingStartEpochSeconds= */ currentDate.minusDays(15).toEpochSecond(),
+                        /* excludingEndEpochSeconds= */ currentDate.minusDays(7).toEpochSecond());
+
+        assertWithMessage("Top users daily I/O usage summaries").that(actual).isNull();
+    }
+
+    @Test
     public void testGetTopUsersDailyIoUsageSummariesWithoutStats() throws Exception {
         injectSampleUserPackageSettings();
         List<WatchdogStorage.IoUsageStatsEntry> entries = new ArrayList<>();
@@ -388,7 +420,7 @@
 
         List<WatchdogStorage.UserPackageDailySummaries> actual =
                 mService.getTopUsersDailyIoUsageSummaries(/* numTopUsers= */ 3,
-                        /* minTotalWrittenBytes= */ 600_000,
+                        /* minSystemTotalWrittenBytes= */ 600_000,
                         /* includingStartEpochSeconds= */ currentDate.minusDays(15).toEpochSecond(),
                         /* excludingEndEpochSeconds= */ currentDate.minusDays(7).toEpochSecond());