Merge "Add overuse configuration cache in CarWatchdogService." into sc-v2-dev
diff --git a/service/src/com/android/car/watchdog/CarWatchdogService.java b/service/src/com/android/car/watchdog/CarWatchdogService.java
index 4e26cb7..f0f559c 100644
--- a/service/src/com/android/car/watchdog/CarWatchdogService.java
+++ b/service/src/com/android/car/watchdog/CarWatchdogService.java
@@ -244,12 +244,14 @@
 
     @Override
     public void dump(IndentingPrintWriter writer) {
-        writer.println("*CarWatchdogService*");
+        writer.println("*" + getClass().getSimpleName() + "*");
+        writer.increaseIndent();
         synchronized (mLock) {
             writer.println("Current garage mode: " + toGarageModeString(mCurrentGarageMode));
         }
         mWatchdogProcessHandler.dump(writer);
         mWatchdogPerfHandler.dump(writer);
+        writer.decreaseIndent();
     }
 
     /**
diff --git a/service/src/com/android/car/watchdog/OveruseConfigurationCache.java b/service/src/com/android/car/watchdog/OveruseConfigurationCache.java
new file mode 100644
index 0000000..1402ef6
--- /dev/null
+++ b/service/src/com/android/car/watchdog/OveruseConfigurationCache.java
@@ -0,0 +1,360 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.watchdog;
+
+import static com.android.car.watchdog.WatchdogPerfHandler.INTERNAL_APPLICATION_CATEGORY_TYPE_MAPS;
+import static com.android.car.watchdog.WatchdogPerfHandler.INTERNAL_APPLICATION_CATEGORY_TYPE_MEDIA;
+
+import android.automotive.watchdog.PerStateBytes;
+import android.automotive.watchdog.internal.ApplicationCategoryType;
+import android.automotive.watchdog.internal.ComponentType;
+import android.automotive.watchdog.internal.IoOveruseConfiguration;
+import android.automotive.watchdog.internal.PackageMetadata;
+import android.automotive.watchdog.internal.PerStateIoOveruseThreshold;
+import android.automotive.watchdog.internal.ResourceOveruseConfiguration;
+import android.automotive.watchdog.internal.ResourceSpecificConfiguration;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.IndentingPrintWriter;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.function.BiFunction;
+
+/**
+ * Cache to store overuse configurations in memory.
+ *
+ * <p>It assumes that the error checking and loading/merging initial configs are done prior to
+ * setting the cache.
+ */
+public final class OveruseConfigurationCache {
+    static final PerStateBytes DEFAULT_THRESHOLD =
+            constructPerStateBytes(Long.MAX_VALUE, Long.MAX_VALUE, Long.MAX_VALUE);
+
+    private final Object mLock = new Object();
+    @GuardedBy("mLock")
+    private final ArraySet<String> mSafeToKillSystemPackages = new ArraySet<>();
+    @GuardedBy("mLock")
+    private final ArraySet<String> mSafeToKillVendorPackages = new ArraySet<>();
+    @GuardedBy("mLock")
+    private final List<String> mVendorPackagePrefixes = new ArrayList<>();
+    @GuardedBy("mLock")
+    private final SparseArray<ArraySet<String>> mPackagesByAppCategoryType = new SparseArray<>();
+    @GuardedBy("mLock")
+    private final SparseArray<PerStateBytes> mGenericIoThresholdsByComponent = new SparseArray<>();
+    @GuardedBy("mLock")
+    private final ArrayMap<String, PerStateBytes> mIoThresholdsBySystemPackages = new ArrayMap<>();
+    @GuardedBy("mLock")
+    private final ArrayMap<String, PerStateBytes> mIoThresholdsByVendorPackages = new ArrayMap<>();
+    @GuardedBy("mLock")
+    private final SparseArray<PerStateBytes> mIoThresholdsByAppCategoryType = new SparseArray<>();
+
+    /** Dumps the contents of the cache. */
+    public void dump(IndentingPrintWriter writer) {
+        writer.println("*" + getClass().getSimpleName() + "*");
+        writer.increaseIndent();
+        synchronized (mLock) {
+            writer.println("mSafeToKillSystemPackages: " + mSafeToKillSystemPackages);
+            writer.println("mSafeToKillVendorPackages: " + mSafeToKillVendorPackages);
+            writer.println("mVendorPackagePrefixes: " + mVendorPackagePrefixes);
+            writer.println("mPackagesByAppCategoryType: ");
+            writer.increaseIndent();
+            for (int i = 0; i < mPackagesByAppCategoryType.size(); ++i) {
+                writer.print("App category: "
+                        + toApplicationCategoryTypeString(mPackagesByAppCategoryType.keyAt(i)));
+                writer.println(", Packages: " + mPackagesByAppCategoryType.valueAt(i));
+            }
+            writer.decreaseIndent();
+            writer.println("mGenericIoThresholdsByComponent: ");
+            writer.increaseIndent();
+            for (int i = 0; i < mGenericIoThresholdsByComponent.size(); ++i) {
+                writer.print("Component type: "
+                        + toComponentTypeString(mGenericIoThresholdsByComponent.keyAt(i)));
+                writer.print(", Threshold: ");
+                dumpPerStateBytes(mGenericIoThresholdsByComponent.valueAt(i), writer);
+            }
+            writer.decreaseIndent();
+            writer.println("mIoThresholdsBySystemPackages: ");
+            writer.increaseIndent();
+            for (int i = 0; i < mIoThresholdsBySystemPackages.size(); ++i) {
+                writer.print("Package name: " + mIoThresholdsBySystemPackages.keyAt(i));
+                writer.print(", Threshold: ");
+                dumpPerStateBytes(mIoThresholdsBySystemPackages.valueAt(i), writer);
+            }
+            writer.decreaseIndent();
+            writer.println("mIoThresholdsByVendorPackages: ");
+            writer.increaseIndent();
+            for (int i = 0; i < mIoThresholdsByVendorPackages.size(); ++i) {
+                writer.print("Package name: " + mIoThresholdsByVendorPackages.keyAt(i));
+                writer.print(", Threshold: ");
+                dumpPerStateBytes(mIoThresholdsByVendorPackages.valueAt(i), writer);
+            }
+            writer.decreaseIndent();
+            writer.println("mIoThresholdsByAppCategoryType: ");
+            writer.increaseIndent();
+            for (int i = 0; i < mIoThresholdsByAppCategoryType.size(); ++i) {
+                writer.print("App category: "
+                        + toApplicationCategoryTypeString(mIoThresholdsByAppCategoryType.keyAt(i)));
+                writer.print(", Threshold: ");
+                dumpPerStateBytes(mIoThresholdsByAppCategoryType.valueAt(i), writer);
+            }
+            writer.decreaseIndent();
+        }
+        writer.decreaseIndent();
+    }
+
+    /** Overwrites the configurations in the cache. */
+    public void set(List<ResourceOveruseConfiguration> configs) {
+        synchronized (mLock) {
+            clearLocked();
+            for (int i = 0; i < configs.size(); i++) {
+                ResourceOveruseConfiguration config = configs.get(i);
+                switch (config.componentType) {
+                    case ComponentType.SYSTEM:
+                        mSafeToKillSystemPackages.addAll(config.safeToKillPackages);
+                        break;
+                    case ComponentType.VENDOR:
+                        mSafeToKillVendorPackages.addAll(config.safeToKillPackages);
+                        mVendorPackagePrefixes.addAll(config.vendorPackagePrefixes);
+                        for (int j = 0; j < config.packageMetadata.size(); ++j) {
+                            PackageMetadata meta = config.packageMetadata.get(j);
+                            ArraySet<String> packages =
+                                    mPackagesByAppCategoryType.get(meta.appCategoryType);
+                            if (packages == null) {
+                                packages = new ArraySet<>();
+                            }
+                            packages.add(meta.packageName);
+                            mPackagesByAppCategoryType.append(meta.appCategoryType, packages);
+                        }
+                        break;
+                    default:
+                        // All third-party apps are killable.
+                        break;
+                }
+                for (int j = 0; j < config.resourceSpecificConfigurations.size(); ++j) {
+                    if (config.resourceSpecificConfigurations.get(j).getTag()
+                            == ResourceSpecificConfiguration.ioOveruseConfiguration) {
+                        setIoThresholdsLocked(config.componentType,
+                                config.resourceSpecificConfigurations.get(j)
+                                        .getIoOveruseConfiguration());
+                    }
+                }
+            }
+        }
+    }
+
+    /** Returns the threshold for the given package and component type. */
+    public PerStateBytes fetchThreshold(String genericPackageName,
+            @ComponentType int componentType) {
+        synchronized (mLock) {
+            PerStateBytes threshold = null;
+            switch (componentType) {
+                case ComponentType.SYSTEM:
+                    threshold = mIoThresholdsBySystemPackages.get(genericPackageName);
+                    if (threshold != null) {
+                        return copyPerStateBytes(threshold);
+                    }
+                    break;
+                case ComponentType.VENDOR:
+                    threshold = mIoThresholdsByVendorPackages.get(genericPackageName);
+                    if (threshold != null) {
+                        return copyPerStateBytes(threshold);
+                    }
+                    break;
+            }
+            threshold = fetchAppCategorySpecificThresholdLocked(genericPackageName);
+            if (threshold != null) {
+                return copyPerStateBytes(threshold);
+            }
+            threshold = mGenericIoThresholdsByComponent.get(componentType);
+            return threshold != null ? copyPerStateBytes(threshold)
+                    : copyPerStateBytes(DEFAULT_THRESHOLD);
+        }
+    }
+
+    /** Returns whether or not the given package is safe-to-kill on resource overuse. */
+    public boolean isSafeToKill(String genericPackageName, @ComponentType int componentType,
+            List<String> sharedPackages) {
+        synchronized (mLock) {
+            BiFunction<List<String>, Set<String>, Boolean> isSafeToKillAnyPackage =
+                    (packages, safeToKillPackages) -> {
+                        if (packages == null) {
+                            return false;
+                        }
+                        for (int i = 0; i < packages.size(); i++) {
+                            if (safeToKillPackages.contains(packages.get(i))) {
+                                return true;
+                            }
+                        }
+                        return false;
+                    };
+
+            switch (componentType) {
+                case ComponentType.SYSTEM:
+                    if (mSafeToKillSystemPackages.contains(genericPackageName)) {
+                        return true;
+                    }
+                    return isSafeToKillAnyPackage.apply(sharedPackages, mSafeToKillSystemPackages);
+                case ComponentType.VENDOR:
+                    if (mSafeToKillVendorPackages.contains(genericPackageName)) {
+                        return true;
+                    }
+                    /*
+                     * Packages under the vendor shared UID may contain system packages because when
+                     * CarWatchdogService derives the shared component type it attributes system
+                     * packages as vendor packages when there is at least one vendor package.
+                     */
+                    return isSafeToKillAnyPackage.apply(sharedPackages, mSafeToKillSystemPackages)
+                            || isSafeToKillAnyPackage.apply(sharedPackages,
+                            mSafeToKillVendorPackages);
+                default:
+                    // Third-party apps are always killable
+                    return true;
+            }
+        }
+    }
+
+    /** Returns the list of vendor package prefixes. */
+    public List<String> getVendorPackagePrefixes() {
+        synchronized (mLock) {
+            return new ArrayList<>(mVendorPackagePrefixes);
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void clearLocked() {
+        mSafeToKillSystemPackages.clear();
+        mSafeToKillVendorPackages.clear();
+        mVendorPackagePrefixes.clear();
+        mPackagesByAppCategoryType.clear();
+        mGenericIoThresholdsByComponent.clear();
+        mIoThresholdsBySystemPackages.clear();
+        mIoThresholdsByVendorPackages.clear();
+        mIoThresholdsByAppCategoryType.clear();
+    }
+
+    @GuardedBy("mLock")
+    private void setIoThresholdsLocked(int componentType, IoOveruseConfiguration ioConfig) {
+        mGenericIoThresholdsByComponent.append(componentType,
+                ioConfig.componentLevelThresholds.perStateWriteBytes);
+        switch (componentType) {
+            case ComponentType.SYSTEM:
+                populateThresholdsByPackagesLocked(
+                        ioConfig.packageSpecificThresholds, mIoThresholdsBySystemPackages);
+                break;
+            case ComponentType.VENDOR:
+                populateThresholdsByPackagesLocked(
+                        ioConfig.packageSpecificThresholds, mIoThresholdsByVendorPackages);
+                setIoThresholdsByAppCategoryTypeLocked(ioConfig.categorySpecificThresholds);
+                break;
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void setIoThresholdsByAppCategoryTypeLocked(
+            List<PerStateIoOveruseThreshold> thresholds) {
+        for (int i = 0; i < thresholds.size(); ++i) {
+            PerStateIoOveruseThreshold threshold = thresholds.get(i);
+            switch(threshold.name) {
+                case INTERNAL_APPLICATION_CATEGORY_TYPE_MAPS:
+                    mIoThresholdsByAppCategoryType.append(
+                            ApplicationCategoryType.MAPS, threshold.perStateWriteBytes);
+                    break;
+                case INTERNAL_APPLICATION_CATEGORY_TYPE_MEDIA:
+                    mIoThresholdsByAppCategoryType.append(ApplicationCategoryType.MEDIA,
+                            threshold.perStateWriteBytes);
+                    break;
+            }
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void populateThresholdsByPackagesLocked(List<PerStateIoOveruseThreshold> thresholds,
+            ArrayMap<String, PerStateBytes> thresholdsByPackages) {
+        for (int i = 0; i < thresholds.size(); ++i) {
+            thresholdsByPackages.put(
+                    thresholds.get(i).name, thresholds.get(i).perStateWriteBytes);
+        }
+    }
+
+    @GuardedBy("mLock")
+    private PerStateBytes fetchAppCategorySpecificThresholdLocked(String genericPackageName) {
+        for (int i = 0; i < mPackagesByAppCategoryType.size(); ++i) {
+            if (mPackagesByAppCategoryType.valueAt(i).contains(genericPackageName)) {
+                return mIoThresholdsByAppCategoryType.get(mPackagesByAppCategoryType.keyAt(i));
+            }
+        }
+        return null;
+    }
+
+    private static String toApplicationCategoryTypeString(@ApplicationCategoryType int type) {
+        switch (type) {
+            case ApplicationCategoryType.MAPS:
+                return "ApplicationCategoryType.MAPS";
+            case ApplicationCategoryType.MEDIA:
+                return "ApplicationCategoryType.MEDIA";
+            case ApplicationCategoryType.OTHERS:
+                return "ApplicationCategoryType.OTHERS";
+            default:
+                return "Invalid ApplicationCategoryType";
+        }
+    }
+
+    private static String toComponentTypeString(@ComponentType int type) {
+        switch (type) {
+            case ComponentType.SYSTEM:
+                return "ComponentType.SYSTEM";
+            case ComponentType.VENDOR:
+                return "ComponentType.VENDOR";
+            case ComponentType.THIRD_PARTY:
+                return "ComponentType.THIRD_PARTY";
+            default:
+                return "ComponentType.UNKNOWN";
+        }
+    }
+
+    private static void dumpPerStateBytes(PerStateBytes perStateBytes,
+            IndentingPrintWriter writer) {
+        if (perStateBytes == null) {
+            writer.println("{NULL}");
+            return;
+        }
+        writer.println("{Foreground bytes: " + perStateBytes.foregroundBytes
+                + ", Background bytes: " + perStateBytes.backgroundBytes + ", Garage mode bytes: "
+                + perStateBytes.garageModeBytes + '}');
+    }
+
+    private static PerStateBytes constructPerStateBytes(long fgBytes, long bgBytes, long gmBytes) {
+        return new PerStateBytes() {{
+                foregroundBytes = fgBytes;
+                backgroundBytes = bgBytes;
+                garageModeBytes = gmBytes;
+            }};
+    }
+
+    private static PerStateBytes copyPerStateBytes(PerStateBytes perStateBytes) {
+        return new PerStateBytes() {{
+                foregroundBytes = perStateBytes.foregroundBytes;
+                backgroundBytes = perStateBytes.backgroundBytes;
+                garageModeBytes = perStateBytes.garageModeBytes;
+            }};
+    }
+}
diff --git a/service/src/com/android/car/watchdog/WatchdogPerfHandler.java b/service/src/com/android/car/watchdog/WatchdogPerfHandler.java
index 557addb..774b806 100644
--- a/service/src/com/android/car/watchdog/WatchdogPerfHandler.java
+++ b/service/src/com/android/car/watchdog/WatchdogPerfHandler.java
@@ -151,6 +151,7 @@
     private final Handler mMainHandler;
     private final Handler mServiceHandler;
     private final WatchdogStorage mWatchdogStorage;
+    private final OveruseConfigurationCache mOveruseConfigurationCache;
     private final Object mLock = new Object();
     @GuardedBy("mLock")
     private final ArrayMap<String, PackageResourceUsage> mUsageByUserPackage = new ArrayMap<>();
@@ -163,10 +164,6 @@
     @GuardedBy("mLock")
     private final SparseArray<ArrayList<ResourceOveruseListenerInfo>>
             mOveruseSystemListenerInfosByUid = new SparseArray<>();
-    @GuardedBy("mLock")
-    private final ArraySet<String> mSafeToKillSystemPackages = new ArraySet<>();
-    @GuardedBy("mLock")
-    private final ArraySet<String> mSafeToKillVendorPackages = new ArraySet<>();
     /** Default killable state for packages. Updated only for {@link UserHandle.ALL} user handle. */
     @GuardedBy("mLock")
     private final ArraySet<String> mDefaultNotKillableGenericPackages = new ArraySet<>();
@@ -218,6 +215,7 @@
         mServiceHandler = new Handler(CarServiceUtils.getHandlerThread(
                 CarWatchdogService.class.getSimpleName()).getLooper());
         mWatchdogStorage = watchdogStorage;
+        mOveruseConfigurationCache = new OveruseConfigurationCache();
         mTimeSource = SYSTEM_INSTANCE;
         mOveruseHandlingDelayMills = OVERUSE_HANDLING_DELAY_MILLS;
         mCurrentUxState = UX_STATE_NO_DISTRACTION;
@@ -254,6 +252,7 @@
         /*
          * TODO(b/183436216): Implement this method.
          */
+        mOveruseConfigurationCache.dump(writer);
     }
 
     /** Retries any pending requests on re-connecting to the daemon */
@@ -557,7 +556,8 @@
                             packageInfo.applicationInfo);
                     int killableState = getPackageKillableStateForUserPackageLocked(
                             userId, genericPackageName, componentType,
-                            isSafeToKillLocked(genericPackageName, componentType, null));
+                            mOveruseConfigurationCache.isSafeToKill(
+                                    genericPackageName, componentType, /* sharedPackages= */null));
                     states.add(new PackageKillableState(packageInfo.packageName, userId,
                             killableState));
                     continue;
@@ -582,7 +582,8 @@
                 }
                 int killableState = getPackageKillableStateForUserPackageLocked(
                         userId, genericPackageName, componentType,
-                        isSafeToKillLocked(genericPackageName, componentType, packageNames));
+                        mOveruseConfigurationCache.isSafeToKill(
+                                genericPackageName, componentType, packageNames));
                 for (int i = 0; i < applicationInfos.size(); ++i) {
                     states.add(new PackageKillableState(
                             applicationInfos.get(i).packageName, userId, killableState));
@@ -806,25 +807,9 @@
             Slogf.e(TAG, "Fetched resource overuse configurations are empty");
             return;
         }
-        synchronized (mLock) {
-            mSafeToKillSystemPackages.clear();
-            mSafeToKillVendorPackages.clear();
-            for (int i = 0; i < internalConfigs.size(); i++) {
-                switch (internalConfigs.get(i).componentType) {
-                    case ComponentType.SYSTEM:
-                        mSafeToKillSystemPackages.addAll(internalConfigs.get(i).safeToKillPackages);
-                        break;
-                    case ComponentType.VENDOR:
-                        mSafeToKillVendorPackages.addAll(internalConfigs.get(i).safeToKillPackages);
-                        mPackageInfoHandler.setVendorPackagePrefixes(
-                                internalConfigs.get(i).vendorPackagePrefixes);
-                        break;
-                    default:
-                        // All third-party apps are killable.
-                        break;
-                }
-            }
-        }
+        mOveruseConfigurationCache.set(internalConfigs);
+        mPackageInfoHandler.setVendorPackagePrefixes(
+                mOveruseConfigurationCache.getVendorPackagePrefixes());
         if (DEBUG) {
             Slogf.d(TAG, "Fetched and synced resource overuse configs.");
         }
@@ -1242,45 +1227,6 @@
         }
     }
 
