Merge "DO NOT MERGE Update NewUserDisclaimer UX" into sc-v2-dev
diff --git a/car-admin-ui-lib/Android.bp b/car-admin-ui-lib/Android.bp
index 35a8fda..96615fa 100644
--- a/car-admin-ui-lib/Android.bp
+++ b/car-admin-ui-lib/Android.bp
@@ -28,7 +28,8 @@
         "src/**/*.java",
     ],
     libs: [
-        "SettingsLib"
+        "SettingsLib",
+        "modules-utils-preconditions",
     ],
     resource_dirs: [
         "src/main/res"
diff --git a/car-admin-ui-lib/src/main/java/com/android/car/admin/ui/UserAvatarView.java b/car-admin-ui-lib/src/main/java/com/android/car/admin/ui/UserAvatarView.java
index e4d2a15..3b92265 100644
--- a/car-admin-ui-lib/src/main/java/com/android/car/admin/ui/UserAvatarView.java
+++ b/car-admin-ui-lib/src/main/java/com/android/car/admin/ui/UserAvatarView.java
@@ -24,6 +24,7 @@
 import android.util.AttributeSet;
 import android.view.View;
 
+import com.android.internal.util.Preconditions;
 import com.android.settingslib.drawable.UserIconDrawable;
 
 // TODO(b/176262528): copied from com.android.systemui, ideally it should be provided by a common
@@ -121,21 +122,26 @@
     }
 
     public void setDrawable(Drawable d) {
-        if (d instanceof UserIconDrawable) {
-            throw new RuntimeException("Recursively adding UserIconDrawable");
-        }
+        Preconditions.checkArgument(!(d instanceof UserIconDrawable),
+                "Recursively adding UserIconDrawable: %s", d);
         mDrawable.setIconDrawable(d);
         mDrawable.setBadge(null);
     }
 
     public void setDrawableWithBadge(Drawable d, int userId) {
-        if (d instanceof UserIconDrawable) {
-            throw new RuntimeException("Recursively adding UserIconDrawable");
-        }
+        Preconditions.checkArgument(!(d instanceof UserIconDrawable),
+                "Recursively adding UserIconDrawable: %s", d);
         mDrawable.setIconDrawable(d);
         mDrawable.setBadgeIfManagedUser(getContext(), userId);
     }
 
+    public void setDrawableWithBadge(Drawable d) {
+        Preconditions.checkArgument(!(d instanceof UserIconDrawable),
+                "Recursively adding UserIconDrawable: %s", d);
+        mDrawable.setIconDrawable(d);
+        mDrawable.setBadgeIfManagedDevice(getContext());
+    }
+
     public UserIconDrawable getUserIconDrawable() {
         return mDrawable;
     }
diff --git a/car-lib/src/android/car/Car.java b/car-lib/src/android/car/Car.java
index 6647a32..a298f7f 100644
--- a/car-lib/src/android/car/Car.java
+++ b/car-lib/src/android/car/Car.java
@@ -492,6 +492,15 @@
             "android.car.permission.CAR_INSTRUMENT_CLUSTER_CONTROL";
 
     /**
+     * Permission necessary to listen for the instrument cluster's navigation state changes.
+     *
+     * @hide
+     */
+    public static final String PERMISSION_CAR_MONITOR_CLUSTER_NAVIGATION_STATE =
+            "android.car.permission.CAR_MONITOR_CLUSTER_NAVIGATION_STATE";
+
+
+    /**
      * Application must have this permission in order to be launched in the instrument cluster
      * display.
      *
diff --git a/car-lib/src/android/car/cluster/ClusterHomeManager.java b/car-lib/src/android/car/cluster/ClusterHomeManager.java
index 9b0cce4..3be2148 100644
--- a/car-lib/src/android/car/cluster/ClusterHomeManager.java
+++ b/car-lib/src/android/car/cluster/ClusterHomeManager.java
@@ -64,9 +64,9 @@
     public static final int CONFIG_DISPLAY_ID = 0x10;
 
     /**
-     * Callback for ClusterHome to get notifications
+     * Callback for ClusterHome to get notifications when cluster state changes.
      */
