Merge "[RESTRICT AUTOMERGE] Remove obsolete logic from VirtualCamera" into sc-dev
diff --git a/car-lib/src/android/car/telemetry/IScriptExecutor.aidl b/car-lib/src/android/car/telemetry/IScriptExecutor.aidl
new file mode 100644
index 0000000..d7b967b
--- /dev/null
+++ b/car-lib/src/android/car/telemetry/IScriptExecutor.aidl
@@ -0,0 +1,43 @@
+/*
+ * 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 android.car.telemetry;
+
+import android.car.telemetry.IScriptExecutorListener;
+import android.os.Bundle;
+
+/**
+ * An internal API provided by isolated Script Executor process
+ * for executing Lua scripts in a sandboxed environment
+ *
+ * @hide
+ */
+interface IScriptExecutor {
+  /**
+   * Executes a specified function in provided Lua script with given input arguments.
+   *
+   * @param scriptBody complete body of Lua script that also contains the function to be invoked
+   * @param functionName the name of the function to execute
+   * @param publishedData input data provided by the source which the function handles
+   * @param savedState key-value pairs preserved from the previous invocation of the function
+   * @param listener callback for the sandboxed environent to report back script execution results, errors, and logs
+   */
+  void invokeScript(String scriptBody,
+                    String functionName,
+                    in byte[] publishedData,
+                    in @nullable Bundle savedState,
+                    in IScriptExecutorListener listener);
+}
diff --git a/car-lib/src/android/car/telemetry/IScriptExecutorListener.aidl b/car-lib/src/android/car/telemetry/IScriptExecutorListener.aidl
new file mode 100644
index 0000000..d751a61
--- /dev/null
+++ b/car-lib/src/android/car/telemetry/IScriptExecutorListener.aidl
@@ -0,0 +1,73 @@
+/*
+ * 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 android.car.telemetry;
+
+import android.os.Bundle;
+
+/**
+ * Listener for {@code IScriptExecutor#invokeScript}.
+ *
+ * An invocation of a script by Script Executor will result in a call of only one
+ * of the three methods below. If a script fully completes its objective, onScriptFinished
+ * is called. If a script's invocation completes normally, onSuccess is called.
+ * onError is called if any error happens before or during script execution and we
+ * should abandon this run of the script.
+ */
+interface IScriptExecutorListener {
+  /**
+   * Called by ScriptExecutor when the script declares itself as "finished".
+   *
+   * @param result final results of the script that will be uploaded.
+   */
+  void onScriptFinished(in byte[] result);
+
+  /**
+   * Called by ScriptExecutor when a function completes successfully and also provides
+   * optional state that the script wants CarTelemetryService to persist.
+   *
+   * @param stateToPersist key-value pairs to persist
+   */
+  void onSuccess(in @nullable Bundle stateToPersist);
+
+  /**
+   * Default error type.
+   */
+  const int ERROR_TYPE_UNSPECIFIED = 0;
+
+  /**
+   * Used when an error occurs in the ScriptExecutor code.
+   */
+  const int ERROR_TYPE_SCRIPT_EXECUTOR_ERROR = 1;
+
+  /**
+   * Used when an error occurs while executing the Lua script (such as
+   * errors returned by lua_pcall)
+   */
+  const int ERROR_TYPE_LUA_RUNTIME_ERROR = 2;
+
+
+  /**
+   * Called by ScriptExecutor to report errors that prevented the script
+   * from running or completing execution successfully.
+   *
+   * @param errorType type of the error message as defined in this aidl file.
+   * @param messsage the human-readable message containing information helpful for analysis or debugging.
+   * @param stackTrace the stack trace of the error if available.
+   */
+  void onError(int errorType, String message, @nullable String stackTrace);
+}
+
diff --git a/car-lib/src/android/car/watchdog/CarWatchdogManager.java b/car-lib/src/android/car/watchdog/CarWatchdogManager.java
index 588ac13..05cba98 100644
--- a/car-lib/src/android/car/watchdog/CarWatchdogManager.java
+++ b/car-lib/src/android/car/watchdog/CarWatchdogManager.java
@@ -694,6 +694,7 @@
      * @param resourceOveruseFlag Flag to indicate the types of resource overuse configurations to
      *                            return.
      *
+     * @throws IllegalStateException if the system is in an invalid state.
      * @hide
      */
     @SystemApi
diff --git a/car-test-lib/src/android/car/test/mocks/JavaMockitoHelper.java b/car-test-lib/src/android/car/test/mocks/JavaMockitoHelper.java
index aaf12a7..9430723 100644
--- a/car-test-lib/src/android/car/test/mocks/JavaMockitoHelper.java
+++ b/car-test-lib/src/android/car/test/mocks/JavaMockitoHelper.java
@@ -89,8 +89,7 @@
      * {@value #ASYNC_TIMEOUT_MS} ms.
      */
     @NonNull
-    public static <T> T getResult(@NonNull Future<T> future)
-            throws InterruptedException, ExecutionException {
+    public static <T> T getResult(@NonNull Future<T> future) {
         return getResult(future, ASYNC_TIMEOUT_MS);
     }
 
diff --git a/car_product/overlay/frameworks/base/core/res/res/values/config.xml b/car_product/overlay/frameworks/base/core/res/res/values/config.xml
index 52739bf..f675d4e 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values/config.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values/config.xml
@@ -146,4 +146,7 @@
     <!-- The name of the package that will hold the system cluster service role. -->
     <string name="config_systemAutomotiveCluster" translatable="false">android.car.cluster</string>
 
+    <!-- Whether this device is supporting the microphone toggle -->
+    <bool name="config_supportsMicToggle">true</bool>
+
 </resources>
diff --git a/cpp/telemetry/ARCHITECTURE.md b/cpp/telemetry/ARCHITECTURE.md
index ae60c6b..75dd194 100644
--- a/cpp/telemetry/ARCHITECTURE.md
+++ b/cpp/telemetry/ARCHITECTURE.md
@@ -10,9 +10,13 @@
 
 ## Structure
 
-- aidl/            - AIDL declerations
-- products/        - AAOS Telemetry product, it's included in car_base.mk
-- sepolicy         - SELinux policies
-- src/             - Source code
-- *.rc             - rc file to start services
-- *.xml            - VINTF manifest (TODO: needed?)
+```
+aidl/                    - Internal AIDL declerations, for public AIDLs, please see
+                           //frameworks/hardware/interfaces/automotive/telemetry
+products/                - AAOS Telemetry product, it's included in car_base.mk
+sepolicy                 - SELinux policies
+src/                     - Source code
+   TelemetryServer.h     - The main class.
+*.rc                     - rc file to start services
+*.xml                    - VINTF manifest (TODO: needed?)
+```
diff --git a/cpp/telemetry/Android.bp b/cpp/telemetry/Android.bp
index 11ed4dc..4e6c1c0 100644
--- a/cpp/telemetry/Android.bp
+++ b/cpp/telemetry/Android.bp
@@ -12,6 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 cc_defaults {
     name: "cartelemetryd_defaults",
     cflags: [
@@ -20,10 +24,11 @@
         "-Wno-unused-parameter",
     ],
     shared_libs: [
-        "android.frameworks.automotive.telemetry-V1-cpp",
+        "android.automotive.telemetry.internal-ndk_platform",
+        "android.frameworks.automotive.telemetry-V1-ndk_platform",
         "libbase",
+        "libbinder_ndk",
         "liblog",
-        "libbinder",
         "libutils",
     ],
     product_variables: {
@@ -42,6 +47,34 @@
     ],
     srcs: [
         "src/CarTelemetryImpl.cpp",
+        "src/CarTelemetryInternalImpl.cpp",
+        "src/RingBuffer.cpp",
+        "src/TelemetryServer.cpp",
+    ],
+    // Allow dependents to use the header files.
+    export_include_dirs: [
+        "src",
+    ],
+}
+
+cc_test {
+    name: "cartelemetryd_impl_test",
+    defaults: [
+        "cartelemetryd_defaults",
+    ],
+    test_suites: ["general-tests"],
+    srcs: [
+        "tests/CarTelemetryImplTest.cpp",
+        "tests/CarTelemetryInternalImplTest.cpp",
+        "tests/RingBufferTest.cpp",
+    ],
+    // Statically link only in tests, for portability reason.
+    static_libs: [
+        "android.automotive.telemetryd@1.0-impl",
+        "android.automotive.telemetry.internal-ndk_platform",
+        "android.frameworks.automotive.telemetry-V1-ndk_platform",
+        "libgmock",
+        "libgtest",
     ],
 }
 
diff --git a/cpp/telemetry/README.md b/cpp/telemetry/README.md
index e1f277b..a13116d 100644
--- a/cpp/telemetry/README.md
+++ b/cpp/telemetry/README.md
@@ -1,3 +1,19 @@
 # Automotive Telemetry Service
 
 A structured log collection service for CarTelemetryService. See ARCHITECTURE.md to learn internals.
+
+## Useful Commands
+
+**Dump service information**
+
+`adb shell dumpsys android.automotive.telemetry.internal.ICarTelemetryInternal/default`
+
+**Starting emulator**
+
+`aae emulator run -selinux permissive -writable-system`
+
+**Running tests**
+
+`atest cartelemetryd_impl_test:CarTelemetryInternalImplTest#TestSetListenerReturnsOk`
+
+`atest cartelemetryd_impl_test`
diff --git a/cpp/telemetry/aidl/Android.bp b/cpp/telemetry/aidl/Android.bp
new file mode 100644
index 0000000..b4cd9c7
--- /dev/null
+++ b/cpp/telemetry/aidl/Android.bp
@@ -0,0 +1,35 @@
+// 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"],
+}
+
+aidl_interface {
+    name: "android.automotive.telemetry.internal",
+    unstable: true,
+    vendor_available: false,
+    srcs: [
+        "android/automotive/telemetry/internal/*.aidl",
+    ],
+    backend: {
+        ndk: {
+            enabled: true,
+        },
+        java: {
+            platform_apis: true,
+            enabled: true,
+        },
+    }
+}
diff --git a/cpp/telemetry/aidl/android/automotive/telemetry/internal/CarDataInternal.aidl b/cpp/telemetry/aidl/android/automotive/telemetry/internal/CarDataInternal.aidl
new file mode 100644
index 0000000..bf31169
--- /dev/null
+++ b/cpp/telemetry/aidl/android/automotive/telemetry/internal/CarDataInternal.aidl
@@ -0,0 +1,32 @@
+/*
+ * 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 android.automotive.telemetry.internal;
+
+/**
+ * Wrapper for {@code android.frameworks.automotive.telemetry.CarData}.
+ */
+parcelable CarDataInternal {
+  /**
+   * Must be a valid id. Scripts subscribe to data using this id.
+   */
+  int id;
+
+  /**
+   * Content corresponding to the schema defined by the id.
+   */
+  byte[] content;
+}
diff --git a/cpp/telemetry/aidl/android/automotive/telemetry/internal/ICarDataListener.aidl b/cpp/telemetry/aidl/android/automotive/telemetry/internal/ICarDataListener.aidl
new file mode 100644
index 0000000..48ab6f9
--- /dev/null
+++ b/cpp/telemetry/aidl/android/automotive/telemetry/internal/ICarDataListener.aidl
@@ -0,0 +1,35 @@
+/*
+ * 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 android.automotive.telemetry.internal;
+
+import android.automotive.telemetry.internal.CarDataInternal;
+
+/**
+ * Listener for {@code ICarTelemetryInternal#registerListener}.
+ */
+oneway interface ICarDataListener {
+  /**
+   * Called by ICarTelemetry when the data are available to be consumed. ICarTelemetry removes
+   * the delivered data when the callback succeeds.
+   *
+   * <p>If the collected data is too large, it will send only chunk of the data, and the callback
+   * will be fired again.
+   *
+   * @param dataList the pushed data.
+   */
+  void onCarDataReceived(in CarDataInternal[] dataList);
+}
diff --git a/cpp/telemetry/aidl/android/automotive/telemetry/internal/ICarTelemetryInternal.aidl b/cpp/telemetry/aidl/android/automotive/telemetry/internal/ICarTelemetryInternal.aidl
new file mode 100644
index 0000000..b8938ff
--- /dev/null
+++ b/cpp/telemetry/aidl/android/automotive/telemetry/internal/ICarTelemetryInternal.aidl
@@ -0,0 +1,38 @@
+/*
+ * 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 android.automotive.telemetry.internal;
+
+import android.automotive.telemetry.internal.ICarDataListener;
+
+/**
+ * An internal API provided by cartelemetryd for receiving the collected data.
+ */
+interface ICarTelemetryInternal {
+  /**
+   * Sets a listener for CarData. If there are existing CarData in the buffer, the daemon will
+   * start pushing them to the listener. There can be only a single registered listener at a time.
+   *
+   * @param listener the only listener.
+   * @throws IllegalStateException if someone is already registered or the listener is dead.
+   */
+  void setListener(in ICarDataListener listener);
+
+  /**
+   * Clears the listener if exists. Silently ignores if there is no listener.
+   */
+  void clearListener();
+}
diff --git a/cpp/telemetry/sampleclient/Android.bp b/cpp/telemetry/sampleclient/Android.bp
index ec608c4..17fdee0 100644
--- a/cpp/telemetry/sampleclient/Android.bp
+++ b/cpp/telemetry/sampleclient/Android.bp
@@ -13,6 +13,10 @@
 // limitations under the License.
 
 // Sample client for ICarTelemetry service.
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 cc_binary {
     name: "android.automotive.telemetryd-sampleclient",
     srcs: [
diff --git a/cpp/telemetry/sampleclient/README.md b/cpp/telemetry/sampleclient/README.md
index 8e8f3cc..ebbaf16 100644
--- a/cpp/telemetry/sampleclient/README.md
+++ b/cpp/telemetry/sampleclient/README.md
@@ -2,11 +2,27 @@
 
 This is a sample vendor service that sends `CarData` to car telemetry service.
 
+## Running
+
+**1. Quick mode - under root**
+
+```
+m -j android.automotive.telemetryd-sampleclient
+
+adb remount  # make sure run "adb disable-verity" before remounting
+adb push $ANDROID_PRODUCT_OUT/vendor/bin/android.automotive.telemetryd-sampleclient /system/bin/
+
+adb shell /system/bin/android.automotive.telemetryd-sampleclient
+
+# Then check logcat and dumpsys to verify the results.
+```
+
+**2. Under vendor**
+
 To include it in the final image, add
 `PRODUCT_PACKAGES += android.automotive.telemetryd-sampleclient` to
 `//packages/services/Car/cpp/telemetry/products/telemetry.mk` (or other suitable mk file).
 
-Example:
 ```
 # this goes to products/telemetry.mk
 
diff --git a/cpp/telemetry/sampleinternalclient/Android.bp b/cpp/telemetry/sampleinternalclient/Android.bp
new file mode 100644
index 0000000..d04ac09
--- /dev/null
+++ b/cpp/telemetry/sampleinternalclient/Android.bp
@@ -0,0 +1,36 @@
+// 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.
+
+// Sample client for ICarTelemetry service.
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_binary {
+    name: "android.automotive.telemetryd-sampleinternalclient",
+    srcs: [
+        "main.cpp",
+    ],
+    cflags: [
+        "-Werror",
+        "-Wall",
+        "-Wno-unused-parameter",
+    ],
+    shared_libs: [
+        "android.automotive.telemetry.internal-ndk_platform",
+        "libbase",
+        "libbinder_ndk",
+        "libutils",
+    ],
+}
diff --git a/cpp/telemetry/sampleinternalclient/main.cpp b/cpp/telemetry/sampleinternalclient/main.cpp
new file mode 100644
index 0000000..06ca549
--- /dev/null
+++ b/cpp/telemetry/sampleinternalclient/main.cpp
@@ -0,0 +1,78 @@
+/*
+ * 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.
+ */
+
+// This is a sample reader client for ICarTelemetryInternal.
+// TODO(b/186017953): remove this client when CarTelemetryService is implemented.
+//
+// adb remount  # make sure run "adb disable-verity" before remounting
+// adb push $ANDROID_PRODUCT_OUT/system/bin/android.automotive.telemetryd-sampleinternalclient
+// /system/bin/
+//
+// adb shell /system/bin/android.automotive.telemetryd-sampleinternalclient
+
+#define LOG_TAG "cartelemetryd_sampleint"
+
+#include <aidl/android/automotive/telemetry/internal/BnCarDataListener.h>
+#include <aidl/android/automotive/telemetry/internal/CarDataInternal.h>
+#include <aidl/android/automotive/telemetry/internal/ICarTelemetryInternal.h>
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
+
+using ::aidl::android::automotive::telemetry::internal::BnCarDataListener;
+using ::aidl::android::automotive::telemetry::internal::CarDataInternal;
+using ::aidl::android::automotive::telemetry::internal::ICarDataListener;
+using ::aidl::android::automotive::telemetry::internal::ICarTelemetryInternal;
+using ::android::base::StringPrintf;
+
+class CarDataListenerImpl : public BnCarDataListener {
+public:
+    ::ndk::ScopedAStatus onCarDataReceived(
+            const std::vector<CarDataInternal>& in_dataList) override;
+};
+
+::ndk::ScopedAStatus CarDataListenerImpl::onCarDataReceived(
+        const std::vector<CarDataInternal>& dataList) {
+    LOG(INFO) << "Received data size = " << dataList.size();
+    for (const auto data : dataList) {
+        LOG(INFO) << "data.id = " << data.id;
+    }
+    return ::ndk::ScopedAStatus::ok();
+}
+
+int main(int argc, char* argv[]) {
+    // The name of the service is described in
+    // https://source.android.com/devices/architecture/aidl/aidl-hals#instance-names
+    const std::string instance = StringPrintf("%s/default", ICarTelemetryInternal::descriptor);
+    LOG(INFO) << "Obtaining: " << instance;
+    std::shared_ptr<ICarTelemetryInternal> service = ICarTelemetryInternal::fromBinder(
+            ndk::SpAIBinder(AServiceManager_getService(instance.c_str())));
+    if (!service) {
+        LOG(FATAL) << "ICarTelemetryInternal service not found, may be still initializing?";
+    }
+
+    LOG(INFO) << "Setting the listener";
+    std::shared_ptr<CarDataListenerImpl> listener = ndk::SharedRefBase::make<CarDataListenerImpl>();
+    auto status = service->setListener(listener);
+    if (!status.isOk()) {
+        LOG(FATAL) << "Failed to set the listener";
+    }
+
+    ::ABinderProcess_startThreadPool();
+    ::ABinderProcess_joinThreadPool();
+    return 1;  // not reachable
+}
diff --git a/cpp/telemetry/sepolicy/private/service_contexts b/cpp/telemetry/sepolicy/private/service_contexts
index 24d7df3..f26b5f8 100644
--- a/cpp/telemetry/sepolicy/private/service_contexts
+++ b/cpp/telemetry/sepolicy/private/service_contexts
@@ -1 +1,2 @@
 android.frameworks.automotive.telemetry.ICarTelemetry/default  u:object_r:cartelemetryd_service:s0
+android.automotive.telemetry.internal.ICarTelemetryInternal/default  u:object_r:cartelemetryd_service:s0
diff --git a/cpp/telemetry/src/BufferedCarData.h b/cpp/telemetry/src/BufferedCarData.h
new file mode 100644
index 0000000..0c6ff4d
--- /dev/null
+++ b/cpp/telemetry/src/BufferedCarData.h
@@ -0,0 +1,55 @@
+/*
+ * 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.
+ */
+
+#ifndef CPP_TELEMETRY_SRC_BUFFEREDCARDATA_H_
+#define CPP_TELEMETRY_SRC_BUFFEREDCARDATA_H_
+
+#include <stdint.h>
+
+#include <tuple>
+#include <vector>
+
+namespace android {
+namespace automotive {
+namespace telemetry {
+
+// Internally stored `CarData` with some extras.
+struct BufferedCarData {
+    BufferedCarData(BufferedCarData&& other) = default;
+    BufferedCarData(const BufferedCarData&) = default;
+    BufferedCarData& operator=(BufferedCarData&& other) = default;
+    BufferedCarData& operator=(const BufferedCarData&) = default;
+
+    inline bool operator==(const BufferedCarData& rhs) const {
+        return std::tie(mId, mContent, mPublisherUid) ==
+                std::tie(rhs.mId, rhs.mContent, rhs.mPublisherUid);
+    }
+
+    // Returns the size of the stored data. Note that it's not the exact size of the struct.
+    int32_t contentSizeInBytes() const { return mContent.size(); }
+
+    const int32_t mId;
+    const std::vector<uint8_t> mContent;
+
+    // The uid of the logging client.
+    const uid_t mPublisherUid;
+};
+
+}  // namespace telemetry
+}  // namespace automotive
+}  // namespace android
+
+#endif  // CPP_TELEMETRY_SRC_BUFFEREDCARDATA_H_
diff --git a/cpp/telemetry/src/CarTelemetryImpl.cpp b/cpp/telemetry/src/CarTelemetryImpl.cpp
index f3187ec..34a4e60 100644
--- a/cpp/telemetry/src/CarTelemetryImpl.cpp
+++ b/cpp/telemetry/src/CarTelemetryImpl.cpp
@@ -16,24 +16,34 @@
 
 #include "CarTelemetryImpl.h"
 
-#include <android-base/logging.h>
-#include <android/frameworks/automotive/telemetry/CarData.h>
+#include "BufferedCarData.h"
+
+#include <aidl/android/frameworks/automotive/telemetry/CarData.h>
+#include <android/binder_ibinder.h>
+
+#include <stdio.h>
+
+#include <memory>
 
 namespace android {
 namespace automotive {
 namespace telemetry {
 
-using ::android::binder::Status;
-using ::android::frameworks::automotive::telemetry::CarData;
+using ::aidl::android::frameworks::automotive::telemetry::CarData;
 
-Status CarTelemetryImpl::write(const std::vector<CarData>& dataList) {
-    LOG(INFO) << "write called";
-    return Status::ok();
+CarTelemetryImpl::CarTelemetryImpl(RingBuffer* buffer) : mRingBuffer(buffer) {}
+
+// TODO(b/174608802): Add 10kb size check for the `dataList`, see the AIDL for the limits
+ndk::ScopedAStatus CarTelemetryImpl::write(const std::vector<CarData>& dataList) {
+    uid_t publisherUid = ::AIBinder_getCallingUid();
+    for (auto&& data : dataList) {
+        mRingBuffer->push({.mId = data.id,
+                           .mContent = std::move(data.content),
+                           .mPublisherUid = publisherUid});
+    }
+    return ndk::ScopedAStatus::ok();
 }
 
-status_t CarTelemetryImpl::dump(int fd, const Vector<String16>& args) {
-    return android::OK;
-}
 }  // namespace telemetry
 }  // namespace automotive
 }  // namespace android
diff --git a/cpp/telemetry/src/CarTelemetryImpl.h b/cpp/telemetry/src/CarTelemetryImpl.h
index f342248..a3bb6c1 100644
--- a/cpp/telemetry/src/CarTelemetryImpl.h
+++ b/cpp/telemetry/src/CarTelemetryImpl.h
@@ -17,22 +17,31 @@
 #ifndef CPP_TELEMETRY_SRC_CARTELEMETRYIMPL_H_
 #define CPP_TELEMETRY_SRC_CARTELEMETRYIMPL_H_
 
-#include <android/frameworks/automotive/telemetry/BnCarTelemetry.h>
-#include <android/frameworks/automotive/telemetry/CarData.h>
+#include "RingBuffer.h"
+
+#include <aidl/android/frameworks/automotive/telemetry/BnCarTelemetry.h>
+#include <aidl/android/frameworks/automotive/telemetry/CarData.h>
 #include <utils/String16.h>
 #include <utils/Vector.h>
 
+#include <vector>
+
 namespace android {
 namespace automotive {
 namespace telemetry {
 
 // Implementation of android.frameworks.automotive.telemetry.ICarTelemetry.
-class CarTelemetryImpl : public android::frameworks::automotive::telemetry::BnCarTelemetry {
+class CarTelemetryImpl : public aidl::android::frameworks::automotive::telemetry::BnCarTelemetry {
 public:
-    android::binder::Status write(
-            const std::vector<android::frameworks::automotive::telemetry::CarData>& dataList)
+    // Doesn't own `buffer`.
+    explicit CarTelemetryImpl(RingBuffer* buffer);
+
+    ndk::ScopedAStatus write(
+            const std::vector<aidl::android::frameworks::automotive::telemetry::CarData>& dataList)
             override;
-    status_t dump(int fd, const android::Vector<android::String16>& args) override;
+
+private:
+    RingBuffer* mRingBuffer;  // not owned
 };
 
 }  // namespace telemetry
diff --git a/cpp/telemetry/src/CarTelemetryInternalImpl.cpp b/cpp/telemetry/src/CarTelemetryInternalImpl.cpp
new file mode 100644
index 0000000..7a3b141
--- /dev/null
+++ b/cpp/telemetry/src/CarTelemetryInternalImpl.cpp
@@ -0,0 +1,103 @@
+/*
+ * 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.
+ */
+
+#include "CarTelemetryInternalImpl.h"
+
+#include <aidl/android/automotive/telemetry/internal/BnCarDataListener.h>
+#include <aidl/android/automotive/telemetry/internal/CarDataInternal.h>
+#include <aidl/android/automotive/telemetry/internal/ICarDataListener.h>
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+
+namespace android {
+namespace automotive {
+namespace telemetry {
+
+using ::aidl::android::automotive::telemetry::internal::BnCarDataListener;
+using ::aidl::android::automotive::telemetry::internal::CarDataInternal;
+using ::aidl::android::automotive::telemetry::internal::ICarDataListener;
+using ::android::base::StringPrintf;
+
+CarTelemetryInternalImpl::CarTelemetryInternalImpl(RingBuffer* buffer) :
+      mRingBuffer(buffer),
+      mBinderDeathRecipient(
+              ::AIBinder_DeathRecipient_new(CarTelemetryInternalImpl::listenerBinderDied)) {}
+
+ndk::ScopedAStatus CarTelemetryInternalImpl::setListener(
+        const std::shared_ptr<ICarDataListener>& listener) {
+    const std::scoped_lock<std::mutex> lock(mMutex);
+
+    if (mCarDataListener != nullptr) {
+        return ndk::ScopedAStatus::fromExceptionCodeWithMessage(::EX_ILLEGAL_STATE,
+                                                                "ICarDataListener is already set.");
+    }
+
+    // If passed a local binder, AIBinder_linkToDeath will do nothing and return
+    // STATUS_INVALID_OPERATION. We ignore this case because we only use local binders in tests
+    // where this is not an error.
+    if (listener->isRemote()) {
+        auto status = ndk::ScopedAStatus::fromStatus(
+                ::AIBinder_linkToDeath(listener->asBinder().get(), mBinderDeathRecipient.get(),
+                                       this));
+        if (!status.isOk()) {
+            return ndk::ScopedAStatus::fromExceptionCodeWithMessage(::EX_ILLEGAL_STATE,
+                                                                    status.getMessage());
+        }
+    }
+
+    mCarDataListener = listener;
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus CarTelemetryInternalImpl::clearListener() {
+    const std::scoped_lock<std::mutex> lock(mMutex);
+    if (mCarDataListener == nullptr) {
+        LOG(INFO) << __func__ << ": No ICarDataListener, ignoring the call";
+        return ndk::ScopedAStatus::ok();
+    }
+    auto status = ndk::ScopedAStatus::fromStatus(
+            ::AIBinder_unlinkToDeath(mCarDataListener->asBinder().get(),
+                                     mBinderDeathRecipient.get(), this));
+    if (!status.isOk()) {
+        LOG(WARNING) << __func__
+                     << ": unlinkToDeath failed, continuing anyway: " << status.getMessage();
+    }
+    mCarDataListener = nullptr;
+    return ndk::ScopedAStatus::ok();
+}
+
+binder_status_t CarTelemetryInternalImpl::dump(int fd, const char** args, uint32_t numArgs) {
+    dprintf(fd, "ICarTelemetryInternal:\n");
+    mRingBuffer->dump(fd);
+    return ::STATUS_OK;
+}
+
+// Removes the listener if its binder dies.
+void CarTelemetryInternalImpl::listenerBinderDiedImpl() {
+    LOG(WARNING) << "A ICarDataListener died, removing the listener.";
+    const std::scoped_lock<std::mutex> lock(mMutex);
+    mCarDataListener = nullptr;
+}
+
+void CarTelemetryInternalImpl::listenerBinderDied(void* cookie) {
+    auto thiz = static_cast<CarTelemetryInternalImpl*>(cookie);
+    thiz->listenerBinderDiedImpl();
+}
+
+}  // namespace telemetry
+}  // namespace automotive
+}  // namespace android
diff --git a/cpp/telemetry/src/CarTelemetryInternalImpl.h b/cpp/telemetry/src/CarTelemetryInternalImpl.h
new file mode 100644
index 0000000..12ad5cd
--- /dev/null
+++ b/cpp/telemetry/src/CarTelemetryInternalImpl.h
@@ -0,0 +1,68 @@
+/*
+ * 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.
+ */
+
+#ifndef CPP_TELEMETRY_SRC_CARTELEMETRYINTERNALIMPL_H_
+#define CPP_TELEMETRY_SRC_CARTELEMETRYINTERNALIMPL_H_
+
+#include "RingBuffer.h"
+
+#include <aidl/android/automotive/telemetry/internal/BnCarTelemetryInternal.h>
+#include <aidl/android/automotive/telemetry/internal/CarDataInternal.h>
+#include <aidl/android/automotive/telemetry/internal/ICarDataListener.h>
+#include <android/binder_status.h>
+#include <utils/Mutex.h>
+#include <utils/String16.h>
+#include <utils/Vector.h>
+
+namespace android {
+namespace automotive {
+namespace telemetry {
+
+// Implementation of android.automotive.telemetry.ICarTelemetryInternal.
+class CarTelemetryInternalImpl :
+      public aidl::android::automotive::telemetry::internal::BnCarTelemetryInternal {
+public:
+    // Doesn't own `buffer`.
+    explicit CarTelemetryInternalImpl(RingBuffer* buffer);
+
+    ndk::ScopedAStatus setListener(
+            const std::shared_ptr<aidl::android::automotive::telemetry::internal::ICarDataListener>&
+                    listener) override;
+
+    ndk::ScopedAStatus clearListener() override;
+
+    binder_status_t dump(int fd, const char** args, uint32_t numArgs) override;
+
+private:
+    // Death recipient callback that is called when ICarDataListener dies.
+    // The cookie is a pointer to a CarTelemetryInternalImpl object.
+    static void listenerBinderDied(void* cookie);
+
+    void listenerBinderDiedImpl();
+
+    RingBuffer* mRingBuffer;  // not owned
+    ndk::ScopedAIBinder_DeathRecipient mBinderDeathRecipient;
+    std::mutex mMutex;  // a mutex for the whole instance
+
+    std::shared_ptr<aidl::android::automotive::telemetry::internal::ICarDataListener>
+            mCarDataListener GUARDED_BY(mMutex);
+};
+
+}  // namespace telemetry
+}  // namespace automotive
+}  // namespace android
+
+#endif  // CPP_TELEMETRY_SRC_CARTELEMETRYINTERNALIMPL_H_
diff --git a/cpp/telemetry/src/RingBuffer.cpp b/cpp/telemetry/src/RingBuffer.cpp
new file mode 100644
index 0000000..36de3f8
--- /dev/null
+++ b/cpp/telemetry/src/RingBuffer.cpp
@@ -0,0 +1,62 @@
+/*
+ * 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.
+ */
+
+#include "RingBuffer.h"
+
+#include <android-base/logging.h>
+
+#include <inttypes.h>  // for PRIu64 and friends
+
+#include <memory>
+
+namespace android {
+namespace automotive {
+namespace telemetry {
+
+RingBuffer::RingBuffer(int32_t limit) : mSizeLimit(limit) {}
+
+void RingBuffer::push(BufferedCarData&& data) {
+    const std::scoped_lock<std::mutex> lock(mMutex);
+    mList.push_back(std::move(data));
+    while (mList.size() > mSizeLimit) {
+        mList.pop_front();
+        mTotalDroppedDataCount += 1;
+    }
+}
+
+BufferedCarData RingBuffer::popFront() {
+    const std::scoped_lock<std::mutex> lock(mMutex);
+    auto result = std::move(mList.front());
+    mList.pop_front();
+    return result;
+}
+
+void RingBuffer::dump(int fd) const {
+    const std::scoped_lock<std::mutex> lock(mMutex);
+    dprintf(fd, "RingBuffer:\n");
+    dprintf(fd, "  mSizeLimit=%d\n", mSizeLimit);
+    dprintf(fd, "  mList.size=%zu\n", mList.size());
+    dprintf(fd, "  mTotalDroppedDataCount=%" PRIu64 "\n", mTotalDroppedDataCount);
+}
+
+int32_t RingBuffer::size() const {
+    const std::scoped_lock<std::mutex> lock(mMutex);
+    return mList.size();
+}
+
+}  // namespace telemetry
+}  // namespace automotive
+}  // namespace android
diff --git a/cpp/telemetry/src/RingBuffer.h b/cpp/telemetry/src/RingBuffer.h
new file mode 100644
index 0000000..07ce709
--- /dev/null
+++ b/cpp/telemetry/src/RingBuffer.h
@@ -0,0 +1,72 @@
+/*
+ * 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.
+ */
+
+#ifndef CPP_TELEMETRY_SRC_RINGBUFFER_H_
+#define CPP_TELEMETRY_SRC_RINGBUFFER_H_
+
+#include "BufferedCarData.h"
+
+#include <list>
+#include <mutex>
+
+namespace android {
+namespace automotive {
+namespace telemetry {
+
+// A ring buffer that holds BufferedCarData. It drops old data if it's full.
+// Thread-safe.
+class RingBuffer {
+public:
+    // RingBuffer limits the number of elements in the buffer to the given param `sizeLimit`.
+    // Doesn't pre-allocate the memory.
+    explicit RingBuffer(int32_t sizeLimit);
+
+    // Not copyable or movable
+    RingBuffer(const RingBuffer&) = delete;
+    RingBuffer& operator=(const RingBuffer&) = delete;
+    RingBuffer(RingBuffer&&) = delete;
+    RingBuffer& operator=(RingBuffer&&) = delete;
+
+    // Pushes the data to the buffer. If the buffer is full, it removes the oldest data.
+    // Supports moving the data to the RingBuffer.
+    void push(BufferedCarData&& data);
+
+    // Returns the oldest element from the ring buffer and removes it from the buffer.
+    BufferedCarData popFront();
+
+    // Dumps the current state for dumpsys.
+    void dump(int fd) const;
+
+    // Returns the number of elements in the buffer.
+    int32_t size() const;
+
+private:
+    mutable std::mutex mMutex;  // a mutex for the whole instance
+
+    const int32_t mSizeLimit;
+
+    // TODO(b/174608802): Improve dropped CarData handling, see ag/13818937 for details.
+    int64_t mTotalDroppedDataCount;
+
+    // Linked list that holds all the data and allows deleting old data when the buffer is full.
+    std::list<BufferedCarData> mList;
+};
+
+}  // namespace telemetry
+}  // namespace automotive
+}  // namespace android
+
+#endif  // CPP_TELEMETRY_SRC_RINGBUFFER_H_
diff --git a/cpp/telemetry/src/TelemetryServer.cpp b/cpp/telemetry/src/TelemetryServer.cpp
new file mode 100644
index 0000000..54cd3c4
--- /dev/null
+++ b/cpp/telemetry/src/TelemetryServer.cpp
@@ -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.
+ */
+
+#include "TelemetryServer.h"
+
+#include "CarTelemetryImpl.h"
+#include "RingBuffer.h"
+
+#include <android-base/chrono_utils.h>
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <android/binder_interface_utils.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
+
+#include <inttypes.h>  // for PRIu64 and friends
+
+#include <memory>
+#include <thread>  // NOLINT(build/c++11)
+
+namespace android {
+namespace automotive {
+namespace telemetry {
+
+using ::android::automotive::telemetry::RingBuffer;
+
+constexpr const char kCarTelemetryServiceName[] =
+        "android.frameworks.automotive.telemetry.ICarTelemetry/default";
+constexpr const char kCarTelemetryInternalServiceName[] =
+        "android.automotive.telemetry.internal.ICarTelemetryInternal/default";
+
+// TODO(b/183444070): make it configurable using sysprop
+// CarData count limit in the RingBuffer. In worst case it will use kMaxBufferSize * 10Kb memory,
+// which is ~ 1MB.
+const int kMaxBufferSize = 100;
+
+TelemetryServer::TelemetryServer() : mRingBuffer(kMaxBufferSize) {}
+
+void TelemetryServer::registerServices() {
+    std::shared_ptr<CarTelemetryImpl> telemetry =
+            ndk::SharedRefBase::make<CarTelemetryImpl>(&mRingBuffer);
+    std::shared_ptr<CarTelemetryInternalImpl> telemetryInternal =
+            ndk::SharedRefBase::make<CarTelemetryInternalImpl>(&mRingBuffer);
+
+    // Wait for the service manager before starting ICarTelemetry service.
+    while (android::base::GetProperty("init.svc.servicemanager", "") != "running") {
+        // Poll frequent enough so the writer clients can connect to the service during boot.
+        std::this_thread::sleep_for(250ms);
+    }
+
+    LOG(VERBOSE) << "Registering " << kCarTelemetryServiceName;
+    binder_exception_t exception =
+            ::AServiceManager_addService(telemetry->asBinder().get(), kCarTelemetryServiceName);
+    if (exception != ::EX_NONE) {
+        LOG(FATAL) << "Unable to register " << kCarTelemetryServiceName
+                   << ", exception=" << exception;
+    }
+
+    LOG(VERBOSE) << "Registering " << kCarTelemetryInternalServiceName;
+    exception = ::AServiceManager_addService(telemetryInternal->asBinder().get(),
+                                             kCarTelemetryInternalServiceName);
+    if (exception != ::EX_NONE) {
+        LOG(FATAL) << "Unable to register " << kCarTelemetryInternalServiceName
+                   << ", exception=" << exception;
+    }
+}
+
+void TelemetryServer::startAndJoinThreadPool() {
+    ::ABinderProcess_startThreadPool();  // Starts the default 15 binder threads.
+    ::ABinderProcess_joinThreadPool();
+}
+
+}  // namespace telemetry
+}  // namespace automotive
+}  // namespace android
diff --git a/cpp/telemetry/src/TelemetryServer.h b/cpp/telemetry/src/TelemetryServer.h
new file mode 100644
index 0000000..0b400a1
--- /dev/null
+++ b/cpp/telemetry/src/TelemetryServer.h
@@ -0,0 +1,48 @@
+/*
+ * 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.
+ */
+
+#ifndef CPP_TELEMETRY_SRC_TELEMETRYSERVER_H_
+#define CPP_TELEMETRY_SRC_TELEMETRYSERVER_H_
+
+#include "CarTelemetryImpl.h"
+#include "CarTelemetryInternalImpl.h"
+
+#include <utils/Errors.h>
+
+namespace android {
+namespace automotive {
+namespace telemetry {
+
+class TelemetryServer {
+public:
+    TelemetryServer();
+
+    // Registers all the implemented AIDL services. Waits until `servicemanager` is available.
+    // Aborts the process if fails.
+    void registerServices();
+
+    // Blocks the thread.
+    void startAndJoinThreadPool();
+
+private:
+    RingBuffer mRingBuffer;
+};
+
+}  // namespace telemetry
+}  // namespace automotive
+}  // namespace android
+
+#endif  // CPP_TELEMETRY_SRC_TELEMETRYSERVER_H_
diff --git a/cpp/telemetry/src/main.cpp b/cpp/telemetry/src/main.cpp
index 1864c56..1bd0dde 100644
--- a/cpp/telemetry/src/main.cpp
+++ b/cpp/telemetry/src/main.cpp
@@ -14,46 +14,23 @@
  * limitations under the License.
  */
 
-#include "CarTelemetryImpl.h"
+#include "TelemetryServer.h"
 
-#include <android-base/chrono_utils.h>
 #include <android-base/logging.h>
-#include <android-base/properties.h>
-#include <binder/IPCThreadState.h>
-#include <binder/IServiceManager.h>
-#include <binder/ProcessState.h>
 
-#include <thread>  // NOLINT(build/c++11)
-
-using ::android::String16;
-using ::android::automotive::telemetry::CarTelemetryImpl;
-
-constexpr const char kCarTelemetryServiceName[] =
-        "android.frameworks.automotive.telemetry.ICarTelemetry/default";
+using ::android::automotive::telemetry::TelemetryServer;
 
 // TODO(b/174608802): handle SIGQUIT/SIGTERM
 
 int main(void) {
     LOG(INFO) << "Starting cartelemetryd";
 
-    android::sp<CarTelemetryImpl> telemetry = new CarTelemetryImpl();
+    TelemetryServer server;
 
-    // Wait for the service manager before starting ICarTelemetry service.
-    while (android::base::GetProperty("init.svc.servicemanager", "") != "running") {
-        // Poll frequent enough so the writer clients can connect to the service during boot.
-        std::this_thread::sleep_for(250ms);
-    }
-
-    LOG(VERBOSE) << "Registering " << kCarTelemetryServiceName;
-    auto status = android::defaultServiceManager()->addService(String16(kCarTelemetryServiceName),
-                                                               telemetry);
-    if (status != android::OK) {
-        LOG(ERROR) << "Unable to register " << kCarTelemetryServiceName << ", status=" << status;
-        return 1;
-    }
+    // Register AIDL services. Aborts the server if fails.
+    server.registerServices();
 
     LOG(VERBOSE) << "Service is created, joining the threadpool";
-    android::ProcessState::self()->startThreadPool();  // Starts default 15 binder threads.
-    android::IPCThreadState::self()->joinThreadPool();
+    server.startAndJoinThreadPool();
     return 1;  // never reaches
 }
diff --git a/cpp/telemetry/tests/CarTelemetryImplTest.cpp b/cpp/telemetry/tests/CarTelemetryImplTest.cpp
new file mode 100644
index 0000000..0286477
--- /dev/null
+++ b/cpp/telemetry/tests/CarTelemetryImplTest.cpp
@@ -0,0 +1,99 @@
+/*
+ * Copyright 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.
+ */
+
+#include "CarTelemetryImpl.h"
+#include "RingBuffer.h"
+
+#include <aidl/android/frameworks/automotive/telemetry/CarData.h>
+#include <aidl/android/frameworks/automotive/telemetry/ICarTelemetry.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <unistd.h>
+
+#include <memory>
+
+namespace android {
+namespace automotive {
+namespace telemetry {
+
+using ::aidl::android::frameworks::automotive::telemetry::CarData;
+using ::aidl::android::frameworks::automotive::telemetry::ICarTelemetry;
+using ::testing::ContainerEq;
+
+const size_t kMaxBufferSize = 5;
+
+CarData buildCarData(int id, const std::vector<uint8_t>& content) {
+    CarData msg;
+    msg.id = id;
+    msg.content = content;
+    return msg;
+}
+
+BufferedCarData buildBufferedCarData(const CarData& data, uid_t publisherUid) {
+    return {.mId = data.id, .mContent = std::move(data.content), .mPublisherUid = publisherUid};
+}
+
+class CarTelemetryImplTest : public ::testing::Test {
+protected:
+    CarTelemetryImplTest() :
+          mBuffer(RingBuffer(kMaxBufferSize)),
+          mTelemetry(ndk::SharedRefBase::make<CarTelemetryImpl>(&mBuffer)) {}
+
+    RingBuffer mBuffer;
+    std::shared_ptr<ICarTelemetry> mTelemetry;
+};
+
+TEST_F(CarTelemetryImplTest, WriteReturnsOkStatus) {
+    CarData msg = buildCarData(101, {1, 0, 1, 0});
+
+    auto status = mTelemetry->write({msg});
+
+    EXPECT_TRUE(status.isOk()) << status.getMessage();
+}
+
+TEST_F(CarTelemetryImplTest, WriteAddsCarDataToRingBuffer) {
+    CarData msg = buildCarData(101, {1, 0, 1, 0});
+
+    mTelemetry->write({msg});
+
+    EXPECT_EQ(mBuffer.popFront(), buildBufferedCarData(msg, getuid()));
+}
+
+TEST_F(CarTelemetryImplTest, WriteBuffersOnlyLimitedAmount) {
+    RingBuffer buffer(/* sizeLimit= */ 3);
+    auto telemetry = ndk::SharedRefBase::make<CarTelemetryImpl>(&buffer);
+
+    CarData msg101_2 = buildCarData(101, {1, 0});
+    CarData msg101_4 = buildCarData(101, {1, 0, 1, 0});
+    CarData msg201_3 = buildCarData(201, {3, 3, 3});
+
+    // Inserting 5 elements
+    telemetry->write({msg101_2, msg101_4, msg101_4, msg201_3});
+    telemetry->write({msg201_3});
+
+    EXPECT_EQ(buffer.size(), 3);
+    std::vector<BufferedCarData> result = {buffer.popFront(), buffer.popFront(), buffer.popFront()};
+    std::vector<BufferedCarData> expected = {buildBufferedCarData(msg101_4, getuid()),
+                                             buildBufferedCarData(msg201_3, getuid()),
+                                             buildBufferedCarData(msg201_3, getuid())};
+    EXPECT_THAT(result, ContainerEq(expected));
+    EXPECT_EQ(buffer.size(), 0);
+}
+
+}  // namespace telemetry
+}  // namespace automotive
+}  // namespace android
diff --git a/cpp/telemetry/tests/CarTelemetryInternalImplTest.cpp b/cpp/telemetry/tests/CarTelemetryInternalImplTest.cpp
new file mode 100644
index 0000000..22838cd
--- /dev/null
+++ b/cpp/telemetry/tests/CarTelemetryInternalImplTest.cpp
@@ -0,0 +1,85 @@
+/*
+ * Copyright 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.
+ */
+
+#include "CarTelemetryInternalImpl.h"
+#include "RingBuffer.h"
+
+#include <aidl/android/automotive/telemetry/internal/BnCarDataListener.h>
+#include <aidl/android/automotive/telemetry/internal/CarDataInternal.h>
+#include <aidl/android/automotive/telemetry/internal/ICarTelemetryInternal.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <unistd.h>
+
+#include <memory>
+
+namespace android {
+namespace automotive {
+namespace telemetry {
+
+using ::aidl::android::automotive::telemetry::internal::BnCarDataListener;
+using ::aidl::android::automotive::telemetry::internal::CarDataInternal;
+using ::aidl::android::automotive::telemetry::internal::ICarTelemetryInternal;
+using ::ndk::ScopedAStatus;
+
+const size_t kMaxBufferSize = 5;
+
+class MockCarDataListener : public BnCarDataListener {
+public:
+    MOCK_METHOD(ScopedAStatus, onCarDataReceived, (const std::vector<CarDataInternal>& dataList),
+                (override));
+};
+
+// The main test class.
+class CarTelemetryInternalImplTest : public ::testing::Test {
+protected:
+    CarTelemetryInternalImplTest() :
+          mBuffer(RingBuffer(kMaxBufferSize)),
+          mTelemetryInternal(ndk::SharedRefBase::make<CarTelemetryInternalImpl>(&mBuffer)),
+          mMockCarDataListener(ndk::SharedRefBase::make<MockCarDataListener>()) {}
+
+    RingBuffer mBuffer;
+    std::shared_ptr<ICarTelemetryInternal> mTelemetryInternal;
+    std::shared_ptr<MockCarDataListener> mMockCarDataListener;
+};
+
+TEST_F(CarTelemetryInternalImplTest, SetListenerReturnsOk) {
+    auto status = mTelemetryInternal->setListener(mMockCarDataListener);
+
+    EXPECT_TRUE(status.isOk()) << status.getMessage();
+}
+
+TEST_F(CarTelemetryInternalImplTest, SetListenerFailsWhenAlreadySubscribed) {
+    mTelemetryInternal->setListener(mMockCarDataListener);
+
+    auto status = mTelemetryInternal->setListener(ndk::SharedRefBase::make<MockCarDataListener>());
+
+    EXPECT_EQ(status.getExceptionCode(), ::EX_ILLEGAL_STATE) << status.getMessage();
+}
+
+TEST_F(CarTelemetryInternalImplTest, ClearListenerWorks) {
+    mTelemetryInternal->setListener(mMockCarDataListener);
+
+    mTelemetryInternal->clearListener();
+    auto status = mTelemetryInternal->setListener(mMockCarDataListener);
+
+    EXPECT_TRUE(status.isOk()) << status.getMessage();
+}
+
+}  // namespace telemetry
+}  // namespace automotive
+}  // namespace android
diff --git a/cpp/telemetry/tests/RingBufferTest.cpp b/cpp/telemetry/tests/RingBufferTest.cpp
new file mode 100644
index 0000000..c5b5bc7
--- /dev/null
+++ b/cpp/telemetry/tests/RingBufferTest.cpp
@@ -0,0 +1,58 @@
+/*
+ * Copyright 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.
+ */
+
+#include "RingBuffer.h"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <memory>
+
+// NOTE: many of RingBuffer's behaviors are tested as part of CarTelemetryImpl.
+
+namespace android {
+namespace automotive {
+namespace telemetry {
+
+using testing::ContainerEq;
+
+BufferedCarData buildBufferedCarData(int32_t id, const std::vector<uint8_t>& content) {
+    return {.mId = id, .mContent = content, .mPublisherUid = 0};
+}
+
+TEST(RingBufferTest, PopFrontReturnsCorrectResults) {
+    RingBuffer buffer(/* sizeLimit= */ 10);
+    buffer.push(buildBufferedCarData(101, {7}));
+    buffer.push(buildBufferedCarData(102, {7}));
+
+    BufferedCarData result = buffer.popFront();
+
+    EXPECT_EQ(result, buildBufferedCarData(101, {7}));
+}
+
+TEST(RingBufferTest, PopFrontRemovesFromBuffer) {
+    RingBuffer buffer(/* sizeLimit= */ 10);
+    buffer.push(buildBufferedCarData(101, {7}));
+    buffer.push(buildBufferedCarData(102, {7, 8}));
+
+    buffer.popFront();
+
+    EXPECT_EQ(buffer.size(), 1);  // only ID=102 left
+}
+
+}  // namespace telemetry
+}  // namespace automotive
+}  // namespace android
diff --git a/cpp/watchdog/aidl/android/automotive/watchdog/internal/ICarWatchdogServiceForSystem.aidl b/cpp/watchdog/aidl/android/automotive/watchdog/internal/ICarWatchdogServiceForSystem.aidl
index 9aa234f..ef68dbc 100644
--- a/cpp/watchdog/aidl/android/automotive/watchdog/internal/ICarWatchdogServiceForSystem.aidl
+++ b/cpp/watchdog/aidl/android/automotive/watchdog/internal/ICarWatchdogServiceForSystem.aidl
@@ -52,7 +52,8 @@
    * @param uids                        List of UIDs to resolve the package infos.
    * @param vendorPackagePrefixes       List of vendor package prefixes.
    */
-  List<PackageInfo> getPackageInfosForUids(in int[] uids, in List<String> vendorPackagePrefixes);
+  List<PackageInfo> getPackageInfosForUids(
+            in int[] uids, in @utf8InCpp List<String> vendorPackagePrefixes);
 
   /**
    * Pushes the latest I/O overuse stats to the watchdog server.
diff --git a/cpp/watchdog/aidl/android/automotive/watchdog/internal/PackageIdentifier.aidl b/cpp/watchdog/aidl/android/automotive/watchdog/internal/PackageIdentifier.aidl
index fa6e36b..c0dd86e 100644
--- a/cpp/watchdog/aidl/android/automotive/watchdog/internal/PackageIdentifier.aidl
+++ b/cpp/watchdog/aidl/android/automotive/watchdog/internal/PackageIdentifier.aidl
@@ -23,7 +23,7 @@
   /**
    * Name of the package.
    */
-  String name;
+  @utf8InCpp String name;
 
   /**
    * UID of the package.
diff --git a/cpp/watchdog/aidl/android/automotive/watchdog/internal/PackageInfo.aidl b/cpp/watchdog/aidl/android/automotive/watchdog/internal/PackageInfo.aidl
index dfcfa9a..2deb5f3 100644
--- a/cpp/watchdog/aidl/android/automotive/watchdog/internal/PackageInfo.aidl
+++ b/cpp/watchdog/aidl/android/automotive/watchdog/internal/PackageInfo.aidl
@@ -38,7 +38,7 @@
   /**
    * List of packages owned by the package. This list is empty when the UID is not a shared UID.
    */
-  List<String> sharedUidPackages;
+  @utf8InCpp List<String> sharedUidPackages;
 
   /**
    * Component type of the package and the owned packages.
diff --git a/cpp/watchdog/aidl/android/automotive/watchdog/internal/PerStateIoOveruseThreshold.aidl b/cpp/watchdog/aidl/android/automotive/watchdog/internal/PerStateIoOveruseThreshold.aidl
index 00a3c93..65f50ce 100644
--- a/cpp/watchdog/aidl/android/automotive/watchdog/internal/PerStateIoOveruseThreshold.aidl
+++ b/cpp/watchdog/aidl/android/automotive/watchdog/internal/PerStateIoOveruseThreshold.aidl
@@ -29,7 +29,7 @@
    * 2. package name for package specific thresholds.
    * 3. string equivalent of ApplicationCategoryType enum for category specific thresholds.
    */
-  String name;
+  @utf8InCpp String name;
 
   /**
    * Defines the I/O overuse thresholds for a package. The thresholds are defined in terms of
diff --git a/cpp/watchdog/aidl/android/automotive/watchdog/internal/ResourceOveruseConfiguration.aidl b/cpp/watchdog/aidl/android/automotive/watchdog/internal/ResourceOveruseConfiguration.aidl
index 6cb6f23..26ae106 100644
--- a/cpp/watchdog/aidl/android/automotive/watchdog/internal/ResourceOveruseConfiguration.aidl
+++ b/cpp/watchdog/aidl/android/automotive/watchdog/internal/ResourceOveruseConfiguration.aidl
@@ -33,14 +33,14 @@
    * List of only non-critical system and vendor packages that are safe to kill on disk I/O
    * overuse. All third-party packages are considered safe to kill.
    */
-  List<String> safeToKillPackages;
+  @utf8InCpp List<String> safeToKillPackages;
 
   /**
    * Defines the list of vendor package prefixes. Any pre-installed package name starting with one
    * of these prefixes will be identified as a vendor package in addition to packages under the
    * vendor partition. This must be defined only by the vendor component.
    */
-  List<String> vendorPackagePrefixes;
+  @utf8InCpp List<String> vendorPackagePrefixes;
 
   /**
    * Defines the package metadata.
diff --git a/cpp/watchdog/car-watchdog-lib/src/android/car/watchdoglib/CarWatchdogDaemonHelper.java b/cpp/watchdog/car-watchdog-lib/src/android/car/watchdoglib/CarWatchdogDaemonHelper.java
index 0f6cce1..57d18b0 100644
--- a/cpp/watchdog/car-watchdog-lib/src/android/car/watchdoglib/CarWatchdogDaemonHelper.java
+++ b/cpp/watchdog/car-watchdog-lib/src/android/car/watchdoglib/CarWatchdogDaemonHelper.java
@@ -24,6 +24,7 @@
 import android.automotive.watchdog.internal.ICarWatchdogMonitor;
 import android.automotive.watchdog.internal.ICarWatchdogServiceForSystem;
 import android.automotive.watchdog.internal.PackageResourceOveruseAction;
+import android.automotive.watchdog.internal.ResourceOveruseConfiguration;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
@@ -34,6 +35,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.CopyOnWriteArrayList;
@@ -46,9 +48,11 @@
 public final class CarWatchdogDaemonHelper {
 
     private static final String TAG = CarWatchdogDaemonHelper.class.getSimpleName();
-    // Carwatchdog daemon polls for the service manager status once every 250 milliseconds.
-    // CAR_WATCHDOG_DAEMON_BIND_RETRY_INTERVAL_MS value should be at least twice the poll interval
-    // used by the daemon.
+    /*
+     * Car watchdog daemon polls for the service manager status once every 250 milliseconds.
+     * CAR_WATCHDOG_DAEMON_BIND_RETRY_INTERVAL_MS value should be at least twice the poll interval
+     * used by the daemon.
+     */
     private static final long CAR_WATCHDOG_DAEMON_BIND_RETRY_INTERVAL_MS = 500;
     private static final long CAR_WATCHDOG_DAEMON_FIND_MARGINAL_TIME_MS = 300;
     private static final int CAR_WATCHDOG_DAEMON_BIND_MAX_RETRY = 3;
@@ -250,10 +254,37 @@
     }
 
     /**
+     * Sets the given resource overuse configurations.
+     *
+     * @param configurations Resource overuse configuration per component type.
+     * @throws IllegalArgumentException If the configurations are invalid.
+     * @throws RemoteException
+     */
+    public void updateResourceOveruseConfigurations(
+            List<ResourceOveruseConfiguration> configurations) throws RemoteException {
+        invokeDaemonMethod((daemon) -> daemon.updateResourceOveruseConfigurations(configurations));
+    }
+
+    /**
+     * Returns the available resource overuse configurations.
+     *
+     * @throws RemoteException
+     */
+    public List<ResourceOveruseConfiguration> getResourceOveruseConfigurations()
+            throws RemoteException {
+        List<ResourceOveruseConfiguration> configurations = new ArrayList<>();
+        invokeDaemonMethod((daemon) -> {
+            configurations.addAll(daemon.getResourceOveruseConfigurations());
+        });
+        return configurations;
+    }
+
+    /**
      * Notifies car watchdog daemon with the actions taken on resource overuse.
      *
      * @param actions List of actions taken on resource overuse. One action taken per resource
      *                overusing user package.
+     * @throws RemoteException
      */
     public void actionTakenOnResourceOveruse(List<PackageResourceOveruseAction> actions)
             throws RemoteException {
diff --git a/cpp/watchdog/server/src/IoOveruseConfigs.cpp b/cpp/watchdog/server/src/IoOveruseConfigs.cpp
index e595e89..90a73df 100644
--- a/cpp/watchdog/server/src/IoOveruseConfigs.cpp
+++ b/cpp/watchdog/server/src/IoOveruseConfigs.cpp
@@ -21,7 +21,6 @@
 #include "PackageInfoResolver.h"
 
 #include <android-base/strings.h>
-#include <utils/String8.h>
 
 #include <inttypes.h>
 
@@ -31,8 +30,6 @@
 namespace automotive {
 namespace watchdog {
 
-using ::android::String16;
-using ::android::String8;
 using ::android::automotive::watchdog::PerStateBytes;
 using ::android::automotive::watchdog::internal::ApplicationCategoryType;
 using ::android::automotive::watchdog::internal::ComponentType;
@@ -73,11 +70,11 @@
         PER_CATEGORY_THRESHOLDS;
 const int32_t kThirdPartyComponentUpdatableConfigs = COMPONENT_SPECIFIC_GENERIC_THRESHOLDS;
 
-const std::vector<String16> toString16Vector(const std::unordered_set<std::string>& values) {
-    std::vector<String16> output;
+const std::vector<std::string> toStringVector(const std::unordered_set<std::string>& values) {
+    std::vector<std::string> output;
     for (const auto& v : values) {
         if (!v.empty()) {
-            output.emplace_back(String16(String8(v.c_str())));
+            output.emplace_back(v);
         }
     }
     return output;
@@ -92,14 +89,13 @@
 std::string toString(const PerStateIoOveruseThreshold& thresholds) {
     return StringPrintf("name=%s, foregroundBytes=%" PRId64 ", backgroundBytes=%" PRId64
                         ", garageModeBytes=%" PRId64,
-                        String8(thresholds.name).c_str(),
-                        thresholds.perStateWriteBytes.foregroundBytes,
+                        thresholds.name.c_str(), thresholds.perStateWriteBytes.foregroundBytes,
                         thresholds.perStateWriteBytes.backgroundBytes,
                         thresholds.perStateWriteBytes.garageModeBytes);
 }
 
 Result<void> containsValidThresholds(const PerStateIoOveruseThreshold& thresholds) {
-    if (thresholds.name.size() == 0) {
+    if (thresholds.name.empty()) {
         return Error() << "Doesn't contain threshold name";
     }
 
@@ -145,7 +141,7 @@
             return Error() << "Invalid " << toString(componentType)
                            << " component level generic thresholds: " << result.error();
         }
-        if (String8(ioOveruseConfig.componentLevelThresholds.name).string() != componentTypeStr) {
+        if (ioOveruseConfig.componentLevelThresholds.name != componentTypeStr) {
             return Error() << "Invalid component name "
                            << ioOveruseConfig.componentLevelThresholds.name
                            << " in component level generic thresholds for component "
@@ -231,38 +227,36 @@
     }
     std::string errorMsgs;
     for (const auto& packageThreshold : thresholds) {
-        std::string packageName = std::string(String8(packageThreshold.name));
-        if (packageName.empty()) {
+        if (packageThreshold.name.empty()) {
             StringAppendF(&errorMsgs, "\tSkipping per-package threshold without package name\n");
             continue;
         }
-        maybeAppendVendorPackagePrefixes(packageName);
+        maybeAppendVendorPackagePrefixes(packageThreshold.name);
         if (auto result = containsValidThresholds(packageThreshold); !result.ok()) {
             StringAppendF(&errorMsgs,
                           "\tSkipping invalid package specific thresholds for package %s: %s\n",
-                          packageName.c_str(), result.error().message().c_str());
+                          packageThreshold.name.c_str(), result.error().message().c_str());
             continue;
         }
-        if (const auto& it = mPerPackageThresholds.find(packageName);
+        if (const auto& it = mPerPackageThresholds.find(packageThreshold.name);
             it != mPerPackageThresholds.end()) {
             StringAppendF(&errorMsgs, "\tDuplicate threshold received for package '%s'\n",
-                          packageName.c_str());
+                          packageThreshold.name.c_str());
         }
-        mPerPackageThresholds[packageName] = packageThreshold;
+        mPerPackageThresholds[packageThreshold.name] = packageThreshold;
     }
     return errorMsgs.empty() ? Result<void>{} : Error() << errorMsgs;
 }
 
 Result<void> ComponentSpecificConfig::updateSafeToKillPackages(
-        const std::vector<String16>& packages,
+        const std::vector<std::string>& packages,
         const std::function<void(const std::string&)>& maybeAppendVendorPackagePrefixes) {
     mSafeToKillPackages.clear();
     if (packages.empty()) {
         return Error() << "\tNo safe-to-kill packages provided so clearing it\n";
     }
     std::string errorMsgs;
-    for (const auto& packageNameStr16 : packages) {
-        std::string packageName = std::string(String8(packageNameStr16));
+    for (const auto& packageName : packages) {
         if (packageName.empty()) {
             StringAppendF(&errorMsgs, "\tSkipping empty safe-to-kill package name");
             continue;
@@ -296,15 +290,15 @@
                           result.error().message().c_str());
             continue;
         }
-        std::string name = std::string(String8(categoryThreshold.name));
-        if (auto category = toApplicationCategoryType(name);
+        if (auto category = toApplicationCategoryType(categoryThreshold.name);
             category == ApplicationCategoryType::OTHERS) {
-            StringAppendF(&errorMsgs, "\tInvalid application category %s\n", name.c_str());
+            StringAppendF(&errorMsgs, "\tInvalid application category %s\n",
+                          categoryThreshold.name.c_str());
         } else {
             if (const auto& it = mPerCategoryThresholds.find(category);
                 it != mPerCategoryThresholds.end()) {
                 StringAppendF(&errorMsgs, "\tDuplicate threshold received for category: '%s'\n",
-                              name.c_str());
+                              categoryThreshold.name.c_str());
             }
             mPerCategoryThresholds[category] = categoryThreshold;
         }
@@ -393,8 +387,8 @@
     std::string nonUpdatableConfigMsgs;
     if (updatableConfigsFilter & OveruseConfigEnum::VENDOR_PACKAGE_PREFIXES) {
         mVendorPackagePrefixes.clear();
-        for (const auto& prefixStr16 : resourceOveruseConfiguration.vendorPackagePrefixes) {
-            if (auto prefix = std::string(String8(prefixStr16)); !prefix.empty()) {
+        for (const auto& prefix : resourceOveruseConfiguration.vendorPackagePrefixes) {
+            if (!prefix.empty()) {
                 mVendorPackagePrefixes.insert(prefix);
             }
         }
@@ -509,7 +503,7 @@
 
 std::optional<ResourceOveruseConfiguration> IoOveruseConfigs::get(
         const ComponentSpecificConfig& componentSpecificConfig, const int32_t componentFilter) {
-    if (componentSpecificConfig.mGeneric.name == String16(kDefaultThresholdName)) {
+    if (componentSpecificConfig.mGeneric.name == kDefaultThresholdName) {
         return {};
     }
     ResourceOveruseConfiguration resourceOveruseConfiguration;
@@ -518,8 +512,7 @@
         ioOveruseConfiguration.componentLevelThresholds = componentSpecificConfig.mGeneric;
     }
     if (componentFilter & OveruseConfigEnum::VENDOR_PACKAGE_PREFIXES) {
-        resourceOveruseConfiguration.vendorPackagePrefixes =
-                toString16Vector(mVendorPackagePrefixes);
+        resourceOveruseConfiguration.vendorPackagePrefixes = toStringVector(mVendorPackagePrefixes);
     }
     if (componentFilter & OveruseConfigEnum::PACKAGE_APP_CATEGORY_MAPPINGS) {
         for (const auto& [packageName, appCategoryType] : mPackagesToAppCategories) {
@@ -536,7 +529,7 @@
     }
     if (componentFilter & OveruseConfigEnum::COMPONENT_SPECIFIC_SAFE_TO_KILL_PACKAGES) {
         resourceOveruseConfiguration.safeToKillPackages =
-                toString16Vector(componentSpecificConfig.mSafeToKillPackages);
+                toStringVector(componentSpecificConfig.mSafeToKillPackages);
     }
     if (componentFilter & OveruseConfigEnum::PER_CATEGORY_THRESHOLDS) {
         for (const auto& [category, threshold] : mPerCategoryThresholds) {
@@ -557,10 +550,10 @@
 }
 
 PerStateBytes IoOveruseConfigs::fetchThreshold(const PackageInfo& packageInfo) const {
-    const std::string packageName = std::string(String8(packageInfo.packageIdentifier.name));
     switch (packageInfo.componentType) {
         case ComponentType::SYSTEM:
-            if (const auto it = mSystemConfig.mPerPackageThresholds.find(packageName);
+            if (const auto it = mSystemConfig.mPerPackageThresholds.find(
+                        packageInfo.packageIdentifier.name);
                 it != mSystemConfig.mPerPackageThresholds.end()) {
                 return it->second.perStateWriteBytes;
             }
@@ -570,7 +563,8 @@
             }
             return mSystemConfig.mGeneric.perStateWriteBytes;
         case ComponentType::VENDOR:
-            if (const auto it = mVendorConfig.mPerPackageThresholds.find(packageName);
+            if (const auto it = mVendorConfig.mPerPackageThresholds.find(
+                        packageInfo.packageIdentifier.name);
                 it != mVendorConfig.mPerPackageThresholds.end()) {
                 return it->second.perStateWriteBytes;
             }
@@ -597,13 +591,12 @@
         // Native packages can't be disabled so don't kill them on I/O overuse.
         return false;
     }
-    const std::string packageName = std::string(String8(packageInfo.packageIdentifier.name));
     switch (packageInfo.componentType) {
         case ComponentType::SYSTEM:
-            return mSystemConfig.mSafeToKillPackages.find(packageName) !=
+            return mSystemConfig.mSafeToKillPackages.find(packageInfo.packageIdentifier.name) !=
                     mSystemConfig.mSafeToKillPackages.end();
         case ComponentType::VENDOR:
-            return mVendorConfig.mSafeToKillPackages.find(packageName) !=
+            return mVendorConfig.mSafeToKillPackages.find(packageInfo.packageIdentifier.name) !=
                     mVendorConfig.mSafeToKillPackages.end();
         default:
             return true;
diff --git a/cpp/watchdog/server/src/IoOveruseConfigs.h b/cpp/watchdog/server/src/IoOveruseConfigs.h
index 40a0b7b..cd80a95 100644
--- a/cpp/watchdog/server/src/IoOveruseConfigs.h
+++ b/cpp/watchdog/server/src/IoOveruseConfigs.h
@@ -26,7 +26,6 @@
 #include <android/automotive/watchdog/internal/PackageInfo.h>
 #include <android/automotive/watchdog/internal/PerStateIoOveruseThreshold.h>
 #include <android/automotive/watchdog/internal/ResourceOveruseConfiguration.h>
-#include <utils/String16.h>
 
 #include <optional>
 #include <string>
@@ -42,7 +41,7 @@
 inline const android::automotive::watchdog::internal::PerStateIoOveruseThreshold
 defaultThreshold() {
     android::automotive::watchdog::internal::PerStateIoOveruseThreshold threshold;
-    threshold.name = android::String16(kDefaultThresholdName);
+    threshold.name = kDefaultThresholdName;
     threshold.perStateWriteBytes.foregroundBytes = std::numeric_limits<uint64_t>::max();
     threshold.perStateWriteBytes.backgroundBytes = std::numeric_limits<uint64_t>::max();
     threshold.perStateWriteBytes.garageModeBytes = std::numeric_limits<uint64_t>::max();
@@ -130,7 +129,7 @@
      * Updates |mSafeToKillPackages|.
      */
     android::base::Result<void> updateSafeToKillPackages(
-            const std::vector<android::String16>& packages,
+            const std::vector<std::string>& packages,
             const std::function<void(const std::string&)>& maybeAppendVendorPackagePrefixes);
 
     /*
diff --git a/cpp/watchdog/server/src/IoOveruseMonitor.cpp b/cpp/watchdog/server/src/IoOveruseMonitor.cpp
index fd9f61f..080b284 100644
--- a/cpp/watchdog/server/src/IoOveruseMonitor.cpp
+++ b/cpp/watchdog/server/src/IoOveruseMonitor.cpp
@@ -15,6 +15,7 @@
  */
 
 #define LOG_TAG "carwatchdogd"
+#define DEBUG false  // STOPSHIP if true.
 
 #include "IoOveruseMonitor.h"
 
@@ -27,6 +28,8 @@
 #include <binder/Status.h>
 #include <cutils/multiuser.h>
 
+#include <limits>
+
 namespace android {
 namespace automotive {
 namespace watchdog {
@@ -44,13 +47,17 @@
 using ::android::base::Result;
 using ::android::binder::Status;
 
+// Minimum written bytes to sync the stats with the Watchdog service.
+constexpr uint64_t kMinSyncWrittenBytes = 100 * 1024;
+// Minimum percentage of threshold to warn killable applications.
 constexpr double kDefaultIoOveruseWarnPercentage = 80;
+// Maximum numer of system-wide stats (from periodic monitoring) to cache.
 constexpr size_t kMaxPeriodicMonitorBufferSize = 1000;
 
 namespace {
 
 std::string uniquePackageIdStr(const PackageIdentifier& id) {
-    return StringPrintf("%s:%" PRId32, String8(id.name).c_str(), multiuser_get_user_id(id.uid));
+    return StringPrintf("%s:%" PRId32, id.name.c_str(), multiuser_get_user_id(id.uid));
 }
 
 PerStateBytes diff(const PerStateBytes& lhs, const PerStateBytes& rhs) {
@@ -76,6 +83,15 @@
     return std::make_tuple(startTime, currentEpochSeconds - startTime);
 }
 
+uint64_t totalPerStateBytes(PerStateBytes perStateBytes) {
+    const auto sum = [](const uint64_t& l, const uint64_t& r) -> uint64_t {
+        return std::numeric_limits<uint64_t>::max() - l > r ? (l + r)
+                                                            : std::numeric_limits<uint64_t>::max();
+    };
+    return sum(perStateBytes.foregroundBytes,
+               sum(perStateBytes.backgroundBytes, perStateBytes.garageModeBytes));
+}
+
 }  // namespace
 
 std::tuple<int64_t, int64_t> calculateStartAndDuration(const time_t& currentTime) {
@@ -84,6 +100,19 @@
     return calculateStartAndDuration(currentGmt);
 }
 
+IoOveruseMonitor::IoOveruseMonitor(
+        const android::sp<IWatchdogServiceHelperInterface>& watchdogServiceHelper) :
+      mMinSyncWrittenBytes(kMinSyncWrittenBytes),
+      mWatchdogServiceHelper(watchdogServiceHelper),
+      mSystemWideWrittenBytes({}),
+      mPeriodicMonitorBufferSize(0),
+      mLastSystemWideIoMonitorTime(0),
+      mUserPackageDailyIoUsageById({}),
+      mIoOveruseWarnPercentage(0),
+      mLastUserPackageIoMonitorTime(0),
+      mOveruseListenersByUid({}),
+      mBinderDeathRecipient(new BinderDeathRecipient(this)) {}
+
 Result<void> IoOveruseMonitor::init() {
     std::unique_lock writeLock(mRwMutex);
     if (isInitializedLocked()) {
@@ -100,7 +129,7 @@
     mIoOveruseWarnPercentage = static_cast<double>(
             sysprop::ioOveruseWarnPercentage().value_or(kDefaultIoOveruseWarnPercentage));
     /*
-     * TODO(b/167240592): Read the latest I/O overuse config.
+     * TODO(b/185287136): Read the latest I/O overuse config.
      *  The latest I/O overuse config is read in this order:
      *  1. From /data partition as this contains the latest config and any updates received from OEM
      *    and system applications.
@@ -109,10 +138,13 @@
      */
 
     mIoOveruseConfigs = new IoOveruseConfigs();
-    // TODO(b/167240592): Read the vendor package prefixes from disk before the below call.
+    // TODO(b/185287136): Read the vendor package prefixes from disk before the below call.
     mPackageInfoResolver = PackageInfoResolver::getInstance();
     mPackageInfoResolver->setPackageConfigurations(mIoOveruseConfigs->vendorPackagePrefixes(),
                                                    mIoOveruseConfigs->packagesToAppCategories());
+    if (DEBUG) {
+        ALOGD("Initialized %s data processor", name().c_str());
+    }
     return {};
 }
 
@@ -129,6 +161,9 @@
     }
     mBinderDeathRecipient.clear();
     mOveruseListenersByUid.clear();
+    if (DEBUG) {
+        ALOGD("Terminated %s data processor", name().c_str());
+    }
     return;
 }
 
@@ -148,7 +183,7 @@
         /*
          * Date changed so reset the daily I/O usage cache.
          *
-         * TODO(b/170741935): Ping CarWatchdogService on date change so it can re-enable the daily
+         * TODO(b/185287136): Ping CarWatchdogService on date change so it can re-enable the daily
          *  disabled packages. Also sync prev day's stats with CarWatchdogService.
          */
         mUserPackageDailyIoUsageById.clear();
@@ -156,14 +191,26 @@
     mLastUserPackageIoMonitorTime = time;
     const auto [startTime, durationInSeconds] = calculateStartAndDuration(curGmt);
 
-    const auto perUidIoUsage = uidIoStats.promote()->deltaStats();
+    auto perUidIoUsage = uidIoStats.promote()->deltaStats();
     /*
-     * TODO(b/167240592): Maybe move the packageInfo fetching logic into UidIoStats module.
+     * TODO(b/185849350): Maybe move the packageInfo fetching logic into UidIoStats module.
      *  This will also help avoid fetching package names in IoPerfCollection module.
      */
     std::vector<uid_t> seenUids;
-    for (const auto& [uid, uidIoStats] : perUidIoUsage) {
-        seenUids.push_back(uid);
+    for (auto it = perUidIoUsage.begin(); it != perUidIoUsage.end();) {
+        /*
+         * UidIoStats::deltaStats returns entries with zero write bytes because other metrics
+         * in these entries are non-zero.
+         */
+        if (it->second.ios.sumWriteBytes() == 0) {
+            it = perUidIoUsage.erase(it);
+            continue;
+        }
+        seenUids.push_back(it->first);
+        ++it;
+    }
+    if (perUidIoUsage.empty()) {
+        return {};
     }
     const auto packageInfosByUid = mPackageInfoResolver->getPackageInfosForUids(seenUids);
     std::unordered_map<uid_t, IoOveruseStats> overusingNativeStats;
@@ -173,7 +220,7 @@
             continue;
         }
         /*
-         * TODO(b/167240592): Derive the garage mode status from the collection flag, which will
+         * TODO(b/185498771): Derive the garage mode status from the collection flag, which will
          *  be added to the |onPeriodicCollection| API.
          */
         UserPackageIoUsage curUsage(packageInfo->second, uidIoStats.ios,
@@ -204,6 +251,16 @@
                 mIoOveruseConfigs->isSafeToKill(dailyIoUsage->packageInfo);
 
         const auto& remainingWriteBytes = stats.ioOveruseStats.remainingWriteBytes;
+        const auto exceedsWarnThreshold = [&](double remaining, double threshold) {
+            if (threshold == 0) {
+                return true;
+            }
+            double usedPercent = (100 - (remaining / threshold) * 100);
+            return usedPercent > mIoOveruseWarnPercentage;
+        };
+        bool shouldSyncWatchdogService =
+                (totalPerStateBytes(dailyIoUsage->writtenBytes) -
+                 dailyIoUsage->lastSyncedWrittenBytes) >= mMinSyncWrittenBytes;
         if (remainingWriteBytes.foregroundBytes == 0 || remainingWriteBytes.backgroundBytes == 0 ||
             remainingWriteBytes.garageModeBytes == 0) {
             stats.ioOveruseStats.totalOveruses = ++dailyIoUsage->totalOveruses;
@@ -221,45 +278,44 @@
             if (dailyIoUsage->packageInfo.uidType == UidType::NATIVE) {
                 overusingNativeStats[uid] = stats.ioOveruseStats;
             }
-            mLatestIoOveruseStats.emplace_back(std::move(stats));
-            continue;
-        }
-        if (dailyIoUsage->packageInfo.uidType == UidType::NATIVE ||
-            !stats.ioOveruseStats.killableOnOveruse || dailyIoUsage->isPackageWarned) {
+            shouldSyncWatchdogService = true;
+        } else if (dailyIoUsage->packageInfo.uidType != UidType::NATIVE &&
+                   stats.ioOveruseStats.killableOnOveruse && !dailyIoUsage->isPackageWarned &&
+                   (exceedsWarnThreshold(remainingWriteBytes.foregroundBytes,
+                                         threshold.foregroundBytes) ||
+                    exceedsWarnThreshold(remainingWriteBytes.backgroundBytes,
+                                         threshold.backgroundBytes) ||
+                    exceedsWarnThreshold(remainingWriteBytes.garageModeBytes,
+                                         threshold.garageModeBytes))) {
             /*
              * No need to warn native services or applications that won't be killed on I/O overuse
              * as they will be sent a notification when they exceed their daily threshold.
              */
-            mLatestIoOveruseStats.emplace_back(std::move(stats));
-            continue;
-        }
-        const auto exceedsWarnThreshold = [&](double remaining, double threshold) {
-            if (threshold == 0) {
-                return true;
-            }
-            double usedPercent = (100 - (remaining / threshold) * 100);
-            return usedPercent > mIoOveruseWarnPercentage;
-        };
-        if (exceedsWarnThreshold(remainingWriteBytes.foregroundBytes, threshold.foregroundBytes) ||
-            exceedsWarnThreshold(remainingWriteBytes.backgroundBytes, threshold.backgroundBytes) ||
-            exceedsWarnThreshold(remainingWriteBytes.garageModeBytes, threshold.garageModeBytes)) {
             stats.shouldNotify = true;
             // Avoid duplicate warning before the daily threshold exceeded notification is sent.
             dailyIoUsage->isPackageWarned = true;
+            shouldSyncWatchdogService = true;
         }
-        mLatestIoOveruseStats.emplace_back(std::move(stats));
+        if (shouldSyncWatchdogService) {
+            dailyIoUsage->lastSyncedWrittenBytes = totalPerStateBytes(dailyIoUsage->writtenBytes);
+            mLatestIoOveruseStats.emplace_back(std::move(stats));
+        }
     }
-
     if (!overusingNativeStats.empty()) {
         notifyNativePackagesLocked(overusingNativeStats);
     }
-
+    if (mLatestIoOveruseStats.empty()) {
+        return {};
+    }
     if (const auto status = mWatchdogServiceHelper->latestIoOveruseStats(mLatestIoOveruseStats);
         !status.isOk()) {
         // Don't clear the cache as it can be pushed again on the next collection.
         ALOGW("Failed to push the latest I/O overuse stats to watchdog service");
     } else {
         mLatestIoOveruseStats.clear();
+        if (DEBUG) {
+            ALOGD("Pushed latest I/O overuse stats to watchdog service");
+        }
     }
 
     return {};
@@ -328,12 +384,12 @@
 }
 
 Result<void> IoOveruseMonitor::onShutdownPrepareComplete() {
-    // TODO(b/167240592): Flush in-memory stats to disk.
+    // TODO(b/185287136): Flush in-memory stats to disk.
     return {};
 }
 
 Result<void> IoOveruseMonitor::onDump([[maybe_unused]] int fd) {
-    // TODO(b/167240592): Dump the list of killed/disabled packages. Dump the list of packages that
+    // TODO(b/183436216): Dump the list of killed/disabled packages. Dump the list of packages that
     //  exceed xx% of their threshold.
     return {};
 }
@@ -351,7 +407,10 @@
         stats.set<ResourceOveruseStats::ioOveruseStats>(ioOveruseStats);
         listener->onOveruse(stats);
     }
-    // TODO(b/167240592): Upload I/O overuse metrics for native packages.
+    if (DEBUG) {
+        ALOGD("Notified native packages on I/O overuse");
+    }
+    // TODO(b/184310189): Upload I/O overuse metrics for native packages.
 }
 
 Result<void> IoOveruseMonitor::updateResourceOveruseConfigurations(
@@ -375,7 +434,10 @@
 
 Result<void> IoOveruseMonitor::actionTakenOnIoOveruse(
         [[maybe_unused]] const std::vector<PackageResourceOveruseAction>& actions) {
-    // TODO(b/167240592): Upload metrics.
+    // TODO(b/184310189): Upload metrics.
+    if (DEBUG) {
+        ALOGD("Recorded action taken on I/O overuse");
+    }
     return {};
 }
 
@@ -395,6 +457,9 @@
                 << "(pid " << callingPid << ", uid: " << callingUid << ") is dead";
     }
     mOveruseListenersByUid[callingUid] = listener;
+    if (DEBUG) {
+        ALOGD("Added I/O overuse listener for uid: %d", callingUid);
+    }
     return {};
 }
 
@@ -410,11 +475,14 @@
         !findListenerAndProcessLocked(binder, processor)) {
         return Error(Status::EX_ILLEGAL_ARGUMENT) << "Listener is not previously registered";
     }
+    if (DEBUG) {
+        ALOGD("Removed I/O overuse listener for uid: %d", IPCThreadState::self()->getCallingUid());
+    }
     return {};
 }
 
 Result<void> IoOveruseMonitor::getIoOveruseStats(IoOveruseStats* ioOveruseStats) {
-    if (std::shared_lock readLock(mRwMutex); !isInitializedLocked()) {
+    if (!isInitialized()) {
         return Error(Status::EX_ILLEGAL_STATE) << "I/O overuse monitor is not initialized";
     }
     uid_t callingUid = IPCThreadState::self()->getCallingUid();
@@ -447,6 +515,9 @@
             calculateStartAndDuration(mLastUserPackageIoMonitorTime);
     ioOveruseStats->startTime = startTime;
     ioOveruseStats->durationInSeconds = durationInSeconds;
+    if (DEBUG) {
+        ALOGD("Returning I/O overuse listener for uid: %d", callingUid);
+    }
     return {};
 }
 
diff --git a/cpp/watchdog/server/src/IoOveruseMonitor.h b/cpp/watchdog/server/src/IoOveruseMonitor.h
index 966e7ec..7040353 100644
--- a/cpp/watchdog/server/src/IoOveruseMonitor.h
+++ b/cpp/watchdog/server/src/IoOveruseMonitor.h
@@ -66,6 +66,9 @@
  */
 class IIoOveruseMonitor : virtual public IDataProcessorInterface {
 public:
+    // Returns whether or not the monitor is initialized.
+    virtual bool isInitialized() = 0;
+
     // Below API is from internal/ICarWatchdog.aidl. Please refer to the AIDL for description.
     virtual android::base::Result<void> updateResourceOveruseConfigurations(
             const std::vector<
@@ -92,19 +95,15 @@
 class IoOveruseMonitor final : public IIoOveruseMonitor {
 public:
     explicit IoOveruseMonitor(
-            const android::sp<IWatchdogServiceHelperInterface>& watchdogServiceHelper) :
-          mWatchdogServiceHelper(watchdogServiceHelper),
-          mSystemWideWrittenBytes({}),
-          mPeriodicMonitorBufferSize(0),
-          mLastSystemWideIoMonitorTime(0),
-          mUserPackageDailyIoUsageById({}),
-          mIoOveruseWarnPercentage(0),
-          mLastUserPackageIoMonitorTime(0),
-          mOveruseListenersByUid({}),
-          mBinderDeathRecipient(new BinderDeathRecipient(this)) {}
+            const android::sp<IWatchdogServiceHelperInterface>& watchdogServiceHelper);
 
     ~IoOveruseMonitor() { terminate(); }
 
+    bool isInitialized() {
+        std::shared_lock readLock(mRwMutex);
+        return isInitializedLocked();
+    }
+
     // Below methods implement IDataProcessorInterface.
     std::string name() { return "IoOveruseMonitor"; }
     friend std::ostream& operator<<(std::ostream& os, const IoOveruseMonitor& monitor);
@@ -116,7 +115,7 @@
         return {};
     }
 
-    // TODO(b/167240592): Forward WatchdogBinderMediator's notifySystemStateChange call to
+    // TODO(b/185498771): Forward WatchdogBinderMediator's notifySystemStateChange call to
     //  WatchdogPerfService. On POWER_CYCLE_SHUTDOWN_PREPARE, switch to garage mode collection
     //  and pass collection flag as a param in this API to indicate garage mode collection.
     android::base::Result<void> onPeriodicCollection(time_t time,
@@ -133,7 +132,7 @@
             time_t time, const android::wp<IProcDiskStatsInterface>& procDiskStats,
             const std::function<void()>& alertHandler);
 
-    // TODO(b/167240592): Forward WatchdogBinderMediator's notifySystemStateChange call to
+    // TODO(b/185498771): Forward WatchdogBinderMediator's notifySystemStateChange call to
     //  WatchdogProcessService. On POWER_CYCLE_SHUTDOWN_PREPARE_COMPLETE, call this method via
     //  the IDataProcessorInterface. onShutdownPrepareComplete, IoOveruseMonitor will flush
     //  in-memory stats to disk.
@@ -187,6 +186,7 @@
         PerStateBytes forgivenWriteBytes = {};
         int totalOveruses = 0;
         bool isPackageWarned = false;
+        uint64_t lastSyncedWrittenBytes = 0;
 
         UserPackageIoUsage& operator+=(const UserPackageIoUsage& r);
 
@@ -219,12 +219,13 @@
 
     // Local IPackageInfoResolver instance. Useful to mock in tests.
     sp<IPackageInfoResolver> mPackageInfoResolver;
+    // Minimum written bytes to sync the stats with the Watchdog service.
+    double mMinSyncWrittenBytes;
+    android::sp<IWatchdogServiceHelperInterface> mWatchdogServiceHelper;
 
     // Makes sure only one collection is running at any given time.
     mutable std::shared_mutex mRwMutex;
 
-    android::sp<IWatchdogServiceHelperInterface> mWatchdogServiceHelper GUARDED_BY(mRwMutex);
-
     // Summary of configs available for all the components and system-wide overuse alert thresholds.
     sp<IIoOveruseConfigs> mIoOveruseConfigs GUARDED_BY(mRwMutex);
 
diff --git a/cpp/watchdog/server/src/IoPerfCollection.cpp b/cpp/watchdog/server/src/IoPerfCollection.cpp
index 059059a..22dc45f 100644
--- a/cpp/watchdog/server/src/IoPerfCollection.cpp
+++ b/cpp/watchdog/server/src/IoPerfCollection.cpp
@@ -421,9 +421,6 @@
 
     for (const auto& uIt : usages) {
         const UidIoUsage& curUsage = uIt.second;
-        if (curUsage.ios.isZero()) {
-            continue;
-        }
         uids.push_back(curUsage.uid);
         uidIoPerfData->total[READ_BYTES][FOREGROUND] +=
                 curUsage.ios.metrics[READ_BYTES][FOREGROUND];
diff --git a/cpp/watchdog/server/src/PackageInfoResolver.cpp b/cpp/watchdog/server/src/PackageInfoResolver.cpp
index f6b1dfd..1a4346a 100644
--- a/cpp/watchdog/server/src/PackageInfoResolver.cpp
+++ b/cpp/watchdog/server/src/PackageInfoResolver.cpp
@@ -23,7 +23,6 @@
 #include <android/automotive/watchdog/internal/ComponentType.h>
 #include <android/automotive/watchdog/internal/UidType.h>
 #include <cutils/android_filesystem_config.h>
-#include <utils/String16.h>
 
 #include <inttypes.h>
 
@@ -36,7 +35,6 @@
 
 using ::android::IBinder;
 using ::android::sp;
-using ::android::String16;
 using ::android::automotive::watchdog::internal::ApplicationCategoryType;
 using ::android::automotive::watchdog::internal::ComponentType;
 using ::android::automotive::watchdog::internal::PackageInfo;
@@ -53,7 +51,7 @@
 
 namespace {
 
-constexpr const char16_t* kSharedPackagePrefix = u"shared:";
+constexpr const char* kSharedPackagePrefix = "shared:";
 
 ComponentType getComponentTypeForNativeUid(uid_t uid, std::string_view packageName,
                                            const std::vector<std::string>& vendorPackagePrefixes) {
@@ -83,7 +81,7 @@
         return Error() << "Failed to fetch package name";
     }
     const char* packageName = usrpwd->pw_name;
-    packageInfo.packageIdentifier.name = String16(packageName);
+    packageInfo.packageIdentifier.name = packageName;
     packageInfo.packageIdentifier.uid = uid;
     packageInfo.uidType = UidType::NATIVE;
     packageInfo.componentType =
@@ -153,7 +151,7 @@
             continue;
         }
         mUidToPackageInfoMapping[uid] = *result;
-        if (result->packageIdentifier.name.startsWith(kSharedPackagePrefix)) {
+        if (StartsWith(result->packageIdentifier.name, kSharedPackagePrefix)) {
             // When the UID is shared, poll car watchdog service to fetch the shared packages info.
             missingUids.emplace_back(static_cast<int32_t>(uid));
         }
@@ -178,10 +176,10 @@
     }
     for (auto& packageInfo : packageInfos) {
         const auto& id = packageInfo.packageIdentifier;
-        if (id.name.size() == 0) {
+        if (id.name.empty()) {
             continue;
         }
-        if (const auto it = mPackagesToAppCategories.find(String8(id.name).c_str());
+        if (const auto it = mPackagesToAppCategories.find(id.name);
             packageInfo.uidType == UidType::APPLICATION && it != mPackagesToAppCategories.end()) {
             packageInfo.appCategoryType = it->second;
         }
@@ -200,8 +198,8 @@
         std::shared_lock readLock(mRWMutex);
         for (const auto& uid : uids) {
             if (mUidToPackageInfoMapping.find(uid) != mUidToPackageInfoMapping.end()) {
-                uidToPackageNameMapping[uid] = std::string(
-                        String8(mUidToPackageInfoMapping.at(uid).packageIdentifier.name));
+                uidToPackageNameMapping[uid] =
+                        mUidToPackageInfoMapping.at(uid).packageIdentifier.name;
             }
         }
     }
diff --git a/cpp/watchdog/server/src/ServiceManager.cpp b/cpp/watchdog/server/src/ServiceManager.cpp
index 978e83c..6bcba40 100644
--- a/cpp/watchdog/server/src/ServiceManager.cpp
+++ b/cpp/watchdog/server/src/ServiceManager.cpp
@@ -26,7 +26,6 @@
 namespace watchdog {
 
 using ::android::sp;
-using ::android::String16;
 using ::android::automotive::watchdog::WatchdogPerfService;
 using ::android::automotive::watchdog::WatchdogProcessService;
 using ::android::base::Error;
diff --git a/cpp/watchdog/server/src/UidIoStats.cpp b/cpp/watchdog/server/src/UidIoStats.cpp
index 31cd0d9..ce7b351 100644
--- a/cpp/watchdog/server/src/UidIoStats.cpp
+++ b/cpp/watchdog/server/src/UidIoStats.cpp
@@ -113,11 +113,16 @@
 
     mDeltaUidIoUsages.clear();
     for (const auto& it : *uidIoUsages) {
-        const UidIoUsage& curUsage = it.second;
-        mDeltaUidIoUsages[it.first] = curUsage;
-        if (mLatestUidIoUsages.find(it.first) != mLatestUidIoUsages.end()) {
-            mDeltaUidIoUsages[it.first] -= mLatestUidIoUsages[it.first];
+        UidIoUsage curUsage = it.second;
+        if (curUsage.ios.isZero()) {
+            continue;
         }
+        if (mLatestUidIoUsages.find(it.first) != mLatestUidIoUsages.end()) {
+            if (curUsage -= mLatestUidIoUsages[it.first]; curUsage.ios.isZero()) {
+                continue;
+            }
+        }
+        mDeltaUidIoUsages[it.first] = curUsage;
     }
     mLatestUidIoUsages = *uidIoUsages;
     return {};
diff --git a/cpp/watchdog/server/src/UidIoStats.h b/cpp/watchdog/server/src/UidIoStats.h
index c6e87d1..3cc6a76 100644
--- a/cpp/watchdog/server/src/UidIoStats.h
+++ b/cpp/watchdog/server/src/UidIoStats.h
@@ -18,6 +18,7 @@
 #define CPP_WATCHDOG_SERVER_SRC_UIDIOSTATS_H_
 
 #include <android-base/result.h>
+#include <android-base/stringprintf.h>
 #include <utils/Mutex.h>
 #include <utils/RefBase.h>
 
@@ -87,6 +88,10 @@
         ios -= rhs.ios;
         return *this;
     }
+    bool operator==(const UidIoUsage& rhs) const { return uid == rhs.uid && ios == rhs.ios; }
+    std::string toString() const {
+        return android::base::StringPrintf("Uid: %d, Usage: {%s}", uid, ios.toString().c_str());
+    }
 };
 
 class UidIoStats : public RefBase {
diff --git a/cpp/watchdog/server/src/WatchdogBinderMediator.cpp b/cpp/watchdog/server/src/WatchdogBinderMediator.cpp
index b27d9a9..0a4eb25 100644
--- a/cpp/watchdog/server/src/WatchdogBinderMediator.cpp
+++ b/cpp/watchdog/server/src/WatchdogBinderMediator.cpp
@@ -32,6 +32,7 @@
 using ::android::defaultServiceManager;
 using ::android::IBinder;
 using ::android::sp;
+using ::android::String16;
 using ::android::base::Error;
 using ::android::base::Join;
 using ::android::base::ParseUint;
@@ -170,7 +171,6 @@
         ALOGW("Failed to dump I/O perf collection: %s", ret.error().message().c_str());
         return ret.error().code();
     }
-    // TODO(b/167240592): Add a dump call to I/O overuse monitor and relevant tests.
     return OK;
 }
 
diff --git a/cpp/watchdog/server/src/WatchdogBinderMediator.h b/cpp/watchdog/server/src/WatchdogBinderMediator.h
index e470290..63b687b 100644
--- a/cpp/watchdog/server/src/WatchdogBinderMediator.h
+++ b/cpp/watchdog/server/src/WatchdogBinderMediator.h
@@ -66,7 +66,7 @@
     ~WatchdogBinderMediator() { terminate(); }
 
     // Implements ICarWatchdog.aidl APIs.
-    status_t dump(int fd, const Vector<String16>& args) override;
+    status_t dump(int fd, const Vector<android::String16>& args) override;
     android::binder::Status registerClient(const android::sp<ICarWatchdogClient>& client,
                                            TimeoutLength timeout) override;
     android::binder::Status unregisterClient(
diff --git a/cpp/watchdog/server/src/WatchdogInternalHandler.cpp b/cpp/watchdog/server/src/WatchdogInternalHandler.cpp
index 31c40bf..556f9f1 100644
--- a/cpp/watchdog/server/src/WatchdogInternalHandler.cpp
+++ b/cpp/watchdog/server/src/WatchdogInternalHandler.cpp
@@ -37,6 +37,7 @@
 using aawi::PackageResourceOveruseAction;
 using aawi::ResourceOveruseConfiguration;
 using ::android::sp;
+using ::android::String16;
 using ::android::binder::Status;
 
 namespace {
@@ -65,6 +66,18 @@
     return mBinderMediator->dump(fd, args);
 }
 
+void WatchdogInternalHandler::checkAndRegisterIoOveruseMonitor() {
+    if (mIoOveruseMonitor->isInitialized()) {
+        return;
+    }
+    if (const auto result = mWatchdogPerfService->registerDataProcessor(mIoOveruseMonitor);
+        !result.ok()) {
+        ALOGE("Failed to register I/O overuse monitor to watchdog performance service: %s",
+              result.error().message().c_str());
+    }
+    return;
+}
+
 Status WatchdogInternalHandler::registerCarWatchdogService(
         const sp<ICarWatchdogServiceForSystem>& service) {
     Status status = checkSystemUser();
@@ -74,6 +87,12 @@
     if (service == nullptr) {
         return fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT, kNullCarWatchdogServiceError);
     }
+    /*
+     * I/O overuse monitor reads from system, vendor, and data partitions during initialization.
+     * When CarService is running these partitions are available to read, thus register the I/O
+     * overuse monitor on processing the request to register CarService.
+     */
+    checkAndRegisterIoOveruseMonitor();
     return mWatchdogServiceHelper->registerService(service);
 }
 
@@ -167,19 +186,6 @@
                 if (const auto result = mWatchdogPerfService->onBootFinished(); !result.ok()) {
                     return fromExceptionCode(result.error().code(), result.error().message());
                 }
-                /*
-                 * I/O overuse monitor reads from data partition on init so register the I/O
-                 * overuse monitor only on boot-complete.
-                 *
-                 * TODO(b/167240592): Uncomment the below code block after the I/O overuse monitor
-                 *  is completely implemented.
-                 * if (const auto result
-                 *          = mWatchdogPerfService->registerDataProcessor(mIoOveruseMonitor);
-                 *     !result.ok()) {
-                 *    ALOGW("Failed to register I/O overuse monitor to watchdog performance "
-                 *          "service: %s", result.error().message().c_str());
-                 * }
-                 */
             }
             return Status::ok();
         }
@@ -194,6 +200,8 @@
     if (!status.isOk()) {
         return status;
     }
+    // Maybe retry registring I/O overuse monitor if failed to initialize previously.
+    checkAndRegisterIoOveruseMonitor();
     if (const auto result = mIoOveruseMonitor->updateResourceOveruseConfigurations(configs);
         !result.ok()) {
         return fromExceptionCode(result.error().code(), result.error().message());
@@ -207,6 +215,8 @@
     if (!status.isOk()) {
         return status;
     }
+    // Maybe retry registring I/O overuse monitor if failed to initialize previously.
+    checkAndRegisterIoOveruseMonitor();
     if (const auto result = mIoOveruseMonitor->getResourceOveruseConfigurations(configs);
         !result.ok()) {
         return fromExceptionCode(result.error().code(), result.error().message());
diff --git a/cpp/watchdog/server/src/WatchdogInternalHandler.h b/cpp/watchdog/server/src/WatchdogInternalHandler.h
index c155523..098b0dd 100644
--- a/cpp/watchdog/server/src/WatchdogInternalHandler.h
+++ b/cpp/watchdog/server/src/WatchdogInternalHandler.h
@@ -32,6 +32,7 @@
 #include <binder/Status.h>
 #include <gtest/gtest_prod.h>
 #include <utils/Errors.h>
+#include <utils/String16.h>
 #include <utils/Vector.h>
 
 namespace android {
@@ -55,7 +56,7 @@
           mIoOveruseMonitor(ioOveruseMonitor) {}
     ~WatchdogInternalHandler() { terminate(); }
 
-    status_t dump(int fd, const Vector<String16>& args) override;
+    status_t dump(int fd, const Vector<android::String16>& args) override;
     android::binder::Status registerCarWatchdogService(
             const android::sp<
                     android::automotive::watchdog::internal::ICarWatchdogServiceForSystem>& service)
@@ -103,6 +104,8 @@
     }
 
 private:
+    void checkAndRegisterIoOveruseMonitor();
+
     android::sp<WatchdogBinderMediator> mBinderMediator;
     android::sp<IWatchdogServiceHelperInterface> mWatchdogServiceHelper;
     android::sp<WatchdogProcessService> mWatchdogProcessService;
diff --git a/cpp/watchdog/server/src/WatchdogPerfService.cpp b/cpp/watchdog/server/src/WatchdogPerfService.cpp
index 79177ad..214a16c 100644
--- a/cpp/watchdog/server/src/WatchdogPerfService.cpp
+++ b/cpp/watchdog/server/src/WatchdogPerfService.cpp
@@ -15,6 +15,7 @@
  */
 
 #define LOG_TAG "carwatchdogd"
+#define DEBUG false  // STOPSHIP if true.
 
 #include "WatchdogPerfService.h"
 
@@ -36,6 +37,7 @@
 namespace watchdog {
 
 using ::android::String16;
+using ::android::String8;
 using ::android::base::Error;
 using ::android::base::Join;
 using ::android::base::ParseUint;
@@ -140,6 +142,9 @@
     }
     Mutex::Autolock lock(mMutex);
     mDataProcessors.push_back(processor);
+    if (DEBUG) {
+        ALOGD("Successfully registered %s to %s", processor->name().c_str(), kServiceName);
+    }
     return {};
 }
 
@@ -241,6 +246,9 @@
     }
     if (mCollectionThread.joinable()) {
         mCollectionThread.join();
+        if (DEBUG) {
+            ALOGD("%s collection thread terminated", kServiceName);
+        }
     }
 }
 
@@ -260,6 +268,9 @@
     mBoottimeCollection.lastUptime = mHandlerLooper->now();
     mHandlerLooper->removeMessages(this);
     mHandlerLooper->sendMessage(this, SwitchMessage::END_BOOTTIME_COLLECTION);
+    if (DEBUG) {
+        ALOGD("Boot-time event finished");
+    }
     return {};
 }
 
@@ -471,6 +482,9 @@
         }
     }
 
+    if (DEBUG) {
+        ALOGD("Custom event finished");
+    }
     WriteStringToFd(kDumpMajorDelimiter, fd);
     return {};
 }
@@ -564,6 +578,9 @@
               toString(mCurrCollectionEvent));
         return {};
     }
+    if (DEBUG) {
+        ALOGD("Processing %s collection event", toString(metadata->eventType));
+    }
     if (metadata->interval < kMinEventInterval) {
         return Error()
                 << "Collection interval of "
@@ -636,6 +653,9 @@
     if (metadata->eventType != static_cast<int>(EventType::PERIODIC_MONITOR)) {
         return Error() << "Invalid monitor event " << toString(metadata->eventType);
     }
+    if (DEBUG) {
+        ALOGD("Processing %s monitor event", toString(metadata->eventType));
+    }
     if (metadata->interval < kMinEventInterval) {
         return Error()
                 << "Monitor interval of "
diff --git a/cpp/watchdog/server/src/WatchdogPerfService.h b/cpp/watchdog/server/src/WatchdogPerfService.h
index c44659b..0551d35 100644
--- a/cpp/watchdog/server/src/WatchdogPerfService.h
+++ b/cpp/watchdog/server/src/WatchdogPerfService.h
@@ -171,7 +171,8 @@
     // 1. Starts a custom collection.
     // 2. Or ends the current custom collection and dumps the collected data.
     // Returns any error observed during the dump generation.
-    virtual android::base::Result<void> onCustomCollection(int fd, const Vector<String16>& args);
+    virtual android::base::Result<void> onCustomCollection(int fd,
+                                                           const Vector<android::String16>& args);
 
     // Generates a dump from the boot-time and periodic collection events.
     virtual android::base::Result<void> onDump(int fd);
diff --git a/cpp/watchdog/server/src/WatchdogProcessService.cpp b/cpp/watchdog/server/src/WatchdogProcessService.cpp
index 7af7ecc..952c83e 100644
--- a/cpp/watchdog/server/src/WatchdogProcessService.cpp
+++ b/cpp/watchdog/server/src/WatchdogProcessService.cpp
@@ -48,6 +48,7 @@
 using aawi::ICarWatchdogServiceForSystem;
 using ::android::IBinder;
 using ::android::sp;
+using ::android::String16;
 using ::android::base::Error;
 using ::android::base::GetProperty;
 using ::android::base::ReadFileToString;
diff --git a/cpp/watchdog/server/src/WatchdogProcessService.h b/cpp/watchdog/server/src/WatchdogProcessService.h
index c4ae2cb..288f99e 100644
--- a/cpp/watchdog/server/src/WatchdogProcessService.h
+++ b/cpp/watchdog/server/src/WatchdogProcessService.h
@@ -50,7 +50,8 @@
 
     android::base::Result<void> start();
     void terminate();
-    virtual android::base::Result<void> dump(int fd, const android::Vector<String16>& args);
+    virtual android::base::Result<void> dump(int fd,
+                                             const android::Vector<android::String16>& args);
     void doHealthCheck(int what);
 
     virtual android::base::Result<void> registerWatchdogServiceHelper(
diff --git a/cpp/watchdog/server/src/WatchdogServiceHelper.cpp b/cpp/watchdog/server/src/WatchdogServiceHelper.cpp
index f89942e..03f3305 100644
--- a/cpp/watchdog/server/src/WatchdogServiceHelper.cpp
+++ b/cpp/watchdog/server/src/WatchdogServiceHelper.cpp
@@ -34,7 +34,6 @@
 using aawi::PackageIoOveruseStats;
 using ::android::IBinder;
 using ::android::sp;
-using ::android::String16;
 using ::android::wp;
 using ::android::base::Error;
 using ::android::base::Result;
@@ -180,21 +179,17 @@
 Status WatchdogServiceHelper::getPackageInfosForUids(
         const std::vector<int32_t>& uids, const std::vector<std::string>& vendorPackagePrefixes,
         std::vector<PackageInfo>* packageInfos) {
-    /*
-     * The expected number of vendor package prefixes is in the order of 10s. Thus the overhead of
-     * forwarding these in each get call is very low.
-     */
-    std::vector<String16> prefixes;
-    for (const auto& prefix : vendorPackagePrefixes) {
-        prefixes.push_back(String16(prefix.c_str()));
-    }
     sp<ICarWatchdogServiceForSystem> service;
     if (std::shared_lock readLock(mRWMutex); mService == nullptr) {
         return fromExceptionCode(Status::EX_ILLEGAL_STATE, "Watchdog service is not initialized");
     } else {
         service = mService;
     }
-    return service->getPackageInfosForUids(uids, prefixes, packageInfos);
+    /*
+     * The expected number of vendor package prefixes is in the order of 10s. Thus the overhead of
+     * forwarding these in each get call is very low.
+     */
+    return service->getPackageInfosForUids(uids, vendorPackagePrefixes, packageInfos);
 }
 
 Status WatchdogServiceHelper::latestIoOveruseStats(
diff --git a/cpp/watchdog/server/tests/IoOveruseConfigsTest.cpp b/cpp/watchdog/server/tests/IoOveruseConfigsTest.cpp
index f803566..433f027 100644
--- a/cpp/watchdog/server/tests/IoOveruseConfigsTest.cpp
+++ b/cpp/watchdog/server/tests/IoOveruseConfigsTest.cpp
@@ -18,7 +18,6 @@
 
 #include <android-base/strings.h>
 #include <gmock/gmock.h>
-#include <utils/String8.h>
 
 #include <inttypes.h>
 
@@ -27,8 +26,6 @@
 namespace watchdog {
 
 using ::android::sp;
-using ::android::String16;
-using ::android::String8;
 using ::android::automotive::watchdog::internal::ApplicationCategoryType;
 using ::android::automotive::watchdog::internal::ComponentType;
 using ::android::automotive::watchdog::internal::IoOveruseAlertThreshold;
@@ -86,7 +83,7 @@
 PerStateIoOveruseThreshold toPerStateIoOveruseThreshold(const std::string& name,
                                                         const PerStateBytes& perStateBytes) {
     PerStateIoOveruseThreshold threshold;
-    threshold.name = String16(String8(name.c_str()));
+    threshold.name = name;
     threshold.perStateWriteBytes = perStateBytes;
     return threshold;
 }
@@ -101,7 +98,7 @@
                                                         const int64_t bgBytes,
                                                         const int64_t garageModeBytes) {
     PerStateIoOveruseThreshold threshold;
-    threshold.name = String16(String8(name.c_str()));
+    threshold.name = name;
     threshold.perStateWriteBytes = toPerStateBytes(fgBytes, bgBytes, garageModeBytes);
     return threshold;
 }
@@ -133,23 +130,13 @@
         const char* packageName, const ComponentType componentType,
         const ApplicationCategoryType appCategoryType = ApplicationCategoryType::OTHERS) {
     PackageInfo packageInfo;
-    packageInfo.packageIdentifier.name = String16(packageName);
+    packageInfo.packageIdentifier.name = packageName;
     packageInfo.uidType = UidType::APPLICATION;
     packageInfo.componentType = componentType;
     packageInfo.appCategoryType = appCategoryType;
     return packageInfo;
 }
 
-std::vector<String16> toString16Vector(const std::vector<std::string>& values) {
-    std::vector<String16> output;
-    for (const auto& v : values) {
-        if (!v.empty()) {
-            output.emplace_back(String16(String8(v.c_str())));
-        }
-    }
-    return output;
-}
-
 ResourceOveruseConfiguration constructResourceOveruseConfig(
         const ComponentType type, const std::vector<std::string>&& safeToKill,
         const std::vector<std::string>&& vendorPrefixes,
@@ -157,8 +144,8 @@
         const IoOveruseConfiguration& ioOveruseConfiguration) {
     ResourceOveruseConfiguration resourceOveruseConfig;
     resourceOveruseConfig.componentType = type;
-    resourceOveruseConfig.safeToKillPackages = toString16Vector(safeToKill);
-    resourceOveruseConfig.vendorPackagePrefixes = toString16Vector(vendorPrefixes);
+    resourceOveruseConfig.safeToKillPackages = safeToKill;
+    resourceOveruseConfig.vendorPackagePrefixes = vendorPrefixes;
     resourceOveruseConfig.packageMetadata = packageMetadata;
     ResourceSpecificConfiguration config;
     config.set<ResourceSpecificConfiguration::ioOveruseConfiguration>(ioOveruseConfiguration);
@@ -318,7 +305,7 @@
     ASSERT_RESULT_OK(ioOveruseConfigs.update(
             {systemResourceConfig, vendorResourceConfig, thirdPartyResourceConfig}));
 
-    vendorResourceConfig.vendorPackagePrefixes.push_back(String16(String8("vendorPkgB")));
+    vendorResourceConfig.vendorPackagePrefixes.push_back("vendorPkgB");
     std::vector<ResourceOveruseConfiguration> expected = {systemResourceConfig,
                                                           vendorResourceConfig,
                                                           thirdPartyResourceConfig};
@@ -755,7 +742,7 @@
     const auto ioOveruseConfigs = sampleIoOveruseConfigs();
 
     PackageInfo packageInfo;
-    packageInfo.packageIdentifier.name = String16("native package");
+    packageInfo.packageIdentifier.name = "native package";
     packageInfo.uidType = UidType::NATIVE;
     packageInfo.componentType = ComponentType::SYSTEM;
 
diff --git a/cpp/watchdog/server/tests/IoOveruseMonitorTest.cpp b/cpp/watchdog/server/tests/IoOveruseMonitorTest.cpp
index 38a0040..4ee94eb 100644
--- a/cpp/watchdog/server/tests/IoOveruseMonitorTest.cpp
+++ b/cpp/watchdog/server/tests/IoOveruseMonitorTest.cpp
@@ -31,12 +31,12 @@
 namespace watchdog {
 
 constexpr size_t kTestMonitorBufferSize = 3;
+constexpr uint64_t KTestMinSyncWrittenBytes = 5'000;
 constexpr double kTestIoOveruseWarnPercentage = 80;
 constexpr std::chrono::seconds kTestMonitorInterval = 5s;
 
 using ::android::IPCThreadState;
 using ::android::RefBase;
-using ::android::String16;
 using ::android::automotive::watchdog::internal::ApplicationCategoryType;
 using ::android::automotive::watchdog::internal::ComponentType;
 using ::android::automotive::watchdog::internal::IoOveruseAlertThreshold;
@@ -69,7 +69,7 @@
 
 PackageIdentifier constructPackageIdentifier(const char* packageName, const int32_t uid) {
     PackageIdentifier packageIdentifier;
-    packageIdentifier.name = String16(String8(packageName));
+    packageIdentifier.name = packageName;
     packageIdentifier.uid = uid;
     return packageIdentifier;
 }
@@ -175,6 +175,7 @@
         if (const auto result = mIoOveruseMonitor->init(); !result.ok()) {
             return result;
         }
+        mIoOveruseMonitor->mMinSyncWrittenBytes = KTestMinSyncWrittenBytes;
         mIoOveruseMonitor->mPeriodicMonitorBufferSize = kTestMonitorBufferSize;
         mIoOveruseMonitor->mIoOveruseWarnPercentage = kTestIoOveruseWarnPercentage;
         mIoOveruseMonitor->mIoOveruseConfigs = ioOveruseConfigs;
@@ -220,23 +221,22 @@
 };
 
 TEST_F(IoOveruseMonitorTest, TestOnPeriodicCollection) {
-    std::unordered_map<uid_t, PackageInfo> packageInfoMapping = {
-            {1001000,
-             constructPackageInfo(
-                     /*packageName=*/"system.daemon", /*uid=*/1001000, UidType::NATIVE)},
-            {1112345,
-             constructPackageInfo(
-                     /*packageName=*/"com.android.google.package", /*uid=*/1112345,
-                     UidType::APPLICATION)},
-            {1212345,
-             constructPackageInfo(
-                     /*packageName=*/"com.android.google.package", /*uid=*/1212345,
-                     UidType::APPLICATION)},
-            {1113999,
-             constructPackageInfo(
-                     /*packageName=*/"com.android.google.package", /*uid=*/1113999,
-                     UidType::APPLICATION)},
-    };
+    std::unordered_map<uid_t, PackageInfo> packageInfoMapping =
+            {{1001000,
+              constructPackageInfo(
+                      /*packageName=*/"system.daemon", /*uid=*/1001000, UidType::NATIVE)},
+             {1112345,
+              constructPackageInfo(
+                      /*packageName=*/"com.android.google.package", /*uid=*/1112345,
+                      UidType::APPLICATION)},
+             {1212345,
+              constructPackageInfo(
+                      /*packageName=*/"com.android.google.package", /*uid=*/1212345,
+                      UidType::APPLICATION)},
+             {1113999,
+              constructPackageInfo(
+                      /*packageName=*/"com.android.google.package", /*uid=*/1113999,
+                      UidType::APPLICATION)}};
     ON_CALL(*mMockPackageInfoResolver, getPackageInfosForUids(_))
             .WillByDefault(Return(packageInfoMapping));
     mMockIoOveruseConfigs->injectPackageConfigs({
@@ -272,24 +272,25 @@
 
     ASSERT_RESULT_OK(
             mIoOveruseMonitor->onPeriodicCollection(currentTime, mockUidIoStats, nullptr, nullptr));
-    std::vector<PackageIoOveruseStats> expectedIoOveruseStats = {
-            constructPackageIoOveruseStats(/*uid*=*/1001000, /*shouldNotify=*/false,
-                                           /*isKillable=*/false, /*remaining=*/
-                                           constructPerStateBytes(10'000, 20'000, 100'000),
-                                           /*written=*/constructPerStateBytes(70'000, 20'000, 0),
-                                           /*totalOveruses=*/0, startTime, durationInSeconds),
-            constructPackageIoOveruseStats(/*uid*=*/1112345, /*shouldNotify=*/false,
-                                           /*isKillable=*/true, /*remaining=*/
-                                           constructPerStateBytes(35'000, 15'000, 100'000),
-                                           /*written=*/constructPerStateBytes(35'000, 15'000, 0),
-                                           /*totalOveruses=*/0, startTime, durationInSeconds),
-            // Exceeds threshold.
-            constructPackageIoOveruseStats(/*uid*=*/1212345, /*shouldNotify=*/true,
-                                           /*isKillable=*/true,
-                                           /*remaining=*/constructPerStateBytes(0, 10'000, 100'000),
-                                           /*written=*/constructPerStateBytes(70'000, 20'000, 0),
-                                           /*totalOveruses=*/1, startTime, durationInSeconds),
-    };
+
+    std::vector<PackageIoOveruseStats> expectedIoOveruseStats =
+            {constructPackageIoOveruseStats(/*uid*=*/1001000, /*shouldNotify=*/false,
+                                            /*isKillable=*/false, /*remaining=*/
+                                            constructPerStateBytes(10'000, 20'000, 100'000),
+                                            /*written=*/constructPerStateBytes(70'000, 20'000, 0),
+                                            /*totalOveruses=*/0, startTime, durationInSeconds),
+             constructPackageIoOveruseStats(/*uid*=*/1112345, /*shouldNotify=*/false,
+                                            /*isKillable=*/true, /*remaining=*/
+                                            constructPerStateBytes(35'000, 15'000, 100'000),
+                                            /*written=*/constructPerStateBytes(35'000, 15'000, 0),
+                                            /*totalOveruses=*/0, startTime, durationInSeconds),
+             // Exceeds threshold.
+             constructPackageIoOveruseStats(/*uid*=*/1212345, /*shouldNotify=*/true,
+                                            /*isKillable=*/true,
+                                            /*remaining=*/
+                                            constructPerStateBytes(0, 10'000, 100'000),
+                                            /*written=*/constructPerStateBytes(70'000, 20'000, 0),
+                                            /*totalOveruses=*/1, startTime, durationInSeconds)};
     EXPECT_THAT(actualIoOveruseStats, UnorderedElementsAreArray(expectedIoOveruseStats))
             << "Expected: " << toString(expectedIoOveruseStats)
             << "\nActual: " << toString(actualIoOveruseStats);
@@ -384,15 +385,171 @@
             << "\nActual: " << toString(actualIoOveruseStats);
 }
 
+TEST_F(IoOveruseMonitorTest, TestOnPeriodicCollectionWithZeroWriteBytes) {
+    sp<MockUidIoStats> mockUidIoStats = new MockUidIoStats();
+    mockUidIoStats->expectDeltaStats(
+            {{1001000, IoUsage(10, 0, /*fgWrBytes=*/0, /*bgWrBytes=*/0, 1, 0)},
+             {1112345, IoUsage(0, 20, /*fgWrBytes=*/0, /*bgWrBytes=*/0, 0, 0)},
+             {1212345, IoUsage(0, 00, /*fgWrBytes=*/0, /*bgWrBytes=*/0, 0, 1)}});
+
+    EXPECT_CALL(*mMockPackageInfoResolver, getPackageInfosForUids(_)).Times(0);
+    EXPECT_CALL(*mMockIoOveruseConfigs, fetchThreshold(_)).Times(0);
+    EXPECT_CALL(*mMockIoOveruseConfigs, isSafeToKill(_)).Times(0);
+    EXPECT_CALL(*mMockWatchdogServiceHelper, latestIoOveruseStats(_)).Times(0);
+
+    ASSERT_RESULT_OK(
+            mIoOveruseMonitor->onPeriodicCollection(std::chrono::system_clock::to_time_t(
+                                                            std::chrono::system_clock::now()),
+                                                    mockUidIoStats, nullptr, nullptr));
+}
+
+TEST_F(IoOveruseMonitorTest, TestOnPeriodicCollectionWithSmallWrittenBytes) {
+    std::unordered_map<uid_t, PackageInfo> packageInfoMapping =
+            {{1001000,
+              constructPackageInfo(
+                      /*packageName=*/"system.daemon", /*uid=*/1001000, UidType::NATIVE)},
+             {1112345,
+              constructPackageInfo(
+                      /*packageName=*/"com.android.google.package", /*uid=*/1112345,
+                      UidType::APPLICATION)},
+             {1212345,
+              constructPackageInfo(
+                      /*packageName=*/"com.android.google.package", /*uid=*/1212345,
+                      UidType::APPLICATION)},
+             {1312345,
+              constructPackageInfo(
+                      /*packageName=*/"com.android.google.package", /*uid=*/1312345,
+                      UidType::APPLICATION)}};
+    EXPECT_CALL(*mMockPackageInfoResolver, getPackageInfosForUids(_))
+            .WillRepeatedly(Return(packageInfoMapping));
+    mMockIoOveruseConfigs->injectPackageConfigs(
+            {{"system.daemon",
+              {constructPerStateBytes(/*fgBytes=*/80'000, /*bgBytes=*/40'000, /*gmBytes=*/100'000),
+               /*isSafeToKill=*/false}},
+             {"com.android.google.package",
+              {constructPerStateBytes(/*fgBytes=*/70'000, /*bgBytes=*/30'000, /*gmBytes=*/100'000),
+               /*isSafeToKill=*/true}}});
+
+    sp<MockUidIoStats> mockUidIoStats = new MockUidIoStats();
+    /*
+     * UID 1212345 current written bytes < |KTestMinSyncWrittenBytes| so the UID's stats are not
+     * synced.
+     */
+    mockUidIoStats->expectDeltaStats(
+            {{1001000, IoUsage(10, 0, /*fgWrBytes=*/59'200, /*bgWrBytes=*/0, 1, 0)},
+             {1112345, IoUsage(0, 20, /*fgWrBytes=*/0, /*bgWrBytes=*/25'200, 0, 0)},
+             {1212345, IoUsage(0, 00, /*fgWrBytes=*/300, /*bgWrBytes=*/600, 0, 1)},
+             {1312345, IoUsage(0, 00, /*fgWrBytes=*/51'200, /*bgWrBytes=*/0, 0, 1)}});
+
+    std::vector<PackageIoOveruseStats> actualIoOveruseStats;
+    EXPECT_CALL(*mMockWatchdogServiceHelper, latestIoOveruseStats(_))
+            .WillOnce(DoAll(SaveArg<0>(&actualIoOveruseStats), Return(Status::ok())));
+
+    time_t currentTime = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
+    const auto [startTime, durationInSeconds] = calculateStartAndDuration(currentTime);
+
+    ASSERT_RESULT_OK(
+            mIoOveruseMonitor->onPeriodicCollection(currentTime, mockUidIoStats, nullptr, nullptr));
+
+    std::vector<PackageIoOveruseStats> expectedIoOveruseStats =
+            {constructPackageIoOveruseStats(/*uid*=*/1001000, /*shouldNotify=*/false,
+                                            /*isKillable=*/false, /*remaining=*/
+                                            constructPerStateBytes(20'800, 40'000, 100'000),
+                                            /*written=*/
+                                            constructPerStateBytes(59'200, 0, 0),
+                                            /*totalOveruses=*/0, startTime, durationInSeconds),
+             constructPackageIoOveruseStats(/*uid*=*/1112345, /*shouldNotify=*/true,
+                                            /*isKillable=*/true, /*remaining=*/
+                                            constructPerStateBytes(70'000, 4'800, 100'000),
+                                            /*written=*/constructPerStateBytes(0, 25'200, 0),
+                                            /*totalOveruses=*/0, startTime, durationInSeconds),
+             constructPackageIoOveruseStats(/*uid*=*/1312345, /*shouldNotify=*/false,
+                                            /*isKillable=*/true, /*remaining=*/
+                                            constructPerStateBytes(18'800, 30'000, 100'000),
+                                            /*written=*/constructPerStateBytes(51'200, 0, 0),
+                                            /*totalOveruses=*/0, startTime, durationInSeconds)};
+
+    EXPECT_THAT(actualIoOveruseStats, UnorderedElementsAreArray(expectedIoOveruseStats))
+            << "Expected: " << toString(expectedIoOveruseStats)
+            << "\nActual: " << toString(actualIoOveruseStats);
+
+    actualIoOveruseStats.clear();
+    EXPECT_CALL(*mMockWatchdogServiceHelper, latestIoOveruseStats(_))
+            .WillOnce(DoAll(SaveArg<0>(&actualIoOveruseStats), Return(Status::ok())));
+
+    /*
+     * UID 1001000 current written bytes is < |kTestMinSyncWrittenBytes| but exceeds warn threshold
+     * but not killable so the UID's stats are not synced.
+     * UID 1112345 current written bytes is < |kTestMinSyncWrittenBytes| but exceeds threshold so
+     * the UID's stats are synced.
+     * UID 1212345 current written bytes is < |kTestMinSyncWrittenBytes| but total written bytes
+     * since last synced > |kTestMinSyncWrittenBytes| so the UID's stats are synced.
+     * UID 1312345 current written bytes is < |kTestMinSyncWrittenBytes| but exceeds warn threshold
+     * and killable so the UID's stat are synced.
+     */
+    mockUidIoStats->expectDeltaStats(
+            {{1001000,
+              IoUsage(10, 0, /*fgWrBytes=*/KTestMinSyncWrittenBytes - 100, /*bgWrBytes=*/0, 1, 0)},
+             {1112345,
+              IoUsage(0, 20, /*fgWrBytes=*/0, /*bgWrBytes=*/KTestMinSyncWrittenBytes - 100, 0, 0)},
+             {1212345,
+              IoUsage(0, 00, /*fgWrBytes=*/KTestMinSyncWrittenBytes - 300, /*bgWrBytes=*/0, 0, 1)},
+             {1312345,
+              IoUsage(0, 00, /*fgWrBytes=*/KTestMinSyncWrittenBytes - 100, /*bgWrBytes=*/0, 0,
+                      1)}});
+
+    ASSERT_RESULT_OK(
+            mIoOveruseMonitor->onPeriodicCollection(currentTime, mockUidIoStats, nullptr, nullptr));
+
+    expectedIoOveruseStats =
+            {constructPackageIoOveruseStats(/*uid*=*/1112345, /*shouldNotify=*/true,
+                                            /*isKillable=*/true, /*remaining=*/
+                                            constructPerStateBytes(70'000, 0, 100'000),
+                                            /*written=*/constructPerStateBytes(0, 30'100, 0),
+                                            /*totalOveruses=*/1, startTime, durationInSeconds),
+             constructPackageIoOveruseStats(/*uid*=*/1212345, /*shouldNotify=*/false,
+                                            /*isKillable=*/true, /*remaining=*/
+                                            constructPerStateBytes(65'000, 29'400, 100'000),
+                                            /*written=*/constructPerStateBytes(5'000, 600, 0),
+                                            /*totalOveruses=*/0, startTime, durationInSeconds),
+             constructPackageIoOveruseStats(/*uid*=*/1312345, /*shouldNotify=*/true,
+                                            /*isKillable=*/true, /*remaining=*/
+                                            constructPerStateBytes(13'900, 30'000, 100'000),
+                                            /*written=*/constructPerStateBytes(56'100, 0, 0),
+                                            /*totalOveruses=*/0, startTime, durationInSeconds)};
+    EXPECT_THAT(actualIoOveruseStats, UnorderedElementsAreArray(expectedIoOveruseStats))
+            << "Expected: " << toString(expectedIoOveruseStats)
+            << "\nActual: " << toString(actualIoOveruseStats);
+}
+
+TEST_F(IoOveruseMonitorTest, TestOnPeriodicCollectionWithNoPackageInfo) {
+    sp<MockUidIoStats> mockUidIoStats = new MockUidIoStats();
+    mockUidIoStats->expectDeltaStats(
+            {{1001000, IoUsage(0, 0, /*fgWrBytes=*/70'000, /*bgWrBytes=*/20'000, 0, 0)},
+             {1112345, IoUsage(0, 0, /*fgWrBytes=*/35'000, /*bgWrBytes=*/15'000, 0, 0)},
+             {1212345, IoUsage(0, 0, /*fgWrBytes=*/70'000, /*bgWrBytes=*/20'000, 0, 0)}});
+
+    ON_CALL(*mMockPackageInfoResolver, getPackageInfosForUids(_))
+            .WillByDefault(Return(std::unordered_map<uid_t, PackageInfo>{}));
+
+    EXPECT_CALL(*mMockIoOveruseConfigs, fetchThreshold(_)).Times(0);
+    EXPECT_CALL(*mMockIoOveruseConfigs, isSafeToKill(_)).Times(0);
+    EXPECT_CALL(*mMockWatchdogServiceHelper, latestIoOveruseStats(_)).Times(0);
+
+    ASSERT_RESULT_OK(
+            mIoOveruseMonitor->onPeriodicCollection(std::chrono::system_clock::to_time_t(
+                                                            std::chrono::system_clock::now()),
+                                                    mockUidIoStats, nullptr, nullptr));
+}
+
 TEST_F(IoOveruseMonitorTest, TestOnPeriodicMonitor) {
-    IIoOveruseConfigs::IoOveruseAlertThresholdSet alertThresholds = {
-            toIoOveruseAlertThreshold(
-                    /*durationInSeconds=*/10, /*writtenBytesPerSecond=*/15'360),
-            toIoOveruseAlertThreshold(
-                    /*durationInSeconds=*/17, /*writtenBytesPerSecond=*/10'240),
-            toIoOveruseAlertThreshold(
-                    /*durationInSeconds=*/23, /*writtenBytesPerSecond=*/7'168),
-    };
+    IIoOveruseConfigs::IoOveruseAlertThresholdSet alertThresholds =
+            {toIoOveruseAlertThreshold(
+                     /*durationInSeconds=*/10, /*writtenBytesPerSecond=*/15'360),
+             toIoOveruseAlertThreshold(
+                     /*durationInSeconds=*/17, /*writtenBytesPerSecond=*/10'240),
+             toIoOveruseAlertThreshold(
+                     /*durationInSeconds=*/23, /*writtenBytesPerSecond=*/7'168)};
     ON_CALL(*mMockIoOveruseConfigs, systemWideAlertThresholds())
             .WillByDefault(ReturnRef(alertThresholds));
 
diff --git a/cpp/watchdog/server/tests/MockCarWatchdogServiceForSystem.h b/cpp/watchdog/server/tests/MockCarWatchdogServiceForSystem.h
index 8ea4ba3..c8f1614 100644
--- a/cpp/watchdog/server/tests/MockCarWatchdogServiceForSystem.h
+++ b/cpp/watchdog/server/tests/MockCarWatchdogServiceForSystem.h
@@ -24,7 +24,6 @@
 #include <binder/Status.h>
 #include <gmock/gmock.h>
 #include <utils/RefBase.h>
-#include <utils/String16.h>
 #include <utils/StrongPointer.h>
 
 namespace android {
@@ -45,7 +44,7 @@
                 (int32_t, android::automotive::watchdog::internal::TimeoutLength), (override));
     MOCK_METHOD(android::binder::Status, prepareProcessTermination, (), (override));
     MOCK_METHOD(android::binder::Status, getPackageInfosForUids,
-                (const std::vector<int32_t>&, const std::vector<android::String16>&,
+                (const std::vector<int32_t>&, const std::vector<std::string>&,
                  std::vector<android::automotive::watchdog::internal::PackageInfo>*),
                 (override));
     MOCK_METHOD(
diff --git a/cpp/watchdog/server/tests/MockDataProcessor.h b/cpp/watchdog/server/tests/MockDataProcessor.h
index e120ff7..34a572e 100644
--- a/cpp/watchdog/server/tests/MockDataProcessor.h
+++ b/cpp/watchdog/server/tests/MockDataProcessor.h
@@ -28,7 +28,7 @@
 class MockDataProcessor : virtual public IDataProcessorInterface {
 public:
     MockDataProcessor() {
-        ON_CALL(*this, name()).WillByDefault(::testing::Return("MockedDataProcessor"));
+        EXPECT_CALL(*this, name()).WillRepeatedly(::testing::Return("MockedDataProcessor"));
     }
     MOCK_METHOD(std::string, name, (), (override));
     MOCK_METHOD(android::base::Result<void>, init, (), (override));
diff --git a/cpp/watchdog/server/tests/MockIoOveruseConfigs.h b/cpp/watchdog/server/tests/MockIoOveruseConfigs.h
index 67c1edf..cc88c1d 100644
--- a/cpp/watchdog/server/tests/MockIoOveruseConfigs.h
+++ b/cpp/watchdog/server/tests/MockIoOveruseConfigs.h
@@ -71,8 +71,7 @@
                 .WillByDefault([perPackageConfig = perPackageConfig](
                                        const android::automotive::watchdog::internal::PackageInfo&
                                                packageInfo) {
-                    const std::string packageName =
-                            std::string(String8(packageInfo.packageIdentifier.name));
+                    const std::string packageName = packageInfo.packageIdentifier.name;
                     if (const auto it = perPackageConfig.find(packageName);
                         it != perPackageConfig.end()) {
                         return it->second.threshold;
@@ -83,8 +82,7 @@
                 .WillByDefault([perPackageConfig = perPackageConfig](
                                        const android::automotive::watchdog::internal::PackageInfo&
                                                packageInfo) {
-                    const std::string packageName =
-                            std::string(String8(packageInfo.packageIdentifier.name));
+                    const std::string packageName = packageInfo.packageIdentifier.name;
                     if (const auto it = perPackageConfig.find(packageName);
                         it != perPackageConfig.end()) {
                         return it->second.isSafeToKill;
diff --git a/cpp/watchdog/server/tests/MockIoOveruseMonitor.h b/cpp/watchdog/server/tests/MockIoOveruseMonitor.h
index 221b1a1..cb7d0c5 100644
--- a/cpp/watchdog/server/tests/MockIoOveruseMonitor.h
+++ b/cpp/watchdog/server/tests/MockIoOveruseMonitor.h
@@ -35,6 +35,7 @@
         ON_CALL(*this, name()).WillByDefault(::testing::Return("MockIoOveruseMonitor"));
     }
     ~MockIoOveruseMonitor() {}
+    MOCK_METHOD(bool, isInitialized, (), (override));
     MOCK_METHOD(android::base::Result<void>, updateResourceOveruseConfigurations,
                 (const std::vector<
                         android::automotive::watchdog::internal::ResourceOveruseConfiguration>&),
diff --git a/cpp/watchdog/server/tests/MockWatchdogPerfService.h b/cpp/watchdog/server/tests/MockWatchdogPerfService.h
index 5d14837..60d165b 100644
--- a/cpp/watchdog/server/tests/MockWatchdogPerfService.h
+++ b/cpp/watchdog/server/tests/MockWatchdogPerfService.h
@@ -36,7 +36,7 @@
     MOCK_METHOD(void, terminate, (), (override));
     MOCK_METHOD(android::base::Result<void>, onBootFinished, (), (override));
     MOCK_METHOD(android::base::Result<void>, onCustomCollection,
-                (int fd, const Vector<String16>& args), (override));
+                (int fd, const Vector<android::String16>& args), (override));
     MOCK_METHOD(android::base::Result<void>, onDump, (int fd), (override));
 };
 
diff --git a/cpp/watchdog/server/tests/MockWatchdogProcessService.h b/cpp/watchdog/server/tests/MockWatchdogProcessService.h
index 8c35f10..b720d91 100644
--- a/cpp/watchdog/server/tests/MockWatchdogProcessService.h
+++ b/cpp/watchdog/server/tests/MockWatchdogProcessService.h
@@ -40,7 +40,7 @@
 class MockWatchdogProcessService : public WatchdogProcessService {
 public:
     MockWatchdogProcessService() : WatchdogProcessService(nullptr) {}
-    MOCK_METHOD(android::base::Result<void>, dump, (int fd, const Vector<String16>& args),
+    MOCK_METHOD(android::base::Result<void>, dump, (int fd, const Vector<android::String16>& args),
                 (override));
     MOCK_METHOD(android::base::Result<void>, registerWatchdogServiceHelper,
                 (const android::sp<IWatchdogServiceHelperInterface>& helper), (override));
diff --git a/cpp/watchdog/server/tests/PackageInfoResolverTest.cpp b/cpp/watchdog/server/tests/PackageInfoResolverTest.cpp
index 92fc5b0..f2fe3a6 100644
--- a/cpp/watchdog/server/tests/PackageInfoResolverTest.cpp
+++ b/cpp/watchdog/server/tests/PackageInfoResolverTest.cpp
@@ -23,13 +23,11 @@
 #include <android/automotive/watchdog/internal/UidType.h>
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
-#include <utils/String16.h>
 
 namespace android {
 namespace automotive {
 namespace watchdog {
 
-using ::android::String16;
 using ::android::automotive::watchdog::internal::ApplicationCategoryType;
 using ::android::automotive::watchdog::internal::ComponentType;
 using ::android::automotive::watchdog::internal::PackageInfo;
@@ -53,9 +51,9 @@
 PackageInfo constructPackageInfo(const char* packageName, int32_t uid, UidType uidType,
                                  ComponentType componentType,
                                  ApplicationCategoryType appCategoryType,
-                                 std::vector<String16> sharedUidPackages = {}) {
+                                 std::vector<std::string> sharedUidPackages = {}) {
     PackageInfo packageInfo;
-    packageInfo.packageIdentifier.name = String16(packageName);
+    packageInfo.packageIdentifier.name = packageName;
     packageInfo.packageIdentifier.uid = uid;
     packageInfo.uidType = uidType;
     packageInfo.componentType = componentType;
@@ -209,7 +207,7 @@
             {6100,
              constructPackageInfo("shared:system.package.A", 6100, UidType::NATIVE,
                                   ComponentType::SYSTEM, ApplicationCategoryType::OTHERS,
-                                  {String16("system.pkg.1"), String16("system.pkg.2")})},
+                                  {"system.pkg.1", "system.pkg.2"})},
             {7700,
              constructPackageInfo("system.package.B", 7700, UidType::NATIVE, ComponentType::SYSTEM,
                                   ApplicationCategoryType::OTHERS)},
diff --git a/cpp/watchdog/server/tests/UidIoStatsTest.cpp b/cpp/watchdog/server/tests/UidIoStatsTest.cpp
index 2326381..6e88ed5 100644
--- a/cpp/watchdog/server/tests/UidIoStatsTest.cpp
+++ b/cpp/watchdog/server/tests/UidIoStatsTest.cpp
@@ -17,6 +17,7 @@
 #include "UidIoStats.h"
 
 #include <android-base/file.h>
+#include <android-base/stringprintf.h>
 #include <gmock/gmock.h>
 
 #include <unordered_map>
@@ -25,25 +26,37 @@
 namespace automotive {
 namespace watchdog {
 
+using ::android::base::StringAppendF;
 using ::android::base::WriteStringToFile;
+using ::testing::UnorderedElementsAreArray;
+
+namespace {
+
+std::string toString(std::unordered_map<uid_t, UidIoUsage> usages) {
+    std::string buffer;
+    for (const auto& [uid, usage] : usages) {
+        StringAppendF(&buffer, "{%s}\n", usage.toString().c_str());
+    }
+    return buffer;
+}
+
+}  // namespace
 
 TEST(UidIoStatsTest, TestValidStatFile) {
     // Format: uid fgRdChar fgWrChar fgRdBytes fgWrBytes bgRdChar bgWrChar bgRdBytes bgWrBytes
     // fgFsync bgFsync
-    constexpr char firstSnapshot[] =
-        "1001234 5000 1000 3000 500 0 0 0 0 20 0\n"
-        "1005678 500 100 30 50 300 400 100 200 45 60\n"
-        "1009 0 0 0 0 40000 50000 20000 30000 0 300\n"
-        "1001000 4000 3000 2000 1000 400 300 200 100 50 10\n";
-    std::unordered_map<uid_t, UidIoUsage> expectedFirstUsage = {
-            {1001234,
-             {.uid = 1001234,
-              .ios = {/*fgRdBytes=*/3000, /*bgRdBytes=*/0, /*fgWrBytes=*/500,
-                      /*bgWrBytes=*/0, /*fgFsync=*/20, /*bgFsync=*/0}}},
-            {1005678, {.uid = 1005678, .ios = {30, 100, 50, 200, 45, 60}}},
-            {1009, {.uid = 1009, .ios = {0, 20000, 0, 30000, 0, 300}}},
-            {1001000, {.uid = 1001000, .ios = {2000, 200, 1000, 100, 50, 10}}},
-    };
+    constexpr char firstSnapshot[] = "1001234 5000 1000 3000 500 0 0 0 0 20 0\n"
+                                     "1005678 500 100 30 50 300 400 100 200 45 60\n"
+                                     "1009 0 0 0 0 40000 50000 20000 30000 0 300\n"
+                                     "1001000 4000 3000 2000 1000 400 300 200 100 50 10\n";
+    std::unordered_map<uid_t, UidIoUsage> expectedFirstUsage =
+            {{1001234,
+              {.uid = 1001234,
+               .ios = {/*fgRdBytes=*/3000, /*bgRdBytes=*/0, /*fgWrBytes=*/500,
+                       /*bgWrBytes=*/0, /*fgFsync=*/20, /*bgFsync=*/0}}},
+             {1005678, {.uid = 1005678, .ios = {30, 100, 50, 200, 45, 60}}},
+             {1009, {.uid = 1009, .ios = {0, 20000, 0, 30000, 0, 300}}},
+             {1001000, {.uid = 1001000, .ios = {2000, 200, 1000, 100, 50, 10}}}};
     TemporaryFile tf;
     ASSERT_NE(tf.fd, -1);
     ASSERT_TRUE(WriteStringToFile(firstSnapshot, tf.path));
@@ -53,62 +66,38 @@
     ASSERT_RESULT_OK(uidIoStats.collect());
 
     const auto& actualFirstUsage = uidIoStats.deltaStats();
-    EXPECT_EQ(expectedFirstUsage.size(), actualFirstUsage.size());
+    EXPECT_THAT(actualFirstUsage, UnorderedElementsAreArray(expectedFirstUsage))
+            << "Expected: " << toString(expectedFirstUsage)
+            << "Actual: " << toString(actualFirstUsage);
 
-    for (const auto& it : expectedFirstUsage) {
-        if (actualFirstUsage.find(it.first) == actualFirstUsage.end()) {
-            ADD_FAILURE() << "Expected uid " << it.first << " not found in the first snapshot";
-        }
-        const UidIoUsage& expected = it.second;
-        const UidIoUsage& actual = actualFirstUsage.at(it.first);
-        EXPECT_EQ(expected.uid, actual.uid);
-        EXPECT_EQ(expected.ios, actual.ios)
-            << "Unexpected I/O usage for uid " << it.first << " in first snapshot.\nExpected:\n"
-            << expected.ios.toString() << "\nActual:\n"<< actual.ios.toString();
-    }
-
-    constexpr char secondSnapshot[] =
-        "1001234 10000 2000 7000 950 0 0 0 0 45 0\n"
-        "1005678 600 100 40 50 1000 1000 1000 600 50 70\n"
-        "1003456 300 500 200 300 0 0 0 0 50 0\n"
-        "1001000 400 300 200 100 40 30 20 10 5 1\n";
-    std::unordered_map<uid_t, UidIoUsage> expectedSecondUsage = {
-            {1001234,
-             {.uid = 1001234,
-              .ios = {/*fgRdBytes=*/4000, /*bgRdBytes=*/0,
-                      /*fgWrBytes=*/450, /*bgWrBytes=*/0, /*fgFsync=*/25,
-                      /*bgFsync=*/0}}},
-            {1005678, {.uid = 1005678, .ios = {10, 900, 0, 400, 5, 10}}},
-            {1003456, {.uid = 1003456, .ios = {200, 0, 300, 0, 50, 0}}},
-            {1001000, {.uid = 1001000, .ios = {0, 0, 0, 0, 0, 0}}},
-    };
+    constexpr char secondSnapshot[] = "1001234 10000 2000 7000 950 0 0 0 0 45 0\n"
+                                      "1005678 600 100 40 50 1000 1000 1000 600 50 70\n"
+                                      "1003456 300 500 200 300 0 0 0 0 50 0\n"
+                                      "1001000 400 300 200 100 40 30 20 10 5 1\n";
+    std::unordered_map<uid_t, UidIoUsage> expectedSecondUsage =
+            {{1001234,
+              {.uid = 1001234,
+               .ios = {/*fgRdBytes=*/4000, /*bgRdBytes=*/0,
+                       /*fgWrBytes=*/450, /*bgWrBytes=*/0, /*fgFsync=*/25,
+                       /*bgFsync=*/0}}},
+             {1005678, {.uid = 1005678, .ios = {10, 900, 0, 400, 5, 10}}},
+             {1003456, {.uid = 1003456, .ios = {200, 0, 300, 0, 50, 0}}}};
     ASSERT_TRUE(WriteStringToFile(secondSnapshot, tf.path));
     ASSERT_RESULT_OK(uidIoStats.collect());
 
     const auto& actualSecondUsage = uidIoStats.deltaStats();
-    EXPECT_EQ(expectedSecondUsage.size(), actualSecondUsage.size());
-
-    for (const auto& it : expectedSecondUsage) {
-        if (actualSecondUsage.find(it.first) == actualSecondUsage.end()) {
-            ADD_FAILURE() << "Expected uid " << it.first << " not found in the second snapshot";
-        }
-        const UidIoUsage& expected = it.second;
-        const UidIoUsage& actual = actualSecondUsage.at(it.first);
-        EXPECT_EQ(expected.uid, actual.uid);
-        EXPECT_EQ(expected.ios, actual.ios)
-            << "Unexpected I/O usage for uid " << it.first << " in second snapshot:.\nExpected:\n"
-            << expected.ios.toString() << "\nActual:\n"<< actual.ios.toString();
-    }
+    EXPECT_THAT(actualSecondUsage, UnorderedElementsAreArray(expectedSecondUsage))
+            << "Expected: " << toString(expectedSecondUsage)
+            << "Actual: " << toString(actualSecondUsage);
 }
 
 TEST(UidIoStatsTest, TestErrorOnInvalidStatFile) {
     // Format: uid fgRdChar fgWrChar fgRdBytes fgWrBytes bgRdChar bgWrChar bgRdBytes bgWrBytes
     // fgFsync bgFsync
-    constexpr char contents[] =
-        "1001234 5000 1000 3000 500 0 0 0 0 20 0\n"
-        "1005678 500 100 30 50 300 400 100 200 45 60\n"
-        "1009012 0 0 0 0 40000 50000 20000 30000 0 300\n"
-        "1001000 4000 3000 2000 1000 CORRUPTED DATA\n";
+    constexpr char contents[] = "1001234 5000 1000 3000 500 0 0 0 0 20 0\n"
+                                "1005678 500 100 30 50 300 400 100 200 45 60\n"
+                                "1009012 0 0 0 0 40000 50000 20000 30000 0 300\n"
+                                "1001000 4000 3000 2000 1000 CORRUPTED DATA\n";
     TemporaryFile tf;
     ASSERT_NE(tf.fd, -1);
     ASSERT_TRUE(WriteStringToFile(contents, tf.path));
diff --git a/cpp/watchdog/server/tests/WatchdogInternalHandlerTest.cpp b/cpp/watchdog/server/tests/WatchdogInternalHandlerTest.cpp
index 3db7e29..f5dd7dc 100644
--- a/cpp/watchdog/server/tests/WatchdogInternalHandlerTest.cpp
+++ b/cpp/watchdog/server/tests/WatchdogInternalHandlerTest.cpp
@@ -48,6 +48,7 @@
 using aawi::PackageResourceOveruseAction;
 using aawi::ResourceOveruseConfiguration;
 using ::android::sp;
+using ::android::String16;
 using ::android::base::Result;
 using ::android::binder::Status;
 using ::testing::_;
@@ -67,7 +68,7 @@
                                          -> Result<void> { return Result<void>{}; }) {}
     ~MockWatchdogBinderMediator() {}
 
-    MOCK_METHOD(status_t, dump, (int fd, const Vector<String16>& args), (override));
+    MOCK_METHOD(status_t, dump, (int fd, const Vector<android::String16>& args), (override));
 };
 
 class ScopedChangeCallingUid : public RefBase {
diff --git a/cpp/watchdog/server/tests/WatchdogPerfServiceTest.cpp b/cpp/watchdog/server/tests/WatchdogPerfServiceTest.cpp
index c7de12b..3873195 100644
--- a/cpp/watchdog/server/tests/WatchdogPerfServiceTest.cpp
+++ b/cpp/watchdog/server/tests/WatchdogPerfServiceTest.cpp
@@ -39,6 +39,7 @@
 namespace automotive {
 namespace watchdog {
 
+using ::android::String16;
 using ::android::wp;
 using ::android::automotive::watchdog::testing::LooperStub;
 using ::android::base::Error;
diff --git a/cpp/watchdog/server/tests/WatchdogServiceHelperTest.cpp b/cpp/watchdog/server/tests/WatchdogServiceHelperTest.cpp
index 681d900..17edadf 100644
--- a/cpp/watchdog/server/tests/WatchdogServiceHelperTest.cpp
+++ b/cpp/watchdog/server/tests/WatchdogServiceHelperTest.cpp
@@ -22,7 +22,6 @@
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 #include <utils/RefBase.h>
-#include <utils/String16.h>
 
 namespace android {
 namespace automotive {
@@ -41,7 +40,6 @@
 using ::android::IBinder;
 using ::android::RefBase;
 using ::android::sp;
-using ::android::String16;
 using ::android::base::Error;
 using ::android::base::Result;
 using ::android::binder::Status;
@@ -80,7 +78,7 @@
                                  ComponentType componentType,
                                  ApplicationCategoryType appCategoryType) {
     PackageInfo packageInfo;
-    packageInfo.packageIdentifier.name = String16(packageName);
+    packageInfo.packageIdentifier.name = packageName;
     packageInfo.packageIdentifier.uid = uid;
     packageInfo.uidType = uidType;
     packageInfo.componentType = componentType;
@@ -329,7 +327,6 @@
 TEST_F(WatchdogServiceHelperTest, TestGetPackageInfosForUids) {
     std::vector<int32_t> uids = {1000};
     std::vector<std::string> prefixesStr = {"vendor.package"};
-    std::vector<String16> prefixesStr16 = {String16("vendor.package")};
     std::vector<PackageInfo> expectedPackageInfo{
             constructPackageInfo("vendor.package.A", 120000, UidType::NATIVE, ComponentType::VENDOR,
                                  ApplicationCategoryType::OTHERS),
@@ -340,7 +337,7 @@
 
     registerCarWatchdogService();
 
-    EXPECT_CALL(*mMockCarWatchdogServiceForSystem, getPackageInfosForUids(uids, prefixesStr16, _))
+    EXPECT_CALL(*mMockCarWatchdogServiceForSystem, getPackageInfosForUids(uids, prefixesStr, _))
             .WillOnce(DoAll(SetArgPointee<2>(expectedPackageInfo), Return(Status::ok())));
 
     Status status =
diff --git a/service/src/com/android/car/CarShellCommand.java b/service/src/com/android/car/CarShellCommand.java
index ffa071e..6867ca5 100644
--- a/service/src/com/android/car/CarShellCommand.java
+++ b/service/src/com/android/car/CarShellCommand.java
@@ -236,6 +236,8 @@
                 PERMISSION_CAR_CONTROL_AUDIO_VOLUME);
         USER_BUILD_COMMAND_TO_PERMISSION_MAP.put(COMMAND_SET_GROUP_VOLUME,
                 PERMISSION_CAR_CONTROL_AUDIO_VOLUME);
+        USER_BUILD_COMMAND_TO_PERMISSION_MAP.put(COMMAND_INJECT_KEY,
+                android.Manifest.permission.INJECT_EVENTS);
     }
 
     private static final String PARAM_DAY_MODE = "day";
@@ -1030,9 +1032,14 @@
     private void injectKeyEvent(int action, int keyCode, int display) {
         long currentTime = SystemClock.uptimeMillis();
         if (action == KeyEvent.ACTION_DOWN) mKeyDownTime = currentTime;
-        mCarInputService.onKeyEvent(
-                new KeyEvent(/* downTime= */ mKeyDownTime, /* eventTime= */ currentTime,
-                        action, keyCode, /* repeat= */ 0), display);
+        long token = Binder.clearCallingIdentity();
+        try {
+            mCarInputService.injectKeyEvent(
+                    new KeyEvent(/* downTime= */ mKeyDownTime, /* eventTime= */ currentTime,
+                            action, keyCode, /* repeat= */ 0), display);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
     }
 
     private void injectRotary(String[] args, IndentingPrintWriter writer) {
diff --git a/service/src/com/android/car/audio/CarAudioContext.java b/service/src/com/android/car/audio/CarAudioContext.java
index 5c76e17..c93eca3 100644
--- a/service/src/com/android/car/audio/CarAudioContext.java
+++ b/service/src/com/android/car/audio/CarAudioContext.java
@@ -254,6 +254,10 @@
         return uniqueContexts;
     }
 
+    static boolean isCriticalAudioContext(@CarAudioContext.AudioContext int audioContext) {
+        return CarAudioContext.EMERGENCY == audioContext || CarAudioContext.SAFETY == audioContext;
+    }
+
     static String toString(@AudioContext int audioContext) {
         String name = CONTEXT_NAMES.get(audioContext);
         if (name != null) {
diff --git a/service/src/com/android/car/audio/CarAudioFocus.java b/service/src/com/android/car/audio/CarAudioFocus.java
index 5b5ac10..0b10e2b 100644
--- a/service/src/com/android/car/audio/CarAudioFocus.java
+++ b/service/src/com/android/car/audio/CarAudioFocus.java
@@ -15,6 +15,8 @@
  */
 package com.android.car.audio;
 
+import static com.android.car.audio.CarAudioContext.isCriticalAudioContext;
+
 import android.content.pm.PackageManager;
 import android.media.AudioAttributes;
 import android.media.AudioFocusInfo;
@@ -26,7 +28,6 @@
 import android.util.Slog;
 
 import com.android.car.CarLog;
-import com.android.car.audio.CarAudioContext.AudioContext;
 import com.android.internal.annotations.GuardedBy;
 
 import java.util.ArrayList;
@@ -131,10 +132,6 @@
         }
     }
 
-    private boolean isCriticalAudioContext(@AudioContext int audioContext) {
-        return CarAudioContext.EMERGENCY == audioContext || CarAudioContext.SAFETY == audioContext;
-    }
-
     // This sends a focus loss message to the targeted requester.
     private void sendFocusLossLocked(AudioFocusInfo loser, int lossType) {
         int result = mAudioManager.dispatchAudioFocusChange(loser, lossType,
diff --git a/service/src/com/android/car/audio/CarAudioPowerListener.java b/service/src/com/android/car/audio/CarAudioPowerListener.java
index eed53cb..0ee885c 100644
--- a/service/src/com/android/car/audio/CarAudioPowerListener.java
+++ b/service/src/com/android/car/audio/CarAudioPowerListener.java
@@ -78,7 +78,7 @@
     void startListeningForPolicyChanges() {
         if (mCarPowerManagementService == null) {
             Slog.w(TAG, "Cannot find CarPowerManagementService");
-            mCarAudioService.enableAudio();
+            mCarAudioService.setAudioEnabled(/* isAudioEnabled= */ true);
             return;
         }
 
@@ -100,7 +100,7 @@
 
         if (policy == null) {
             Slog.w(TAG, "Policy is null. Defaulting to enabled");
-            mCarAudioService.enableAudio();
+            mCarAudioService.setAudioEnabled(/* isAudioEnabled= */ true);
             return;
         }
 
@@ -112,11 +112,6 @@
     @GuardedBy("mLock")
     private void updateAudioPowerStateLocked(CarPowerPolicy policy) {
         mIsAudioEnabled = policy.isComponentEnabled(AUDIO);
-
-        if (mIsAudioEnabled) {
-            mCarAudioService.enableAudio();
-        } else {
-            mCarAudioService.disableAudio();
-        }
+        mCarAudioService.setAudioEnabled(mIsAudioEnabled);
     }
 }
diff --git a/service/src/com/android/car/audio/CarAudioService.java b/service/src/com/android/car/audio/CarAudioService.java
index 08e648d..1e81c94 100644
--- a/service/src/com/android/car/audio/CarAudioService.java
+++ b/service/src/com/android/car/audio/CarAudioService.java
@@ -1243,22 +1243,16 @@
         return getCarAudioZone(zoneId).getInputAudioDevices();
     }
 
-    void disableAudio() {
-        // TODO(b/176258537) mute everything
+    void setAudioEnabled(boolean isAudioEnabled) {
         if (Slogf.isLoggable(CarLog.TAG_AUDIO, Log.DEBUG)) {
-            Slogf.d(CarLog.TAG_AUDIO, "Disabling audio");
+            Slogf.d(CarLog.TAG_AUDIO, "Setting isAudioEnabled to %b", isAudioEnabled);
         }
 
-        mFocusHandler.setRestrictFocus(/* isFocusRestricted= */ true);
-    }
-
-    void enableAudio() {
-        // TODO(b/176258537) unmute appropriate things
-        if (Slogf.isLoggable(CarLog.TAG_AUDIO, Log.DEBUG)) {
-            Slogf.d(CarLog.TAG_AUDIO, "Enabling audio");
+        mFocusHandler.setRestrictFocus(/* isFocusRestricted= */ !isAudioEnabled);
+        if (mUseCarVolumeGroupMuting) {
+            mCarVolumeGroupMuting.setRestrictMuting(/* isMutingRestricted= */ !isAudioEnabled);
         }
-
-        mFocusHandler.setRestrictFocus(/* isFocusRestricted= */ false);
+        // TODO(b/176258537) if not using group volume, then set master mute accordingly
     }
 
     private void enforcePermission(String permissionName) {
diff --git a/service/src/com/android/car/audio/CarAudioZone.java b/service/src/com/android/car/audio/CarAudioZone.java
index 33d5d40..de540d2 100644
--- a/service/src/com/android/car/audio/CarAudioZone.java
+++ b/service/src/com/android/car/audio/CarAudioZone.java
@@ -115,7 +115,8 @@
      *
      * Note that it is fine that there are devices which do not appear in any group. Those devices
      * may be reserved for other purposes.
-     * Step value validation is done in {@link CarVolumeGroup#bind(int, CarAudioDeviceInfo)}
+     * Step value validation is done in
+     * {@link CarVolumeGroup.Builder#setDeviceInfoForContext(int, CarAudioDeviceInfo)}
      */
     boolean validateVolumeGroups() {
         Set<Integer> contexts = new HashSet<>();
diff --git a/service/src/com/android/car/audio/CarAudioZonesHelper.java b/service/src/com/android/car/audio/CarAudioZonesHelper.java
index cf85356..7956ea2 100644
--- a/service/src/com/android/car/audio/CarAudioZonesHelper.java
+++ b/service/src/com/android/car/audio/CarAudioZonesHelper.java
@@ -119,9 +119,10 @@
         }
     }
 
-    static void bindNonLegacyContexts(CarVolumeGroup group, CarAudioDeviceInfo info) {
+    static void setNonLegacyContexts(CarVolumeGroup.Builder groupBuilder,
+            CarAudioDeviceInfo info) {
         for (@AudioContext int audioContext : NON_LEGACY_CONTEXTS) {
-            group.bind(audioContext, info);
+            groupBuilder.setDeviceInfoForContext(audioContext, info);
         }
     }
 
@@ -401,19 +402,20 @@
 
     private CarVolumeGroup parseVolumeGroup(XmlPullParser parser, int zoneId, int groupId)
             throws XmlPullParserException, IOException {
-        CarVolumeGroup group =
-                new CarVolumeGroup(zoneId, groupId, mCarAudioSettings, mUseCarVolumeGroupMute);
+        CarVolumeGroup.Builder groupBuilder =
+                new CarVolumeGroup.Builder(zoneId, groupId, mCarAudioSettings,
+                        mUseCarVolumeGroupMute);
         while (parser.next() != XmlPullParser.END_TAG) {
             if (parser.getEventType() != XmlPullParser.START_TAG) continue;
             if (TAG_AUDIO_DEVICE.equals(parser.getName())) {
                 String address = parser.getAttributeValue(NAMESPACE, ATTR_DEVICE_ADDRESS);
                 validateOutputDeviceExist(address);
-                parseVolumeGroupContexts(parser, group, address);
+                parseVolumeGroupContexts(parser, groupBuilder, address);
             } else {
                 skip(parser);
             }
         }
-        return group;
+        return groupBuilder.build();
     }
 
     private void validateOutputDeviceExist(String address) {
@@ -425,7 +427,7 @@
     }
 
     private void parseVolumeGroupContexts(
-            XmlPullParser parser, CarVolumeGroup group, String address)
+            XmlPullParser parser, CarVolumeGroup.Builder groupBuilder, String address)
             throws XmlPullParserException, IOException {
         while (parser.next() != XmlPullParser.END_TAG) {
             if (parser.getEventType() != XmlPullParser.START_TAG) continue;
@@ -434,11 +436,11 @@
                         parser.getAttributeValue(NAMESPACE, ATTR_CONTEXT_NAME));
                 validateCarAudioContextSupport(carAudioContext);
                 CarAudioDeviceInfo info = mAddressToCarAudioDeviceInfo.get(address);
-                group.bind(carAudioContext, info);
+                groupBuilder.setDeviceInfoForContext(carAudioContext, info);
 
                 // If V1, default new contexts to same device as DEFAULT_AUDIO_USAGE
                 if (isVersionOne() && carAudioContext == CarAudioService.DEFAULT_AUDIO_CONTEXT) {
-                    bindNonLegacyContexts(group, info);
+                    setNonLegacyContexts(groupBuilder, info);
                 }
             }
             // Always skip to upper level since we're at the lowest.
diff --git a/service/src/com/android/car/audio/CarAudioZonesHelperLegacy.java b/service/src/com/android/car/audio/CarAudioZonesHelperLegacy.java
index 42fccf2..173252a 100644
--- a/service/src/com/android/car/audio/CarAudioZonesHelperLegacy.java
+++ b/service/src/com/android/car/audio/CarAudioZonesHelperLegacy.java
@@ -127,29 +127,15 @@
     }
 
     SparseArray<CarAudioZone> loadAudioZones() {
-        final CarAudioZone zone = new CarAudioZone(PRIMARY_AUDIO_ZONE,
-                "Primary zone");
-        for (CarVolumeGroup group : loadVolumeGroups()) {
-            zone.addVolumeGroup(group);
-            bindContextsForVolumeGroup(group);
+        CarAudioZone zone = new CarAudioZone(PRIMARY_AUDIO_ZONE, "Primary zone");
+        for (CarVolumeGroup volumeGroup : loadVolumeGroups()) {
+            zone.addVolumeGroup(volumeGroup);
         }
         SparseArray<CarAudioZone> carAudioZones = new SparseArray<>();
         carAudioZones.put(PRIMARY_AUDIO_ZONE, zone);
         return carAudioZones;
     }
 
-    private void bindContextsForVolumeGroup(CarVolumeGroup group) {
-        for (int legacyAudioContext : group.getContexts()) {
-            int busNumber = mLegacyAudioContextToBus.get(legacyAudioContext);
-            CarAudioDeviceInfo info = mBusToCarAudioDeviceInfo.get(busNumber);
-            group.bind(legacyAudioContext, info);
-
-            if (legacyAudioContext == CarAudioService.DEFAULT_AUDIO_CONTEXT) {
-                CarAudioZonesHelper.bindNonLegacyContexts(group, info);
-            }
-        }
-    }
-
     /**
      * @return all {@link CarVolumeGroup} read from configuration.
      */
@@ -186,8 +172,33 @@
         return carVolumeGroups;
     }
 
-    private CarVolumeGroup parseVolumeGroup(int id, AttributeSet attrs, XmlResourceParser parser)
-            throws XmlPullParserException, IOException {
+    private CarVolumeGroup parseVolumeGroup(int id, AttributeSet attrs,
+            XmlResourceParser parser) throws XmlPullParserException, IOException {
+        CarVolumeGroup.Builder builder = new CarVolumeGroup.Builder(PRIMARY_AUDIO_ZONE, id,
+                mCarAudioSettings, /* useCarVolumeGroupMute= */ false);
+
+        List<Integer> audioContexts = parseAudioContexts(parser, attrs);
+
+        for (int i = 0; i < audioContexts.size(); i++) {
+            bindContextToBuilder(builder, audioContexts.get(i));
+        }
+
+        return builder.build();
+    }
+
+
+    private void bindContextToBuilder(CarVolumeGroup.Builder groupBuilder, int legacyAudioContext) {
+        int busNumber = mLegacyAudioContextToBus.get(legacyAudioContext);
+        CarAudioDeviceInfo info = mBusToCarAudioDeviceInfo.get(busNumber);
+        groupBuilder.setDeviceInfoForContext(legacyAudioContext, info);
+
+        if (legacyAudioContext == CarAudioService.DEFAULT_AUDIO_CONTEXT) {
+            CarAudioZonesHelper.setNonLegacyContexts(groupBuilder, info);
+        }
+    }
+
+    private List<Integer> parseAudioContexts(XmlResourceParser parser, AttributeSet attrs)
+            throws IOException, XmlPullParserException {
         List<Integer> contexts = new ArrayList<>();
         int type;
         int innerDepth = parser.getDepth();
@@ -204,8 +215,7 @@
             }
         }
 
-        return new CarVolumeGroup(mCarAudioSettings, PRIMARY_AUDIO_ZONE, id,
-                contexts.stream().mapToInt(i -> i).filter(i -> i >= 0).toArray());
+        return contexts;
     }
 
     /**
diff --git a/service/src/com/android/car/audio/CarVolumeGroup.java b/service/src/com/android/car/audio/CarVolumeGroup.java
index cabce82..4394be0 100644
--- a/service/src/com/android/car/audio/CarVolumeGroup.java
+++ b/service/src/com/android/car/audio/CarVolumeGroup.java
@@ -28,6 +28,7 @@
 import com.android.car.CarLog;
 import com.android.car.audio.CarAudioContext.AudioContext;
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.Preconditions;
 
 import java.util.ArrayList;
@@ -46,42 +47,50 @@
 /* package */ final class CarVolumeGroup {
 
     private final boolean mUseCarVolumeGroupMute;
+    private final boolean mHasCriticalAudioContexts;
     private final CarAudioSettings mSettingsManager;
-    private final int mZoneId;
+    private final int mDefaultGain;
     private final int mId;
-    private final SparseArray<String> mContextToAddress = new SparseArray<>();
-    private final Map<String, CarAudioDeviceInfo> mAddressToCarAudioDeviceInfo = new HashMap<>();
+    private final int mMaxGain;
+    private final int mMinGain;
+    private final int mStepSize;
+    private final int mZoneId;
+    private final SparseArray<String> mContextToAddress;
+    private final Map<String, CarAudioDeviceInfo> mAddressToCarAudioDeviceInfo;
 
     private final Object mLock = new Object();
 
-    private int mDefaultGain = Integer.MIN_VALUE;
-    private int mMaxGain = Integer.MIN_VALUE;
-    private int mMinGain = Integer.MAX_VALUE;
-    private int mStepSize = 0;
+    @GuardedBy("mLock")
     private int mStoredGainIndex;
+    @GuardedBy("mLock")
     private int mCurrentGainIndex = -1;
+    @GuardedBy("mLock")
     private boolean mIsMuted;
+    @GuardedBy("mLock")
     private @UserIdInt int mUserId = UserHandle.USER_CURRENT;
 
-    CarVolumeGroup(int zoneId, int id, CarAudioSettings settings, boolean useCarVolumeGroupMute) {
-        mSettingsManager = settings;
+    private CarVolumeGroup(int zoneId, int id, CarAudioSettings settingsManager, int stepSize,
+            int defaultGain, int minGain, int maxGain, SparseArray<String> contextToAddress,
+            Map<String, CarAudioDeviceInfo> addressToCarAudioDeviceInfo,
+            boolean useCarVolumeGroupMute) {
+
+        mSettingsManager = settingsManager;
         mZoneId = zoneId;
         mId = id;
-        mStoredGainIndex = mSettingsManager.getStoredVolumeGainIndexForUser(mUserId, mZoneId, mId);
+        mStepSize = stepSize;
+        mDefaultGain = defaultGain;
+        mMinGain = minGain;
+        mMaxGain = maxGain;
+        mContextToAddress = contextToAddress;
+        mAddressToCarAudioDeviceInfo = addressToCarAudioDeviceInfo;
         mUseCarVolumeGroupMute = useCarVolumeGroupMute;
+
+        mHasCriticalAudioContexts = containsCriticalAudioContext(contextToAddress);
     }
 
-    /**
-     * @deprecated In favor of {@link #CarVolumeGroup(int, int, CarAudioSettings, boolean)}
-     * Only used for legacy configuration via IAudioControl@1.0
-     */
-    @Deprecated
-    CarVolumeGroup(CarAudioSettings settings, int zoneId, int id, @NonNull int[] contexts) {
-        this(zoneId, id, settings, false);
-        // Deal with the pre-populated car audio contexts
-        for (int audioContext : contexts) {
-            mContextToAddress.put(audioContext, null);
-        }
+    void init() {
+        mStoredGainIndex = mSettingsManager.getStoredVolumeGainIndexForUser(mUserId, mZoneId, mId);
+        updateCurrentGainIndexLocked();
     }
 
     @Nullable
@@ -89,7 +98,8 @@
         return mAddressToCarAudioDeviceInfo.get(address);
     }
 
-    @AudioContext int[] getContexts() {
+    @AudioContext
+    int[] getContexts() {
         final int[] carAudioContexts = new int[mContextToAddress.size()];
         for (int i = 0; i < carAudioContexts.length; i++) {
             carAudioContexts[i] = mContextToAddress.keyAt(i);
@@ -106,7 +116,8 @@
         return mContextToAddress.get(audioContext);
     }
 
-    @AudioContext List<Integer> getContextsForAddress(@NonNull String address) {
+    @AudioContext
+    List<Integer> getContextsForAddress(@NonNull String address) {
         List<Integer> carAudioContexts = new ArrayList<>();
         for (int i = 0; i < mContextToAddress.size(); i++) {
             String value = mContextToAddress.valueAt(i);
@@ -121,57 +132,15 @@
         return new ArrayList<>(mAddressToCarAudioDeviceInfo.keySet());
     }
 
-    /**
-     * Binds the context number to physical address and audio device port information.
-     * Because this may change the groups min/max values, thus invalidating an index computed from
-     * a gain before this call, all calls to this function must happen at startup before any
-     * set/getGainIndex calls.
-     *
-     * @param carAudioContext Context to bind audio to {@link CarAudioContext}
-     * @param info {@link CarAudioDeviceInfo} instance relates to the physical address
-     */
-    void bind(int carAudioContext, CarAudioDeviceInfo info) {
-        Preconditions.checkArgument(mContextToAddress.get(carAudioContext) == null,
-                String.format("Context %s has already been bound to %s",
-                        CarAudioContext.toString(carAudioContext),
-                        mContextToAddress.get(carAudioContext)));
-
-        synchronized (mLock) {
-            if (mAddressToCarAudioDeviceInfo.size() == 0) {
-                mStepSize = info.getStepValue();
-            } else {
-                Preconditions.checkArgument(
-                        info.getStepValue() == mStepSize,
-                        "Gain controls within one group must have same step value");
-            }
-
-            mAddressToCarAudioDeviceInfo.put(info.getAddress(), info);
-            mContextToAddress.put(carAudioContext, info.getAddress());
-
-            if (info.getDefaultGain() > mDefaultGain) {
-                // We're arbitrarily selecting the highest
-                // device default gain as the group's default.
-                mDefaultGain = info.getDefaultGain();
-            }
-            if (info.getMaxGain() > mMaxGain) {
-                mMaxGain = info.getMaxGain();
-            }
-            if (info.getMinGain() < mMinGain) {
-                mMinGain = info.getMinGain();
-            }
-            updateCurrentGainIndexLocked();
-        }
-    }
-
     int getMaxGainIndex() {
         synchronized (mLock) {
-            return getIndexForGainLocked(mMaxGain);
+            return getIndexForGain(mMaxGain);
         }
     }
 
     int getMinGainIndex() {
         synchronized (mLock) {
-            return getIndexForGainLocked(mMinGain);
+            return getIndexForGain(mMinGain);
         }
     }
 
@@ -183,19 +152,13 @@
 
     /**
      * Sets the gain on this group, gain will be set on all devices within volume group.
-     * @param gainIndex The gain index
      */
     void setCurrentGainIndex(int gainIndex) {
+        int gainInMillibels = getGainForIndex(gainIndex);
+        Preconditions.checkArgument(isValidGainIndex(gainIndex),
+                "Gain out of range (%d:%d) %d index %d", mMinGain, mMaxGain,
+                gainInMillibels, gainIndex);
         synchronized (mLock) {
-            int gainInMillibels = getGainForIndexLocked(gainIndex);
-            Preconditions.checkArgument(
-                    gainInMillibels >= mMinGain && gainInMillibels <= mMaxGain,
-                    "Gain out of range ("
-                            + mMinGain + ":"
-                            + mMaxGain + ") "
-                            + gainInMillibels + "index "
-                            + gainIndex);
-
             for (String address : mAddressToCarAudioDeviceInfo.keySet()) {
                 CarAudioDeviceInfo info = mAddressToCarAudioDeviceInfo.get(address);
                 info.setCurrentGain(gainInMillibels);
@@ -217,6 +180,10 @@
         return mAddressToCarAudioDeviceInfo.get(address).getAudioDevicePort();
     }
 
+    boolean hasCriticalAudioContexts() {
+        return mHasCriticalAudioContexts;
+    }
+
     @Override
     public String toString() {
         return "CarVolumeGroup id: " + mId
@@ -229,12 +196,14 @@
         synchronized (mLock) {
             writer.printf("CarVolumeGroup(%d)\n", mId);
             writer.increaseIndent();
+            writer.printf("Zone Id(%b)\n", mZoneId);
             writer.printf("Is Muted(%b)\n", mIsMuted);
             writer.printf("UserId(%d)\n", mUserId);
             writer.printf("Persist Volume Group Mute(%b)\n",
                     mSettingsManager.isPersistVolumeGroupMuteEnabled(mUserId));
+            writer.printf("Step size: %d\n", mStepSize);
             writer.printf("Gain values (min / max / default/ current): %d %d %d %d\n", mMinGain,
-                    mMaxGain, mDefaultGain, getGainForIndexLocked(mCurrentGainIndex));
+                    mMaxGain, mDefaultGain, getGainForIndex(mCurrentGainIndex));
             writer.printf("Gain indexes (min / max / default / current): %d %d %d %d\n",
                     getMinGainIndex(), getMaxGainIndex(), getDefaultGainIndex(), mCurrentGainIndex);
             for (int i = 0; i < mContextToAddress.size(); i++) {
@@ -279,6 +248,16 @@
         }
     }
 
+    private static boolean containsCriticalAudioContext(SparseArray<String> contextToAddress) {
+        for (int i = 0; i < contextToAddress.size(); i++) {
+            int audioContext = contextToAddress.keyAt(i);
+            if (CarAudioContext.isCriticalAudioContext(audioContext)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     @GuardedBy("mLock")
     private void updateUserIdLocked(@UserIdInt int userId) {
         mUserId = userId;
@@ -299,21 +278,21 @@
      */
     @GuardedBy("mLock")
     private void updateCurrentGainIndexLocked() {
-        if (isValidGainLocked(mStoredGainIndex)) {
+        if (isValidGainIndex(mStoredGainIndex)) {
             mCurrentGainIndex = mStoredGainIndex;
         } else {
-            mCurrentGainIndex = getIndexForGainLocked(mDefaultGain);
+            mCurrentGainIndex = getIndexForGain(mDefaultGain);
         }
     }
 
-    @GuardedBy("mLock")
-    private boolean isValidGainLocked(int gain) {
-        return gain >= getIndexForGainLocked(mMinGain) && gain <= getIndexForGainLocked(mMaxGain);
+    private boolean isValidGainIndex(int gainIndex) {
+        return gainIndex >= getIndexForGain(mMinGain)
+                && gainIndex <= getIndexForGain(mMaxGain);
     }
 
     private int getDefaultGainIndex() {
         synchronized (mLock) {
-            return getIndexForGainLocked(mDefaultGain);
+            return getIndexForGain(mDefaultGain);
         }
     }
 
@@ -323,12 +302,11 @@
                 mZoneId, mId, gainIndex);
     }
 
-    private int getGainForIndexLocked(int gainIndex) {
+    private int getGainForIndex(int gainIndex) {
         return mMinGain + gainIndex * mStepSize;
     }
 
-    @GuardedBy("mLock")
-    private int getIndexForGainLocked(int gainInMillibel) {
+    private int getIndexForGain(int gainInMillibel) {
         return (gainInMillibel - mMinGain) / mStepSize;
     }
 
@@ -343,4 +321,75 @@
         }
         mIsMuted = mSettingsManager.getVolumeGroupMuteForUser(mUserId, mZoneId, mId);
     }
+
+    static final class Builder {
+        private static final int UNSET_STEP_SIZE = -1;
+
+        private final int mId;
+        private final int mZoneId;
+        private final boolean mUseCarVolumeGroupMute;
+        private final CarAudioSettings mCarAudioSettings;
+        private final SparseArray<String> mContextToAddress = new SparseArray<>();
+        private final Map<String, CarAudioDeviceInfo> mAddressToCarAudioDeviceInfo =
+                new HashMap<>();
+
+        @VisibleForTesting
+        int mStepSize = UNSET_STEP_SIZE;
+        @VisibleForTesting
+        int mDefaultGain = Integer.MIN_VALUE;
+        @VisibleForTesting
+        int mMaxGain = Integer.MIN_VALUE;
+        @VisibleForTesting
+        int mMinGain = Integer.MAX_VALUE;
+
+        Builder(int zoneId, int id, CarAudioSettings carAudioSettings,
+                boolean useCarVolumeGroupMute) {
+            mZoneId = zoneId;
+            mId = id;
+            mCarAudioSettings = carAudioSettings;
+            mUseCarVolumeGroupMute = useCarVolumeGroupMute;
+        }
+
+        Builder setDeviceInfoForContext(int carAudioContext, CarAudioDeviceInfo info) {
+            Preconditions.checkArgument(mContextToAddress.get(carAudioContext) == null,
+                    "Context %s has already been set to %s",
+                    CarAudioContext.toString(carAudioContext),
+                    mContextToAddress.get(carAudioContext));
+
+            if (mAddressToCarAudioDeviceInfo.isEmpty()) {
+                mStepSize = info.getStepValue();
+            } else {
+                Preconditions.checkArgument(
+                        info.getStepValue() == mStepSize,
+                        "Gain controls within one group must have same step value");
+            }
+
+            mAddressToCarAudioDeviceInfo.put(info.getAddress(), info);
+            mContextToAddress.put(carAudioContext, info.getAddress());
+
+            if (info.getDefaultGain() > mDefaultGain) {
+                // We're arbitrarily selecting the highest
+                // device default gain as the group's default.
+                mDefaultGain = info.getDefaultGain();
+            }
+            if (info.getMaxGain() > mMaxGain) {
+                mMaxGain = info.getMaxGain();
+            }
+            if (info.getMinGain() < mMinGain) {
+                mMinGain = info.getMinGain();
+            }
+
+            return this;
+        }
+
+        CarVolumeGroup build() {
+            Preconditions.checkArgument(mStepSize != UNSET_STEP_SIZE,
+                    "setDeviceInfoForContext has to be called at least once before building");
+            CarVolumeGroup group = new CarVolumeGroup(mZoneId, mId, mCarAudioSettings, mStepSize,
+                    mDefaultGain, mMinGain, mMaxGain, mContextToAddress,
+                    mAddressToCarAudioDeviceInfo, mUseCarVolumeGroupMute);
+            group.init();
+            return group;
+        }
+    }
 }
diff --git a/service/src/com/android/car/audio/CarVolumeGroupMuting.java b/service/src/com/android/car/audio/CarVolumeGroupMuting.java
index f6d2ce9..7101127 100644
--- a/service/src/com/android/car/audio/CarVolumeGroupMuting.java
+++ b/service/src/com/android/car/audio/CarVolumeGroupMuting.java
@@ -44,6 +44,8 @@
     private final Object mLock = new Object();
     @GuardedBy("mLock")
     private List<MutingInfo> mLastMutingInformation;
+    @GuardedBy("mLock")
+    private boolean mIsMutingRestricted;
 
     CarVolumeGroupMuting(@NonNull SparseArray<CarAudioZone> carAudioZones,
             @NonNull AudioControlWrapper audioControlWrapper) {
@@ -72,11 +74,26 @@
         if (Log.isLoggable(TAG, Log.DEBUG)) {
             Slog.d(TAG, "carMuteChanged");
         }
+
         List<MutingInfo> mutingInfo = generateMutingInfo();
         setLastMutingInfo(mutingInfo);
         mAudioControlWrapper.onDevicesToMuteChange(mutingInfo);
     }
 
+    public void setRestrictMuting(boolean isMutingRestricted) {
+        synchronized (mLock) {
+            mIsMutingRestricted = isMutingRestricted;
+        }
+
+        carMuteChanged();
+    }
+
+    private boolean isMutingRestricted() {
+        synchronized (mLock) {
+            return mIsMutingRestricted;
+        }
+    }
+
     private void setLastMutingInfo(List<MutingInfo> mutingInfo) {
         synchronized (mLock) {
             mLastMutingInformation = mutingInfo;
@@ -92,8 +109,11 @@
 
     private List<MutingInfo> generateMutingInfo() {
         List<MutingInfo> mutingInformation = new ArrayList<>(mCarAudioZones.size());
+
+        boolean isMutingRestricted = isMutingRestricted();
         for (int index = 0; index < mCarAudioZones.size(); index++) {
-            mutingInformation.add(generateMutingInfoFromZone(mCarAudioZones.valueAt(index)));
+            mutingInformation.add(generateMutingInfoFromZone(mCarAudioZones.valueAt(index),
+                    isMutingRestricted));
         }
 
         return mutingInformation;
@@ -106,6 +126,7 @@
         writer.println(TAG);
         writer.increaseIndent();
         synchronized (mLock) {
+            writer.printf("Is muting restricted? %b\n", mIsMutingRestricted);
             for (int index = 0; index < mLastMutingInformation.size(); index++) {
                 dumpCarMutingInfo(writer, mLastMutingInformation.get(index));
             }
@@ -134,7 +155,8 @@
     }
 
     @VisibleForTesting
-    static MutingInfo generateMutingInfoFromZone(CarAudioZone audioZone) {
+    static MutingInfo generateMutingInfoFromZone(CarAudioZone audioZone,
+            boolean isMutingRestricted) {
         MutingInfo mutingInfo = new MutingInfo();
         mutingInfo.zoneId = audioZone.getId();
 
@@ -144,11 +166,12 @@
 
         for (int groupIndex = 0; groupIndex < groups.length; groupIndex++) {
             CarVolumeGroup group = groups[groupIndex];
-            if (group.isMuted()) {
+
+            if (group.isMuted() || (isMutingRestricted && !group.hasCriticalAudioContexts())) {
                 mutedDevices.addAll(group.getAddresses());
-                continue;
+            } else {
+                unMutedDevices.addAll(group.getAddresses());
             }
-            unMutedDevices.addAll(group.getAddresses());
         }
 
         mutingInfo.deviceAddressesToMute = mutedDevices.toArray(new String[mutedDevices.size()]);
diff --git a/service/src/com/android/car/watchdog/CarWatchdogService.java b/service/src/com/android/car/watchdog/CarWatchdogService.java
index 8b650ed..0b26e0a 100644
--- a/service/src/com/android/car/watchdog/CarWatchdogService.java
+++ b/service/src/com/android/car/watchdog/CarWatchdogService.java
@@ -63,8 +63,8 @@
  * Service to implement CarWatchdogManager API.
  */
 public final class CarWatchdogService extends ICarWatchdogService.Stub implements CarServiceBase {
-    private static final boolean DEBUG = false; // STOPSHIP if true
-    private static final String TAG = CarLog.tagFor(CarWatchdogService.class);
+    static final boolean DEBUG = false; // STOPSHIP if true
+    static final String TAG = CarLog.tagFor(CarWatchdogService.class);
 
     private final Context mContext;
     private final ICarWatchdogServiceForSystemImpl mWatchdogServiceForSystem;
@@ -85,9 +85,9 @@
         mCarWatchdogDaemonHelper = new CarWatchdogDaemonHelper(TAG_WATCHDOG);
         mWatchdogServiceForSystem = new ICarWatchdogServiceForSystemImpl(this);
         mWatchdogProcessHandler = new WatchdogProcessHandler(mWatchdogServiceForSystem,
-                mCarWatchdogDaemonHelper, DEBUG);
+                mCarWatchdogDaemonHelper);
         mWatchdogPerfHandler = new WatchdogPerfHandler(mContext, mCarWatchdogDaemonHelper,
-                mPackageInfoHandler, DEBUG);
+                mPackageInfoHandler);
     }
 
     @Override
diff --git a/service/src/com/android/car/watchdog/PackageInfoHandler.java b/service/src/com/android/car/watchdog/PackageInfoHandler.java
index c2b3323..f04f2b9 100644
--- a/service/src/com/android/car/watchdog/PackageInfoHandler.java
+++ b/service/src/com/android/car/watchdog/PackageInfoHandler.java
@@ -45,6 +45,8 @@
     /* Cache of uid to package name mapping. */
     @GuardedBy("mLock")
     private final SparseArray<String> mPackageNamesByUid = new SparseArray<>();
+    @GuardedBy("mLock")
+    private List<String> mVendorPackagePrefixes = new ArrayList<>();
 
     public PackageInfoHandler(PackageManager packageManager) {
         mPackageManager = packageManager;
@@ -93,25 +95,31 @@
      */
     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;
+        }
         SparseArray<String> packageNamesByUid = getPackageNamesForUids(uids);
         ArrayList<PackageInfo> packageInfos = new ArrayList<>(packageNamesByUid.size());
         for (int i = 0; i < packageNamesByUid.size(); ++i) {
             packageInfos.add(getPackageInfo(packageNamesByUid.keyAt(i),
-                    packageNamesByUid.valueAt(i), vendorPackagePrefixes));
+                    packageNamesByUid.valueAt(i)));
         }
         return packageInfos;
     }
 
-    private PackageInfo getPackageInfo(
-            int uid, String packageName, List<String> vendorPackagePrefixes) {
+    private PackageInfo getPackageInfo(int uid, String packageName) {
         PackageInfo packageInfo = new PackageInfo();
         packageInfo.packageIdentifier = new PackageIdentifier();
         packageInfo.packageIdentifier.uid = uid;
         packageInfo.packageIdentifier.name = packageName;
         packageInfo.sharedUidPackages = new ArrayList<>();
         packageInfo.componentType = ComponentType.UNKNOWN;
-        // TODO(b/170741935): Identify application category type using the package names. Vendor
-        //  should define the mappings from package name to the application category type.
+        /* Application category type mapping is handled on the daemon side. */
         packageInfo.appCategoryType = ApplicationCategoryType.OTHERS;
         int userId = UserHandle.getUserId(uid);
         int appId = UserHandle.getAppId(uid);
@@ -126,14 +134,13 @@
             boolean seenVendor = false;
             boolean seenSystem = false;
             boolean seenThirdParty = false;
-            /**
+            /*
              * A shared UID has multiple packages associated with it and these packages may be
              * mapped to different component types. Thus map the shared UID to the most restrictive
              * component type.
              */
             for (int i = 0; i < sharedUidPackages.length; ++i) {
-                int componentType = getPackageComponentType(userId, sharedUidPackages[i],
-                        vendorPackagePrefixes);
+                int componentType = getPackageComponentType(userId, sharedUidPackages[i]);
                 switch(componentType) {
                     case ComponentType.VENDOR:
                         seenVendor = true;
@@ -159,35 +166,41 @@
             }
         } else {
             packageInfo.componentType = getPackageComponentType(
-                    userId, packageName, vendorPackagePrefixes);
+                    userId, packageName);
         }
         return packageInfo;
     }
 
-    private int getPackageComponentType(
-            int userId, String packageName, List<String> vendorPackagePrefixes) {
+    private int getPackageComponentType(int userId, String packageName) {
         try {
             ApplicationInfo info = mPackageManager.getApplicationInfoAsUser(packageName,
                     /* flags= */ 0, userId);
-            if ((info.privateFlags & ApplicationInfo.PRIVATE_FLAG_OEM) != 0
-                    || (info.privateFlags & ApplicationInfo.PRIVATE_FLAG_VENDOR) != 0
-                    || (info.privateFlags & ApplicationInfo.PRIVATE_FLAG_ODM) != 0) {
-                return ComponentType.VENDOR;
-            }
-            if ((info.flags & ApplicationInfo.FLAG_SYSTEM) != 0
-                    || (info.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0
-                    || (info.privateFlags & ApplicationInfo.PRIVATE_FLAG_PRODUCT) != 0
-                    || (info.privateFlags & ApplicationInfo.PRIVATE_FLAG_SYSTEM_EXT) != 0) {
-                for (String prefix : vendorPackagePrefixes) {
+            return getComponentType(packageName, info);
+        } catch (PackageManager.NameNotFoundException e) {
+            Slogf.e(TAG, "Package '%s' not found for user %d: %s", packageName, userId, e);
+        }
+        return ComponentType.UNKNOWN;
+    }
+
+    /** Returns the component type for the given package and its application info. */
+    public int getComponentType(String packageName, ApplicationInfo info) {
+        if ((info.privateFlags & ApplicationInfo.PRIVATE_FLAG_OEM) != 0
+                || (info.privateFlags & ApplicationInfo.PRIVATE_FLAG_VENDOR) != 0
+                || (info.privateFlags & ApplicationInfo.PRIVATE_FLAG_ODM) != 0) {
+            return ComponentType.VENDOR;
+        }
+        if ((info.flags & ApplicationInfo.FLAG_SYSTEM) != 0
+                || (info.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0
+                || (info.privateFlags & ApplicationInfo.PRIVATE_FLAG_PRODUCT) != 0
+                || (info.privateFlags & ApplicationInfo.PRIVATE_FLAG_SYSTEM_EXT) != 0) {
+            synchronized (mLock) {
+                for (String prefix : mVendorPackagePrefixes) {
                     if (packageName.startsWith(prefix)) {
                         return ComponentType.VENDOR;
                     }
                 }
-                return ComponentType.SYSTEM;
             }
-        } catch (PackageManager.NameNotFoundException e) {
-            Slogf.e(TAG, "Package '%s' not found for user %d: %s", packageName, userId, e);
-            return ComponentType.UNKNOWN;
+            return ComponentType.SYSTEM;
         }
         return ComponentType.THIRD_PARTY;
     }
diff --git a/service/src/com/android/car/watchdog/WatchdogPerfHandler.java b/service/src/com/android/car/watchdog/WatchdogPerfHandler.java
index 37fbbb3..80dcb82 100644
--- a/service/src/com/android/car/watchdog/WatchdogPerfHandler.java
+++ b/service/src/com/android/car/watchdog/WatchdogPerfHandler.java
@@ -34,11 +34,18 @@
 import android.annotation.UserIdInt;
 import android.app.ActivityThread;
 import android.automotive.watchdog.ResourceType;
+import android.automotive.watchdog.internal.ApplicationCategoryType;
+import android.automotive.watchdog.internal.ComponentType;
 import android.automotive.watchdog.internal.PackageIdentifier;
 import android.automotive.watchdog.internal.PackageIoOveruseStats;
+import android.automotive.watchdog.internal.PackageMetadata;
 import android.automotive.watchdog.internal.PackageResourceOveruseAction;
+import android.automotive.watchdog.internal.PerStateIoOveruseThreshold;
+import android.automotive.watchdog.internal.ResourceSpecificConfiguration;
 import android.car.watchdog.CarWatchdogManager;
 import android.car.watchdog.IResourceOveruseListener;
+import android.car.watchdog.IoOveruseAlertThreshold;
+import android.car.watchdog.IoOveruseConfiguration;
 import android.car.watchdog.IoOveruseStats;
 import android.car.watchdog.PackageKillableState;
 import android.car.watchdog.PackageKillableState.KillableState;
@@ -48,18 +55,21 @@
 import android.car.watchdoglib.CarWatchdogDaemonHelper;
 import android.content.Context;
 import android.content.pm.IPackageManager;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.IndentingPrintWriter;
 import android.util.SparseArray;
 
-import com.android.car.CarLog;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.Preconditions;
@@ -78,9 +88,10 @@
  * Handles system resource performance monitoring module.
  */
 public final class WatchdogPerfHandler {
-    private static final String TAG = CarLog.tagFor(CarWatchdogService.class);
+    public static final String INTERNAL_APPLICATION_CATEGORY_TYPE_MAPS = "MAPS";
+    public static final String INTERNAL_APPLICATION_CATEGORY_TYPE_MEDIA = "MEDIA";
+    public static final String INTERNAL_APPLICATION_CATEGORY_TYPE_UNKNOWN = "UNKNOWN";
 
-    private final boolean mIsDebugEnabled;
     private final Context mContext;
     private final CarWatchdogDaemonHelper mCarWatchdogDaemonHelper;
     private final PackageInfoHandler mPackageInfoHandler;
@@ -102,10 +113,15 @@
             new SparseArray<>();
     @GuardedBy("mLock")
     private ZonedDateTime mLastStatsReportUTC;
+    /* Set of safe-to-kill system and vendor packages. */
+    @GuardedBy("mLock")
+    public final Set<String> mSafeToKillPackages = new ArraySet<>();
+    /* Default killable state for packages when not updated by the user. */
+    @GuardedBy("mLock")
+    public final Set<String> mDefaultNotKillablePackages = new ArraySet<>();
 
     public WatchdogPerfHandler(Context context, CarWatchdogDaemonHelper daemonHelper,
-            PackageInfoHandler packageInfoHandler, boolean isDebugEnabled) {
-        mIsDebugEnabled = isDebugEnabled;
+            PackageInfoHandler packageInfoHandler) {
         mContext = context;
         mCarWatchdogDaemonHelper = daemonHelper;
         mPackageInfoHandler = packageInfoHandler;
@@ -119,31 +135,31 @@
          * TODO(b/183947162): Opt-in to receive package change broadcast and handle package enabled
          *  state changes.
          *
-         * TODO(b/170741935): Read the current day's I/O overuse stats from database and push them
+         * TODO(b/185287136): Persist in-memory data:
+         *  1. Read the current day's I/O overuse stats from database and push them
          *  to the daemon.
+         *  2. Fetch the safe-to-kill from daemon on initialization and update mSafeToKillPackages.
          */
         synchronized (mLock) {
             checkAndHandleDateChangeLocked();
         }
-        if (mIsDebugEnabled) {
-            Slogf.d(TAG, "WatchdogPerfHandler is initialized");
+        if (CarWatchdogService.DEBUG) {
+            Slogf.d(CarWatchdogService.TAG, "WatchdogPerfHandler is initialized");
         }
     }
 
     /** Releases the handler */
     public void release() {
-        /*
-         * TODO(b/170741935): Write daily usage to SQLite DB storage.
-         */
-        if (mIsDebugEnabled) {
-            Slogf.d(TAG, "WatchdogPerfHandler is released");
+        /* TODO(b/185287136): Write daily usage to SQLite DB storage. */
+        if (CarWatchdogService.DEBUG) {
+            Slogf.d(CarWatchdogService.TAG, "WatchdogPerfHandler is released");
         }
     }
 
     /** Dumps its state. */
     public void dump(IndentingPrintWriter writer) {
-        /**
-         * TODO(b/170741935): Implement this method.
+        /*
+         * TODO(b/183436216): Implement this method.
          */
     }
 
@@ -156,9 +172,29 @@
                 "Must provide valid resource overuse flag");
         Preconditions.checkArgument((maxStatsPeriod > 0),
                 "Must provide valid maximum stats period");
-        // TODO(b/170741935): Implement this method.
-        return new ResourceOveruseStats.Builder("",
-                UserHandle.getUserHandleForUid(Binder.getCallingUid())).build();
+        // When more resource stats are added, make this as optional.
+        Preconditions.checkArgument((resourceOveruseFlag & FLAG_RESOURCE_OVERUSE_IO) != 0,
+                "Must provide resource I/O overuse flag");
+        int callingUid = Binder.getCallingUid();
+        int callingUserId = UserHandle.getUserId(callingUid);
+        UserHandle callingUserHandle = UserHandle.of(callingUserId);
+        String callingPackageName =
+                mPackageInfoHandler.getPackageNamesForUids(new int[]{callingUid})
+                        .get(callingUid, null);
+        if (callingPackageName == null) {
+            Slogf.w(CarWatchdogService.TAG, "Failed to fetch package info for uid %d", callingUid);
+            return new ResourceOveruseStats.Builder("", callingUserHandle).build();
+        }
+        ResourceOveruseStats.Builder statsBuilder =
+                new ResourceOveruseStats.Builder(callingPackageName, callingUserHandle);
+        statsBuilder.setIoOveruseStats(getIoOveruseStats(callingUserId, callingPackageName,
+                /* minimumBytesWritten= */ 0, maxStatsPeriod));
+        if (CarWatchdogService.DEBUG) {
+            Slogf.d(CarWatchdogService.TAG, "Returning all resource overuse stats for calling uid "
+                            + "%d [user %d and package '%s']", callingUid, callingUserId,
+                    callingPackageName);
+        }
+        return statsBuilder.build();
     }
 
     /** Returns resource overuse stats for all packages. */
@@ -171,8 +207,24 @@
                 "Must provide valid resource overuse flag");
         Preconditions.checkArgument((maxStatsPeriod > 0),
                 "Must provide valid maximum stats period");
-        // TODO(b/170741935): Implement this method.
-        return new ArrayList<>();
+        // When more resource types are added, make this as optional.
+        Preconditions.checkArgument((resourceOveruseFlag & FLAG_RESOURCE_OVERUSE_IO) != 0,
+                "Must provide resource I/O overuse flag");
+        long minimumBytesWritten = getMinimumBytesWritten(minimumStatsFlag);
+        List<ResourceOveruseStats> allStats = new ArrayList<>();
+        for (PackageResourceUsage usage : mUsageByUserPackage.values()) {
+            ResourceOveruseStats.Builder statsBuilder = usage.getResourceOveruseStatsBuilder();
+            IoOveruseStats ioOveruseStats = getIoOveruseStats(usage.userId, usage.packageName,
+                    minimumBytesWritten, maxStatsPeriod);
+            if (ioOveruseStats == null) {
+                continue;
+            }
+            allStats.add(statsBuilder.setIoOveruseStats(ioOveruseStats).build());
+        }
+        if (CarWatchdogService.DEBUG) {
+            Slogf.d(CarWatchdogService.TAG, "Returning all resource overuse stats");
+        }
+        return allStats;
     }
 
     /** Returns resource overuse stats for the specified user package. */
@@ -183,12 +235,25 @@
             @CarWatchdogManager.StatsPeriod int maxStatsPeriod) {
         Objects.requireNonNull(packageName, "Package name must be non-null");
         Objects.requireNonNull(userHandle, "User handle must be non-null");
+        Preconditions.checkArgument((userHandle != UserHandle.ALL),
+                "Must provide the user handle for a specific user");
         Preconditions.checkArgument((resourceOveruseFlag > 0),
                 "Must provide valid resource overuse flag");
         Preconditions.checkArgument((maxStatsPeriod > 0),
                 "Must provide valid maximum stats period");
-        // TODO(b/170741935): Implement this method.
-        return new ResourceOveruseStats.Builder("", userHandle).build();
+        // When more resource types are added, make this as optional.
+        Preconditions.checkArgument((resourceOveruseFlag & FLAG_RESOURCE_OVERUSE_IO) != 0,
+                "Must provide resource I/O overuse flag");
+        ResourceOveruseStats.Builder statsBuilder =
+                new ResourceOveruseStats.Builder(packageName, userHandle);
+        statsBuilder.setIoOveruseStats(getIoOveruseStats(userHandle.getIdentifier(), packageName,
+                /* minimumBytesWritten= */ 0, maxStatsPeriod));
+        if (CarWatchdogService.DEBUG) {
+            Slogf.d(CarWatchdogService.TAG,
+                    "Returning resource overuse stats for user %d, package '%s'",
+                    userHandle.getIdentifier(), packageName);
+        }
+        return statsBuilder.build();
     }
 
     /** Adds the resource overuse listener. */
@@ -238,18 +303,107 @@
             boolean isKillable) {
         Objects.requireNonNull(packageName, "Package name must be non-null");
         Objects.requireNonNull(userHandle, "User handle must be non-null");
-        /*
-         * TODO(b/170741935): Add/remove the package from the user do-no-kill list.
-         *  If the {@code userHandle == UserHandle.ALL}, update the settings for all users.
-         */
+        if (userHandle == UserHandle.ALL) {
+            synchronized (mLock) {
+                for (PackageResourceUsage usage : mUsageByUserPackage.values()) {
+                    if (!usage.packageName.equals(packageName)) {
+                        continue;
+                    }
+                    if (!usage.setKillableState(isKillable)) {
+                        Slogf.e(CarWatchdogService.TAG,
+                                "Cannot set killable state for package '%s'", packageName);
+                        throw new IllegalArgumentException(
+                                "Package killable state is not updatable");
+                    }
+                }
+                if (!isKillable) {
+                    mDefaultNotKillablePackages.add(packageName);
+                } else {
+                    mDefaultNotKillablePackages.remove(packageName);
+                }
+            }
+            if (CarWatchdogService.DEBUG) {
+                Slogf.d(CarWatchdogService.TAG,
+                        "Successfully set killable package state for all users");
+            }
+            return;
+        }
+        int userId = userHandle.getIdentifier();
+        String key = getUserPackageUniqueId(userId, packageName);
+        synchronized (mLock) {
+            /*
+             * When the queried package is not cached in {@link mUsageByUserPackage}, the set API
+             * will update the killable state even when the package should never be killed.
+             * But the get API will return the correct killable state. This behavior is tolerable
+             * because in production the set API should be called only after the get API.
+             * For instance, when this case happens by mistake and the package overuses resource
+             * between the set and the get API calls, the daemon will provide correct killable
+             * state when pushing the latest stats. Ergo, the invalid killable state doesn't have
+             * any effect.
+             */
+            PackageResourceUsage usage = mUsageByUserPackage.getOrDefault(key,
+                    new PackageResourceUsage(userId, packageName));
+            if (!usage.setKillableState(isKillable)) {
+                Slogf.e(CarWatchdogService.TAG,
+                        "User %d cannot set killable state for package '%s'",
+                        userHandle.getIdentifier(), packageName);
+                throw new IllegalArgumentException("Package killable state is not updatable");
+            }
+            mUsageByUserPackage.put(key, usage);
+        }
+        if (CarWatchdogService.DEBUG) {
+            Slogf.d(CarWatchdogService.TAG, "Successfully set killable package state for user %d",
+                    userId);
+        }
     }
 
     /** Returns the list of package killable states on resource overuse for the user. */
     @NonNull
     public List<PackageKillableState> getPackageKillableStatesAsUser(UserHandle userHandle) {
         Objects.requireNonNull(userHandle, "User handle must be non-null");
-        // TODO(b/170741935): Implement this method.
-        return new ArrayList<>();
+        PackageManager pm = mContext.getPackageManager();
+        if (userHandle != UserHandle.ALL) {
+            if (CarWatchdogService.DEBUG) {
+                Slogf.d(CarWatchdogService.TAG, "Returning all package killable states for user %d",
+                        userHandle.getIdentifier());
+            }
+            return getPackageKillableStatesForUserId(userHandle.getIdentifier(), pm);
+        }
+        List<PackageKillableState> packageKillableStates = new ArrayList<>();
+        UserManager userManager = UserManager.get(mContext);
+        List<UserInfo> userInfos = userManager.getAliveUsers();
+        for (UserInfo userInfo : userInfos) {
+            packageKillableStates.addAll(getPackageKillableStatesForUserId(userInfo.id, pm));
+        }
+        if (CarWatchdogService.DEBUG) {
+            Slogf.d(CarWatchdogService.TAG, "Returning all package killable states for all users");
+        }
+        return packageKillableStates;
+    }
+
+    private List<PackageKillableState> getPackageKillableStatesForUserId(int userId,
+            PackageManager pm) {
+        List<PackageInfo> packageInfos = pm.getInstalledPackagesAsUser(/* flags= */0, userId);
+        List<PackageKillableState> states = new ArrayList<>();
+        synchronized (mLock) {
+            for (int i = 0; i < packageInfos.size(); ++i) {
+                PackageInfo packageInfo = packageInfos.get(i);
+                String key = getUserPackageUniqueId(userId, packageInfo.packageName);
+                PackageResourceUsage usage = mUsageByUserPackage.getOrDefault(key,
+                        new PackageResourceUsage(userId, packageInfo.packageName));
+                int killableState = usage.syncAndFetchKillableStateLocked(
+                        mPackageInfoHandler.getComponentType(packageInfo.packageName,
+                                packageInfo.applicationInfo));
+                mUsageByUserPackage.put(key, usage);
+                states.add(
+                        new PackageKillableState(packageInfo.packageName, userId, killableState));
+            }
+        }
+        if (CarWatchdogService.DEBUG) {
+            Slogf.d(CarWatchdogService.TAG,
+                    "Returning the package killable states for a user package");
+        }
+        return states;
     }
 
     /** Sets the given resource overuse configurations. */
@@ -257,11 +411,18 @@
             List<ResourceOveruseConfiguration> configurations,
             @CarWatchdogManager.ResourceOveruseFlag int resourceOveruseFlag) {
         Objects.requireNonNull(configurations, "Configurations must be non-null");
+        Preconditions.checkArgument((configurations.size() > 0),
+                "Must provide at least one configuration");
         Preconditions.checkArgument((resourceOveruseFlag > 0),
                 "Must provide valid resource overuse flag");
         Set<Integer> seenComponentTypes = new ArraySet<>();
+        List<android.automotive.watchdog.internal.ResourceOveruseConfiguration> internalConfigs =
+                new ArrayList<>();
         for (ResourceOveruseConfiguration config : configurations) {
             int componentType = config.getComponentType();
+            if (toComponentTypeStr(componentType).equals("UNKNOWN")) {
+                throw new IllegalArgumentException("Invalid component type in the configuration");
+            }
             if (seenComponentTypes.contains(componentType)) {
                 throw new IllegalArgumentException(
                         "Cannot provide duplicate configurations for the same component type");
@@ -271,8 +432,24 @@
                 throw new IllegalArgumentException("Must provide I/O overuse configuration");
             }
             seenComponentTypes.add(config.getComponentType());
+            internalConfigs.add(toInternalResourceOveruseConfiguration(config,
+                    resourceOveruseFlag));
         }
-        // TODO(b/170741935): Implement this method.
+
+        // TODO(b/186119640): Add retry logic when daemon is not available.
+        try {
+            mCarWatchdogDaemonHelper.updateResourceOveruseConfigurations(internalConfigs);
+        } catch (IllegalArgumentException e) {
+            Slogf.w(CarWatchdogService.TAG, "Failed to set resource overuse configurations: %s", e);
+            throw e;
+        } catch (RemoteException | RuntimeException e) {
+            Slogf.w(CarWatchdogService.TAG, "Failed to set resource overuse configurations: %s", e);
+            throw new IllegalStateException(e);
+        }
+        /* TODO(b/185287136): Fetch safe-to-kill list from daemon and update mSafeToKillPackages. */
+        if (CarWatchdogService.DEBUG) {
+            Slogf.d(CarWatchdogService.TAG, "Set the resource overuse configuration successfully");
+        }
     }
 
     /** Returns the available resource overuse configurations. */
@@ -281,8 +458,25 @@
             @CarWatchdogManager.ResourceOveruseFlag int resourceOveruseFlag) {
         Preconditions.checkArgument((resourceOveruseFlag > 0),
                 "Must provide valid resource overuse flag");
-        // TODO(b/170741935): Implement this method.
-        return new ArrayList<>();
+        List<android.automotive.watchdog.internal.ResourceOveruseConfiguration> internalConfigs =
+                new ArrayList<>();
+        // TODO(b/186119640): Add retry logic when daemon is not available.
+        try {
+            internalConfigs = mCarWatchdogDaemonHelper.getResourceOveruseConfigurations();
+        } catch (RemoteException | RuntimeException e) {
+            Slogf.w(CarWatchdogService.TAG, "Failed to fetch resource overuse configurations: %s",
+                    e);
+            throw new IllegalStateException(e);
+        }
+        List<ResourceOveruseConfiguration> configs = new ArrayList<>();
+        for (android.automotive.watchdog.internal.ResourceOveruseConfiguration internalConfig
+                : internalConfigs) {
+            configs.add(toResourceOveruseConfiguration(internalConfig, resourceOveruseFlag));
+        }
+        if (CarWatchdogService.DEBUG) {
+            Slogf.d(CarWatchdogService.TAG, "Returning the resource overuse configuration");
+        }
+        return configs;
     }
 
     /** Processes the latest I/O overuse stats */
@@ -308,9 +502,12 @@
                      * and only the daemon is aware of such packages. Thus the flag is used to
                      * indicate which packages should be notified.
                      */
-                    notifyResourceOveruseStatsLocked(stats.uid,
-                            usage.getResourceOveruseStatsWithIo());
+                    ResourceOveruseStats resourceOveruseStats =
+                            usage.getResourceOveruseStatsBuilder().setIoOveruseStats(
+                                    usage.getIoOveruseStats()).build();
+                    notifyResourceOveruseStatsLocked(stats.uid, resourceOveruseStats);
                 }
+
                 if (!usage.ioUsage.exceedsThreshold()) {
                     continue;
                 }
@@ -326,7 +523,6 @@
                  * #2 The package has no recurring overuse behavior and the user opted to not
                  *    kill the package so honor the user's decision.
                  */
-                String userPackageId = getUserPackageUniqueId(userId, packageName);
                 int killableState = usage.getKillableState();
                 if (killableState == KILLABLE_STATE_NEVER) {
                     mOveruseActionsByUserPackage.add(overuseAction);
@@ -363,8 +559,8 @@
                         usage.oldEnabledState = oldEnabledState;
                     }
                 } catch (RemoteException e) {
-                    Slogf.e(TAG, "Failed to disable application enabled setting for user %d, "
-                            + "package '%s'", userId, packageName);
+                    Slogf.e(CarWatchdogService.TAG, "Failed to disable application enabled setting "
+                            + "for user %d, package '%s'", userId, packageName);
                 }
                 mOveruseActionsByUserPackage.add(overuseAction);
             }
@@ -373,6 +569,9 @@
                         WatchdogPerfHandler::notifyActionsTakenOnOveruse, this));
             }
         }
+        if (CarWatchdogService.DEBUG) {
+            Slogf.d(CarWatchdogService.TAG, "Processed latest I/O overuse stats");
+        }
     }
 
     /** Notify daemon about the actions take on resource overuse */
@@ -387,9 +586,13 @@
         }
         try {
             mCarWatchdogDaemonHelper.actionTakenOnResourceOveruse(actions);
-        } catch (RemoteException e) {
-            Slogf.w(TAG, "Failed to notify car watchdog daemon of actions taken on "
-                    + "resource overuse: %s", e);
+        } catch (RemoteException | RuntimeException e) {
+            Slogf.w(CarWatchdogService.TAG, "Failed to notify car watchdog daemon of actions taken "
+                    + "on resource overuse: %s", e);
+        }
+        if (CarWatchdogService.DEBUG) {
+            Slogf.d(CarWatchdogService.TAG,
+                    "Notified car watchdog daemon of actions taken on resource overuse");
         }
     }
 
@@ -401,7 +604,7 @@
             try {
                 listenerInfo.listener.onOveruse(resourceOveruseStats);
             } catch (RemoteException e) {
-                Slogf.e(TAG,
+                Slogf.e(CarWatchdogService.TAG,
                         "Failed to notify listener(uid %d, package '%s') on resource overuse: %s",
                         uid, resourceOveruseStats, e);
             }
@@ -415,11 +618,15 @@
             try {
                 systemListenerInfo.listener.onOveruse(resourceOveruseStats);
             } catch (RemoteException e) {
-                Slogf.e(TAG, "Failed to notify system listener(uid %d, pid: %d) of resource "
-                                + "overuse by package(uid %d, package '%s'): %s",
+                Slogf.e(CarWatchdogService.TAG, "Failed to notify system listener(uid %d, pid: %d) "
+                                + "of resource overuse by package(uid %d, package '%s'): %s",
                         systemListenerInfo.uid, systemListenerInfo.pid, uid, packageName, e);
             }
         }
+        if (CarWatchdogService.DEBUG) {
+            Slogf.d(CarWatchdogService.TAG,
+                    "Notified resource overuse stats to listening applications");
+        }
     }
 
     private void checkAndHandleDateChangeLocked() {
@@ -443,7 +650,7 @@
                             usage.oldEnabledState,
                             /* flags= */ 0, usage.userId, mContext.getPackageName());
                 } catch (RemoteException e) {
-                    Slogf.e(TAG,
+                    Slogf.e(CarWatchdogService.TAG,
                             "Failed to reset enabled setting for disabled package '%s', user %d",
                             usage.packageName, usage.userId);
                 }
@@ -451,6 +658,9 @@
             /* TODO(b/170741935): Stash the old usage into SQLite DB storage. */
             usage.ioUsage.clear();
         }
+        if (CarWatchdogService.DEBUG) {
+            Slogf.d(CarWatchdogService.TAG, "Handled date change successfully");
+        }
     }
 
     private PackageResourceUsage cacheAndFetchUsageLocked(@UserIdInt int userId, String packageName,
@@ -458,19 +668,39 @@
         String key = getUserPackageUniqueId(userId, packageName);
         PackageResourceUsage usage = mUsageByUserPackage.getOrDefault(key,
                 new PackageResourceUsage(userId, packageName));
-        usage.update(internalStats);
+        usage.updateLocked(internalStats);
         mUsageByUserPackage.put(key, usage);
         return usage;
     }
 
     private boolean isRecurringOveruseLocked(PackageResourceUsage ioUsage) {
         /*
-         * TODO(b/170741935): Look up I/O overuse history and determine whether or not the package
+         * TODO(b/185287136): Look up I/O overuse history and determine whether or not the package
          *  has recurring I/O overuse behavior.
          */
         return false;
     }
 
+    private IoOveruseStats getIoOveruseStats(int userId, String packageName,
+            long minimumBytesWritten, @CarWatchdogManager.StatsPeriod int maxStatsPeriod) {
+        String key = getUserPackageUniqueId(userId, packageName);
+        PackageResourceUsage usage = mUsageByUserPackage.get(key);
+        if (usage == null) {
+            return null;
+        }
+        IoOveruseStats stats = usage.getIoOveruseStats();
+        long totalBytesWritten = stats != null ? stats.getTotalBytesWritten() : 0;
+        /*
+         * TODO(b/185431129): When maxStatsPeriod > current day, populate the historical stats
+         *  from the local database. Also handle the case where the package doesn't have current
+         *  day stats but has historical stats.
+         */
+        if (totalBytesWritten < minimumBytesWritten) {
+            return null;
+        }
+        return stats;
+    }
+
     private void addResourceOveruseListenerLocked(
             @CarWatchdogManager.ResourceOveruseFlag int resourceOveruseFlag,
             @NonNull IResourceOveruseListener listener,
@@ -495,20 +725,21 @@
         try {
             listenerInfo.linkToDeath();
         } catch (RemoteException e) {
-            Slogf.w(TAG, "Cannot add %s: linkToDeath to listener failed", listenerType);
+            Slogf.w(CarWatchdogService.TAG, "Cannot add %s: linkToDeath to listener failed",
+                    listenerType);
             return;
         }
 
         if (existingListenerInfo != null) {
-            Slogf.w(TAG, "Overwriting existing %s: pid %d, uid: %d", listenerType,
-                    existingListenerInfo.pid, existingListenerInfo.uid);
+            Slogf.w(CarWatchdogService.TAG, "Overwriting existing %s: pid %d, uid: %d",
+                    listenerType, existingListenerInfo.pid, existingListenerInfo.uid);
             existingListenerInfo.unlinkToDeath();
         }
 
 
         listenerInfosByUid.put(callingUid, listenerInfo);
-        if (mIsDebugEnabled) {
-            Slogf.d(TAG, "The %s (pid: %d, uid: %d) is added", listenerType,
+        if (CarWatchdogService.DEBUG) {
+            Slogf.d(CarWatchdogService.TAG, "The %s (pid: %d, uid: %d) is added", listenerType,
                     callingPid, callingUid);
         }
     }
@@ -522,14 +753,15 @@
 
         ResourceOveruseListenerInfo listenerInfo = listenerInfosByUid.get(callingUid, null);
         if (listenerInfo == null || listenerInfo.listener != listener) {
-            Slogf.w(TAG, "Cannot remove the %s: it has not been registered before", listenerType);
+            Slogf.w(CarWatchdogService.TAG,
+                    "Cannot remove the %s: it has not been registered before", listenerType);
             return;
         }
         listenerInfo.unlinkToDeath();
         listenerInfosByUid.remove(callingUid);
-        if (mIsDebugEnabled) {
-            Slogf.d(TAG, "The %s (pid: %d, uid: %d) is removed", listenerType, listenerInfo.pid,
-                    listenerInfo.uid);
+        if (CarWatchdogService.DEBUG) {
+            Slogf.d(CarWatchdogService.TAG, "The %s (pid: %d, uid: %d) is removed", listenerType,
+                    listenerInfo.pid, listenerInfo.uid);
         }
     }
 
@@ -561,9 +793,8 @@
 
     private static PerStateBytes toPerStateBytes(
             android.automotive.watchdog.PerStateBytes internalPerStateBytes) {
-        PerStateBytes perStateBytes = new PerStateBytes(internalPerStateBytes.foregroundBytes,
+        return new PerStateBytes(internalPerStateBytes.foregroundBytes,
                 internalPerStateBytes.backgroundBytes, internalPerStateBytes.garageModeBytes);
-        return perStateBytes;
     }
 
     private static long totalPerStateBytes(
@@ -575,7 +806,231 @@
                 internalPerStateBytes.backgroundBytes), internalPerStateBytes.garageModeBytes);
     }
 
-    private static final class PackageResourceUsage {
+    private static long getMinimumBytesWritten(
+            @CarWatchdogManager.MinimumStatsFlag int minimumStatsIoFlag) {
+        switch (minimumStatsIoFlag) {
+            case 0:
+                return 0;
+            case CarWatchdogManager.FLAG_MINIMUM_STATS_IO_1_MB:
+                return 1024 * 1024;
+            case CarWatchdogManager.FLAG_MINIMUM_STATS_IO_100_MB:
+                return 100 * 1024 * 1024;
+            case CarWatchdogManager.FLAG_MINIMUM_STATS_IO_1_GB:
+                return 1024 * 1024 * 1024;
+            default:
+                throw new IllegalArgumentException(
+                        "Must provide valid minimum stats flag for I/O resource");
+        }
+    }
+
+    private static android.automotive.watchdog.internal.ResourceOveruseConfiguration
+            toInternalResourceOveruseConfiguration(ResourceOveruseConfiguration config,
+            @CarWatchdogManager.ResourceOveruseFlag int resourceOveruseFlag) {
+        android.automotive.watchdog.internal.ResourceOveruseConfiguration internalConfig =
+                new android.automotive.watchdog.internal.ResourceOveruseConfiguration();
+        internalConfig.componentType = config.getComponentType();
+        internalConfig.safeToKillPackages = config.getSafeToKillPackages();
+        internalConfig.vendorPackagePrefixes = config.getVendorPackagePrefixes();
+        internalConfig.packageMetadata = new ArrayList<>();
+        for (Map.Entry<String, String> entry : config.getPackagesToAppCategoryTypes().entrySet()) {
+            if (entry.getKey().isEmpty()) {
+                continue;
+            }
+            PackageMetadata metadata = new PackageMetadata();
+            metadata.packageName = entry.getKey();
+            switch(entry.getValue()) {
+                case ResourceOveruseConfiguration.APPLICATION_CATEGORY_TYPE_MAPS:
+                    metadata.appCategoryType = ApplicationCategoryType.MAPS;
+                    break;
+                case ResourceOveruseConfiguration.APPLICATION_CATEGORY_TYPE_MEDIA:
+                    metadata.appCategoryType = ApplicationCategoryType.MEDIA;
+                    break;
+                default:
+                    continue;
+            }
+            internalConfig.packageMetadata.add(metadata);
+        }
+        internalConfig.resourceSpecificConfigurations = new ArrayList<>();
+        if ((resourceOveruseFlag & FLAG_RESOURCE_OVERUSE_IO) != 0
+                && config.getIoOveruseConfiguration() != null) {
+            internalConfig.resourceSpecificConfigurations.add(
+                    toResourceSpecificConfiguration(config.getComponentType(),
+                            config.getIoOveruseConfiguration()));
+        }
+        return internalConfig;
+    }
+
+    private static ResourceSpecificConfiguration
+            toResourceSpecificConfiguration(int componentType, IoOveruseConfiguration config) {
+        android.automotive.watchdog.internal.IoOveruseConfiguration internalConfig =
+                new android.automotive.watchdog.internal.IoOveruseConfiguration();
+        internalConfig.componentLevelThresholds = toPerStateIoOveruseThreshold(
+                toComponentTypeStr(componentType), config.getComponentLevelThresholds());
+        internalConfig.packageSpecificThresholds = toPerStateIoOveruseThresholds(
+                config.getPackageSpecificThresholds());
+        internalConfig.categorySpecificThresholds = toPerStateIoOveruseThresholds(
+                config.getAppCategorySpecificThresholds());
+        for (PerStateIoOveruseThreshold threshold : internalConfig.categorySpecificThresholds) {
+            switch(threshold.name) {
+                case ResourceOveruseConfiguration.APPLICATION_CATEGORY_TYPE_MAPS:
+                    threshold.name = INTERNAL_APPLICATION_CATEGORY_TYPE_MAPS;
+                    break;
+                case ResourceOveruseConfiguration.APPLICATION_CATEGORY_TYPE_MEDIA:
+                    threshold.name = INTERNAL_APPLICATION_CATEGORY_TYPE_MEDIA;
+                    break;
+                default:
+                    threshold.name = INTERNAL_APPLICATION_CATEGORY_TYPE_UNKNOWN;
+            }
+        }
+        internalConfig.systemWideThresholds = toInternalIoOveruseAlertThresholds(
+                config.getSystemWideThresholds());
+
+        ResourceSpecificConfiguration resourceSpecificConfig = new ResourceSpecificConfiguration();
+        resourceSpecificConfig.setIoOveruseConfiguration(internalConfig);
+        return resourceSpecificConfig;
+    }
+
+    @VisibleForTesting
+    static String toComponentTypeStr(int componentType) {
+        switch(componentType) {
+            case ComponentType.SYSTEM:
+                return "SYSTEM";
+            case ComponentType.VENDOR:
+                return "VENDOR";
+            case ComponentType.THIRD_PARTY:
+                return "THIRD_PARTY";
+            default:
+                return "UNKNOWN";
+        }
+    }
+
+    private static List<PerStateIoOveruseThreshold> toPerStateIoOveruseThresholds(
+            Map<String, PerStateBytes> thresholds) {
+        List<PerStateIoOveruseThreshold> internalThresholds = new ArrayList<>();
+        for (Map.Entry<String, PerStateBytes> entry : thresholds.entrySet()) {
+            if (!entry.getKey().isEmpty()) {
+                internalThresholds.add(toPerStateIoOveruseThreshold(entry.getKey(),
+                        entry.getValue()));
+            }
+        }
+        return internalThresholds;
+    }
+
+    private static PerStateIoOveruseThreshold toPerStateIoOveruseThreshold(String name,
+            PerStateBytes perStateBytes) {
+        PerStateIoOveruseThreshold threshold = new PerStateIoOveruseThreshold();
+        threshold.name = name;
+        threshold.perStateWriteBytes = new android.automotive.watchdog.PerStateBytes();
+        threshold.perStateWriteBytes.foregroundBytes = perStateBytes.getForegroundModeBytes();
+        threshold.perStateWriteBytes.backgroundBytes = perStateBytes.getBackgroundModeBytes();
+        threshold.perStateWriteBytes.garageModeBytes = perStateBytes.getGarageModeBytes();
+        return threshold;
+    }
+
+    private static List<android.automotive.watchdog.internal.IoOveruseAlertThreshold>
+            toInternalIoOveruseAlertThresholds(List<IoOveruseAlertThreshold> thresholds) {
+        List<android.automotive.watchdog.internal.IoOveruseAlertThreshold> internalThresholds =
+                new ArrayList<>();
+        for (IoOveruseAlertThreshold threshold : thresholds) {
+            if (threshold.getDurationInSeconds() == 0
+                    || threshold.getWrittenBytesPerSecond() == 0) {
+                continue;
+            }
+            android.automotive.watchdog.internal.IoOveruseAlertThreshold internalThreshold =
+                    new android.automotive.watchdog.internal.IoOveruseAlertThreshold();
+            internalThreshold.durationInSeconds = threshold.getDurationInSeconds();
+            internalThreshold.writtenBytesPerSecond = threshold.getWrittenBytesPerSecond();
+            internalThresholds.add(internalThreshold);
+        }
+        return internalThresholds;
+    }
+
+    private static ResourceOveruseConfiguration toResourceOveruseConfiguration(
+            android.automotive.watchdog.internal.ResourceOveruseConfiguration internalConfig,
+            @CarWatchdogManager.ResourceOveruseFlag int resourceOveruseFlag) {
+        Map<String, String> packagesToAppCategoryTypes = new ArrayMap<>();
+        for (PackageMetadata metadata : internalConfig.packageMetadata) {
+            String categoryTypeStr;
+            switch (metadata.appCategoryType) {
+                case ApplicationCategoryType.MAPS:
+                    categoryTypeStr = ResourceOveruseConfiguration.APPLICATION_CATEGORY_TYPE_MAPS;
+                    break;
+                case ApplicationCategoryType.MEDIA:
+                    categoryTypeStr = ResourceOveruseConfiguration.APPLICATION_CATEGORY_TYPE_MEDIA;
+                    break;
+                default:
+                    continue;
+            }
+            packagesToAppCategoryTypes.put(metadata.packageName, categoryTypeStr);
+        }
+        ResourceOveruseConfiguration.Builder configBuilder =
+                new ResourceOveruseConfiguration.Builder(
+                internalConfig.componentType,
+                internalConfig.safeToKillPackages,
+                internalConfig.vendorPackagePrefixes,
+                packagesToAppCategoryTypes);
+        for (ResourceSpecificConfiguration resourceSpecificConfig :
+                internalConfig.resourceSpecificConfigurations) {
+            if (resourceSpecificConfig.getTag()
+                    == ResourceSpecificConfiguration.ioOveruseConfiguration
+                    && (resourceOveruseFlag & FLAG_RESOURCE_OVERUSE_IO) != 0) {
+                configBuilder.setIoOveruseConfiguration(toIoOveruseConfiguration(
+                        resourceSpecificConfig.getIoOveruseConfiguration()));
+            }
+        }
+        return configBuilder.build();
+    }
+
+    private static IoOveruseConfiguration toIoOveruseConfiguration(
+            android.automotive.watchdog.internal.IoOveruseConfiguration internalConfig) {
+        PerStateBytes componentLevelThresholds =
+                toPerStateBytes(internalConfig.componentLevelThresholds.perStateWriteBytes);
+        Map<String, PerStateBytes> packageSpecificThresholds =
+                toPerStateBytesMap(internalConfig.packageSpecificThresholds);
+        Map<String, PerStateBytes> appCategorySpecificThresholds =
+                toPerStateBytesMap(internalConfig.categorySpecificThresholds);
+        replaceKey(appCategorySpecificThresholds, INTERNAL_APPLICATION_CATEGORY_TYPE_MAPS,
+                ResourceOveruseConfiguration.APPLICATION_CATEGORY_TYPE_MAPS);
+        replaceKey(appCategorySpecificThresholds, INTERNAL_APPLICATION_CATEGORY_TYPE_MEDIA,
+                ResourceOveruseConfiguration.APPLICATION_CATEGORY_TYPE_MEDIA);
+        List<IoOveruseAlertThreshold> systemWideThresholds =
+                toIoOveruseAlertThresholds(internalConfig.systemWideThresholds);
+
+        IoOveruseConfiguration.Builder configBuilder = new IoOveruseConfiguration.Builder(
+                componentLevelThresholds, packageSpecificThresholds, appCategorySpecificThresholds,
+                systemWideThresholds);
+        return configBuilder.build();
+    }
+
+    private static Map<String, PerStateBytes> toPerStateBytesMap(
+            List<PerStateIoOveruseThreshold> thresholds) {
+        Map<String, PerStateBytes> thresholdsMap = new ArrayMap<>();
+        for (PerStateIoOveruseThreshold threshold : thresholds) {
+            thresholdsMap.put(threshold.name, toPerStateBytes(threshold.perStateWriteBytes));
+        }
+        return thresholdsMap;
+    }
+
+    private static List<IoOveruseAlertThreshold> toIoOveruseAlertThresholds(
+            List<android.automotive.watchdog.internal.IoOveruseAlertThreshold> internalThresholds) {
+        List<IoOveruseAlertThreshold> thresholds = new ArrayList<>();
+        for (android.automotive.watchdog.internal.IoOveruseAlertThreshold internalThreshold
+                : internalThresholds) {
+            thresholds.add(new IoOveruseAlertThreshold(internalThreshold.durationInSeconds,
+                    internalThreshold.writtenBytesPerSecond));
+        }
+        return thresholds;
+    }
+
+    private static void replaceKey(Map<String, PerStateBytes> map, String oldKey, String newKey) {
+        PerStateBytes perStateBytes = map.get(oldKey);
+        if (perStateBytes != null) {
+            map.put(newKey, perStateBytes);
+            map.remove(oldKey);
+        }
+    }
+
+    private final class PackageResourceUsage {
         public final String packageName;
         public @UserIdInt final int userId;
         public final PackageIoUsage ioUsage;
@@ -583,15 +1038,17 @@
 
         private @KillableState int mKillableState;
 
+        /** Must be called only after acquiring {@link mLock} */
         PackageResourceUsage(@UserIdInt int userId, String packageName) {
             this.packageName = packageName;
             this.userId = userId;
             this.ioUsage = new PackageIoUsage();
             this.oldEnabledState = -1;
-            this.mKillableState = KILLABLE_STATE_YES;
+            this.mKillableState = mDefaultNotKillablePackages.contains(packageName)
+                    ? KILLABLE_STATE_NO : KILLABLE_STATE_YES;
         }
 
-        public void update(android.automotive.watchdog.IoOveruseStats internalStats) {
+        public void updateLocked(android.automotive.watchdog.IoOveruseStats internalStats) {
             if (!internalStats.killableOnOveruse) {
                 /*
                  * Killable value specified in the internal stats is provided by the native daemon.
@@ -600,20 +1057,28 @@
                  * vendor services and doesn't reflect the user choices. Thus if the internal stats
                  * specify the application is not killable, the application is not safe-to-kill.
                  */
-                this.mKillableState = KILLABLE_STATE_NEVER;
+                mKillableState = KILLABLE_STATE_NEVER;
+            } else if (mKillableState == KILLABLE_STATE_NEVER) {
+                /*
+                 * This case happens when a previously unsafe to kill system/vendor package was
+                 * recently marked as safe-to-kill so update the old state to the default value.
+                 */
+                mKillableState = mDefaultNotKillablePackages.contains(packageName)
+                        ? KILLABLE_STATE_NO : KILLABLE_STATE_YES;
             }
             ioUsage.update(internalStats);
         }
 
-        public ResourceOveruseStats getResourceOveruseStatsWithIo() {
-            IoOveruseStats ioOveruseStats = null;
-            if (ioUsage.hasUsage()) {
-                ioOveruseStats = ioUsage.getStatsBuilder().setKillableOnOveruse(
-                        mKillableState != PackageKillableState.KILLABLE_STATE_NEVER).build();
-            }
+        public ResourceOveruseStats.Builder getResourceOveruseStatsBuilder() {
+            return new ResourceOveruseStats.Builder(packageName, UserHandle.of(userId));
+        }
 
-            return new ResourceOveruseStats.Builder(packageName, UserHandle.of(userId))
-                    .setIoOveruseStats(ioOveruseStats).build();
+        public IoOveruseStats getIoOveruseStats() {
+            if (!ioUsage.hasUsage()) {
+                return null;
+            }
+            return ioUsage.getStatsBuilder().setKillableOnOveruse(
+                        mKillableState != KILLABLE_STATE_NEVER).build();
         }
 
         public @KillableState int getKillableState() {
@@ -621,12 +1086,30 @@
         }
 
         public boolean setKillableState(boolean isKillable) {
-            if (mKillableState == PackageKillableState.KILLABLE_STATE_NEVER) {
+            if (mKillableState == KILLABLE_STATE_NEVER) {
                 return false;
             }
             mKillableState = isKillable ? KILLABLE_STATE_YES : KILLABLE_STATE_NO;
             return true;
         }
+
+        public int syncAndFetchKillableStateLocked(int myComponentType) {
+            /*
+             * The killable state goes out-of-sync:
+             * 1. When the on-device safe-to-kill list is recently updated and the user package
+             * didn't have any resource usage so the native daemon didn't update the killable state.
+             * 2. When a package has no resource usage and is initialized outside of processing the
+             * latest resource usage stats.
+             */
+            if (myComponentType != ComponentType.THIRD_PARTY
+                    && !mSafeToKillPackages.contains(packageName)) {
+                mKillableState = KILLABLE_STATE_NEVER;
+            } else if (mKillableState == KILLABLE_STATE_NEVER) {
+                mKillableState = mDefaultNotKillablePackages.contains(packageName)
+                        ? KILLABLE_STATE_NO : KILLABLE_STATE_YES;
+            }
+            return mKillableState;
+        }
     }
 
     private static final class PackageIoUsage {
@@ -697,7 +1180,7 @@
 
         @Override
         public void binderDied() {
-            Slogf.w(TAG, "Resource overuse listener%s (pid: %d) died",
+            Slogf.w(CarWatchdogService.TAG, "Resource overuse listener%s (pid: %d) died",
                     isListenerForSystem ? " for system" : "", pid);
             onResourceOveruseListenerDeath(uid, isListenerForSystem);
             unlinkToDeath();
diff --git a/service/src/com/android/car/watchdog/WatchdogProcessHandler.java b/service/src/com/android/car/watchdog/WatchdogProcessHandler.java
index 59e76c7..2754755 100644
--- a/service/src/com/android/car/watchdog/WatchdogProcessHandler.java
+++ b/service/src/com/android/car/watchdog/WatchdogProcessHandler.java
@@ -37,7 +37,6 @@
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
 
-import com.android.car.CarLog;
 import com.android.internal.annotations.GuardedBy;
 import com.android.server.utils.Slogf;
 
@@ -47,11 +46,9 @@
  * Handles clients' health status checking and reporting the statuses to the watchdog daemon.
  */
 public final class WatchdogProcessHandler {
-    private static final String TAG = CarLog.tagFor(CarWatchdogService.class);
     private static final int[] ALL_TIMEOUTS =
             { TIMEOUT_CRITICAL, TIMEOUT_MODERATE, TIMEOUT_NORMAL };
 
-    private final boolean mIsDebugEnabled;
     private final ICarWatchdogServiceForSystem mWatchdogServiceForSystem;
     private final CarWatchdogDaemonHelper mCarWatchdogDaemonHelper;
     private final Handler mMainHandler = new Handler(Looper.getMainLooper());
@@ -85,8 +82,7 @@
     private final SparseBooleanArray mStoppedUser = new SparseBooleanArray();
 
     public WatchdogProcessHandler(ICarWatchdogServiceForSystem serviceImpl,
-            CarWatchdogDaemonHelper daemonHelper, boolean isDebugEnabled) {
-        mIsDebugEnabled = isDebugEnabled;
+            CarWatchdogDaemonHelper daemonHelper) {
         mWatchdogServiceForSystem = serviceImpl;
         mCarWatchdogDaemonHelper = daemonHelper;
     }
@@ -98,8 +94,8 @@
             mPingedClientMap.put(timeout, new SparseArray<ClientInfo>());
             mClientCheckInProgress.put(timeout, false);
         }
-        if (mIsDebugEnabled) {
-            Slogf.d(TAG, "WatchdogProcessHandler is initialized");
+        if (CarWatchdogService.DEBUG) {
+            Slogf.d(CarWatchdogService.TAG, "WatchdogProcessHandler is initialized");
         }
     }
 
@@ -136,14 +132,15 @@
         synchronized (mLock) {
             ArrayList<ClientInfo> clients = mClientMap.get(timeout);
             if (clients == null) {
-                Slogf.w(TAG, "Cannot register the client: invalid timeout");
+                Slogf.w(CarWatchdogService.TAG, "Cannot register the client: invalid timeout");
                 return;
             }
             IBinder binder = client.asBinder();
             for (int i = 0; i < clients.size(); i++) {
                 ClientInfo clientInfo = clients.get(i);
                 if (binder == clientInfo.client.asBinder()) {
-                    Slogf.w(TAG, "Cannot register the client: the client(pid: %d) has been already "
+                    Slogf.w(CarWatchdogService.TAG,
+                            "Cannot register the client: the client(pid: %d) has been already "
                             + "registered", clientInfo.pid);
                     return;
                 }
@@ -154,12 +151,13 @@
             try {
                 clientInfo.linkToDeath();
             } catch (RemoteException e) {
-                Slogf.w(TAG, "Cannot register the client: linkToDeath to the client failed");
+                Slogf.w(CarWatchdogService.TAG,
+                        "Cannot register the client: linkToDeath to the client failed");
                 return;
             }
             clients.add(clientInfo);
-            if (mIsDebugEnabled) {
-                Slogf.d(TAG, "Client(pid: %d) is registered", pid);
+            if (CarWatchdogService.DEBUG) {
+                Slogf.d(CarWatchdogService.TAG, "Client(pid: %d) is registered", pid);
             }
         }
     }
@@ -177,14 +175,16 @@
                     }
                     clientInfo.unlinkToDeath();
                     clients.remove(i);
-                    if (mIsDebugEnabled) {
-                        Slogf.d(TAG, "Client(pid: %d) is unregistered", clientInfo.pid);
+                    if (CarWatchdogService.DEBUG) {
+                        Slogf.d(CarWatchdogService.TAG, "Client(pid: %d) is unregistered",
+                                clientInfo.pid);
                     }
                     return;
                 }
             }
         }
-        Slogf.w(TAG, "Cannot unregister the client: the client has not been registered before");
+        Slogf.w(CarWatchdogService.TAG,
+                "Cannot unregister the client: the client has not been registered before");
         return;
     }
 
@@ -297,8 +297,8 @@
             try {
                 clientInfo.client.onCheckHealthStatus(clientInfo.sessionId, timeout);
             } catch (RemoteException e) {
-                Slogf.w(TAG, "Sending a ping message to client(pid: %d) failed: %s", clientInfo.pid,
-                        e);
+                Slogf.w(CarWatchdogService.TAG,
+                        "Sending a ping message to client(pid: %d) failed: %s", clientInfo.pid, e);
                 synchronized (mLock) {
                     pingedClients.remove(clientInfo.sessionId);
                 }
@@ -348,7 +348,8 @@
             try {
                 clientInfo.client.onPrepareProcessTermination();
             } catch (RemoteException e) {
-                Slogf.w(TAG, "Notifying onPrepareProcessTermination to client(pid: %d) failed: %s",
+                Slogf.w(CarWatchdogService.TAG,
+                        "Notifying onPrepareProcessTermination to client(pid: %d) failed: %s",
                         clientInfo.pid, e);
             }
         }
@@ -357,7 +358,8 @@
             mCarWatchdogDaemonHelper.tellCarWatchdogServiceAlive(
                     mWatchdogServiceForSystem, clientsNotResponding, sessionId);
         } catch (RemoteException | RuntimeException e) {
-            Slogf.w(TAG, "Cannot respond to car watchdog daemon (sessionId=%d): %s", sessionId, e);
+            Slogf.w(CarWatchdogService.TAG,
+                    "Cannot respond to car watchdog daemon (sessionId=%d): %s", sessionId, e);
         }
     }
 
@@ -380,7 +382,7 @@
             case TIMEOUT_NORMAL:
                 return "normal";
             default:
-                Slogf.w(TAG, "Unknown timeout value");
+                Slogf.w(CarWatchdogService.TAG, "Unknown timeout value");
                 return "unknown";
         }
     }
@@ -394,7 +396,7 @@
             case TIMEOUT_NORMAL:
                 return 10000L;
             default:
-                Slogf.w(TAG, "Unknown timeout value");
+                Slogf.w(CarWatchdogService.TAG, "Unknown timeout value");
                 return 10000L;
         }
     }
@@ -416,7 +418,7 @@
 
         @Override
         public void binderDied() {
-            Slogf.w(TAG, "Client(pid: %d) died", pid);
+            Slogf.w(CarWatchdogService.TAG, "Client(pid: %d) died", pid);
             onClientDeath(client, timeout);
         }
 
diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/bluetooth_devices.xml b/tests/EmbeddedKitchenSinkApp/res/layout/bluetooth_devices.xml
new file mode 100644
index 0000000..c856233
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/res/layout/bluetooth_devices.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<GridLayout xmlns:android="http://schemas.android.com/apk/res/android"
+      xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+
+    android:layout_width="wrap_content"
+    android:layout_height="match_parent"
+    android:columnCount = "2">
+    <TextView
+      android:id="@+id/bluetooth_device"
+      android:layout_height="wrap_content"
+      android:layout_width="wrap_content"/>
+    <Button
+      android:id="@+id/bluetooth_pick_device"
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      android:text="@string/bluetooth_pick_device"/>
+    <TableLayout
+      android:id="@+id/PairedDeviceTable"
+      android:layout_width="409dp"
+      android:layout_height="190dp"
+      tools:layout_editor_absoluteX="1dp"
+      tools:layout_editor_absoluteY="1dp"/>
+</GridLayout>
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkActivity.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkActivity.java
index acbc2ad..01748a6 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkActivity.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkActivity.java
@@ -47,6 +47,7 @@
 import com.google.android.car.kitchensink.assistant.CarAssistantFragment;
 import com.google.android.car.kitchensink.audio.AudioTestFragment;
 import com.google.android.car.kitchensink.audio.CarAudioInputTestFragment;
+import com.google.android.car.kitchensink.bluetooth.BluetoothDeviceFragment;
 import com.google.android.car.kitchensink.bluetooth.BluetoothHeadsetFragment;
 import com.google.android.car.kitchensink.bluetooth.MapMceTestFragment;
 import com.google.android.car.kitchensink.carboard.KeyboardTestFragment;
@@ -173,6 +174,7 @@
             new FragmentMenuEntry("assistant", CarAssistantFragment.class),
             new FragmentMenuEntry("audio", AudioTestFragment.class),
             new FragmentMenuEntry("Audio Input", CarAudioInputTestFragment.class),
+            new FragmentMenuEntry("BT device", BluetoothDeviceFragment.class),
             new FragmentMenuEntry("BT headset", BluetoothHeadsetFragment.class),
             new FragmentMenuEntry("BT messaging", MapMceTestFragment.class),
             new FragmentMenuEntry("carapi", CarApiTestFragment.class),
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/bluetooth/BluetoothDeviceFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/bluetooth/BluetoothDeviceFragment.java
new file mode 100644
index 0000000..c1de700
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/bluetooth/BluetoothDeviceFragment.java
@@ -0,0 +1,162 @@
+/*
+ * 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.google.android.car.kitchensink.bluetooth;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothDevicePicker;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.TableLayout;
+import android.widget.TableRow;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+
+import com.google.android.car.kitchensink.R;
+
+import java.util.Set;
+import java.util.concurrent.Executor;
+
+public class BluetoothDeviceFragment extends Fragment {
+    private static final String TAG = "CAR.BLUETOOTH.KS";
+    BluetoothAdapter mBluetoothAdapter;
+    BluetoothDevice mPickedDevice;
+    Executor mExecutor;
+    BluetoothDeviceTypeChecker mDeviceTypeChecker;
+
+    TextView mPickedDeviceText;
+    Button mDevicePicker;
+    TableLayout mTableLayout;
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
+            @Nullable Bundle savedInstanceState) {
+        View v = inflater.inflate(R.layout.bluetooth_devices, container, false);
+        mDeviceTypeChecker = new BluetoothDeviceTypeChecker(getContext(), true);
+        mDevicePicker = v.findViewById(R.id.bluetooth_pick_device);
+        mTableLayout = v.findViewById(R.id.PairedDeviceTable);
+        mExecutor = new ThreadPerTaskExecutor();
+
+        // Pick a bluetooth device
+        mDevicePicker.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                launchDevicePicker();
+            }
+        });
+
+        return v;
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        checkAllDevices();
+    }
+
+    void launchDevicePicker() {
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(BluetoothDevicePicker.ACTION_DEVICE_SELECTED);
+        getContext().registerReceiver(mPickerReceiver, filter);
+
+        Intent intent = new Intent(BluetoothDevicePicker.ACTION_LAUNCH);
+        intent.setFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+        getContext().startActivity(intent);
+    }
+
+    void checkAllDevices() {
+        BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
+        if (bluetoothAdapter == null) {
+            Log.w(TAG, "Bluetooth Adapter not available");
+            return;
+        }
+        mTableLayout.removeAllViews();
+        Set<BluetoothDevice> bondedDevices = bluetoothAdapter.getBondedDevices();
+        Context context = getContext();
+        for (BluetoothDevice device : bondedDevices) {
+            TableRow row = new TableRow(context);
+            TextView deviceName = new TextView(context);
+            deviceName.setText(device.getName());
+            TextView deviceType = new TextView(context);
+            deviceType.setText(Boolean.toString(mDeviceTypeChecker.isIapDevice(device)));
+            row.addView(deviceName);
+            row.addView(deviceType);
+            mTableLayout.addView(row);
+        }
+    }
+
+    private void addDeviceToTable(BluetoothDevice device, String value) {
+        getActivity().runOnUiThread(new Runnable() {
+            public void run() {
+                Context context = getContext();
+                TableRow row = new TableRow(context);
+                TextView deviceName = new TextView(context);
+                TextView deviceValue = new TextView(context);
+                deviceName.setText(device.getName());
+                deviceValue.setText(value);
+                row.addView(deviceName);
+                row.addView(deviceValue);
+                mTableLayout.addView(row);
+            }
+        });
+    }
+
+    private final BroadcastReceiver mPickerReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            Log.v(TAG, "mPickerReceiver got " + action);
+
+            if (BluetoothDevicePicker.ACTION_DEVICE_SELECTED.equals(action)) {
+                final BluetoothDevice device =
+                        intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+                Log.v(TAG, "mPickerReceiver got " + device);
+                if (device == null) {
+                    Toast.makeText(getContext(), "No device selected", Toast.LENGTH_SHORT).show();
+                    return;
+                }
+                mExecutor.execute(new Runnable() {
+                    @Override
+                    public void run() {
+                        addDeviceToTable(device,
+                                Boolean.toString(mDeviceTypeChecker.isIapDevice(device)));
+                        Log.w(TAG, "Is iAP" + mDeviceTypeChecker.isIapDevice(device));
+                    }
+                });
+                Log.w(TAG, "Dispatched");
+                getContext().unregisterReceiver(mPickerReceiver);
+            }
+        }
+    };
+
+    private class ThreadPerTaskExecutor implements Executor {
+        public void execute(Runnable r) {
+            new Thread(r).start();
+        }
+    }
+}
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/bluetooth/BluetoothDeviceTypeChecker.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/bluetooth/BluetoothDeviceTypeChecker.java
new file mode 100644
index 0000000..1523634
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/bluetooth/BluetoothDeviceTypeChecker.java
@@ -0,0 +1,137 @@
+/*
+ * 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.google.android.car.kitchensink.bluetooth;
+
+import android.bluetooth.BluetoothDevice;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.ParcelUuid;
+import android.util.Log;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+
+public class BluetoothDeviceTypeChecker {
+    private static final ParcelUuid IAP_UUID =
+            ParcelUuid.fromString("00000000-deca-fade-deca-deafdecacafe");
+    private static final int MAX_SECONDS_TO_BLOCK = 10;
+    private static final String TAG = "BluetoothDeviceTypeChecker";
+
+    private final Context mContext;
+    private final boolean mAllowBlocking;
+    private final Object mLock = new Object();
+
+    CompletableFuture<ParcelUuid[]> mDeviceUUidsFuture;
+    BluetoothDevice mBluetoothDevice;
+
+    /**
+     * BluetoothDeviceTypeChecker
+     * Class designed to fetch and check UUID records for matches based on either cached or live
+     * records.  Live records are fetched if allowBlocking is enabled and there is nothing in the
+     * cache.  Paired devices should always have records in the cache if the BluetoothAdapter is on.
+     *
+     * @param context The context on which to receive updates if live records are necessary
+     * @param allowBlocking If cached SDP records are not available allow methods to block in a
+     *                     best effort of acquiring them.
+     */
+    public BluetoothDeviceTypeChecker(Context context, boolean allowBlocking) {
+        mContext = context;
+        if (mContext != null) {
+            mAllowBlocking = allowBlocking;
+        } else {
+            mAllowBlocking = false;
+        }
+    }
+
+    /**
+     * isIapDevice
+     * Check if device is indicating support for iAP
+     * @param device
+     * @return
+     */
+    public boolean isIapDevice(BluetoothDevice device) {
+        return deviceContainsUuid(device, IAP_UUID);
+    }
+
+    /**
+     * deviceContainsUuid
+     * Check if device contains a specific UUID record
+     * @param device to perform a lookup on
+     * @param uuid to check in the records
+     * @return
+     */
+    public boolean deviceContainsUuid(BluetoothDevice device, ParcelUuid uuid) {
+        if (device == null) return false;
+        if (uuid == null) return false;
+
+        synchronized (mLock) {
+            mBluetoothDevice = device;
+            ParcelUuid[] uuidsArray = device.getUuids();
+            if (mAllowBlocking && (uuidsArray == null || uuidsArray.length == 0)) {
+                uuidsArray = blockingFetchUuids(device);
+            }
+            if (uuidsArray == null || uuidsArray.length == 0) {
+                return false;
+            }
+            for (int i = 0; i < uuidsArray.length; i++) {
+                if (uuid.equals(uuidsArray[i])) {
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+
+    /*
+    Perform a blocking fetch of the UUIDs on specified BluetoothDevice
+     */
+    private ParcelUuid[] blockingFetchUuids(BluetoothDevice device) {
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(BluetoothDevice.ACTION_UUID);
+        mContext.registerReceiver(mUuidReceiver, filter);
+        mDeviceUUidsFuture = new CompletableFuture<>();
+        if (!device.fetchUuidsWithSdp()) {
+            Log.w(TAG, "fetching UUIDs failed.");
+            mContext.unregisterReceiver(mUuidReceiver);
+            return new ParcelUuid[0];
+        }
+        try {
+            return mDeviceUUidsFuture.get(MAX_SECONDS_TO_BLOCK, TimeUnit.SECONDS);
+        } catch (Exception e) {
+            mContext.unregisterReceiver(mUuidReceiver);
+            return new ParcelUuid[0];
+        }
+    }
+
+    /*
+    Broadcast receiver on which to receive updates to Bluetooth UUID records.
+     */
+    private BroadcastReceiver mUuidReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            BluetoothDevice device =
+                    intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+            if (mBluetoothDevice.equals(device)
+                    && BluetoothDevice.ACTION_UUID.equals(intent.getAction())) {
+                mDeviceUUidsFuture.complete(device.getUuids());
+                mContext.unregisterReceiver(this);
+            }
+        }
+    };
+}
diff --git a/tests/carservice_test/src/com/android/car/audio/CarVolumeGroupTest.java b/tests/carservice_test/src/com/android/car/audio/CarVolumeGroupTest.java
deleted file mode 100644
index cb78b50..0000000
--- a/tests/carservice_test/src/com/android/car/audio/CarVolumeGroupTest.java
+++ /dev/null
@@ -1,593 +0,0 @@
-/*
- * Copyright (C) 2019 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.audio;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-import static org.testng.Assert.expectThrows;
-
-import android.annotation.UserIdInt;
-import android.app.ActivityManager;
-import android.car.test.mocks.AbstractExtendedMockitoTestCase;
-import android.os.UserHandle;
-import android.util.SparseBooleanArray;
-import android.util.SparseIntArray;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
-import com.google.common.primitives.Ints;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mockito;
-
-import java.util.List;
-
-@RunWith(AndroidJUnit4.class)
-public class CarVolumeGroupTest extends AbstractExtendedMockitoTestCase{
-    private static final int STEP_VALUE = 2;
-    private static final int MIN_GAIN = 0;
-    private static final int MAX_GAIN = 5;
-    private static final int DEFAULT_GAIN = 0;
-    private static final int TEST_USER_10 = 10;
-    private static final int TEST_USER_11 = 11;
-    private static final String OTHER_ADDRESS = "other_address";
-    private static final String MEDIA_DEVICE_ADDRESS = "music";
-    private static final String NAVIGATION_DEVICE_ADDRESS = "navigation";
-
-
-    private CarAudioDeviceInfo mMediaDevice;
-    private CarAudioDeviceInfo mNavigationDevice;
-
-    @Override
-    protected void onSessionBuilder(CustomMockitoSessionBuilder session) {
-        session.spyStatic(ActivityManager.class);
-    }
-
-    @Before
-    public void setUp() {
-        mMediaDevice = generateCarAudioDeviceInfo(MEDIA_DEVICE_ADDRESS);
-        mNavigationDevice = generateCarAudioDeviceInfo(NAVIGATION_DEVICE_ADDRESS);
-    }
-
-    @Test
-    public void bind_associatesDeviceAddresses() {
-        CarVolumeGroup carVolumeGroup =
-                getVolumeGroupWithGainAndUser(2, UserHandle.USER_CURRENT);
-
-        carVolumeGroup.bind(CarAudioContext.MUSIC, mMediaDevice);
-        assertEquals(1, carVolumeGroup.getAddresses().size());
-
-        carVolumeGroup.bind(CarAudioContext.NAVIGATION, mNavigationDevice);
-
-        List<String> addresses = carVolumeGroup.getAddresses();
-        assertEquals(2, addresses.size());
-        assertTrue(addresses.contains(MEDIA_DEVICE_ADDRESS));
-        assertTrue(addresses.contains(NAVIGATION_DEVICE_ADDRESS));
-    }
-
-    @Test
-    public void bind_checksForSameStepSize() {
-        CarVolumeGroup carVolumeGroup =
-                getVolumeGroupWithGainAndUser(2, UserHandle.USER_CURRENT);
-
-        carVolumeGroup.bind(CarAudioContext.MUSIC, mMediaDevice);
-        CarAudioDeviceInfo differentStepValueDevice = generateCarAudioDeviceInfo(
-                NAVIGATION_DEVICE_ADDRESS, STEP_VALUE + 1,
-                MIN_GAIN, MAX_GAIN);
-
-        IllegalArgumentException thrown = expectThrows(IllegalArgumentException.class,
-                () -> carVolumeGroup.bind(CarAudioContext.NAVIGATION, differentStepValueDevice));
-        assertThat(thrown).hasMessageThat()
-                .contains("Gain controls within one group must have same step value");
-    }
-
-    @Test
-    public void bind_updatesMinGainToSmallestValue() {
-        CarVolumeGroup carVolumeGroup =
-                getVolumeGroupWithGainAndUser(2, UserHandle.USER_CURRENT);
-
-        CarAudioDeviceInfo largestMinGain = generateCarAudioDeviceInfo(
-                NAVIGATION_DEVICE_ADDRESS, 1, 10, 10);
-        carVolumeGroup.bind(CarAudioContext.NAVIGATION, largestMinGain);
-
-        assertEquals(0, carVolumeGroup.getMaxGainIndex());
-
-        CarAudioDeviceInfo smallestMinGain = generateCarAudioDeviceInfo(
-                NAVIGATION_DEVICE_ADDRESS, 1, 2, 10);
-        carVolumeGroup.bind(CarAudioContext.NOTIFICATION, smallestMinGain);
-
-        assertEquals(8, carVolumeGroup.getMaxGainIndex());
-
-        CarAudioDeviceInfo middleMinGain = generateCarAudioDeviceInfo(
-                NAVIGATION_DEVICE_ADDRESS, 1, 7, 10);
-        carVolumeGroup.bind(CarAudioContext.VOICE_COMMAND, middleMinGain);
-
-        assertEquals(8, carVolumeGroup.getMaxGainIndex());
-    }
-
-    @Test
-    public void bind_updatesMaxGainToLargestValue() {
-        CarVolumeGroup carVolumeGroup =
-                getVolumeGroupWithGainAndUser(2, UserHandle.USER_CURRENT);
-
-        CarAudioDeviceInfo smallestMaxGain = generateCarAudioDeviceInfo(
-                NAVIGATION_DEVICE_ADDRESS, 1, 1, 5);
-        carVolumeGroup.bind(CarAudioContext.NAVIGATION, smallestMaxGain);
-
-        assertEquals(4, carVolumeGroup.getMaxGainIndex());
-
-        CarAudioDeviceInfo largestMaxGain = generateCarAudioDeviceInfo(
-                NAVIGATION_DEVICE_ADDRESS, 1, 1, 10);
-        carVolumeGroup.bind(CarAudioContext.NOTIFICATION, largestMaxGain);
-
-        assertEquals(9, carVolumeGroup.getMaxGainIndex());
-
-        CarAudioDeviceInfo middleMaxGain = generateCarAudioDeviceInfo(
-                NAVIGATION_DEVICE_ADDRESS, 1, 1, 7);
-        carVolumeGroup.bind(CarAudioContext.VOICE_COMMAND, middleMaxGain);
-
-        assertEquals(9, carVolumeGroup.getMaxGainIndex());
-    }
-
-    @Test
-    public void bind_checksThatTheSameContextIsNotBoundTwice() {
-        CarVolumeGroup carVolumeGroup =
-                getVolumeGroupWithGainAndUser(2, UserHandle.USER_CURRENT);
-
-        carVolumeGroup.bind(CarAudioContext.NAVIGATION, mMediaDevice);
-
-        IllegalArgumentException thrown = expectThrows(IllegalArgumentException.class,
-                () -> carVolumeGroup.bind(CarAudioContext.NAVIGATION, mMediaDevice));
-        assertThat(thrown).hasMessageThat()
-                .contains("Context NAVIGATION has already been bound to " + MEDIA_DEVICE_ADDRESS);
-    }
-
-    @Test
-    public void getContexts_returnsAllContextsBoundToVolumeGroup() {
-        CarVolumeGroup carVolumeGroup = testVolumeGroupSetup();
-
-        int[] contexts = carVolumeGroup.getContexts();
-
-        assertEquals(6, contexts.length);
-
-        List<Integer> contextsList = Ints.asList(contexts);
-        assertTrue(contextsList.contains(CarAudioContext.MUSIC));
-        assertTrue(contextsList.contains(CarAudioContext.CALL));
-        assertTrue(contextsList.contains(CarAudioContext.CALL_RING));
-        assertTrue(contextsList.contains(CarAudioContext.NAVIGATION));
-        assertTrue(contextsList.contains(CarAudioContext.ALARM));
-        assertTrue(contextsList.contains(CarAudioContext.NOTIFICATION));
-    }
-
-    @Test
-    public void getContextsForAddress_returnsContextsBoundToThatAddress() {
-        CarVolumeGroup carVolumeGroup = testVolumeGroupSetup();
-
-        List<Integer> contextsList = carVolumeGroup.getContextsForAddress(MEDIA_DEVICE_ADDRESS);
-
-        assertThat(contextsList).containsExactly(CarAudioContext.MUSIC,
-                CarAudioContext.CALL, CarAudioContext.CALL_RING);
-    }
-
-    @Test
-    public void getContextsForAddress_returnsEmptyArrayIfAddressNotBound() {
-        CarVolumeGroup carVolumeGroup = testVolumeGroupSetup();
-
-        List<Integer> contextsList = carVolumeGroup.getContextsForAddress(OTHER_ADDRESS);
-
-        assertThat(contextsList).isEmpty();
-    }
-
-    @Test
-    public void getCarAudioDeviceInfoForAddress_returnsExpectedDevice() {
-        CarVolumeGroup carVolumeGroup = testVolumeGroupSetup();
-
-        CarAudioDeviceInfo actualDevice = carVolumeGroup.getCarAudioDeviceInfoForAddress(
-                MEDIA_DEVICE_ADDRESS);
-
-        assertEquals(mMediaDevice, actualDevice);
-    }
-
-    @Test
-    public void getCarAudioDeviceInfoForAddress_returnsNullIfAddressNotBound() {
-        CarVolumeGroup carVolumeGroup = testVolumeGroupSetup();
-
-        CarAudioDeviceInfo actualDevice = carVolumeGroup.getCarAudioDeviceInfoForAddress(
-                OTHER_ADDRESS);
-
-        assertNull(actualDevice);
-    }
-
-    @Test
-    public void setCurrentGainIndex_setsGainOnAllBoundDevices() {
-        CarVolumeGroup carVolumeGroup = testVolumeGroupSetup();
-
-        carVolumeGroup.setCurrentGainIndex(2);
-        verify(mMediaDevice).setCurrentGain(4);
-        verify(mNavigationDevice).setCurrentGain(4);
-    }
-
-    @Test
-    public void setCurrentGainIndex_updatesCurrentGainIndex() {
-        CarVolumeGroup carVolumeGroup = testVolumeGroupSetup();
-
-        carVolumeGroup.setCurrentGainIndex(2);
-
-        assertEquals(2, carVolumeGroup.getCurrentGainIndex());
-    }
-
-    @Test
-    public void setCurrentGainIndex_checksNewGainIsAboveMin() {
-        CarVolumeGroup carVolumeGroup = testVolumeGroupSetup();
-
-        IllegalArgumentException thrown = expectThrows(IllegalArgumentException.class,
-                () -> carVolumeGroup.setCurrentGainIndex(-1));
-        assertThat(thrown).hasMessageThat().contains("Gain out of range (0:5) -2index -1");
-    }
-
-    @Test
-    public void setCurrentGainIndex_checksNewGainIsBelowMax() {
-        CarVolumeGroup carVolumeGroup = testVolumeGroupSetup();
-
-        IllegalArgumentException thrown = expectThrows(IllegalArgumentException.class,
-                () -> carVolumeGroup.setCurrentGainIndex(3));
-        assertThat(thrown).hasMessageThat().contains("Gain out of range (0:5) 6index 3");
-    }
-
-    @Test
-    public void getMinGainIndex_alwaysReturnsZero() {
-        CarVolumeGroup carVolumeGroup =
-                getVolumeGroupWithGainAndUser(2, UserHandle.USER_CURRENT);
-        CarAudioDeviceInfo minGainPlusOneDevice = generateCarAudioDeviceInfo(
-                NAVIGATION_DEVICE_ADDRESS, STEP_VALUE, 10, MAX_GAIN);
-        carVolumeGroup.bind(CarAudioContext.NAVIGATION, minGainPlusOneDevice);
-
-        assertEquals(0, carVolumeGroup.getMinGainIndex());
-
-        CarAudioDeviceInfo minGainDevice = generateCarAudioDeviceInfo(
-                NAVIGATION_DEVICE_ADDRESS, STEP_VALUE, 1, MAX_GAIN);
-        carVolumeGroup.bind(CarAudioContext.NOTIFICATION, minGainDevice);
-
-        assertEquals(0, carVolumeGroup.getMinGainIndex());
-    }
-
-    @Test
-    public void loadVolumesSettingsForUser_setsCurrentGainIndexForUser() {
-        CarAudioSettings settings = new CarVolumeGroupSettingsBuilder(0, 0)
-                .setGainIndexForUser(TEST_USER_10, 2)
-                .setGainIndexForUser(TEST_USER_11, 0)
-                .build();
-
-        CarVolumeGroup carVolumeGroup = new CarVolumeGroup(0, 0, settings, false);
-
-        CarAudioDeviceInfo deviceInfo = generateCarAudioDeviceInfo(
-                NAVIGATION_DEVICE_ADDRESS, STEP_VALUE, MIN_GAIN, MAX_GAIN);
-        carVolumeGroup.bind(CarAudioContext.NAVIGATION, deviceInfo);
-        carVolumeGroup.loadVolumesSettingsForUser(TEST_USER_10);
-
-        assertEquals(2, carVolumeGroup.getCurrentGainIndex());
-
-        carVolumeGroup.loadVolumesSettingsForUser(TEST_USER_11);
-
-        assertEquals(0, carVolumeGroup.getCurrentGainIndex());
-    }
-
-    @Test
-    public void loadVolumesSettingsForUser_setsCurrentGainIndexToDefault() {
-        CarAudioSettings settings = new CarVolumeGroupSettingsBuilder(0, 0)
-                .setGainIndexForUser(TEST_USER_10, 10)
-                .build();
-        CarVolumeGroup carVolumeGroup = new CarVolumeGroup(0, 0, settings, false);
-
-        CarAudioDeviceInfo deviceInfo = generateCarAudioDeviceInfo(
-                NAVIGATION_DEVICE_ADDRESS, STEP_VALUE, MIN_GAIN, MAX_GAIN);
-        carVolumeGroup.bind(CarAudioContext.NAVIGATION, deviceInfo);
-
-        carVolumeGroup.setCurrentGainIndex(2);
-
-        assertEquals(2, carVolumeGroup.getCurrentGainIndex());
-
-        carVolumeGroup.loadVolumesSettingsForUser(0);
-
-        assertEquals(0, carVolumeGroup.getCurrentGainIndex());
-    }
-
-    @Test
-    public void setCurrentGainIndex_setsCurrentGainIndexForUser() {
-        CarAudioSettings settings = new CarVolumeGroupSettingsBuilder(0, 0)
-                .setGainIndexForUser(TEST_USER_11, 2)
-                .build();
-        CarVolumeGroup carVolumeGroup = new CarVolumeGroup(0, 0, settings, false);
-
-        CarAudioDeviceInfo deviceInfo = generateCarAudioDeviceInfo(
-                NAVIGATION_DEVICE_ADDRESS, STEP_VALUE, MIN_GAIN, MAX_GAIN);
-        carVolumeGroup.bind(CarAudioContext.NAVIGATION, deviceInfo);
-        carVolumeGroup.loadVolumesSettingsForUser(TEST_USER_11);
-
-        carVolumeGroup.setCurrentGainIndex(MIN_GAIN);
-
-        verify(settings).storeVolumeGainIndexForUser(TEST_USER_11, 0, 0, MIN_GAIN);
-    }
-
-    @Test
-    public void setCurrentGainIndex_setsCurrentGainIndexForDefaultUser() {
-        CarAudioSettings settings = new CarVolumeGroupSettingsBuilder(0, 0)
-                .setGainIndexForUser(UserHandle.USER_CURRENT, 2)
-                .build();
-        CarVolumeGroup carVolumeGroup = new CarVolumeGroup(0, 0, settings, false);
-
-        CarAudioDeviceInfo deviceInfo = generateCarAudioDeviceInfo(
-                NAVIGATION_DEVICE_ADDRESS, STEP_VALUE, MIN_GAIN, MAX_GAIN);
-        carVolumeGroup.bind(CarAudioContext.NAVIGATION, deviceInfo);
-
-        carVolumeGroup.setCurrentGainIndex(MIN_GAIN);
-
-        verify(settings)
-                .storeVolumeGainIndexForUser(UserHandle.USER_CURRENT, 0, 0, MIN_GAIN);
-    }
-
-    @Test
-    public void bind_setsCurrentGainIndexToStoredGainIndex() {
-        CarVolumeGroup carVolumeGroup =
-                getVolumeGroupWithGainAndUser(2, UserHandle.USER_CURRENT);
-
-        CarAudioDeviceInfo deviceInfo = generateCarAudioDeviceInfo(
-                NAVIGATION_DEVICE_ADDRESS, STEP_VALUE, MIN_GAIN, MAX_GAIN);
-        carVolumeGroup.bind(CarAudioContext.NAVIGATION, deviceInfo);
-
-
-        assertEquals(2, carVolumeGroup.getCurrentGainIndex());
-    }
-
-    @Test
-    public void getAddressForContext_returnsExpectedDeviceAddress() {
-        CarVolumeGroup carVolumeGroup =
-                getVolumeGroupWithGainAndUser(2, UserHandle.USER_CURRENT);
-
-        carVolumeGroup.bind(CarAudioContext.MUSIC, mMediaDevice);
-
-        String mediaAddress = carVolumeGroup.getAddressForContext(CarAudioContext.MUSIC);
-
-        assertEquals(mMediaDevice.getAddress(), mediaAddress);
-    }
-
-    @Test
-    public void getAddressForContext_returnsNull() {
-        CarVolumeGroup carVolumeGroup =
-                getVolumeGroupWithGainAndUser(2, UserHandle.USER_CURRENT);
-
-        String nullAddress = carVolumeGroup.getAddressForContext(CarAudioContext.MUSIC);
-
-        assertNull(nullAddress);
-    }
-
-    @Test
-    public void isMuted_whenDefault_returnsFalse() {
-        CarVolumeGroup carVolumeGroup = testVolumeGroupSetup();
-
-        assertThat(carVolumeGroup.isMuted()).isFalse();
-    }
-
-    @Test
-    public void isMuted_afterMuting_returnsTrue() {
-        CarVolumeGroup carVolumeGroup = testVolumeGroupSetup();
-        carVolumeGroup.setMute(true);
-
-        assertThat(carVolumeGroup.isMuted()).isTrue();
-    }
-
-    @Test
-    public void isMuted_afterUnMuting_returnsFalse() {
-        CarVolumeGroup carVolumeGroup = testVolumeGroupSetup();
-        carVolumeGroup.setMute(false);
-
-        assertThat(carVolumeGroup.isMuted()).isFalse();
-    }
-
-    @Test
-    public void setMute_withMutedState_storesValueToSetting() {
-        CarAudioSettings settings = new CarVolumeGroupSettingsBuilder(0, 0)
-                .setMuteForUser(TEST_USER_10, false)
-                .setIsPersistVolumeGroupEnabled(true)
-                .build();
-        CarVolumeGroup carVolumeGroup = new CarVolumeGroup(0, 0, settings, true);
-        CarAudioDeviceInfo deviceInfo = generateCarAudioDeviceInfo(
-                NAVIGATION_DEVICE_ADDRESS, STEP_VALUE, MIN_GAIN, MAX_GAIN);
-        carVolumeGroup.bind(CarAudioContext.NAVIGATION, deviceInfo);
-        carVolumeGroup.loadVolumesSettingsForUser(TEST_USER_10);
-
-        carVolumeGroup.setMute(true);
-
-        verify(settings)
-                .storeVolumeGroupMuteForUser(TEST_USER_10, 0, 0, true);
-    }
-
-    @Test
-    public void setMute_withUnMutedState_storesValueToSetting() {
-        CarAudioSettings settings = new CarVolumeGroupSettingsBuilder(0, 0)
-                .setMuteForUser(TEST_USER_10, false)
-                .setIsPersistVolumeGroupEnabled(true)
-                .build();
-        CarVolumeGroup carVolumeGroup = new CarVolumeGroup(0, 0, settings, true);
-        CarAudioDeviceInfo deviceInfo = generateCarAudioDeviceInfo(
-                NAVIGATION_DEVICE_ADDRESS, STEP_VALUE, MIN_GAIN, MAX_GAIN);
-        carVolumeGroup.bind(CarAudioContext.NAVIGATION, deviceInfo);
-        carVolumeGroup.loadVolumesSettingsForUser(TEST_USER_10);
-
-        carVolumeGroup.setMute(false);
-
-        verify(settings)
-                .storeVolumeGroupMuteForUser(TEST_USER_10, 0, 0, false);
-    }
-
-    @Test
-    public void loadVolumesSettingsForUser_withMutedState_loadsMuteStateForUser() {
-        CarVolumeGroup carVolumeGroup = getVolumeGroupWithMuteForUser(true, true,
-                TEST_USER_10);
-        CarAudioDeviceInfo deviceInfo = generateCarAudioDeviceInfo(
-                NAVIGATION_DEVICE_ADDRESS, STEP_VALUE, MIN_GAIN, MAX_GAIN);
-        carVolumeGroup.bind(CarAudioContext.NAVIGATION, deviceInfo);
-
-        carVolumeGroup.loadVolumesSettingsForUser(TEST_USER_10);
-
-        assertEquals(true, carVolumeGroup.isMuted());
-    }
-
-    @Test
-    public void loadVolumesSettingsForUser_withDisabledUseVolumeGroupMute_doesNotLoadMute() {
-        CarAudioSettings settings = new CarVolumeGroupSettingsBuilder(0, 0)
-                .setMuteForUser(TEST_USER_10, true)
-                .setIsPersistVolumeGroupEnabled(true)
-                .build();
-        CarVolumeGroup carVolumeGroup = new CarVolumeGroup(0, 0, settings, false);
-        CarAudioDeviceInfo deviceInfo = generateCarAudioDeviceInfo(
-                NAVIGATION_DEVICE_ADDRESS, STEP_VALUE, MIN_GAIN, MAX_GAIN);
-        carVolumeGroup.bind(CarAudioContext.NAVIGATION, deviceInfo);
-
-        carVolumeGroup.loadVolumesSettingsForUser(TEST_USER_10);
-
-        assertEquals(false, carVolumeGroup.isMuted());
-    }
-
-    @Test
-    public void loadVolumesSettingsForUser_withUnMutedState_loadsMuteStateForUser() {
-        CarVolumeGroup carVolumeGroup = getVolumeGroupWithMuteForUser(false, true,
-                TEST_USER_10);
-        CarAudioDeviceInfo deviceInfo = generateCarAudioDeviceInfo(
-                NAVIGATION_DEVICE_ADDRESS, STEP_VALUE, MIN_GAIN, MAX_GAIN);
-        carVolumeGroup.bind(CarAudioContext.NAVIGATION, deviceInfo);
-
-        carVolumeGroup.loadVolumesSettingsForUser(TEST_USER_10);
-
-        assertEquals(false, carVolumeGroup.isMuted());
-    }
-
-    @Test
-    public void loadVolumesSettingsForUser_withMutedStateAndNoPersist_returnsDefaultMuteState() {
-        CarVolumeGroup carVolumeGroup = getVolumeGroupWithMuteForUser(true, false,
-                TEST_USER_10);
-        CarAudioDeviceInfo deviceInfo = generateCarAudioDeviceInfo(
-                NAVIGATION_DEVICE_ADDRESS, STEP_VALUE, MIN_GAIN, MAX_GAIN);
-        carVolumeGroup.bind(CarAudioContext.NAVIGATION, deviceInfo);
-
-        carVolumeGroup.loadVolumesSettingsForUser(TEST_USER_10);
-
-        assertEquals(false, carVolumeGroup.isMuted());
-    }
-
-    CarVolumeGroup getVolumeGroupWithGainAndUser(int gain, @UserIdInt int userId) {
-        CarAudioSettings settings = new CarVolumeGroupSettingsBuilder(0, 0)
-                .setGainIndexForUser(userId, gain)
-                .build();
-        CarVolumeGroup carVolumeGroup = new CarVolumeGroup(0, 0, settings, false);
-
-        return carVolumeGroup;
-    }
-
-    CarVolumeGroup getVolumeGroupWithMuteForUser(boolean isMuted, boolean persistMute,
-            @UserIdInt int userId) {
-        CarAudioSettings settings = new CarVolumeGroupSettingsBuilder(0, 0)
-                .setMuteForUser(userId, isMuted)
-                .setIsPersistVolumeGroupEnabled(persistMute)
-                .build();
-        CarVolumeGroup carVolumeGroup = new CarVolumeGroup(0, 0, settings, true);
-
-        return carVolumeGroup;
-    }
-
-    private CarVolumeGroup testVolumeGroupSetup() {
-        CarVolumeGroup carVolumeGroup =
-                getVolumeGroupWithGainAndUser(2, UserHandle.USER_CURRENT);
-
-        carVolumeGroup.bind(CarAudioContext.MUSIC, mMediaDevice);
-        carVolumeGroup.bind(CarAudioContext.CALL, mMediaDevice);
-        carVolumeGroup.bind(CarAudioContext.CALL_RING, mMediaDevice);
-
-        carVolumeGroup.bind(CarAudioContext.NAVIGATION, mNavigationDevice);
-        carVolumeGroup.bind(CarAudioContext.ALARM, mNavigationDevice);
-        carVolumeGroup.bind(CarAudioContext.NOTIFICATION, mNavigationDevice);
-
-        return carVolumeGroup;
-    }
-
-    private CarAudioDeviceInfo generateCarAudioDeviceInfo(String address) {
-        return generateCarAudioDeviceInfo(address, STEP_VALUE, MIN_GAIN, MAX_GAIN);
-    }
-
-    private CarAudioDeviceInfo generateCarAudioDeviceInfo(String address, int stepValue,
-            int minGain, int maxGain) {
-        CarAudioDeviceInfo cadiMock = Mockito.mock(CarAudioDeviceInfo.class);
-        when(cadiMock.getStepValue()).thenReturn(stepValue);
-        when(cadiMock.getDefaultGain()).thenReturn(DEFAULT_GAIN);
-        when(cadiMock.getMaxGain()).thenReturn(maxGain);
-        when(cadiMock.getMinGain()).thenReturn(minGain);
-        when(cadiMock.getAddress()).thenReturn(address);
-        return cadiMock;
-    }
-
-    private static final class CarVolumeGroupSettingsBuilder {
-        private SparseIntArray mStoredGainIndexes = new SparseIntArray();
-        private SparseBooleanArray mStoreMuteStates = new SparseBooleanArray();
-        private boolean mPersistMute;
-        private final int mZoneId;
-        private final int mGroupId;
-
-        CarVolumeGroupSettingsBuilder(int zoneId, int groupId) {
-            mZoneId = zoneId;
-            mGroupId = groupId;
-        }
-
-        CarVolumeGroupSettingsBuilder setGainIndexForUser(@UserIdInt int userId, int gainIndex) {
-            mStoredGainIndexes.put(userId, gainIndex);
-            return this;
-        }
-
-        CarVolumeGroupSettingsBuilder setMuteForUser(@UserIdInt int userId, boolean mute) {
-            mStoreMuteStates.put(userId, mute);
-            return this;
-        }
-
-        CarVolumeGroupSettingsBuilder setIsPersistVolumeGroupEnabled(boolean persistMute) {
-            mPersistMute = persistMute;
-            return this;
-        }
-
-        CarAudioSettings build() {
-            CarAudioSettings settingsMock = Mockito.mock(CarAudioSettings.class);
-            for (int storeIndex = 0; storeIndex < mStoredGainIndexes.size(); storeIndex++) {
-                int gainUserId = mStoredGainIndexes.keyAt(storeIndex);
-                when(settingsMock
-                        .getStoredVolumeGainIndexForUser(gainUserId, mZoneId,
-                        mGroupId)).thenReturn(mStoredGainIndexes.get(gainUserId, DEFAULT_GAIN));
-            }
-            for (int muteIndex = 0; muteIndex < mStoreMuteStates.size(); muteIndex++) {
-                int muteUserId = mStoreMuteStates.keyAt(muteIndex);
-                when(settingsMock.getVolumeGroupMuteForUser(muteUserId, mZoneId, mGroupId))
-                        .thenReturn(mStoreMuteStates.get(muteUserId, false));
-                when(settingsMock.isPersistVolumeGroupMuteEnabled(muteUserId))
-                        .thenReturn(mPersistMute);
-            }
-            return settingsMock;
-        }
-    }
-}
diff --git a/tests/carservice_unit_test/src/android/car/test/mocks/JavaMockitoHelperTest.java b/tests/carservice_unit_test/src/android/car/test/mocks/JavaMockitoHelperTest.java
new file mode 100644
index 0000000..f83deee
--- /dev/null
+++ b/tests/carservice_unit_test/src/android/car/test/mocks/JavaMockitoHelperTest.java
@@ -0,0 +1,93 @@
+/*
+ * 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 android.car.test.mocks;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import org.junit.Test;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+public final class JavaMockitoHelperTest {
+
+    private static final long TIMEOUT_MS = 1_000L;
+
+    @Test
+    public void testAwait_Semaphore() throws InterruptedException {
+        Semaphore semaphore = new Semaphore(1);
+
+        JavaMockitoHelper.await(semaphore, TIMEOUT_MS);
+
+        assertThat(semaphore.availablePermits()).isEqualTo(0);
+    }
+
+    @Test
+    public void testAwait_CountDownLatch() throws InterruptedException {
+        CountDownLatch latch = new CountDownLatch(1);
+        new Thread(() -> latch.countDown(), "testAwait_CountDownLatch").start();
+
+        JavaMockitoHelper.await(latch, TIMEOUT_MS);
+
+        assertThat(latch.getCount()).isEqualTo(0);
+    }
+
+    @Test
+    public void testSilentAwait_notCalled() {
+        CountDownLatch latch = new CountDownLatch(1);
+
+        assertThat(JavaMockitoHelper.silentAwait(latch, 5L)).isFalse();
+        assertThat(latch.getCount()).isEqualTo(1);
+    }
+
+    @Test
+    public void testSilentAwait_called() {
+        CountDownLatch latch = new CountDownLatch(1);
+        new Thread(() -> latch.countDown(), "testSilentAwait_called").start();
+
+        assertThat(JavaMockitoHelper.silentAwait(latch, TIMEOUT_MS)).isTrue();
+        assertThat(latch.getCount()).isEqualTo(0);
+    }
+
+    @Test
+    public void testGetResult() throws InterruptedException, ExecutionException, TimeoutException {
+        Future<String> future = mock(Future.class);
+        when(future.get(anyLong(), any())).thenReturn("done");
+
+        assertThat(JavaMockitoHelper.getResult(future)).isEqualTo("done");
+    }
+
+    @Test
+    public void testGetResult_withCustomTimeout()
+            throws InterruptedException, ExecutionException, TimeoutException {
+        Future<String> future = mock(Future.class);
+        when(future.get(anyLong(), any(TimeUnit.class))).thenReturn("done");
+
+        assertThat(JavaMockitoHelper.getResult(future, TIMEOUT_MS)).isEqualTo("done");
+        verify(future).get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+    }
+}
diff --git a/tests/carservice_unit_test/src/android/car/test/util/SyncAnswerTest.java b/tests/carservice_unit_test/src/android/car/test/mocks/SyncAnswerTest.java
similarity index 97%
rename from tests/carservice_unit_test/src/android/car/test/util/SyncAnswerTest.java
rename to tests/carservice_unit_test/src/android/car/test/mocks/SyncAnswerTest.java
index 2e26e7e..4847588 100644
--- a/tests/carservice_unit_test/src/android/car/test/util/SyncAnswerTest.java
+++ b/tests/carservice_unit_test/src/android/car/test/mocks/SyncAnswerTest.java
@@ -14,15 +14,13 @@
  * limitations under the License.
  */
 
-package android.car.test.util;
+package android.car.test.mocks;
 
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.when;
 
-import android.car.test.mocks.SyncAnswer;
-
 import org.junit.Before;
 import org.junit.Test;
 
diff --git a/tests/carservice_unit_test/src/android/car/watchdoglib/CarWatchdogDaemonHelperTest.java b/tests/carservice_unit_test/src/android/car/watchdoglib/CarWatchdogDaemonHelperTest.java
index d682969..92dfb9a 100644
--- a/tests/carservice_unit_test/src/android/car/watchdoglib/CarWatchdogDaemonHelperTest.java
+++ b/tests/carservice_unit_test/src/android/car/watchdoglib/CarWatchdogDaemonHelperTest.java
@@ -20,11 +20,15 @@
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 import static org.testng.Assert.assertThrows;
 
+import android.automotive.watchdog.internal.ComponentType;
 import android.automotive.watchdog.internal.ICarWatchdog;
 import android.automotive.watchdog.internal.ICarWatchdogMonitor;
 import android.automotive.watchdog.internal.ICarWatchdogServiceForSystem;
@@ -32,6 +36,7 @@
 import android.automotive.watchdog.internal.PackageIoOveruseStats;
 import android.automotive.watchdog.internal.PackageResourceOveruseAction;
 import android.automotive.watchdog.internal.PowerCycle;
+import android.automotive.watchdog.internal.ResourceOveruseConfiguration;
 import android.automotive.watchdog.internal.StateType;
 import android.os.Binder;
 import android.os.IBinder;
@@ -47,6 +52,7 @@
 import org.mockito.quality.Strictness;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 
 /**
@@ -154,6 +160,31 @@
     }
 
     @Test
+    public void testIndirectCall_updateResourceOveruseConfigurations() throws Exception {
+        ResourceOveruseConfiguration config = new ResourceOveruseConfiguration();
+        config.componentType = ComponentType.SYSTEM;
+        List<ResourceOveruseConfiguration> configs = new ArrayList<>(Collections.singleton(config));
+
+        mCarWatchdogDaemonHelper.updateResourceOveruseConfigurations(configs);
+
+        verify(mFakeCarWatchdog).updateResourceOveruseConfigurations(eq(configs));
+    }
+
+    @Test
+    public void testIndirectCall_getResourceOveruseConfigurations() throws Exception {
+        ResourceOveruseConfiguration config = new ResourceOveruseConfiguration();
+        config.componentType = ComponentType.SYSTEM;
+        List<ResourceOveruseConfiguration> expected =
+                new ArrayList<>(Collections.singleton(config));
+        when(mFakeCarWatchdog.getResourceOveruseConfigurations()).thenReturn(expected);
+
+        List<ResourceOveruseConfiguration> actual =
+                mCarWatchdogDaemonHelper.getResourceOveruseConfigurations();
+
+        assertThat(actual).isEqualTo(expected);
+    }
+
+    @Test
     public void testIndirectCall_actionTakenOnResourceOveruse() throws Exception {
         List<PackageResourceOveruseAction> actions = new ArrayList<>();
 
diff --git a/tests/carservice_unit_test/src/com/android/car/audio/CarAudioContextTest.java b/tests/carservice_unit_test/src/com/android/car/audio/CarAudioContextTest.java
index be2e6dd..88ac26c 100644
--- a/tests/carservice_unit_test/src/com/android/car/audio/CarAudioContextTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/audio/CarAudioContextTest.java
@@ -27,8 +27,10 @@
 import static com.android.car.audio.CarAudioContext.INVALID;
 import static com.android.car.audio.CarAudioContext.MUSIC;
 import static com.android.car.audio.CarAudioContext.NAVIGATION;
+import static com.android.car.audio.CarAudioContext.isCriticalAudioContext;
 
 import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
 
 import static org.testng.Assert.assertThrows;
 
@@ -125,4 +127,38 @@
 
         assertThat(result).containsExactly(MUSIC, NAVIGATION, EMERGENCY);
     }
+
+    @Test
+    public void isCriticalAudioContext_forNonCritialContexts_returnsFalse() {
+        assertWithMessage("Non-critical context INVALID")
+                .that(isCriticalAudioContext(CarAudioContext.INVALID)).isFalse();
+        assertWithMessage("Non-critical context MUSIC")
+                .that(isCriticalAudioContext(CarAudioContext.MUSIC)).isFalse();
+        assertWithMessage("Non-critical context NAVIGATION")
+                .that(isCriticalAudioContext(CarAudioContext.NAVIGATION)).isFalse();
+        assertWithMessage("Non-critical context VOICE_COMMAND")
+                .that(isCriticalAudioContext(CarAudioContext.VOICE_COMMAND)).isFalse();
+        assertWithMessage("Non-critical context CALL_RING")
+                .that(isCriticalAudioContext(CarAudioContext.CALL_RING)).isFalse();
+        assertWithMessage("Non-critical context CALL")
+                .that(isCriticalAudioContext(CarAudioContext.CALL)).isFalse();
+        assertWithMessage("Non-critical context ALARM")
+                .that(isCriticalAudioContext(CarAudioContext.ALARM)).isFalse();
+        assertWithMessage("Non-critical context NOTIFICATION")
+                .that(isCriticalAudioContext(CarAudioContext.NOTIFICATION)).isFalse();
+        assertWithMessage("Non-critical context SYSTEM_SOUND")
+                .that(isCriticalAudioContext(CarAudioContext.SYSTEM_SOUND)).isFalse();
+        assertWithMessage("Non-critical context VEHICLE_STATUS")
+                .that(isCriticalAudioContext(CarAudioContext.VEHICLE_STATUS)).isFalse();
+        assertWithMessage("Non-critical context ANNOUNCEMENT")
+                .that(isCriticalAudioContext(CarAudioContext.ANNOUNCEMENT)).isFalse();
+    }
+
+    @Test
+    public void isCriticalAudioContext_forCriticalContexts_returnsTrue() {
+        assertWithMessage("Critical context EMERGENCY")
+                .that(isCriticalAudioContext(CarAudioContext.EMERGENCY)).isTrue();
+        assertWithMessage("Critical context SAFETY")
+                .that(isCriticalAudioContext(CarAudioContext.SAFETY)).isTrue();
+    }
 }
diff --git a/tests/carservice_unit_test/src/com/android/car/audio/CarAudioPowerListenerTest.java b/tests/carservice_unit_test/src/com/android/car/audio/CarAudioPowerListenerTest.java
index 1002897..130bee3 100644
--- a/tests/carservice_unit_test/src/com/android/car/audio/CarAudioPowerListenerTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/audio/CarAudioPowerListenerTest.java
@@ -67,7 +67,7 @@
 
         listener.startListeningForPolicyChanges();
 
-        verify(mMockCarAudioService).enableAudio();
+        verify(mMockCarAudioService).setAudioEnabled(true);
     }
 
     @Test
@@ -94,7 +94,7 @@
 
         listener.startListeningForPolicyChanges();
 
-        verify(mMockCarAudioService).enableAudio();
+        verify(mMockCarAudioService).setAudioEnabled(true);
     }
 
     @Test
@@ -105,7 +105,7 @@
 
         listener.startListeningForPolicyChanges();
 
-        verify(mMockCarAudioService).enableAudio();
+        verify(mMockCarAudioService).setAudioEnabled(true);
     }
 
     @Test
@@ -116,53 +116,53 @@
 
         listener.startListeningForPolicyChanges();
 
-        verify(mMockCarAudioService).disableAudio();
+        verify(mMockCarAudioService).setAudioEnabled(false);
     }
 
     @Test
     public void onPolicyChange_withPowerSwitchingToEnabled_enablesAudio() throws Exception {
         withAudioInitiallyDisabled();
         ICarPowerPolicyListener changeListener = registerAndGetChangeListener();
-        verify(mMockCarAudioService, never()).enableAudio();
+        verify(mMockCarAudioService, never()).setAudioEnabled(true);
 
         changeListener.onPolicyChanged(EMPTY_POLICY, ENABLED_POLICY);
 
-        verify(mMockCarAudioService).enableAudio();
+        verify(mMockCarAudioService).setAudioEnabled(true);
     }
 
     @Test
     public void onPolicyChange_withPowerRemainingEnabled_doesNothing() throws Exception {
         withAudioInitiallyEnabled();
         ICarPowerPolicyListener changeListener = registerAndGetChangeListener();
-        verify(mMockCarAudioService).enableAudio();
+        verify(mMockCarAudioService).setAudioEnabled(true);
 
         changeListener.onPolicyChanged(EMPTY_POLICY, ENABLED_POLICY);
 
-        verify(mMockCarAudioService).enableAudio();
-        verify(mMockCarAudioService, never()).disableAudio();
+        verify(mMockCarAudioService).setAudioEnabled(true);
+        verify(mMockCarAudioService, never()).setAudioEnabled(false);
     }
 
     @Test
     public void onPolicyChange_withPowerSwitchingToDisabled_disablesAudio() throws Exception {
         withAudioInitiallyEnabled();
         ICarPowerPolicyListener changeListener = registerAndGetChangeListener();
-        verify(mMockCarAudioService, never()).disableAudio();
+        verify(mMockCarAudioService, never()).setAudioEnabled(false);
 
         changeListener.onPolicyChanged(EMPTY_POLICY, DISABLED_POLICY);
 
-        verify(mMockCarAudioService).disableAudio();
+        verify(mMockCarAudioService).setAudioEnabled(false);
     }
 
     @Test
     public void onPolicyChange_withPowerStayingDisabled_doesNothing() throws Exception {
         withAudioInitiallyDisabled();
         ICarPowerPolicyListener changeListener = registerAndGetChangeListener();
-        verify(mMockCarAudioService).disableAudio();
+        verify(mMockCarAudioService).setAudioEnabled(false);
 
         changeListener.onPolicyChanged(EMPTY_POLICY, DISABLED_POLICY);
 
-        verify(mMockCarAudioService).disableAudio();
-        verify(mMockCarAudioService, never()).enableAudio();
+        verify(mMockCarAudioService).setAudioEnabled(false);
+        verify(mMockCarAudioService, never()).setAudioEnabled(true);
     }
 
     private void withAudioInitiallyEnabled() {
diff --git a/tests/carservice_unit_test/src/com/android/car/audio/CarVolumeGroupMutingTest.java b/tests/carservice_unit_test/src/com/android/car/audio/CarVolumeGroupMutingTest.java
index 18bbbea..5c04921 100644
--- a/tests/carservice_unit_test/src/com/android/car/audio/CarVolumeGroupMutingTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/audio/CarVolumeGroupMutingTest.java
@@ -16,8 +16,10 @@
 
 package com.android.car.audio;
 
+import static com.android.car.audio.CarAudioContext.EMERGENCY;
 import static com.android.car.audio.CarAudioContext.MUSIC;
 import static com.android.car.audio.CarAudioContext.NAVIGATION;
+import static com.android.car.audio.CarAudioContext.SAFETY;
 import static com.android.car.audio.CarAudioContext.VOICE_COMMAND;
 
 import static com.google.common.truth.Truth.assertWithMessage;
@@ -48,8 +50,10 @@
     private static final String PRIMARY_MEDIA_ADDRESS = "media";
     private static final String PRIMARY_NAVIGATION_ADDRESS = "navigation";
     private static final String PRIMARY_VOICE_ADDRESS = "voice";
-    private static final String SECONDARY_ADDRESS = "media";
-    private static final String TERTIARY_ADDRESS = "media";
+    private static final String SECONDARY_ADDRESS = "secondary";
+    private static final String TERTIARY_ADDRESS = "tertiary";
+    private static final String EMERGENCY_ADDRESS = "emergency";
+    private static final String SAFETY_ADDRESS = "safety";
     private static final int PRIMARY_ZONE_ID = CarAudioManager.PRIMARY_AUDIO_ZONE;
     private static final int SECONDARY_ZONE_ID = CarAudioManager.PRIMARY_AUDIO_ZONE + 1;
     private static final int TERTIARY_ZONE_ID = CarAudioManager.PRIMARY_AUDIO_ZONE + 2;
@@ -70,21 +74,12 @@
 
     @Before
     public void setUp() {
-        mMusicCarVolumeGroup = new VolumeGroupBuilder()
-                .addDeviceAddressAndContexts(MUSIC, PRIMARY_MEDIA_ADDRESS)
-                .build();
-        mNavigationCarVolumeGroup = new VolumeGroupBuilder()
-                .addDeviceAddressAndContexts(NAVIGATION, PRIMARY_NAVIGATION_ADDRESS)
-                .build();
-        mVoiceCarVolumeGroup = new VolumeGroupBuilder()
-                .addDeviceAddressAndContexts(VOICE_COMMAND, PRIMARY_VOICE_ADDRESS)
-                .build();
-        mSecondaryZoneVolumeGroup = new VolumeGroupBuilder()
-                .addDeviceAddressAndContexts(MUSIC, SECONDARY_ADDRESS)
-                .build();
-        mTertiaryZoneVolumeGroup = new VolumeGroupBuilder()
-                .addDeviceAddressAndContexts(MUSIC, TERTIARY_ADDRESS)
-                .build();
+        mMusicCarVolumeGroup = groupWithContextAndAddress(MUSIC, PRIMARY_MEDIA_ADDRESS);
+        mNavigationCarVolumeGroup = groupWithContextAndAddress(NAVIGATION,
+                PRIMARY_NAVIGATION_ADDRESS);
+        mVoiceCarVolumeGroup = groupWithContextAndAddress(VOICE_COMMAND, PRIMARY_VOICE_ADDRESS);
+        mSecondaryZoneVolumeGroup = groupWithContextAndAddress(MUSIC, SECONDARY_ADDRESS);
+        mTertiaryZoneVolumeGroup = groupWithContextAndAddress(MUSIC, TERTIARY_ADDRESS);
 
         mPrimaryAudioZone =
                 new TestCarAudioZoneBuilder("Primary Zone", PRIMARY_ZONE_ID)
@@ -93,20 +88,14 @@
                         .addVolumeGroup(mVoiceCarVolumeGroup)
                         .build();
 
-        mSingleDevicePrimaryZone =
-                new TestCarAudioZoneBuilder("Primary Zone", PRIMARY_ZONE_ID)
-                        .addVolumeGroup(mMusicCarVolumeGroup)
-                        .build();
+        mSingleDevicePrimaryZone = createAudioZone(mMusicCarVolumeGroup, "Primary Zone",
+                PRIMARY_ZONE_ID);
 
-        mSingleDeviceSecondaryZone =
-                new TestCarAudioZoneBuilder("Secondary Zone", SECONDARY_ZONE_ID)
-                        .addVolumeGroup(mSecondaryZoneVolumeGroup)
-                        .build();
+        mSingleDeviceSecondaryZone = createAudioZone(mSecondaryZoneVolumeGroup, "Secondary Zone",
+                SECONDARY_ZONE_ID);
 
-        mSingleDeviceTertiaryZone =
-                new TestCarAudioZoneBuilder("Tertiary Zone", TERTIARY_ZONE_ID)
-                        .addVolumeGroup(mTertiaryZoneVolumeGroup)
-                        .build();
+        mSingleDeviceTertiaryZone = createAudioZone(mTertiaryZoneVolumeGroup, "Tertiary Zone",
+                TERTIARY_ZONE_ID);
 
         when(mMockAudioControlWrapper
                 .supportsFeature(AudioControlWrapper.AUDIOCONTROL_FEATURE_AUDIO_GROUP_MUTING))
@@ -234,7 +223,7 @@
 
         carGroupMuting.carMuteChanged();
 
-        List<MutingInfo> mutingInfo =  captureMutingInfoList();
+        List<MutingInfo> mutingInfo = captureMutingInfoList();
         MutingInfo info = mutingInfo.get(mutingInfo.size() - 1);
         assertWithMessage("Device addresses to un-mute")
                 .that(info.deviceAddressesToUnmute).asList().containsExactly(
@@ -331,6 +320,49 @@
         }
     }
 
+    @Test
+    public void setRestrictMuting_isMutingRestrictedTrue_mutesNonCriticalVolumeGroups() {
+        setUpCarVolumeGroupIsMuted(mSecondaryZoneVolumeGroup, false);
+        setUpCarVolumeGroupIsMuted(mMusicCarVolumeGroup, false);
+        setUpCarVolumeGroupIsMuted(mTertiaryZoneVolumeGroup, false);
+        CarVolumeGroupMuting carGroupMuting =
+                new CarVolumeGroupMuting(getAudioZones(mSingleDevicePrimaryZone,
+                        mSingleDeviceSecondaryZone, mSingleDeviceTertiaryZone),
+                        mMockAudioControlWrapper);
+
+        carGroupMuting.setRestrictMuting(true);
+
+        for (MutingInfo info : captureMutingInfoList()) {
+            assertWithMessage("Devices addresses to mute for zone %s", info.zoneId)
+                    .that(info.deviceAddressesToMute).asList().hasSize(1);
+        }
+    }
+
+    @Test
+    public void setRestrictMuting_isMutingRestrictedTrue_leavesCriticalGroupsAsIs() {
+        setUpCarVolumeGroupIsMuted(mMusicCarVolumeGroup, false);
+        setUpCarVolumeGroupHasCriticalAudioContexts(mMusicCarVolumeGroup);
+        setUpCarVolumeGroupIsMuted(mSecondaryZoneVolumeGroup, true);
+        setUpCarVolumeGroupHasCriticalAudioContexts(mSecondaryZoneVolumeGroup);
+        CarVolumeGroupMuting carGroupMuting = new CarVolumeGroupMuting(
+                getAudioZones(mSingleDevicePrimaryZone, mSingleDeviceSecondaryZone),
+                mMockAudioControlWrapper);
+
+        carGroupMuting.setRestrictMuting(true);
+
+        for (MutingInfo info : captureMutingInfoList()) {
+            if (info.zoneId == PRIMARY_ZONE_ID) {
+                assertWithMessage("Devices addresses to unmute for zone %s", info.zoneId)
+                        .that(info.deviceAddressesToUnmute).asList().containsExactly(
+                        PRIMARY_MEDIA_ADDRESS);
+
+            } else if (info.zoneId == SECONDARY_ZONE_ID) {
+                assertWithMessage("Devices addresses to mute for zone %s", info.zoneId)
+                        .that(info.deviceAddressesToMute).asList().containsExactly(
+                                SECONDARY_ADDRESS);
+            }
+        }
+    }
 
     @Test
     public void generateMutingInfoFromZone_withNoGroupsMuted_returnsEmptyMutedList() {
@@ -338,7 +370,8 @@
         setUpCarVolumeGroupIsMuted(mNavigationCarVolumeGroup, false);
         setUpCarVolumeGroupIsMuted(mVoiceCarVolumeGroup, false);
 
-        MutingInfo info = CarVolumeGroupMuting.generateMutingInfoFromZone(mPrimaryAudioZone);
+        MutingInfo info = CarVolumeGroupMuting.generateMutingInfoFromZone(mPrimaryAudioZone,
+                /* isMutingRestricted= */ false);
 
         assertWithMessage("Device addresses to mute")
                 .that(info.deviceAddressesToMute).asList().isEmpty();
@@ -350,7 +383,8 @@
         setUpCarVolumeGroupIsMuted(mNavigationCarVolumeGroup, false);
         setUpCarVolumeGroupIsMuted(mVoiceCarVolumeGroup, false);
 
-        MutingInfo info = CarVolumeGroupMuting.generateMutingInfoFromZone(mPrimaryAudioZone);
+        MutingInfo info = CarVolumeGroupMuting.generateMutingInfoFromZone(mPrimaryAudioZone,
+                /* isMutingRestricted= */ false);
 
         assertWithMessage("Device addresses to mute")
                 .that(info.deviceAddressesToMute).asList().containsExactly(PRIMARY_MEDIA_ADDRESS);
@@ -362,7 +396,8 @@
         setUpCarVolumeGroupIsMuted(mNavigationCarVolumeGroup, true);
         setUpCarVolumeGroupIsMuted(mVoiceCarVolumeGroup, true);
 
-        MutingInfo info = CarVolumeGroupMuting.generateMutingInfoFromZone(mPrimaryAudioZone);
+        MutingInfo info = CarVolumeGroupMuting.generateMutingInfoFromZone(mPrimaryAudioZone,
+                /* isMutingRestricted= */ false);
 
         assertWithMessage("Device addresses to mute")
                 .that(info.deviceAddressesToMute).asList().containsExactly(PRIMARY_MEDIA_ADDRESS,
@@ -371,17 +406,16 @@
 
     @Test
     public void generateMutingInfoFromZone_withMutedMultiDeviceGroup_returnsAllDevicesMuted() {
-        CarAudioZone primaryZone =
-                new TestCarAudioZoneBuilder("Primary Zone", PRIMARY_ZONE_ID)
-                        .addVolumeGroup(new VolumeGroupBuilder()
-                                .addDeviceAddressAndContexts(MUSIC, PRIMARY_MEDIA_ADDRESS)
-                                .addDeviceAddressAndContexts(VOICE_COMMAND, PRIMARY_VOICE_ADDRESS)
-                                .addDeviceAddressAndContexts(NAVIGATION, PRIMARY_NAVIGATION_ADDRESS)
-                                .setIsMuted(true)
-                                .build())
-                        .build();
+        CarAudioZone primaryZone = createAudioZone(
+                new VolumeGroupBuilder()
+                        .addDeviceAddressAndContexts(MUSIC, PRIMARY_MEDIA_ADDRESS)
+                        .addDeviceAddressAndContexts(VOICE_COMMAND, PRIMARY_VOICE_ADDRESS)
+                        .addDeviceAddressAndContexts(NAVIGATION, PRIMARY_NAVIGATION_ADDRESS)
+                        .setIsMuted(true)
+                        .build(), "Primary Zone", PRIMARY_ZONE_ID);
 
-        MutingInfo info = CarVolumeGroupMuting.generateMutingInfoFromZone(primaryZone);
+        MutingInfo info = CarVolumeGroupMuting.generateMutingInfoFromZone(primaryZone,
+                /* isMutingRestricted= */ false);
 
         assertWithMessage("Device addresses to mute")
                 .that(info.deviceAddressesToMute).asList().containsExactly(PRIMARY_MEDIA_ADDRESS,
@@ -390,35 +424,95 @@
 
     @Test
     public void generateMutingInfoFromZone_withUnMutedMultiDeviceGroup_returnsAllDevicesUnMuted() {
-        CarAudioZone primaryZone =
-                new TestCarAudioZoneBuilder("Primary Zone", PRIMARY_ZONE_ID)
-                        .addVolumeGroup(new VolumeGroupBuilder()
-                                .addDeviceAddressAndContexts(MUSIC, PRIMARY_MEDIA_ADDRESS)
-                                .addDeviceAddressAndContexts(VOICE_COMMAND, PRIMARY_VOICE_ADDRESS)
-                                .addDeviceAddressAndContexts(NAVIGATION, PRIMARY_NAVIGATION_ADDRESS)
-                                .build())
-                        .build();
+        CarAudioZone primaryZone = createAudioZone(
+                new VolumeGroupBuilder()
+                        .addDeviceAddressAndContexts(MUSIC, PRIMARY_MEDIA_ADDRESS)
+                        .addDeviceAddressAndContexts(VOICE_COMMAND, PRIMARY_VOICE_ADDRESS)
+                        .addDeviceAddressAndContexts(NAVIGATION, PRIMARY_NAVIGATION_ADDRESS)
+                        .build(), "Primary Zone", PRIMARY_ZONE_ID);
 
-        MutingInfo info = CarVolumeGroupMuting.generateMutingInfoFromZone(primaryZone);
+        MutingInfo info = CarVolumeGroupMuting.generateMutingInfoFromZone(primaryZone,
+                /* isMutingRestricted= */ false);
 
         assertWithMessage("Device addresses to un-mute")
                 .that(info.deviceAddressesToUnmute).asList().containsExactly(PRIMARY_MEDIA_ADDRESS,
                 PRIMARY_NAVIGATION_ADDRESS, PRIMARY_VOICE_ADDRESS);
     }
 
+    @Test
+    public void generateMutingInfoFromZone_mutingRestricted_mutesAllNonCriticalDevices() {
+        CarAudioZone primaryZone = createAudioZone(
+                new VolumeGroupBuilder()
+                        .addDeviceAddressAndContexts(MUSIC, PRIMARY_MEDIA_ADDRESS)
+                        .addDeviceAddressAndContexts(VOICE_COMMAND, PRIMARY_VOICE_ADDRESS)
+                        .addDeviceAddressAndContexts(NAVIGATION, PRIMARY_NAVIGATION_ADDRESS)
+                        .build(), "Primary Zone", PRIMARY_ZONE_ID);
+
+        MutingInfo info = CarVolumeGroupMuting.generateMutingInfoFromZone(primaryZone,
+                /* isMutingRestricted= */ true);
+
+        assertWithMessage("Device addresses to un-mute")
+                .that(info.deviceAddressesToMute).asList().containsExactly(PRIMARY_MEDIA_ADDRESS,
+                PRIMARY_NAVIGATION_ADDRESS, PRIMARY_VOICE_ADDRESS);
+    }
+
+    @Test
+    public void generateMutingInfoFromZone_mutingRestricted_setsAllCriticalGroupsToTheirState() {
+        CarAudioZone primaryZone =
+                new TestCarAudioZoneBuilder("Primary Zone", PRIMARY_ZONE_ID)
+                        .addVolumeGroup(new VolumeGroupBuilder()
+                                .addDeviceAddressAndContexts(EMERGENCY, EMERGENCY_ADDRESS)
+                                .addDeviceAddressAndContexts(VOICE_COMMAND, PRIMARY_VOICE_ADDRESS)
+                                .build())
+                        .addVolumeGroup(new VolumeGroupBuilder()
+                                .addDeviceAddressAndContexts(SAFETY, SAFETY_ADDRESS)
+                                .addDeviceAddressAndContexts(NAVIGATION, PRIMARY_NAVIGATION_ADDRESS)
+                                .setIsMuted(true)
+                                .build()
+                        )
+                        .build();
+        setUpCarVolumeGroupHasCriticalAudioContexts(primaryZone.getVolumeGroups()[0]);
+        setUpCarVolumeGroupHasCriticalAudioContexts(primaryZone.getVolumeGroups()[1]);
+
+        MutingInfo info = CarVolumeGroupMuting.generateMutingInfoFromZone(primaryZone,
+                /* isMutingRestricted= */ true);
+
+        assertWithMessage("Device addresses to mute")
+                .that(info.deviceAddressesToMute).asList()
+                .containsExactly(SAFETY_ADDRESS, PRIMARY_NAVIGATION_ADDRESS);
+        assertWithMessage("Device addresses to un-mute")
+                .that(info.deviceAddressesToUnmute).asList()
+                .containsExactly(EMERGENCY_ADDRESS, PRIMARY_VOICE_ADDRESS);
+    }
+
+
+    private CarAudioZone createAudioZone(CarVolumeGroup volumeGroup, String name, int zoneId) {
+        return new TestCarAudioZoneBuilder(name, zoneId)
+                .addVolumeGroup(volumeGroup)
+                .build();
+    }
+
+    private CarVolumeGroup groupWithContextAndAddress(int context, String address) {
+        return new VolumeGroupBuilder().addDeviceAddressAndContexts(context, address).build();
+    }
+
     private List<MutingInfo> captureMutingInfoList() {
         ArgumentCaptor<List<MutingInfo>> captor = ArgumentCaptor.forClass(List.class);
         verify(mMockAudioControlWrapper).onDevicesToMuteChange(captor.capture());
         return captor.getValue();
     }
 
-    private void setUpCarVolumeGroupIsMuted(CarVolumeGroup musicCarVolumeGroup, boolean muted) {
-        when(musicCarVolumeGroup.isMuted()).thenReturn(muted);
+    private void setUpCarVolumeGroupIsMuted(CarVolumeGroup carVolumeGroup, boolean muted) {
+        when(carVolumeGroup.isMuted()).thenReturn(muted);
     }
 
-    private SparseArray<CarAudioZone> getAudioZones(CarAudioZone ...zones) {
+    private void setUpCarVolumeGroupHasCriticalAudioContexts(CarVolumeGroup carVolumeGroup) {
+        when(carVolumeGroup.hasCriticalAudioContexts()).thenReturn(true);
+    }
+
+    private SparseArray<CarAudioZone> getAudioZones(CarAudioZone... zones) {
         SparseArray<CarAudioZone> audioZones = new SparseArray<>();
-        for (CarAudioZone zone: zones) {
+        for (CarAudioZone zone : zones) {
             audioZones.put(zone.getId(), zone);
         }
         return audioZones;
diff --git a/tests/carservice_unit_test/src/com/android/car/audio/CarVolumeGroupUnitTest.java b/tests/carservice_unit_test/src/com/android/car/audio/CarVolumeGroupUnitTest.java
new file mode 100644
index 0000000..9e38583
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/audio/CarVolumeGroupUnitTest.java
@@ -0,0 +1,635 @@
+/*
+ * 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.audio;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.expectThrows;
+
+import android.annotation.UserIdInt;
+import android.os.UserHandle;
+import android.util.SparseBooleanArray;
+import android.util.SparseIntArray;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.util.List;
+
+@RunWith(MockitoJUnitRunner.class)
+public class CarVolumeGroupUnitTest {
+    private static final int ZONE_ID = 0;
+    private static final int GROUP_ID = 0;
+    private static final int STEP_VALUE = 2;
+    private static final int MIN_GAIN = 3;
+    private static final int MAX_GAIN = 10;
+    private static final int DEFAULT_GAIN = 5;
+    private static final int DEFAULT_GAIN_INDEX = (DEFAULT_GAIN - MIN_GAIN) / STEP_VALUE;
+    private static final int MIN_GAIN_INDEX = 0;
+    private static final int MAX_GAIN_INDEX = (MAX_GAIN - MIN_GAIN) / STEP_VALUE;
+    private static final int TEST_GAIN_INDEX = 2;
+    private static final int TEST_USER_10 = 10;
+    private static final int TEST_USER_11 = 11;
+    private static final String MEDIA_DEVICE_ADDRESS = "music";
+    private static final String NAVIGATION_DEVICE_ADDRESS = "navigation";
+    private static final String OTHER_ADDRESS = "other_address";
+
+    private CarAudioDeviceInfo mMediaDeviceInfo;
+    private CarAudioDeviceInfo mNavigationDeviceInfo;
+
+    @Mock
+    CarAudioSettings mSettingsMock;
+
+    @Before
+    public void setUp() {
+        mMediaDeviceInfo = new InfoBuilder(MEDIA_DEVICE_ADDRESS).build();
+        mNavigationDeviceInfo = new InfoBuilder(NAVIGATION_DEVICE_ADDRESS).build();
+    }
+
+    @Test
+    public void setDeviceInfoForContext_associatesDeviceAddresses() {
+        CarVolumeGroup.Builder builder = getBuilder();
+
+        builder.setDeviceInfoForContext(CarAudioContext.MUSIC, mMediaDeviceInfo);
+        builder.setDeviceInfoForContext(CarAudioContext.NAVIGATION, mNavigationDeviceInfo);
+        CarVolumeGroup carVolumeGroup = builder.build();
+
+        assertThat(carVolumeGroup.getAddresses()).containsExactly(MEDIA_DEVICE_ADDRESS,
+                NAVIGATION_DEVICE_ADDRESS);
+    }
+
+    @Test
+    public void setDeviceInfoForContext_associatesContexts() {
+        CarVolumeGroup.Builder builder = getBuilder();
+
+        builder.setDeviceInfoForContext(CarAudioContext.MUSIC, mMediaDeviceInfo);
+        builder.setDeviceInfoForContext(CarAudioContext.NAVIGATION, mNavigationDeviceInfo);
+        CarVolumeGroup carVolumeGroup = builder.build();
+
+        assertThat(carVolumeGroup.getContexts()).asList().containsExactly(CarAudioContext.MUSIC,
+                CarAudioContext.NAVIGATION);
+    }
+
+    @Test
+    public void setDeviceInfoForContext_withDifferentStepSize_throws() {
+        CarVolumeGroup.Builder builder = getBuilder();
+        builder.setDeviceInfoForContext(CarAudioContext.MUSIC, mMediaDeviceInfo);
+        CarAudioDeviceInfo differentStepValueDevice = new InfoBuilder(NAVIGATION_DEVICE_ADDRESS)
+                .setStepValue(mMediaDeviceInfo.getStepValue() + 1).build();
+
+        IllegalArgumentException thrown = expectThrows(IllegalArgumentException.class,
+                () -> builder.setDeviceInfoForContext(CarAudioContext.NAVIGATION,
+                        differentStepValueDevice));
+
+        assertThat(thrown).hasMessageThat()
+                .contains("Gain controls within one group must have same step value");
+    }
+
+    @Test
+    public void setDeviceInfoForContext_withSameContext_throws() {
+        CarVolumeGroup.Builder builder = getBuilder();
+        builder.setDeviceInfoForContext(CarAudioContext.MUSIC, mMediaDeviceInfo);
+
+        IllegalArgumentException thrown = expectThrows(IllegalArgumentException.class,
+                () -> builder.setDeviceInfoForContext(CarAudioContext.MUSIC,
+                        mNavigationDeviceInfo));
+
+        assertThat(thrown).hasMessageThat()
+                .contains("has already been set to");
+    }
+
+    @Test
+    public void setDeviceInfoForContext_withFirstCall_setsMinGain() {
+        CarVolumeGroup.Builder builder = getBuilder();
+
+        builder.setDeviceInfoForContext(CarAudioContext.MUSIC, mMediaDeviceInfo);
+
+        assertThat(builder.mMinGain).isEqualTo(mMediaDeviceInfo.getMinGain());
+    }
+
+    @Test
+    public void setDeviceInfoForContext_withFirstCall_setsMaxGain() {
+        CarVolumeGroup.Builder builder = getBuilder();
+
+        builder.setDeviceInfoForContext(CarAudioContext.MUSIC, mMediaDeviceInfo);
+
+        assertThat(builder.mMaxGain).isEqualTo(mMediaDeviceInfo.getMaxGain());
+    }
+
+    @Test
+    public void setDeviceInfoForContext_withFirstCall_setsDefaultGain() {
+        CarVolumeGroup.Builder builder = getBuilder();
+
+        builder.setDeviceInfoForContext(CarAudioContext.MUSIC, mMediaDeviceInfo);
+
+        assertThat(builder.mDefaultGain).isEqualTo(mMediaDeviceInfo.getDefaultGain());
+    }
+
+    @Test
+    public void setDeviceInfoForContext_SecondCallWithSmallerMinGain_updatesMinGain() {
+        CarVolumeGroup.Builder builder = getBuilder();
+        builder.setDeviceInfoForContext(CarAudioContext.MUSIC, mMediaDeviceInfo);
+        CarAudioDeviceInfo secondInfo = new InfoBuilder(NAVIGATION_DEVICE_ADDRESS)
+                .setMinGain(mMediaDeviceInfo.getMinGain() - 1).build();
+
+        builder.setDeviceInfoForContext(CarAudioContext.NAVIGATION, secondInfo);
+
+        assertThat(builder.mMinGain).isEqualTo(secondInfo.getMinGain());
+    }
+
+    @Test
+    public void setDeviceInfoForContext_SecondCallWithLargerMinGain_keepsFirstMinGain() {
+        CarVolumeGroup.Builder builder = getBuilder();
+        builder.setDeviceInfoForContext(CarAudioContext.MUSIC, mMediaDeviceInfo);
+        CarAudioDeviceInfo secondInfo = new InfoBuilder(NAVIGATION_DEVICE_ADDRESS)
+                .setMinGain(mMediaDeviceInfo.getMinGain() + 1).build();
+
+        builder.setDeviceInfoForContext(CarAudioContext.NAVIGATION, secondInfo);
+
+        assertThat(builder.mMinGain).isEqualTo(mMediaDeviceInfo.getMinGain());
+    }
+
+    @Test
+    public void setDeviceInfoForContext_SecondCallWithLargerMaxGain_updatesMaxGain() {
+        CarVolumeGroup.Builder builder = getBuilder();
+        builder.setDeviceInfoForContext(CarAudioContext.MUSIC, mMediaDeviceInfo);
+        CarAudioDeviceInfo secondInfo = new InfoBuilder(NAVIGATION_DEVICE_ADDRESS)
+                .setMaxGain(mMediaDeviceInfo.getMaxGain() + 1).build();
+
+        builder.setDeviceInfoForContext(CarAudioContext.NAVIGATION, secondInfo);
+
+        assertThat(builder.mMaxGain).isEqualTo(secondInfo.getMaxGain());
+    }
+
+    @Test
+    public void setDeviceInfoForContext_SecondCallWithSmallerMaxGain_keepsFirstMaxGain() {
+        CarVolumeGroup.Builder builder = getBuilder();
+        builder.setDeviceInfoForContext(CarAudioContext.MUSIC, mMediaDeviceInfo);
+        CarAudioDeviceInfo secondInfo = new InfoBuilder(NAVIGATION_DEVICE_ADDRESS)
+                .setMaxGain(mMediaDeviceInfo.getMaxGain() - 1).build();
+
+        builder.setDeviceInfoForContext(CarAudioContext.NAVIGATION, secondInfo);
+
+        assertThat(builder.mMaxGain).isEqualTo(mMediaDeviceInfo.getMaxGain());
+    }
+
+    @Test
+    public void setDeviceInfoForContext_SecondCallWithLargerDefaultGain_updatesDefaultGain() {
+        CarVolumeGroup.Builder builder = getBuilder();
+        builder.setDeviceInfoForContext(CarAudioContext.MUSIC, mMediaDeviceInfo);
+        CarAudioDeviceInfo secondInfo = new InfoBuilder(NAVIGATION_DEVICE_ADDRESS)
+                .setDefaultGain(mMediaDeviceInfo.getDefaultGain() + 1).build();
+
+        builder.setDeviceInfoForContext(CarAudioContext.NAVIGATION, secondInfo);
+
+        assertThat(builder.mDefaultGain).isEqualTo(secondInfo.getDefaultGain());
+    }
+
+    @Test
+    public void setDeviceInfoForContext_SecondCallWithSmallerDefaultGain_keepsFirstDefaultGain() {
+        CarVolumeGroup.Builder builder = getBuilder();
+        builder.setDeviceInfoForContext(CarAudioContext.MUSIC, mMediaDeviceInfo);
+        CarAudioDeviceInfo secondInfo = new InfoBuilder(NAVIGATION_DEVICE_ADDRESS)
+                .setDefaultGain(mMediaDeviceInfo.getDefaultGain() - 1).build();
+
+        builder.setDeviceInfoForContext(CarAudioContext.NAVIGATION, secondInfo);
+
+        assertThat(builder.mDefaultGain).isEqualTo(mMediaDeviceInfo.getDefaultGain());
+    }
+
+    @Test
+    public void builderBuild_withNoCallToSetDeviceInfoForContext_throws() {
+        CarVolumeGroup.Builder builder = getBuilder();
+
+        Exception e = expectThrows(IllegalArgumentException.class, builder::build);
+
+        assertThat(e).hasMessageThat().isEqualTo(
+                "setDeviceInfoForContext has to be called at least once before building");
+    }
+
+    @Test
+    public void builderBuild_withNoStoredGain_usesDefaultGain() {
+        CarVolumeGroup.Builder builder = getBuilder().setDeviceInfoForContext(CarAudioContext.MUSIC,
+                mMediaDeviceInfo);
+        when(mSettingsMock.getStoredVolumeGainIndexForUser(UserHandle.USER_CURRENT, ZONE_ID,
+                GROUP_ID)).thenReturn(-1);
+
+
+        CarVolumeGroup carVolumeGroup = builder.build();
+
+        assertThat(carVolumeGroup.getCurrentGainIndex()).isEqualTo(DEFAULT_GAIN_INDEX);
+    }
+
+    @Test
+    public void builderBuild_withTooLargeStoredGain_usesDefaultGain() {
+        CarVolumeGroup.Builder builder = getBuilder().setDeviceInfoForContext(CarAudioContext.MUSIC,
+                mMediaDeviceInfo);
+        when(mSettingsMock.getStoredVolumeGainIndexForUser(UserHandle.USER_CURRENT, ZONE_ID,
+                GROUP_ID)).thenReturn(MAX_GAIN_INDEX + 1);
+
+        CarVolumeGroup carVolumeGroup = builder.build();
+
+        assertThat(carVolumeGroup.getCurrentGainIndex()).isEqualTo(DEFAULT_GAIN_INDEX);
+    }
+
+    @Test
+    public void builderBuild_withTooSmallStoredGain_usesDefaultGain() {
+        CarVolumeGroup.Builder builder = getBuilder().setDeviceInfoForContext(CarAudioContext.MUSIC,
+                mMediaDeviceInfo);
+        when(mSettingsMock.getStoredVolumeGainIndexForUser(UserHandle.USER_CURRENT, ZONE_ID,
+                GROUP_ID)).thenReturn(MIN_GAIN_INDEX - 1);
+
+        CarVolumeGroup carVolumeGroup = builder.build();
+
+        assertThat(carVolumeGroup.getCurrentGainIndex()).isEqualTo(DEFAULT_GAIN_INDEX);
+    }
+
+    @Test
+    public void builderBuild_withValidStoredGain_usesStoredGain() {
+        CarVolumeGroup.Builder builder = getBuilder().setDeviceInfoForContext(CarAudioContext.MUSIC,
+                mMediaDeviceInfo);
+        when(mSettingsMock.getStoredVolumeGainIndexForUser(UserHandle.USER_CURRENT, ZONE_ID,
+                GROUP_ID)).thenReturn(MAX_GAIN_INDEX - 1);
+
+        CarVolumeGroup carVolumeGroup = builder.build();
+
+        assertThat(carVolumeGroup.getCurrentGainIndex()).isEqualTo(MAX_GAIN_INDEX - 1);
+    }
+
+    @Test
+    public void getAddressForContext_withSupportedContext_returnsAddress() {
+        CarVolumeGroup carVolumeGroup = getCarVolumeGroupWithMusicBound();
+
+        assertThat(carVolumeGroup.getAddressForContext(CarAudioContext.MUSIC))
+                .isEqualTo(mMediaDeviceInfo.getAddress());
+    }
+
+    @Test
+    public void getAddressForContext_withUnsupportedContext_returnsNull() {
+        CarVolumeGroup carVolumeGroup = getCarVolumeGroupWithMusicBound();
+
+        assertThat(carVolumeGroup.getAddressForContext(CarAudioContext.NAVIGATION)).isNull();
+    }
+
+    @Test
+    public void isMuted_whenDefault_returnsFalse() {
+        CarVolumeGroup carVolumeGroup = getCarVolumeGroupWithMusicBound();
+
+        assertThat(carVolumeGroup.isMuted()).isFalse();
+    }
+
+    @Test
+    public void isMuted_afterMuting_returnsTrue() {
+        CarVolumeGroup carVolumeGroup = getCarVolumeGroupWithMusicBound();
+
+        carVolumeGroup.setMute(true);
+
+        assertThat(carVolumeGroup.isMuted()).isTrue();
+    }
+
+    @Test
+    public void isMuted_afterUnMuting_returnsFalse() {
+        CarVolumeGroup carVolumeGroup = getCarVolumeGroupWithMusicBound();
+
+        carVolumeGroup.setMute(false);
+
+        assertThat(carVolumeGroup.isMuted()).isFalse();
+    }
+
+    @Test
+    public void setMute_withMutedState_storesValueToSetting() {
+        CarAudioSettings settings = new SettingsBuilder(0, 0)
+                .setMuteForUser10(false)
+                .setIsPersistVolumeGroupEnabled(true)
+                .build();
+        CarVolumeGroup carVolumeGroup = getCarVolumeGroupWithNavigationBound(settings, true);
+        carVolumeGroup.loadVolumesSettingsForUser(TEST_USER_10);
+
+        carVolumeGroup.setMute(true);
+
+        verify(settings)
+                .storeVolumeGroupMuteForUser(TEST_USER_10, 0, 0, true);
+    }
+
+    @Test
+    public void setMute_withUnMutedState_storesValueToSetting() {
+        CarAudioSettings settings = new SettingsBuilder(0, 0)
+                .setMuteForUser10(false)
+                .setIsPersistVolumeGroupEnabled(true)
+                .build();
+        CarVolumeGroup carVolumeGroup = getCarVolumeGroupWithNavigationBound(settings, true);
+        carVolumeGroup.loadVolumesSettingsForUser(TEST_USER_10);
+
+        carVolumeGroup.setMute(false);
+
+        verify(settings)
+                .storeVolumeGroupMuteForUser(TEST_USER_10, 0, 0, false);
+    }
+
+    @Test
+    public void getContextsForAddress_returnsContextsBoundToThatAddress() {
+        CarVolumeGroup carVolumeGroup = testVolumeGroupSetup();
+
+        List<Integer> contextsList = carVolumeGroup.getContextsForAddress(MEDIA_DEVICE_ADDRESS);
+
+        assertThat(contextsList).containsExactly(CarAudioContext.MUSIC,
+                CarAudioContext.CALL, CarAudioContext.CALL_RING);
+    }
+
+    @Test
+    public void getContextsForAddress_returnsEmptyArrayIfAddressNotBound() {
+        CarVolumeGroup carVolumeGroup = testVolumeGroupSetup();
+
+        List<Integer> contextsList = carVolumeGroup.getContextsForAddress(OTHER_ADDRESS);
+
+        assertThat(contextsList).isEmpty();
+    }
+
+    @Test
+    public void getCarAudioDeviceInfoForAddress_returnsExpectedDevice() {
+        CarVolumeGroup carVolumeGroup = testVolumeGroupSetup();
+
+        CarAudioDeviceInfo actualDevice = carVolumeGroup.getCarAudioDeviceInfoForAddress(
+                MEDIA_DEVICE_ADDRESS);
+
+        assertThat(actualDevice).isEqualTo(mMediaDeviceInfo);
+    }
+
+    @Test
+    public void getCarAudioDeviceInfoForAddress_returnsNullIfAddressNotBound() {
+        CarVolumeGroup carVolumeGroup = testVolumeGroupSetup();
+
+        CarAudioDeviceInfo actualDevice = carVolumeGroup.getCarAudioDeviceInfoForAddress(
+                OTHER_ADDRESS);
+
+        assertThat(actualDevice).isNull();
+    }
+
+    @Test
+    public void setCurrentGainIndex_setsGainOnAllBoundDevices() {
+        CarVolumeGroup carVolumeGroup = testVolumeGroupSetup();
+
+        carVolumeGroup.setCurrentGainIndex(TEST_GAIN_INDEX);
+
+        verify(mMediaDeviceInfo).setCurrentGain(7);
+        verify(mNavigationDeviceInfo).setCurrentGain(7);
+    }
+
+    @Test
+    public void setCurrentGainIndex_updatesCurrentGainIndex() {
+        CarVolumeGroup carVolumeGroup = testVolumeGroupSetup();
+
+        carVolumeGroup.setCurrentGainIndex(TEST_GAIN_INDEX);
+
+        assertThat(carVolumeGroup.getCurrentGainIndex()).isEqualTo(TEST_GAIN_INDEX);
+    }
+
+    @Test
+    public void setCurrentGainIndex_checksNewGainIsAboveMin() {
+        CarVolumeGroup carVolumeGroup = testVolumeGroupSetup();
+
+        IllegalArgumentException thrown = expectThrows(IllegalArgumentException.class,
+                () -> carVolumeGroup.setCurrentGainIndex(MIN_GAIN_INDEX - 1));
+        assertThat(thrown).hasMessageThat()
+                .contains("Gain out of range (" + MIN_GAIN + ":" + MAX_GAIN + ")");
+    }
+
+    @Test
+    public void setCurrentGainIndex_checksNewGainIsBelowMax() {
+        CarVolumeGroup carVolumeGroup = testVolumeGroupSetup();
+
+        IllegalArgumentException thrown = expectThrows(IllegalArgumentException.class,
+                () -> carVolumeGroup.setCurrentGainIndex(MAX_GAIN_INDEX + 1));
+        assertThat(thrown).hasMessageThat()
+                .contains("Gain out of range (" + MIN_GAIN + ":" + MAX_GAIN + ")");
+    }
+
+    @Test
+    public void setCurrentGainIndex_setsCurrentGainIndexForUser() {
+        CarAudioSettings settings = new SettingsBuilder(0, 0)
+                .setGainIndexForUser(TEST_USER_11)
+                .build();
+        CarVolumeGroup carVolumeGroup = getCarVolumeGroupWithNavigationBound(settings, false);
+        carVolumeGroup.loadVolumesSettingsForUser(TEST_USER_11);
+
+        carVolumeGroup.setCurrentGainIndex(MIN_GAIN);
+
+        verify(settings).storeVolumeGainIndexForUser(TEST_USER_11, 0, 0, MIN_GAIN);
+    }
+
+    @Test
+    public void setCurrentGainIndex_setsCurrentGainIndexForDefaultUser() {
+        CarAudioSettings settings = new SettingsBuilder(0, 0)
+                .setGainIndexForUser(UserHandle.USER_CURRENT)
+                .build();
+        CarVolumeGroup carVolumeGroup = getCarVolumeGroupWithNavigationBound(settings, false);
+
+        carVolumeGroup.setCurrentGainIndex(MIN_GAIN);
+
+        verify(settings)
+                .storeVolumeGainIndexForUser(UserHandle.USER_CURRENT, 0, 0, MIN_GAIN);
+    }
+
+    @Test
+    public void loadVolumesSettingsForUser_withMutedState_loadsMuteStateForUser() {
+        CarVolumeGroup carVolumeGroup = getVolumeGroupWithMuteAndNavBound(true, true, true);
+
+        carVolumeGroup.loadVolumesSettingsForUser(TEST_USER_10);
+
+        assertThat(carVolumeGroup.isMuted()).isTrue();
+    }
+
+    @Test
+    public void loadVolumesSettingsForUser_withDisabledUseVolumeGroupMute_doesNotLoadMute() {
+        CarVolumeGroup carVolumeGroup = getVolumeGroupWithMuteAndNavBound(true, true, false);
+
+        carVolumeGroup.loadVolumesSettingsForUser(TEST_USER_10);
+
+        assertThat(carVolumeGroup.isMuted()).isFalse();
+    }
+
+    @Test
+    public void loadVolumesSettingsForUser_withUnMutedState_loadsMuteStateForUser() {
+        CarVolumeGroup carVolumeGroup = getVolumeGroupWithMuteAndNavBound(false, true, true);
+
+        carVolumeGroup.loadVolumesSettingsForUser(TEST_USER_10);
+
+        assertThat(carVolumeGroup.isMuted()).isFalse();
+    }
+
+    @Test
+    public void loadVolumesSettingsForUser_withMutedStateAndNoPersist_returnsDefaultMuteState() {
+        CarVolumeGroup carVolumeGroup = getVolumeGroupWithMuteAndNavBound(true, false, true);
+
+        carVolumeGroup.loadVolumesSettingsForUser(TEST_USER_10);
+
+        assertThat(carVolumeGroup.isMuted()).isFalse();
+    }
+
+    @Test
+    public void hasCriticalAudioContexts_withoutCriticalContexts_returnsFalse() {
+        CarVolumeGroup carVolumeGroup = getCarVolumeGroupWithMusicBound();
+
+        assertThat(carVolumeGroup.hasCriticalAudioContexts()).isFalse();
+    }
+
+    @Test
+    public void hasCriticalAudioContexts_withCriticalContexts_returnsTrue() {
+        CarVolumeGroup carVolumeGroup = getBuilder()
+                .setDeviceInfoForContext(CarAudioContext.EMERGENCY, mMediaDeviceInfo)
+                .build();
+
+        assertThat(carVolumeGroup.hasCriticalAudioContexts()).isTrue();
+    }
+
+    private CarVolumeGroup getCarVolumeGroupWithMusicBound() {
+        return getBuilder()
+                .setDeviceInfoForContext(CarAudioContext.MUSIC, mMediaDeviceInfo)
+                .build();
+    }
+
+    private CarVolumeGroup getCarVolumeGroupWithNavigationBound(CarAudioSettings settings,
+            boolean useCarVolumeGroupMute) {
+        return new CarVolumeGroup.Builder(0, 0, settings, useCarVolumeGroupMute)
+                .setDeviceInfoForContext(CarAudioContext.NAVIGATION, mNavigationDeviceInfo)
+                .build();
+    }
+
+    CarVolumeGroup getVolumeGroupWithMuteAndNavBound(boolean isMuted, boolean persistMute,
+            boolean useCarVolumeGroupMute) {
+        CarAudioSettings settings = new SettingsBuilder(0, 0)
+                .setMuteForUser10(isMuted)
+                .setIsPersistVolumeGroupEnabled(persistMute)
+                .build();
+        return getCarVolumeGroupWithNavigationBound(settings, useCarVolumeGroupMute);
+    }
+
+    private CarVolumeGroup testVolumeGroupSetup() {
+        CarVolumeGroup.Builder builder = getBuilder();
+
+        builder.setDeviceInfoForContext(CarAudioContext.MUSIC, mMediaDeviceInfo);
+        builder.setDeviceInfoForContext(CarAudioContext.CALL, mMediaDeviceInfo);
+        builder.setDeviceInfoForContext(CarAudioContext.CALL_RING, mMediaDeviceInfo);
+
+        builder.setDeviceInfoForContext(CarAudioContext.NAVIGATION, mNavigationDeviceInfo);
+        builder.setDeviceInfoForContext(CarAudioContext.ALARM, mNavigationDeviceInfo);
+        builder.setDeviceInfoForContext(CarAudioContext.NOTIFICATION, mNavigationDeviceInfo);
+
+        return builder.build();
+    }
+
+    CarVolumeGroup.Builder getBuilder() {
+        return new CarVolumeGroup.Builder(ZONE_ID, GROUP_ID, mSettingsMock, true);
+    }
+
+    private static final class SettingsBuilder {
+        private final SparseIntArray mStoredGainIndexes = new SparseIntArray();
+        private final SparseBooleanArray mStoreMuteStates = new SparseBooleanArray();
+        private final int mZoneId;
+        private final int mGroupId;
+
+        private boolean mPersistMute;
+
+        SettingsBuilder(int zoneId, int groupId) {
+            mZoneId = zoneId;
+            mGroupId = groupId;
+        }
+
+        SettingsBuilder setGainIndexForUser(@UserIdInt int userId) {
+            mStoredGainIndexes.put(userId, TEST_GAIN_INDEX);
+            return this;
+        }
+
+        SettingsBuilder setMuteForUser10(boolean mute) {
+            mStoreMuteStates.put(CarVolumeGroupUnitTest.TEST_USER_10, mute);
+            return this;
+        }
+
+        SettingsBuilder setIsPersistVolumeGroupEnabled(boolean persistMute) {
+            mPersistMute = persistMute;
+            return this;
+        }
+
+        CarAudioSettings build() {
+            CarAudioSettings settingsMock = Mockito.mock(CarAudioSettings.class);
+            for (int storeIndex = 0; storeIndex < mStoredGainIndexes.size(); storeIndex++) {
+                int gainUserId = mStoredGainIndexes.keyAt(storeIndex);
+                when(settingsMock
+                        .getStoredVolumeGainIndexForUser(gainUserId, mZoneId,
+                                mGroupId)).thenReturn(
+                        mStoredGainIndexes.get(gainUserId, DEFAULT_GAIN));
+            }
+            for (int muteIndex = 0; muteIndex < mStoreMuteStates.size(); muteIndex++) {
+                int muteUserId = mStoreMuteStates.keyAt(muteIndex);
+                when(settingsMock.getVolumeGroupMuteForUser(muteUserId, mZoneId, mGroupId))
+                        .thenReturn(mStoreMuteStates.get(muteUserId, false));
+                when(settingsMock.isPersistVolumeGroupMuteEnabled(muteUserId))
+                        .thenReturn(mPersistMute);
+            }
+            return settingsMock;
+        }
+    }
+
+    private static final class InfoBuilder {
+        private final String mAddress;
+
+        private int mStepValue = STEP_VALUE;
+        private int mDefaultGain = DEFAULT_GAIN;
+        private int mMinGain = MIN_GAIN;
+        private int mMaxGain = MAX_GAIN;
+
+        InfoBuilder(String address) {
+            mAddress = address;
+        }
+
+        InfoBuilder setStepValue(int stepValue) {
+            mStepValue = stepValue;
+            return this;
+        }
+
+        InfoBuilder setDefaultGain(int defaultGain) {
+            mDefaultGain = defaultGain;
+            return this;
+        }
+
+        InfoBuilder setMinGain(int minGain) {
+            mMinGain = minGain;
+            return this;
+        }
+
+        InfoBuilder setMaxGain(int maxGain) {
+            mMaxGain = maxGain;
+            return this;
+        }
+
+        CarAudioDeviceInfo build() {
+            CarAudioDeviceInfo infoMock = Mockito.mock(CarAudioDeviceInfo.class);
+            when(infoMock.getStepValue()).thenReturn(mStepValue);
+            when(infoMock.getDefaultGain()).thenReturn(mDefaultGain);
+            when(infoMock.getMaxGain()).thenReturn(mMaxGain);
+            when(infoMock.getMinGain()).thenReturn(mMinGain);
+            when(infoMock.getAddress()).thenReturn(mAddress);
+            return infoMock;
+        }
+    }
+}
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 fa4c30f..cdb3452 100644
--- a/tests/carservice_unit_test/src/com/android/car/watchdog/CarWatchdogServiceUnitTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/watchdog/CarWatchdogServiceUnitTest.java
@@ -18,6 +18,7 @@
 
 import static android.automotive.watchdog.internal.ResourceOveruseActionType.KILLED;
 import static android.automotive.watchdog.internal.ResourceOveruseActionType.NOT_KILLED;
+import static android.car.test.mocks.AndroidMockitoHelper.mockUmGetAliveUsers;
 import static android.car.test.mocks.AndroidMockitoHelper.mockUmGetAllUsers;
 import static android.car.watchdog.CarWatchdogManager.TIMEOUT_CRITICAL;
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
@@ -51,13 +52,21 @@
 import android.automotive.watchdog.internal.PackageIdentifier;
 import android.automotive.watchdog.internal.PackageInfo;
 import android.automotive.watchdog.internal.PackageIoOveruseStats;
+import android.automotive.watchdog.internal.PackageMetadata;
 import android.automotive.watchdog.internal.PackageResourceOveruseAction;
+import android.automotive.watchdog.internal.PerStateIoOveruseThreshold;
+import android.automotive.watchdog.internal.ResourceSpecificConfiguration;
 import android.automotive.watchdog.internal.UidType;
 import android.car.test.mocks.AbstractExtendedMockitoTestCase;
 import android.car.watchdog.CarWatchdogManager;
 import android.car.watchdog.ICarWatchdogServiceCallback;
 import android.car.watchdog.IResourceOveruseListener;
+import android.car.watchdog.IoOveruseAlertThreshold;
+import android.car.watchdog.IoOveruseConfiguration;
 import android.car.watchdog.IoOveruseStats;
+import android.car.watchdog.PackageKillableState;
+import android.car.watchdog.PerStateBytes;
+import android.car.watchdog.ResourceOveruseConfiguration;
 import android.car.watchdog.ResourceOveruseStats;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
@@ -66,13 +75,16 @@
 import android.content.pm.UserInfo;
 import android.os.Binder;
 import android.os.IBinder;
+import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.SparseArray;
-import android.util.SparseBooleanArray;
+
+import com.android.internal.util.function.TriConsumer;
 
 import com.google.common.truth.Correspondence;
 
@@ -89,6 +101,8 @@
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
 
 /**
  * <p>This class contains unit tests for the {@link CarWatchdogService}.
@@ -102,9 +116,9 @@
 
     @Mock private Context mMockContext;
     @Mock private PackageManager mMockPackageManager;
-    @Mock private UserManager mUserManager;
-    @Mock private IBinder mBinder;
-    @Mock private ICarWatchdog mCarWatchdogDaemon;
+    @Mock private UserManager mMockUserManager;
+    @Mock private IBinder mMockBinder;
+    @Mock private ICarWatchdog mMockCarWatchdogDaemon;
 
     private CarWatchdogService mCarWatchdogService;
     private ICarWatchdogServiceForSystem mWatchdogServiceForSystemImpl;
@@ -135,7 +149,7 @@
     @Test
     public void testCarWatchdogServiceHealthCheck() throws Exception {
         mWatchdogServiceForSystemImpl.checkIfAlive(123456, TIMEOUT_CRITICAL);
-        verify(mCarWatchdogDaemon,
+        verify(mMockCarWatchdogDaemon,
                 timeout(MAX_WAIT_TIME_MS)).tellCarWatchdogServiceAlive(
                         eq(mWatchdogServiceForSystemImpl), any(int[].class), eq(123456));
     }
@@ -178,6 +192,204 @@
     }
 
     @Test
+    public void testGetResourceOveruseStats() throws Exception {
+        SparseArray<String> packageNamesByUid = new SparseArray<>();
+        packageNamesByUid.put(Binder.getCallingUid(), mMockContext.getPackageName());
+        injectUidToPackageNameMapping(packageNamesByUid);
+
+        List<PackageIoOveruseStats> packageIoOveruseStats = new ArrayList<>(
+                Collections.singletonList(
+                    constructPackageIoOveruseStats(
+                            Binder.getCallingUid(), /* shouldNotify= */false,
+                            constructInternalIoOveruseStats(/* killableOnOveruse= */false,
+                                    /* remainingWriteBytes= */constructPerStateBytes(20, 20, 20),
+                                    /* writtenBytes= */constructPerStateBytes(100, 200, 300),
+                                    /* totalOveruses= */2)))
+        );
+        mWatchdogServiceForSystemImpl.latestIoOveruseStats(packageIoOveruseStats);
+
+        ResourceOveruseStats expectedStats =
+                constructResourceOveruseStats(packageNamesByUid.keyAt(0),
+                        packageNamesByUid.valueAt(0), packageIoOveruseStats.get(0).ioOveruseStats);
+
+        ResourceOveruseStats actualStats = mCarWatchdogService.getResourceOveruseStats(
+                CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO,
+                CarWatchdogManager.STATS_PERIOD_CURRENT_DAY);
+
+        assertWithMessage("Expected: " + expectedStats.toString() + "\nActual: "
+                + actualStats.toString())
+                .that(ResourceOveruseStatsSubject.isEquals(actualStats, expectedStats)).isTrue();
+    }
+
+    @Test
+    public void testFailsGetResourceOveruseStatsWithInvalidArgs() throws Exception {
+        assertThrows(IllegalArgumentException.class,
+                () -> mCarWatchdogService.getResourceOveruseStats(/* resourceOveruseFlag= */0,
+                        CarWatchdogManager.STATS_PERIOD_CURRENT_DAY));
+
+        assertThrows(IllegalArgumentException.class,
+                () -> mCarWatchdogService.getResourceOveruseStats(
+                        CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO, /* maxStatsPeriod= */0));
+    }
+
+    @Test
+    public void testGetAllResourceOveruseStatsWithNoMinimum() throws Exception {
+        SparseArray<String> packageNamesByUid = new SparseArray<>();
+        packageNamesByUid.put(1103456, "third_party_package");
+        packageNamesByUid.put(1201278, "vendor_package.critical");
+        injectUidToPackageNameMapping(packageNamesByUid);
+
+        List<PackageIoOveruseStats> packageIoOveruseStats = new ArrayList<>(Arrays.asList(
+                constructPackageIoOveruseStats(packageNamesByUid.keyAt(0), /* shouldNotify= */false,
+                        constructInternalIoOveruseStats(/* killableOnOveruse= */true,
+                                /* remainingWriteBytes= */constructPerStateBytes(20, 20, 20),
+                                /* writtenBytes= */constructPerStateBytes(100, 200, 300),
+                                /* totalOveruses= */2)),
+                constructPackageIoOveruseStats(packageNamesByUid.keyAt(1), /* shouldNotify= */false,
+                        constructInternalIoOveruseStats(/* killableOnOveruse= */false,
+                                /* remainingWriteBytes= */constructPerStateBytes(450, 120, 340),
+                                /* writtenBytes= */constructPerStateBytes(5000, 6000, 9000),
+                                /* totalOveruses= */2))));
+        mWatchdogServiceForSystemImpl.latestIoOveruseStats(packageIoOveruseStats);
+
+        List<ResourceOveruseStats> expectedStats = new ArrayList<>(Arrays.asList(
+                constructResourceOveruseStats(packageNamesByUid.keyAt(0),
+                        packageNamesByUid.valueAt(0), packageIoOveruseStats.get(0).ioOveruseStats),
+                constructResourceOveruseStats(packageNamesByUid.keyAt(1),
+                        packageNamesByUid.valueAt(1), packageIoOveruseStats.get(1).ioOveruseStats))
+        );
+
+        List<ResourceOveruseStats> actualStats = mCarWatchdogService.getAllResourceOveruseStats(
+                CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO, /* minimumStatsFlag= */0,
+                CarWatchdogManager.STATS_PERIOD_CURRENT_DAY);
+
+        ResourceOveruseStatsSubject.assertThat(actualStats)
+                .containsExactlyElementsIn(expectedStats);
+    }
+
+    @Test
+    public void testFailsGetAllResourceOveruseStatsWithInvalidArgs() throws Exception {
+        assertThrows(IllegalArgumentException.class,
+                () -> mCarWatchdogService.getAllResourceOveruseStats(0, /* minimumStatsFlag= */0,
+                        CarWatchdogManager.STATS_PERIOD_CURRENT_DAY));
+
+        assertThrows(IllegalArgumentException.class,
+                () -> mCarWatchdogService.getAllResourceOveruseStats(
+                        CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO,
+                        CarWatchdogManager.FLAG_MINIMUM_STATS_IO_1_MB
+                                | CarWatchdogManager.FLAG_MINIMUM_STATS_IO_100_MB,
+                        CarWatchdogManager.STATS_PERIOD_CURRENT_DAY));
+
+        assertThrows(IllegalArgumentException.class,
+                () -> mCarWatchdogService.getAllResourceOveruseStats(
+                        CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO, /* minimumStatsFlag= */1 << 5,
+                        CarWatchdogManager.STATS_PERIOD_CURRENT_DAY));
+
+        assertThrows(IllegalArgumentException.class,
+                () -> mCarWatchdogService.getAllResourceOveruseStats(
+                        CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO, /* minimumStatsFlag= */0,
+                        /* maxStatsPeriod= */0));
+    }
+
+    @Test
+    public void testGetAllResourceOveruseStatsWithMinimum() throws Exception {
+        SparseArray<String> packageNamesByUid = new SparseArray<>();
+        packageNamesByUid.put(1103456, "third_party_package");
+        packageNamesByUid.put(1201278, "vendor_package.critical");
+        injectUidToPackageNameMapping(packageNamesByUid);
+
+        List<PackageIoOveruseStats> packageIoOveruseStats = new ArrayList<>(Arrays.asList(
+                constructPackageIoOveruseStats(packageNamesByUid.keyAt(0), /* shouldNotify= */false,
+                        constructInternalIoOveruseStats(/* killableOnOveruse= */true,
+                                /* remainingWriteBytes= */constructPerStateBytes(20, 20, 20),
+                                /* writtenBytes= */constructPerStateBytes(100, 200, 300),
+                                /* totalOveruses= */2)),
+                constructPackageIoOveruseStats(packageNamesByUid.keyAt(1), /* shouldNotify= */false,
+                        constructInternalIoOveruseStats(/* killableOnOveruse= */false,
+                                /* remainingWriteBytes= */constructPerStateBytes(450, 120, 340),
+                                /* writtenBytes= */constructPerStateBytes(7000000, 6000, 9000),
+                                /* totalOveruses= */2))));
+        mWatchdogServiceForSystemImpl.latestIoOveruseStats(packageIoOveruseStats);
+
+        List<ResourceOveruseStats> expectedStats = new ArrayList<>(Arrays.asList(
+                constructResourceOveruseStats(packageNamesByUid.keyAt(1),
+                        packageNamesByUid.valueAt(1), packageIoOveruseStats.get(1).ioOveruseStats))
+        );
+
+        List<ResourceOveruseStats> actualStats = mCarWatchdogService.getAllResourceOveruseStats(
+                CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO,
+                CarWatchdogManager.FLAG_MINIMUM_STATS_IO_1_MB,
+                CarWatchdogManager.STATS_PERIOD_CURRENT_DAY);
+
+        ResourceOveruseStatsSubject.assertThat(actualStats)
+                .containsExactlyElementsIn(expectedStats);
+    }
+
+    @Test
+    public void testGetResourceOveruseStatsForUserPackage() throws Exception {
+        SparseArray<String> packageNamesByUid = new SparseArray<>();
+        packageNamesByUid.put(1103456, "third_party_package");
+        packageNamesByUid.put(1201278, "vendor_package.critical");
+        injectUidToPackageNameMapping(packageNamesByUid);
+
+        List<PackageIoOveruseStats> packageIoOveruseStats = new ArrayList<>(Arrays.asList(
+                constructPackageIoOveruseStats(packageNamesByUid.keyAt(0), /* shouldNotify= */false,
+                        constructInternalIoOveruseStats(/* killableOnOveruse= */true,
+                                /* remainingWriteBytes= */constructPerStateBytes(20, 20, 20),
+                                /* writtenBytes= */constructPerStateBytes(100, 200, 300),
+                                /* totalOveruses= */2)),
+                constructPackageIoOveruseStats(packageNamesByUid.keyAt(1), /* shouldNotify= */false,
+                        constructInternalIoOveruseStats(/* killableOnOveruse= */false,
+                                /* remainingWriteBytes= */constructPerStateBytes(450, 120, 340),
+                                /* writtenBytes= */constructPerStateBytes(500, 600, 900),
+                                /* totalOveruses= */2))));
+        mWatchdogServiceForSystemImpl.latestIoOveruseStats(packageIoOveruseStats);
+
+        ResourceOveruseStats expectedStats =
+                constructResourceOveruseStats(packageNamesByUid.keyAt(1),
+                        packageNamesByUid.valueAt(1), packageIoOveruseStats.get(1).ioOveruseStats);
+
+        ResourceOveruseStats actualStats =
+                mCarWatchdogService.getResourceOveruseStatsForUserPackage(
+                        "vendor_package.critical", new UserHandle(12),
+                        CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO,
+                        CarWatchdogManager.STATS_PERIOD_CURRENT_DAY);
+
+        assertWithMessage("Expected: " + expectedStats.toString() + "\nActual: "
+                + actualStats.toString())
+                .that(ResourceOveruseStatsSubject.isEquals(actualStats, expectedStats)).isTrue();
+    }
+
+    @Test
+    public void testFailsGetResourceOveruseStatsForUserPackageWithInvalidArgs() throws Exception {
+        assertThrows(NullPointerException.class,
+                () -> mCarWatchdogService.getResourceOveruseStatsForUserPackage(
+                        /* packageName= */null, new UserHandle(10),
+                        CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO,
+                        CarWatchdogManager.STATS_PERIOD_CURRENT_DAY));
+
+        assertThrows(NullPointerException.class,
+                () -> mCarWatchdogService.getResourceOveruseStatsForUserPackage("some.package",
+                        /* userHandle= */null, CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO,
+                CarWatchdogManager.STATS_PERIOD_CURRENT_DAY));
+
+        assertThrows(IllegalArgumentException.class,
+                () -> mCarWatchdogService.getResourceOveruseStatsForUserPackage("some.package",
+                        UserHandle.ALL, CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO,
+                        CarWatchdogManager.STATS_PERIOD_CURRENT_DAY));
+
+        assertThrows(IllegalArgumentException.class,
+                () -> mCarWatchdogService.getResourceOveruseStatsForUserPackage("some.package",
+                        new UserHandle(10), /* resourceOveruseFlag= */0,
+                        CarWatchdogManager.STATS_PERIOD_CURRENT_DAY));
+
+        assertThrows(IllegalArgumentException.class,
+                () -> mCarWatchdogService.getResourceOveruseStatsForUserPackage("some.package",
+                        new UserHandle(10), CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO,
+                        /* maxStatsPeriod= */0));
+    }
+
+    @Test
     public void testAddResourceOveruseListenerThrowsWithInvalidFlag() throws Exception {
         IResourceOveruseListener mockListener = createMockResourceOveruseListener();
         assertThrows(IllegalArgumentException.class, () -> {
@@ -203,8 +415,9 @@
                 Collections.singletonList(constructPackageIoOveruseStats(
                         callingUid, /* shouldNotify= */true,
                         constructInternalIoOveruseStats(/* killableOnOveruse= */true,
-                                constructPerStateBytes(20, 20, 20),
-                                constructPerStateBytes(100, 200, 300), /* totalOveruses= */2))));
+                                /* remainingWriteBytes= */constructPerStateBytes(20, 20, 20),
+                                /* writtenBytes= */constructPerStateBytes(100, 200, 300),
+                                /* totalOveruses= */2))));
 
         mWatchdogServiceForSystemImpl.latestIoOveruseStats(packageIoOveruseStats);
 
@@ -246,8 +459,9 @@
                 Collections.singletonList(constructPackageIoOveruseStats(
                         callingUid, /* shouldNotify= */true,
                         constructInternalIoOveruseStats(/* killableOnOveruse= */true,
-                                constructPerStateBytes(20, 20, 20),
-                                constructPerStateBytes(100, 200, 300), /* totalOveruses= */2))));
+                                /* remainingWriteBytes= */constructPerStateBytes(20, 20, 20),
+                                /* writtenBytes= */constructPerStateBytes(100, 200, 300),
+                                /* totalOveruses= */2))));
 
         mWatchdogServiceForSystemImpl.latestIoOveruseStats(packageIoOveruseStats);
 
@@ -264,6 +478,308 @@
     }
 
     @Test
+    public void testSetKillablePackageAsUserWithPackageStats() throws Exception {
+        mockUmGetAliveUsers(mMockUserManager, 11, 12);
+        injectPackageInfos(new ArrayList<>(Arrays.asList("third_party_package",
+                "vendor_package.critical")));
+
+        SparseArray<String> packageNamesByUid = new SparseArray<>();
+        packageNamesByUid.put(1103456, "third_party_package");
+        packageNamesByUid.put(1101278, "vendor_package.critical");
+        injectIoOveruseStatsForPackages(packageNamesByUid,
+                new ArraySet<>(Collections.singletonList("third_party_package")));
+
+        UserHandle userHandle = new UserHandle(11);
+
+        mCarWatchdogService.setKillablePackageAsUser("third_party_package", userHandle,
+                /* isKillable= */ true);
+        assertThrows(IllegalArgumentException.class,
+                () -> mCarWatchdogService.setKillablePackageAsUser("vendor_package.critical",
+                        userHandle, /* isKillable= */ true));
+
+        PackageKillableStateSubject.assertThat(
+                mCarWatchdogService.getPackageKillableStatesAsUser(userHandle)).containsExactly(
+                        new PackageKillableState("third_party_package", 11,
+                                PackageKillableState.KILLABLE_STATE_YES),
+                        new PackageKillableState("vendor_package.critical", 11,
+                                PackageKillableState.KILLABLE_STATE_NEVER));
+
+        mCarWatchdogService.setKillablePackageAsUser("third_party_package", userHandle,
+                /* isKillable= */ false);
+        assertThrows(IllegalArgumentException.class,
+                () -> mCarWatchdogService.setKillablePackageAsUser("vendor_package.critical",
+                        userHandle, /* isKillable= */ false));
+
+        PackageKillableStateSubject.assertThat(
+                mCarWatchdogService.getPackageKillableStatesAsUser(userHandle)).containsExactly(
+                new PackageKillableState("third_party_package", 11,
+                        PackageKillableState.KILLABLE_STATE_NO),
+                new PackageKillableState("vendor_package.critical", 11,
+                        PackageKillableState.KILLABLE_STATE_NEVER));
+    }
+
+    @Test
+    public void testSetKillablePackageAsUserWithNoPackageStats() throws Exception {
+        mockUmGetAliveUsers(mMockUserManager, 11, 12);
+        injectPackageInfos(new ArrayList<>(Arrays.asList("third_party_package",
+                "vendor_package.critical")));
+
+        UserHandle userHandle = new UserHandle(11);
+        mCarWatchdogService.setKillablePackageAsUser("third_party_package", userHandle,
+                /* isKillable= */ true);
+        mCarWatchdogService.setKillablePackageAsUser("vendor_package.critical",
+                        userHandle, /* isKillable= */ true);
+
+        PackageKillableStateSubject.assertThat(
+                mCarWatchdogService.getPackageKillableStatesAsUser(userHandle)).containsExactly(
+                new PackageKillableState("third_party_package", 11,
+                        PackageKillableState.KILLABLE_STATE_YES),
+                new PackageKillableState("vendor_package.critical", 11,
+                        PackageKillableState.KILLABLE_STATE_NEVER));
+
+        mCarWatchdogService.setKillablePackageAsUser("third_party_package", userHandle,
+                /* isKillable= */ false);
+        assertThrows(IllegalArgumentException.class,
+                () -> mCarWatchdogService.setKillablePackageAsUser("vendor_package.critical",
+                        userHandle, /* isKillable= */ false));
+
+        PackageKillableStateSubject.assertThat(
+                mCarWatchdogService.getPackageKillableStatesAsUser(userHandle)).containsExactly(
+                new PackageKillableState("third_party_package", 11,
+                        PackageKillableState.KILLABLE_STATE_NO),
+                new PackageKillableState("vendor_package.critical", 11,
+                        PackageKillableState.KILLABLE_STATE_NEVER));
+    }
+
+    @Test
+    public void testSetKillablePackageAsUserForAllUsersWithPackageStats() throws Exception {
+        mockUmGetAliveUsers(mMockUserManager, 11, 12);
+        injectPackageInfos(new ArrayList<>(Arrays.asList("third_party_package",
+                "vendor_package.critical")));
+
+        SparseArray<String> packageNamesByUid = new SparseArray<>();
+        packageNamesByUid.put(1103456, "third_party_package");
+        packageNamesByUid.put(1101278, "vendor_package.critical");
+        injectIoOveruseStatsForPackages(packageNamesByUid,
+                new ArraySet<>(Collections.singletonList("third_party_package")));
+
+        mCarWatchdogService.setKillablePackageAsUser("third_party_package", UserHandle.ALL,
+                /* isKillable= */ true);
+        assertThrows(IllegalArgumentException.class,
+                () -> mCarWatchdogService.setKillablePackageAsUser("vendor_package.critical",
+                        UserHandle.ALL, /* isKillable= */ true));
+
+        PackageKillableStateSubject.assertThat(
+                mCarWatchdogService.getPackageKillableStatesAsUser(UserHandle.ALL)).containsExactly(
+                new PackageKillableState("third_party_package", 11,
+                        PackageKillableState.KILLABLE_STATE_YES),
+                new PackageKillableState("vendor_package.critical", 11,
+                        PackageKillableState.KILLABLE_STATE_NEVER),
+                new PackageKillableState("third_party_package", 12,
+                        PackageKillableState.KILLABLE_STATE_YES),
+                new PackageKillableState("vendor_package.critical", 12,
+                        PackageKillableState.KILLABLE_STATE_NEVER));
+
+        mCarWatchdogService.setKillablePackageAsUser("third_party_package", UserHandle.ALL,
+                /* isKillable= */ false);
+        assertThrows(IllegalArgumentException.class,
+                () -> mCarWatchdogService.setKillablePackageAsUser("vendor_package.critical",
+                        UserHandle.ALL, /* isKillable= */ false));
+
+        PackageKillableStateSubject.assertThat(
+                mCarWatchdogService.getPackageKillableStatesAsUser(UserHandle.ALL)).containsExactly(
+                new PackageKillableState("third_party_package", 11,
+                        PackageKillableState.KILLABLE_STATE_NO),
+                new PackageKillableState("vendor_package.critical", 11,
+                        PackageKillableState.KILLABLE_STATE_NEVER),
+                new PackageKillableState("third_party_package", 12,
+                        PackageKillableState.KILLABLE_STATE_NO),
+                new PackageKillableState("vendor_package.critical", 12,
+                        PackageKillableState.KILLABLE_STATE_NEVER));
+    }
+
+    @Test
+    public void testSetKillablePackageAsUserForAllUsersWithNoPackageStats() throws Exception {
+        mockUmGetAliveUsers(mMockUserManager, 11, 12);
+        injectPackageInfos(new ArrayList<>(Arrays.asList("third_party_package",
+                "vendor_package.critical")));
+
+        mCarWatchdogService.setKillablePackageAsUser("third_party_package", UserHandle.ALL,
+                /* isKillable= */ true);
+        mCarWatchdogService.setKillablePackageAsUser("vendor_package.critical",
+                        UserHandle.ALL, /* isKillable= */ true);
+
+        PackageKillableStateSubject.assertThat(
+                mCarWatchdogService.getPackageKillableStatesAsUser(UserHandle.ALL)).containsExactly(
+                new PackageKillableState("third_party_package", 11,
+                        PackageKillableState.KILLABLE_STATE_YES),
+                new PackageKillableState("vendor_package.critical", 11,
+                        PackageKillableState.KILLABLE_STATE_NEVER),
+                new PackageKillableState("third_party_package", 12,
+                        PackageKillableState.KILLABLE_STATE_YES),
+                new PackageKillableState("vendor_package.critical", 12,
+                        PackageKillableState.KILLABLE_STATE_NEVER));
+
+        mCarWatchdogService.setKillablePackageAsUser("third_party_package", UserHandle.ALL,
+                /* isKillable= */ false);
+        assertThrows(IllegalArgumentException.class,
+                () -> mCarWatchdogService.setKillablePackageAsUser("vendor_package.critical",
+                        UserHandle.ALL, /* isKillable= */ false));
+
+        PackageKillableStateSubject.assertThat(
+                mCarWatchdogService.getPackageKillableStatesAsUser(UserHandle.ALL)).containsExactly(
+                new PackageKillableState("third_party_package", 11,
+                        PackageKillableState.KILLABLE_STATE_NO),
+                new PackageKillableState("vendor_package.critical", 11,
+                        PackageKillableState.KILLABLE_STATE_NEVER),
+                new PackageKillableState("third_party_package", 12,
+                        PackageKillableState.KILLABLE_STATE_NO),
+                new PackageKillableState("vendor_package.critical", 12,
+                        PackageKillableState.KILLABLE_STATE_NEVER));
+    }
+
+    @Test
+    public void testGetPackageKillableStatesAsUser() throws Exception {
+        mockUmGetAliveUsers(mMockUserManager, 11, 12);
+        injectPackageInfos(new ArrayList<>(Arrays.asList("third_party_package",
+                "vendor_package.critical")));
+        PackageKillableStateSubject.assertThat(
+                mCarWatchdogService.getPackageKillableStatesAsUser(new UserHandle(11)))
+                .containsExactly(
+                        new PackageKillableState("third_party_package", 11,
+                                PackageKillableState.KILLABLE_STATE_YES),
+                        new PackageKillableState("vendor_package.critical", 11,
+                                PackageKillableState.KILLABLE_STATE_NEVER));
+    }
+
+    @Test
+    public void testGetPackageKillableStatesAsUserForAllUsers() throws Exception {
+        mockUmGetAliveUsers(mMockUserManager, 11, 12);
+        injectPackageInfos(new ArrayList<>(Arrays.asList("third_party_package",
+                "vendor_package.critical")));
+        PackageKillableStateSubject.assertThat(
+                mCarWatchdogService.getPackageKillableStatesAsUser(UserHandle.ALL)).containsExactly(
+                new PackageKillableState("third_party_package", 11,
+                        PackageKillableState.KILLABLE_STATE_YES),
+                new PackageKillableState("vendor_package.critical", 11,
+                        PackageKillableState.KILLABLE_STATE_NEVER),
+                new PackageKillableState("third_party_package", 12,
+                        PackageKillableState.KILLABLE_STATE_YES),
+                new PackageKillableState("vendor_package.critical", 12,
+                        PackageKillableState.KILLABLE_STATE_NEVER));
+    }
+
+    @Test
+    public void testSetResourceOveruseConfigurations() throws Exception {
+        List<ResourceOveruseConfiguration> resourceOveruseConfigs = new ArrayList<>(Arrays.asList(
+                sampleResourceOveruseConfigurationBuilder(ComponentType.SYSTEM,
+                        sampleIoOveruseConfigurationBuilder(ComponentType.SYSTEM).build()).build(),
+                sampleResourceOveruseConfigurationBuilder(ComponentType.VENDOR,
+                        sampleIoOveruseConfigurationBuilder(ComponentType.VENDOR).build()).build(),
+                sampleResourceOveruseConfigurationBuilder(ComponentType.THIRD_PARTY,
+                        sampleIoOveruseConfigurationBuilder(ComponentType.THIRD_PARTY).build())
+                        .build()));
+
+        mCarWatchdogService.setResourceOveruseConfigurations(resourceOveruseConfigs,
+                CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO);
+
+        List<android.automotive.watchdog.internal.ResourceOveruseConfiguration>
+                actualConfigs = captureOnSetResourceOveruseConfigurations();
+
+        List<android.automotive.watchdog.internal.ResourceOveruseConfiguration>
+                expectedConfigs = new ArrayList<>(Arrays.asList(
+                sampleInternalResourceOveruseConfiguration(ComponentType.SYSTEM,
+                        sampleInternalIoOveruseConfiguration(ComponentType.SYSTEM)),
+                sampleInternalResourceOveruseConfiguration(ComponentType.VENDOR,
+                        sampleInternalIoOveruseConfiguration(ComponentType.VENDOR)),
+                sampleInternalResourceOveruseConfiguration(ComponentType.THIRD_PARTY,
+                        sampleInternalIoOveruseConfiguration(ComponentType.THIRD_PARTY))));
+
+        InternalResourceOveruseConfigurationSubject.assertThat(actualConfigs)
+                .containsExactlyElementsIn(expectedConfigs);
+    }
+
+    @Test
+    public void testFailsSetResourceOveruseConfigurationsOnInvalidArgs() throws Exception {
+        assertThrows(NullPointerException.class,
+                () -> mCarWatchdogService.setResourceOveruseConfigurations(null,
+                        CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO));
+
+        assertThrows(IllegalArgumentException.class,
+                () -> mCarWatchdogService.setResourceOveruseConfigurations(new ArrayList<>(),
+                        CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO));
+
+        List<ResourceOveruseConfiguration> resourceOveruseConfigs = new ArrayList<>(
+                Collections.singletonList(
+                        sampleResourceOveruseConfigurationBuilder(ComponentType.SYSTEM,
+                                sampleIoOveruseConfigurationBuilder(ComponentType.SYSTEM).build())
+                                .build()));
+        assertThrows(IllegalArgumentException.class,
+                () -> mCarWatchdogService.setResourceOveruseConfigurations(resourceOveruseConfigs,
+                        0));
+    }
+
+    @Test
+    public void testFailsSetResourceOveruseConfigurationsOnDuplicateComponents() throws Exception {
+        ResourceOveruseConfiguration config =
+                sampleResourceOveruseConfigurationBuilder(ComponentType.SYSTEM,
+                sampleIoOveruseConfigurationBuilder(ComponentType.SYSTEM).build()).build();
+        List<ResourceOveruseConfiguration> resourceOveruseConfigs = new ArrayList<>(Arrays.asList(
+                config, config));
+        assertThrows(IllegalArgumentException.class,
+                () -> mCarWatchdogService.setResourceOveruseConfigurations(resourceOveruseConfigs,
+                        CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO));
+    }
+
+    @Test
+    public void testFailsSetResourceOveruseConfigurationsOnNullIoOveruseConfiguration()
+            throws Exception {
+        List<ResourceOveruseConfiguration> resourceOveruseConfigs = new ArrayList<>(
+                Collections.singletonList(
+                        sampleResourceOveruseConfigurationBuilder(ComponentType.SYSTEM,
+                                null).build()));
+        assertThrows(IllegalArgumentException.class,
+                () -> mCarWatchdogService.setResourceOveruseConfigurations(resourceOveruseConfigs,
+                        CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO));
+    }
+
+    @Test
+    public void testGetResourceOveruseConfigurations() throws Exception {
+        List<android.automotive.watchdog.internal.ResourceOveruseConfiguration>
+                internalResourceOveruseConfigs = new ArrayList<>(Arrays.asList(
+                sampleInternalResourceOveruseConfiguration(ComponentType.SYSTEM,
+                        sampleInternalIoOveruseConfiguration(ComponentType.SYSTEM)),
+                sampleInternalResourceOveruseConfiguration(ComponentType.VENDOR,
+                        sampleInternalIoOveruseConfiguration(ComponentType.VENDOR)),
+                sampleInternalResourceOveruseConfiguration(ComponentType.THIRD_PARTY,
+                        sampleInternalIoOveruseConfiguration(ComponentType.THIRD_PARTY))));
+        doReturn(internalResourceOveruseConfigs).when(mMockCarWatchdogDaemon)
+                .getResourceOveruseConfigurations();
+
+        List<ResourceOveruseConfiguration> actualConfigs =
+                mCarWatchdogService.getResourceOveruseConfigurations(
+                        CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO);
+
+        List<ResourceOveruseConfiguration> expectedConfigs = new ArrayList<>(Arrays.asList(
+                sampleResourceOveruseConfigurationBuilder(ComponentType.SYSTEM,
+                        sampleIoOveruseConfigurationBuilder(ComponentType.SYSTEM).build()).build(),
+                sampleResourceOveruseConfigurationBuilder(ComponentType.VENDOR,
+                        sampleIoOveruseConfigurationBuilder(ComponentType.VENDOR).build()).build(),
+                sampleResourceOveruseConfigurationBuilder(ComponentType.THIRD_PARTY,
+                        sampleIoOveruseConfigurationBuilder(ComponentType.THIRD_PARTY).build())
+                        .build()));
+
+        ResourceOveruseConfigurationSubject.assertThat(actualConfigs)
+                .containsExactlyElementsIn(expectedConfigs);
+    }
+
+    @Test
+    public void testFailsGetResourceOveruseConfigurationsOnInvalidArgs() throws Exception {
+        assertThrows(IllegalArgumentException.class,
+                () -> mCarWatchdogService.getResourceOveruseConfigurations(0));
+    }
+
+    @Test
     public void testLatestIoOveruseStats() throws Exception {
         int criticalSysPkgUid = Binder.getCallingUid();
         int nonCriticalSysPkgUid = getUid(1056);
@@ -275,12 +791,6 @@
         packageNamesByUid.put(nonCriticalSysPkgUid, "non_critical.system.package");
         packageNamesByUid.put(nonCriticalVndrPkgUid, "non_critical.vendor.package");
         packageNamesByUid.put(thirdPartyPkgUid, "third_party.package");
-
-        SparseBooleanArray killableOnOveruseByUid = new SparseBooleanArray();
-        killableOnOveruseByUid.put(criticalSysPkgUid, false);
-        killableOnOveruseByUid.put(nonCriticalSysPkgUid, true);
-        killableOnOveruseByUid.put(nonCriticalVndrPkgUid, true);
-        killableOnOveruseByUid.put(thirdPartyPkgUid, true);
         injectUidToPackageNameMapping(packageNamesByUid);
 
         IResourceOveruseListener mockSystemListener = createMockResourceOveruseListener();
@@ -298,28 +808,28 @@
         List<PackageIoOveruseStats> packageIoOveruseStats = new ArrayList<>(Arrays.asList(
                 /* Overuse occurred but cannot be killed/disabled. */
                 constructPackageIoOveruseStats(criticalSysPkgUid, /* shouldNotify= */true,
-                        constructInternalIoOveruseStats(
-                                killableOnOveruseByUid.get(criticalSysPkgUid),
-                                constructPerStateBytes(0, 0, 0),
-                                constructPerStateBytes(100, 200, 300), /* totalOveruses= */2)),
+                        constructInternalIoOveruseStats(/* killableOnOveruse= */false,
+                                /* remainingWriteBytes= */constructPerStateBytes(0, 0, 0),
+                                /* writtenBytes= */constructPerStateBytes(100, 200, 300),
+                                /* totalOveruses= */2)),
                 /* No overuse occurred but should be notified. */
                 constructPackageIoOveruseStats(nonCriticalSysPkgUid, /* shouldNotify= */true,
-                        constructInternalIoOveruseStats(
-                                killableOnOveruseByUid.get(nonCriticalSysPkgUid),
-                                constructPerStateBytes(20, 30, 40),
-                                constructPerStateBytes(100, 200, 300), /* totalOveruses= */2)),
+                        constructInternalIoOveruseStats(/* killableOnOveruse= */true,
+                                /* remainingWriteBytes= */constructPerStateBytes(20, 30, 40),
+                                /* writtenBytes= */constructPerStateBytes(100, 200, 300),
+                                /* totalOveruses= */2)),
                 /* Neither overuse occurred nor be notified. */
                 constructPackageIoOveruseStats(nonCriticalVndrPkgUid, /* shouldNotify= */false,
-                        constructInternalIoOveruseStats(
-                                killableOnOveruseByUid.get(nonCriticalVndrPkgUid),
-                                constructPerStateBytes(200, 300, 400),
-                                constructPerStateBytes(100, 200, 300), /* totalOveruses= */2)),
+                        constructInternalIoOveruseStats(/* killableOnOveruse= */true,
+                                /* remainingWriteBytes= */constructPerStateBytes(200, 300, 400),
+                                /* writtenBytes= */constructPerStateBytes(100, 200, 300),
+                                /* totalOveruses= */2)),
                 /* Overuse occurred and can be killed/disabled. */
                 constructPackageIoOveruseStats(thirdPartyPkgUid, /* shouldNotify= */true,
-                        constructInternalIoOveruseStats(
-                                killableOnOveruseByUid.get(thirdPartyPkgUid),
-                                constructPerStateBytes(0, 0, 0),
-                                constructPerStateBytes(100, 200, 300), /* totalOveruses= */2))));
+                        constructInternalIoOveruseStats(/* killableOnOveruse= */true,
+                                /* remainingWriteBytes= */constructPerStateBytes(0, 0, 0),
+                                /* writtenBytes= */constructPerStateBytes(100, 200, 300),
+                                /* totalOveruses= */2))));
 
         mWatchdogServiceForSystemImpl.latestIoOveruseStats(packageIoOveruseStats);
 
@@ -327,19 +837,16 @@
 
         expectedStats.add(constructResourceOveruseStats(criticalSysPkgUid,
                 packageNamesByUid.get(criticalSysPkgUid),
-                killableOnOveruseByUid.get(criticalSysPkgUid),
                 packageIoOveruseStats.get(0).ioOveruseStats));
 
         verifyOnOveruseCalled(expectedStats, mockListener);
 
         expectedStats.add(constructResourceOveruseStats(nonCriticalSysPkgUid,
                 packageNamesByUid.get(nonCriticalSysPkgUid),
-                killableOnOveruseByUid.get(nonCriticalSysPkgUid),
                 packageIoOveruseStats.get(1).ioOveruseStats));
 
         expectedStats.add(constructResourceOveruseStats(thirdPartyPkgUid,
                 packageNamesByUid.get(thirdPartyPkgUid),
-                killableOnOveruseByUid.get(thirdPartyPkgUid),
                 packageIoOveruseStats.get(3).ioOveruseStats));
 
         verifyOnOveruseCalled(expectedStats, mockSystemListener);
@@ -499,19 +1006,19 @@
     }
 
     private void mockWatchdogDaemon() {
-        doReturn(mBinder).when(() -> ServiceManager.getService(CAR_WATCHDOG_DAEMON_INTERFACE));
-        when(mBinder.queryLocalInterface(anyString())).thenReturn(mCarWatchdogDaemon);
+        doReturn(mMockBinder).when(() -> ServiceManager.getService(CAR_WATCHDOG_DAEMON_INTERFACE));
+        when(mMockBinder.queryLocalInterface(anyString())).thenReturn(mMockCarWatchdogDaemon);
     }
 
     private void setupUsers() {
-        when(mMockContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
-        mockUmGetAllUsers(mUserManager, new UserInfo[0]);
+        when(mMockContext.getSystemService(Context.USER_SERVICE)).thenReturn(mMockUserManager);
+        mockUmGetAllUsers(mMockUserManager, new UserInfo[0]);
     }
 
     private ICarWatchdogServiceForSystem registerCarWatchdogService() throws Exception {
         ArgumentCaptor<ICarWatchdogServiceForSystem> watchdogServiceForSystemImplCaptor =
                 ArgumentCaptor.forClass(ICarWatchdogServiceForSystem.class);
-        verify(mCarWatchdogDaemon).registerCarWatchdogService(
+        verify(mMockCarWatchdogDaemon).registerCarWatchdogService(
                 watchdogServiceForSystemImplCaptor.capture());
         return watchdogServiceForSystemImplCaptor.getValue();
     }
@@ -520,15 +1027,24 @@
         mCarWatchdogService.registerClient(client, TIMEOUT_CRITICAL);
         mWatchdogServiceForSystemImpl.checkIfAlive(123456, TIMEOUT_CRITICAL);
         ArgumentCaptor<int[]> notRespondingClients = ArgumentCaptor.forClass(int[].class);
-        verify(mCarWatchdogDaemon, timeout(MAX_WAIT_TIME_MS)).tellCarWatchdogServiceAlive(
+        verify(mMockCarWatchdogDaemon, timeout(MAX_WAIT_TIME_MS)).tellCarWatchdogServiceAlive(
                 eq(mWatchdogServiceForSystemImpl), notRespondingClients.capture(), eq(123456));
         assertThat(notRespondingClients.getValue().length).isEqualTo(0);
         mWatchdogServiceForSystemImpl.checkIfAlive(987654, TIMEOUT_CRITICAL);
-        verify(mCarWatchdogDaemon, timeout(MAX_WAIT_TIME_MS)).tellCarWatchdogServiceAlive(
+        verify(mMockCarWatchdogDaemon, timeout(MAX_WAIT_TIME_MS)).tellCarWatchdogServiceAlive(
                 eq(mWatchdogServiceForSystemImpl), notRespondingClients.capture(), eq(987654));
         assertThat(notRespondingClients.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).updateResourceOveruseConfigurations(
+                resourceOveruseConfigurationsCaptor.capture());
+        return resourceOveruseConfigurationsCaptor.getValue();
+    }
+
     private void injectUidToPackageNameMapping(SparseArray<String> packageNamesByUid) {
         doAnswer(args -> {
             int[] uids = args.getArgument(0);
@@ -540,6 +1056,49 @@
         }).when(mMockPackageManager).getNamesForUids(any());
     }
 
+    private void injectIoOveruseStatsForPackages(SparseArray<String> packageNamesByUid,
+            Set<String> killablePackages) throws RemoteException {
+        injectUidToPackageNameMapping(packageNamesByUid);
+        List<PackageIoOveruseStats> packageIoOveruseStats = new ArrayList<>();
+        for (int i = 0; i < packageNamesByUid.size(); ++i) {
+            String packageName = packageNamesByUid.valueAt(i);
+            int uid = packageNamesByUid.keyAt(i);
+            packageIoOveruseStats.add(constructPackageIoOveruseStats(uid,
+                    false,
+                    constructInternalIoOveruseStats(killablePackages.contains(packageName),
+                            /* remainingWriteBytes= */constructPerStateBytes(20, 20, 20),
+                            /* writtenBytes= */constructPerStateBytes(100, 200, 300),
+                            /* totalOveruses= */2)));
+        }
+        mWatchdogServiceForSystemImpl.latestIoOveruseStats(packageIoOveruseStats);
+    }
+
+    private void injectPackageInfos(List<String> packageNames) {
+        List<android.content.pm.PackageInfo> packageInfos = new ArrayList<>();
+        TriConsumer<String, Integer, Integer> addPackageInfo =
+                (packageName, flags, privateFlags) -> {
+                    android.content.pm.PackageInfo packageInfo =
+                            new android.content.pm.PackageInfo();
+                    packageInfo.packageName = packageName;
+                    packageInfo.applicationInfo = new ApplicationInfo();
+                    packageInfo.applicationInfo.flags = flags;
+                    packageInfo.applicationInfo.privateFlags = privateFlags;
+                    packageInfos.add(packageInfo);
+                };
+        for (String packageName : packageNames) {
+            if (packageName.startsWith("system")) {
+                addPackageInfo.accept(packageName, ApplicationInfo.FLAG_SYSTEM, 0);
+            } else if (packageName.startsWith("vendor")) {
+                addPackageInfo.accept(packageName, ApplicationInfo.FLAG_SYSTEM,
+                        ApplicationInfo.PRIVATE_FLAG_OEM);
+            } else {
+                addPackageInfo.accept(packageName, 0, 0);
+            }
+        }
+        doReturn(packageInfos).when(mMockPackageManager).getInstalledPackagesAsUser(
+                eq(0), anyInt());
+    }
+
     private void mockApplicationEnabledSettingAccessors(IPackageManager pm) throws Exception {
         doReturn(COMPONENT_ENABLED_STATE_ENABLED).when(pm)
                 .getApplicationEnabledSetting(anyString(), eq(UserHandle.myUserId()));
@@ -553,7 +1112,7 @@
         ArgumentCaptor<List<PackageResourceOveruseAction>> resourceOveruseActionsCaptor =
                 ArgumentCaptor.forClass((Class) List.class);
 
-        verify(mCarWatchdogDaemon, timeout(MAX_WAIT_TIME_MS)).actionTakenOnResourceOveruse(
+        verify(mMockCarWatchdogDaemon, timeout(MAX_WAIT_TIME_MS)).actionTakenOnResourceOveruse(
                 resourceOveruseActionsCaptor.capture());
         List<PackageResourceOveruseAction> actual = resourceOveruseActionsCaptor.getValue();
 
@@ -586,6 +1145,118 @@
         return UserHandle.getUid(UserHandle.myUserId(), appId);
     }
 
+    private static ResourceOveruseConfiguration.Builder sampleResourceOveruseConfigurationBuilder(
+            int componentType, IoOveruseConfiguration ioOveruseConfig) {
+        String prefix = WatchdogPerfHandler.toComponentTypeStr(componentType);
+        List<String> safeToKill = new ArrayList<>(Arrays.asList(
+                prefix + "_package.A", prefix + "_pkg.B"));
+        List<String> vendorPrefixes = new ArrayList<>(Arrays.asList(
+                prefix + "_package", prefix + "_pkg"));
+        Map<String, String> pkgToAppCategory = new ArrayMap<>();
+        pkgToAppCategory.put(prefix + "_package.A", "android.car.watchdog.app.category.MEDIA");
+        ResourceOveruseConfiguration.Builder configBuilder =
+                new ResourceOveruseConfiguration.Builder(componentType, safeToKill,
+                        vendorPrefixes, pkgToAppCategory);
+        configBuilder.setIoOveruseConfiguration(ioOveruseConfig);
+        return configBuilder;
+    }
+
+    private static IoOveruseConfiguration.Builder sampleIoOveruseConfigurationBuilder(
+            int componentType) {
+        String prefix = WatchdogPerfHandler.toComponentTypeStr(componentType);
+        PerStateBytes componentLevelThresholds = new PerStateBytes(
+                /* foregroundModeBytes= */10, /* backgroundModeBytes= */20,
+                /* garageModeBytes= */30);
+        Map<String, PerStateBytes> packageSpecificThresholds = new ArrayMap<>();
+        packageSpecificThresholds.put(prefix + "_package.A", new PerStateBytes(
+                /* foregroundModeBytes= */40, /* backgroundModeBytes= */50,
+                /* garageModeBytes= */60));
+
+        Map<String, PerStateBytes> appCategorySpecificThresholds = new ArrayMap<>();
+        appCategorySpecificThresholds.put(
+                ResourceOveruseConfiguration.APPLICATION_CATEGORY_TYPE_MEDIA,
+                new PerStateBytes(/* foregroundModeBytes= */100, /* backgroundModeBytes= */200,
+                        /* garageModeBytes= */300));
+        appCategorySpecificThresholds.put(
+                ResourceOveruseConfiguration.APPLICATION_CATEGORY_TYPE_MAPS,
+                new PerStateBytes(/* foregroundModeBytes= */1100, /* backgroundModeBytes= */2200,
+                        /* garageModeBytes= */3300));
+
+        List<IoOveruseAlertThreshold> systemWideThresholds = new ArrayList<>(
+                Collections.singletonList(new IoOveruseAlertThreshold(/* durationInSeconds= */10,
+                        /* writtenBytesPerSecond= */200)));
+
+        return new IoOveruseConfiguration.Builder(componentLevelThresholds,
+                packageSpecificThresholds, appCategorySpecificThresholds, systemWideThresholds);
+    }
+
+    private static android.automotive.watchdog.internal.ResourceOveruseConfiguration
+            sampleInternalResourceOveruseConfiguration(int componentType,
+            android.automotive.watchdog.internal.IoOveruseConfiguration ioOveruseConfig) {
+        String prefix = WatchdogPerfHandler.toComponentTypeStr(componentType);
+        android.automotive.watchdog.internal.ResourceOveruseConfiguration config =
+                new android.automotive.watchdog.internal.ResourceOveruseConfiguration();
+        config.componentType = componentType;
+        config.safeToKillPackages = new ArrayList<>(Arrays.asList(
+                prefix + "_package.A", prefix + "_pkg.B"));
+        config.vendorPackagePrefixes = new ArrayList<>(Arrays.asList(
+                prefix + "_package", prefix + "_pkg"));
+
+        PackageMetadata metadata = new PackageMetadata();
+        metadata.packageName = prefix + "_package.A";
+        metadata.appCategoryType = ApplicationCategoryType.MEDIA;
+        config.packageMetadata = new ArrayList<>(Collections.singletonList(metadata));
+
+        ResourceSpecificConfiguration resourceSpecificConfig = new ResourceSpecificConfiguration();
+        resourceSpecificConfig.setIoOveruseConfiguration(ioOveruseConfig);
+        config.resourceSpecificConfigurations = new ArrayList<>(
+                Collections.singletonList(resourceSpecificConfig));
+
+        return config;
+    }
+
+    private static android.automotive.watchdog.internal.IoOveruseConfiguration
+            sampleInternalIoOveruseConfiguration(int componentType) {
+        String prefix = WatchdogPerfHandler.toComponentTypeStr(componentType);
+        android.automotive.watchdog.internal.IoOveruseConfiguration config =
+                new android.automotive.watchdog.internal.IoOveruseConfiguration();
+        config.componentLevelThresholds = constructPerStateIoOveruseThreshold(prefix,
+                /* fgBytes= */10, /* bgBytes= */20, /* gmBytes= */30);
+        config.packageSpecificThresholds = new ArrayList<>(Collections.singletonList(
+                constructPerStateIoOveruseThreshold(prefix + "_package.A", /* fgBytes= */40,
+                        /* bgBytes= */50, /* gmBytes= */60)));
+        config.categorySpecificThresholds = new ArrayList<>(Arrays.asList(
+                constructPerStateIoOveruseThreshold(
+                        WatchdogPerfHandler.INTERNAL_APPLICATION_CATEGORY_TYPE_MEDIA,
+                        /* fgBytes= */100, /* bgBytes= */200, /* gmBytes= */300),
+                constructPerStateIoOveruseThreshold(
+                        WatchdogPerfHandler.INTERNAL_APPLICATION_CATEGORY_TYPE_MAPS,
+                        /* fgBytes= */1100, /* bgBytes= */2200, /* gmBytes= */3300)));
+        config.systemWideThresholds = new ArrayList<>(Collections.singletonList(
+                constructInternalIoOveruseAlertThreshold(/* duration= */10, /* writeBPS= */200)));
+        return config;
+    }
+
+    private static PerStateIoOveruseThreshold constructPerStateIoOveruseThreshold(String name,
+            long fgBytes, long bgBytes, long gmBytes) {
+        PerStateIoOveruseThreshold threshold = new PerStateIoOveruseThreshold();
+        threshold.name = name;
+        threshold.perStateWriteBytes = new android.automotive.watchdog.PerStateBytes();
+        threshold.perStateWriteBytes.foregroundBytes = fgBytes;
+        threshold.perStateWriteBytes.backgroundBytes = bgBytes;
+        threshold.perStateWriteBytes.garageModeBytes = gmBytes;
+        return threshold;
+    }
+
+    private static android.automotive.watchdog.internal.IoOveruseAlertThreshold
+            constructInternalIoOveruseAlertThreshold(long duration, long writeBPS) {
+        android.automotive.watchdog.internal.IoOveruseAlertThreshold threshold =
+                new android.automotive.watchdog.internal.IoOveruseAlertThreshold();
+        threshold.durationInSeconds = duration;
+        threshold.writtenBytesPerSecond = writeBPS;
+        return threshold;
+    }
+
     private static PackageIoOveruseStats constructPackageIoOveruseStats(int uid,
             boolean shouldNotify, android.automotive.watchdog.IoOveruseStats ioOveruseStats) {
         PackageIoOveruseStats stats = new PackageIoOveruseStats();
@@ -596,11 +1267,10 @@
     }
 
     private static ResourceOveruseStats constructResourceOveruseStats(int uid, String packageName,
-            boolean killableOnOveruse,
             android.automotive.watchdog.IoOveruseStats internalIoOveruseStats) {
         IoOveruseStats ioOveruseStats =
                 WatchdogPerfHandler.toIoOveruseStatsBuilder(internalIoOveruseStats)
-                        .setKillableOnOveruse(killableOnOveruse).build();
+                        .setKillableOnOveruse(internalIoOveruseStats.killableOnOveruse).build();
 
         return new ResourceOveruseStats.Builder(packageName, UserHandle.getUserHandleForUid(uid))
                 .setIoOveruseStats(ioOveruseStats).build();
@@ -629,7 +1299,7 @@
         return perStateBytes;
     }
 
-    public static PackageResourceOveruseAction constructPackageResourceOveruseAction(
+    private static PackageResourceOveruseAction constructPackageResourceOveruseAction(
             String packageName, int uid, int[] resourceTypes, int resourceOveruseActionType) {
         PackageResourceOveruseAction action = new PackageResourceOveruseAction();
         action.packageIdentifier = new PackageIdentifier();
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
new file mode 100644
index 0000000..47ad645
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/watchdog/InternalIoOveruseConfigurationSubject.java
@@ -0,0 +1,161 @@
+/*
+ * 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 android.automotive.watchdog.internal.IoOveruseAlertThreshold;
+import android.automotive.watchdog.internal.IoOveruseConfiguration;
+import android.automotive.watchdog.internal.PerStateIoOveruseThreshold;
+
+import com.google.common.truth.Correspondence;
+import com.google.common.truth.FailureMetadata;
+import com.google.common.truth.Subject;
+import com.google.common.truth.Truth;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+public final class InternalIoOveruseConfigurationSubject extends Subject {
+    // Boiler-plate Subject.Factory for InternalIoOveruseConfigurationSubject
+    private static final Subject.Factory<
+            com.android.car.watchdog.InternalIoOveruseConfigurationSubject,
+            Iterable<IoOveruseConfiguration>> Io_OVERUSE_CONFIG_SUBJECT_FACTORY =
+            com.android.car.watchdog.InternalIoOveruseConfigurationSubject::new;
+
+    private final Iterable<IoOveruseConfiguration> mActual;
+
+    // User-defined entry point
+    public static InternalIoOveruseConfigurationSubject assertThat(
+            @Nullable Iterable<IoOveruseConfiguration> stats) {
+        return assertAbout(Io_OVERUSE_CONFIG_SUBJECT_FACTORY).that(stats);
+    }
+
+    public static Subject.Factory<InternalIoOveruseConfigurationSubject,
+            Iterable<IoOveruseConfiguration>> resourceOveruseStats() {
+        return Io_OVERUSE_CONFIG_SUBJECT_FACTORY;
+    }
+
+    public void containsExactly(IoOveruseConfiguration... stats) {
+        containsExactlyElementsIn(Arrays.asList(stats));
+    }
+
+    public void containsExactlyElementsIn(Iterable<IoOveruseConfiguration> expected) {
+        Truth.assertWithMessage("Expected: " + expected.toString() + "\nActual: "
+                + mActual.toString()).that(mActual)
+                .comparingElementsUsing(Correspondence.from(
+                        InternalIoOveruseConfigurationSubject::isEquals, "is equal to"))
+                .containsExactlyElementsIn(expected);
+    }
+
+    public static boolean isEquals(IoOveruseConfiguration actual,
+            IoOveruseConfiguration expected) {
+        if (actual == null || expected == null) {
+            return (actual == null) && (expected == null);
+        }
+        return actual.componentLevelThresholds.name == expected.componentLevelThresholds.name
+                && isPerStateBytesEquals(actual.componentLevelThresholds.perStateWriteBytes,
+                    expected.componentLevelThresholds.perStateWriteBytes)
+                && isPerStateThresholdEquals(actual.packageSpecificThresholds,
+                    expected.packageSpecificThresholds)
+                && isPerStateThresholdEquals(actual.categorySpecificThresholds,
+                    expected.categorySpecificThresholds)
+                && isAlertThresholdEquals(actual.systemWideThresholds,
+                    expected.systemWideThresholds);
+    }
+
+    public static StringBuilder toStringBuilder(StringBuilder builder,
+            IoOveruseConfiguration config) {
+        builder.append("{Component-level thresholds: ")
+                .append(toString(config.componentLevelThresholds))
+                .append(", Package specific thresholds: [")
+                .append(config.packageSpecificThresholds.stream()
+                        .map(InternalIoOveruseConfigurationSubject::toString)
+                        .collect(Collectors.joining(", ")))
+                .append("], Category specific thresholds: [")
+                .append(config.categorySpecificThresholds.stream()
+                        .map(InternalIoOveruseConfigurationSubject::toString)
+                        .collect(Collectors.joining(", ")))
+                .append("], System wide thresholds: [")
+                .append(config.systemWideThresholds.stream()
+                        .map(InternalIoOveruseConfigurationSubject::toString)
+                        .collect(Collectors.joining(", ")))
+                .append("]}");
+        return builder;
+    }
+
+    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("}}");
+        return builder.toString();
+    }
+
+    public static String toString(IoOveruseAlertThreshold threshold) {
+        StringBuilder builder = new StringBuilder();
+        builder.append("{durationInSeconds: ").append(threshold.durationInSeconds)
+                .append(", writtenBytesPerSecond: ").append(threshold.writtenBytesPerSecond)
+                .append("}");
+        return builder.toString();
+    }
+
+    private InternalIoOveruseConfigurationSubject(FailureMetadata failureMetadata,
+            @Nullable Iterable<IoOveruseConfiguration> iterableSubject) {
+        super(failureMetadata, iterableSubject);
+        this.mActual = iterableSubject;
+    }
+
+    private static boolean isPerStateThresholdEquals(List<PerStateIoOveruseThreshold> actual,
+            List<PerStateIoOveruseThreshold> expected) {
+        Set<String> actualStr = toPerStateThresholdStrings(actual);
+        Set<String> expectedStr = toPerStateThresholdStrings(expected);
+        return actualStr.equals(expectedStr);
+    }
+
+    private static boolean isAlertThresholdEquals(List<IoOveruseAlertThreshold> actual,
+            List<IoOveruseAlertThreshold> expected) {
+        Set<String> actualStr = toAlertThresholdStrings(actual);
+        Set<String> expectedStr = toAlertThresholdStrings(expected);
+        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,
+                x.perStateWriteBytes.foregroundBytes, x.perStateWriteBytes.backgroundBytes,
+                x.perStateWriteBytes.garageModeBytes))
+                .collect(Collectors.toSet());
+    }
+
+    private static Set<String> toAlertThresholdStrings(
+            List<IoOveruseAlertThreshold> thresholds) {
+        return thresholds.stream().map(x -> String.format("%d:%d", x.durationInSeconds,
+                x.writtenBytesPerSecond)).collect(Collectors.toSet());
+    }
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/watchdog/InternalResourceOveruseConfigurationSubject.java b/tests/carservice_unit_test/src/com/android/car/watchdog/InternalResourceOveruseConfigurationSubject.java
new file mode 100644
index 0000000..d324efe
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/watchdog/InternalResourceOveruseConfigurationSubject.java
@@ -0,0 +1,191 @@
+/*
+ * 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.internal.PackageMetadata;
+import android.automotive.watchdog.internal.ResourceOveruseConfiguration;
+import android.automotive.watchdog.internal.ResourceSpecificConfiguration;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.truth.Correspondence;
+import com.google.common.truth.FailureMetadata;
+import com.google.common.truth.Subject;
+import com.google.common.truth.Truth;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+public final class InternalResourceOveruseConfigurationSubject extends Subject {
+    // Boiler-plate Subject.Factory for InternalResourceOveruseConfigurationSubject
+    private static final Subject.Factory<
+            com.android.car.watchdog.InternalResourceOveruseConfigurationSubject,
+            Iterable<ResourceOveruseConfiguration>> RESOURCE_OVERUSE_CONFIG_SUBJECT_FACTORY =
+            com.android.car.watchdog.InternalResourceOveruseConfigurationSubject::new;
+
+    private static final Correspondence<List<PackageMetadata>, List<PackageMetadata>>
+            METADATA_LIST_CORRESPONDENCE = Correspondence.from(
+                    InternalResourceOveruseConfigurationSubject::isPackageMetadataEquals,
+            "is equal to");
+
+    private static final Correspondence<List<ResourceSpecificConfiguration>,
+            List<ResourceSpecificConfiguration>>
+            RESOURCE_SPECIFIC_CONFIG_LIST_CORRESPONDENCE = Correspondence.from(
+            InternalResourceOveruseConfigurationSubject::isResourceSpecificConfigEquals,
+            "is equal to");
+
+    private final Iterable<ResourceOveruseConfiguration> mActual;
+
+    // User-defined entry point
+    public static InternalResourceOveruseConfigurationSubject assertThat(
+            @Nullable Iterable<ResourceOveruseConfiguration> stats) {
+        return assertAbout(RESOURCE_OVERUSE_CONFIG_SUBJECT_FACTORY).that(stats);
+    }
+
+    public static Subject.Factory<InternalResourceOveruseConfigurationSubject,
+            Iterable<ResourceOveruseConfiguration>> resourceOveruseStats() {
+        return RESOURCE_OVERUSE_CONFIG_SUBJECT_FACTORY;
+    }
+
+    public void containsExactly(ResourceOveruseConfiguration... stats) {
+        containsExactlyElementsIn(Arrays.asList(stats));
+    }
+
+    public void containsExactlyElementsIn(Iterable<ResourceOveruseConfiguration> expected) {
+        Truth.assertWithMessage("Expected: " + toString(expected) + "\nActual: "
+                + toString(mActual)).that(mActual)
+                .comparingElementsUsing(Correspondence.from(
+                        InternalResourceOveruseConfigurationSubject::isEquals, "is equal to"))
+                .containsExactlyElementsIn(expected);
+    }
+
+    public static boolean isPackageMetadataEquals(List<PackageMetadata> actual,
+            List<PackageMetadata> expected) {
+        Set<String> actualStr = toMetadataStrings(actual);
+        Set<String> expectedStr = toMetadataStrings(expected);
+        return actualStr.equals(expectedStr);
+    }
+
+    public static boolean isResourceSpecificConfigEquals(List<ResourceSpecificConfiguration> actual,
+            List<ResourceSpecificConfiguration> expected) {
+        if (actual.size() != expected.size()) {
+            return false;
+        }
+        if (actual.size() == 0) {
+            return true;
+        }
+        /*
+         * When more resource types are added make this comparison more generic. Because the
+         * resource overuse configuration should contain only one I/O overuse configuration, the
+         * comparison only checks for first I/O overuse configuration.
+         */
+        ResourceSpecificConfiguration actualElement = actual.get(0);
+        ResourceSpecificConfiguration expectedElement = expected.get(0);
+        if (actualElement.getTag() != expectedElement.getTag()) {
+            return false;
+        }
+        if (actualElement.getTag() != ResourceSpecificConfiguration.ioOveruseConfiguration) {
+            return false;
+        }
+        return InternalIoOveruseConfigurationSubject.isEquals(
+                actualElement.getIoOveruseConfiguration(),
+                expectedElement.getIoOveruseConfiguration());
+    }
+
+    public static boolean isEquals(ResourceOveruseConfiguration actual,
+            ResourceOveruseConfiguration expected) {
+        if (actual == null || expected == null) {
+            return (actual == null) && (expected == null);
+        }
+        return actual.componentType == expected.componentType
+                && ImmutableSet.copyOf(actual.safeToKillPackages).equals(
+                        ImmutableSet.copyOf(expected.safeToKillPackages))
+                && ImmutableSet.copyOf(actual.vendorPackagePrefixes).equals(
+                        ImmutableSet.copyOf(expected.vendorPackagePrefixes))
+                && METADATA_LIST_CORRESPONDENCE.compare(actual.packageMetadata,
+                        expected.packageMetadata)
+                && RESOURCE_SPECIFIC_CONFIG_LIST_CORRESPONDENCE.compare(
+                        actual.resourceSpecificConfigurations,
+                        expected.resourceSpecificConfigurations);
+    }
+
+    private InternalResourceOveruseConfigurationSubject(FailureMetadata failureMetadata,
+            @Nullable Iterable<ResourceOveruseConfiguration> iterableSubject) {
+        super(failureMetadata, iterableSubject);
+        this.mActual = iterableSubject;
+    }
+
+    private static Set<String> toMetadataStrings(List<PackageMetadata> metadata) {
+        return metadata.stream().map(x -> String.format("%s:%d", x.packageName, x.appCategoryType))
+                .collect(Collectors.toSet());
+    }
+
+    private static String toString(
+            Iterable<ResourceOveruseConfiguration> configs) {
+        StringBuilder builder = new StringBuilder();
+        builder.append("[");
+        for (ResourceOveruseConfiguration config : configs) {
+            toStringBuilder(builder, config).append(", ");
+        }
+        builder.append("]");
+        return builder.toString();
+    }
+
+    private static StringBuilder toStringBuilder(StringBuilder builder,
+            ResourceOveruseConfiguration config) {
+        builder.append("{Component type: ").append(config.componentType)
+                .append(", Safe-to-kill packages: [")
+                .append(config.safeToKillPackages.stream().map(Object::toString)
+                        .collect(Collectors.joining(", ")))
+                .append("], Vendor package prefixes: [")
+                .append(config.vendorPackagePrefixes.stream().map(Object::toString)
+                        .collect(Collectors.joining(", ")))
+                .append("], Package Metadata: [")
+                .append(config.packageMetadata.stream()
+                        .map(InternalResourceOveruseConfigurationSubject::toPackageMetadataString)
+                        .collect(Collectors.joining(", ")))
+                .append("], Resource specific configurations: [")
+                .append(config.resourceSpecificConfigurations.stream()
+                        .map(InternalResourceOveruseConfigurationSubject
+                                ::toResourceOveruseConfigString).collect(Collectors.joining(", ")))
+                .append("]}");
+        return builder;
+    }
+
+    private static String toResourceOveruseConfigString(ResourceSpecificConfiguration config) {
+        StringBuilder builder = new StringBuilder();
+        builder.append("{Tag: ").append(config.getTag()).append(", Value: ");
+        if (config.getTag() == ResourceSpecificConfiguration.ioOveruseConfiguration) {
+            InternalIoOveruseConfigurationSubject.toStringBuilder(builder,
+                    config.getIoOveruseConfiguration());
+        } else {
+            builder.append("UNKNOWN");
+        }
+        return builder.toString();
+    }
+
+    private static String toPackageMetadataString(PackageMetadata metadata) {
+        StringBuilder builder = new StringBuilder();
+        builder.append("{Name: ").append(metadata.packageName).append(", App category type: ")
+                .append(metadata.appCategoryType).append("}");
+        return builder.toString();
+    }
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/watchdog/IoOveruseConfigurationSubject.java b/tests/carservice_unit_test/src/com/android/car/watchdog/IoOveruseConfigurationSubject.java
new file mode 100644
index 0000000..3629898
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/watchdog/IoOveruseConfigurationSubject.java
@@ -0,0 +1,123 @@
+/*
+ * 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.car.watchdog.IoOveruseAlertThreshold;
+import android.car.watchdog.IoOveruseConfiguration;
+import android.car.watchdog.PerStateBytes;
+
+import com.google.common.base.Equivalence;
+import com.google.common.collect.Maps;
+import com.google.common.truth.Correspondence;
+import com.google.common.truth.FailureMetadata;
+import com.google.common.truth.Subject;
+import com.google.common.truth.Truth;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+public final class IoOveruseConfigurationSubject extends Subject {
+    // Boiler-plate Subject.Factory for IoOveruseConfigurationSubject
+    private static final Subject.Factory<
+            com.android.car.watchdog.IoOveruseConfigurationSubject,
+            Iterable<IoOveruseConfiguration>> Io_OVERUSE_CONFIG_SUBJECT_FACTORY =
+            com.android.car.watchdog.IoOveruseConfigurationSubject::new;
+
+    private static final Equivalence<PerStateBytes> PER_STATE_BYTES_EQUIVALENCE =
+            new Equivalence<PerStateBytes>() {
+                @Override
+                protected boolean doEquivalent(PerStateBytes actual, PerStateBytes expected) {
+                    return isPerStateBytesEquals(actual, expected);
+                }
+
+                @Override
+                protected int doHash(PerStateBytes perStateBytes) {
+                    return (int) ((perStateBytes.getForegroundModeBytes() * 10 ^ 4)
+                            + (perStateBytes.getBackgroundModeBytes() * 10 ^ 3)
+                            + perStateBytes.getGarageModeBytes());
+                }
+            };
+
+    private final Iterable<IoOveruseConfiguration> mActual;
+
+    // User-defined entry point
+    public static IoOveruseConfigurationSubject assertThat(
+            @Nullable Iterable<IoOveruseConfiguration> stats) {
+        return assertAbout(Io_OVERUSE_CONFIG_SUBJECT_FACTORY).that(stats);
+    }
+
+    public static Subject.Factory<IoOveruseConfigurationSubject,
+            Iterable<IoOveruseConfiguration>> resourceOveruseStats() {
+        return Io_OVERUSE_CONFIG_SUBJECT_FACTORY;
+    }
+
+    public void containsExactly(IoOveruseConfiguration... stats) {
+        containsExactlyElementsIn(Arrays.asList(stats));
+    }
+
+    public void containsExactlyElementsIn(Iterable<IoOveruseConfiguration> expected) {
+        Truth.assertThat(mActual)
+                .comparingElementsUsing(Correspondence.from(
+                        IoOveruseConfigurationSubject::isEquals, "is equal to"))
+                .containsExactlyElementsIn(expected);
+    }
+
+    public static boolean isEquals(IoOveruseConfiguration actual,
+            IoOveruseConfiguration expected) {
+        if (actual == null || expected == null) {
+            return (actual == null) && (expected == null);
+        }
+
+        return isPerStateBytesEquals(actual.getComponentLevelThresholds(),
+                expected.getComponentLevelThresholds())
+                && Maps.difference(actual.getPackageSpecificThresholds(),
+                expected.getPackageSpecificThresholds(), PER_STATE_BYTES_EQUIVALENCE).areEqual()
+                && Maps.difference(actual.getAppCategorySpecificThresholds(),
+                expected.getAppCategorySpecificThresholds(), PER_STATE_BYTES_EQUIVALENCE).areEqual()
+                && isAlertThresholdEquals(actual.getSystemWideThresholds(),
+                expected.getSystemWideThresholds());
+    }
+
+    private IoOveruseConfigurationSubject(FailureMetadata failureMetadata,
+            @Nullable Iterable<IoOveruseConfiguration> iterableSubject) {
+        super(failureMetadata, iterableSubject);
+        this.mActual = iterableSubject;
+    }
+
+    private static boolean isPerStateBytesEquals(PerStateBytes actual, PerStateBytes expected) {
+        return actual.getForegroundModeBytes() == expected.getForegroundModeBytes()
+                && actual.getBackgroundModeBytes() == expected.getBackgroundModeBytes()
+                && actual.getGarageModeBytes() == expected.getGarageModeBytes();
+    }
+
+    private static boolean isAlertThresholdEquals(List<IoOveruseAlertThreshold> actual,
+            List<IoOveruseAlertThreshold> expected) {
+        Set<String> actualStr = toAlertThresholdStrings(actual);
+        Set<String> expectedStr = toAlertThresholdStrings(expected);
+        return actualStr.equals(expectedStr);
+    }
+
+    private static Set<String> toAlertThresholdStrings(List<IoOveruseAlertThreshold> thresholds) {
+        return thresholds.stream().map(x -> String.format("%d:%d", x.getDurationInSeconds(),
+                x.getWrittenBytesPerSecond())).collect(Collectors.toSet());
+    }
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/watchdog/PackageKillableStateSubject.java b/tests/carservice_unit_test/src/com/android/car/watchdog/PackageKillableStateSubject.java
new file mode 100644
index 0000000..953d1df
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/watchdog/PackageKillableStateSubject.java
@@ -0,0 +1,77 @@
+/*
+ * 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.car.watchdog.PackageKillableState;
+
+import com.google.common.truth.Correspondence;
+import com.google.common.truth.FailureMetadata;
+import com.google.common.truth.Subject;
+import com.google.common.truth.Truth;
+
+import java.util.Arrays;
+
+public final class PackageKillableStateSubject extends Subject {
+    // Boiler-plate Subject.Factory for PackageKillableStateSubject
+    private static final Subject.Factory<
+            com.android.car.watchdog.PackageKillableStateSubject,
+            Iterable<PackageKillableState>> PACKAGE_KILLABLE_STATE_SUBJECT_FACTORY =
+            com.android.car.watchdog.PackageKillableStateSubject::new;
+
+    private final Iterable<PackageKillableState> mActual;
+
+    // User-defined entry point
+    public static PackageKillableStateSubject assertThat(
+            @Nullable Iterable<PackageKillableState> stats) {
+        return assertAbout(PACKAGE_KILLABLE_STATE_SUBJECT_FACTORY).that(stats);
+    }
+
+    public static Subject.Factory<PackageKillableStateSubject,
+            Iterable<PackageKillableState>> resourceOveruseStats() {
+        return PACKAGE_KILLABLE_STATE_SUBJECT_FACTORY;
+    }
+
+    public void containsExactly(PackageKillableState... stats) {
+        containsExactlyElementsIn(Arrays.asList(stats));
+    }
+
+    public void containsExactlyElementsIn(Iterable<PackageKillableState> expected) {
+        Truth.assertThat(mActual)
+                .comparingElementsUsing(Correspondence.from(
+                        PackageKillableStateSubject::isEquals, "is equal to"))
+                .containsExactlyElementsIn(expected);
+    }
+
+    public static boolean isEquals(PackageKillableState actual,
+            PackageKillableState expected) {
+        if (actual == null || expected == null) {
+            return (actual == null) && (expected == null);
+        }
+        return actual.getPackageName().equals(expected.getPackageName())
+                && actual.getUserId() == expected.getUserId()
+                && actual.getKillableState() == expected.getKillableState();
+    }
+
+    private PackageKillableStateSubject(FailureMetadata failureMetadata,
+            @Nullable Iterable<PackageKillableState> iterableSubject) {
+        super(failureMetadata, iterableSubject);
+        this.mActual = iterableSubject;
+    }
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/watchdog/ResourceOveruseConfigurationSubject.java b/tests/carservice_unit_test/src/com/android/car/watchdog/ResourceOveruseConfigurationSubject.java
new file mode 100644
index 0000000..5bfdf17
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/watchdog/ResourceOveruseConfigurationSubject.java
@@ -0,0 +1,85 @@
+/*
+ * 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.car.watchdog.ResourceOveruseConfiguration;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Maps;
+import com.google.common.truth.Correspondence;
+import com.google.common.truth.FailureMetadata;
+import com.google.common.truth.Subject;
+import com.google.common.truth.Truth;
+
+import java.util.Arrays;
+
+public final class ResourceOveruseConfigurationSubject extends Subject {
+    // Boiler-plate Subject.Factory for ResourceOveruseConfigurationSubject
+    private static final Subject.Factory<
+            com.android.car.watchdog.ResourceOveruseConfigurationSubject,
+            Iterable<ResourceOveruseConfiguration>> RESOURCE_OVERUSE_CONFIG_SUBJECT_FACTORY =
+            com.android.car.watchdog.ResourceOveruseConfigurationSubject::new;
+
+    private final Iterable<ResourceOveruseConfiguration> mActual;
+
+    // User-defined entry point
+    public static ResourceOveruseConfigurationSubject assertThat(
+            @Nullable Iterable<ResourceOveruseConfiguration> stats) {
+        return assertAbout(RESOURCE_OVERUSE_CONFIG_SUBJECT_FACTORY).that(stats);
+    }
+
+    public static Subject.Factory<ResourceOveruseConfigurationSubject,
+            Iterable<ResourceOveruseConfiguration>> resourceOveruseStats() {
+        return RESOURCE_OVERUSE_CONFIG_SUBJECT_FACTORY;
+    }
+
+    public void containsExactly(ResourceOveruseConfiguration... stats) {
+        containsExactlyElementsIn(Arrays.asList(stats));
+    }
+
+    public void containsExactlyElementsIn(Iterable<ResourceOveruseConfiguration> expected) {
+        Truth.assertThat(mActual)
+                .comparingElementsUsing(Correspondence.from(
+                        ResourceOveruseConfigurationSubject::isEquals, "is equal to"))
+                .containsExactlyElementsIn(expected);
+    }
+
+    public static boolean isEquals(ResourceOveruseConfiguration actual,
+            ResourceOveruseConfiguration expected) {
+        if (actual == null || expected == null) {
+            return (actual == null) && (expected == null);
+        }
+        return actual.getComponentType() == expected.getComponentType()
+                && ImmutableSet.copyOf(actual.getSafeToKillPackages()).equals(
+                ImmutableSet.copyOf(expected.getSafeToKillPackages()))
+                && ImmutableSet.copyOf(actual.getVendorPackagePrefixes()).equals(
+                ImmutableSet.copyOf(expected.getVendorPackagePrefixes()))
+                && Maps.difference(actual.getPackagesToAppCategoryTypes(),
+                expected.getPackagesToAppCategoryTypes()).areEqual()
+                && IoOveruseConfigurationSubject.isEquals(actual.getIoOveruseConfiguration(),
+                    expected.getIoOveruseConfiguration());
+    }
+
+    private ResourceOveruseConfigurationSubject(FailureMetadata failureMetadata,
+            @Nullable Iterable<ResourceOveruseConfiguration> iterableSubject) {
+        super(failureMetadata, iterableSubject);
+        this.mActual = iterableSubject;
+    }
+}