-    @GuardedBy("mLock")
-    private boolean isSafeToKillLocked(String genericPackageName, int componentType,
-            List<String> sharedPackages) {
-        BiFunction<List<String>, Set<String>, Boolean> isSafeToKillAnyPackage =
-                (packages, safeToKillPackages) -> {
-                    if (packages == null) {
-                        return false;
-                    }
-                    for (int i = 0; i < packages.size(); i++) {
-                        if (safeToKillPackages.contains(packages.get(i))) {
-                            return true;
-                        }
-                    }
-                    return false;
-                };
-
-        switch (componentType) {
-            case ComponentType.SYSTEM:
-                if (mSafeToKillSystemPackages.contains(genericPackageName)) {
-                    return true;
-                }
-                return isSafeToKillAnyPackage.apply(sharedPackages, mSafeToKillSystemPackages);
-            case ComponentType.VENDOR:
-                if (mSafeToKillVendorPackages.contains(genericPackageName)) {
-                    return true;
-                }
-                /*
-                 * Packages under the vendor shared UID may contain system packages because when
-                 * CarWatchdogService derives the shared component type it attributes system
-                 * packages as vendor packages when there is at least one vendor package.
-                 */
-                return isSafeToKillAnyPackage.apply(sharedPackages, mSafeToKillSystemPackages)
-                        || isSafeToKillAnyPackage.apply(sharedPackages, mSafeToKillVendorPackages);
-            default:
-                // Third-party apps are always killable
-                return true;
-        }
-    }
-
     private int[] getAliveUserIds() {
         UserManager userManager = UserManager.get(mContext);
         List<UserHandle> aliveUsers = userManager.getUserHandles(/* excludeDying= */ true);
diff --git a/service/src/com/android/car/watchdog/WatchdogProcessHandler.java b/service/src/com/android/car/watchdog/WatchdogProcessHandler.java
index 63aa135..e0cd6f3 100644
--- a/service/src/com/android/car/watchdog/WatchdogProcessHandler.java
+++ b/service/src/com/android/car/watchdog/WatchdogProcessHandler.java
@@ -122,6 +122,7 @@
             } else {
                 writer.println("none");
             }