-    public interface ClusterHomeCallback {
+    public interface ClusterStateListener {
         /**
          * Called when ClusterOS changes the cluster display state, the geometry of cluster display,
          * or the uiType.
@@ -74,61 +74,121 @@
          * @param changes the flag indicates which fields are updated
          */
         void onClusterStateChanged(ClusterState state, @Config int changes);
+    }
 
+    /**
+     * Callback for ClusterHome to get notifications when cluster navigation state changes.
+     */
+    public interface ClusterNavigationStateListener {
         /** Called when the App who owns the navigation focus casts the new navigation state. */
         void onNavigationState(byte[] navigationState);
     }
 
-    private static class CallbackRecord {
+    private static class ClusterStateListenerRecord {
         final Executor mExecutor;
-        final ClusterHomeCallback mCallback;
-        CallbackRecord(Executor executor, ClusterHomeCallback callback) {
+        final ClusterStateListener mListener;
+        ClusterStateListenerRecord(Executor executor, ClusterStateListener listener) {
             mExecutor = executor;
-            mCallback = callback;
+            mListener = listener;
         }
         @Override
         public boolean equals(Object obj) {
             if (this == obj) {
                 return true;
             }
-            if (!(obj instanceof CallbackRecord)) {
+            if (!(obj instanceof ClusterStateListenerRecord)) {
                 return false;
             }
-            return mCallback == ((CallbackRecord) obj).mCallback;
+            return mListener == ((ClusterStateListenerRecord) obj).mListener;
         }
         @Override
         public int hashCode() {
-            return mCallback.hashCode();
+            return mListener.hashCode();
+        }
+    }
+
+    private static class ClusterNavigationStateListenerRecord {
+        final Executor mExecutor;
+        final ClusterNavigationStateListener mListener;
+
+        ClusterNavigationStateListenerRecord(Executor executor,
+                ClusterNavigationStateListener listener) {
+            mExecutor = executor;
+            mListener = listener;
+        }
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj) {
+                return true;
+            }
+            if (!(obj instanceof ClusterNavigationStateListenerRecord)) {
+                return false;
+            }
+            return mListener == ((ClusterNavigationStateListenerRecord) obj).mListener;
+        }
+        @Override
+        public int hashCode() {
+            return mListener.hashCode();
         }
     }
 
     private final IClusterHomeService mService;
-    private final IClusterHomeCallbackImpl mBinderCallback;
-    private final CopyOnWriteArrayList<CallbackRecord> mCallbacks = new CopyOnWriteArrayList<>();
+    private final IClusterStateListenerImpl mClusterStateListenerBinderCallback;
+    private final IClusterNavigationStateListenerImpl mClusterNavigationStateListenerBinderCallback;
+    private final CopyOnWriteArrayList<ClusterStateListenerRecord> mStateListeners =
+            new CopyOnWriteArrayList<>();
+    private final CopyOnWriteArrayList<ClusterNavigationStateListenerRecord>
+            mNavigationStateListeners = new CopyOnWriteArrayList<>();
 
     /** @hide */
     @VisibleForTesting
     public ClusterHomeManager(Car car, IBinder service) {
         super(car);
         mService = IClusterHomeService.Stub.asInterface(service);
-        mBinderCallback = new IClusterHomeCallbackImpl(this);
+        mClusterStateListenerBinderCallback = new IClusterStateListenerImpl(this);
+        mClusterNavigationStateListenerBinderCallback =
+                new IClusterNavigationStateListenerImpl(this);
     }
 
     /**
      * Registers the callback for ClusterHome.
      */
     @RequiresPermission(Car.PERMISSION_CAR_INSTRUMENT_CLUSTER_CONTROL)
-    public void registerClusterHomeCallback(
-            @NonNull Executor executor, @NonNull ClusterHomeCallback callback) {
+    public void registerClusterStateListener(
+            @NonNull Executor executor, @NonNull ClusterStateListener callback) {
         Objects.requireNonNull(executor, "executor cannot be null");
         Objects.requireNonNull(callback, "callback cannot be null");
-        CallbackRecord callbackRecord = new CallbackRecord(executor, callback);
-        if (!mCallbacks.addIfAbsent(callbackRecord)) {
+        ClusterStateListenerRecord clusterStateListenerRecord =
+                new ClusterStateListenerRecord(executor, callback);
+        if (!mStateListeners.addIfAbsent(clusterStateListenerRecord)) {
             return;
         }
-        if (mCallbacks.size() == 1) {
+        if (mStateListeners.size() == 1) {
             try {
-                mService.registerCallback(mBinderCallback);
+                mService.registerClusterStateListener(mClusterStateListenerBinderCallback);
+            } catch (RemoteException e) {
+                handleRemoteExceptionFromCarService(e);
+            }
+        }
+    }
+
+    /**
+     * Registers the callback for ClusterHome.
+     */
+    @RequiresPermission(Car.PERMISSION_CAR_MONITOR_CLUSTER_NAVIGATION_STATE)
+    public void registerClusterNavigationStateListener(
+            @NonNull Executor executor, @NonNull ClusterNavigationStateListener callback) {
+        Objects.requireNonNull(executor, "executor cannot be null");
+        Objects.requireNonNull(callback, "callback cannot be null");
+        ClusterNavigationStateListenerRecord clusterStateListenerRecord =
+                new ClusterNavigationStateListenerRecord(executor, callback);
+        if (!mNavigationStateListeners.addIfAbsent(clusterStateListenerRecord)) {
+            return;
+        }
+        if (mNavigationStateListeners.size() == 1) {
+            try {
+                mService.registerClusterNavigationStateListener(
+                        mClusterNavigationStateListenerBinderCallback);
             } catch (RemoteException e) {
                 handleRemoteExceptionFromCarService(e);
             }
@@ -139,24 +199,46 @@
      * Unregisters the callback.
      */
     @RequiresPermission(Car.PERMISSION_CAR_INSTRUMENT_CLUSTER_CONTROL)
-    public void unregisterClusterHomeCallback(@NonNull ClusterHomeCallback callback) {
+    public void unregisterClusterStateListener(@NonNull ClusterStateListener callback) {
         Objects.requireNonNull(callback, "callback cannot be null");
-        if (!mCallbacks.remove(new CallbackRecord(/* executor= */ null, callback))) {
+        if (!mStateListeners
+                .remove(new ClusterStateListenerRecord(/* executor= */ null, callback))) {
             return;
         }
-        if (mCallbacks.isEmpty()) {
+        if (mStateListeners.isEmpty()) {
             try {
-                mService.unregisterCallback(mBinderCallback);
+                mService.unregisterClusterStateListener(mClusterStateListenerBinderCallback);
             } catch (RemoteException ignored) {
                 // ignore for unregistering
             }
         }
     }
 
-    private static class IClusterHomeCallbackImpl extends IClusterHomeCallback.Stub {
+    /**
+     * Unregisters the callback.
+     */
+    @RequiresPermission(Car.PERMISSION_CAR_MONITOR_CLUSTER_NAVIGATION_STATE)
+    public void unregisterClusterNavigationStateListener(
+            @NonNull ClusterNavigationStateListener callback) {
+        Objects.requireNonNull(callback, "callback cannot be null");
+        if (!mNavigationStateListeners.remove(new ClusterNavigationStateListenerRecord(
+                /* executor= */ null, callback))) {
+            return;
+        }
+        if (mNavigationStateListeners.isEmpty()) {
+            try {
+                mService.unregisterClusterNavigationStateListener(
+                        mClusterNavigationStateListenerBinderCallback);
+            } catch (RemoteException ignored) {
+                // ignore for unregistering
+            }
+        }
+    }
+
+    private static class IClusterStateListenerImpl extends IClusterStateListener.Stub {
         private final WeakReference<ClusterHomeManager> mManager;
 
-        private IClusterHomeCallbackImpl(ClusterHomeManager manager) {
+        private IClusterStateListenerImpl(ClusterHomeManager manager) {
             mManager = new WeakReference<>(manager);
         }
 
@@ -164,19 +246,28 @@
         public void onClusterStateChanged(@NonNull ClusterState state, @Config int changes) {
             ClusterHomeManager manager = mManager.get();
             if (manager != null) {
-                for (CallbackRecord cb : manager.mCallbacks) {
+                for (ClusterStateListenerRecord cb : manager.mStateListeners) {
                     cb.mExecutor.execute(
-                            () -> cb.mCallback.onClusterStateChanged(state, changes));
+                            () -> cb.mListener.onClusterStateChanged(state, changes));
                 }
             }
         }
+    }
+
+    private static class IClusterNavigationStateListenerImpl extends
+            IClusterNavigationStateListener.Stub {
+        private final WeakReference<ClusterHomeManager> mManager;
+
+        private IClusterNavigationStateListenerImpl(ClusterHomeManager manager) {
+            mManager = new WeakReference<>(manager);
+        }
 
         @Override
         public void onNavigationStateChanged(@NonNull byte[] navigationState) {
             ClusterHomeManager manager = mManager.get();
             if (manager != null) {
-                for (CallbackRecord cb : manager.mCallbacks) {
-                    cb.mExecutor.execute(() -> cb.mCallback.onNavigationState(navigationState));
+                for (ClusterNavigationStateListenerRecord lr : manager.mNavigationStateListeners) {
+                    lr.mExecutor.execute(() -> lr.mListener.onNavigationState(navigationState));
                 }
             }
         }
@@ -267,6 +358,7 @@
 
     @Override
     protected void onCarDisconnected() {
-        mCallbacks.clear();
+        mStateListeners.clear();
+        mNavigationStateListeners.clear();
     }
 }
diff --git a/car-lib/src/android/car/cluster/IClusterHomeService.aidl b/car-lib/src/android/car/cluster/IClusterHomeService.aidl
index 7fa42af..c5c589b 100644
--- a/car-lib/src/android/car/cluster/IClusterHomeService.aidl
+++ b/car-lib/src/android/car/cluster/IClusterHomeService.aidl
@@ -17,7 +17,8 @@
 package android.car.cluster;
 
 import android.car.cluster.ClusterState;
-import android.car.cluster.IClusterHomeCallback;
+import android.car.cluster.IClusterStateListener;
+import android.car.cluster.IClusterNavigationStateListener;
 import android.content.Intent;
 import android.os.Bundle;
 
@@ -48,10 +49,16 @@
      * does not stop the activity but it will not be re-launched any more.
      */
     void stopFixedActivityMode() = 3;
-    /** Registers a callback */
-    void registerCallback(in IClusterHomeCallback callback) = 4;
-    /** Unregisters a callback */
-    void unregisterCallback(in IClusterHomeCallback callback) = 5;
+    // 4 removed. Do not use - void registerCallback(in IClusterHomeCallback callback) = 4;
+    // 5 removed. Do not use - void unregisterCallback(in IClusterHomeCallback callback) = 5;
     /** Returns the current {@code ClusterDisplayState}. */
     ClusterState getClusterState() = 6;
+    /** Registers a listener to listen for cluster state changes. */
+    void registerClusterStateListener(in IClusterStateListener listener) = 7;
+    /** Unregisters a cluster state listener. */
+    void unregisterClusterStateListener(in IClusterStateListener listener) = 8;
+    /** Registers a listener to lsiten for clustser navigation state changes. */
+    void registerClusterNavigationStateListener(in IClusterNavigationStateListener listener) = 9;
+    /** Unregisters a cluster navigation state listener. */
+    void unregisterClusterNavigationStateListener(in IClusterNavigationStateListener listener) = 10;
 }
diff --git a/car-lib/src/android/car/cluster/IClusterHomeCallback.aidl b/car-lib/src/android/car/cluster/IClusterNavigationStateListener.aidl
similarity index 77%
copy from car-lib/src/android/car/cluster/IClusterHomeCallback.aidl
copy to car-lib/src/android/car/cluster/IClusterNavigationStateListener.aidl
index 08c967b..470b891 100644
--- a/car-lib/src/android/car/cluster/IClusterHomeCallback.aidl
+++ b/car-lib/src/android/car/cluster/IClusterNavigationStateListener.aidl
@@ -19,12 +19,9 @@
 import android.car.cluster.ClusterState;
 
 /** @hide */
-oneway interface IClusterHomeCallback {
-    /**
-     * Called when ClusterOS changes the cluster display state, the geometry of cluster display,
-     * or the uiType.
-     */
-    void onClusterStateChanged(in ClusterState state, int changes) = 1;
+oneway interface IClusterNavigationStateListener {
+    // 1 removed. Do not use - void onClusterStateChanged(in ClusterState state, int changes) = 1;
+
     /** Called when the App who owns the navigation focus casts the new navigation state. */
     void onNavigationStateChanged(in byte[] navigationState) = 2;
 }
diff --git a/car-lib/src/android/car/cluster/IClusterHomeCallback.aidl b/car-lib/src/android/car/cluster/IClusterStateListener.aidl
similarity index 81%
rename from car-lib/src/android/car/cluster/IClusterHomeCallback.aidl
rename to car-lib/src/android/car/cluster/IClusterStateListener.aidl
index 08c967b..4fac990 100644
--- a/car-lib/src/android/car/cluster/IClusterHomeCallback.aidl
+++ b/car-lib/src/android/car/cluster/IClusterStateListener.aidl
@@ -19,12 +19,12 @@
 import android.car.cluster.ClusterState;
 
 /** @hide */
-oneway interface IClusterHomeCallback {
+oneway interface IClusterStateListener {
     /**
      * Called when ClusterOS changes the cluster display state, the geometry of cluster display,
      * or the uiType.
      */
     void onClusterStateChanged(in ClusterState state, int changes) = 1;
-    /** Called when the App who owns the navigation focus casts the new navigation state. */
-    void onNavigationStateChanged(in byte[] navigationState) = 2;
+
+    // 2 removed. Do not use - void onNavigationStateChanged(in byte[] navigationState) = 2;
 }
diff --git a/cpp/evs/apps/default/EvsStateControl.cpp b/cpp/evs/apps/default/EvsStateControl.cpp
index cb5faf1..e01d25a 100644
--- a/cpp/evs/apps/default/EvsStateControl.cpp
+++ b/cpp/evs/apps/default/EvsStateControl.cpp
@@ -119,8 +119,11 @@
 
 
 void EvsStateControl::terminateUpdateLoop() {
-    // Join a rendering thread
-    if (mRenderThread.joinable()) {
+    if (mRenderThread.get_id() == std::this_thread::get_id()) {
+        // We should not join by ourselves
+        mRenderThread.detach();
+    } else if (mRenderThread.joinable()) {
+        // Join a rendering thread
         mRenderThread.join();
     }
 }
@@ -148,6 +151,7 @@
     bool run = true;
     while (run) {
         // Process incoming commands
+        sp<IEvsDisplay> displayHandle;
         {
             std::lock_guard <std::mutex> lock(mLock);
             while (!mCommandQueue.empty()) {
@@ -165,6 +169,13 @@
                 }
                 mCommandQueue.pop();
             }
+
+            displayHandle = mDisplay.promote();
+        }
+
+        if (!displayHandle) {
+            LOG(ERROR) << "We've lost the display";
+            break;
         }
 
         // Review vehicle state and choose an appropriate renderer
@@ -177,7 +188,7 @@
         if (mCurrentRenderer) {
             // Get the output buffer we'll use to display the imagery
             BufferDesc_1_0 tgtBuffer = {};
-            mDisplay->getTargetBuffer([&tgtBuffer](const BufferDesc_1_0& buff) {
+            displayHandle->getTargetBuffer([&tgtBuffer](const BufferDesc_1_0& buff) {
                                           tgtBuffer = buff;
                                       }
             );
@@ -193,7 +204,7 @@
                 }
 
                 // Send the finished image back for display
-                mDisplay->returnTargetBufferForDisplay(tgtBuffer);
+                displayHandle->returnTargetBufferForDisplay(tgtBuffer);
 
                 if (!mFirstFrameIsDisplayed) {
                     mFirstFrameIsDisplayed = true;
@@ -361,9 +372,14 @@
     }
 
     // Now set the display state based on whether we have a video feed to show
+    sp<IEvsDisplay> displayHandle = mDisplay.promote();
+    if (!displayHandle) {
+        return false;
+    }
+
     if (mDesiredRenderer == nullptr) {
         LOG(DEBUG) << "Turning off the display";
-        mDisplay->setDisplayState(EvsDisplayState::NOT_VISIBLE);
+        displayHandle->setDisplayState(EvsDisplayState::NOT_VISIBLE);
     } else {
         mCurrentRenderer = std::move(mDesiredRenderer);
 
@@ -378,7 +394,8 @@
         // Activate the display
         LOG(DEBUG) << "EvsActivateDisplayTiming start time: "
                    << android::elapsedRealtime() << " ms.";
-        Return<EvsResult> result = mDisplay->setDisplayState(EvsDisplayState::VISIBLE_ON_NEXT_FRAME);
+        Return<EvsResult> result = displayHandle->setDisplayState(
+                EvsDisplayState::VISIBLE_ON_NEXT_FRAME);
         if (result != EvsResult::OK) {
             LOG(ERROR) << "setDisplayState returned an error "
                        << result.description();
diff --git a/cpp/evs/apps/default/EvsStateControl.h b/cpp/evs/apps/default/EvsStateControl.h
index d847195..c73a856 100644
--- a/cpp/evs/apps/default/EvsStateControl.h
+++ b/cpp/evs/apps/default/EvsStateControl.h
@@ -36,6 +36,7 @@
 using ::android::hardware::hidl_vec;
 using ::android::hardware::hidl_handle;
 using ::android::sp;
+using ::android::wp;
 using ::android::hardware::automotive::evs::V1_1::IEvsDisplay;
 using ::android::hardware::camera::device::V3_2::Stream;
 
@@ -90,7 +91,7 @@
 
     sp<IVehicle>                mVehicle;
     sp<IEvsEnumerator>          mEvs;
-    sp<IEvsDisplay>             mDisplay;
+    wp<IEvsDisplay>             mDisplay;
     const ConfigManager&        mConfig;
 
     VehiclePropValue            mGearValue;
diff --git a/cpp/evs/apps/default/evs_app.cpp b/cpp/evs/apps/default/evs_app.cpp
index a3062fd..5a9d1d9 100644
--- a/cpp/evs/apps/default/evs_app.cpp
+++ b/cpp/evs/apps/default/evs_app.cpp
@@ -50,12 +50,13 @@
 EvsStateControl *pStateController;
 
 void sigHandler(int sig) {
-    LOG(ERROR) << "evs_app is being terminated on receiving a signal " << sig;
+    LOG(WARNING) << "evs_app is being terminated on receiving a signal " << sig;
     if (pEvs != nullptr) {
         // Attempt to clean up the resources
         pStateController->postCommand({EvsStateControl::Op::EXIT, 0, 0}, true);
         pStateController->terminateUpdateLoop();
-        pEvs->closeDisplay(pDisplay);
+        pDisplay = nullptr;
+        pEvs = nullptr;
     }
 
     android::hardware::IPCThreadState::self()->stopProcess();
diff --git a/cpp/evs/sampleDriver/android.hardware.automotive.evs@1.1-sample.rc b/cpp/evs/sampleDriver/android.hardware.automotive.evs@1.1-sample.rc
index e7d7217..30709d4 100644
--- a/cpp/evs/sampleDriver/android.hardware.automotive.evs@1.1-sample.rc
+++ b/cpp/evs/sampleDriver/android.hardware.automotive.evs@1.1-sample.rc
@@ -3,5 +3,6 @@
     priority -20
     user graphics
     group automotive_evs camera
+    onrestart restart automotive_display
     onrestart restart evs_manager
     disabled # will not automatically start with its class; must be explictly started.
diff --git a/cpp/watchdog/server/src/WatchdogProcessService.cpp b/cpp/watchdog/server/src/WatchdogProcessService.cpp
index fca1174..891c6e2 100644
--- a/cpp/watchdog/server/src/WatchdogProcessService.cpp
+++ b/cpp/watchdog/server/src/WatchdogProcessService.cpp
@@ -21,7 +21,6 @@
 
 #include "WatchdogServiceHelper.h"
 
-#include <android-base/chrono_utils.h>
 #include <android-base/file.h>
 #include <android-base/macros.h>
 #include <android-base/properties.h>
@@ -50,6 +49,7 @@
 using ::android::sp;
 using ::android::String16;
 using ::android::base::Error;
+using ::android::base::GetIntProperty;
 using ::android::base::GetProperty;
 using ::android::base::ReadFileToString;
 using ::android::base::Result;
@@ -83,12 +83,14 @@
 const int32_t MSG_VHAL_WATCHDOG_ALIVE = static_cast<int>(TimeoutLength::TIMEOUT_NORMAL) + 1;
 const int32_t MSG_VHAL_HEALTH_CHECK = MSG_VHAL_WATCHDOG_ALIVE + 1;
 
-// TODO(b/193742550): Restore the timeout to 3s after configuration by vendors is added.
-// VHAL sends heart beat every 6s. Car watchdog checks if there is the latest heart beat from VHAL
-// with 1s marginal time.
-constexpr std::chrono::milliseconds kVhalHeartBeatIntervalMs = 6s;
-constexpr std::chrono::nanoseconds kVhalHealthCheckDelayNs = kVhalHeartBeatIntervalMs + 1s;
+// VHAL is supposed to send heart beat every 3s. Car watchdog checks if there is the latest heart
+// beat from VHAL within 3s, allowing 1s marginal time.
+// If {@code ro.carwatchdog.vhal_healthcheck.interval} is set, car watchdog checks VHAL health at
+// the given interval. The lower bound of the interval is 3s.
+constexpr int32_t kDefaultVhalCheckIntervalSec = 3;
+constexpr std::chrono::milliseconds kHealthCheckDelayMs = 1s;
 
+constexpr const char kPropertyVhalCheckInterval[] = "ro.carwatchdog.vhal_healthcheck.interval";
 constexpr const char kServiceName[] = "WatchdogProcessService";
 constexpr const char kVhalInterfaceName[] = "android.hardware.automotive.vehicle@2.0::IVehicle";
 
@@ -140,6 +142,10 @@
         mClients.insert(std::make_pair(timeout, std::vector<ClientInfo>()));
         mPingedClients.insert(std::make_pair(timeout, PingedClientMap()));
     }
+    int32_t vhalHealthCheckIntervalSec =
+            GetIntProperty(kPropertyVhalCheckInterval, kDefaultVhalCheckIntervalSec);
+    vhalHealthCheckIntervalSec = std::max(vhalHealthCheckIntervalSec, kDefaultVhalCheckIntervalSec);
+    mVhalHealthCheckWindowMs = std::chrono::seconds(vhalHealthCheckIntervalSec);
 }
 Result<void> WatchdogProcessService::registerWatchdogServiceHelper(
         const sp<IWatchdogServiceHelper>& helper) {
@@ -339,6 +345,9 @@
         }
     }
     WriteStringToFd(StringPrintf("%sStopped users: %s\n", indent, buffer.c_str()), fd);
+    WriteStringToFd(StringPrintf("%sVHAL health check interval: %lldms\n", indent,
+                                 mVhalHealthCheckWindowMs.count()),
+                    fd);
     return {};
 }
 
@@ -764,7 +773,8 @@
         ALOGW("Failed to subscribe to VHAL_HEARTBEAT. Checking VHAL health is disabled.");
         return;
     }
-    mHandlerLooper->sendMessageDelayed(kVhalHealthCheckDelayNs.count(), mMessageHandler,
+    std::chrono::nanoseconds intervalNs = mVhalHealthCheckWindowMs + kHealthCheckDelayMs;
+    mHandlerLooper->sendMessageDelayed(intervalNs.count(), mMessageHandler,
                                        Message(MSG_VHAL_HEALTH_CHECK));
 }
 
@@ -789,7 +799,8 @@
         terminateVhal();
         return;
     }
-    mHandlerLooper->sendMessageDelayed(kVhalHealthCheckDelayNs.count(), mMessageHandler,
+    std::chrono::nanoseconds intervalNs = mVhalHealthCheckWindowMs + kHealthCheckDelayMs;
+    mHandlerLooper->sendMessageDelayed(intervalNs.count(), mMessageHandler,
                                        Message(MSG_VHAL_HEALTH_CHECK));
 }
 
@@ -800,7 +811,7 @@
         Mutex::Autolock lock(mMutex);
         lastEventTime = mVhalHeartBeat.eventTime;
     }
-    if (currentUptime > lastEventTime + kVhalHeartBeatIntervalMs.count()) {
+    if (currentUptime > lastEventTime + mVhalHealthCheckWindowMs.count()) {
         ALOGW("VHAL failed to update heart beat within timeout. Terminating VHAL...");
         terminateVhal();
     }
diff --git a/cpp/watchdog/server/src/WatchdogProcessService.h b/cpp/watchdog/server/src/WatchdogProcessService.h
index 7fab6cb..46d3ef4 100644
--- a/cpp/watchdog/server/src/WatchdogProcessService.h
+++ b/cpp/watchdog/server/src/WatchdogProcessService.h
@@ -17,6 +17,7 @@
 #ifndef CPP_WATCHDOG_SERVER_SRC_WATCHDOGPROCESSSERVICE_H_
 #define CPP_WATCHDOG_SERVER_SRC_WATCHDOGPROCESSSERVICE_H_
 
+#include <android-base/chrono_utils.h>
 #include <android-base/result.h>
 #include <android/automotive/watchdog/ICarWatchdogClient.h>
 #include <android/automotive/watchdog/internal/ICarWatchdogMonitor.h>
@@ -244,6 +245,7 @@
             mNotSupportedVhalProperties;
     android::sp<PropertyChangeListener> mPropertyChangeListener;
     HeartBeat mVhalHeartBeat GUARDED_BY(mMutex);
+    std::chrono::milliseconds mVhalHealthCheckWindowMs;
     android::sp<IWatchdogServiceHelper> mWatchdogServiceHelper GUARDED_BY(mMutex);
 };
 
diff --git a/service/Android.bp b/service/Android.bp
index fe07310..58a8e7c 100644
--- a/service/Android.bp
+++ b/service/Android.bp
@@ -59,6 +59,7 @@
     "car-admin-ui-lib",
     "Slogf",
     "cartelemetry-protos",
+    "carwatchdog-protos",
 ]
 
 android_app {
diff --git a/service/AndroidManifest.xml b/service/AndroidManifest.xml
index ffe96c4..51fe718 100644
--- a/service/AndroidManifest.xml
+++ b/service/AndroidManifest.xml
@@ -465,7 +465,7 @@
          android:label="@string/car_permission_car_display_in_cluster"
          android:description="@string/car_permission_desc_car_display_in_cluster"/>
 
-    <!-- Allows an application to lunch applications in the instrument cluster.
+    <!-- Allows an application to launch applications in the instrument cluster.
          <p>Protection level: signature|privileged
     -->
     <permission android:name="android.car.permission.CAR_INSTRUMENT_CLUSTER_CONTROL"
@@ -473,6 +473,14 @@
          android:label="@string/car_permission_car_cluster_control"
          android:description="@string/car_permission_desc_car_cluster_control"/>
 
+    <!-- Allows an application to listen for navigation state changes in instrument cluster.
+     <p>Protection level: signature|privileged
+    -->
+    <permission android:name="android.car.permission.CAR_MONITOR_CLUSTER_NAVIGATION_STATE"
+        android:protectionLevel="signature|privileged"
+        android:label="@string/car_permission_car_cluster_control"
+        android:description="@string/car_permission_desc_car_cluster_control"/>
+
     <!-- Allows an application to communicate with a device in AOAP mode.
          <p>Protection level: signature|privileged
     -->
diff --git a/service/res/values-as/strings.xml b/service/res/values-as/strings.xml
index ca68c26..c484cd4 100644
--- a/service/res/values-as/strings.xml
+++ b/service/res/values-as/strings.xml
@@ -89,8 +89,8 @@
     <string name="car_permission_desc_vms_subscriber" msgid="7551009457847673620">"VMS বাৰ্তাৰ গ্ৰাহকভুক্তি কৰিব পাৰে"</string>
     <string name="car_permission_label_bind_vms_client" msgid="4889732900973280313">"VMS ক্লায়েণ্ট সেৱা"</string>
     <string name="car_permission_desc_bind_vms_client" msgid="4062835325264330564">"VMS ক্লায়েণ্টৰ সৈতে সংযুক্ত হ’ব পাৰে"</string>
-    <string name="car_permission_label_storage_monitoring" msgid="2327639346522530549">"সঞ্চয়াগাৰ নিৰীক্ষণ কৰিব"</string>
-    <string name="car_permission_desc_storage_monitoring" msgid="2075712271139671318">"সঞ্চয়াগাৰ ব্যৱহাৰৰ তথ্য নিৰীক্ষণ কৰিব"</string>
+    <string name="car_permission_label_storage_monitoring" msgid="2327639346522530549">"ফ্লেশ্ব ষ্ট’ৰেজ নিৰীক্ষণ কৰা"</string>
+    <string name="car_permission_desc_storage_monitoring" msgid="2075712271139671318">"ফ্লেশ্ব ষ্ট’ৰেজৰ ব্যৱহাৰ নিৰীক্ষণ কৰক"</string>
     <string name="car_permission_label_driving_state" msgid="7754624599537393650">"গাড়ী চালনাৰ স্থিতি সলনি হ’লে সেইয়া জানিব"</string>
     <string name="car_permission_desc_driving_state" msgid="2684025262811635737">"গাড়ী চালনাৰ স্থিতি সলনি হ’লে সেইয়া জানিব।"</string>
     <string name="car_permission_label_use_telemetry_service" msgid="948005838683758846">"গাড়ীৰ টেলিমেট্ৰী সেৱা ব্যৱহাৰ কৰক"</string>
diff --git a/service/src/com/android/car/cluster/ClusterHomeService.java b/service/src/com/android/car/cluster/ClusterHomeService.java
index 3af771a..7592d60 100644
--- a/service/src/com/android/car/cluster/ClusterHomeService.java
+++ b/service/src/com/android/car/cluster/ClusterHomeService.java
@@ -29,8 +29,9 @@
 import android.car.ICarOccupantZoneCallback;
 import android.car.cluster.ClusterHomeManager;
 import android.car.cluster.ClusterState;
-import android.car.cluster.IClusterHomeCallback;
 import android.car.cluster.IClusterHomeService;
+import android.car.cluster.IClusterNavigationStateListener;
+import android.car.cluster.IClusterStateListener;
 import android.car.cluster.navigation.NavigationState.NavigationStateProto;
 import android.car.navigation.CarNavigationInstrumentCluster;
 import android.content.ComponentName;
@@ -86,7 +87,10 @@
     private Intent mLastIntent;
     private int mLastIntentUserId = UserHandle.USER_SYSTEM;
 
-    private final RemoteCallbackList<IClusterHomeCallback> mClientCallbacks =
+    private final RemoteCallbackList<IClusterStateListener> mClientListeners =
+            new RemoteCallbackList<>();
+
+    private final RemoteCallbackList<IClusterNavigationStateListener> mClientNavigationListeners =
             new RemoteCallbackList<>();
 
     public ClusterHomeService(Context context, ClusterHalService clusterHalService,
@@ -173,7 +177,8 @@
         mOccupantZoneService.unregisterCallback(mOccupantZoneCallback);
         mClusterHalService.setCallback(null);
         mClusterNavigationService.setClusterServiceCallback(null);
-        mClientCallbacks.kill();
+        mClientListeners.kill();
+        mClientNavigationListeners.kill();
     }
 
     @Override
@@ -216,16 +221,16 @@
 
     private void sendDisplayState(int changes) {
         ClusterState state = createClusterState();
-        int n = mClientCallbacks.beginBroadcast();
+        int n = mClientListeners.beginBroadcast();
         for (int i = 0; i < n; i++) {
-            IClusterHomeCallback callback = mClientCallbacks.getBroadcastItem(i);
+            IClusterStateListener callback = mClientListeners.getBroadcastItem(i);
             try {
                 callback.onClusterStateChanged(state, changes);
             } catch (RemoteException ignores) {
                 // ignore
             }
         }
-        mClientCallbacks.finishBroadcast();
+        mClientListeners.finishBroadcast();
     }
 
     // ClusterNavigationServiceCallback starts
@@ -237,16 +242,17 @@
     }
 
     private void sendNavigationState(byte[] protoBytes) {
-        final int n = mClientCallbacks.beginBroadcast();
+        final int n = mClientNavigationListeners.beginBroadcast();
         for (int i = 0; i < n; i++) {
-            IClusterHomeCallback callback = mClientCallbacks.getBroadcastItem(i);
+            IClusterNavigationStateListener callback =
+                    mClientNavigationListeners.getBroadcastItem(i);
             try {
                 callback.onNavigationStateChanged(protoBytes);
             } catch (RemoteException ignores) {
                 // ignore
             }
         }
-        mClientCallbacks.finishBroadcast();
+        mClientNavigationListeners.finishBroadcast();
 
         if (!mClusterHalService.isNavigationStateSupported()) {
             Slogf.d(TAG, "No Cluster NavigationState HAL property");
@@ -323,19 +329,35 @@
     }
 
     @Override
-    public void registerCallback(IClusterHomeCallback callback) {
+    public void registerClusterStateListener(IClusterStateListener listener) {
         enforcePermission(Car.PERMISSION_CAR_INSTRUMENT_CLUSTER_CONTROL);
         if (!mServiceEnabled) throw new IllegalStateException("Service is not enabled");
 
-        mClientCallbacks.register(callback);
+        mClientListeners.register(listener);
     }
 
     @Override
-    public void unregisterCallback(IClusterHomeCallback callback) {
+    public void unregisterClusterStateListener(IClusterStateListener listener) {
         enforcePermission(Car.PERMISSION_CAR_INSTRUMENT_CLUSTER_CONTROL);
         if (!mServiceEnabled) throw new IllegalStateException("Service is not enabled");
 
-        mClientCallbacks.unregister(callback);
+        mClientListeners.unregister(listener);
+    }
+
+    @Override
+    public void registerClusterNavigationStateListener(IClusterNavigationStateListener listener) {
+        enforcePermission(Car.PERMISSION_CAR_MONITOR_CLUSTER_NAVIGATION_STATE);
+        if (!mServiceEnabled) throw new IllegalStateException("Service is not enabled");
+
+        mClientNavigationListeners.register(listener);
+    }
+
+    @Override
+    public void unregisterClusterNavigationStateListener(IClusterNavigationStateListener listener) {
+        enforcePermission(Car.PERMISSION_CAR_MONITOR_CLUSTER_NAVIGATION_STATE);
+        if (!mServiceEnabled) throw new IllegalStateException("Service is not enabled");
+
+        mClientNavigationListeners.unregister(listener);
     }
 
     @Override
diff --git a/service/src/com/android/car/watchdog/CarWatchdogService.java b/service/src/com/android/car/watchdog/CarWatchdogService.java
index 0d9b845..be888d3 100644
--- a/service/src/com/android/car/watchdog/CarWatchdogService.java
+++ b/service/src/com/android/car/watchdog/CarWatchdogService.java
@@ -115,12 +115,21 @@
             switch (action) {
                 case ACTION_GARAGE_MODE_ON:
                 case ACTION_GARAGE_MODE_OFF:
-                    handleGarageModeIntent(action.equals(ACTION_GARAGE_MODE_ON));
-                    break;
+                    int garageMode;
+                    synchronized (mLock) {
+                        garageMode = mCurrentGarageMode = action.equals(ACTION_GARAGE_MODE_ON)
+                                ? GarageMode.GARAGE_MODE_ON : GarageMode.GARAGE_MODE_OFF;
+                    }
+                    mWatchdogPerfHandler.onGarageModeChange(garageMode);
+                    if (garageMode == GarageMode.GARAGE_MODE_ON) {
+                        mWatchdogStorage.shrinkDatabase();
+                    }
+                    notifyGarageModeChange(garageMode);
+                    return;
                 case Intent.ACTION_USER_REMOVED:
                     UserHandle user = intent.getParcelableExtra(Intent.EXTRA_USER);
                     mWatchdogPerfHandler.deleteUser(user.getIdentifier());
-                    break;
+                    return;
             }
         }
     };
@@ -129,20 +138,18 @@
             new ICarPowerStateListener.Stub() {
         @Override
         public void onStateChanged(int state) {
-            int powerCycle;
-            switch (state) {
-                // SHUTDOWN_PREPARE covers suspend and shutdown.
-                case CarPowerStateListener.SHUTDOWN_PREPARE:
-                    powerCycle = PowerCycle.POWER_CYCLE_SHUTDOWN_PREPARE;
-                    break;
-                case CarPowerStateListener.SHUTDOWN_ENTER:
-                case CarPowerStateListener.SUSPEND_ENTER:
-                    powerCycle = PowerCycle.POWER_CYCLE_SHUTDOWN_ENTER;
+            CarPowerManagementService powerService =
+                    CarLocalServices.getService(CarPowerManagementService.class);
+            if (powerService == null) {
+                return;
+            }
+            int powerCycle = carPowerStateToPowerCycle(powerService.getPowerState());
+            switch (powerCycle) {
+                case PowerCycle.POWER_CYCLE_SHUTDOWN_ENTER:
                     mWatchdogPerfHandler.writeToDatabase();
                     break;
-                    // ON covers resume.
-                case CarPowerStateListener.ON:
-                    powerCycle = PowerCycle.POWER_CYCLE_RESUME;
+                // ON covers resume.
+                case PowerCycle.POWER_CYCLE_RESUME:
                     // There might be outdated & incorrect info. We should reset them before
                     // starting to do health check.
                     mWatchdogProcessHandler.prepareHealthCheck();
@@ -150,7 +157,7 @@
                 default:
                     return;
             }
-            notifyPowerCycleStateChange(powerCycle);
+            notifyPowerCycleChange(powerCycle);
         }
     };
 
@@ -178,6 +185,8 @@
     @GuardedBy("mLock")
     private boolean mIsConnected;
     @GuardedBy("mLock")
+    private @GarageMode int mCurrentGarageMode;
+    @GuardedBy("mLock")
     private boolean mIsDisplayEnabled;
 
     public CarWatchdogService(Context context) {
@@ -202,6 +211,7 @@
             }
             registerToDaemon();
         };
+        mCurrentGarageMode = GarageMode.GARAGE_MODE_OFF;
         mIsDisplayEnabled = true;
     }
 
@@ -235,9 +245,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();
     }
 
     /**
@@ -419,24 +434,34 @@
         mWatchdogPerfHandler.setRecurringOveruseThreshold(threshold);
     }
 
-    private void handleGarageModeIntent(boolean isOn) {
-        if (isOn) {
-            mWatchdogStorage.shrinkDatabase();
-        }
+    private void notifyAllUserStates() {
+        UserManager userManager = UserManager.get(mContext);
+        List<UserInfo> users = userManager.getUsers();
         try {
-            mCarWatchdogDaemonHelper.notifySystemStateChange(StateType.GARAGE_MODE,
-                    isOn ? GarageMode.GARAGE_MODE_ON : GarageMode.GARAGE_MODE_OFF,
-                    /* arg2= */ MISSING_ARG_VALUE);
+            // TODO(b/152780162): reduce the number of RPC calls(isUserRunning).
+            for (int i = 0; i < users.size(); ++i) {
+                UserInfo info = users.get(i);
+                int userState = userManager.isUserRunning(info.id)
+                        ? UserState.USER_STATE_STARTED
+                        : UserState.USER_STATE_STOPPED;
+                mCarWatchdogDaemonHelper.notifySystemStateChange(StateType.USER_STATE, info.id,
+                        userState);
+                mWatchdogProcessHandler.updateUserState(info.id,
+                        userState == UserState.USER_STATE_STOPPED);
+            }
             if (DEBUG) {
-                Slogf.d(TAG, "Notified car watchdog daemon of garage mode(%s)",
-                        isOn ? "ON" : "OFF");
+                Slogf.d(TAG, "Notified car watchdog daemon of user states");
             }
         } catch (RemoteException | RuntimeException e) {
-            Slogf.w(TAG, e, "Notifying garage mode state change failed");
+            Slogf.w(TAG, "Notifying latest user states failed: %s", e);
         }
     }
 
-    private void notifyPowerCycleStateChange(int powerCycle) {
+    private void notifyPowerCycleChange(@PowerCycle int powerCycle) {
+        if (powerCycle == PowerCycle.NUM_POWER_CYLES) {
+            Slogf.e(TAG, "Skipping notifying invalid power cycle (%d)", powerCycle);
+            return;
+        }
         try {
             mCarWatchdogDaemonHelper.notifySystemStateChange(
                     StateType.POWER_CYCLE, powerCycle, MISSING_ARG_VALUE);
@@ -444,7 +469,19 @@
                 Slogf.d(TAG, "Notified car watchdog daemon of power cycle(%d)", powerCycle);
             }
         } catch (RemoteException | RuntimeException e) {
-            Slogf.w(TAG, "Notifying power cycle state change failed: %s", e);
+            Slogf.w(TAG, e, "Notifying power cycle change to %d failed", powerCycle);
+        }
+    }
+
+    private void notifyGarageModeChange(@GarageMode int garageMode) {
+        try {
+            mCarWatchdogDaemonHelper.notifySystemStateChange(
+                    StateType.GARAGE_MODE, garageMode, MISSING_ARG_VALUE);
+            if (DEBUG) {
+                Slogf.d(TAG, "Notified car watchdog daemon of garage mode(%d)", garageMode);
+            }
+        } catch (RemoteException | RuntimeException e) {
+            Slogf.w(TAG, e, "Notifying garage mode change to %d failed", garageMode);
         }
     }
 
@@ -471,24 +508,27 @@
         } catch (RemoteException | RuntimeException e) {
             Slogf.w(TAG, "Cannot register to car watchdog daemon: %s", e);
         }
-        UserManager userManager = UserManager.get(mContext);
-        List<UserInfo> users = userManager.getUsers();
-        try {
-            // TODO(b/152780162): reduce the number of RPC calls(isUserRunning).
-            for (UserInfo info : users) {
-                int userState = userManager.isUserRunning(info.id)
-                        ? UserState.USER_STATE_STARTED : UserState.USER_STATE_STOPPED;
-                mCarWatchdogDaemonHelper.notifySystemStateChange(StateType.USER_STATE, info.id,
-                        userState);
-                if (userState == UserState.USER_STATE_STOPPED) {
-                    mWatchdogProcessHandler.updateUserState(info.id, /*isStopped=*/ true);
-                } else {
-                    mWatchdogProcessHandler.updateUserState(info.id, /*isStopped=*/ false);
-                }
+        notifyAllUserStates();
+        CarPowerManagementService powerService =
+                CarLocalServices.getService(CarPowerManagementService.class);
+        if (powerService != null) {
+            int powerState = powerService.getPowerState();
+            int powerCycle = carPowerStateToPowerCycle(powerState);
+            if (powerCycle != PowerCycle.NUM_POWER_CYLES) {
+                notifyPowerCycleChange(powerCycle);
+            } else {
+                Slogf.i(TAG, "Skipping notifying %d power state", powerState);
             }
-        } catch (RemoteException | RuntimeException e) {
-            Slogf.w(TAG, "Notifying system state change failed: %s", e);
         }
+        int garageMode;
+        synchronized (mLock) {
+            // To avoid race condition, fetch {@link mCurrentGarageMode} just before
+            // the {@link notifyGarageModeChange} call. For instance, if {@code mCurrentGarageMode}
+            // changes before the above {@link notifyPowerCycleChange} call returns,
+            // the {@link garageMode}'s value will be out of date.
+            garageMode = mCurrentGarageMode;
+        }
+        notifyGarageModeChange(garageMode);
     }
 
     private void unregisterFromDaemon() {
@@ -572,6 +612,30 @@
         mContext.registerReceiverForAllUsers(mBroadcastReceiver, filter, null, null);
     }
 
+    private static @PowerCycle int carPowerStateToPowerCycle(int powerState) {
+        switch (powerState) {
+            // SHUTDOWN_PREPARE covers suspend and shutdown.
+            case CarPowerStateListener.SHUTDOWN_PREPARE:
+                return PowerCycle.POWER_CYCLE_SHUTDOWN_PREPARE;
+            case CarPowerStateListener.SHUTDOWN_ENTER:
+            case CarPowerStateListener.SUSPEND_ENTER:
+                return PowerCycle.POWER_CYCLE_SHUTDOWN_ENTER;
+            // ON covers resume.
+            case CarPowerStateListener.ON:
+                return PowerCycle.POWER_CYCLE_RESUME;
+        }
+        return PowerCycle.NUM_POWER_CYLES;
+    }
+
+    private static String toGarageModeString(@GarageMode int garageMode) {
+        switch (garageMode) {
+            case GarageMode.GARAGE_MODE_OFF:
+                return "GARAGE_MODE_OFF";
+            case GarageMode.GARAGE_MODE_ON:
+                return "GARAGE_MODE_ON";
+        }
+        return "INVALID";
+    }
 
     private static final class ICarWatchdogServiceForSystemImpl
             extends ICarWatchdogServiceForSystem.Stub {
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/PackageInfoHandler.java b/service/src/com/android/car/watchdog/PackageInfoHandler.java
index e3f4bb8..359b515 100644
--- a/service/src/com/android/car/watchdog/PackageInfoHandler.java
+++ b/service/src/com/android/car/watchdog/PackageInfoHandler.java
@@ -27,6 +27,7 @@
 import android.os.Process;
 import android.os.UserHandle;
 import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.IntArray;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
@@ -54,6 +55,9 @@
     @GuardedBy("mLock")
     private final ArrayMap<String, String> mGenericPackageNameByPackage = new ArrayMap<>();
     @GuardedBy("mLock")
+    private final SparseArray<ArraySet<String>> mGenericPackageNamesByComponentType =
+            new SparseArray<>();
+    @GuardedBy("mLock")
     private List<String> mVendorPackagePrefixes = new ArrayList<>();
 
     public PackageInfoHandler(PackageManager packageManager) {
@@ -63,7 +67,7 @@
     /**
      * Returns the generic package names for the given UIDs.
      *
-     * Some UIDs may not have names. This may occur when a UID is being removed and the
+     * <p>Some UIDs may not have names. This may occur when a UID is being removed and the
      * internal data structures are not up-to-date. The caller should handle it.
      */
     public SparseArray<String> getNamesForUids(int[] uids) {
@@ -105,7 +109,7 @@
     /**
      * Returns the generic package name for the user package.
      *
-     * Returns null when no generic package name is found.
+     * <p>Returns null when no generic package name is found.
      */
     @Nullable
     public String getNameForUserPackage(String packageName, int userId) {
@@ -155,19 +159,12 @@
     /**
      * Returns the internal package infos for the given UIDs.
      *
-     * Some UIDs may not have package infos. This may occur when a UID is being removed and the
+     * <p>Some UIDs may not have package infos. This may occur when a UID is being removed and the
      * internal data structures are not up-to-date. The caller should handle it.
      */
     public List<PackageInfo> getPackageInfosForUids(int[] uids,
             List<String> vendorPackagePrefixes) {
-        synchronized (mLock) {
-            /*
-             * Vendor package prefixes don't change frequently because it changes only when the
-             * vendor configuration is updated. Thus caching this locally during this call should
-             * keep the cache up-to-date because the daemon issues this call frequently.
-             */
-            mVendorPackagePrefixes = vendorPackagePrefixes;
-        }
+        setVendorPackagePrefixes(vendorPackagePrefixes);
         SparseArray<String> genericPackageNameByUid = getNamesForUids(uids);
         ArrayList<PackageInfo> packageInfos = new ArrayList<>(genericPackageNameByUid.size());
         for (int i = 0; i < genericPackageNameByUid.size(); ++i) {
@@ -177,6 +174,37 @@
         return packageInfos;
     }
 
+    /** Returns component type for the given uid and package name. */
+    public @ComponentType int getComponentType(int uid, String genericPackageName) {
+        synchronized (mLock) {
+            for (int i = 0; i < mGenericPackageNamesByComponentType.size(); ++i) {
+                if (mGenericPackageNamesByComponentType.valueAt(i).contains(genericPackageName)) {
+                    return mGenericPackageNamesByComponentType.keyAt(i);
+                }
+            }
+        }
+        int componentType = ComponentType.UNKNOWN;
+        if (genericPackageName.startsWith(SHARED_PACKAGE_PREFIX)) {
+            synchronized (mLock) {
+                if (!mPackagesBySharedUid.contains(uid)) {
+                    populateSharedPackagesLocked(uid, genericPackageName);
+                }
+                List<String> packages = mPackagesBySharedUid.get(uid);
+                if (packages != null) {
+                    componentType = getSharedComponentTypeInternal(
+                            UserHandle.getUserHandleForUid(uid), packages, genericPackageName);
+                }
+            }
+        } else {
+            componentType = getUserPackageComponentType(
+                    UserHandle.getUserHandleForUid(uid), genericPackageName);
+        }
+        if (componentType != ComponentType.UNKNOWN) {
+            cachePackageComponentType(genericPackageName, componentType);
+        }
+        return componentType;
+    }
+
     @GuardedBy("mLock")
     private void populateSharedPackagesLocked(int uid, String genericPackageName) {
         String[] packages = mPackageManager.getPackagesForUid(uid);
@@ -192,38 +220,20 @@
         packageInfo.packageIdentifier.uid = uid;
         packageInfo.packageIdentifier.name = genericPackageName;
         packageInfo.sharedUidPackages = new ArrayList<>();
-        packageInfo.componentType = ComponentType.UNKNOWN;
+        packageInfo.componentType = getComponentType(uid, genericPackageName);
         /* Application category type mapping is handled on the daemon side. */
         packageInfo.appCategoryType = ApplicationCategoryType.OTHERS;
-        int userId = UserHandle.getUserId(uid);
         int appId = UserHandle.getAppId(uid);
         packageInfo.uidType = appId >= Process.FIRST_APPLICATION_UID ? UidType.APPLICATION :
                 UidType.NATIVE;
 
         if (genericPackageName.startsWith(SHARED_PACKAGE_PREFIX)) {
-            List<String> packages = null;
             synchronized (mLock) {
-                packages = mPackagesBySharedUid.get(uid);
-                if (packages == null) {
-                    return packageInfo;
+                List<String> packages = mPackagesBySharedUid.get(uid);
+                if (packages != null) {
+                    packageInfo.sharedUidPackages = new ArrayList<>(packages);
                 }
             }
-            List<ApplicationInfo> applicationInfos = new ArrayList<>();
-            for (int i = 0; i < packages.size(); ++i) {
-                try {
-                    applicationInfos.add(mPackageManager.getApplicationInfoAsUser(packages.get(i),
-                            /* flags= */ 0, userId));
-                } catch (PackageManager.NameNotFoundException e) {
-                    Slogf.e(TAG, "Package '%s' not found for user %d: %s", packages.get(i), userId,
-                            e);
-                }
-            }
-            packageInfo.componentType = getSharedComponentType(
-                    applicationInfos, genericPackageName);
-            packageInfo.sharedUidPackages = new ArrayList<>(packages);
-        } else {
-            packageInfo.componentType = getUserPackageComponentType(
-                    userId, genericPackageName);
         }
         return packageInfo;
     }
@@ -235,8 +245,8 @@
      * mapped to different component types. Thus map the shared UID to the most restrictive
      * component type.
      */
-    public int getSharedComponentType(List<ApplicationInfo> applicationInfos,
-            String genericPackageName) {
+    public @ComponentType int getSharedComponentType(
+            List<ApplicationInfo> applicationInfos, String genericPackageName) {
         SparseBooleanArray seenComponents = new SparseBooleanArray();
         for (int i = 0; i < applicationInfos.size(); ++i) {
             int type = getComponentType(applicationInfos.get(i));
@@ -259,17 +269,34 @@
         return ComponentType.UNKNOWN;
     }
 
-    private int getUserPackageComponentType(int userId, String packageName) {
+    private @ComponentType int getUserPackageComponentType(
+            UserHandle userHandle, String packageName) {
         try {
-            ApplicationInfo info = mPackageManager.getApplicationInfoAsUser(packageName,
-                    /* flags= */ 0, userId);
+            ApplicationInfo info = mPackageManager.getApplicationInfoAsUser(
+                    packageName, /* flags= */ 0, userHandle);
             return getComponentType(info);
         } catch (PackageManager.NameNotFoundException e) {
-            Slogf.e(TAG, "Package '%s' not found for user %d: %s", packageName, userId, e);
+            Slogf.e(TAG, e, "Package '%s' not found for user %d", packageName,
+                    userHandle.getIdentifier());
         }
         return ComponentType.UNKNOWN;
     }
 
+    private @ComponentType int getSharedComponentTypeInternal(
+            UserHandle userHandle, List<String> packages, String genericPackageName) {
+        List<ApplicationInfo> applicationInfos = new ArrayList<>();
+        for (int i = 0; i < packages.size(); ++i) {
+            try {
+                applicationInfos.add(mPackageManager.getApplicationInfoAsUser(
+                        packages.get(i), /* flags= */ 0, userHandle));
+            } catch (PackageManager.NameNotFoundException e) {
+                Slogf.w(TAG, "Package '%s' not found for user %d", packages.get(i),
+                        userHandle.getIdentifier());
+            }
+        }
+        return getSharedComponentType(applicationInfos, genericPackageName);
+    }
+
     /** Returns the component type for the given application info. */
     public int getComponentType(ApplicationInfo applicationInfo) {
         if ((applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_OEM) != 0
@@ -295,7 +322,26 @@
 
     void setVendorPackagePrefixes(List<String> vendorPackagePrefixes) {
         synchronized (mLock) {
+            if (mVendorPackagePrefixes.equals(vendorPackagePrefixes)) {
+                return;
+            }
             mVendorPackagePrefixes = vendorPackagePrefixes;
+            // When the vendor package prefixes change due to config update, the component types
+            // for these packages also change. Ergo, clear the component type cache, so the
+            // component types can be inferred again.
+            mGenericPackageNamesByComponentType.clear();
+        }
+    }
+
+    private void cachePackageComponentType(String genericPackageName,
+            @ComponentType int componentType) {
+        synchronized (mLock) {
+            ArraySet<String> packages = mGenericPackageNamesByComponentType.get(componentType);
+            if (packages == null) {
+                packages = new ArraySet<>();
+            }
+            packages.add(genericPackageName);
+            mGenericPackageNamesByComponentType.append(componentType, packages);
         }
     }
 }
diff --git a/service/src/com/android/car/watchdog/WatchdogPerfHandler.java b/service/src/com/android/car/watchdog/WatchdogPerfHandler.java
index 557addb..50a4d24 100644
--- a/service/src/com/android/car/watchdog/WatchdogPerfHandler.java
+++ b/service/src/com/android/car/watchdog/WatchdogPerfHandler.java
@@ -30,7 +30,15 @@
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED;
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER;
+import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
 
+import static com.android.car.CarStatsLog.CAR_WATCHDOG_IO_OVERUSE_STATS_REPORTED;
+import static com.android.car.CarStatsLog.CAR_WATCHDOG_KILL_STATS_REPORTED;
+import static com.android.car.CarStatsLog.CAR_WATCHDOG_KILL_STATS_REPORTED__KILL_REASON__KILLED_ON_IO_OVERUSE;
+import static com.android.car.CarStatsLog.CAR_WATCHDOG_KILL_STATS_REPORTED__SYSTEM_STATE__GARAGE_MODE;
+import static com.android.car.CarStatsLog.CAR_WATCHDOG_KILL_STATS_REPORTED__SYSTEM_STATE__USER_INTERACTION_MODE;
+import static com.android.car.CarStatsLog.CAR_WATCHDOG_KILL_STATS_REPORTED__SYSTEM_STATE__USER_NO_INTERACTION_MODE;
+import static com.android.car.CarStatsLog.CAR_WATCHDOG_KILL_STATS_REPORTED__UID_STATE__UNKNOWN_UID_STATE;
 import static com.android.car.watchdog.CarWatchdogService.DEBUG;
 import static com.android.car.watchdog.CarWatchdogService.SYSTEM_INSTANCE;
 import static com.android.car.watchdog.CarWatchdogService.TAG;
@@ -45,6 +53,7 @@
 import android.automotive.watchdog.ResourceType;
 import android.automotive.watchdog.internal.ApplicationCategoryType;
 import android.automotive.watchdog.internal.ComponentType;
+import android.automotive.watchdog.internal.GarageMode;
 import android.automotive.watchdog.internal.IoUsageStats;
 import android.automotive.watchdog.internal.PackageIdentifier;
 import android.automotive.watchdog.internal.PackageIoOveruseStats;
@@ -88,6 +97,7 @@
 
 import com.android.car.CarLocalServices;
 import com.android.car.CarServiceUtils;
+import com.android.car.CarStatsLog;
 import com.android.car.CarUxRestrictionsManagerService;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
@@ -151,6 +161,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 +174,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<>();
@@ -192,6 +199,8 @@
     @GuardedBy("mLock")
     private CarUxRestrictions mCurrentUxRestrictions;
     @GuardedBy("mLock")
+    private @GarageMode int mCurrentGarageMode;
+    @GuardedBy("mLock")
     private TimeSourceInterface mTimeSource;
     @GuardedBy("mLock")
     private long mOveruseHandlingDelayMills;
@@ -218,9 +227,11 @@
         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;
+        mCurrentGarageMode = GarageMode.GARAGE_MODE_OFF;
         mRecurringOveruseThreshold = RECURRING_OVERUSE_THRESHOLD;
     }
 
@@ -254,6 +265,7 @@
         /*
          * TODO(b/183436216): Implement this method.
          */
+        mOveruseConfigurationCache.dump(writer);
     }
 
     /** Retries any pending requests on re-connecting to the daemon */
@@ -297,6 +309,15 @@
         }
     }
 
+    /** Handles garage mode change. */
+    public void onGarageModeChange(@GarageMode int garageMode) {
+        synchronized (mLock) {
+            mCurrentGarageMode = garageMode;
+            mCurrentUxState = UX_STATE_NO_INTERACTION;
+            performOveruseHandlingLocked();
+        }
+    }
+
     /** Returns resource overuse stats for the calling package. */
     @NonNull
     public ResourceOveruseStats getResourceOveruseStats(
@@ -557,7 +578,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 +604,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));
@@ -664,6 +687,7 @@
             uids[i] = packageIoOveruseStats.get(i).uid;
         }
         SparseArray<String> genericPackageNamesByUid = mPackageInfoHandler.getNamesForUids(uids);
+        ArraySet<String> overusingUserPackageKeys = new ArraySet<>();
         synchronized (mLock) {
             checkAndHandleDateChangeLocked();
             for (int i = 0; i < packageIoOveruseStats.size(); ++i) {
@@ -688,6 +712,7 @@
                 if (!usage.ioUsage.exceedsThreshold()) {
                     continue;
                 }
+                overusingUserPackageKeys.add(usage.getUniqueId());
                 PackageResourceOveruseAction overuseAction = new PackageResourceOveruseAction();
                 overuseAction.packageIdentifier = new PackageIdentifier();
                 overuseAction.packageIdentifier.name = genericPackageName;
@@ -716,6 +741,9 @@
                     }}, mOveruseHandlingDelayMills);
             }
         }
+        if (!overusingUserPackageKeys.isEmpty()) {
+            pushIoOveruseMetrics(overusingUserPackageKeys);
+        }
         if (DEBUG) {
             Slogf.d(TAG, "Processed latest I/O overuse stats");
         }
@@ -723,13 +751,33 @@
 
     /** Resets the resource overuse settings and stats for the given generic package names. */
     public void resetResourceOveruseStats(Set<String> genericPackageNames) {
+        IPackageManager packageManager = ActivityThread.getPackageManager();
         synchronized (mLock) {
             for (int i = 0; i < mUsageByUserPackage.size(); ++i) {
                 PackageResourceUsage usage = mUsageByUserPackage.valueAt(i);
-                if (genericPackageNames.contains(usage.genericPackageName)) {
-                    usage.resetStats();
-                    usage.verifyAndSetKillableState(/* isKillable= */ true);
-                    mWatchdogStorage.deleteUserPackage(usage.userId, usage.genericPackageName);
+                if (!genericPackageNames.contains(usage.genericPackageName)) {
+                    continue;
+                }
+                usage.resetStats();
+                usage.verifyAndSetKillableState(/* isKillable= */ true);
+                mWatchdogStorage.deleteUserPackage(usage.userId, usage.genericPackageName);
+                mActionableUserPackages.remove(usage.getUniqueId());
+                Slogf.i(TAG,
+                        "Reset resource overuse settings and stats for user '%d' package '%s'",
+                        usage.userId, usage.genericPackageName);
+                try {
+                    if (packageManager.getApplicationEnabledSetting(usage.genericPackageName,
+                            usage.userId) != COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED) {
+                        continue;
+                    }
+                    packageManager.setApplicationEnabledSetting(usage.genericPackageName,
+                            COMPONENT_ENABLED_STATE_ENABLED, /* flags= */ 0, usage.userId,
+                            mContext.getPackageName());
+                    Slogf.i(TAG, "Enabled user '%d' package '%s'", usage.userId,
+                            usage.genericPackageName);
+                } catch (RemoteException | IllegalArgumentException e) {
+                    Slogf.e(TAG, e, "Failed to verify and enable user %d, package '%s'",
+                            usage.userId, usage.genericPackageName);
                 }
             }
         }
@@ -806,25 +854,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 +1274,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);
@@ -1304,6 +1297,7 @@
             return;
         }
         IPackageManager packageManager = ActivityThread.getPackageManager();
+        ArraySet<String> killedUserPackageKeys = new ArraySet<>();
         for (int i = 0; i < mActionableUserPackages.size(); ++i) {
             PackageResourceUsage usage =
                     mUsageByUserPackage.get(mActionableUserPackages.valueAt(i));
@@ -1317,6 +1311,7 @@
             if (killableState != KILLABLE_STATE_YES) {
                 continue;
             }
+            killedUserPackageKeys.add(usage.getUniqueId());
             PackageResourceOveruseAction overuseAction = new PackageResourceOveruseAction();
             overuseAction.packageIdentifier = new PackageIdentifier();
             overuseAction.packageIdentifier.name = usage.genericPackageName;
@@ -1360,6 +1355,7 @@
                 mOveruseActionsByUserPackage.add(overuseAction);
             }
         }
+        pushIoOveruseKillMetrics(killedUserPackageKeys);
         if (!mOveruseActionsByUserPackage.isEmpty()) {
             mMainHandler.post(this::notifyActionsTakenOnOveruse);
         }
@@ -1383,6 +1379,78 @@
         mUserNotifiablePackages.clear();
     }
 
+    private void pushIoOveruseMetrics(ArraySet<String> userPackageKeys) {
+        SparseArray<AtomsProto.CarWatchdogIoOveruseStats> statsByUid = new SparseArray<>();
+        synchronized (mLock) {
+            for (int i = 0; i < userPackageKeys.size(); ++i) {
+                String key = userPackageKeys.valueAt(i);
+                PackageResourceUsage usage = mUsageByUserPackage.get(key);
+                if (usage == null) {
+                    Slogf.w(TAG, "Missing usage stats for user package key %s", key);
+                    continue;
+                }
+                statsByUid.put(usage.getUid(), constructCarWatchdogIoOveruseStatsLocked(usage));
+            }
+        }
+        for (int i = 0; i < statsByUid.size(); ++i) {
+            CarStatsLog.write(CAR_WATCHDOG_IO_OVERUSE_STATS_REPORTED, statsByUid.keyAt(i),
+                    statsByUid.valueAt(i).toByteArray());
+        }
+    }
+
+    private void pushIoOveruseKillMetrics(ArraySet<String> userPackageKeys) {
+        int systemState;
+        SparseArray<AtomsProto.CarWatchdogIoOveruseStats> statsByUid = new SparseArray<>();
+        synchronized (mLock) {
+            systemState = inferSystemStateLocked();
+            for (int i = 0; i < userPackageKeys.size(); ++i) {
+                String key = userPackageKeys.valueAt(i);
+                PackageResourceUsage usage = mUsageByUserPackage.get(key);
+                if (usage == null) {
+                    Slogf.w(TAG, "Missing usage stats for user package key %s", key);
+                    continue;
+                }
+                statsByUid.put(usage.getUid(), constructCarWatchdogIoOveruseStatsLocked(usage));
+            }
+        }
+        for (int i = 0; i < statsByUid.size(); ++i) {
+            // TODO(b/200598815): After watchdog can classify foreground vs background apps,
+            //  report the correct uid state.
+            CarStatsLog.write(CAR_WATCHDOG_KILL_STATS_REPORTED, statsByUid.keyAt(i),
+                    CAR_WATCHDOG_KILL_STATS_REPORTED__UID_STATE__UNKNOWN_UID_STATE,
+                    systemState,
+                    CAR_WATCHDOG_KILL_STATS_REPORTED__KILL_REASON__KILLED_ON_IO_OVERUSE,
+                    /* arg5= */ null, statsByUid.valueAt(i).toByteArray());
+        }
+    }
+
+    @GuardedBy("mLock")
+    private int inferSystemStateLocked() {
+        if (mCurrentGarageMode == GarageMode.GARAGE_MODE_ON) {
+            return CAR_WATCHDOG_KILL_STATS_REPORTED__SYSTEM_STATE__GARAGE_MODE;
+        }
+        return mCurrentUxState == UX_STATE_NO_INTERACTION
+                ? CAR_WATCHDOG_KILL_STATS_REPORTED__SYSTEM_STATE__USER_NO_INTERACTION_MODE
+                : CAR_WATCHDOG_KILL_STATS_REPORTED__SYSTEM_STATE__USER_INTERACTION_MODE;
+    }
+
+    @GuardedBy("mLock")
+    private AtomsProto.CarWatchdogIoOveruseStats constructCarWatchdogIoOveruseStatsLocked(
+            PackageResourceUsage usage) {
+        @ComponentType int componentType = mPackageInfoHandler.getComponentType(
+                usage.getUid(), usage.genericPackageName);
+        android.automotive.watchdog.PerStateBytes threshold =
+                mOveruseConfigurationCache.fetchThreshold(usage.genericPackageName, componentType);
+        android.automotive.watchdog.PerStateBytes writtenBytes =
+                usage.ioUsage.getInternalIoOveruseStats().writtenBytes;
+        return constructCarWatchdogIoOveruseStats(
+                AtomsProto.CarWatchdogIoOveruseStats.Period.DAILY,
+                constructCarWatchdogPerStateBytes(threshold.foregroundBytes,
+                        threshold.backgroundBytes, threshold.garageModeBytes),
+                constructCarWatchdogPerStateBytes(writtenBytes.foregroundBytes,
+                        writtenBytes.backgroundBytes, writtenBytes.garageModeBytes));
+    }
+
     /** Notify daemon about the actions take on resource overuse */
     private void notifyActionsTakenOnOveruse() {
         List<PackageResourceOveruseAction> actions;
@@ -1747,6 +1815,27 @@
         }
     }
 
+    @VisibleForTesting
+    static AtomsProto.CarWatchdogIoOveruseStats constructCarWatchdogIoOveruseStats(
+            AtomsProto.CarWatchdogIoOveruseStats.Period period,
+            AtomsProto.CarWatchdogPerStateBytes threshold,
+            AtomsProto.CarWatchdogPerStateBytes writtenBytes) {
+        // TODO(b/184310189): Report uptime once daemon pushes it to CarService.
+        return AtomsProto.CarWatchdogIoOveruseStats.newBuilder()
+                .setPeriod(period)
+                .setThreshold(threshold)
+                .setWrittenBytes(writtenBytes).build();
+    }
+
+    @VisibleForTesting
+    static AtomsProto.CarWatchdogPerStateBytes constructCarWatchdogPerStateBytes(
+            long foregroundBytes, long backgroundBytes, long garageModeBytes) {
+        return AtomsProto.CarWatchdogPerStateBytes.newBuilder()
+                .setForegroundBytes(foregroundBytes)
+                .setBackgroundBytes(backgroundBytes)
+                .setGarageModeBytes(garageModeBytes).build();
+    }
+
     private final class PackageResourceUsage {
         public final String genericPackageName;
         public @UserIdInt final int userId;
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/service/src/com/android/car/watchdog/proto/Android.bp b/service/src/com/android/car/watchdog/proto/Android.bp
new file mode 100644
index 0000000..6faa912
--- /dev/null
+++ b/service/src/com/android/car/watchdog/proto/Android.bp
@@ -0,0 +1,33 @@
+// 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_library {
+    name: "carwatchdog-protos",
+    proto: {
+        type: "lite",
+    },
+    sdk_version: "module_current",
+    min_sdk_version: "31",
+    apex_available: [
+        "//apex_available:platform",
+        "com.android.car.framework",
+    ],
+    srcs: [
+        "atoms.proto",
+    ],
+}
diff --git a/service/src/com/android/car/watchdog/proto/atoms.proto b/service/src/com/android/car/watchdog/proto/atoms.proto
new file mode 100644
index 0000000..95f6210
--- /dev/null
+++ b/service/src/com/android/car/watchdog/proto/atoms.proto
@@ -0,0 +1,160 @@
+/*
+ * 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.
+ */
+
+// Partial clone of frameworks/proto_logging/stats/atoms.proto. CarWatchdogService only uses small
+// number of atoms.
+
+syntax = "proto2";
+
+package android.car.watchdog.statsd;
+option java_package = "com.android.car.watchdog";
+option java_outer_classname = "AtomsProto";
+
+/**
+ * Logs the current state of an application/process before it is killed.
+ *
+ * Keep in sync with proto file at frameworks/proto_logging/stats/atoms.proto
+ */
+message CarWatchdogKillStatsReported {
+  // Linux process uid for the package.
+  optional int32 uid = 1;
+
+  // State of the uid when it was killed.
+  enum UidState {
+    UNKNOWN_UID_STATE = 0;
+    BACKGROUND_MODE = 1;
+    FOREGROUND_MODE = 2;
+  }
+  optional UidState uid_state = 2;
+
+  // System state indicating whether the system was in normal mode or garage mode.
+  enum SystemState {
+    UNKNOWN_SYSTEM_STATE = 0;
+    USER_INTERACTION_MODE = 1;
+    USER_NO_INTERACTION_MODE = 2;
+    GARAGE_MODE = 3;
+  }
+  optional SystemState system_state = 3;
+
+  // Reason for killing the application.
+  // Keep in sync with proto file at packages/services/Car/cpp/watchdog/proto
+  enum KillReason {
+    UNKNOWN_KILL_REASON = 0;
+    KILLED_ON_ANR = 1;
+    KILLED_ON_IO_OVERUSE = 2;
+    KILLED_ON_MEMORY_OVERUSE = 3;
+  }
+  optional KillReason kill_reason = 4;
+
+  // Stats of the processes owned by the application when the application was killed.
+  // The process stack traces are not collected when the application was killed due to IO_OVERUSE.
+  optional CarWatchdogProcessStats process_stats = 5;
+
+  // The application's I/O overuse stats logged only when the kill reason is KILLED_ON_IO_OVERUSE.
+  optional CarWatchdogIoOveruseStats io_overuse_stats = 6;
+}
+
+/**
+ * Logs the I/O overuse stats for an application on detecting I/O overuse.
+ *
+ * Keep in sync with proto file at frameworks/proto_logging/stats/atoms.proto
+ */
+message CarWatchdogIoOveruseStatsReported {
+  // Linux process uid for the package.
+  optional int32 uid = 1;
+
+  // The application's I/O overuse stats.
+  optional CarWatchdogIoOveruseStats io_overuse_stats = 2;
+}
+
+/**
+ * Logs I/O overuse stats for a package.
+ *
+ * Keep in sync with proto file at frameworks/proto_logging/stats/atoms.proto
+ */
+message CarWatchdogIoOveruseStats {
+  enum Period {
+    UNKNOWN_PERIOD = 0;
+    DAILY = 1;
+    WEEKLY = 2;
+  }
+
+  // Threshold and usage stats period.
+  optional Period period = 1;
+
+  // Threshold in-terms of write bytes defined for the package.
+  optional CarWatchdogPerStateBytes threshold = 2;
+
+  // Number of write bytes in each state for the specified period.
+  optional CarWatchdogPerStateBytes written_bytes = 3;
+
+  // Application or service uptime during the aforemetioned period.
+  optional uint64 uptime_millis = 4;
+};
+
+/**
+ * Logs bytes attributed to each application and system states.
+ *
+ * Keep in sync with proto file at frameworks/proto_logging/stats/atoms.proto
+ */
+message CarWatchdogPerStateBytes {
+  // Number of bytes attributed to the application foreground.
+  optional int64 foreground_bytes = 1;
+
+  // Number of bytes attributed to the application background.
+  optional int64 background_bytes = 2;
+
+  // Number of bytes attributed to the garage mode.
+  optional int64 garage_mode_bytes = 3;
+}
+
+/**
+ * Logs each CarWatchdogProcessStat in CarWatchdogProcessStats.
+ *
+ * Keep in sync with proto file at frameworks/proto_logging/stats/atoms.proto
+ */
+message CarWatchdogProcessStats {
+  // Records the stats of the processes owned by an application.
+  repeated CarWatchdogProcessStat process_stat = 1;
+}
+
+/**
+ * Logs a process's stats.
+ *
+ * Keep in sync with proto file at frameworks/proto_logging/stats/atoms.proto
+ */
+message CarWatchdogProcessStat {
+  // Command name of the process.
+  optional string process_name = 1;
+
+  // Process uptime.
+  optional uint64 uptime_millis = 2;
+
+  // Number of major page faults caused by the process and its children.
+  optional uint64 major_page_faults = 3;
+
+  // Peak virtual memory size in kb.
+  optional uint64 vm_peak_kb = 4;
+
+  // Virtual memory size in kb.
+  optional uint64 vm_size_kb = 5;
+
+  // Peak resident set size (high water mark) in kb.
+  optional uint64 vm_hwm_kb = 6;
+
+  // Resident set size in kb.
+  optional uint64 vm_rss_kb = 7;
+}
diff --git a/tests/BugReportApp/res/values-bn/strings.xml b/tests/BugReportApp/res/values-bn/strings.xml
index ae550c9..07200af 100644
--- a/tests/BugReportApp/res/values-bn/strings.xml
+++ b/tests/BugReportApp/res/values-bn/strings.xml
@@ -31,8 +31,7 @@
     <string name="bugreport_dialog_title" msgid="3315160684205929910">"কথা বলুন এবং সমস্যার বিষয়ে বিবরণ দিন"</string>
     <string name="bugreport_dialog_add_audio_to_existing" msgid="4958460267276935700">"সমস্যার বিষয়ে রিপোর্ট করার জন্য %s-এ অডিও মেসেজ করুন"</string>
     <string name="bugreport_dialog_recording_finished" msgid="3982335902169398758">"রেকর্ড করা হয়ে গেছে"</string>
-    <!-- no translation found for bugreport_dialog_in_progress_title (1663500052146177338) -->
-    <skip />
+    <string name="bugreport_dialog_in_progress_title" msgid="1663500052146177338">"সমস্যা সংক্রান্ত রিপোর্ট তৈরি করা হচ্ছে"</string>
     <string name="bugreport_dialog_in_progress_title_finished" msgid="1610236990020413471">"সমস্যার বিষয়ে রিপোর্ট সংগ্রহ করা হয়েছে"</string>
     <string name="bugreport_add_audio_button_text" msgid="8606400151705699144">"অডিও মেসেজ যোগ করুন"</string>
     <string name="bugreport_add_audio_upload_button_text" msgid="3830917832551764694">"অডিও যোগ করুন এবং আপলোড করুন"</string>
@@ -40,16 +39,12 @@
     <string name="bugreport_upload_button_text" msgid="4136749466634820848">"আপলোড করুন"</string>
     <string name="bugreport_upload_gcs_button_text" msgid="5844929656507607424">"GCS-এ আপলোড করুন"</string>
     <string name="toast_permissions_denied" msgid="7054832711916992770">"অনুমতির অনুমোদন দিন"</string>
-    <!-- no translation found for toast_bug_report_in_progress (5218530088025955746) -->
-    <skip />
-    <!-- no translation found for toast_bug_report_started (891404618481185195) -->
-    <skip />
+    <string name="toast_bug_report_in_progress" msgid="5218530088025955746">"সমস্যা সংক্রান্ত রিপোর্ট তৈরি করা হচ্ছে"</string>
+    <string name="toast_bug_report_started" msgid="891404618481185195">"সমস্যা সংক্রান্ত রিপোর্ট তৈরি করার প্রক্রিয়া শুরু করা হয়েছে"</string>
     <string name="toast_status_failed" msgid="6365384202315043395">"সমস্যার বিষয়ে রিপোর্ট করা যায়নি"</string>
     <string name="toast_status_screencap_failed" msgid="2187083897594745149">"স্ক্রিন ক্যাপচার করা যায়নি"</string>
-    <!-- no translation found for toast_status_dump_state_failed (3496460783060512078) -->
-    <skip />
+    <string name="toast_status_dump_state_failed" msgid="3496460783060512078">"Dumpstate লোড করা যায়নি"</string>
     <string name="notification_bugreport_in_progress" msgid="8486454116357963238">"সমস্যার বিষয়ে রিপোর্ট প্রগ্রেসে রয়েছে"</string>
-    <!-- no translation found for notification_bugreport_finished_title (1188447311929693472) -->
-    <skip />
+    <string name="notification_bugreport_finished_title" msgid="1188447311929693472">"সমস্যা সংক্রান্ত রিপোর্ট সংগ্রহ করা হয়েছে"</string>
     <string name="notification_bugreport_channel_name" msgid="776902295433824255">"সমস্যার বিষয়ে রিপোর্টের স্ট্যাটাসের চ্যানেল"</string>
 </resources>
diff --git a/tests/BugReportApp/res/values-eu/strings.xml b/tests/BugReportApp/res/values-eu/strings.xml
index aab592b..284f4e5 100644
--- a/tests/BugReportApp/res/values-eu/strings.xml
+++ b/tests/BugReportApp/res/values-eu/strings.xml
@@ -31,8 +31,7 @@
     <string name="bugreport_dialog_title" msgid="3315160684205929910">"Azaldu arazoa ozen"</string>
     <string name="bugreport_dialog_add_audio_to_existing" msgid="4958460267276935700">"Akatsen txostenerako audio-mezua (%s)"</string>
     <string name="bugreport_dialog_recording_finished" msgid="3982335902169398758">"Amaitu da grabatzen"</string>
-    <!-- no translation found for bugreport_dialog_in_progress_title (1663500052146177338) -->
-    <skip />
+    <string name="bugreport_dialog_in_progress_title" msgid="1663500052146177338">"Akatsen txostena sortzen"</string>
     <string name="bugreport_dialog_in_progress_title_finished" msgid="1610236990020413471">"Bildu da akatsen txosten bat"</string>
     <string name="bugreport_add_audio_button_text" msgid="8606400151705699144">"Gehitu audioa"</string>
     <string name="bugreport_add_audio_upload_button_text" msgid="3830917832551764694">"Gehitu eta kargatu audioa"</string>
@@ -40,16 +39,12 @@
     <string name="bugreport_upload_button_text" msgid="4136749466634820848">"Kargatu"</string>
     <string name="bugreport_upload_gcs_button_text" msgid="5844929656507607424">"Kargatu Google-ren hodeiko biltegira"</string>
     <string name="toast_permissions_denied" msgid="7054832711916992770">"Eman baimenak"</string>
-    <!-- no translation found for toast_bug_report_in_progress (5218530088025955746) -->
-    <skip />
-    <!-- no translation found for toast_bug_report_started (891404618481185195) -->
-    <skip />
+    <string name="toast_bug_report_in_progress" msgid="5218530088025955746">"Akatsen txostena sortzen"</string>
+    <string name="toast_bug_report_started" msgid="891404618481185195">"Akatsen txostena sortzen hasi da"</string>
     <string name="toast_status_failed" msgid="6365384202315043395">"Ezin izan da sortu akatsen txostena"</string>
     <string name="toast_status_screencap_failed" msgid="2187083897594745149">"Ezin izan da egin pantaila-argazkia"</string>
-    <!-- no translation found for toast_status_dump_state_failed (3496460783060512078) -->
-    <skip />
+    <string name="toast_status_dump_state_failed" msgid="3496460783060512078">"Dumpstate-k huts egin du"</string>
     <string name="notification_bugreport_in_progress" msgid="8486454116357963238">"Akatsen txostena sortzen"</string>
-    <!-- no translation found for notification_bugreport_finished_title (1188447311929693472) -->
-    <skip />
+    <string name="notification_bugreport_finished_title" msgid="1188447311929693472">"Sortu da akatsen txostena"</string>
     <string name="notification_bugreport_channel_name" msgid="776902295433824255">"Akatsen txostenaren egoeraren kanala"</string>
 </resources>
diff --git a/tests/BugReportApp/res/values-hi/strings.xml b/tests/BugReportApp/res/values-hi/strings.xml
index 5e2a7a3..66421ae 100644
--- a/tests/BugReportApp/res/values-hi/strings.xml
+++ b/tests/BugReportApp/res/values-hi/strings.xml
@@ -31,8 +31,7 @@
     <string name="bugreport_dialog_title" msgid="3315160684205929910">"बोलें और समस्या बताएं"</string>
     <string name="bugreport_dialog_add_audio_to_existing" msgid="4958460267276935700">"गड़बड़ी की रिपोर्ट का ऑडियो मैसेज इस %s पर उपलब्ध है"</string>
     <string name="bugreport_dialog_recording_finished" msgid="3982335902169398758">"रिकॉर्डिंग पूरी हुई"</string>
-    <!-- no translation found for bugreport_dialog_in_progress_title (1663500052146177338) -->
-    <skip />
+    <string name="bugreport_dialog_in_progress_title" msgid="1663500052146177338">"गड़बड़ी की रिपोर्ट बनाई जा रही है"</string>
     <string name="bugreport_dialog_in_progress_title_finished" msgid="1610236990020413471">"गड़बड़ी की रिपोर्ट इकट्ठा की गई"</string>
     <string name="bugreport_add_audio_button_text" msgid="8606400151705699144">"ऑडियो मैसेज जोड़ें"</string>
     <string name="bugreport_add_audio_upload_button_text" msgid="3830917832551764694">"ऑडियो मैसेज जोड़ें और अपलोड करें"</string>
@@ -40,16 +39,12 @@
     <string name="bugreport_upload_button_text" msgid="4136749466634820848">"अपलोड करें"</string>
     <string name="bugreport_upload_gcs_button_text" msgid="5844929656507607424">"GCS (जीसीएस) पर अपलोड करें"</string>
     <string name="toast_permissions_denied" msgid="7054832711916992770">"कृपया अनुमति दें"</string>
-    <!-- no translation found for toast_bug_report_in_progress (5218530088025955746) -->
-    <skip />
-    <!-- no translation found for toast_bug_report_started (891404618481185195) -->
-    <skip />
+    <string name="toast_bug_report_in_progress" msgid="5218530088025955746">"गड़बड़ी की रिपोर्ट बनाई जा रही है"</string>
+    <string name="toast_bug_report_started" msgid="891404618481185195">"गड़बड़ी की रिपोर्ट बनाने की प्रक्रिया शुरू की गई"</string>
     <string name="toast_status_failed" msgid="6365384202315043395">"गड़बड़ी की रिपोर्ट नहीं बन पाई"</string>
     <string name="toast_status_screencap_failed" msgid="2187083897594745149">"स्क्रीन कैप्चर नहीं की जा सकी"</string>
-    <!-- no translation found for toast_status_dump_state_failed (3496460783060512078) -->
-    <skip />
+    <string name="toast_status_dump_state_failed" msgid="3496460783060512078">"Dumpstate लोड नहीं की जा सकी"</string>
     <string name="notification_bugreport_in_progress" msgid="8486454116357963238">"गड़बड़ी की रिपोर्ट बनाई जा रही है"</string>
-    <!-- no translation found for notification_bugreport_finished_title (1188447311929693472) -->
-    <skip />
+    <string name="notification_bugreport_finished_title" msgid="1188447311929693472">"गड़बड़ी की रिपोर्ट को इकट्ठा किया गया"</string>
     <string name="notification_bugreport_channel_name" msgid="776902295433824255">"गड़बड़ी की रिपोर्ट की स्थिति का चैनल"</string>
 </resources>
diff --git a/tests/BugReportApp/res/values-kk/strings.xml b/tests/BugReportApp/res/values-kk/strings.xml
index 5a5eb17..01c4209 100644
--- a/tests/BugReportApp/res/values-kk/strings.xml
+++ b/tests/BugReportApp/res/values-kk/strings.xml
@@ -31,8 +31,7 @@
     <string name="bugreport_dialog_title" msgid="3315160684205929910">"Қатені ауызша сипаттап беріңіз"</string>
     <string name="bugreport_dialog_add_audio_to_existing" msgid="4958460267276935700">"Қате туралы есептің аудиохабары (%s)"</string>
     <string name="bugreport_dialog_recording_finished" msgid="3982335902169398758">"Жазу аяқталды."</string>
-    <!-- no translation found for bugreport_dialog_in_progress_title (1663500052146177338) -->
-    <skip />
+    <string name="bugreport_dialog_in_progress_title" msgid="1663500052146177338">"Қате туралы есеп алынуда"</string>
     <string name="bugreport_dialog_in_progress_title_finished" msgid="1610236990020413471">"Қате туралы есеп алынды"</string>
     <string name="bugreport_add_audio_button_text" msgid="8606400151705699144">"Аудиохабар енгізу"</string>
     <string name="bugreport_add_audio_upload_button_text" msgid="3830917832551764694">"Аудиохабар енгізу және жүктеп салу"</string>
@@ -40,16 +39,12 @@
     <string name="bugreport_upload_button_text" msgid="4136749466634820848">"Жүктеп салу"</string>
     <string name="bugreport_upload_gcs_button_text" msgid="5844929656507607424">"GCS серверіне жүктеп салу"</string>
     <string name="toast_permissions_denied" msgid="7054832711916992770">"Рұқсаттар беріңіз."</string>
-    <!-- no translation found for toast_bug_report_in_progress (5218530088025955746) -->
-    <skip />
-    <!-- no translation found for toast_bug_report_started (891404618481185195) -->
-    <skip />
+    <string name="toast_bug_report_in_progress" msgid="5218530088025955746">"Қате туралы есеп алынуда"</string>
+    <string name="toast_bug_report_started" msgid="891404618481185195">"Қате туралы есепті алу басталды"</string>
     <string name="toast_status_failed" msgid="6365384202315043395">"Қате туралы есеп алынбады."</string>
     <string name="toast_status_screencap_failed" msgid="2187083897594745149">"Скриншот түсірілмеді."</string>
-    <!-- no translation found for toast_status_dump_state_failed (3496460783060512078) -->
-    <skip />
+    <string name="toast_status_dump_state_failed" msgid="3496460783060512078">"Dumpstate қызметінің қатесі"</string>
     <string name="notification_bugreport_in_progress" msgid="8486454116357963238">"Қате туралы есеп алынуда"</string>
-    <!-- no translation found for notification_bugreport_finished_title (1188447311929693472) -->
-    <skip />
+    <string name="notification_bugreport_finished_title" msgid="1188447311929693472">"Қате туралы есеп алынды"</string>
     <string name="notification_bugreport_channel_name" msgid="776902295433824255">"Қате туралы есеп арнасы"</string>
 </resources>
diff --git a/tests/BugReportApp/res/values-pa/strings.xml b/tests/BugReportApp/res/values-pa/strings.xml
index 1787ad5..ea06fcd 100644
--- a/tests/BugReportApp/res/values-pa/strings.xml
+++ b/tests/BugReportApp/res/values-pa/strings.xml
@@ -31,8 +31,7 @@
     <string name="bugreport_dialog_title" msgid="3315160684205929910">"ਬੋਲ ਕੇ ਸਮੱਸਿਆ ਦਾ ਵਰਣਨ ਕਰੋ"</string>
     <string name="bugreport_dialog_add_audio_to_existing" msgid="4958460267276935700">"%s ’ਤੇ ਬੱਗ ਰਿਪੋਰਟ ਲਈ ਆਡੀਓ ਸੁਨੇਹਾ"</string>
     <string name="bugreport_dialog_recording_finished" msgid="3982335902169398758">"ਰਿਕਾਰਡਿੰਗ ਪੂਰੀ ਹੋ ਗਈ"</string>
-    <!-- no translation found for bugreport_dialog_in_progress_title (1663500052146177338) -->
-    <skip />
+    <string name="bugreport_dialog_in_progress_title" msgid="1663500052146177338">"ਬੱਗ ਰਿਪੋਰਟ ਪ੍ਰਕਿਰਿਆ-ਅਧੀਨ ਹੈ"</string>
     <string name="bugreport_dialog_in_progress_title_finished" msgid="1610236990020413471">"ਬੱਗ ਰਿਪੋਰਟ ਇਕੱਤਰ ਕੀਤੀ ਜਾ ਚੁੱਕੀ ਹੈ"</string>
     <string name="bugreport_add_audio_button_text" msgid="8606400151705699144">"ਆਡੀਓ ਸ਼ਾਮਲ ਕਰੋ"</string>
     <string name="bugreport_add_audio_upload_button_text" msgid="3830917832551764694">"ਆਡੀਓ ਸ਼ਾਮਲ ਕਰਕੇ ਅੱਪਲੋਡ ਕਰੋ"</string>
@@ -40,16 +39,12 @@
     <string name="bugreport_upload_button_text" msgid="4136749466634820848">"ਅੱਪਲੋਡ ਕਰੋ"</string>
     <string name="bugreport_upload_gcs_button_text" msgid="5844929656507607424">"GCS \'ਤੇ ਅੱਪਲੋਡ ਕਰੋ"</string>
     <string name="toast_permissions_denied" msgid="7054832711916992770">"ਕਿਰਪਾ ਕਰਕੇ ਇਜਾਜ਼ਤਾਂ ਦਿਓ"</string>
-    <!-- no translation found for toast_bug_report_in_progress (5218530088025955746) -->
-    <skip />
-    <!-- no translation found for toast_bug_report_started (891404618481185195) -->
-    <skip />
+    <string name="toast_bug_report_in_progress" msgid="5218530088025955746">"ਬੱਗ ਰਿਪੋਰਟ ਪ੍ਰਕਿਰਿਆ-ਅਧੀਨ ਹੈ"</string>
+    <string name="toast_bug_report_started" msgid="891404618481185195">"ਬੱਗ ਰਿਪੋਰਟ ਸ਼ੁਰੂ ਕੀਤੀ ਗਈ"</string>
     <string name="toast_status_failed" msgid="6365384202315043395">"ਬੱਗ ਰਿਪੋਰਟ ਇਕੱਤਰ ਕਰਨਾ ਅਸਫਲ ਰਿਹਾ"</string>
     <string name="toast_status_screencap_failed" msgid="2187083897594745149">"ਸਕ੍ਰੀਨ ਨੂੰ ਕੈਪਚਰ ਕਰਨਾ ਅਸਫਲ ਰਿਹਾ"</string>
-    <!-- no translation found for toast_status_dump_state_failed (3496460783060512078) -->
-    <skip />
+    <string name="toast_status_dump_state_failed" msgid="3496460783060512078">"Dumpstate ਅਸਫਲ ਰਿਹਾ"</string>
     <string name="notification_bugreport_in_progress" msgid="8486454116357963238">"ਬੱਗ ਰਿਪੋਰਟ ਇਕੱਤਰ ਕਰਨਾ ਜਾਰੀ ਹੈ"</string>
-    <!-- no translation found for notification_bugreport_finished_title (1188447311929693472) -->
-    <skip />
+    <string name="notification_bugreport_finished_title" msgid="1188447311929693472">"ਬੱਗ ਰਿਪੋਰਟ ਇਕੱਤਰ ਕੀਤੀ ਗਈ"</string>
     <string name="notification_bugreport_channel_name" msgid="776902295433824255">"ਬੱਗ ਰਿਪੋਰਟ ਦੀ ਸਥਿਤੀ ਸੰਬੰਧੀ ਚੈਨਲ"</string>
 </resources>
diff --git a/tests/BugReportApp/res/values-sl/strings.xml b/tests/BugReportApp/res/values-sl/strings.xml
index 27f650b..84db60b 100644
--- a/tests/BugReportApp/res/values-sl/strings.xml
+++ b/tests/BugReportApp/res/values-sl/strings.xml
@@ -31,8 +31,7 @@
     <string name="bugreport_dialog_title" msgid="3315160684205929910">"Govorite in opišite težavo"</string>
     <string name="bugreport_dialog_add_audio_to_existing" msgid="4958460267276935700">"Zvočno sporočilo za poročilo o napakah pri %s"</string>
     <string name="bugreport_dialog_recording_finished" msgid="3982335902169398758">"Snemanje je dokončano"</string>
-    <!-- no translation found for bugreport_dialog_in_progress_title (1663500052146177338) -->
-    <skip />
+    <string name="bugreport_dialog_in_progress_title" msgid="1663500052146177338">"Poročilo o napakah se ustvarja."</string>
     <string name="bugreport_dialog_in_progress_title_finished" msgid="1610236990020413471">"Poročilo o napakah je zbrano"</string>
     <string name="bugreport_add_audio_button_text" msgid="8606400151705699144">"Dodaj zvok"</string>
     <string name="bugreport_add_audio_upload_button_text" msgid="3830917832551764694">"Dodaj zvok in naloži"</string>
@@ -40,16 +39,12 @@
     <string name="bugreport_upload_button_text" msgid="4136749466634820848">"Naloži"</string>
     <string name="bugreport_upload_gcs_button_text" msgid="5844929656507607424">"Naloži v GCS"</string>
     <string name="toast_permissions_denied" msgid="7054832711916992770">"Odobrite dovoljenja"</string>
-    <!-- no translation found for toast_bug_report_in_progress (5218530088025955746) -->
-    <skip />
-    <!-- no translation found for toast_bug_report_started (891404618481185195) -->
-    <skip />
+    <string name="toast_bug_report_in_progress" msgid="5218530088025955746">"Poročilo o napakah se ustvarja."</string>
+    <string name="toast_bug_report_started" msgid="891404618481185195">"Poročilo o napakah je začelo nastajati."</string>
     <string name="toast_status_failed" msgid="6365384202315043395">"Ustvarjanje poročila o napakah ni uspelo"</string>
     <string name="toast_status_screencap_failed" msgid="2187083897594745149">"Zajemanje zaslona ni uspelo"</string>
-    <!-- no translation found for toast_status_dump_state_failed (3496460783060512078) -->
-    <skip />
+    <string name="toast_status_dump_state_failed" msgid="3496460783060512078">"Storitev Dumpstate ni bila uspešna."</string>
     <string name="notification_bugreport_in_progress" msgid="8486454116357963238">"Poročilo o napakah se ustvarja"</string>
-    <!-- no translation found for notification_bugreport_finished_title (1188447311929693472) -->
-    <skip />
+    <string name="notification_bugreport_finished_title" msgid="1188447311929693472">"Poročilo o napakah je zbrano."</string>
     <string name="notification_bugreport_channel_name" msgid="776902295433824255">"Kanala stanja poročila o napakah"</string>
 </resources>
diff --git a/tests/BugReportApp/res/values-vi/strings.xml b/tests/BugReportApp/res/values-vi/strings.xml
index 006d7dd..61599dd 100644
--- a/tests/BugReportApp/res/values-vi/strings.xml
+++ b/tests/BugReportApp/res/values-vi/strings.xml
@@ -31,8 +31,7 @@
     <string name="bugreport_dialog_title" msgid="3315160684205929910">"Hãy nói để mô tả vấn đề"</string>
     <string name="bugreport_dialog_add_audio_to_existing" msgid="4958460267276935700">"Thông báo bằng âm thanh cho báo cáo lỗi lúc %s"</string>
     <string name="bugreport_dialog_recording_finished" msgid="3982335902169398758">"Đã ghi xong"</string>
-    <!-- no translation found for bugreport_dialog_in_progress_title (1663500052146177338) -->
-    <skip />
+    <string name="bugreport_dialog_in_progress_title" msgid="1663500052146177338">"Đang báo cáo lỗi"</string>
     <string name="bugreport_dialog_in_progress_title_finished" msgid="1610236990020413471">"Đã thu thập báo cáo lỗi"</string>
     <string name="bugreport_add_audio_button_text" msgid="8606400151705699144">"Thêm âm thanh"</string>
     <string name="bugreport_add_audio_upload_button_text" msgid="3830917832551764694">"Thêm âm thanh và tải lên"</string>
@@ -40,16 +39,12 @@
     <string name="bugreport_upload_button_text" msgid="4136749466634820848">"Tải lên"</string>
     <string name="bugreport_upload_gcs_button_text" msgid="5844929656507607424">"Tải lên GCS"</string>
     <string name="toast_permissions_denied" msgid="7054832711916992770">"Vui lòng cấp quyền"</string>
-    <!-- no translation found for toast_bug_report_in_progress (5218530088025955746) -->
-    <skip />
-    <!-- no translation found for toast_bug_report_started (891404618481185195) -->
-    <skip />
+    <string name="toast_bug_report_in_progress" msgid="5218530088025955746">"Đang báo cáo lỗi"</string>
+    <string name="toast_bug_report_started" msgid="891404618481185195">"Đã bắt đầu báo cáo lỗi"</string>
     <string name="toast_status_failed" msgid="6365384202315043395">"Không báo cáo được lỗi"</string>
     <string name="toast_status_screencap_failed" msgid="2187083897594745149">"Không chụp được màn hình"</string>
-    <!-- no translation found for toast_status_dump_state_failed (3496460783060512078) -->
-    <skip />
+    <string name="toast_status_dump_state_failed" msgid="3496460783060512078">"Không thực hiện được dịch vụ dumpstate"</string>
     <string name="notification_bugreport_in_progress" msgid="8486454116357963238">"Đang báo cáo lỗi"</string>
-    <!-- no translation found for notification_bugreport_finished_title (1188447311929693472) -->
-    <skip />
+    <string name="notification_bugreport_finished_title" msgid="1188447311929693472">"Đã thu thập báo cáo lỗi"</string>
     <string name="notification_bugreport_channel_name" msgid="776902295433824255">"Kênh trạng thái báo cáo lỗi"</string>
 </resources>
diff --git a/tests/BugReportApp/res/values-zu/strings.xml b/tests/BugReportApp/res/values-zu/strings.xml
index daffef7..06a7f78 100644
--- a/tests/BugReportApp/res/values-zu/strings.xml
+++ b/tests/BugReportApp/res/values-zu/strings.xml
@@ -31,8 +31,7 @@
     <string name="bugreport_dialog_title" msgid="3315160684205929910">"Khuluma uphinde uchaze inkinga"</string>
     <string name="bugreport_dialog_add_audio_to_existing" msgid="4958460267276935700">"Umlayezo womsindo wokubika kwesiphazamiso ku-%s"</string>
     <string name="bugreport_dialog_recording_finished" msgid="3982335902169398758">"Ukurekhoda kuqediwe"</string>
-    <!-- no translation found for bugreport_dialog_in_progress_title (1663500052146177338) -->
-    <skip />
+    <string name="bugreport_dialog_in_progress_title" msgid="1663500052146177338">"Umbiko wesiphazamisi uyaqhubeka"</string>
     <string name="bugreport_dialog_in_progress_title_finished" msgid="1610236990020413471">"Kuqoqwe umbiko wesiphazamisi"</string>
     <string name="bugreport_add_audio_button_text" msgid="8606400151705699144">"Engeza umsindo"</string>
     <string name="bugreport_add_audio_upload_button_text" msgid="3830917832551764694">"Engeza umsindo nokulayisha"</string>
@@ -40,16 +39,12 @@
     <string name="bugreport_upload_button_text" msgid="4136749466634820848">"Layisha"</string>
     <string name="bugreport_upload_gcs_button_text" msgid="5844929656507607424">"Layisha ku-GCS"</string>
     <string name="toast_permissions_denied" msgid="7054832711916992770">"Sicela unikeze izimvume"</string>
-    <!-- no translation found for toast_bug_report_in_progress (5218530088025955746) -->
-    <skip />
-    <!-- no translation found for toast_bug_report_started (891404618481185195) -->
-    <skip />
+    <string name="toast_bug_report_in_progress" msgid="5218530088025955746">"Umbiko wesiphazamisi uyaqhubeka"</string>
+    <string name="toast_bug_report_started" msgid="891404618481185195">"Umbiko wesiphazamisi uqalile"</string>
     <string name="toast_status_failed" msgid="6365384202315043395">"Umbiko wesiphazamisi uhlulekile"</string>
     <string name="toast_status_screencap_failed" msgid="2187083897594745149">"Ukuthathwa kwesikrini kuhlulekile"</string>
-    <!-- no translation found for toast_status_dump_state_failed (3496460783060512078) -->
-    <skip />
+    <string name="toast_status_dump_state_failed" msgid="3496460783060512078">"Isimo sokulahla sihlulekile"</string>
     <string name="notification_bugreport_in_progress" msgid="8486454116357963238">"Umbiko wesiphazamisi uyaqhubeka"</string>
-    <!-- no translation found for notification_bugreport_finished_title (1188447311929693472) -->
-    <skip />
+    <string name="notification_bugreport_finished_title" msgid="1188447311929693472">"Umbiko wesiphazamisi uqoqiwe"</string>
     <string name="notification_bugreport_channel_name" msgid="776902295433824255">"Isiteshi sesimo sombiko wesiphazamisi"</string>
 </resources>
diff --git a/tests/DiagnosticTools/res/values-nl/strings.xml b/tests/DiagnosticTools/res/values-nl/strings.xml
index a808fd2..6366836 100644
--- a/tests/DiagnosticTools/res/values-nl/strings.xml
+++ b/tests/DiagnosticTools/res/values-nl/strings.xml
@@ -17,5 +17,5 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="display_freeze_frame_info" msgid="1425573367248263107">"Informatie over freeze frame weergeven"</string>
+    <string name="display_freeze_frame_info" msgid="1425573367248263107">"Informatie over freeze frame tonen"</string>
 </resources>
diff --git a/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml b/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml
index 3b709b5..9b22570 100644
--- a/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml
+++ b/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml
@@ -26,6 +26,7 @@
     <!-- use for CarServiceTest -->
     <uses-permission android:name="android.car.permission.CAR_DRIVING_STATE"/>
     <uses-permission android:name="android.car.permission.CAR_ENERGY"/>
+    <uses-permission android:name="android.car.permission.CAR_MONITOR_CLUSTER_NAVIGATION_STATE" />
     <!-- use for AndroidCarApiTest -->
     <uses-permission android:name="android.car.permission.CAR_INFO"/>
     <!-- use for AndroidCarApiTest -->
diff --git a/tests/carservice_test/src/com/android/car/cluster/ClusterHomeManagerTest.java b/tests/carservice_test/src/com/android/car/cluster/ClusterHomeManagerTest.java
index b56c841..40ed29b 100644
--- a/tests/carservice_test/src/com/android/car/cluster/ClusterHomeManagerTest.java
+++ b/tests/carservice_test/src/com/android/car/cluster/ClusterHomeManagerTest.java
@@ -86,9 +86,12 @@
     private ClusterHomeManager mClusterHomeManager;
     private final ClusterPropertyHandler mPropertyHandler = new ClusterPropertyHandler();
     private final CountDownLatch mPropertySetReady = new CountDownLatch(1);
-    private final CountDownLatch mCallbackReceived = new CountDownLatch(1);
+    private final CountDownLatch mClusterStateListenerCalled = new CountDownLatch(1);
+    private final CountDownLatch mClusterNavigationStateListenerCalled = new CountDownLatch(1);
 
-    private ClusterHomeCallbackImpl mClusterHomeCallback = new ClusterHomeCallbackImpl();
+    private ClusterStateListenerImpl mClusterStateListener = new ClusterStateListenerImpl();
+    private ClusterNavigationStateListenerImpl mClusterNavigationStateListener =
+            new ClusterNavigationStateListenerImpl();
     private ClusterState mState;
     private int mChanges = 0;
     private byte[] mNavigationState;
@@ -155,8 +158,11 @@
         super.setUp();
         mClusterHomeManager = (ClusterHomeManager) getCar().getCarManager(Car.CLUSTER_HOME_SERVICE);
         if (!isNoHalPropertyTest() && mClusterHomeManager != null) {
-            mClusterHomeManager.registerClusterHomeCallback(
-                    getContext().getMainExecutor(), mClusterHomeCallback);
+            mClusterHomeManager.registerClusterStateListener(
+                    getContext().getMainExecutor(), mClusterStateListener);
+
+            mClusterHomeManager.registerClusterNavigationStateListener(
+                    getContext().getMainExecutor(), mClusterNavigationStateListener);
         }
     }
 
@@ -173,7 +179,9 @@
     @Override
     public void tearDown() throws Exception {
         if (!isNoHalPropertyTest() && mClusterHomeManager != null) {
-            mClusterHomeManager.unregisterClusterHomeCallback(mClusterHomeCallback);
+            mClusterHomeManager.unregisterClusterStateListener(mClusterStateListener);
+            mClusterHomeManager
+                    .unregisterClusterNavigationStateListener(mClusterNavigationStateListener);
         }
         super.tearDown();
     }
@@ -181,7 +189,7 @@
     @Test
     public void testClusterSwitchUi() throws InterruptedException {
         getMockedVehicleHal().injectEvent(createSwitchUiEvent(UI_TYPE_2));
-        mCallbackReceived.await(TEST_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+        mClusterStateListenerCalled.await(TEST_TIMEOUT_MS, TimeUnit.MILLISECONDS);
 
         assertThat(mState).isNotNull();
         assertThat(mState.uiType).isEqualTo(UI_TYPE_2);
@@ -193,7 +201,7 @@
         getMockedVehicleHal().injectEvent(createDisplayStateEvent(
                 DISPLAY_ON, BOUNDS_LEFT, BOUNDS_TOP, BOUNDS_RIGHT, BOUNDS_BOTTOM,
                 INSET_LEFT, INSET_TOP, INSET_RIGHT, INSET_BOTTOM));
-        mCallbackReceived.await(TEST_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+        mClusterStateListenerCalled.await(TEST_TIMEOUT_MS, TimeUnit.MILLISECONDS);
 
         assertThat(mState).isNotNull();
         assertThat(mState.on).isEqualTo(true);
@@ -246,10 +254,16 @@
         assertThrows(IllegalStateException.class,
                 () -> mClusterHomeManager.requestDisplay(UI_TYPE_1));
         assertThrows(IllegalStateException.class,
-                () -> mClusterHomeManager.registerClusterHomeCallback(
-                        getContext().getMainExecutor(), mClusterHomeCallback));
+                () -> mClusterHomeManager.registerClusterStateListener(
+                        getContext().getMainExecutor(), mClusterStateListener));
         assertThrows(IllegalStateException.class,
-                () -> mClusterHomeManager.unregisterClusterHomeCallback(mClusterHomeCallback));
+                () -> mClusterHomeManager.unregisterClusterStateListener(mClusterStateListener));
+        assertThrows(IllegalStateException.class,
+                () -> mClusterHomeManager.registerClusterNavigationStateListener(
+                        getContext().getMainExecutor(), mClusterNavigationStateListener));
+        assertThrows(IllegalStateException.class,
+                () -> mClusterHomeManager
+                        .unregisterClusterNavigationStateListener(mClusterNavigationStateListener));
     }
 
     @Test
@@ -319,15 +333,19 @@
         }
     }
 
-    private class ClusterHomeCallbackImpl implements ClusterHomeManager.ClusterHomeCallback {
+    private class ClusterStateListenerImpl implements ClusterHomeManager.ClusterStateListener {
         public void onClusterStateChanged(ClusterState state, int changes) {
             mState = state;
             mChanges = changes;
-            mCallbackReceived.countDown();
+            mClusterStateListenerCalled.countDown();
         }
+    }
+    private class ClusterNavigationStateListenerImpl implements
+            ClusterHomeManager.ClusterNavigationStateListener {
+        @Override
         public void onNavigationState(byte[] navigationState) {
             mNavigationState = navigationState;
-            mCallbackReceived.countDown();
+            mClusterNavigationStateListenerCalled.countDown();
         }
     }
 
diff --git a/tests/carservice_unit_test/src/com/android/car/cluster/ClusterHomeServiceUnitTest.java b/tests/carservice_unit_test/src/com/android/car/cluster/ClusterHomeServiceUnitTest.java
index 4afe81e..e5bcd5f 100644
--- a/tests/carservice_unit_test/src/com/android/car/cluster/ClusterHomeServiceUnitTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/cluster/ClusterHomeServiceUnitTest.java
@@ -33,7 +33,8 @@
 import android.app.ActivityOptions;
 import android.car.cluster.ClusterHomeManager;
 import android.car.cluster.ClusterState;
-import android.car.cluster.IClusterHomeCallback;
+import android.car.cluster.IClusterNavigationStateListener;
+import android.car.cluster.IClusterStateListener;
 import android.car.cluster.navigation.NavigationState.NavigationStateProto;
 import android.car.navigation.CarNavigationInstrumentCluster;
 import android.content.ComponentName;
@@ -92,14 +93,18 @@
     private int mClusterStateChanges;
     private byte[] mNavigationState;
 
-    private IClusterHomeCallback mClusterHomeCallback;
-    private class IClusterHomeCallbackImpl extends IClusterHomeCallback.Stub {
+    private IClusterStateListener mClusterStateListener;
+    private IClusterNavigationStateListener mClusterNavigationStateListener;
+
+    private class IClusterStateListenerImpl extends IClusterStateListener.Stub {
         @Override
         public void onClusterStateChanged(ClusterState state, int changes) {
             mClusterState = state;
             mClusterStateChanges = changes;
         }
+    }
 
+    private class IClusterNavigationStateListenerImpl extends IClusterNavigationStateListener.Stub {
         @Override
         public void onNavigationStateChanged(byte[] navigationState) {
             mNavigationState = navigationState;
@@ -130,15 +135,21 @@
         mClusterHomeService.init();
     }
 
-    public void registerClusterHomeCallback() {
-        mClusterHomeCallback = new IClusterHomeCallbackImpl();
-        mClusterHomeService.registerCallback(mClusterHomeCallback);
+    public void registerClusterHomeCallbacks() {
+        mClusterStateListener = new IClusterStateListenerImpl();
+        mClusterNavigationStateListener = new IClusterNavigationStateListenerImpl();
+        mClusterHomeService.registerClusterStateListener(mClusterStateListener);
+        mClusterHomeService.registerClusterNavigationStateListener(mClusterNavigationStateListener);
     }
 
     @After
     public void tearDown() throws Exception {
-        if (mClusterHomeCallback != null) {
-            mClusterHomeService.unregisterCallback(mClusterHomeCallback);
+        if (mClusterStateListener != null) {
+            mClusterHomeService.unregisterClusterStateListener(mClusterStateListener);
+        }
+        if (mClusterNavigationStateListener != null) {
+            mClusterHomeService.unregisterClusterNavigationStateListener(
+                    mClusterNavigationStateListener);
         }
         mClusterHomeService.release();
     }
@@ -171,7 +182,7 @@
 
     @Test
     public void onSwitchUiSendsDisplayState() {
-        registerClusterHomeCallback();
+        registerClusterHomeCallbacks();
 
         mClusterHomeService.onSwitchUi(UI_TYPE_CLUSTER_MAPS);
 
@@ -183,7 +194,7 @@
 
     @Test
     public void displayOnSendsDisplayState() {
-        registerClusterHomeCallback();
+        registerClusterHomeCallbacks();
 
         mClusterHomeService.onDisplayState(ClusterHalService.DISPLAY_ON,
                 /* bounds= */ null, /* insets= */ null);
@@ -196,7 +207,7 @@
 
     @Test
     public void displayBoundsSendsDisplayState() {
-        registerClusterHomeCallback();
+        registerClusterHomeCallbacks();
 
         Rect newBounds = new Rect(10, 10, CLUSTER_WIDTH - 10, CLUSTER_HEIGHT - 10);
         mClusterHomeService.onDisplayState(ClusterHalService.DONT_CARE,
@@ -210,7 +221,7 @@
 
     @Test
     public void displayInsetsSendsDisplayState() {
-        registerClusterHomeCallback();
+        registerClusterHomeCallbacks();
 
         Insets newInsets = Insets.of(10, 10, 10, 10);
         mClusterHomeService.onDisplayState(ClusterHalService.DONT_CARE, /* bounds= */ null,
@@ -224,7 +235,7 @@
 
     @Test
     public void onNavigationStateChangedSendsNavigationState() {
-        registerClusterHomeCallback();
+        registerClusterHomeCallbacks();
 
         Bundle bundle = new Bundle();
         byte[] newNavState = new byte[] {(byte) 1, (byte) 2, (byte) 3};
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 c360db0..e757536 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
@@ -21,18 +21,25 @@
 import static android.car.drivingstate.CarUxRestrictions.UX_RESTRICTIONS_BASELINE;
 import static android.car.test.mocks.AndroidMockitoHelper.mockUmGetAllUsers;
 import static android.car.test.mocks.AndroidMockitoHelper.mockUmGetUserHandles;
+import static android.car.test.mocks.AndroidMockitoHelper.mockUmIsUserRunning;
 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;
 
+import static com.android.car.CarStatsLog.CAR_WATCHDOG_KILL_STATS_REPORTED__KILL_REASON__KILLED_ON_IO_OVERUSE;
+import static com.android.car.CarStatsLog.CAR_WATCHDOG_KILL_STATS_REPORTED__SYSTEM_STATE__GARAGE_MODE;
+import static com.android.car.CarStatsLog.CAR_WATCHDOG_KILL_STATS_REPORTED__SYSTEM_STATE__USER_NO_INTERACTION_MODE;
+import static com.android.car.CarStatsLog.CAR_WATCHDOG_KILL_STATS_REPORTED__UID_STATE__UNKNOWN_UID_STATE;
 import static com.android.car.watchdog.WatchdogStorage.ZONE_OFFSET;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
 import static org.junit.Assert.assertThrows;
+import static org.mockito.AdditionalMatchers.or;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyInt;
@@ -42,9 +49,9 @@
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
@@ -62,10 +69,12 @@
 import android.automotive.watchdog.internal.PackageMetadata;
 import android.automotive.watchdog.internal.PackageResourceOveruseAction;
 import android.automotive.watchdog.internal.PerStateIoOveruseThreshold;
+import android.automotive.watchdog.internal.PowerCycle;
 import android.automotive.watchdog.internal.ResourceSpecificConfiguration;
 import android.automotive.watchdog.internal.StateType;
 import android.automotive.watchdog.internal.UidType;
 import android.automotive.watchdog.internal.UserPackageIoUsageStats;
+import android.automotive.watchdog.internal.UserState;
 import android.car.drivingstate.CarUxRestrictions;
 import android.car.drivingstate.ICarUxRestrictionsChangeListener;
 import android.car.hardware.power.CarPowerManager.CarPowerStateListener;
@@ -107,6 +116,7 @@
 
 import com.android.car.CarLocalServices;
 import com.android.car.CarServiceUtils;
+import com.android.car.CarStatsLog;
 import com.android.car.CarUxRestrictionsManagerService;
 import com.android.car.power.CarPowerManagementService;
 
@@ -117,8 +127,8 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
 import org.mockito.Mock;
-import org.mockito.Mockito;
 import org.mockito.junit.MockitoJUnitRunner;
 
 import java.time.Instant;
@@ -156,6 +166,30 @@
     @Mock private ICarWatchdog mMockCarWatchdogDaemon;
     @Mock private WatchdogStorage mMockWatchdogStorage;
 
+
+    @Captor private ArgumentCaptor<ICarPowerStateListener> mICarPowerStateListenerCaptor;
+    @Captor private ArgumentCaptor<ICarPowerPolicyListener> mICarPowerPolicyListenerCaptor;
+    @Captor private ArgumentCaptor<BroadcastReceiver> mBroadcastReceiverCaptor;
+    @Captor private ArgumentCaptor<ICarUxRestrictionsChangeListener>
+            mICarUxRestrictionsChangeListener;
+    @Captor private ArgumentCaptor<IBinder.DeathRecipient> mDeathRecipientCaptor;
+    @Captor private ArgumentCaptor<ICarWatchdogServiceForSystem>
+            mICarWatchdogServiceForSystemCaptor;
+    @Captor private ArgumentCaptor<List<
+            android.automotive.watchdog.internal.ResourceOveruseConfiguration>>
+            mResourceOveruseConfigurationsCaptor;
+    @Captor private ArgumentCaptor<List<PackageResourceOveruseAction>>
+            mResourceOveruseActionsCaptor;
+    @Captor private ArgumentCaptor<int[]> mIntArrayCaptor;
+    @Captor private ArgumentCaptor<byte[]> mOveruseStatsCaptor;
+    @Captor private ArgumentCaptor<byte[]> mKilledStatsCaptor;
+    @Captor private ArgumentCaptor<Integer> mOverusingUidCaptor;
+    @Captor private ArgumentCaptor<Integer> mKilledUidCaptor;
+    @Captor private ArgumentCaptor<Integer> mUidStateCaptor;
+    @Captor private ArgumentCaptor<Integer> mSystemStateCaptor;
+    @Captor private ArgumentCaptor<Integer> mKillReasonCaptor;
+
+
     private CarWatchdogService mCarWatchdogService;
     private ICarWatchdogServiceForSystem mWatchdogServiceForSystemImpl;
     private IBinder.DeathRecipient mCarWatchdogDaemonBinderDeathRecipient;
@@ -174,6 +208,7 @@
     private final List<WatchdogStorage.UserPackageSettingsEntry> mUserPackageSettingsEntries =
             new ArrayList<>();
     private final List<WatchdogStorage.IoUsageStatsEntry> mIoUsageStatsEntries = new ArrayList<>();
+    private final IPackageManager mSpiedPackageManager = spy(ActivityThread.getPackageManager());
 
     @Override
     protected void onSessionBuilder(CustomMockitoSessionBuilder builder) {
@@ -181,7 +216,8 @@
             .spyStatic(ServiceManager.class)
             .spyStatic(Binder.class)
             .spyStatic(ActivityThread.class)
-            .spyStatic(CarLocalServices.class);
+            .spyStatic(CarLocalServices.class)
+            .spyStatic(CarStatsLog.class);
     }
 
     /**
@@ -196,6 +232,7 @@
                 .when(() -> CarLocalServices.getService(CarPowerManagementService.class));
         doReturn(mMockCarUxRestrictionsManagerService)
                 .when(() -> CarLocalServices.getService(CarUxRestrictionsManagerService.class));
+        doReturn(mSpiedPackageManager).when(() -> ActivityThread.getPackageManager());
 
         mCarWatchdogService = new CarWatchdogService(mMockContext, mMockWatchdogStorage);
         mCarWatchdogService.setOveruseHandlingDelay(OVERUSE_HANDLING_DELAY_MILLS);
@@ -208,11 +245,9 @@
         captureCarPowerListeners();
         captureBroadcastReceiver();
         captureCarUxRestrictionsChangeListener();
-        captureWatchdogServiceForSystem();
-        captureDaemonBinderDeathRecipient();
+        captureAndVerifyRegistrationWithDaemon(/* waitOnMain= */ true);
         verifyDatabaseInit(/* wantedInvocations= */ 1);
         mockPackageManager();
-        verifyResourceOveruseConfigurationsSynced(/* wantedInvocations= */ 1);
     }
 
     /**
@@ -295,13 +330,38 @@
     public void testGarageModeStateChangeToOff() throws Exception {
         mBroadcastReceiver.onReceive(mMockContext,
                 new Intent().setAction(CarWatchdogService.ACTION_GARAGE_MODE_OFF));
-        verify(mMockCarWatchdogDaemon)
+        // GARAGE_MODE_OFF is notified twice: Once during the initial daemon connect and once when
+        // the ACTION_GARAGE_MODE_OFF intent is received.
+        verify(mMockCarWatchdogDaemon, times(2))
                 .notifySystemStateChange(
                         eq(StateType.GARAGE_MODE), eq(GarageMode.GARAGE_MODE_OFF), eq(-1));
         verify(mMockWatchdogStorage, never()).shrinkDatabase();
     }
 
     @Test
+    public void testWatchdogDaemonRestart() throws Exception {
+        crashWatchdogDaemon();
+
+        mockUmGetUserHandles(mMockUserManager, /* excludeDying= */ false, 101, 102);
+        mockUmIsUserRunning(mMockUserManager, /* userId= */ 101, /* isRunning= */ false);
+        mockUmIsUserRunning(mMockUserManager, /* userId= */ 102, /* isRunning= */ true);
+        setCarPowerState(CarPowerStateListener.SHUTDOWN_ENTER);
+        mBroadcastReceiver.onReceive(mMockContext,
+                new Intent().setAction(CarWatchdogService.ACTION_GARAGE_MODE_ON));
+
+        restartWatchdogDaemonAndAwait();
+
+        verify(mMockCarWatchdogDaemon, times(1)).notifySystemStateChange(
+                eq(StateType.USER_STATE), eq(101), eq(UserState.USER_STATE_STOPPED));
+        verify(mMockCarWatchdogDaemon, times(1)).notifySystemStateChange(
+                eq(StateType.USER_STATE), eq(102), eq(UserState.USER_STATE_STARTED));
+        verify(mMockCarWatchdogDaemon, times(1)).notifySystemStateChange(
+                eq(StateType.POWER_CYCLE), eq(PowerCycle.POWER_CYCLE_SHUTDOWN_ENTER), eq(-1));
+        verify(mMockCarWatchdogDaemon, times(1)).notifySystemStateChange(
+                eq(StateType.GARAGE_MODE), eq(GarageMode.GARAGE_MODE_ON), eq(-1));
+    }
+
+    @Test
     public void testUserRemovedBroadcast() throws Exception {
         mockUmGetUserHandles(mMockUserManager, /* excludeDying= */ true, 101, 102);
         mBroadcastReceiver.onReceive(mMockContext,
@@ -1254,65 +1314,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
@@ -1348,71 +1393,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));
     }
 
@@ -1477,12 +1506,16 @@
                 sampleResourceOveruseConfigurations(), CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO))
                 .isEqualTo(CarWatchdogManager.RETURN_CODE_SUCCESS);
 
-        /* Expect two calls, the first is made at car watchdog service init */
-        verifyResourceOveruseConfigurationsSynced(2);
-
         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
@@ -1663,9 +1696,6 @@
 
     @Test
     public void testGetResourceOveruseConfigurations() throws Exception {
-        when(mMockCarWatchdogDaemon.getResourceOveruseConfigurations())
-                .thenReturn(sampleInternalResourceOveruseConfigurations());
-
         List<ResourceOveruseConfiguration> actualConfigs =
                 mCarWatchdogService.getResourceOveruseConfigurations(
                         CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO);
@@ -1699,9 +1729,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);
@@ -1809,7 +1836,7 @@
                         /* forgivenWriteBytes= */ constructPerStateBytes(100, 200, 300),
                         constructInternalIoOveruseStats(/* killableOnOveruse= */ true,
                                 /* remainingWriteBytes= */ constructPerStateBytes(0, 0, 0),
-                                /* writtenBytes= */ constructPerStateBytes(100, 200, 300),
+                                /* writtenBytes= */ constructPerStateBytes(300, 600, 900),
                                 /* totalOveruses= */ 3)));
 
         pushLatestIoOveruseStatsAndWait(packageIoOveruseStats);
@@ -1836,6 +1863,25 @@
 
         verifyOnOveruseCalled(expectedStats, mockSystemListener);
 
+        List<AtomsProto.CarWatchdogIoOveruseStatsReported> expectedReportedOveruseStats =
+                new ArrayList<>();
+        expectedReportedOveruseStats.add(constructIoOveruseStatsReported(criticalSysPkgUid,
+                WatchdogPerfHandler.constructCarWatchdogPerStateBytes(10, 20, 30),
+                WatchdogPerfHandler.constructCarWatchdogPerStateBytes(100, 200, 300)));
+        expectedReportedOveruseStats.add(constructIoOveruseStatsReported(thirdPartyPkgUid,
+                WatchdogPerfHandler.constructCarWatchdogPerStateBytes(30, 60, 90),
+                WatchdogPerfHandler.constructCarWatchdogPerStateBytes(300, 600, 900)));
+
+        captureAndVerifyIoOveruseStatsReported(expectedReportedOveruseStats);
+
+        List<AtomsProto.CarWatchdogKillStatsReported> expectedReportedKillStats =
+                Collections.singletonList(constructIoOveruseKillStatsReported(thirdPartyPkgUid,
+                        CAR_WATCHDOG_KILL_STATS_REPORTED__SYSTEM_STATE__USER_NO_INTERACTION_MODE,
+                        WatchdogPerfHandler.constructCarWatchdogPerStateBytes(30, 60, 90),
+                        WatchdogPerfHandler.constructCarWatchdogPerStateBytes(300, 600, 900)));
+
+        captureAndVerifyKillStatsReported(expectedReportedKillStats);
+
         /* {@link thirdPartyPkgUid} action is sent twice. Once before it is killed and once after
          * it is killed. This is done because the package is killed only after the display is turned
          * off.
@@ -1894,7 +1940,7 @@
                         /* forgivenWriteBytes= */ constructPerStateBytes(100, 200, 300),
                         constructInternalIoOveruseStats(/* killableOnOveruse= */ true,
                                 /* remainingWriteBytes= */ constructPerStateBytes(0, 0, 0),
-                                /* writtenBytes= */ constructPerStateBytes(100, 200, 300),
+                                /* writtenBytes= */ constructPerStateBytes(300, 600, 900),
                                 /* totalOveruses= */ 3)));
 
         pushLatestIoOveruseStatsAndWait(packageIoOveruseStats);
@@ -1921,6 +1967,25 @@
 
         verifyOnOveruseCalled(expectedStats, mockSystemListener);
 
+        List<AtomsProto.CarWatchdogIoOveruseStatsReported> expectedReportedOveruseStats =
+                new ArrayList<>();
+        expectedReportedOveruseStats.add(constructIoOveruseStatsReported(criticalSysSharedUid,
+                WatchdogPerfHandler.constructCarWatchdogPerStateBytes(10, 20, 30),
+                WatchdogPerfHandler.constructCarWatchdogPerStateBytes(100, 200, 300)));
+        expectedReportedOveruseStats.add(constructIoOveruseStatsReported(thirdPartySharedUid,
+                WatchdogPerfHandler.constructCarWatchdogPerStateBytes(30, 60, 90),
+                WatchdogPerfHandler.constructCarWatchdogPerStateBytes(300, 600, 900)));
+
+        captureAndVerifyIoOveruseStatsReported(expectedReportedOveruseStats);
+
+        List<AtomsProto.CarWatchdogKillStatsReported> expectedReportedKillStats =
+                Collections.singletonList(constructIoOveruseKillStatsReported(thirdPartySharedUid,
+                        CAR_WATCHDOG_KILL_STATS_REPORTED__SYSTEM_STATE__USER_NO_INTERACTION_MODE,
+                        WatchdogPerfHandler.constructCarWatchdogPerStateBytes(30, 60, 90),
+                        WatchdogPerfHandler.constructCarWatchdogPerStateBytes(300, 600, 900)));
+
+        captureAndVerifyKillStatsReported(expectedReportedKillStats);
+
         /* {@link thirdPartySharedUid} action is sent twice. Once before it is killed and once after
          * it is killed. This is done because the package is killed only after the display is turned
          * off.
@@ -1987,7 +2052,7 @@
         mCarWatchdogService.setKillablePackageAsUser(
                 "third_party_package.A", new UserHandle(12), /* isKillable= */ false);
 
-        mCarPowerStateListener.onStateChanged(CarPowerStateListener.SHUTDOWN_ENTER);
+        setCarPowerState(CarPowerStateListener.SHUTDOWN_ENTER);
         verify(mMockWatchdogStorage).saveIoUsageStats(any());
         verify(mMockWatchdogStorage).saveUserPackageSettings(any());
         mCarWatchdogService.release();
@@ -2190,6 +2255,11 @@
 
         pushLatestIoOveruseStatsAndWait(packageIoOveruseStats);
 
+        captureAndVerifyIoOveruseStatsReported(sampleReportedOveruseStats());
+
+        verify(() -> CarStatsLog.write(eq(CarStatsLog.CAR_WATCHDOG_KILL_STATS_REPORTED),
+                anyInt(), anyInt(), anyInt(), anyInt(), any(), any()), never());
+
         verifyActionsTakenOnResourceOveruse(sampleActionTakenOnOveruse(
                 /* notKilledUids= */ new int[]{
                         10010001, 10110001, 10010004, 10110004, 10010005, 10110005},
@@ -2209,6 +2279,11 @@
 
         pushLatestIoOveruseStatsAndWait(packageIoOveruseStats);
 
+        captureAndVerifyIoOveruseStatsReported(sampleReportedOveruseStats());
+
+        verify(() -> CarStatsLog.write(eq(CarStatsLog.CAR_WATCHDOG_KILL_STATS_REPORTED),
+                anyInt(), anyInt(), anyInt(), anyInt(), any(), any()), never());
+
         verifyActionsTakenOnResourceOveruse(sampleActionTakenOnOveruse(
                 /* notKilledUids= */ new int[]{
                         10010001, 10110001, 10010004, 10110004, 10010005, 10110005},
@@ -2228,6 +2303,11 @@
 
         pushLatestIoOveruseStatsAndWait(packageIoOveruseStats);
 
+        captureAndVerifyIoOveruseStatsReported(sampleReportedOveruseStats());
+
+        verify(() -> CarStatsLog.write(eq(CarStatsLog.CAR_WATCHDOG_KILL_STATS_REPORTED),
+                anyInt(), anyInt(), anyInt(), anyInt(), any(), any()), never());
+
         verifyActionsTakenOnResourceOveruse(sampleActionTakenOnOveruse(
                 /* notKilledUids= */ new int[]{
                         10010001, 10110001, 10010004, 10110004, 10010005, 10110005},
@@ -2255,6 +2335,12 @@
 
         setDisplayStateEnabled(false);
 
+        captureAndVerifyIoOveruseStatsReported(sampleReportedOveruseStats());
+
+        captureAndVerifyKillStatsReported(sampleReportedKillStats(
+                CAR_WATCHDOG_KILL_STATS_REPORTED__SYSTEM_STATE__USER_NO_INTERACTION_MODE,
+                /* killedUids= */ new int[]{10010004, 10110004, 10010005, 10110005}));
+
         verifyActionsTakenOnResourceOveruse(sampleActionTakenOnOveruse(
                 /* notKilledUids= */ new int[]{
                         10010001, 10110001, 10010004, 10110004, 10010005, 10110005},
@@ -2279,6 +2365,12 @@
 
         pushLatestIoOveruseStatsAndWait(packageIoOveruseStats);
 
+        captureAndVerifyIoOveruseStatsReported(sampleReportedOveruseStats());
+
+        captureAndVerifyKillStatsReported(sampleReportedKillStats(
+                CAR_WATCHDOG_KILL_STATS_REPORTED__SYSTEM_STATE__USER_NO_INTERACTION_MODE,
+                /* killedUids= */ new int[]{10010004, 10110004, 10010005, 10110005}));
+
         verifyActionsTakenOnResourceOveruse(sampleActionTakenOnOveruse(
                 /* notKilledUids= */ new int[]{
                         10010001, 10110001, 10010004, 10110004, 10010005, 10110005},
@@ -2313,6 +2405,12 @@
         setRequiresDistractionOptimization(false);
         setDisplayStateEnabled(false);
 
+        captureAndVerifyIoOveruseStatsReported(sampleReportedOveruseStats());
+
+        captureAndVerifyKillStatsReported(sampleReportedKillStats(
+                CAR_WATCHDOG_KILL_STATS_REPORTED__SYSTEM_STATE__USER_NO_INTERACTION_MODE,
+                /* killedUids= */ new int[]{10010004, 10110004, 10010005, 10110005}));
+
         verifyActionsTakenOnResourceOveruse(sampleActionTakenOnOveruse(
                 /* notKilledUids= */ new int[]{
                         10010001, 10110001, 10010004, 10110004, 10010005, 10110005},
@@ -2346,6 +2444,12 @@
         setRequiresDistractionOptimization(false);
         setDisplayStateEnabled(false);
 
+        captureAndVerifyIoOveruseStatsReported(sampleReportedOveruseStats());
+
+        captureAndVerifyKillStatsReported(sampleReportedKillStats(
+                CAR_WATCHDOG_KILL_STATS_REPORTED__SYSTEM_STATE__USER_NO_INTERACTION_MODE,
+                /* killedUids= */ new int[]{10110004, 10010005}));
+
         verifyActionsTakenOnResourceOveruse(sampleActionTakenOnOveruse(
                 /* notKilledUids= */ new int[]{
                         10010001, 10110001, 10010004, 10110004, 10010005, 10110005},
@@ -2378,6 +2482,12 @@
         setRequiresDistractionOptimization(false);
         setDisplayStateEnabled(false);
 
+        captureAndVerifyIoOveruseStatsReported(sampleReportedOveruseStats());
+
+        captureAndVerifyKillStatsReported(sampleReportedKillStats(
+                CAR_WATCHDOG_KILL_STATS_REPORTED__SYSTEM_STATE__USER_NO_INTERACTION_MODE,
+                /* killedUids= */ new int[]{10110004, 10010005}));
+
         verifyActionsTakenOnResourceOveruse(sampleActionTakenOnOveruse(
                 /* notKilledUids= */ new int[]{
                         10010001, 10110001, 10010004, 10110004, 10010005, 10110005},
@@ -2411,6 +2521,44 @@
         setRequiresDistractionOptimization(false);
         setDisplayStateEnabled(false);
 
+        captureAndVerifyIoOveruseStatsReported(sampleReportedOveruseStats());
+
+        captureAndVerifyKillStatsReported(sampleReportedKillStats(
+                CAR_WATCHDOG_KILL_STATS_REPORTED__SYSTEM_STATE__USER_NO_INTERACTION_MODE,
+                /* killedUids= */ new int[]{10010004, 10110004, 10010005, 10110005}));
+
+        verifyActionsTakenOnResourceOveruse(sampleActionTakenOnOveruse(
+                /* notKilledUids= */ new int[]{
+                        10010001, 10110001, 10010004, 10110004, 10010005, 10110005},
+                /* killedRecurringOveruseUids= */ new int[]{10010004, 10110004, 10010005, 10110005}
+        ));
+
+        assertWithMessage("Disabled user packages").that(mDisabledUserPackages).containsExactly(
+                "100:vendor_package.non_critical", "101:vendor_package.non_critical",
+                "100:third_party_package.A", "101:third_party_package.A",
+                "100:third_party_package.B", "101:third_party_package.B");
+    }
+
+    @Test
+    public void testImmediateDisableRecurrentlyOverusingAppDuringGarageMode()
+            throws Exception {
+        setUpSampleUserAndPackages();
+        setRequiresDistractionOptimization(false);
+        setDisplayStateEnabled(false);
+        mBroadcastReceiver.onReceive(mMockContext,
+                new Intent().setAction(CarWatchdogService.ACTION_GARAGE_MODE_ON));
+
+        List<PackageIoOveruseStats> packageIoOveruseStats =
+                sampleIoOveruseStats(/* requireRecurrentOveruseStats= */ true);
+
+        pushLatestIoOveruseStatsAndWait(packageIoOveruseStats);
+
+        captureAndVerifyIoOveruseStatsReported(sampleReportedOveruseStats());
+
+        captureAndVerifyKillStatsReported(sampleReportedKillStats(
+                CAR_WATCHDOG_KILL_STATS_REPORTED__SYSTEM_STATE__GARAGE_MODE,
+                /* killedUids= */ new int[]{10010004, 10110004, 10010005, 10110005}));
+
         verifyActionsTakenOnResourceOveruse(sampleActionTakenOnOveruse(
                 /* notKilledUids= */ new int[]{
                         10010001, 10110001, 10010004, 10110004, 10010005, 10110005},
@@ -2434,12 +2582,17 @@
         String packageName = mMockContext.getPackageName();
         mGenericPackageNameByUid.put(10003346, packageName);
         mGenericPackageNameByUid.put(10101278, "vendor_package.critical");
+        mGenericPackageNameByUid.put(10103456, "third_party_package");
         injectIoOveruseStatsForPackages(
                 mGenericPackageNameByUid, /* killablePackages= */ new ArraySet<>(),
                 /* shouldNotifyPackages= */ new ArraySet<>());
 
+        doReturn(COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED).when(mSpiedPackageManager)
+                .getApplicationEnabledSetting(
+                        or(eq("third_party_package"), eq("vendor_package.critical")), eq(101));
+
         mWatchdogServiceForSystemImpl.resetResourceOveruseStats(
-                Collections.singletonList(packageName));
+                Arrays.asList(packageName, "third_party_package"));
 
         ResourceOveruseStats actualStats =
                 mCarWatchdogService.getResourceOveruseStatsForUserPackage(
@@ -2453,6 +2606,13 @@
         ResourceOveruseStatsSubject.assertEquals(actualStats, expectedStats);
 
         verify(mMockWatchdogStorage).deleteUserPackage(eq(user.getIdentifier()), eq(packageName));
+
+        verify(mSpiedPackageManager).getApplicationEnabledSetting(packageName, 100);
+        verify(mSpiedPackageManager).getApplicationEnabledSetting("third_party_package", 101);
+        verify(mSpiedPackageManager).setApplicationEnabledSetting(eq("third_party_package"),
+                eq(COMPONENT_ENABLED_STATE_ENABLED), anyInt(), eq(101), anyString());
+
+        verifyNoMoreInteractions(mSpiedPackageManager);
     }
 
     @Test
@@ -2667,6 +2827,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 =
@@ -2677,10 +2957,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;
     }
 
@@ -2711,63 +2993,61 @@
     }
 
     private void captureCarPowerListeners() {
-        ArgumentCaptor<ICarPowerStateListener> powerStateListenerArgumentCaptor =
-                ArgumentCaptor.forClass(ICarPowerStateListener.class);
         verify(mMockCarPowerManagementService).registerListener(
-                powerStateListenerArgumentCaptor.capture());
-        mCarPowerStateListener = powerStateListenerArgumentCaptor.getValue();
+                mICarPowerStateListenerCaptor.capture());
+        mCarPowerStateListener = mICarPowerStateListenerCaptor.getValue();
         assertWithMessage("Car power state listener").that(mCarPowerStateListener).isNotNull();
 
-        ArgumentCaptor<ICarPowerPolicyListener> powerPolicyListenerArgumentCaptor =
-                ArgumentCaptor.forClass(ICarPowerPolicyListener.class);
         verify(mMockCarPowerManagementService).addPowerPolicyListener(
-                any(), powerPolicyListenerArgumentCaptor.capture());
-        mCarPowerPolicyListener = powerPolicyListenerArgumentCaptor.getValue();
+                any(), mICarPowerPolicyListenerCaptor.capture());
+        mCarPowerPolicyListener = mICarPowerPolicyListenerCaptor.getValue();
         assertWithMessage("Car power policy listener").that(mCarPowerPolicyListener).isNotNull();
     }
 
     private void captureBroadcastReceiver() {
-        ArgumentCaptor<BroadcastReceiver> receiverArgumentCaptor =
-                ArgumentCaptor.forClass(BroadcastReceiver.class);
         verify(mMockContext)
-                .registerReceiverForAllUsers(receiverArgumentCaptor.capture(), any(), any(), any());
-        mBroadcastReceiver = receiverArgumentCaptor.getValue();
+                .registerReceiverForAllUsers(mBroadcastReceiverCaptor.capture(), any(), any(),
+                        any());
+        mBroadcastReceiver = mBroadcastReceiverCaptor.getValue();
         assertWithMessage("Broadcast receiver").that(mBroadcastReceiver).isNotNull();
     }
 
     private void captureCarUxRestrictionsChangeListener() {
-        ArgumentCaptor<ICarUxRestrictionsChangeListener> listenerArgumentCaptor =
-                ArgumentCaptor.forClass(ICarUxRestrictionsChangeListener.class);
         verify(mMockCarUxRestrictionsManagerService).registerUxRestrictionsChangeListener(
-                listenerArgumentCaptor.capture(), eq(Display.DEFAULT_DISPLAY));
-        mCarUxRestrictionsChangeListener = listenerArgumentCaptor.getValue();
+                mICarUxRestrictionsChangeListener.capture(), eq(Display.DEFAULT_DISPLAY));
+        mCarUxRestrictionsChangeListener = mICarUxRestrictionsChangeListener.getValue();
         assertWithMessage("UX restrictions change listener").that(mCarUxRestrictionsChangeListener)
                 .isNotNull();
     }
 
-    private void captureWatchdogServiceForSystem() throws Exception {
-        /* Registering to daemon is done on the main thread. To ensure the registration completes
-         * before verification, execute an empty block on the main thread.
-         */
-        CarServiceUtils.runOnMainSync(() -> {});
+    private void captureAndVerifyRegistrationWithDaemon(boolean waitOnMain) throws Exception {
+        if (waitOnMain) {
+            // Registering to daemon is done on the main thread. To ensure the registration
+            // completes before verification, execute an empty block on the main thread.
+            CarServiceUtils.runOnMainSync(() -> {});
+        }
 
-        ArgumentCaptor<ICarWatchdogServiceForSystem> watchdogServiceForSystemImplCaptor =
-                ArgumentCaptor.forClass(ICarWatchdogServiceForSystem.class);
-        verify(mMockCarWatchdogDaemon, atLeastOnce()).registerCarWatchdogService(
-                watchdogServiceForSystemImplCaptor.capture());
-        mWatchdogServiceForSystemImpl = watchdogServiceForSystemImplCaptor.getValue();
-        assertWithMessage("Car watchdog service for system")
-                .that(mWatchdogServiceForSystemImpl).isNotNull();
-    }
+        verify(mMockCarWatchdogDaemon, atLeastOnce()).asBinder();
 
-    private void captureDaemonBinderDeathRecipient() throws Exception {
-        ArgumentCaptor<IBinder.DeathRecipient> deathRecipientCaptor =
-                ArgumentCaptor.forClass(IBinder.DeathRecipient.class);
-        verify(mMockBinder, timeout(MAX_WAIT_TIME_MS).atLeastOnce())
-                .linkToDeath(deathRecipientCaptor.capture(), anyInt());
-        mCarWatchdogDaemonBinderDeathRecipient = deathRecipientCaptor.getValue();
+        verify(mMockBinder, atLeastOnce()).linkToDeath(mDeathRecipientCaptor.capture(), anyInt());
+        mCarWatchdogDaemonBinderDeathRecipient = mDeathRecipientCaptor.getValue();
         assertWithMessage("Watchdog daemon binder death recipient")
                 .that(mCarWatchdogDaemonBinderDeathRecipient).isNotNull();
+
+        verify(mMockCarWatchdogDaemon, atLeastOnce()).registerCarWatchdogService(
+                mICarWatchdogServiceForSystemCaptor.capture());
+        mWatchdogServiceForSystemImpl = mICarWatchdogServiceForSystemCaptor.getValue();
+        assertWithMessage("Car watchdog service for system")
+                .that(mWatchdogServiceForSystemImpl).isNotNull();
+
+        verify(mMockCarWatchdogDaemon, atLeastOnce()).notifySystemStateChange(
+                eq(StateType.GARAGE_MODE), eq(GarageMode.GARAGE_MODE_OFF), eq(-1));
+
+        // Once registration with daemon completes, the service post a new message on the main
+        // thread to fetch and sync resource overuse configs.
+        CarServiceUtils.runOnMainSync(() -> {});
+
+        verify(mMockCarWatchdogDaemon, atLeastOnce()).getResourceOveruseConfigurations();
     }
 
     private void verifyDatabaseInit(int wantedInvocations) throws Exception {
@@ -2832,19 +3112,22 @@
                     }
                     return packageInfos;
                 });
-        IPackageManager pm = Mockito.spy(ActivityThread.getPackageManager());
-        when(ActivityThread.getPackageManager()).thenReturn(pm);
         doAnswer((args) -> {
             String value = args.getArgument(3) + ":" + args.getArgument(0);
             mDisabledUserPackages.add(value);
             return null;
-        }).when(pm).setApplicationEnabledSetting(
+        }).when(mSpiedPackageManager).setApplicationEnabledSetting(
                 anyString(), eq(COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED), anyInt(),
                 anyInt(), anyString());
-        doReturn(COMPONENT_ENABLED_STATE_ENABLED).when(pm)
+        doReturn(COMPONENT_ENABLED_STATE_ENABLED).when(mSpiedPackageManager)
                 .getApplicationEnabledSetting(anyString(), anyInt());
     }
 
+    private void setCarPowerState(int powerState) throws Exception {
+        when(mMockCarPowerManagementService.getPowerState()).thenReturn(powerState);
+        mCarPowerStateListener.onStateChanged(powerState);
+    }
+
     private void setDisplayStateEnabled(boolean isEnabled) throws Exception {
         int[] enabledComponents = new int[]{};
         int[] disabledComponents = new int[]{};
@@ -2878,11 +3161,7 @@
         }).when(mMockBinder).linkToDeath(any(), anyInt());
         mockWatchdogDaemon();
         latch.await(MAX_WAIT_TIME_MS, TimeUnit.MILLISECONDS);
-        /* On daemon connect, CarWatchdogService posts a new message on the main thread to fetch
-         * the resource overuse configs. Post a message on the same thread and wait until the fetch
-         * completes, so the tests are deterministic.
-         */
-        CarServiceUtils.runOnMainSync(() -> {});
+        captureAndVerifyRegistrationWithDaemon(/* waitOnMain= */ false);
     }
 
     private void setDate(int numDaysAgo) {
@@ -2904,57 +3183,25 @@
         mTimeSource = timeSource;
     }
 
-    private void verifyResourceOveruseConfigurationsSynced(int wantedInvocations)
-            throws Exception {
-        /*
-         * Syncing the resource configuration in the service with the daemon is done on the main
-         * thread. To ensure the sync completes before verification, execute an empty block on the
-         * main thread.
-         */
-        CarServiceUtils.runOnMainSync(() -> {});
-        verify(mMockCarWatchdogDaemon, times(wantedInvocations)).getResourceOveruseConfigurations();
-    }
-
 
     private void testClientHealthCheck(TestClient client, int badClientCount) throws Exception {
         mCarWatchdogService.registerClient(client, TIMEOUT_CRITICAL);
         mWatchdogServiceForSystemImpl.checkIfAlive(123456, TIMEOUT_CRITICAL);
-        ArgumentCaptor<int[]> notRespondingClients = ArgumentCaptor.forClass(int[].class);
         verify(mMockCarWatchdogDaemon, timeout(MAX_WAIT_TIME_MS)).tellCarWatchdogServiceAlive(
-                eq(mWatchdogServiceForSystemImpl), notRespondingClients.capture(), eq(123456));
-        assertThat(notRespondingClients.getValue().length).isEqualTo(0);
+                eq(mWatchdogServiceForSystemImpl), mIntArrayCaptor.capture(), eq(123456));
+        assertThat(mIntArrayCaptor.getValue()).isEmpty();
         mWatchdogServiceForSystemImpl.checkIfAlive(987654, TIMEOUT_CRITICAL);
         verify(mMockCarWatchdogDaemon, timeout(MAX_WAIT_TIME_MS)).tellCarWatchdogServiceAlive(
-                eq(mWatchdogServiceForSystemImpl), notRespondingClients.capture(), eq(987654));
-        assertThat(notRespondingClients.getValue().length).isEqualTo(badClientCount);
+                eq(mWatchdogServiceForSystemImpl), mIntArrayCaptor.capture(), eq(987654));
+        assertThat(mIntArrayCaptor.getValue().length).isEqualTo(badClientCount);
     }
 
     private List<android.automotive.watchdog.internal.ResourceOveruseConfiguration>
             captureOnSetResourceOveruseConfigurations() throws Exception {
-        ArgumentCaptor<List<android.automotive.watchdog.internal.ResourceOveruseConfiguration>>
-                resourceOveruseConfigurationsCaptor = ArgumentCaptor.forClass(List.class);
         verify(mMockCarWatchdogDaemon, timeout(MAX_WAIT_TIME_MS))
-                .updateResourceOveruseConfigurations(resourceOveruseConfigurationsCaptor.capture());
-        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();
-        // getResourceOveruseConfigurations is posted on the main thread after daemon restart, so
-        // wait for it to complete.
-        CarServiceUtils.runOnMainSync(() -> {});
-
-        /* Method should be invoked 2 times. Once at test setup and once more after the daemon
-         * crashes and reconnects.
-         */
-        verify(mMockCarWatchdogDaemon, times(2)).getResourceOveruseConfigurations();
+                .updateResourceOveruseConfigurations(
+                        mResourceOveruseConfigurationsCaptor.capture());
+        return mResourceOveruseConfigurationsCaptor.getValue();
     }
 
     private SparseArray<PackageIoOveruseStats> injectIoOveruseStatsForPackages(
@@ -3023,15 +3270,13 @@
         // this to complete by posting a task on the main thread.
         CarServiceUtils.runOnMainSync(() -> {});
 
-        ArgumentCaptor<List<PackageResourceOveruseAction>> resourceOveruseActionsCaptor =
-                ArgumentCaptor.forClass((Class) List.class);
         verify(mMockCarWatchdogDaemon,
                 timeout(MAX_WAIT_TIME_MS).atLeastOnce()).actionTakenOnResourceOveruse(
-                resourceOveruseActionsCaptor.capture());
+                mResourceOveruseActionsCaptor.capture());
 
         List<PackageResourceOveruseAction> actual = new ArrayList<>();
         for (List<PackageResourceOveruseAction> actions :
-                resourceOveruseActionsCaptor.getAllValues()) {
+                mResourceOveruseActionsCaptor.getAllValues()) {
             actual.addAll(actions);
         }
 
@@ -3112,8 +3357,55 @@
         return packageIoOveruseStats;
     }
 
-    List<PackageResourceOveruseAction> sampleActionTakenOnOveruse(int[] notKilledUids,
-            int[] killedRecurringOveruseUids) {
+    private static List<AtomsProto.CarWatchdogIoOveruseStatsReported> sampleReportedOveruseStats() {
+        // The below thresholds are from {@link sampleInternalResourceOveruseConfiguration} and
+        // UID/stat are from {@link sampleIoOveruseStats}.
+        AtomsProto.CarWatchdogPerStateBytes systemThreshold =
+                WatchdogPerfHandler.constructCarWatchdogPerStateBytes(10, 20, 30);
+        AtomsProto.CarWatchdogPerStateBytes vendorThreshold =
+                WatchdogPerfHandler.constructCarWatchdogPerStateBytes(20, 40, 60);
+        AtomsProto.CarWatchdogPerStateBytes thirdPartyThreshold =
+                WatchdogPerfHandler.constructCarWatchdogPerStateBytes(30, 60, 90);
+        AtomsProto.CarWatchdogPerStateBytes writtenBytes =
+                WatchdogPerfHandler.constructCarWatchdogPerStateBytes(100, 200, 300);
+        List<AtomsProto.CarWatchdogIoOveruseStatsReported> reportedOveruseStats = new ArrayList<>();
+        reportedOveruseStats.add(constructIoOveruseStatsReported(
+                10010001, systemThreshold, writtenBytes));
+        reportedOveruseStats.add(constructIoOveruseStatsReported(
+                10110001, systemThreshold, writtenBytes));
+        reportedOveruseStats.add(constructIoOveruseStatsReported(
+                10010004, vendorThreshold, writtenBytes));
+        reportedOveruseStats.add(constructIoOveruseStatsReported(
+                10110004, vendorThreshold, writtenBytes));
+        reportedOveruseStats.add(constructIoOveruseStatsReported(
+                10010005, thirdPartyThreshold, writtenBytes));
+        reportedOveruseStats.add(constructIoOveruseStatsReported(
+                10110005, thirdPartyThreshold, writtenBytes));
+        return reportedOveruseStats;
+    }
+
+    private static List<AtomsProto.CarWatchdogKillStatsReported> sampleReportedKillStats(
+            int systemState, int[] killedUids) {
+        // The below thresholds are from {@link sampleInternalResourceOveruseConfiguration} and
+        // UID/stat are from {@link sampleIoOveruseStats}.
+        AtomsProto.CarWatchdogPerStateBytes vendorThreshold =
+                WatchdogPerfHandler.constructCarWatchdogPerStateBytes(20, 40, 60);
+        AtomsProto.CarWatchdogPerStateBytes thirdPartyThreshold =
+                WatchdogPerfHandler.constructCarWatchdogPerStateBytes(30, 60, 90);
+        AtomsProto.CarWatchdogPerStateBytes writtenBytes =
+                WatchdogPerfHandler.constructCarWatchdogPerStateBytes(100, 200, 300);
+        List<AtomsProto.CarWatchdogKillStatsReported> reportedKillStats = new ArrayList<>();
+        for (int uid : killedUids) {
+            AtomsProto.CarWatchdogPerStateBytes threshold =
+                    UserHandle.getAppId(uid) == 10004 ? vendorThreshold : thirdPartyThreshold;
+            reportedKillStats.add(constructIoOveruseKillStatsReported(
+                    uid, systemState, threshold, writtenBytes));
+        }
+        return reportedKillStats;
+    }
+
+    private List<PackageResourceOveruseAction> sampleActionTakenOnOveruse(
+            int[] notKilledUids, int[] killedRecurringOveruseUids) {
         List<PackageResourceOveruseAction> actions = new ArrayList<>();
         for (int uid : notKilledUids) {
             actions.add(constructPackageResourceOveruseAction(
@@ -3163,14 +3455,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);
@@ -3179,49 +3477,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);
@@ -3231,28 +3537,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();
@@ -3377,6 +3696,144 @@
         }
     }
 
+    private static AtomsProto.CarWatchdogIoOveruseStatsReported
+            constructIoOveruseStatsReported(int uid, AtomsProto.CarWatchdogPerStateBytes threshold,
+            AtomsProto.CarWatchdogPerStateBytes writtenBytes) {
+        return constructCarWatchdogIoOveruseStatsReported(
+                uid, WatchdogPerfHandler.constructCarWatchdogIoOveruseStats(
+                        AtomsProto.CarWatchdogIoOveruseStats.Period.DAILY, threshold, writtenBytes)
+        );
+    }
+
+    private static AtomsProto.CarWatchdogIoOveruseStatsReported
+            constructCarWatchdogIoOveruseStatsReported(
+                    int uid, AtomsProto.CarWatchdogIoOveruseStats ioOveruseStats) {
+        return AtomsProto.CarWatchdogIoOveruseStatsReported.newBuilder()
+                .setUid(uid)
+                .setIoOveruseStats(ioOveruseStats)
+                .build();
+    }
+
+    private static AtomsProto.CarWatchdogKillStatsReported constructIoOveruseKillStatsReported(
+            int uid, int systemState, AtomsProto.CarWatchdogPerStateBytes threshold,
+            AtomsProto.CarWatchdogPerStateBytes writtenBytes) {
+        return constructCarWatchdogKillStatsReported(uid,
+                CAR_WATCHDOG_KILL_STATS_REPORTED__UID_STATE__UNKNOWN_UID_STATE, systemState,
+                CAR_WATCHDOG_KILL_STATS_REPORTED__KILL_REASON__KILLED_ON_IO_OVERUSE,
+                WatchdogPerfHandler.constructCarWatchdogIoOveruseStats(
+                        AtomsProto.CarWatchdogIoOveruseStats.Period.DAILY, threshold, writtenBytes)
+        );
+    }
+
+    private static AtomsProto.CarWatchdogKillStatsReported constructCarWatchdogKillStatsReported(
+            int uid, int uidState, int systemState, int killReason,
+            AtomsProto.CarWatchdogIoOveruseStats ioOveruseStats) {
+        return AtomsProto.CarWatchdogKillStatsReported.newBuilder()
+                .setUid(uid)
+                .setUidState(AtomsProto.CarWatchdogKillStatsReported.UidState.forNumber(uidState))
+                .setSystemState(AtomsProto.CarWatchdogKillStatsReported.SystemState.forNumber(
+                        systemState))
+                .setKillReason(AtomsProto.CarWatchdogKillStatsReported.KillReason.forNumber(
+                        killReason))
+                .setIoOveruseStats(ioOveruseStats)
+                .build();
+    }
+
+    private void captureAndVerifyIoOveruseStatsReported(
+            List<AtomsProto.CarWatchdogIoOveruseStatsReported> expected) throws Exception {
+        verify(() -> CarStatsLog.write(eq(CarStatsLog.CAR_WATCHDOG_IO_OVERUSE_STATS_REPORTED),
+                mOverusingUidCaptor.capture(), mOveruseStatsCaptor.capture()),
+                times(expected.size()));
+
+        List<Integer> allUidValues = mOverusingUidCaptor.getAllValues();
+        List<byte[]> allOveruseStatsValues = mOveruseStatsCaptor.getAllValues();
+        List<AtomsProto.CarWatchdogIoOveruseStatsReported> actual = new ArrayList<>();
+        for (int i = 0; i < expected.size(); ++i) {
+            actual.add(constructCarWatchdogIoOveruseStatsReported(allUidValues.get(i),
+                    AtomsProto.CarWatchdogIoOveruseStats.parseFrom(allOveruseStatsValues.get(i))));
+        }
+        assertWithMessage("I/O overuse stats reported to statsd").that(actual)
+                .comparingElementsUsing(Correspondence.from(
+                        CarWatchdogServiceUnitTest::isCarWatchdogIoOveruseStatsReportedEquals,
+                        "is equal to")).containsExactlyElementsIn(expected);
+    }
+
+    private void captureAndVerifyKillStatsReported(
+            List<AtomsProto.CarWatchdogKillStatsReported> expected) throws Exception {
+        // Overuse handling task is posted on the main thread and this task performs disabling and
+        // uploading metrics. Wait for this task to complete.
+        CarServiceUtils.runOnMainSync(() -> {});
+
+        verify(() -> CarStatsLog.write(eq(CarStatsLog.CAR_WATCHDOG_KILL_STATS_REPORTED),
+                mKilledUidCaptor.capture(), mUidStateCaptor.capture(),
+                mSystemStateCaptor.capture(), mKillReasonCaptor.capture(), eq(null),
+                mKilledStatsCaptor.capture()), times(expected.size()));
+
+        List<Integer> allUidValues = mKilledUidCaptor.getAllValues();
+        List<Integer> allUidStateValues = mUidStateCaptor.getAllValues();
+        List<Integer> allSystemStateValues = mSystemStateCaptor.getAllValues();
+        List<Integer> allKillReasonValues = mKillReasonCaptor.getAllValues();
+        List<byte[]> allIoOveruseStatsValues = mKilledStatsCaptor.getAllValues();
+        List<AtomsProto.CarWatchdogKillStatsReported> actual = new ArrayList<>();
+        for (int i = 0; i < expected.size(); ++i) {
+            actual.add(constructCarWatchdogKillStatsReported(allUidValues.get(i),
+                    allUidStateValues.get(i), allSystemStateValues.get(i),
+                    allKillReasonValues.get(i),
+                    AtomsProto.CarWatchdogIoOveruseStats.parseFrom(
+                            allIoOveruseStatsValues.get(i))));
+        }
+        assertWithMessage("I/O overuse kill stats reported to statsd").that(actual)
+                .comparingElementsUsing(Correspondence.from(
+                        CarWatchdogServiceUnitTest::isCarWatchdogKillStatsReported, "is equal to"))
+                .containsExactlyElementsIn(expected);
+    }
+
+    public static boolean isCarWatchdogIoOveruseStatsReportedEquals(
+            AtomsProto.CarWatchdogIoOveruseStatsReported actual,
+            AtomsProto.CarWatchdogIoOveruseStatsReported expected) {
+        return actual.getUid() == expected.getUid()
+                && isCarWatchdogIoOveruseStatsEquals(
+                actual.getIoOveruseStats(), expected.getIoOveruseStats());
+    }
+
+    public static boolean isCarWatchdogKillStatsReported(
+            AtomsProto.CarWatchdogKillStatsReported actual,
+            AtomsProto.CarWatchdogKillStatsReported expected) {
+        return actual.getUid() == expected.getUid()
+                && actual.getUidState() == expected.getUidState()
+                && actual.getSystemState() == expected.getSystemState()
+                && actual.getKillReason() == expected.getKillReason()
+                // Process stats is null on I/O overuse kill stats, so equality checking on the
+                // objects is sufficient.
+                && actual.getProcessStats().equals(expected.getProcessStats())
+                && isCarWatchdogIoOveruseStatsEquals(
+                actual.getIoOveruseStats(), expected.getIoOveruseStats());
+    }
+
+    private static boolean isCarWatchdogIoOveruseStatsEquals(
+            AtomsProto.CarWatchdogIoOveruseStats actual,
+            AtomsProto.CarWatchdogIoOveruseStats expected) {
+        if (actual == null || expected == null) {
+            return (actual == null) && (expected == null);
+        }
+        return actual.getPeriod() == expected.getPeriod()
+                && isCarWatchdogPerStateBytesEquals(actual.getThreshold(), expected.getThreshold())
+                && isCarWatchdogPerStateBytesEquals(
+                actual.getWrittenBytes(), expected.getWrittenBytes())
+                && actual.getUptimeMillis() == expected.getUptimeMillis();
+    }
+
+    private static boolean isCarWatchdogPerStateBytesEquals(
+            AtomsProto.CarWatchdogPerStateBytes actual,
+            AtomsProto.CarWatchdogPerStateBytes expected) {
+        if (actual == null || expected == null) {
+            return (actual == null) && (expected == null);
+        }
+        return actual.getForegroundBytes() == expected.getForegroundBytes()
+                && actual.getBackgroundBytes() == expected.getBackgroundBytes()
+                && actual.getGarageModeBytes() == expected.getGarageModeBytes();
+    }
+
     private class TestClient extends ICarWatchdogServiceCallback.Stub {
         protected int mLastSessionId = INVALID_SESSION_ID;
 
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);