Remove resource usage stats on user delete and cleanup on system startup

Test: atest CarWatchdogServiceUnitTest WatchdogStorageUnitTest CarWatchdogServiceTest
Bug: 183413754
Change-Id: I550fbd7cd6c94960b3c3ee7d05f2dad7267238ca
Merged-In: I550fbd7cd6c94960b3c3ee7d05f2dad7267238ca
diff --git a/service/src/com/android/car/watchdog/CarWatchdogService.java b/service/src/com/android/car/watchdog/CarWatchdogService.java
index 93243d4..318d132 100644
--- a/service/src/com/android/car/watchdog/CarWatchdogService.java
+++ b/service/src/com/android/car/watchdog/CarWatchdogService.java
@@ -77,6 +77,7 @@
             "com.android.server.jobscheduler.GARAGE_MODE_ON";
     static final String ACTION_GARAGE_MODE_OFF =
             "com.android.server.jobscheduler.GARAGE_MODE_OFF";
+    static final int MISSING_ARG_VALUE = -1;
     static final TimeSourceInterface SYSTEM_INSTANCE = new TimeSourceInterface() {
         @Override
         public Instant now() {
@@ -106,25 +107,15 @@
         @Override
         public void onReceive(Context context, Intent intent) {
             final String action = intent.getAction();
-            boolean isGarageMode = false;
-            if (action.equals(ACTION_GARAGE_MODE_ON)) {
-                isGarageMode = true;
-            } else if (!action.equals(ACTION_GARAGE_MODE_OFF)) {
-                return;
-            }
-            if (isGarageMode) {
-                mWatchdogStorage.shrinkDatabase();
-            }
-            try {
-                mCarWatchdogDaemonHelper.notifySystemStateChange(StateType.GARAGE_MODE,
-                        isGarageMode ? GarageMode.GARAGE_MODE_ON : GarageMode.GARAGE_MODE_OFF,
-                        /* arg2= */ -1);
-                if (DEBUG) {
-                    Slogf.d(TAG, "Notified car watchdog daemon of garage mode(%s)",
-                            isGarageMode ? "ON" : "OFF");
-                }
-            } catch (RemoteException | RuntimeException e) {
-                Slogf.w(TAG, "Notifying garage mode state change failed: %s", e);
+            switch (action) {
+                case ACTION_GARAGE_MODE_ON:
+                case ACTION_GARAGE_MODE_OFF:
+                    handleGarageModeIntent(action.equals(ACTION_GARAGE_MODE_ON));
+                    break;
+                case Intent.ACTION_USER_REMOVED:
+                    UserHandle user = intent.getParcelableExtra(Intent.EXTRA_USER);
+                    mWatchdogPerfHandler.deleteUser(user.getIdentifier());
+                    break;
             }
         }
     };
@@ -361,6 +352,23 @@
         mWatchdogPerfHandler.setTimeSource(timeSource);
     }
 
+    private void handleGarageModeIntent(boolean isOn) {
+        if (isOn) {
+            mWatchdogStorage.shrinkDatabase();
+        }
+        try {
+            mCarWatchdogDaemonHelper.notifySystemStateChange(StateType.GARAGE_MODE,
+                    isOn ? GarageMode.GARAGE_MODE_ON : GarageMode.GARAGE_MODE_OFF,
+                    /* arg2= */ MISSING_ARG_VALUE);
+            if (DEBUG) {
+                Slogf.d(TAG, "Notified car watchdog daemon of garage mode(%s)",
+                        isOn ? "ON" : "OFF");
+            }
+        } catch (RemoteException | RuntimeException e) {
+            Slogf.w(TAG, e, "Notifying garage mode state change failed");
+        }
+    }
+
     private void postRegisterToDaemonMessage() {
         CarServiceUtils.runOnMain(() -> {
             synchronized (mLock) {
@@ -447,7 +455,7 @@
                 }
                 try {
                     mCarWatchdogDaemonHelper.notifySystemStateChange(StateType.POWER_CYCLE,
-                            powerCycle, /* arg2= */ -1);
+                            powerCycle, /* arg2= */ MISSING_ARG_VALUE);
                     if (DEBUG) {
                         Slogf.d(TAG, "Notified car watchdog daemon of power cycle(%d)", powerCycle);
                     }
@@ -499,6 +507,7 @@
         IntentFilter filter = new IntentFilter();
         filter.addAction(ACTION_GARAGE_MODE_ON);
         filter.addAction(ACTION_GARAGE_MODE_OFF);
+        filter.addAction(Intent.ACTION_USER_REMOVED);
 
         mContext.registerReceiverForAllUsers(mBroadcastReceiver, filter, null, null);
     }
diff --git a/service/src/com/android/car/watchdog/WatchdogPerfHandler.java b/service/src/com/android/car/watchdog/WatchdogPerfHandler.java
index 416795e..495c439 100644
--- a/service/src/com/android/car/watchdog/WatchdogPerfHandler.java
+++ b/service/src/com/android/car/watchdog/WatchdogPerfHandler.java
@@ -68,7 +68,6 @@
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
-import android.content.pm.UserInfo;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -401,12 +400,11 @@
     }
 
     private void setPackageKillableStateForAllUsers(String packageName, boolean isKillable) {
-        UserManager userManager = UserManager.get(mContext);
-        List<UserInfo> userInfos = userManager.getAliveUsers();
+        int[] userIds = getAliveUserIds();
         String genericPackageName = null;
         synchronized (mLock) {
-            for (int i = 0; i < userInfos.size(); ++i) {
-                int userId = userInfos.get(i).id;
+            for (int i = 0; i < userIds.length; ++i) {
+                int userId = userIds[i];
                 String name = mPackageInfoHandler.getNameForUserPackage(packageName, userId);
                 if (name == null) {
                     continue;
@@ -449,11 +447,10 @@
             return getPackageKillableStatesForUserId(userHandle.getIdentifier(), pm);
         }
         List<PackageKillableState> packageKillableStates = new ArrayList<>();
-        UserManager userManager = UserManager.get(mContext);
-        List<UserInfo> userInfos = userManager.getAliveUsers();
-        for (int i = 0; i < userInfos.size(); ++i) {
+        int[] userIds = getAliveUserIds();
+        for (int i = 0; i < userIds.length; ++i) {
             packageKillableStates.addAll(
-                    getPackageKillableStatesForUserId(userInfos.get(i).id, pm));
+                    getPackageKillableStatesForUserId(userIds[i], pm));
         }
         if (DEBUG) {
             Slogf.d(TAG, "Returning all package killable states for all users");
@@ -749,6 +746,21 @@
         }
     }
 
+    /** Deletes all data for specific user. */
+    public void deleteUser(@UserIdInt int userId) {
+        synchronized (mLock) {
+            for (int i = mUsageByUserPackage.size() - 1; i >= 0; --i) {
+                if (userId == mUsageByUserPackage.valueAt(i).userId) {
+                    mUsageByUserPackage.removeAt(i);
+                }
+            }
+            mWatchdogStorage.syncUsers(getAliveUserIds());
+        }
+        if (DEBUG) {
+            Slogf.d(TAG, "Resource usage for user id: %d was deleted.", userId);
+        }
+    }
+
     /** Sets the time source. */
     public void setTimeSource(TimeSourceInterface timeSource) {
         synchronized (mLock) {
@@ -801,6 +813,7 @@
     }
 
     private void readFromDatabase() {
+        mWatchdogStorage.syncUsers(getAliveUserIds());
         List<WatchdogStorage.UserPackageSettingsEntry> settingsEntries =
                 mWatchdogStorage.getUserPackageSettings();
         Slogf.i(TAG, "Read %d user package settings from database", settingsEntries.size());
@@ -1225,6 +1238,17 @@
         }
     }
 
+    private int[] getAliveUserIds() {
+        UserManager userManager = UserManager.get(mContext);
+        List<UserHandle> aliveUsers = userManager.getUserHandles(/* excludeDying= */ true);
+        int userSize = aliveUsers.size();
+        int[] userIds = new int[userSize];
+        for (int i = 0; i < userSize; ++i) {
+            userIds[i] = aliveUsers.get(i).getIdentifier();
+        }
+        return userIds;
+    }
+
     private static String getUserPackageUniqueId(int userId, String genericPackageName) {
         return String.valueOf(userId) + ":" + genericPackageName;
     }
diff --git a/service/src/com/android/car/watchdog/WatchdogStorage.java b/service/src/com/android/car/watchdog/WatchdogStorage.java
index 78b6927..42f6313 100644
--- a/service/src/com/android/car/watchdog/WatchdogStorage.java
+++ b/service/src/com/android/car/watchdog/WatchdogStorage.java
@@ -32,6 +32,7 @@
 import android.os.Process;
 import android.util.ArrayMap;
 import android.util.ArraySet;
+import android.util.IntArray;
 import android.util.Slog;
 
 import com.android.car.CarLog;
@@ -196,6 +197,25 @@
         }
     }
 
+    /**
+     * Deletes all user package settings and resource stats for all non-alive users.
+     *
+     * @param aliveUserIds Array of alive user ids.
+     */
+    public void syncUsers(int[] aliveUserIds) {
+        IntArray aliveUsers = IntArray.wrap(aliveUserIds);
+        for (int i = mUserPackagesByKey.size() - 1; i >= 0; --i) {
+            UserPackage userPackage = mUserPackagesByKey.valueAt(i);
+            if (aliveUsers.indexOf(userPackage.getUserId()) == -1) {
+                mUserPackagesByKey.removeAt(i);
+                mUserPackagesById.remove(userPackage.getUniqueId());
+            }
+        }
+        try (SQLiteDatabase db = mDbHelper.getWritableDatabase()) {
+            UserPackageSettingsTable.syncUserPackagesWithAliveUsers(db, aliveUsers);
+        }
+    }
+
     @VisibleForTesting
     boolean saveIoUsageStats(List<IoUsageStatsEntry> entries, boolean shouldCheckRetention) {
         ZonedDateTime currentDate =
@@ -371,6 +391,24 @@
             Slogf.i(TAG, "Deleted %d user package settings db rows for user %d and package %s",
                     deletedRows, userId, packageName);
         }
+
+        public static void syncUserPackagesWithAliveUsers(SQLiteDatabase db, IntArray aliveUsers) {
+            StringBuilder queryBuilder = new StringBuilder();
+            for (int i = 0; i < aliveUsers.size(); ++i) {
+                if (i == 0) {
+                    queryBuilder.append(COLUMN_USER_ID).append(" NOT IN (");
+                } else {
+                    queryBuilder.append(", ");
+                }
+                queryBuilder.append(aliveUsers.get(i));
+                if (i == aliveUsers.size() - 1) {
+                    queryBuilder.append(")");
+                }
+            }
+            int deletedRows = db.delete(TABLE_NAME, queryBuilder.toString(), new String[]{});
+            Slogf.i(TAG, "Deleted %d user package settings db rows while syncing with alive users",
+                    deletedRows);
+        }
     }
 
     /** Defines the I/O usage entry stored in the IoUsageStatsTable. */
diff --git a/tests/carservice_test/src/com/android/car/watchdog/CarWatchdogServiceTest.java b/tests/carservice_test/src/com/android/car/watchdog/CarWatchdogServiceTest.java
index ba01d41..9a24a7e 100644
--- a/tests/carservice_test/src/com/android/car/watchdog/CarWatchdogServiceTest.java
+++ b/tests/carservice_test/src/com/android/car/watchdog/CarWatchdogServiceTest.java
@@ -77,8 +77,8 @@
     private final Executor mExecutor =
             InstrumentationRegistry.getInstrumentation().getTargetContext().getMainExecutor();
     private final UserInfo[] mUserInfos = new UserInfo[] {
-            new UserInfoBuilder(10).setName("user 1").build(),
-            new UserInfoBuilder(11).setName("user 2").build()
+            new UserInfoBuilder(100).setName("user 1").build(),
+            new UserInfoBuilder(101).setName("user 2").build()
     };
 
     @Mock private Context mMockContext;
@@ -101,8 +101,8 @@
         when(mServiceBinder.queryLocalInterface(anyString())).thenReturn(mCarWatchdogService);
         when(mMockContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
         mockUmGetAllUsers(mUserManager, mUserInfos);
-        mockUmIsUserRunning(mUserManager, 10, true);
-        mockUmIsUserRunning(mUserManager, 11, false);
+        mockUmIsUserRunning(mUserManager, 100, true);
+        mockUmIsUserRunning(mUserManager, 101, false);
 
         mCarWatchdogService.init();
         mWatchdogServiceForSystemImpl = registerCarWatchdogService();
@@ -224,11 +224,11 @@
     }
 
     private void expectRunningUser() {
-        doReturn(10).when(() -> UserHandle.getUserId(Binder.getCallingUid()));
+        doReturn(100).when(() -> UserHandle.getUserId(Binder.getCallingUid()));
     }
 
     private void expectStoppedUser() {
-        doReturn(11).when(() -> UserHandle.getUserId(Binder.getCallingUid()));
+        doReturn(101).when(() -> UserHandle.getUserId(Binder.getCallingUid()));
     }
 
     private final class TestClient {
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 c1e3938..6734322 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
@@ -18,8 +18,8 @@
 
 import static android.automotive.watchdog.internal.ResourceOveruseActionType.KILLED;
 import static android.automotive.watchdog.internal.ResourceOveruseActionType.NOT_KILLED;
-import static android.car.test.mocks.AndroidMockitoHelper.mockUmGetAliveUsers;
 import static android.car.test.mocks.AndroidMockitoHelper.mockUmGetAllUsers;
+import static android.car.test.mocks.AndroidMockitoHelper.mockUmGetUserHandles;
 import static android.car.watchdog.CarWatchdogManager.TIMEOUT_CRITICAL;
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED;
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
@@ -283,6 +283,15 @@
     }
 
     @Test
+    public void testUserRemovedBroadcast() throws Exception {
+        mockUmGetUserHandles(mMockUserManager, /* excludeDying= */ true, 101, 102);
+        mBroadcastReceiver.onReceive(mMockContext,
+                new Intent().setAction(Intent.ACTION_USER_REMOVED)
+                        .putExtra(Intent.EXTRA_USER, UserHandle.of(100)));
+        verify(mMockWatchdogStorage).syncUsers(new int[] {101, 102});
+    }
+
+    @Test
     public void testGetResourceOveruseStats() throws Exception {
         int uid = Binder.getCallingUid();
         injectPackageInfos(Collections.singletonList(
@@ -1001,7 +1010,7 @@
 
     @Test
     public void testSetKillablePackageAsUser() throws Exception {
-        mockUmGetAliveUsers(mMockUserManager, 11, 12);
+        mockUmGetUserHandles(mMockUserManager, /* excludeDying= */ true, 11, 12);
         injectPackageInfos(Arrays.asList(
                 constructPackageManagerPackageInfo("third_party_package", 1103456, null),
                 constructPackageManagerPackageInfo("vendor_package.critical", 1101278, null),
@@ -1029,7 +1038,7 @@
                 () -> mCarWatchdogService.setKillablePackageAsUser("vendor_package.critical",
                         userHandle, /* isKillable= */ true));
 
-        mockUmGetAliveUsers(mMockUserManager, 11, 12, 13);
+        mockUmGetUserHandles(mMockUserManager, /* excludeDying= */ true, 11, 12, 13);
         injectPackageInfos(Collections.singletonList(
                 constructPackageManagerPackageInfo("third_party_package", 1303456, null)));
 
@@ -1049,7 +1058,7 @@
 
     @Test
     public void testSetKillablePackageAsUserWithSharedUids() throws Exception {
-        mockUmGetAliveUsers(mMockUserManager, 11, 12);
+        mockUmGetUserHandles(mMockUserManager, /* excludeDying= */ true, 11, 12);
         injectPackageInfos(Arrays.asList(
                 constructPackageManagerPackageInfo(
                         "third_party_package.A", 1103456, "third_party_shared_package.A"),
@@ -1094,7 +1103,7 @@
 
     @Test
     public void testSetKillablePackageAsUserForAllUsers() throws Exception {
-        mockUmGetAliveUsers(mMockUserManager, 11, 12);
+        mockUmGetUserHandles(mMockUserManager, /* excludeDying= */ true, 11, 12);
         injectPackageInfos(Arrays.asList(
                 constructPackageManagerPackageInfo("third_party_package", 1103456, null),
                 constructPackageManagerPackageInfo("vendor_package.critical", 1101278, null),
@@ -1121,7 +1130,7 @@
                 () -> mCarWatchdogService.setKillablePackageAsUser("vendor_package.critical",
                         UserHandle.ALL, /* isKillable= */ true));
 
-        mockUmGetAliveUsers(mMockUserManager, 11, 12, 13);
+        mockUmGetUserHandles(mMockUserManager, /* excludeDying= */ true, 11, 12, 13);
         injectPackageInfos(Collections.singletonList(
                 constructPackageManagerPackageInfo("third_party_package", 1303456, null)));
 
@@ -1141,7 +1150,7 @@
 
     @Test
     public void testSetKillablePackageAsUsersForAllUsersWithSharedUids() throws Exception {
-        mockUmGetAliveUsers(mMockUserManager, 11, 12);
+        mockUmGetUserHandles(mMockUserManager, /* excludeDying= */ true, 11, 12);
         injectPackageInfos(Arrays.asList(
                 constructPackageManagerPackageInfo(
                         "third_party_package.A", 1103456, "third_party_shared_package.A"),
@@ -1174,7 +1183,7 @@
                 new PackageKillableState("third_party_package.B", 12,
                         PackageKillableState.KILLABLE_STATE_NO));
 
-        mockUmGetAliveUsers(mMockUserManager, 11, 12, 13);
+        mockUmGetUserHandles(mMockUserManager, /* excludeDying= */ true, 11, 12, 13);
         injectPackageInfos(Arrays.asList(
                 constructPackageManagerPackageInfo(
                         "third_party_package.A", 1303456, "third_party_shared_package.A"),
@@ -1192,7 +1201,7 @@
 
     @Test
     public void testGetPackageKillableStatesAsUser() throws Exception {
-        mockUmGetAliveUsers(mMockUserManager, 11, 12);
+        mockUmGetUserHandles(mMockUserManager, /* excludeDying= */ true, 11, 12);
         injectPackageInfos(Arrays.asList(
                 constructPackageManagerPackageInfo("third_party_package", 1103456, null),
                 constructPackageManagerPackageInfo("vendor_package.critical", 1101278, null),
@@ -1210,7 +1219,7 @@
 
     @Test
     public void testGetPackageKillableStatesAsUserWithSafeToKillPackages() throws Exception {
-        mockUmGetAliveUsers(mMockUserManager, 11, 12);
+        mockUmGetUserHandles(mMockUserManager, /* excludeDying= */ true, 11, 12);
         injectPackageInfos(Arrays.asList(
                 constructPackageManagerPackageInfo("system_package.non_critical.A", 1102459, null),
                 constructPackageManagerPackageInfo("third_party_package", 1103456, null),
@@ -1242,7 +1251,7 @@
 
     @Test
     public void testGetPackageKillableStatesAsUserWithVendorPackagePrefixes() throws Exception {
-        mockUmGetAliveUsers(mMockUserManager, 11);
+        mockUmGetUserHandles(mMockUserManager, /* excludeDying= */ true, 11);
         /* Package names which start with "system" are constructed as system packages. */
         injectPackageInfos(Arrays.asList(
                 constructPackageManagerPackageInfo("system_package_as_vendor", 1102459, null)));
@@ -1273,7 +1282,7 @@
 
     @Test
     public void testGetPackageKillableStatesAsUserWithSharedUids() throws Exception {
-        mockUmGetAliveUsers(mMockUserManager, 11, 12);
+        mockUmGetUserHandles(mMockUserManager, /* excludeDying= */ true, 11, 12);
         injectPackageInfos(Arrays.asList(
                 constructPackageManagerPackageInfo(
                         "system_package.A", 1103456, "vendor_shared_package.A"),
@@ -1304,7 +1313,7 @@
     @Test
     public void testGetPackageKillableStatesAsUserWithSharedUidsAndSafeToKillPackages()
             throws Exception {
-        mockUmGetAliveUsers(mMockUserManager, 11);
+        mockUmGetUserHandles(mMockUserManager, /* excludeDying= */ true, 11);
         injectPackageInfos(Arrays.asList(
                 constructPackageManagerPackageInfo(
                         "vendor_package.non_critical.A", 1103456, "vendor_shared_package.A"),
@@ -1343,7 +1352,7 @@
     @Test
     public void testGetPackageKillableStatesAsUserWithSharedUidsAndSafeToKillSharedPackage()
             throws Exception {
-        mockUmGetAliveUsers(mMockUserManager, 11);
+        mockUmGetUserHandles(mMockUserManager, /* excludeDying= */ true, 11);
         injectPackageInfos(Arrays.asList(
                 constructPackageManagerPackageInfo(
                         "vendor_package.non_critical.A", 1103456, "vendor_shared_package.B"),
@@ -1374,7 +1383,7 @@
 
     @Test
     public void testGetPackageKillableStatesAsUserForAllUsers() throws Exception {
-        mockUmGetAliveUsers(mMockUserManager, 11, 12);
+        mockUmGetUserHandles(mMockUserManager, /* excludeDying= */ true, 11, 12);
         injectPackageInfos(Arrays.asList(
                 constructPackageManagerPackageInfo("third_party_package", 1103456, null),
                 constructPackageManagerPackageInfo("vendor_package.critical", 1101278, null),
@@ -1395,7 +1404,7 @@
 
     @Test
     public void testGetPackageKillableStatesAsUserForAllUsersWithSharedUids() throws Exception {
-        mockUmGetAliveUsers(mMockUserManager, 11, 12);
+        mockUmGetUserHandles(mMockUserManager, /* excludeDying= */ true, 11, 12);
         injectPackageInfos(Arrays.asList(
                 constructPackageManagerPackageInfo(
                         "system_package.A", 1103456, "vendor_shared_package.A"),
@@ -1897,7 +1906,7 @@
 
     @Test
     public void testPersistStatsOnShutdownEnter() throws Exception {
-        mockUmGetAliveUsers(mMockUserManager, 10, 11, 12);
+        mockUmGetUserHandles(mMockUserManager, /* excludeDying= */ true, 10, 11, 12);
         injectPackageInfos(Arrays.asList(
                 constructPackageManagerPackageInfo(
                         "third_party_package", 1103456, "vendor_shared_package.critical"),
@@ -1961,7 +1970,7 @@
 
     @Test
     public void testPersistIoOveruseStatsOnDateChange() throws Exception {
-        mockUmGetAliveUsers(mMockUserManager, 10);
+        mockUmGetUserHandles(mMockUserManager, /* excludeDying= */ true, 10);
         injectPackageInfos(Arrays.asList(
                 constructPackageManagerPackageInfo("system_package", 1011200, null),
                 constructPackageManagerPackageInfo("third_party_package", 1001100, null)));
@@ -2053,7 +2062,7 @@
 
     @Test
     public void testResetResourceOveruseStatsResetsUserPackageSettings() throws Exception {
-        mockUmGetAliveUsers(mMockUserManager, 100, 101);
+        mockUmGetUserHandles(mMockUserManager, /* excludeDying= */ true, 100, 101);
         injectPackageInfos(Arrays.asList(
                 constructPackageManagerPackageInfo("third_party_package.A", 10001278, null),
                 constructPackageManagerPackageInfo("third_party_package.A", 10101278, null),
@@ -2388,6 +2397,7 @@
          */
         CarServiceUtils.getHandlerThread(CarWatchdogService.class.getSimpleName())
                 .getThreadHandler().post(() -> {});
+        verify(mMockWatchdogStorage, times(wantedInvocations)).syncUsers(any());
         verify(mMockWatchdogStorage, times(wantedInvocations)).getUserPackageSettings();
         verify(mMockWatchdogStorage, times(wantedInvocations)).getTodayIoUsageStats();
     }
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 3f16750..e2c12bb 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,6 +346,68 @@
     }
 
     @Test
+    public void testSyncUsers() throws Exception {
+        List<WatchdogStorage.UserPackageSettingsEntry> settingsEntries = sampleSettings();
+        List<WatchdogStorage.IoUsageStatsEntry> ioUsageStatsEntries = sampleStatsForToday();
+
+        assertThat(mService.saveUserPackageSettings(settingsEntries)).isTrue();
+        assertThat(mService.saveIoUsageStats(ioUsageStatsEntries)).isTrue();
+
+        mService.syncUsers(/* aliveUserIds= */ new int[] {101});
+
+        settingsEntries.removeIf((s) -> s.userId == 100);
+        ioUsageStatsEntries.removeIf((e) -> e.userId == 100);
+
+        UserPackageSettingsEntrySubject.assertThat(mService.getUserPackageSettings())
+                .containsExactlyElementsIn(settingsEntries);
+
+        IoUsageStatsEntrySubject.assertThat(mService.getTodayIoUsageStats())
+                .containsExactlyElementsIn(ioUsageStatsEntries);
+    }
+
+    @Test
+    public void testSyncUsersWithHistoricalIoOveruseStats() throws Exception {
+        List<WatchdogStorage.UserPackageSettingsEntry> settingsEntries = sampleSettings();
+
+        assertThat(mService.saveUserPackageSettings(settingsEntries)).isTrue();
+        assertThat(mService.saveIoUsageStats(sampleStatsBetweenDates(
+                /* includingStartDaysAgo= */ 1, /* excludingEndDaysAgo= */ 6))).isTrue();
+
+        mService.syncUsers(/* aliveUserIds= */ new int[] {101});
+
+        settingsEntries.removeIf((s) -> s.userId == 100);
+
+        UserPackageSettingsEntrySubject.assertThat(mService.getUserPackageSettings())
+                .containsExactlyElementsIn(settingsEntries);
+
+        IoOveruseStats actualSystemPackage = mService.getHistoricalIoOveruseStats(
+                /* userId= */ 100, "system_package.non_critical.A", /* numDaysAgo= */ 7);
+        IoOveruseStats actualVendorPackage = mService.getHistoricalIoOveruseStats(
+                /* userId= */ 100, "vendor_package.critical.C", /* numDaysAgo= */ 7);
+
+        assertWithMessage("System I/O overuse stats for deleted user")
+                .that(actualSystemPackage).isNull();
+        assertWithMessage("Vendor I/O overuse stats for deleted user")
+                .that(actualVendorPackage).isNull();
+    }
+
+    @Test
+    public void testSyncUsersWithNoDataForDeletedUser() throws Exception {
+        List<WatchdogStorage.UserPackageSettingsEntry> settingsEntries = sampleSettings();
+        List<WatchdogStorage.IoUsageStatsEntry> ioUsageStatsEntries = sampleStatsForToday();
+
+        assertThat(mService.saveUserPackageSettings(settingsEntries)).isTrue();
+        assertThat(mService.saveIoUsageStats(ioUsageStatsEntries)).isTrue();
+
+        mService.syncUsers(/* aliveUserIds= */ new int[] {100, 101});
+
+        UserPackageSettingsEntrySubject.assertThat(mService.getUserPackageSettings())
+                .containsExactlyElementsIn(settingsEntries);
+        IoUsageStatsEntrySubject.assertThat(mService.getTodayIoUsageStats())
+                .containsExactlyElementsIn(ioUsageStatsEntries);
+    }
+
+    @Test
     public void testTruncateStatsOutsideRetentionPeriodOnDateChange() throws Exception {
         injectSampleUserPackageSettings();
         setDate(/* numDaysAgo= */ 1);
@@ -454,7 +516,7 @@
     private static ArrayList<WatchdogStorage.IoUsageStatsEntry> sampleStatsForDate(
             long statsDateEpoch, long duration) {
         ArrayList<WatchdogStorage.IoUsageStatsEntry> entries = new ArrayList<>();
-        for (int i = 100; i < 101; ++i) {
+        for (int i = 100; i <= 101; ++i) {
             entries.add(constructIoUsageStatsEntry(
                     /* userId= */ i, "system_package.non_critical.A", statsDateEpoch, duration,
                     /* remainingWriteBytes= */