+            writer.decreaseIndent();
         }
     }
 
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 b6fb9db..8f30d7a 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
@@ -1280,65 +1280,50 @@
 
     @Test
     public void testGetPackageKillableStatesAsUserWithSafeToKillPackages() throws Exception {
-        mockUmGetUserHandles(mMockUserManager, /* excludeDying= */ true, 11, 12);
+        mockUmGetUserHandles(mMockUserManager, /* excludeDying= */ true, 100, 101);
         injectPackageInfos(Arrays.asList(
-                constructPackageManagerPackageInfo("system_package.non_critical.A", 1102459, null),
-                constructPackageManagerPackageInfo("third_party_package", 1103456, null),
-                constructPackageManagerPackageInfo("vendor_package.critical.B", 1101278, null),
-                constructPackageManagerPackageInfo("vendor_package.non_critical.A", 1105573, null),
-                constructPackageManagerPackageInfo("third_party_package", 1203456, null),
-                constructPackageManagerPackageInfo("vendor_package.critical.B", 1201278, null)));
-
-        List<android.automotive.watchdog.internal.ResourceOveruseConfiguration> configs =
-                sampleInternalResourceOveruseConfigurations();
-        injectResourceOveruseConfigsAndWait(configs);
+                constructPackageManagerPackageInfo("system_package.non_critical.A", 10002459, null),
+                constructPackageManagerPackageInfo("third_party_package", 10003456, null),
+                constructPackageManagerPackageInfo("vendor_package.critical.B", 10001278, null),
+                constructPackageManagerPackageInfo("vendor_package.non_critical.A", 10005573, null),
+                constructPackageManagerPackageInfo("third_party_package", 10103456, null),
+                constructPackageManagerPackageInfo("vendor_package.critical.B", 10101278, null)));
 
         PackageKillableStateSubject.assertThat(
                 mCarWatchdogService.getPackageKillableStatesAsUser(UserHandle.ALL))
                 .containsExactly(
-                        new PackageKillableState("system_package.non_critical.A", 11,
+                        new PackageKillableState("system_package.non_critical.A", 100,
                                 PackageKillableState.KILLABLE_STATE_YES),
-                        new PackageKillableState("third_party_package", 11,
+                        new PackageKillableState("third_party_package", 100,
                                 PackageKillableState.KILLABLE_STATE_YES),
-                        new PackageKillableState("vendor_package.critical.B", 11,
+                        new PackageKillableState("vendor_package.critical.B", 100,
                                 PackageKillableState.KILLABLE_STATE_NEVER),
-                        new PackageKillableState("vendor_package.non_critical.A", 11,
+                        new PackageKillableState("vendor_package.non_critical.A", 100,
                                 PackageKillableState.KILLABLE_STATE_YES),
-                        new PackageKillableState("third_party_package", 12,
+                        new PackageKillableState("third_party_package", 101,
                                 PackageKillableState.KILLABLE_STATE_YES),
-                        new PackageKillableState("vendor_package.critical.B", 12,
+                        new PackageKillableState("vendor_package.critical.B", 101,
                                 PackageKillableState.KILLABLE_STATE_NEVER));
     }
 
     @Test
     public void testGetPackageKillableStatesAsUserWithVendorPackagePrefixes() throws Exception {
-        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)));
-
-        android.automotive.watchdog.internal.ResourceOveruseConfiguration vendorConfig =
-                new android.automotive.watchdog.internal.ResourceOveruseConfiguration();
-        vendorConfig.componentType = ComponentType.VENDOR;
-        vendorConfig.safeToKillPackages = Collections.singletonList("system_package_as_vendor");
-        vendorConfig.vendorPackagePrefixes = Collections.singletonList(
-                "system_package_as_vendor");
-        injectResourceOveruseConfigsAndWait(Collections.singletonList(vendorConfig));
+        mockUmGetUserHandles(mMockUserManager, /* excludeDying= */ true, 100);
+        injectPackageInfos(Collections.singletonList(constructPackageManagerPackageInfo(
+                "some_pkg_as_vendor_pkg", 10002459, /* sharedUserId= */ null, /* flags= */ 0,
+                ApplicationInfo.PRIVATE_FLAG_SYSTEM_EXT)));
 
         List<PackageKillableState> killableStates =
-                mCarWatchdogService.getPackageKillableStatesAsUser(new UserHandle(11));
+                mCarWatchdogService.getPackageKillableStatesAsUser(new UserHandle(100));
 
-        /* When CarWatchdogService connects with the watchdog daemon, CarWatchdogService fetches
-         * resource overuse configs from watchdog daemon. The vendor package prefixes in the
-         * configs help identify vendor packages. The safe-to-kill list in the configs helps
-         * identify safe-to-kill vendor packages. |system_package_as_vendor| is a critical system
-         * package by default but with the latest resource overuse configs, this package should be
-         * classified as a safe-to-kill vendor package.
-         */
+        // The vendor package prefixes in the resource overuse configs help identify vendor
+        // packages. The safe-to-kill list in the vendor configs helps identify safe-to-kill vendor
+        // packages. |system_package_as_vendor| is a critical system package by default but with
+        // the resource overuse configs, this package should be classified as a safe-to-kill vendor
+        // package.
         PackageKillableStateSubject.assertThat(killableStates)
-                .containsExactly(
-                        new PackageKillableState("system_package_as_vendor", 11,
-                                PackageKillableState.KILLABLE_STATE_YES));
+                .containsExactly(new PackageKillableState("some_pkg_as_vendor_pkg", 100,
+                        PackageKillableState.KILLABLE_STATE_YES));
     }
 
     @Test
@@ -1374,71 +1359,55 @@
     @Test
     public void testGetPackageKillableStatesAsUserWithSharedUidsAndSafeToKillPackages()
             throws Exception {
-        mockUmGetUserHandles(mMockUserManager, /* excludeDying= */ true, 11);
+        mockUmGetUserHandles(mMockUserManager, /* excludeDying= */ true, 100);
         injectPackageInfos(Arrays.asList(
                 constructPackageManagerPackageInfo(
-                        "vendor_package.non_critical.A", 1103456, "vendor_shared_package.A"),
+                        "vendor_package.non_critical.A", 10003456, "vendor_shared_package.A"),
                 constructPackageManagerPackageInfo(
-                        "system_package.A", 1103456, "vendor_shared_package.A"),
+                        "system_package.A", 10003456, "vendor_shared_package.A"),
                 constructPackageManagerPackageInfo(
-                        "vendor_package.B", 1103456, "vendor_shared_package.A"),
+                        "vendor_package.B", 10003456, "vendor_shared_package.A"),
                 constructPackageManagerPackageInfo(
-                        "third_party_package.C", 1105678, "third_party_shared_package"),
+                        "third_party_package.C", 10005678, "third_party_shared_package"),
                 constructPackageManagerPackageInfo(
-                        "third_party_package.D", 1105678, "third_party_shared_package")));
-
-        android.automotive.watchdog.internal.ResourceOveruseConfiguration vendorConfig =
-                new android.automotive.watchdog.internal.ResourceOveruseConfiguration();
-        vendorConfig.componentType = ComponentType.VENDOR;
-        vendorConfig.safeToKillPackages = Collections.singletonList(
-                "vendor_package.non_critical.A");
-        vendorConfig.vendorPackagePrefixes = new ArrayList<>();
-        injectResourceOveruseConfigsAndWait(Collections.singletonList(vendorConfig));
+                        "third_party_package.D", 10005678, "third_party_shared_package")));
 
         PackageKillableStateSubject.assertThat(
-                mCarWatchdogService.getPackageKillableStatesAsUser(new UserHandle(11)))
+                mCarWatchdogService.getPackageKillableStatesAsUser(new UserHandle(100)))
                 .containsExactly(
-                        new PackageKillableState("vendor_package.non_critical.A", 11,
+                        new PackageKillableState("vendor_package.non_critical.A", 100,
                                 PackageKillableState.KILLABLE_STATE_YES),
-                        new PackageKillableState("system_package.A", 11,
+                        new PackageKillableState("system_package.A", 100,
                                 PackageKillableState.KILLABLE_STATE_YES),
-                        new PackageKillableState("vendor_package.B", 11,
+                        new PackageKillableState("vendor_package.B", 100,
                                 PackageKillableState.KILLABLE_STATE_YES),
-                        new PackageKillableState("third_party_package.C", 11,
+                        new PackageKillableState("third_party_package.C", 100,
                                 PackageKillableState.KILLABLE_STATE_YES),
-                        new PackageKillableState("third_party_package.D", 11,
+                        new PackageKillableState("third_party_package.D", 100,
                                 PackageKillableState.KILLABLE_STATE_YES));
     }
 
     @Test
     public void testGetPackageKillableStatesAsUserWithSharedUidsAndSafeToKillSharedPackage()
             throws Exception {
-        mockUmGetUserHandles(mMockUserManager, /* excludeDying= */ true, 11);
+        mockUmGetUserHandles(mMockUserManager, /* excludeDying= */ true, 100);
         injectPackageInfos(Arrays.asList(
                 constructPackageManagerPackageInfo(
-                        "vendor_package.non_critical.A", 1103456, "vendor_shared_package.B"),
+                        "vendor_package.A", 10003456, "vendor_shared_package.non_critical.B"),
                 constructPackageManagerPackageInfo(
-                        "system_package.non_critical.A", 1103456, "vendor_shared_package.B"),
+                        "system_package.A", 10003456, "vendor_shared_package.non_critical.B"),
                 constructPackageManagerPackageInfo(
-                        "vendor_package.non_critical.B", 1103456, "vendor_shared_package.B")));
-
-        android.automotive.watchdog.internal.ResourceOveruseConfiguration vendorConfig =
-                new android.automotive.watchdog.internal.ResourceOveruseConfiguration();
-        vendorConfig.componentType = ComponentType.VENDOR;
-        vendorConfig.safeToKillPackages = Collections.singletonList(
-                "shared:vendor_shared_package.B");
-        vendorConfig.vendorPackagePrefixes = new ArrayList<>();
-        injectResourceOveruseConfigsAndWait(Collections.singletonList(vendorConfig));
+                        "vendor_package.B", 10003456, "vendor_shared_package.non_critical.B")));
 
 
         PackageKillableStateSubject.assertThat(
-                mCarWatchdogService.getPackageKillableStatesAsUser(new UserHandle(11)))
+                mCarWatchdogService.getPackageKillableStatesAsUser(new UserHandle(100)))
                 .containsExactly(
-                        new PackageKillableState("vendor_package.non_critical.A", 11,
+                        new PackageKillableState("vendor_package.A", 100,
                                 PackageKillableState.KILLABLE_STATE_YES),
-                        new PackageKillableState("system_package.non_critical.A", 11,
+                        new PackageKillableState("system_package.A", 100,
                                 PackageKillableState.KILLABLE_STATE_YES),
-                        new PackageKillableState("vendor_package.non_critical.B", 11,
+                        new PackageKillableState("vendor_package.B", 100,
                                 PackageKillableState.KILLABLE_STATE_YES));
     }
 
@@ -1503,12 +1472,16 @@
                 sampleResourceOveruseConfigurations(), CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO))
                 .isEqualTo(CarWatchdogManager.RETURN_CODE_SUCCESS);
 
-        /* Expect two calls, the first is made at car watchdog service init */
-        verify(mMockCarWatchdogDaemon, times(2)).getResourceOveruseConfigurations();
-
         InternalResourceOveruseConfigurationSubject
                 .assertThat(captureOnSetResourceOveruseConfigurations())
                 .containsExactlyElementsIn(sampleInternalResourceOveruseConfigurations());
+
+        // CarService fetches and syncs resource overuse configuration on the main thread by posting
+        // a new message. Wait until this completes.
+        CarServiceUtils.runOnMainSync(() -> {});
+
+        /* Expect two calls, the first is made at car watchdog service init */
+        verify(mMockCarWatchdogDaemon, times(2)).getResourceOveruseConfigurations();
     }
 
     @Test
@@ -1689,9 +1662,6 @@
 
     @Test
     public void testGetResourceOveruseConfigurations() throws Exception {
-        when(mMockCarWatchdogDaemon.getResourceOveruseConfigurations())
-                .thenReturn(sampleInternalResourceOveruseConfigurations());
-
         List<ResourceOveruseConfiguration> actualConfigs =
                 mCarWatchdogService.getResourceOveruseConfigurations(
                         CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO);
@@ -1725,9 +1695,6 @@
                 .when(() -> ServiceManager.getService(CAR_WATCHDOG_DAEMON_INTERFACE));
         mCarWatchdogDaemonBinderDeathRecipient.binderDied();
 
-        when(mMockCarWatchdogDaemon.getResourceOveruseConfigurations())
-                .thenReturn(sampleInternalResourceOveruseConfigurations());
-
         List<ResourceOveruseConfiguration> actualConfigs =
                 mCarWatchdogService.getResourceOveruseConfigurations(
                         CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO);
@@ -2693,6 +2660,126 @@
         verify(mMockCarWatchdogDaemon, never()).controlProcessHealthCheck(anyBoolean());
     }
 
+    @Test
+    public void testOveruseConfigurationCacheGetVendorPackagePrefixes() throws Exception {
+        OveruseConfigurationCache cache = new OveruseConfigurationCache();
+
+        cache.set(sampleInternalResourceOveruseConfigurations());
+
+        assertWithMessage("Vendor package prefixes").that(cache.getVendorPackagePrefixes())
+                .containsExactly("vendor_package", "some_pkg_as_vendor_pkg");
+    }
+
+    @Test
+    public void testOveruseConfigurationCacheFetchThreshold() throws Exception {
+        OveruseConfigurationCache cache = new OveruseConfigurationCache();
+
+        cache.set(sampleInternalResourceOveruseConfigurations());
+
+        InternalPerStateBytesSubject.assertWithMessage(
+                cache.fetchThreshold("system_package.non_critical.A", ComponentType.SYSTEM),
+                "System package with generic threshold")
+                .isEqualTo(constructPerStateBytes(10, 20, 30));
+
+        InternalPerStateBytesSubject.assertWithMessage(
+                cache.fetchThreshold("system_package.A", ComponentType.SYSTEM),
+                "System package with package specific threshold")
+                .isEqualTo(constructPerStateBytes(40, 50, 60));
+
+        InternalPerStateBytesSubject.assertWithMessage(
+                cache.fetchThreshold("system_package.MEDIA", ComponentType.SYSTEM),
+                "System package with media category threshold")
+                .isEqualTo(constructPerStateBytes(200, 400, 600));
+
+        InternalPerStateBytesSubject.assertWithMessage(
+                cache.fetchThreshold("vendor_package.non_critical.A", ComponentType.VENDOR),
+                "Vendor package with generic threshold")
+                .isEqualTo(constructPerStateBytes(20, 40, 60));
+
+        InternalPerStateBytesSubject.assertWithMessage(
+                cache.fetchThreshold("vendor_package.A", ComponentType.VENDOR),
+                "Vendor package with package specific threshold")
+                .isEqualTo(constructPerStateBytes(80, 100, 120));
+
+        InternalPerStateBytesSubject.assertWithMessage(
+                cache.fetchThreshold("vendor_package.MEDIA", ComponentType.VENDOR),
+                "Vendor package with media category threshold")
+                .isEqualTo(constructPerStateBytes(200, 400, 600));
+
+        InternalPerStateBytesSubject.assertWithMessage(
+                cache.fetchThreshold("third_party_package.A",
+                        ComponentType.THIRD_PARTY),
+                "3p package with generic threshold").isEqualTo(constructPerStateBytes(30, 60, 90));
+
+        InternalPerStateBytesSubject.assertWithMessage(
+                cache.fetchThreshold("third_party_package.MAPS", ComponentType.VENDOR),
+                "3p package with maps category threshold")
+                .isEqualTo(constructPerStateBytes(2200, 4400, 6600));
+    }
+
+    @Test
+    public void testOveruseConfigurationCacheIsSafeToKill() throws Exception {
+        OveruseConfigurationCache cache = new OveruseConfigurationCache();
+
+        cache.set(sampleInternalResourceOveruseConfigurations());
+
+        assertWithMessage("isSafeToKill non-critical system package").that(cache.isSafeToKill(
+                "system_package.non_critical.A", ComponentType.SYSTEM, null)).isTrue();
+
+        assertWithMessage("isSafeToKill shared non-critical system package")
+                .that(cache.isSafeToKill("system_package.A", ComponentType.SYSTEM,
+                        Collections.singletonList("system_package.non_critical.A"))).isTrue();
+
+        assertWithMessage("isSafeToKill non-critical vendor package").that(cache.isSafeToKill(
+                "vendor_package.non_critical.A", ComponentType.VENDOR, null)).isTrue();
+
+        assertWithMessage("isSafeToKill shared non-critical vendor package")
+                .that(cache.isSafeToKill("vendor_package.A", ComponentType.VENDOR,
+                        Collections.singletonList("vendor_package.non_critical.A"))).isTrue();
+
+        assertWithMessage("isSafeToKill 3p package").that(cache.isSafeToKill(
+                "third_party_package.A", ComponentType.THIRD_PARTY, null)).isTrue();
+
+        assertWithMessage("isSafeToKill critical system package").that(cache.isSafeToKill(
+                "system_package.A", ComponentType.SYSTEM, null)).isFalse();
+
+        assertWithMessage("isSafeToKill critical vendor package").that(cache.isSafeToKill(
+                "vendor_package.A", ComponentType.VENDOR, null)).isFalse();
+    }
+
+    @Test
+    public void testOverwriteOveruseConfigurationCache() throws Exception {
+        OveruseConfigurationCache cache = new OveruseConfigurationCache();
+
+        cache.set(sampleInternalResourceOveruseConfigurations());
+
+        cache.set(new ArrayList<>());
+
+        assertWithMessage("Vendor package prefixes").that(cache.getVendorPackagePrefixes())
+                .isEmpty();
+
+        InternalPerStateBytesSubject.assertWithMessage(
+                cache.fetchThreshold("system_package.A", ComponentType.SYSTEM),
+                "System package with default threshold")
+                .isEqualTo(OveruseConfigurationCache.DEFAULT_THRESHOLD);
+
+        InternalPerStateBytesSubject.assertWithMessage(
+                cache.fetchThreshold("vendor_package.A", ComponentType.VENDOR),
+                "Vendor package with default threshold")
+                .isEqualTo(OveruseConfigurationCache.DEFAULT_THRESHOLD);
+
+        InternalPerStateBytesSubject.assertWithMessage(
+                cache.fetchThreshold("third_party_package.A", ComponentType.THIRD_PARTY),
+                "3p package with default threshold")
+                .isEqualTo(OveruseConfigurationCache.DEFAULT_THRESHOLD);
+
+        assertWithMessage("isSafeToKill any system package").that(cache.isSafeToKill(
+                "system_package.non_critical.A", ComponentType.SYSTEM, null)).isFalse();
+
+        assertWithMessage("isSafeToKill any vendor package").that(cache.isSafeToKill(
+                "vendor_package.non_critical.A", ComponentType.VENDOR, null)).isFalse();
+    }
+
     public static android.automotive.watchdog.PerStateBytes constructPerStateBytes(
             long fgBytes, long bgBytes, long gmBytes) {
         android.automotive.watchdog.PerStateBytes perStateBytes =
@@ -2703,10 +2790,12 @@
         return perStateBytes;
     }
 
-    private void mockWatchdogDaemon() {
+    private void mockWatchdogDaemon() throws Exception {
         when(mMockBinder.queryLocalInterface(anyString())).thenReturn(mMockCarWatchdogDaemon);
         when(mMockCarWatchdogDaemon.asBinder()).thenReturn(mMockBinder);
         doReturn(mMockBinder).when(() -> ServiceManager.getService(CAR_WATCHDOG_DAEMON_INTERFACE));
+        when(mMockCarWatchdogDaemon.getResourceOveruseConfigurations()).thenReturn(
+                sampleInternalResourceOveruseConfigurations());
         mIsDaemonCrashed = false;
     }
 
@@ -2963,22 +3052,6 @@
         return resourceOveruseConfigurationsCaptor.getValue();
     }
 
-    private void injectResourceOveruseConfigsAndWait(
-            List<android.automotive.watchdog.internal.ResourceOveruseConfiguration> configs)
-            throws Exception {
-        when(mMockCarWatchdogDaemon.getResourceOveruseConfigurations()).thenReturn(configs);
-        /* Trigger CarWatchdogService to fetch/sync resource overuse configurations by changing the
-         * daemon connection status from connected -> disconnected -> connected.
-         */
-        crashWatchdogDaemon();
-        restartWatchdogDaemonAndAwait();
-
-        /* Method should be invoked 2 times. Once at test setup and once more after the daemon
-         * crashes and reconnects.
-         */
-        verify(mMockCarWatchdogDaemon, times(2)).getResourceOveruseConfigurations();
-    }
-
     private SparseArray<PackageIoOveruseStats> injectIoOveruseStatsForPackages(
             SparseArray<String> genericPackageNameByUid, Set<String> killablePackages,
             Set<String> shouldNotifyPackages) throws Exception {
@@ -3185,14 +3258,20 @@
     }
 
     private static ResourceOveruseConfiguration.Builder sampleResourceOveruseConfigurationBuilder(
-            int componentType, IoOveruseConfiguration ioOveruseConfig) {
+            @ComponentType int componentType, IoOveruseConfiguration ioOveruseConfig) {
         String prefix = WatchdogPerfHandler.toComponentTypeStr(componentType).toLowerCase();
         List<String> safeToKill = Arrays.asList(prefix + "_package.non_critical.A",
-                prefix + "_pkg.non_critical.B");
-        List<String> vendorPrefixes = Arrays.asList(prefix + "_package", prefix + "_pkg");
+                prefix + "_pkg.non_critical.B",
+                "shared:" + prefix + "_shared_package.non_critical.B",
+                "some_pkg_as_" + prefix + "_pkg");
+        List<String> vendorPrefixes = Arrays.asList(
+                prefix + "_package", "some_pkg_as_" + prefix + "_pkg");
         Map<String, String> pkgToAppCategory = new ArrayMap<>();
-        pkgToAppCategory.put(prefix + "_package.non_critical.A",
-                "android.car.watchdog.app.category.MEDIA");
+        pkgToAppCategory.put("system_package.MEDIA", "android.car.watchdog.app.category.MEDIA");
+        pkgToAppCategory.put("system_package.A", "android.car.watchdog.app.category.MAPS");
+        pkgToAppCategory.put("vendor_package.MEDIA", "android.car.watchdog.app.category.MEDIA");
+        pkgToAppCategory.put("vendor_package.A", "android.car.watchdog.app.category.MAPS");
+        pkgToAppCategory.put("third_party_package.MAPS", "android.car.watchdog.app.category.MAPS");
         ResourceOveruseConfiguration.Builder configBuilder =
                 new ResourceOveruseConfiguration.Builder(componentType, safeToKill,
                         vendorPrefixes, pkgToAppCategory);
@@ -3201,49 +3280,57 @@
     }
 
     private static IoOveruseConfiguration.Builder sampleIoOveruseConfigurationBuilder(
-            int componentType) {
+            @ComponentType int componentType) {
         String prefix = WatchdogPerfHandler.toComponentTypeStr(componentType).toLowerCase();
         PerStateBytes componentLevelThresholds = new PerStateBytes(
-                /* foregroundModeBytes= */ 10, /* backgroundModeBytes= */ 20,
-                /* garageModeBytes= */ 30);
+                /* foregroundModeBytes= */ componentType * 10L,
+                /* backgroundModeBytes= */ componentType * 20L,
+                /* garageModeBytes= */ componentType * 30L);
         Map<String, PerStateBytes> packageSpecificThresholds = new ArrayMap<>();
         packageSpecificThresholds.put(prefix + "_package.A", new PerStateBytes(
-                /* foregroundModeBytes= */ 40, /* backgroundModeBytes= */ 50,
-                /* garageModeBytes= */ 60));
+                /* foregroundModeBytes= */ componentType * 40L,
+                /* backgroundModeBytes= */ componentType * 50L,
+                /* garageModeBytes= */ componentType * 60L));
 
         Map<String, PerStateBytes> appCategorySpecificThresholds = new ArrayMap<>();
         appCategorySpecificThresholds.put(
                 ResourceOveruseConfiguration.APPLICATION_CATEGORY_TYPE_MEDIA,
-                new PerStateBytes(/* foregroundModeBytes= */ 100, /* backgroundModeBytes= */ 200,
-                        /* garageModeBytes= */ 300));
+                new PerStateBytes(/* foregroundModeBytes= */ componentType * 100L,
+                        /* backgroundModeBytes= */ componentType * 200L,
+                        /* garageModeBytes= */ componentType * 300L));
         appCategorySpecificThresholds.put(
                 ResourceOveruseConfiguration.APPLICATION_CATEGORY_TYPE_MAPS,
-                new PerStateBytes(/* foregroundModeBytes= */ 1100, /* backgroundModeBytes= */ 2200,
-                        /* garageModeBytes= */ 3300));
+                new PerStateBytes(/* foregroundModeBytes= */ componentType * 1100L,
+                        /* backgroundModeBytes= */ componentType * 2200L,
+                        /* garageModeBytes= */ componentType * 3300L));
 
         List<IoOveruseAlertThreshold> systemWideThresholds = Collections.singletonList(
-                new IoOveruseAlertThreshold(/* durationInSeconds= */ 10,
-                        /* writtenBytesPerSecond= */ 200));
+                new IoOveruseAlertThreshold(/* durationInSeconds= */ componentType * 10L,
+                        /* writtenBytesPerSecond= */ componentType * 200L));
 
         return new IoOveruseConfiguration.Builder(componentLevelThresholds,
                 packageSpecificThresholds, appCategorySpecificThresholds, systemWideThresholds);
     }
 
     private static android.automotive.watchdog.internal.ResourceOveruseConfiguration
-            sampleInternalResourceOveruseConfiguration(int componentType,
+            sampleInternalResourceOveruseConfiguration(@ComponentType int componentType,
             android.automotive.watchdog.internal.IoOveruseConfiguration ioOveruseConfig) {
         String prefix = WatchdogPerfHandler.toComponentTypeStr(componentType).toLowerCase();
         android.automotive.watchdog.internal.ResourceOveruseConfiguration config =
                 new android.automotive.watchdog.internal.ResourceOveruseConfiguration();
         config.componentType = componentType;
         config.safeToKillPackages = Arrays.asList(prefix + "_package.non_critical.A",
-                prefix + "_pkg.non_critical.B");
-        config.vendorPackagePrefixes = Arrays.asList(prefix + "_package", prefix + "_pkg");
-
-        PackageMetadata metadata = new PackageMetadata();
-        metadata.packageName = prefix + "_package.non_critical.A";
-        metadata.appCategoryType = ApplicationCategoryType.MEDIA;
-        config.packageMetadata = Collections.singletonList(metadata);
+                prefix + "_pkg.non_critical.B",
+                "shared:" + prefix + "_shared_package.non_critical.B",
+                "some_pkg_as_" + prefix + "_pkg");
+        config.vendorPackagePrefixes = Arrays.asList(
+                prefix + "_package", "some_pkg_as_" + prefix + "_pkg");
+        config.packageMetadata = Arrays.asList(
+                constructPackageMetadata("system_package.MEDIA", ApplicationCategoryType.MEDIA),
+                constructPackageMetadata("system_package.A", ApplicationCategoryType.MAPS),
+                constructPackageMetadata("vendor_package.MEDIA", ApplicationCategoryType.MEDIA),
+                constructPackageMetadata("vendor_package.A", ApplicationCategoryType.MAPS),
+                constructPackageMetadata("third_party_package.MAPS", ApplicationCategoryType.MAPS));
 
         ResourceSpecificConfiguration resourceSpecificConfig = new ResourceSpecificConfiguration();
         resourceSpecificConfig.setIoOveruseConfiguration(ioOveruseConfig);
@@ -3253,28 +3340,41 @@
     }
 
     private static android.automotive.watchdog.internal.IoOveruseConfiguration
-            sampleInternalIoOveruseConfiguration(int componentType) {
+            sampleInternalIoOveruseConfiguration(@ComponentType int componentType) {
         String prefix = WatchdogPerfHandler.toComponentTypeStr(componentType).toLowerCase();
         android.automotive.watchdog.internal.IoOveruseConfiguration config =
                 new android.automotive.watchdog.internal.IoOveruseConfiguration();
         config.componentLevelThresholds = constructPerStateIoOveruseThreshold(
-                WatchdogPerfHandler.toComponentTypeStr(componentType), /* fgBytes= */ 10,
-                /* bgBytes= */ 20, /* gmBytes= */ 30);
+                WatchdogPerfHandler.toComponentTypeStr(componentType),
+                /* fgBytes= */ componentType * 10L, /* bgBytes= */ componentType *  20L,
+                /*gmBytes= */ componentType * 30L);
         config.packageSpecificThresholds = Collections.singletonList(
-                constructPerStateIoOveruseThreshold(prefix + "_package.A", /* fgBytes= */ 40,
-                        /* bgBytes= */ 50, /* gmBytes= */ 60));
+                constructPerStateIoOveruseThreshold(prefix + "_package.A",
+                        /* fgBytes= */ componentType * 40L, /* bgBytes= */ componentType * 50L,
+                        /* gmBytes= */ componentType * 60L));
         config.categorySpecificThresholds = Arrays.asList(
                 constructPerStateIoOveruseThreshold(
                         WatchdogPerfHandler.INTERNAL_APPLICATION_CATEGORY_TYPE_MEDIA,
-                        /* fgBytes= */ 100, /* bgBytes= */ 200, /* gmBytes= */ 300),
+                        /* fgBytes= */ componentType * 100L, /* bgBytes= */ componentType * 200L,
+                        /* gmBytes= */ componentType * 300L),
                 constructPerStateIoOveruseThreshold(
                         WatchdogPerfHandler.INTERNAL_APPLICATION_CATEGORY_TYPE_MAPS,
-                        /* fgBytes= */ 1100, /* bgBytes= */ 2200, /* gmBytes= */ 3300));
+                        /* fgBytes= */ componentType * 1100L, /* bgBytes= */ componentType * 2200L,
+                        /* gmBytes= */ componentType * 3300L));
         config.systemWideThresholds = Collections.singletonList(
-                constructInternalIoOveruseAlertThreshold(/* duration= */ 10, /* writeBPS= */ 200));
+                constructInternalIoOveruseAlertThreshold(
+                        /* duration= */ componentType * 10L, /* writeBPS= */ componentType * 200L));
         return config;
     }
 
+    private static PackageMetadata constructPackageMetadata(
+            String packageName, @ApplicationCategoryType int appCategoryType) {
+        PackageMetadata metadata = new PackageMetadata();
+        metadata.packageName = packageName;
+        metadata.appCategoryType = appCategoryType;
+        return metadata;
+    }
+
     private static PerStateIoOveruseThreshold constructPerStateIoOveruseThreshold(String name,
             long fgBytes, long bgBytes, long gmBytes) {
         PerStateIoOveruseThreshold threshold = new PerStateIoOveruseThreshold();
diff --git a/tests/carservice_unit_test/src/com/android/car/watchdog/InternalIoOveruseConfigurationSubject.java b/tests/carservice_unit_test/src/com/android/car/watchdog/InternalIoOveruseConfigurationSubject.java
index 9cb67da..47e496e 100644
--- a/tests/carservice_unit_test/src/com/android/car/watchdog/InternalIoOveruseConfigurationSubject.java
+++ b/tests/carservice_unit_test/src/com/android/car/watchdog/InternalIoOveruseConfigurationSubject.java
@@ -19,7 +19,6 @@
 import static com.google.common.truth.Truth.assertAbout;
 
 import android.annotation.Nullable;
-import android.automotive.watchdog.PerStateBytes;
 import android.automotive.watchdog.internal.IoOveruseAlertThreshold;
 import android.automotive.watchdog.internal.IoOveruseConfiguration;
 import android.automotive.watchdog.internal.PerStateIoOveruseThreshold;
@@ -71,9 +70,10 @@
         if (actual == null || expected == null) {
             return (actual == null) && (expected == null);
         }
-        return actual.componentLevelThresholds.name == expected.componentLevelThresholds.name
-                && isPerStateBytesEquals(actual.componentLevelThresholds.perStateWriteBytes,
-                    expected.componentLevelThresholds.perStateWriteBytes)
+        return actual.componentLevelThresholds.name.equals(expected.componentLevelThresholds.name)
+                && InternalPerStateBytesSubject.isEquals(
+                        actual.componentLevelThresholds.perStateWriteBytes,
+                        expected.componentLevelThresholds.perStateWriteBytes)
                 && isPerStateThresholdEquals(actual.packageSpecificThresholds,
                     expected.packageSpecificThresholds)
                 && isPerStateThresholdEquals(actual.categorySpecificThresholds,
@@ -104,10 +104,9 @@
 
     public static String toString(PerStateIoOveruseThreshold threshold) {
         StringBuilder builder = new StringBuilder();
-        builder.append("{Name: ").append(threshold.name).append(", WriteBytes: {fgBytes: ")
-                .append(threshold.perStateWriteBytes.foregroundBytes).append(", bgBytes: ")
-                .append(threshold.perStateWriteBytes.backgroundBytes).append(", gmBytes: ")
-                .append(threshold.perStateWriteBytes.garageModeBytes).append("}}");
+        builder.append("{Name: ").append(threshold.name).append(", WriteBytes: ");
+        InternalPerStateBytesSubject.toStringBuilder(builder, threshold.perStateWriteBytes);
+        builder.append("}");
         return builder.toString();
     }
 
@@ -139,12 +138,6 @@
         return actualStr.equals(expectedStr);
     }
 
-    private static boolean isPerStateBytesEquals(PerStateBytes acutal, PerStateBytes expected) {
-        return acutal.foregroundBytes == expected.foregroundBytes
-                && acutal.backgroundBytes == expected.backgroundBytes
-                && acutal.garageModeBytes == expected.garageModeBytes;
-    }
-
     private static Set<String> toPerStateThresholdStrings(
             List<PerStateIoOveruseThreshold> thresholds) {
         return thresholds.stream().map(x -> String.format("%s:{%d,%d,%d}", x.name,
diff --git a/tests/carservice_unit_test/src/com/android/car/watchdog/InternalPerStateBytesSubject.java b/tests/carservice_unit_test/src/com/android/car/watchdog/InternalPerStateBytesSubject.java
new file mode 100644
index 0000000..2f97e13
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/watchdog/InternalPerStateBytesSubject.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.watchdog;
+
+import static com.google.common.truth.Truth.assertAbout;
+
+import android.annotation.Nullable;
+import android.automotive.watchdog.PerStateBytes;
+
+import com.google.common.truth.FailureMetadata;
+import com.google.common.truth.Subject;
+import com.google.common.truth.Truth;
+
+public final class InternalPerStateBytesSubject extends Subject {
+    // Boiler-plate Subject.Factory for PerStateBytesSubject
+    private static final Subject.Factory<com.android.car.watchdog.InternalPerStateBytesSubject,
+            PerStateBytes> PER_STATE_BYTES_SUBJECT_FACTORY =
+            com.android.car.watchdog.InternalPerStateBytesSubject::new;
+    private static final String NULL_ENTRY_STRING = "{NULL}";
+
+    private final PerStateBytes mActual;
+
+    // User-defined entry point
+    public static InternalPerStateBytesSubject assertThat(@Nullable PerStateBytes perStateBytes) {
+        return assertAbout(PER_STATE_BYTES_SUBJECT_FACTORY).that(perStateBytes);
+    }
+
+    // Static method for getting the subject factory (for use with assertAbout())
+    public static Subject.Factory<InternalPerStateBytesSubject, PerStateBytes> perStateBytes() {
+        return PER_STATE_BYTES_SUBJECT_FACTORY;
+    }
+
+    public static InternalPerStateBytesSubject assertWithMessage(
+            @Nullable PerStateBytes perStateBytes, String format, Object... args) {
+        return Truth.assertWithMessage(format, args).about(PER_STATE_BYTES_SUBJECT_FACTORY)
+                .that(perStateBytes);
+    }
+
+    private InternalPerStateBytesSubject(FailureMetadata failureMetadata,
+            @Nullable PerStateBytes subject) {
+        super(failureMetadata, subject);
+        this.mActual = subject;
+    }
+
+    // User-defined test assertion SPI below this point
+    public void isEqualTo(PerStateBytes expected) {
+        if (mActual == expected) {
+            return;
+        }
+        check("foregroundBytes").that(mActual.foregroundBytes).isEqualTo(expected.foregroundBytes);
+        check("backgroundBytes").that(mActual.backgroundBytes).isEqualTo(expected.backgroundBytes);
+        check("garageModeBytes").that(mActual.garageModeBytes).isEqualTo(expected.garageModeBytes);
+    }
+
+    public static boolean isEquals(PerStateBytes actual, PerStateBytes expected) {
+        if (actual == null || expected == null) {
+            return (actual == null) && (expected == null);
+        }
+        return actual.foregroundBytes == expected.foregroundBytes
+                && actual.backgroundBytes == expected.backgroundBytes
+                && actual.garageModeBytes == expected.garageModeBytes;
+    }
+
+    public static StringBuilder toStringBuilder(
+            StringBuilder builder, PerStateBytes perStateBytes) {
+        if (perStateBytes == null) {
+            return builder.append(NULL_ENTRY_STRING);
+        }
+        return builder.append("{Foreground bytes: ").append(perStateBytes.foregroundBytes)
+                .append(", Background bytes: ").append(perStateBytes.backgroundBytes)
+                .append(", Garage mode bytes: ").append(perStateBytes.garageModeBytes)
+                .append('}');
+    }
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/watchdog/IoUsageStatsEntrySubject.java b/tests/carservice_unit_test/src/com/android/car/watchdog/IoUsageStatsEntrySubject.java
index 91f5875..0479c93 100644
--- a/tests/carservice_unit_test/src/com/android/car/watchdog/IoUsageStatsEntrySubject.java
+++ b/tests/carservice_unit_test/src/com/android/car/watchdog/IoUsageStatsEntrySubject.java
@@ -20,7 +20,6 @@
 
 import android.annotation.Nullable;
 import android.automotive.watchdog.IoOveruseStats;
-import android.automotive.watchdog.PerStateBytes;
 
 import com.google.common.truth.Correspondence;
 import com.google.common.truth.FailureMetadata;
@@ -80,7 +79,7 @@
         }
         return actual.userId == expected.userId && actual.packageName.equals(expected.packageName)
                 && actual.ioUsage.getTotalTimesKilled() == expected.ioUsage.getTotalTimesKilled()
-                && isEqualsPerStateBytes(actual.ioUsage.getForgivenWriteBytes(),
+                && InternalPerStateBytesSubject.isEquals(actual.ioUsage.getForgivenWriteBytes(),
                 expected.ioUsage.getForgivenWriteBytes())
                 && isEqualsIoOveruseStats(actual.ioUsage.getInternalIoOveruseStats(),
                 expected.ioUsage.getInternalIoOveruseStats());
@@ -92,22 +91,14 @@
             return (actual == null) && (expected == null);
         }
         return actual.killableOnOveruse == expected.killableOnOveruse
-                && isEqualsPerStateBytes(actual.remainingWriteBytes, expected.remainingWriteBytes)
+                && InternalPerStateBytesSubject.isEquals(
+                        actual.remainingWriteBytes, expected.remainingWriteBytes)
                 && actual.startTime == expected.startTime
                 && actual.durationInSeconds == expected.durationInSeconds
-                && isEqualsPerStateBytes(actual.writtenBytes, expected.writtenBytes)
+                && InternalPerStateBytesSubject.isEquals(actual.writtenBytes, expected.writtenBytes)
                 && actual.totalOveruses == expected.totalOveruses;
     }
 
-    private static boolean isEqualsPerStateBytes(PerStateBytes actual, PerStateBytes expected) {
-        if (actual == null || expected == null) {
-            return (actual == null) && (expected == null);
-        }
-        return actual.foregroundBytes == expected.foregroundBytes
-                && actual.backgroundBytes == expected.backgroundBytes
-                && actual.garageModeBytes == expected.garageModeBytes;
-    }
-
     private static String toString(Iterable<WatchdogStorage.IoUsageStatsEntry> entries) {
         StringBuilder builder = new StringBuilder();
         builder.append('[');
@@ -135,7 +126,7 @@
         builder.append("{IoOveruseStats: ");
         toStringBuilder(builder, ioUsage.getInternalIoOveruseStats());
         builder.append(", ForgivenWriteBytes: ");
-        toStringBuilder(builder, ioUsage.getForgivenWriteBytes());
+        InternalPerStateBytesSubject.toStringBuilder(builder, ioUsage.getForgivenWriteBytes());
         return builder.append(", Total times killed: ").append(ioUsage.getTotalTimesKilled())
                 .append('}');
     }
@@ -147,26 +138,15 @@
         }
         builder.append("{Killable on overuse: ").append(stats.killableOnOveruse)
                 .append(", Remaining write bytes: ");
-        toStringBuilder(builder, stats.remainingWriteBytes);
+        InternalPerStateBytesSubject.toStringBuilder(builder, stats.remainingWriteBytes);
         builder.append(", Start time: ").append(stats.startTime)
                 .append(", Duration: ").append(stats.durationInSeconds).append(" seconds")
                 .append(", Total overuses: ").append(stats.totalOveruses)
                 .append(", Written bytes: ");
-        toStringBuilder(builder, stats.writtenBytes);
+        InternalPerStateBytesSubject.toStringBuilder(builder, stats.writtenBytes);
         return builder.append('}');
     }
 
-    private static StringBuilder toStringBuilder(
-            StringBuilder builder, PerStateBytes perStateBytes) {
-        if (perStateBytes == null) {
-            return builder.append(NULL_ENTRY_STRING);
-        }
-        return builder.append("{Foreground bytes: ").append(perStateBytes.foregroundBytes)
-                .append(", Background bytes: ").append(perStateBytes.backgroundBytes)
-                .append(", Garage mode bytes: ").append(perStateBytes.garageModeBytes)
-                .append('}');
-    }
-
     private IoUsageStatsEntrySubject(FailureMetadata failureMetadata,
             @Nullable Iterable<WatchdogStorage.IoUsageStatsEntry> iterableSubject) {
         super(failureMetadata, iterableSubject);