Merge "Adding unit tests to BlockingAnswer" into sc-dev
diff --git a/car-lib/api/test-current.txt b/car-lib/api/test-current.txt
index b82b7b7..3be98c4 100644
--- a/car-lib/api/test-current.txt
+++ b/car-lib/api/test-current.txt
@@ -31,6 +31,7 @@
   public final class CarDevicePolicyManager {
     method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.car.admin.CreateUserResult createUser(@Nullable String, int);
     method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.car.admin.RemoveUserResult removeUser(@NonNull android.os.UserHandle);
+    method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.car.admin.StartUserInBackgroundResult startUserInBackground(@NonNull android.os.UserHandle);
     field public static final int USER_TYPE_ADMIN = 1; // 0x1
     field public static final int USER_TYPE_GUEST = 2; // 0x2
     field public static final int USER_TYPE_REGULAR = 0; // 0x0
@@ -57,6 +58,15 @@
     field public static final int STATUS_SUCCESS_SET_EPHEMERAL = 3; // 0x3
   }
 
+  public final class StartUserInBackgroundResult {
+    method public int getStatus();
+    method public boolean isSuccess();
+    field public static final int STATUS_FAILURE_GENERIC = 100; // 0x64
+    field public static final int STATUS_FAILURE_USER_DOES_NOT_EXIST = 3; // 0x3
+    field public static final int STATUS_SUCCESS = 1; // 0x1
+    field public static final int STATUS_SUCCESS_CURRENT_USER = 2; // 0x2
+  }
+
 }
 
 package android.car.content.pm {
diff --git a/car-lib/src/android/car/Car.java b/car-lib/src/android/car/Car.java
index d74d44b..db85829 100644
--- a/car-lib/src/android/car/Car.java
+++ b/car-lib/src/android/car/Car.java
@@ -54,6 +54,7 @@
 import android.car.navigation.CarNavigationStatusManager;
 import android.car.occupantawareness.OccupantAwarenessManager;
 import android.car.storagemonitoring.CarStorageMonitoringManager;
+import android.car.telemetry.CarTelemetryManager;
 import android.car.test.CarTestManagerBinderWrapper;
 import android.car.user.CarUserManager;
 import android.car.vms.VmsClientManager;
@@ -364,6 +365,14 @@
     @SystemApi
     public static final String CAR_EVS_SERVICE = "car_evs_service";
 
+    /**
+     * Service name for {@link android.car.telemetry.CarTelemetryManager}
+     *
+     * @hide
+     */
+    @OptionalFeature
+    public static final String CAR_TELEMETRY_SERVICE = "car_telemetry_service";
+
     /** Permission necessary to access car's mileage information.
      *  @hide
      */
@@ -816,6 +825,14 @@
             "android.car.permission.MONITOR_CAR_EVS_STATUS";
 
     /**
+     * Permission necessary to use the CarTelemetryService.
+     *
+     * @hide
+     */
+    public static final String PERMISSION_USE_CAR_TELEMETRY_SERVICE =
+            "android.car.permission.USE_CAR_TELEMETRY_SERVICE";
+
+    /**
      * Type of car connection: platform runs directly in car.
      *
      * @deprecated connection type constants are no longer used
@@ -1880,6 +1897,9 @@
             case CAR_EVS_SERVICE:
                 manager = new CarEvsManager(this, binder);
                 break;
+            case CAR_TELEMETRY_SERVICE:
+                manager = new CarTelemetryManager(this, binder);
+                break;
             default:
                 // Experimental or non-existing
                 String className = null;
diff --git a/car-lib/src/android/car/admin/CarDevicePolicyManager.java b/car-lib/src/android/car/admin/CarDevicePolicyManager.java
index 4ed2833..e60d60d 100644
--- a/car-lib/src/android/car/admin/CarDevicePolicyManager.java
+++ b/car-lib/src/android/car/admin/CarDevicePolicyManager.java
@@ -28,6 +28,7 @@
 import android.car.CarManagerBase;
 import android.car.user.UserCreationResult;
 import android.car.user.UserRemovalResult;
+import android.car.user.UserStartResult;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.UserHandle;
@@ -203,6 +204,45 @@
         }
     }
 
+    /**
+     * Starts a user in the background.
+     *
+     * @param user identification of the user to be started.
+     *
+     * @return whether the user was successfully started.
+     *
+     * @hide
+     */
+    @TestApi
+    @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
+            android.Manifest.permission.CREATE_USERS})
+    @NonNull
+    public StartUserInBackgroundResult startUserInBackground(@NonNull UserHandle user) {
+        Objects.requireNonNull(user, "user cannot be null");
+
+        int userId = user.getIdentifier();
+        int uid = myUid();
+        EventLog.writeEvent(EventLogTags.CAR_DP_MGR_START_USER_IN_BACKGROUND_REQ, uid, userId);
+        int status = StartUserInBackgroundResult.STATUS_FAILURE_GENERIC;
+        try {
+            AndroidFuture<UserStartResult> future = new AndroidFuture<>();
+            mService.startUserInBackground(userId, future);
+            UserStartResult result = future.get(DEVICE_POLICY_MANAGER_TIMEOUT_MS,
+                    TimeUnit.MILLISECONDS);
+            status = result.getStatus();
+            return new StartUserInBackgroundResult(status);
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+            return new StartUserInBackgroundResult(status);
+        } catch (ExecutionException | TimeoutException e) {
+            return new StartUserInBackgroundResult(status);
+        } catch (RemoteException e) {
+            return handleRemoteExceptionFromCarService(e, new StartUserInBackgroundResult(status));
+        } finally {
+            EventLog.writeEvent(EventLogTags.CAR_DP_MGR_START_USER_IN_BACKGROUND_RESP, uid, status);
+        }
+    }
+
     /** @hide */
     @Override
     public void onCarDisconnected() {
diff --git a/car-lib/src/android/car/admin/ICarDevicePolicyService.aidl b/car-lib/src/android/car/admin/ICarDevicePolicyService.aidl
index 3d9bec1..88b42b1 100644
--- a/car-lib/src/android/car/admin/ICarDevicePolicyService.aidl
+++ b/car-lib/src/android/car/admin/ICarDevicePolicyService.aidl
@@ -16,12 +16,16 @@
 
 package android.car.admin;
 
-import android.car.user.UserRemovalResult;
 import android.car.user.UserCreationResult;
+import android.car.user.UserRemovalResult;
+import android.car.user.UserStartResult;
+import android.car.user.UserStopResult;
 import com.android.internal.infra.AndroidFuture;
 
 /** @hide */
 interface ICarDevicePolicyService {
     void removeUser(int userId, in AndroidFuture<UserRemovalResult> receiver);
     void createUser(String name, int flags, in AndroidFuture<UserCreationResult> receiver);
+    void startUserInBackground(int userId, in AndroidFuture<UserStartResult> receiver);
+    void stopUser(int userId, in AndroidFuture<UserStopResult> receiver);
 }
diff --git a/car-lib/src/android/car/admin/StartUserInBackgroundResult.java b/car-lib/src/android/car/admin/StartUserInBackgroundResult.java
new file mode 100644
index 0000000..11d276e
--- /dev/null
+++ b/car-lib/src/android/car/admin/StartUserInBackgroundResult.java
@@ -0,0 +1,115 @@
+/*
+ * 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.admin;
+
+import android.annotation.IntDef;
+import android.annotation.TestApi;
+import android.car.user.UserStartResult;
+import android.util.DebugUtils;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Result of a {@link CarDevicePolicyManager#startUserInBackground operation.
+ *
+ * @hide
+ */
+@TestApi
+public final class StartUserInBackgroundResult {
+
+    /**
+     * User was started.
+     */
+    public static final int STATUS_SUCCESS = 1;
+
+    /**
+     * User was the current user.
+     */
+    public static final int STATUS_SUCCESS_CURRENT_USER = 2;
+
+    /**
+     * User was not started because it does not exist.
+     */
+    public static final int STATUS_FAILURE_USER_DOES_NOT_EXIST = 3;
+
+    /**
+     * User was not started for some other reason not described above.
+     */
+    public static final int STATUS_FAILURE_GENERIC = 100;
+
+    /** @hide */
+    @IntDef(prefix = "STATUS_", value = {
+            STATUS_SUCCESS,
+            STATUS_SUCCESS_CURRENT_USER,
+            STATUS_FAILURE_USER_DOES_NOT_EXIST,
+            STATUS_FAILURE_GENERIC
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Status {
+    }
+
+    private final @Status int mStatus;
+
+    /** @hide */
+    @VisibleForTesting
+    public StartUserInBackgroundResult(@UserStartResult.Status int status) {
+        switch(status) {
+            case UserStartResult.STATUS_SUCCESSFUL:
+                mStatus = STATUS_SUCCESS;
+                break;
+            case UserStartResult.STATUS_SUCCESSFUL_USER_IS_CURRENT_USER:
+                mStatus = STATUS_SUCCESS_CURRENT_USER;
+                break;
+            case UserStartResult.STATUS_USER_DOES_NOT_EXIST:
+                mStatus = STATUS_FAILURE_USER_DOES_NOT_EXIST;
+                break;
+            default:
+                mStatus = STATUS_FAILURE_GENERIC;
+        }
+    }
+
+    /**
+     * Gets the specific result of the operation.
+     *
+     * @return either {@link StartUserInBackgroundResult#STATUS_SUCCESS},
+     *         {@link StartUserInBackgroundResult#STATUS_SUCCESS_CURRENT_USER},
+     *         {@link StartUserInBackgroundResult#STATUS_FAILURE_USER_DOES_NOT_EXIST}, or
+     *         {@link StartUserInBackgroundResult#STATUS_FAILURE_GENERIC}.
+     */
+    public @Status int getStatus() {
+        return mStatus;
+    }
+
+    /**
+     * Gets whether the operation was successful or not.
+     */
+    public boolean isSuccess() {
+        return mStatus == STATUS_SUCCESS || mStatus == STATUS_SUCCESS_CURRENT_USER;
+    }
+
+    @Override
+    public String toString() {
+        return "StartUserInBackgroundResult[" + statusToString(mStatus) + "]";
+    }
+
+    private static String statusToString(int status) {
+        return DebugUtils.valueToString(StartUserInBackgroundResult.class, "STATUS_", status);
+    }
+}
diff --git a/car-lib/src/android/car/telemetry/CarTelemetryManager.java b/car-lib/src/android/car/telemetry/CarTelemetryManager.java
new file mode 100644
index 0000000..24d787b
--- /dev/null
+++ b/car-lib/src/android/car/telemetry/CarTelemetryManager.java
@@ -0,0 +1,172 @@
+/*
+ * 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.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.car.Car;
+import android.car.CarManagerBase;
+import android.car.annotation.RequiredFeature;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.lang.ref.WeakReference;
+import java.util.concurrent.Executor;
+
+/**
+ * Provides an application interface for interacting with the Car Telemetry Service.
+ *
+ * @hide
+ */
+@RequiredFeature(Car.CAR_TELEMETRY_SERVICE)
+public final class CarTelemetryManager extends CarManagerBase {
+
+    private static final boolean DEBUG = false;
+    private static final String TAG = CarTelemetryManager.class.getSimpleName();
+
+    private final CarTelemetryServiceListener mCarTelemetryServiceListener =
+            new CarTelemetryServiceListener(this);
+    private final ICarTelemetryService mService;
+    private final Object mLock = new Object();
+
+    @GuardedBy("mLock")
+    private CarTelemetryResultsListener mResultsListener;
+    @GuardedBy("mLock")
+    private Executor mExecutor;
+
+    /**
+     * Application registers {@link CarTelemetryResultsListener} object to receive data from
+     * {@link com.android.car.telemetry.CarTelemetryService}.
+     *
+     * @hide
+     */
+    public interface CarTelemetryResultsListener {
+        /**
+         * Called by {@link com.android.car.telemetry.CarTelemetryService} to send data to
+         * the client.
+         * TODO(b/184964661): Publish the documentation for the format of the results.
+         *
+         * @param data the serialized car telemetry results.
+         */
+        void onDataReceived(@NonNull byte[] data);
+    }
+
+    /**
+     * Class implementing the listener interface
+     * {@link com.android.car.ICarTelemetryServiceListener} to receive telemetry results.
+     */
+    private static final class CarTelemetryServiceListener
+            extends ICarTelemetryServiceListener.Stub {
+        private WeakReference<CarTelemetryManager> mManager;
+
+        private CarTelemetryServiceListener(CarTelemetryManager manager) {
+            mManager = new WeakReference<>(manager);
+        }
+
+        @Override
+        public void onDataReceived(@NonNull byte[] data) {
+            CarTelemetryManager manager = mManager.get();
+            if (manager == null) {
+                return;
+            }
+            manager.onDataReceived(data);
+        }
+    }
+
+    private void onDataReceived(byte[] data) {
+        synchronized (mLock) {
+            mExecutor.execute(() -> mResultsListener.onDataReceived(data));
+        }
+    }
+
+    /**
+     * Gets an instance of CarTelemetryManager.
+     *
+     * CarTelemetryManager manages {@link com.android.car.telemetry.CarTelemetryService} and
+     * provides APIs so the client can use the car telemetry service.
+     *
+     * There is only one client to this manager, which is OEM's cloud application. It uses the
+     * APIs to send config to and receive data from CarTelemetryService.
+     *
+     * @hide
+     */
+    public CarTelemetryManager(Car car, IBinder service) {
+        super(car);
+        mService = ICarTelemetryService.Stub.asInterface(service);
+        if (DEBUG) {
+            Slog.d(TAG, "starting car telemetry manager");
+        }
+    }
+
+    /** @hide */
+    @Override
+    public void onCarDisconnected() {
+        synchronized (mLock) {
+            mResultsListener = null;
+            mExecutor = null;
+        }
+    }
+
+    /**
+     * Registers a listener with {@link com.android.car.telemetry.CarTelemetryService} for client
+     * to receive script execution results.
+     *
+     * @param listener to received data from {@link com.android.car.telemetry.CarTelemetryService}.
+     * @throws IllegalStateException if the listener is already set.
+     *
+     * @hide
+     */
+    @RequiresPermission(Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE)
+    public void setListener(@NonNull @CallbackExecutor Executor executor,
+            @NonNull CarTelemetryResultsListener listener) {
+        synchronized (mLock) {
+            if (mResultsListener != null) {
+                throw new IllegalStateException(
+                        "Attempting to set a listener that is already set.");
+            }
+            mExecutor = executor;
+            mResultsListener = listener;
+        }
+        try {
+            mService.setListener(mCarTelemetryServiceListener);
+        } catch (RemoteException e) {
+            handleRemoteExceptionFromCarService(e);
+        }
+    }
+
+    /**
+     * Unregisters the listener from {@link com.android.car.telemetry.CarTelemetryService}.
+     *
+     * @hide
+     */
+    @RequiresPermission(Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE)
+    public void clearListener() {
+        synchronized (mLock) {
+            mResultsListener = null;
+            mExecutor = null;
+        }
+        try {
+            mService.clearListener();
+        } catch (RemoteException e) {
+            handleRemoteExceptionFromCarService(e);
+        }
+    }
+}
diff --git a/car-lib/src/android/car/telemetry/ICarTelemetryService.aidl b/car-lib/src/android/car/telemetry/ICarTelemetryService.aidl
new file mode 100644
index 0000000..e2eb3e9
--- /dev/null
+++ b/car-lib/src/android/car/telemetry/ICarTelemetryService.aidl
@@ -0,0 +1,21 @@
+package android.car.telemetry;
+
+import android.car.telemetry.ICarTelemetryServiceListener;
+
+/**
+ * Internal binder interface for {@code CarTelemetryService}, used by {@code CarTelemetryManager}.
+ *
+ * @hide
+ */
+interface ICarTelemetryService {
+
+    /**
+     * Registers a listener with CarTelemetryService for the service to send data to cloud app.
+     */
+    void setListener(in ICarTelemetryServiceListener listener);
+
+    /**
+     * Clears the listener registered with CarTelemetryService.
+     */
+    void clearListener();
+}
\ No newline at end of file
diff --git a/car-lib/src/android/car/telemetry/ICarTelemetryServiceListener.aidl b/car-lib/src/android/car/telemetry/ICarTelemetryServiceListener.aidl
new file mode 100644
index 0000000..6ee574f
--- /dev/null
+++ b/car-lib/src/android/car/telemetry/ICarTelemetryServiceListener.aidl
@@ -0,0 +1,37 @@
+/*
+ * 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 java.util.List;
+
+/**
+ * Binder interface implemented by {@code CarTelemetryManager}. Enables sending results from
+ * {@code CarTelemetryService} to {@code CarTelemetryManager}.
+ *
+ * @hide
+ */
+oneway interface ICarTelemetryServiceListener {
+
+    /**
+     * Called by {@code CarTelemetryService} when there is a list of data to send to the
+     * cloud app.
+     *
+     * @param data the serialized bytes of a message, e.g. the script execution results
+     * or errors.
+     */
+    void onDataReceived(in byte[] data);
+}
\ No newline at end of file
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/user/UserStartResult.aidl b/car-lib/src/android/car/user/UserStartResult.aidl
new file mode 100644
index 0000000..d5333ec
--- /dev/null
+++ b/car-lib/src/android/car/user/UserStartResult.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.user;
+
+parcelable UserStartResult;
diff --git a/car-lib/src/android/car/user/UserStartResult.java b/car-lib/src/android/car/user/UserStartResult.java
new file mode 100644
index 0000000..d1a5d32
--- /dev/null
+++ b/car-lib/src/android/car/user/UserStartResult.java
@@ -0,0 +1,242 @@
+/*
+ * 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.user;
+
+import android.os.Parcelable;
+
+import com.android.internal.util.DataClass;
+
+/**
+ * User start results.
+ *
+ * @hide
+ */
+@DataClass(
+        genToString = true,
+        genHiddenConstructor = true,
+        genHiddenConstDefs = true)
+public final class UserStartResult implements Parcelable, OperationResult {
+
+    /**
+    * When user start is successful.
+    *
+    * @hide
+    */
+    public static final int STATUS_SUCCESSFUL = CommonResults.STATUS_SUCCESSFUL;
+
+    /**
+    * When user start failed.
+    *
+    * @hide
+    */
+    public static final int STATUS_ANDROID_FAILURE = CommonResults.STATUS_ANDROID_FAILURE;
+
+     /**
+     * When user to start is same as current user.
+     *
+     * @hide
+     */
+    public static final int STATUS_SUCCESSFUL_USER_IS_CURRENT_USER =
+            CommonResults.LAST_COMMON_STATUS + 1;
+
+    /**
+     * When user to start does not exist.
+     *
+     * @hide
+     */
+    public static final int STATUS_USER_DOES_NOT_EXIST = CommonResults.LAST_COMMON_STATUS + 2;
+
+    /**
+    * Gets the user start result status.
+    *
+    * @return either {@link UserStartRsult#STATUS_SUCCESSFUL},
+    *         {@link UserStartResult#STATUS_SUCCESSFUL_USER_IS_CURRENT_USER},
+    *         {@link UserStartResult#STATUS_ANDROID_FAILURE},
+    *         {@link UserStartResult#STATUS_USER_DOES_NOT_EXIST}, or
+    */
+    private final @Status int mStatus;
+
+    @Override
+    public boolean isSuccess() {
+        return mStatus == STATUS_SUCCESSFUL || mStatus == STATUS_SUCCESSFUL_USER_IS_CURRENT_USER;
+    }
+
+
+    // Code below generated by codegen v1.0.23.
+    //
+    // DO NOT MODIFY!
+    // CHECKSTYLE:OFF Generated code
+    //
+    // To regenerate run:
+    // $ codegen $ANDROID_BUILD_TOP/packages/services/Car/car-lib/src/android/car/user/UserStartResult.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
+
+
+    /** @hide */
+    @android.annotation.IntDef(prefix = "STATUS_", value = {
+        STATUS_SUCCESSFUL,
+        STATUS_ANDROID_FAILURE,
+        STATUS_SUCCESSFUL_USER_IS_CURRENT_USER,
+        STATUS_USER_DOES_NOT_EXIST
+    })
+    @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE)
+    @DataClass.Generated.Member
+    public @interface Status {}
+
+    /** @hide */
+    @DataClass.Generated.Member
+    public static String statusToString(@Status int value) {
+        switch (value) {
+            case STATUS_SUCCESSFUL:
+                    return "STATUS_SUCCESSFUL";
+            case STATUS_ANDROID_FAILURE:
+                    return "STATUS_ANDROID_FAILURE";
+            case STATUS_SUCCESSFUL_USER_IS_CURRENT_USER:
+                    return "STATUS_SUCCESSFUL_USER_IS_CURRENT_USER";
+            case STATUS_USER_DOES_NOT_EXIST:
+                    return "STATUS_USER_DOES_NOT_EXIST";
+            default: return Integer.toHexString(value);
+        }
+    }
+
+    /**
+     * Creates a new UserStartResult.
+     *
+     * @param status
+     *   Gets the user start result status.
+     *
+     *   @return either {@link UserStartRsult#STATUS_SUCCESSFUL},
+     *           {@link UserStartResult#STATUS_ANDROID_FAILURE},
+     *           {@link UserStartResult#STATUS_SUCCESSFUL_USER_IS_CURRENT_USER},
+     *           {@link UserStartResult#STATUS_USER_DOES_NOT_EXIST}, or
+     * @hide
+     */
+    @DataClass.Generated.Member
+    public UserStartResult(
+            @Status int status) {
+        this.mStatus = status;
+
+        if (!(mStatus == STATUS_SUCCESSFUL)
+                && !(mStatus == STATUS_ANDROID_FAILURE)
+                && !(mStatus == STATUS_SUCCESSFUL_USER_IS_CURRENT_USER)
+                && !(mStatus == STATUS_USER_DOES_NOT_EXIST)) {
+            throw new java.lang.IllegalArgumentException(
+                    "status was " + mStatus + " but must be one of: "
+                            + "STATUS_SUCCESSFUL(" + STATUS_SUCCESSFUL + "), "
+                            + "STATUS_ANDROID_FAILURE(" + STATUS_ANDROID_FAILURE + "), "
+                            + "STATUS_SUCCESSFUL_USER_IS_CURRENT_USER(" + STATUS_SUCCESSFUL_USER_IS_CURRENT_USER + "), "
+                            + "STATUS_USER_DOES_NOT_EXIST(" + STATUS_USER_DOES_NOT_EXIST + ")");
+        }
+
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    /**
+     * Gets the user start result status.
+     *
+     * @return either {@link UserStartRsult#STATUS_SUCCESSFUL},
+     *         {@link UserStartResult#STATUS_ANDROID_FAILURE},
+     *         {@link UserStartResult#STATUS_SUCCESSFUL_USER_IS_CURRENT_USER},
+     *         {@link UserStartResult#STATUS_USER_DOES_NOT_EXIST}, or
+     */
+    @DataClass.Generated.Member
+    public @Status int getStatus() {
+        return mStatus;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public String toString() {
+        // You can override field toString logic by defining methods like:
+        // String fieldNameToString() { ... }
+
+        return "UserStartResult { " +
+                "status = " + statusToString(mStatus) +
+        " }";
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public void writeToParcel(@android.annotation.NonNull android.os.Parcel dest, int flags) {
+        // You can override field parcelling by defining methods like:
+        // void parcelFieldName(Parcel dest, int flags) { ... }
+
+        dest.writeInt(mStatus);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int describeContents() { return 0; }
+
+    /** @hide */
+    @SuppressWarnings({"unchecked", "RedundantCast"})
+    @DataClass.Generated.Member
+    /* package-private */ UserStartResult(@android.annotation.NonNull android.os.Parcel in) {
+        // You can override field unparcelling by defining methods like:
+        // static FieldType unparcelFieldName(Parcel in) { ... }
+
+        int status = in.readInt();
+
+        this.mStatus = status;
+
+        if (!(mStatus == STATUS_SUCCESSFUL)
+                && !(mStatus == STATUS_ANDROID_FAILURE)
+                && !(mStatus == STATUS_SUCCESSFUL_USER_IS_CURRENT_USER)
+                && !(mStatus == STATUS_USER_DOES_NOT_EXIST)) {
+            throw new java.lang.IllegalArgumentException(
+                    "status was " + mStatus + " but must be one of: "
+                            + "STATUS_SUCCESSFUL(" + STATUS_SUCCESSFUL + "), "
+                            + "STATUS_ANDROID_FAILURE(" + STATUS_ANDROID_FAILURE + "), "
+                            + "STATUS_SUCCESSFUL_USER_IS_CURRENT_USER(" + STATUS_SUCCESSFUL_USER_IS_CURRENT_USER + "), "
+                            + "STATUS_USER_DOES_NOT_EXIST(" + STATUS_USER_DOES_NOT_EXIST + ")");
+        }
+
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    @DataClass.Generated.Member
+    public static final @android.annotation.NonNull Parcelable.Creator<UserStartResult> CREATOR
+            = new Parcelable.Creator<UserStartResult>() {
+        @Override
+        public UserStartResult[] newArray(int size) {
+            return new UserStartResult[size];
+        }
+
+        @Override
+        public UserStartResult createFromParcel(@android.annotation.NonNull android.os.Parcel in) {
+            return new UserStartResult(in);
+        }
+    };
+
+    @DataClass.Generated(
+            time = 1618957103487L,
+            codegenVersion = "1.0.23",
+            sourceFile = "packages/services/Car/car-lib/src/android/car/user/UserStartResult.java",
+            inputSignatures = "public static final  int STATUS_SUCCESSFUL\npublic static final  int STATUS_ANDROID_FAILURE\npublic static final  int STATUS_SUCCESSFUL_USER_IS_CURRENT_USER\npublic static final  int STATUS_USER_DOES_NOT_EXIST\nprivate final @android.car.user.UserStartResult.Status int mStatus\npublic @java.lang.Override boolean isSuccess()\nclass UserStartResult extends java.lang.Object implements [android.os.Parcelable, android.car.user.OperationResult]\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstructor=true, genHiddenConstDefs=true)")
+    @Deprecated
+    private void __metadata() {}
+
+
+    //@formatter:on
+    // End of generated code
+
+}
diff --git a/car-lib/src/android/car/user/UserStopResult.aidl b/car-lib/src/android/car/user/UserStopResult.aidl
new file mode 100644
index 0000000..d90eb46
--- /dev/null
+++ b/car-lib/src/android/car/user/UserStopResult.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.user;
+
+parcelable UserStopResult;
\ No newline at end of file
diff --git a/car-lib/src/android/car/user/UserStopResult.java b/car-lib/src/android/car/user/UserStopResult.java
new file mode 100644
index 0000000..5f17220
--- /dev/null
+++ b/car-lib/src/android/car/user/UserStopResult.java
@@ -0,0 +1,262 @@
+/*
+ * 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.user;
+
+import android.annotation.IntDef;
+import android.os.Parcelable;
+
+import com.android.internal.util.DataClass;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * User remove result.
+ *
+ * @hide
+ */
+@DataClass(
+        genToString = true,
+        genHiddenConstructor = true,
+        genHiddenConstDefs = true)
+public final class UserStopResult implements Parcelable, OperationResult {
+
+    /**
+     * When user stop is successful.
+     */
+    public static final int STATUS_SUCCESSFUL = CommonResults.STATUS_SUCCESSFUL;
+
+    /**
+     * When user stop fails.
+     */
+    public static final int STATUS_ANDROID_FAILURE = CommonResults.STATUS_ANDROID_FAILURE;
+
+     /**
+     * When user to stop doesn't exits.
+     */
+    public static final int STATUS_USER_DOES_NOT_EXIST = CommonResults.LAST_COMMON_STATUS + 1;
+
+    /**
+     * When user to stop is the system user.
+     */
+    public static final int STATUS_FAILURE_SYSTEM_USER = CommonResults.LAST_COMMON_STATUS + 2;
+
+    /**
+     * When user to stop is the current user.
+     */
+    public static final int STATUS_FAILURE_CURRENT_USER = CommonResults.LAST_COMMON_STATUS + 3;
+
+     /**
+     * Gets the user switch result status.
+     *
+     * @return either {@link UserStopResult#STATUS_SUCCESSFUL},
+     * {@link UserStopResult#STATUS_ANDROID_FAILURE},
+     * {@link UserStopResult#STATUS_USER_DOES_NOT_EXIST},
+     * {@link UserStopResult#STATUS_FAILURE_SYSTEM_USER}, or
+     * {@link UserStopResult#STATUS_FAILURE_CURRENT_USER}.
+     */
+    private final @Status int mStatus;
+
+    /**
+     * Checks if the {@code status} represents a success status.
+     *
+     * @param status to check
+     * @return true for a success status
+     */
+    public static boolean isSuccess(@Status int status) {
+        return status == STATUS_SUCCESSFUL;
+    }
+
+    @Override
+    public boolean isSuccess() {
+        return isSuccess(mStatus);
+    }
+
+
+    // Code below generated by codegen v1.0.23.
+    //
+    // DO NOT MODIFY!
+    // CHECKSTYLE:OFF Generated code
+    //
+    // To regenerate run:
+    // $ codegen $ANDROID_BUILD_TOP/packages/services/Car/car-lib/src/android/car/user/UserStopResult.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
+
+
+    /** @hide */
+    @IntDef(prefix = "STATUS_", value = {
+        STATUS_SUCCESSFUL,
+        STATUS_ANDROID_FAILURE,
+        STATUS_USER_DOES_NOT_EXIST,
+        STATUS_FAILURE_SYSTEM_USER,
+        STATUS_FAILURE_CURRENT_USER
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @DataClass.Generated.Member
+    public @interface Status {}
+
+    /** @hide */
+    @DataClass.Generated.Member
+    public static String statusToString(@Status int value) {
+        switch (value) {
+            case STATUS_SUCCESSFUL:
+                    return "STATUS_SUCCESSFUL";
+            case STATUS_ANDROID_FAILURE:
+                    return "STATUS_ANDROID_FAILURE";
+            case STATUS_USER_DOES_NOT_EXIST:
+                    return "STATUS_USER_DOES_NOT_EXIST";
+            case STATUS_FAILURE_SYSTEM_USER:
+                    return "STATUS_FAILURE_SYSTEM_USER";
+            case STATUS_FAILURE_CURRENT_USER:
+                    return "STATUS_FAILURE_CURRENT_USER";
+            default: return Integer.toHexString(value);
+        }
+    }
+
+    /**
+     * Creates a new UserStopResult.
+     *
+     * @param status
+     *   Gets the user switch result status.
+     *
+     *   @return either {@link UserStopResult#STATUS_SUCCESSFUL},
+     *   {@link UserStopResult#STATUS_ANDROID_FAILURE},
+     *   {@link UserStopResult#STATUS_USER_DOES_NOT_EXIST},
+     *   {@link UserStopResult#STATUS_FAILURE_SYSTEM_USER}, or
+     *   {@link UserStopResult#STATUS_FAILURE_CURRENT_USER}.
+     * @hide
+     */
+    @DataClass.Generated.Member
+    public UserStopResult(
+            @Status int status) {
+        this.mStatus = status;
+
+        if (!(mStatus == STATUS_SUCCESSFUL)
+                && !(mStatus == STATUS_ANDROID_FAILURE)
+                && !(mStatus == STATUS_USER_DOES_NOT_EXIST)
+                && !(mStatus == STATUS_FAILURE_SYSTEM_USER)
+                && !(mStatus == STATUS_FAILURE_CURRENT_USER)) {
+            throw new java.lang.IllegalArgumentException(
+                    "status was " + mStatus + " but must be one of: "
+                            + "STATUS_SUCCESSFUL(" + STATUS_SUCCESSFUL + "), "
+                            + "STATUS_ANDROID_FAILURE(" + STATUS_ANDROID_FAILURE + "), "
+                            + "STATUS_USER_DOES_NOT_EXIST(" + STATUS_USER_DOES_NOT_EXIST + "), "
+                            + "STATUS_FAILURE_SYSTEM_USER(" + STATUS_FAILURE_SYSTEM_USER + "), "
+                            + "STATUS_FAILURE_CURRENT_USER(" + STATUS_FAILURE_CURRENT_USER + ")");
+        }
+
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    /**
+     * Gets the user switch result status.
+     *
+     * @return either {@link UserStopResult#STATUS_SUCCESSFUL},
+     * {@link UserStopResult#STATUS_ANDROID_FAILURE},
+     * {@link UserStopResult#STATUS_USER_DOES_NOT_EXIST},
+     * {@link UserStopResult#STATUS_FAILURE_SYSTEM_USER}, or
+     * {@link UserStopResult#STATUS_FAILURE_CURRENT_USER}.
+     */
+    @DataClass.Generated.Member
+    public @Status int getStatus() {
+        return mStatus;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public String toString() {
+        // You can override field toString logic by defining methods like:
+        // String fieldNameToString() { ... }
+
+        return "UserStopResult { " +
+                "status = " + statusToString(mStatus) +
+        " }";
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public void writeToParcel(@android.annotation.NonNull android.os.Parcel dest, int flags) {
+        // You can override field parcelling by defining methods like:
+        // void parcelFieldName(Parcel dest, int flags) { ... }
+
+        dest.writeInt(mStatus);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int describeContents() { return 0; }
+
+    /** @hide */
+    @SuppressWarnings({"unchecked", "RedundantCast"})
+    @DataClass.Generated.Member
+    /* package-private */ UserStopResult(@android.annotation.NonNull android.os.Parcel in) {
+        // You can override field unparcelling by defining methods like:
+        // static FieldType unparcelFieldName(Parcel in) { ... }
+
+        int status = in.readInt();
+
+        this.mStatus = status;
+
+        if (!(mStatus == STATUS_SUCCESSFUL)
+                && !(mStatus == STATUS_ANDROID_FAILURE)
+                && !(mStatus == STATUS_USER_DOES_NOT_EXIST)
+                && !(mStatus == STATUS_FAILURE_SYSTEM_USER)
+                && !(mStatus == STATUS_FAILURE_CURRENT_USER)) {
+            throw new java.lang.IllegalArgumentException(
+                    "status was " + mStatus + " but must be one of: "
+                            + "STATUS_SUCCESSFUL(" + STATUS_SUCCESSFUL + "), "
+                            + "STATUS_ANDROID_FAILURE(" + STATUS_ANDROID_FAILURE + "), "
+                            + "STATUS_USER_DOES_NOT_EXIST(" + STATUS_USER_DOES_NOT_EXIST + "), "
+                            + "STATUS_FAILURE_SYSTEM_USER(" + STATUS_FAILURE_SYSTEM_USER + "), "
+                            + "STATUS_FAILURE_CURRENT_USER(" + STATUS_FAILURE_CURRENT_USER + ")");
+        }
+
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    @DataClass.Generated.Member
+    public static final @android.annotation.NonNull Parcelable.Creator<UserStopResult> CREATOR
+            = new Parcelable.Creator<UserStopResult>() {
+        @Override
+        public UserStopResult[] newArray(int size) {
+            return new UserStopResult[size];
+        }
+
+        @Override
+        public UserStopResult createFromParcel(@android.annotation.NonNull android.os.Parcel in) {
+            return new UserStopResult(in);
+        }
+    };
+
+    @DataClass.Generated(
+            time = 1619209981496L,
+            codegenVersion = "1.0.23",
+            sourceFile = "packages/services/Car/car-lib/src/android/car/user/UserStopResult.java",
+            inputSignatures = "public static final  int STATUS_SUCCESSFUL\npublic static final  int STATUS_ANDROID_FAILURE\npublic static final  int STATUS_USER_DOES_NOT_EXIST\npublic static final  int STATUS_FAILURE_SYSTEM_USER\npublic static final  int STATUS_FAILURE_CURRENT_USER\nprivate final @android.car.user.UserStopResult.Status int mStatus\npublic static  boolean isSuccess(int)\npublic @java.lang.Override boolean isSuccess()\nclass UserStopResult extends java.lang.Object implements [android.os.Parcelable, android.car.user.OperationResult]\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstructor=true, genHiddenConstDefs=true)")
+    @Deprecated
+    private void __metadata() {}
+
+
+    //@formatter:on
+    // End of generated code
+
+}
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-lib/src/com/android/car/internal/common/EventLogTags.logtags b/car-lib/src/com/android/car/internal/common/EventLogTags.logtags
index 31ed2b1..f781285 100644
--- a/car-lib/src/com/android/car/internal/common/EventLogTags.logtags
+++ b/car-lib/src/com/android/car/internal/common/EventLogTags.logtags
@@ -90,6 +90,8 @@
 150122 car_user_svc_notify_internal_lifecycle_listener (listener_name|3),(event_type|1),(from_user_id|1),(to_user_id|1)
 150123 car_user_svc_pre_creation_requested (number_users|1),(number_guests|1)
 150124 car_user_svc_pre_creation_status (number_existing_users|1),(number_users_to_add|1),(number_users_to_remove|1),(number_existing_guests|1),(number_guests_to_add|1),(number_guests_to_remove|1),(number_invalid_users_to_remove|1)
+150125 car_user_svc_start_user_in_background_req (user_id|1)
+150126 car_user_svc_start_user_in_background_resp (user_id|1),(result|1)
 
 150140 car_user_hal_initial_user_info_req (request_id|1),(request_type|1),(timeout|1)
 150141 car_user_hal_initial_user_info_resp (request_id|1),(status|1),(action|1),(user_id|1),(flags|1),(safe_name|3),(user_locales|3)
@@ -127,3 +129,5 @@
 150201 car_dp_mgr_remove_user_resp (uid|1),(status|1)
 150202 car_dp_mgr_create_user_req (uid|1),(safe_name|3),(flags|1)
 150203 car_dp_mgr_create_user_resp (uid|1),(status|1)
+150204 car_dp_mgr_start_user_in_background_req (uid|1),(user_id|1)
+150205 car_dp_mgr_start_user_in_background_resp (uid|1),(status|1)
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-test-lib/src/android/car/testapi/CarTelemetryController.java b/car-test-lib/src/android/car/testapi/CarTelemetryController.java
new file mode 100644
index 0000000..1f57497
--- /dev/null
+++ b/car-test-lib/src/android/car/testapi/CarTelemetryController.java
@@ -0,0 +1,30 @@
+/*
+ * 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.testapi;
+
+/**
+ * Controller to manipulate and verify {@link android.car.telemetry.CarTelemetryManager} in
+ * unit tests.
+ */
+public interface CarTelemetryController {
+    /**
+     * Returns {@code true} if a {@link
+     * android.car.telemetry.CarTelemetryManager.CarTelemetryResultsListener} is
+     * registered with the manager, otherwise returns {@code false}.
+     */
+    boolean isListenerSet();
+}
diff --git a/car-test-lib/src/android/car/testapi/FakeCar.java b/car-test-lib/src/android/car/testapi/FakeCar.java
index 4a3a98a..0508ed0 100644
--- a/car-test-lib/src/android/car/testapi/FakeCar.java
+++ b/car-test-lib/src/android/car/testapi/FakeCar.java
@@ -24,7 +24,6 @@
 import android.car.diagnostic.ICarDiagnostic;
 import android.car.drivingstate.ICarDrivingState;
 import android.car.hardware.power.ICarPower;
-import android.car.media.ICarAudio;
 import android.car.storagemonitoring.ICarStorageMonitoring;
 import android.content.Context;
 import android.os.IBinder;
@@ -128,8 +127,15 @@
         return mService.mCarUxRestrictionService;
     }
 
+    /**
+     * Returns a test controller that can modify and query the underlying service for the {@link
+     * android.car.telemetry.CarTelemetryManager}.
+     */
+    public CarTelemetryController getCarTelemetryController() {
+        return mService.mCarTelemetry;
+    }
+
     private static class FakeCarService extends ICar.Stub {
-        @Mock ICarAudio.Stub mCarAudio;
         @Mock ICarPackageManager.Stub mCarPackageManager;
         @Mock ICarDiagnostic.Stub mCarDiagnostic;
         @Mock ICarPower.Stub mCarPower;
@@ -138,19 +144,23 @@
         @Mock ICarStorageMonitoring.Stub mCarStorageMonitoring;
         @Mock ICarDrivingState.Stub mCarDrivingState;
 
+        private final FakeCarAudioService mCarAudio;
         private final FakeAppFocusService mAppFocus;
         private final FakeCarPropertyService mCarProperty;
         private final FakeCarProjectionService mCarProjection;
         private final FakeInstrumentClusterNavigation mInstrumentClusterNavigation;
         private final FakeCarUxRestrictionsService mCarUxRestrictionService;
+        private final FakeCarTelemetryService mCarTelemetry;
 
         FakeCarService(Context context) {
             MockitoAnnotations.initMocks(this);
+            mCarAudio = new FakeCarAudioService();
             mAppFocus = new FakeAppFocusService(context);
             mCarProperty = new FakeCarPropertyService();
             mCarProjection = new FakeCarProjectionService(context);
             mInstrumentClusterNavigation = new FakeInstrumentClusterNavigation();
             mCarUxRestrictionService = new FakeCarUxRestrictionsService();
+            mCarTelemetry = new FakeCarTelemetryService();
         }
 
         @Override
@@ -193,6 +203,8 @@
                     return mCarDrivingState;
                 case Car.CAR_UX_RESTRICTION_SERVICE:
                     return mCarUxRestrictionService;
+                case Car.CAR_TELEMETRY_SERVICE:
+                    return mCarTelemetry;
                 default:
                     Log.w(TAG, "getCarService for unknown service:" + serviceName);
                     return null;
diff --git a/car-test-lib/src/android/car/testapi/FakeCarAudioService.java b/car-test-lib/src/android/car/testapi/FakeCarAudioService.java
new file mode 100644
index 0000000..7b0bb09
--- /dev/null
+++ b/car-test-lib/src/android/car/testapi/FakeCarAudioService.java
@@ -0,0 +1,146 @@
+/*
+ * 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.testapi;
+
+import android.car.media.CarAudioPatchHandle;
+import android.car.media.ICarAudio;
+import android.media.AudioDeviceAttributes;
+import android.os.IBinder;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Fake service that is used by {@link FakeCar} to provide an implementation of {@link ICarAudio}.
+ * The reason we couldn't use a mock version of this service is that {@link AudioDeviceAttributes}
+ * is annotated with @hide, and Mockito fails to create a mock instance.
+ */
+final class FakeCarAudioService extends ICarAudio.Stub {
+    @Override
+    public boolean isAudioFeatureEnabled(int feature) {
+        return false;
+    }
+
+    @Override
+    public void setGroupVolume(int zoneId, int groupId, int index, int flags) {
+    }
+
+    @Override
+    public int getGroupMaxVolume(int zoneId, int groupId) {
+        return 0;
+    }
+
+    @Override
+    public int getGroupMinVolume(int zoneId, int groupId) {
+        return 0;
+    }
+
+    @Override
+    public int getGroupVolume(int zoneId, int groupId) {
+        return 0;
+    }
+
+    @Override
+    public void setFadeTowardFront(float value) {
+    }
+
+    @Override
+    public void setBalanceTowardRight(float value) {
+    }
+
+    @Override
+    public String[] getExternalSources() {
+        return new String[] {};
+    }
+
+    @Override
+    public CarAudioPatchHandle createAudioPatch(String sourceAddress, int usage,
+            int gainInMillibels) {
+        return null;
+    }
+
+    @Override
+    public void releaseAudioPatch(CarAudioPatchHandle patch) {
+    }
+
+    @Override
+    public int getVolumeGroupCount(int zoneId) {
+        return 0;
+    }
+
+    @Override
+    public int getVolumeGroupIdForUsage(int zoneId, int usage) {
+        return 0;
+    }
+
+    @Override
+    public int[] getUsagesForVolumeGroupId(int zoneId, int groupId) {
+        return new int[] {};
+    }
+
+    @Override
+    public int[] getAudioZoneIds() {
+        return new int[] {};
+    }
+
+    @Override
+    public int getZoneIdForUid(int uid) {
+        return 0;
+    }
+
+    @Override
+    public boolean setZoneIdForUid(int zoneId, int uid) {
+        return false;
+    }
+
+    @Override
+    public boolean clearZoneIdForUid(int uid) {
+        return false;
+    }
+
+    @Override
+    public boolean isVolumeGroupMuted(int zoneId, int groupId) {
+        return false;
+    }
+
+    @Override
+    public void setVolumeGroupMute(int zoneId, int groupId, boolean mute, int flags) {
+    }
+
+    @Override
+    public String getOutputDeviceAddressForUsage(int zoneId, int usage) {
+        return "";
+    }
+
+    @Override
+    public List<AudioDeviceAttributes> getInputDevicesForZoneId(int zoneId) {
+        return Collections.emptyList();
+    }
+
+    @Override
+    public boolean isPlaybackOnVolumeGroupActive(int volumeGroupId, int audioZoneId) {
+        return false;
+    }
+
+    @Override
+    public void registerVolumeCallback(IBinder binder) {
+    }
+
+    @Override
+    public void unregisterVolumeCallback(IBinder binder) {
+    }
+}
diff --git a/car-test-lib/src/android/car/testapi/FakeCarTelemetryService.java b/car-test-lib/src/android/car/testapi/FakeCarTelemetryService.java
new file mode 100644
index 0000000..af8a2b4
--- /dev/null
+++ b/car-test-lib/src/android/car/testapi/FakeCarTelemetryService.java
@@ -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.
+ */
+
+package android.car.testapi;
+
+import android.car.telemetry.ICarTelemetryService;
+import android.car.telemetry.ICarTelemetryServiceListener;
+
+/**
+ * A fake implementation of {@link ICarTelemetryService.Stub} to facilitate the use of
+ * {@link android.car.telemetry.CarTelemetryManager} in external unit tests.
+ *
+ * @hide
+ */
+public class FakeCarTelemetryService extends ICarTelemetryService.Stub implements
+        CarTelemetryController {
+
+    private ICarTelemetryServiceListener mListener;
+
+    @Override
+    public void setListener(ICarTelemetryServiceListener listener) {
+        mListener = listener;
+    }
+
+    @Override
+    public void clearListener() {
+        mListener = null;
+    }
+
+    /**************************** CarTelemetryController impl ********************************/
+    @Override
+    public boolean isListenerSet() {
+        return mListener != null;
+    }
+}
diff --git a/car_product/build/car_base.mk b/car_product/build/car_base.mk
index 41408eb..2f9b344 100644
--- a/car_product/build/car_base.mk
+++ b/car_product/build/car_base.mk
@@ -65,6 +65,9 @@
 # EVS service
 include packages/services/Car/cpp/evs/manager/evsmanager.mk
 
+# Automotive Telemetry services
+include packages/services/Car/cpp/telemetry/products/telemetry.mk
+
 # EVS manager overrides cameraserver on automotive implementations so
 # we need to configure Camera API to not connect to it
 PRODUCT_PROPERTY_OVERRIDES += config.disable_cameraservice=true
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/computepipe/router/1.0/RouterSvc.cpp b/cpp/computepipe/router/1.0/RouterSvc.cpp
index 0b296e6..0edd0ef 100644
--- a/cpp/computepipe/router/1.0/RouterSvc.cpp
+++ b/cpp/computepipe/router/1.0/RouterSvc.cpp
@@ -17,7 +17,7 @@
 
 #include <android/binder_interface_utils.h>
 #include <android/binder_manager.h>
-#include <binder/IServiceManager.h>
+#include <log/log.h>
 
 #include "PipeQuery.h"
 #include "PipeRegistration.h"
diff --git a/cpp/evs/apps/default/config.json b/cpp/evs/apps/default/config.json
index 16148a7..dc2771c 100644
--- a/cpp/evs/apps/default/config.json
+++ b/cpp/evs/apps/default/config.json
@@ -8,7 +8,7 @@
   },
   "displays" : [
     {
-      "displayPort" : 136,
+      "displayPort" : 129,
       "frontRange" : 100,
       "rearRange" : 100
     },
diff --git a/cpp/evs/manager/1.1/VirtualCamera.cpp b/cpp/evs/manager/1.1/VirtualCamera.cpp
index 731b0cc..427666c 100644
--- a/cpp/evs/manager/1.1/VirtualCamera.cpp
+++ b/cpp/evs/manager/1.1/VirtualCamera.cpp
@@ -155,19 +155,9 @@
         } else if (mCaptureThread.joinable()) {
             // Keep forwarding frames as long as a capture thread is alive
             if (mFramesHeld.size() > 0 && mStream_1_1 != nullptr) {
-                // Pass this buffer through to our client
-                hardware::hidl_vec<BufferDesc_1_1> frames;
-                frames.resize(1);
-                auto pHwCamera = mHalCamera.begin()->second.promote();
-                if (pHwCamera != nullptr) {
-                    frames[0] = mFramesHeld[mHalCamera.begin()->first].back();
-                }
-
                 // Notify a new frame receipt
-                {
-                    std::lock_guard<std::mutex> lock(mFrameDeliveryMutex);
-                    mSourceCameras.erase(bufDesc.deviceId);
-                }
+                std::lock_guard<std::mutex> lock(mFrameDeliveryMutex);
+                mSourceCameras.erase(bufDesc.deviceId);
                 mFramesReadySignal.notify_all();
             }
         }
diff --git a/cpp/powerpolicy/server/src/CarPowerPolicyServer.cpp b/cpp/powerpolicy/server/src/CarPowerPolicyServer.cpp
index 8331612..4c9b260 100644
--- a/cpp/powerpolicy/server/src/CarPowerPolicyServer.cpp
+++ b/cpp/powerpolicy/server/src/CarPowerPolicyServer.cpp
@@ -495,8 +495,14 @@
             mPendingPowerPolicyId = policyId;
             return {};
         }
+        bool isPolicyApplied = isPowerPolicyAppliedLocked();
+        if (isPolicyApplied && mCurrentPowerPolicyMeta.powerPolicy->policyId == policyId) {
+            ALOGI("Applying policy skipped: the given policy(ID: %s) is the current policy",
+                  policyId.c_str());
+            return {};
+        }
         if (policyMeta->isPreemptive) {
-            if (isPowerPolicyAppliedLocked() && !mCurrentPowerPolicyMeta.isPreemptive) {
+            if (isPolicyApplied && !mCurrentPowerPolicyMeta.isPreemptive) {
                 mPendingPowerPolicyId = mCurrentPowerPolicyMeta.powerPolicy->policyId;
             }
             mIsPowerPolicyLocked = true;
diff --git a/cpp/surround_view/app/Android.bp b/cpp/surround_view/app/Android.bp
index 5145c5e..76753d3 100644
--- a/cpp/surround_view/app/Android.bp
+++ b/cpp/surround_view/app/Android.bp
@@ -23,6 +23,7 @@
     name: "sv_app",
 
     srcs: [
+        "SurroundViewAppCommon.cpp",
         "SurroundViewServiceCallback.cpp",
         "shader.cpp",
         "sv_app.cpp",
diff --git a/cpp/surround_view/app/SurroundViewAppCommon.cpp b/cpp/surround_view/app/SurroundViewAppCommon.cpp
new file mode 100644
index 0000000..b1a2792
--- /dev/null
+++ b/cpp/surround_view/app/SurroundViewAppCommon.cpp
@@ -0,0 +1,191 @@
+/*
+ * Copyright 2020 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 "SurroundViewAppCommon.h"
+
+namespace android {
+namespace hardware {
+namespace automotive {
+namespace sv {
+namespace app {
+
+bool run2dSurroundView(sp<ISurroundViewService> pSurroundViewService, sp<IEvsDisplay> pDisplay) {
+    LOG(INFO) << "Run 2d Surround View demo";
+
+    // Call HIDL API "start2dSession"
+    sp<ISurroundView2dSession> surroundView2dSession;
+
+    SvResult svResult;
+    pSurroundViewService->start2dSession(
+            [&surroundView2dSession, &svResult](const sp<ISurroundView2dSession>& session,
+                                                SvResult result) {
+                surroundView2dSession = session;
+                svResult = result;
+            });
+
+    if (surroundView2dSession == nullptr || svResult != SvResult::OK) {
+        LOG(ERROR) << "Failed to start2dSession";
+        return false;
+    } else {
+        LOG(INFO) << "start2dSession succeeded";
+    }
+
+    sp<SurroundViewServiceCallback> sv2dCallback =
+            new SurroundViewServiceCallback(pDisplay, surroundView2dSession);
+
+    // Start 2d stream with callback with default quality and resolution.
+    // The quality is defaulted to be HIGH_QUALITY, and the default resolution
+    // is set in the sv config file.
+    if (surroundView2dSession->startStream(sv2dCallback) != SvResult::OK) {
+        LOG(ERROR) << "Failed to start 2d stream";
+        return false;
+    }
+
+    const int kTotalViewingTimeSecs = 10;
+
+    // Let the SV algorithm run for HIGH_QUALITY until the wait time finishes
+    std::this_thread::sleep_for(std::chrono::seconds(kTotalViewingTimeSecs));
+
+    // Switch to low quality and lower resolution
+    Sv2dConfig config = {.width = kLowResolutionWidth, .blending = SvQuality::LOW};
+    if (surroundView2dSession->set2dConfig(config) != SvResult::OK) {
+        LOG(ERROR) << "Failed to set2dConfig";
+        return false;
+    }
+
+    // Let the SV algorithm run for LOW_QUALITY until the wait time finishes
+    std::this_thread::sleep_for(std::chrono::seconds(kTotalViewingTimeSecs));
+
+    // TODO(b/150412555): wait for the last frame
+    // Stop the 2d stream and session
+    surroundView2dSession->stopStream();
+
+    pSurroundViewService->stop2dSession(surroundView2dSession);
+    surroundView2dSession = nullptr;
+
+    LOG(INFO) << "SV 2D session finished.";
+
+    return true;
+};
+
+// Given a valid sv 3d session and pose, viewid and hfov parameters, sets the view.
+bool setView(sp<ISurroundView3dSession> surroundView3dSession, uint32_t viewId, uint32_t poseIndex,
+             float hfov) {
+    const View3d view3d = {
+            .viewId = viewId,
+            .pose =
+                    {
+                            .rotation = {.x = kPoseRot[poseIndex][0],
+                                         .y = kPoseRot[poseIndex][1],
+                                         .z = kPoseRot[poseIndex][2],
+                                         .w = kPoseRot[poseIndex][3]},
+                            .translation = {.x = kPoseTrans[poseIndex][0],
+                                            .y = kPoseTrans[poseIndex][1],
+                                            .z = kPoseTrans[poseIndex][2]},
+                    },
+            .horizontalFov = hfov,
+    };
+
+    const std::vector<View3d> views = {view3d};
+    if (surroundView3dSession->setViews(views) != SvResult::OK) {
+        return false;
+    }
+    return true;
+}
+
+bool run3dSurroundView(sp<ISurroundViewService> pSurroundViewService, sp<IEvsDisplay> pDisplay) {
+    LOG(INFO) << "Run 3d Surround View demo";
+
+    // Call HIDL API "start3dSession"
+    sp<ISurroundView3dSession> surroundView3dSession;
+
+    SvResult svResult;
+    pSurroundViewService->start3dSession(
+            [&surroundView3dSession, &svResult](const sp<ISurroundView3dSession>& session,
+                                                SvResult result) {
+                surroundView3dSession = session;
+                svResult = result;
+            });
+
+    if (surroundView3dSession == nullptr || svResult != SvResult::OK) {
+        LOG(ERROR) << "Failed to start3dSession";
+        return false;
+    } else {
+        LOG(INFO) << "start3dSession succeeded";
+    }
+
+    sp<SurroundViewServiceCallback> sv3dCallback =
+            new SurroundViewServiceCallback(pDisplay, surroundView3dSession);
+
+    // A view must be set before the 3d stream is started.
+    if (!setView(surroundView3dSession, /*viewId=*/0, /*poseIndex=*/0, kHorizontalFov)) {
+        LOG(ERROR) << "Failed to setView of pose index :" << 0;
+        return false;
+    }
+
+    // Start 3d stream with callback with default quality and resolution.
+    // The quality is defaulted to be HIGH_QUALITY, and the default resolution
+    // is set in the sv config file.
+    if (surroundView3dSession->startStream(sv3dCallback) != SvResult::OK) {
+        LOG(ERROR) << "Failed to start 3d stream";
+        return false;
+    }
+
+    // Let the SV algorithm run for 10 seconds for HIGH_QUALITY
+    const int kTotalViewingTimeSecs = 10;
+    const std::chrono::milliseconds perPoseSleepTimeMs(kTotalViewingTimeSecs * 1000 / kPoseCount);
+    // Iterate through the pre-set views.
+    for (uint32_t i = 0; i < kPoseCount; i++) {
+        if (!setView(surroundView3dSession, /*viewId=*/i, /*poseIndex=*/i, kHorizontalFov)) {
+            LOG(WARNING) << "Failed to setView of pose index :" << i;
+        }
+        std::this_thread::sleep_for(perPoseSleepTimeMs);
+    }
+
+    // Switch to low quality and lower resolution
+    Sv3dConfig config = {.width = kLowResolutionWidth,
+                         .height = kLowResolutionHeight,
+                         .carDetails = SvQuality::LOW};
+
+    if (surroundView3dSession->set3dConfig(config) != SvResult::OK) {
+        LOG(ERROR) << "Failed to set3dConfig";
+        return false;
+    }
+
+    // Let the SV algorithm run for 10 seconds for LOW_QUALITY
+    for (uint32_t i = 0; i < kPoseCount; i++) {
+        if (!setView(surroundView3dSession, i + kPoseCount, i, kHorizontalFov)) {
+            LOG(WARNING) << "Failed to setView of pose index :" << i;
+        }
+        std::this_thread::sleep_for(perPoseSleepTimeMs);
+    }
+
+    // TODO(b/150412555): wait for the last frame
+    // Stop the 3d stream and session
+    surroundView3dSession->stopStream();
+
+    pSurroundViewService->stop3dSession(surroundView3dSession);
+    surroundView3dSession = nullptr;
+
+    LOG(DEBUG) << "SV 3D session finished.";
+
+    return true;
+};
+}  // namespace app
+}  // namespace sv
+}  // namespace automotive
+}  // namespace hardware
+}  // namespace android
diff --git a/cpp/surround_view/app/SurroundViewAppCommon.h b/cpp/surround_view/app/SurroundViewAppCommon.h
new file mode 100644
index 0000000..04e59fa
--- /dev/null
+++ b/cpp/surround_view/app/SurroundViewAppCommon.h
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2020 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.
+ */
+#pragma once
+
+#include "SurroundViewServiceCallback.h"
+
+#include <android-base/logging.h>
+#include <android/hardware/automotive/evs/1.1/IEvsEnumerator.h>
+#include <android/hardware/automotive/sv/1.0/ISurroundView2dSession.h>
+#include <android/hardware/automotive/sv/1.0/ISurroundView3dSession.h>
+#include <android/hardware/automotive/sv/1.0/ISurroundViewService.h>
+#include <hidl/HidlTransportSupport.h>
+#include <utils/Log.h>
+#include <utils/StrongPointer.h>
+
+#include <stdio.h>
+
+#include <thread>
+
+using namespace android::hardware::automotive::sv::V1_0;
+using namespace android::hardware::automotive::evs::V1_1;
+
+namespace android {
+namespace hardware {
+namespace automotive {
+namespace sv {
+namespace app {
+
+const int kLowResolutionWidth = 120;
+const int kLowResolutionHeight = 90;
+
+enum DemoMode {
+    UNKNOWN,
+    DEMO_2D,
+    DEMO_3D,
+};
+
+const float kHorizontalFov = 90;
+
+// Number of views to generate.
+const uint32_t kPoseCount = 16;
+
+// Set of pose rotations expressed in quaternions.
+// Views are generated about a circle at a height about the car, point towards the center.
+const float kPoseRot[kPoseCount][4] = {{-0.251292, -0.251292, -0.660948, 0.660948},
+                                       {0.197439, 0.295488, 0.777193, -0.519304},
+                                       {0.135998, 0.328329, 0.86357, -0.357702},
+                                       {0.0693313, 0.348552, 0.916761, -0.182355},
+                                       {-7.76709e-09, 0.355381, 0.934722, 2.0429e-08},
+                                       {-0.0693313, 0.348552, 0.916761, 0.182355},
+                                       {-0.135998, 0.328329, 0.86357, 0.357702},
+                                       {-0.197439, 0.295488, 0.777193, 0.519304},
+                                       {-0.251292, 0.251292, 0.660948, 0.660948},
+                                       {-0.295488, 0.197439, 0.519304, 0.777193},
+                                       {-0.328329, 0.135998, 0.357702, 0.86357},
+                                       {-0.348552, 0.0693313, 0.182355, 0.916761},
+                                       {-0.355381, -2.11894e-09, -5.57322e-09, 0.934722},
+                                       {-0.348552, -0.0693313, -0.182355, 0.916761},
+                                       {-0.328329, -0.135998, -0.357702, 0.86357},
+                                       {-0.295488, -0.197439, -0.519304, 0.777193}};
+
+// Set of pose translations i.e. positions of the views.
+// Views are generated about a circle at a height about the car, point towards the center.
+const float kPoseTrans[kPoseCount][4] = {{4, 0, 2.5},
+                                         {3.69552, 1.53073, 2.5},
+                                         {2.82843, 2.82843, 2.5},
+                                         {1.53073, 3.69552, 2.5},
+                                         {-1.74846e-07, 4, 2.5},
+                                         {-1.53073, 3.69552, 2.5},
+                                         {-2.82843, 2.82843, 2.5},
+                                         {-3.69552, 1.53073, 2.5},
+                                         {-4, -3.49691e-07, 2.5},
+                                         {-3.69552, -1.53073, 2.5},
+                                         {-2.82843, -2.82843, 2.5},
+                                         {-1.53073, -3.69552, 2.5},
+                                         {4.76995e-08, -4, 2.5},
+                                         {1.53073, -3.69552, 2.5},
+                                         {2.82843, -2.82843, 2.5},
+                                         {3.69552, -1.53073, 2.5}};
+
+bool run2dSurroundView(sp<ISurroundViewService> pSurroundViewService, sp<IEvsDisplay> pDisplay);
+
+bool run3dSurroundView(sp<ISurroundViewService> pSurroundViewService, sp<IEvsDisplay> pDisplay);
+
+// Given a valid sv 3d session and pose, viewid and hfov parameters, sets the view.
+bool setView(sp<ISurroundView3dSession> surroundView3dSession, uint32_t viewId, uint32_t poseIndex,
+             float hfov);
+
+}  // namespace app
+}  // namespace sv
+}  // namespace automotive
+}  // namespace hardware
+}  // namespace android
diff --git a/cpp/surround_view/app/SurroundViewServiceCallback.cpp b/cpp/surround_view/app/SurroundViewServiceCallback.cpp
index 12dba98..a3ce519 100644
--- a/cpp/surround_view/app/SurroundViewServiceCallback.cpp
+++ b/cpp/surround_view/app/SurroundViewServiceCallback.cpp
@@ -13,7 +13,6 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 #include "SurroundViewServiceCallback.h"
 
 #include <android-base/logging.h>
@@ -441,15 +440,6 @@
             LOG(INFO) << "Successfully attached render target";
         }
 
-        // Call HIDL API "doneWithFrames" to return the ownership
-        // back to SV service
-        if (mSession == nullptr) {
-            LOG(ERROR) << "SurroundViewSession in callback is invalid";
-            return {};
-        } else {
-            mSession->doneWithFrames(svFramesDesc);
-        }
-
         // Render frame to EVS display
         LOG(INFO) << "Rendering to display buffer";
         sp<GraphicBuffer> graphicBuffer =
diff --git a/cpp/surround_view/app/SurroundViewServiceCallback.h b/cpp/surround_view/app/SurroundViewServiceCallback.h
index b2ab831..467c69a 100644
--- a/cpp/surround_view/app/SurroundViewServiceCallback.h
+++ b/cpp/surround_view/app/SurroundViewServiceCallback.h
@@ -13,6 +13,8 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+#pragma once
+
 #include <stdio.h>
 
 #include <utils/StrongPointer.h>
diff --git a/cpp/surround_view/app/sv_app.cpp b/cpp/surround_view/app/sv_app.cpp
index 43369c2..d07671d 100644
--- a/cpp/surround_view/app/sv_app.cpp
+++ b/cpp/surround_view/app/sv_app.cpp
@@ -14,17 +14,7 @@
  * limitations under the License.
  */
 
-#include <android-base/logging.h>
-#include <android/hardware/automotive/evs/1.1/IEvsEnumerator.h>
-#include <android/hardware/automotive/sv/1.0/ISurroundViewService.h>
-#include <android/hardware/automotive/sv/1.0/ISurroundView2dSession.h>
-#include <android/hardware/automotive/sv/1.0/ISurroundView3dSession.h>
-#include <hidl/HidlTransportSupport.h>
-#include <stdio.h>
-#include <utils/StrongPointer.h>
-#include <utils/Log.h>
-#include <thread>
-
+#include "SurroundViewAppCommon.h"
 #include "SurroundViewServiceCallback.h"
 
 // libhidl:
@@ -40,220 +30,7 @@
 
 using namespace android::hardware::automotive::sv::V1_0;
 using namespace android::hardware::automotive::evs::V1_1;
-
-const int kLowResolutionWidth = 120;
-const int kLowResolutionHeight = 90;
-
-enum DemoMode {
-    UNKNOWN,
-    DEMO_2D,
-    DEMO_3D,
-};
-
-const float kHorizontalFov = 90;
-
-// Number of views to generate.
-const uint32_t kPoseCount = 16;
-
-// Set of pose rotations expressed in quaternions.
-// Views are generated about a circle at a height about the car, point towards the center.
-const float kPoseRot[kPoseCount][4] = {
-    {-0.251292, -0.251292, -0.660948, 0.660948},
-    {0.197439, 0.295488, 0.777193, -0.519304},
-    {0.135998, 0.328329, 0.86357, -0.357702},
-    {0.0693313, 0.348552, 0.916761, -0.182355},
-    {-7.76709e-09, 0.355381, 0.934722, 2.0429e-08},
-    {-0.0693313, 0.348552, 0.916761, 0.182355},
-    {-0.135998, 0.328329, 0.86357, 0.357702},
-    {-0.197439, 0.295488, 0.777193, 0.519304},
-    {-0.251292, 0.251292, 0.660948, 0.660948},
-    {-0.295488, 0.197439, 0.519304, 0.777193},
-    {-0.328329, 0.135998, 0.357702, 0.86357},
-    {-0.348552, 0.0693313, 0.182355, 0.916761},
-    {-0.355381, -2.11894e-09, -5.57322e-09, 0.934722},
-    {-0.348552, -0.0693313, -0.182355, 0.916761},
-    {-0.328329, -0.135998, -0.357702, 0.86357},
-    {-0.295488, -0.197439, -0.519304, 0.777193}
-};
-
-// Set of pose translations i.e. positions of the views.
-// Views are generated about a circle at a height about the car, point towards the center.
-const float kPoseTrans[kPoseCount][4] = {
-    {4, 0, 2.5},
-    {3.69552, 1.53073, 2.5},
-    {2.82843, 2.82843, 2.5},
-    {1.53073, 3.69552, 2.5},
-    {-1.74846e-07, 4, 2.5},
-    {-1.53073, 3.69552, 2.5},
-    {-2.82843, 2.82843, 2.5},
-    {-3.69552, 1.53073, 2.5},
-    {-4, -3.49691e-07, 2.5},
-    {-3.69552, -1.53073, 2.5},
-    {-2.82843, -2.82843, 2.5},
-    {-1.53073, -3.69552, 2.5},
-    {4.76995e-08, -4, 2.5},
-    {1.53073, -3.69552, 2.5},
-    {2.82843, -2.82843, 2.5},
-    {3.69552, -1.53073, 2.5}
-};
-
-bool run2dSurroundView(sp<ISurroundViewService> pSurroundViewService,
-                       sp<IEvsDisplay> pDisplay) {
-    LOG(INFO) << "Run 2d Surround View demo";
-
-    // Call HIDL API "start2dSession"
-    sp<ISurroundView2dSession> surroundView2dSession;
-
-    SvResult svResult;
-    pSurroundViewService->start2dSession(
-        [&surroundView2dSession, &svResult](
-            const sp<ISurroundView2dSession>& session, SvResult result) {
-        surroundView2dSession = session;
-        svResult = result;
-    });
-
-    if (surroundView2dSession == nullptr || svResult != SvResult::OK) {
-        LOG(ERROR) << "Failed to start2dSession";
-        return false;
-    } else {
-        LOG(INFO) << "start2dSession succeeded";
-    }
-
-    sp<SurroundViewServiceCallback> sv2dCallback
-        = new SurroundViewServiceCallback(pDisplay, surroundView2dSession);
-
-    // Start 2d stream with callback
-    if (surroundView2dSession->startStream(sv2dCallback) != SvResult::OK) {
-        LOG(ERROR) << "Failed to start 2d stream";
-        return false;
-    }
-
-    // Let the SV algorithm run for 10 seconds for HIGH_QUALITY
-    std::this_thread::sleep_for(std::chrono::seconds(10));
-
-    // Switch to low quality and lower resolution
-    Sv2dConfig config;
-    config.width = kLowResolutionWidth;
-    config.blending = SvQuality::LOW;
-    if (surroundView2dSession->set2dConfig(config) != SvResult::OK) {
-        LOG(ERROR) << "Failed to set2dConfig";
-        return false;
-    }
-
-    // Let the SV algorithm run for 10 seconds for LOW_QUALITY
-    std::this_thread::sleep_for(std::chrono::seconds(10));
-
-    // TODO(b/150412555): wait for the last frame
-    // Stop the 2d stream and session
-    surroundView2dSession->stopStream();
-
-    pSurroundViewService->stop2dSession(surroundView2dSession);
-    surroundView2dSession = nullptr;
-
-    LOG(INFO) << "SV 2D session finished.";
-
-    return true;
-};
-
-// Given a valid sv 3d session and pose, viewid and hfov parameters, sets the view.
-bool setView(sp<ISurroundView3dSession> surroundView3dSession, uint32_t viewId,
-        uint32_t poseIndex, float hfov)
-{
-    const View3d view3d = {
-        .viewId = viewId,
-        .pose = {
-            .rotation = {.x=kPoseRot[poseIndex][0], .y=kPoseRot[poseIndex][1],
-                    .z=kPoseRot[poseIndex][2], .w=kPoseRot[poseIndex][3]},
-            .translation = {.x=kPoseTrans[poseIndex][0], .y=kPoseTrans[poseIndex][1],
-                    .z=kPoseTrans[poseIndex][2]},
-        },
-        .horizontalFov = hfov,
-    };
-
-    const std::vector<View3d> views = {view3d};
-    if (surroundView3dSession->setViews(views) != SvResult::OK) {
-        return false;
-    }
-    return true;
-}
-
-bool run3dSurroundView(sp<ISurroundViewService> pSurroundViewService,
-                       sp<IEvsDisplay> pDisplay) {
-    LOG(INFO) << "Run 3d Surround View demo";
-
-    // Call HIDL API "start3dSession"
-    sp<ISurroundView3dSession> surroundView3dSession;
-
-    SvResult svResult;
-    pSurroundViewService->start3dSession(
-        [&surroundView3dSession, &svResult](
-            const sp<ISurroundView3dSession>& session, SvResult result) {
-        surroundView3dSession = session;
-        svResult = result;
-    });
-
-    if (surroundView3dSession == nullptr || svResult != SvResult::OK) {
-        LOG(ERROR) << "Failed to start3dSession";
-        return false;
-    } else {
-        LOG(INFO) << "start3dSession succeeded";
-    }
-
-    sp<SurroundViewServiceCallback> sv3dCallback
-        = new SurroundViewServiceCallback(pDisplay, surroundView3dSession);
-
-    // A view must be set before the 3d stream is started.
-    if (!setView(surroundView3dSession, 0, 0, kHorizontalFov)) {
-        LOG(ERROR) << "Failed to setView of pose index :" << 0;
-        return false;
-    }
-
-    // Start 3d stream with callback
-    if (surroundView3dSession->startStream(sv3dCallback) != SvResult::OK) {
-        LOG(ERROR) << "Failed to start 3d stream";
-        return false;
-    }
-
-    // Let the SV algorithm run for 10 seconds for HIGH_QUALITY
-    const int totalViewingTimeSecs = 10;
-    const std::chrono::milliseconds
-            perPoseSleepTimeMs(totalViewingTimeSecs * 1000 / kPoseCount);
-    for(uint32_t i = 1; i < kPoseCount; i++) {
-        if (!setView(surroundView3dSession, i, i, kHorizontalFov)) {
-            LOG(WARNING) << "Failed to setView of pose index :" << i;
-        }
-        std::this_thread::sleep_for(perPoseSleepTimeMs);
-    }
-
-    // Switch to low quality and lower resolution
-    Sv3dConfig config;
-    config.width = kLowResolutionWidth;
-    config.height = kLowResolutionHeight;
-    config.carDetails = SvQuality::LOW;
-    if (surroundView3dSession->set3dConfig(config) != SvResult::OK) {
-        LOG(ERROR) << "Failed to set3dConfig";
-        return false;
-    }
-
-    // Let the SV algorithm run for 10 seconds for LOW_QUALITY
-    for(uint32_t i = 0; i < kPoseCount; i++) {
-        if(!setView(surroundView3dSession, i + kPoseCount, i, kHorizontalFov)) {
-            LOG(WARNING) << "Failed to setView of pose index :" << i;
-        }
-        std::this_thread::sleep_for(perPoseSleepTimeMs);
-    }
-
-    // TODO(b/150412555): wait for the last frame
-    // Stop the 3d stream and session
-    surroundView3dSession->stopStream();
-
-    pSurroundViewService->stop3dSession(surroundView3dSession);
-    surroundView3dSession = nullptr;
-
-    LOG(DEBUG) << "SV 3D session finished.";
-
-    return true;
-};
+using namespace android::hardware::automotive::sv::app;
 
 // Main entry point
 int main(int argc, char** argv) {
diff --git a/cpp/surround_view/service-impl/test_data/sv_sample_config.xml b/cpp/surround_view/service-impl/test_data/sv_sample_config.xml
index 9a4b124..1a92bee 100644
--- a/cpp/surround_view/service-impl/test_data/sv_sample_config.xml
+++ b/cpp/surround_view/service-impl/test_data/sv_sample_config.xml
@@ -21,8 +21,8 @@
     <Sv2dEnabled>true</Sv2dEnabled>
     <Sv2dParams>
         <OutputResolution>
-            <Width>768</Width>
-            <Height>1024</Height>
+            <Width>1200</Width>
+            <Height>1600</Height>
         </OutputResolution>
         <GroundMapping>
             <Width>9.0</Width>
@@ -44,7 +44,7 @@
             <HighQuality>multiband</HighQuality>
             <LowQuality>alpha</LowQuality>
         </BlendingType>
-        <GpuAccelerationEnabled>false</GpuAccelerationEnabled>
+        <GpuAccelerationEnabled>true</GpuAccelerationEnabled>
     </Sv2dParams>
 
     <Sv3dEnabled>true</Sv3dEnabled>
diff --git a/cpp/telemetry/ARCHITECTURE.md b/cpp/telemetry/ARCHITECTURE.md
new file mode 100644
index 0000000..75dd194
--- /dev/null
+++ b/cpp/telemetry/ARCHITECTURE.md
@@ -0,0 +1,22 @@
+# Architecture of Car Telemetry Service
+
+## Names
+
+- C++ namespace `android.automotive.telemetry` - for all the car telemetry related projects.
+- android.telemetry.ICarTelemetry - AIDL interface for collecting car data.
+- cartelemetryd (android.automotive.telemetryd) -  a daemon that implements `ICarTelemetry`
+                                                   interface.
+- CarTelemetryService - a part of CarService that executes scrits. Located in car services dir.
+
+## Structure
+
+```
+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
new file mode 100644
index 0000000..4e6c1c0
--- /dev/null
+++ b/cpp/telemetry/Android.bp
@@ -0,0 +1,94 @@
+// 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"],
+}
+
+cc_defaults {
+    name: "cartelemetryd_defaults",
+    cflags: [
+        "-Werror",
+        "-Wall",
+        "-Wno-unused-parameter",
+    ],
+    shared_libs: [
+        "android.automotive.telemetry.internal-ndk_platform",
+        "android.frameworks.automotive.telemetry-V1-ndk_platform",
+        "libbase",
+        "libbinder_ndk",
+        "liblog",
+        "libutils",
+    ],
+    product_variables: {
+        debuggable: {
+            cflags: [
+                "-DCARTELEMETRYD_DEBUG=true",
+            ]
+        }
+    },
+}
+
+cc_library {
+    name: "android.automotive.telemetryd@1.0-impl",
+    defaults: [
+        "cartelemetryd_defaults",
+    ],
+    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",
+    ],
+}
+
+cc_binary {
+    name: "android.automotive.telemetryd@1.0",
+    defaults: [
+        "cartelemetryd_defaults",
+    ],
+    srcs: [
+        "src/main.cpp"
+    ],
+    init_rc: ["android.automotive.telemetryd@1.0.rc"],
+    vintf_fragments: ["android.automotive.telemetryd@1.0.xml"],
+    shared_libs: [
+        "android.automotive.telemetryd@1.0-impl"
+    ],
+}
diff --git a/cpp/telemetry/OWNERS b/cpp/telemetry/OWNERS
new file mode 100644
index 0000000..80794e1
--- /dev/null
+++ b/cpp/telemetry/OWNERS
@@ -0,0 +1,3 @@
+sgurun@google.com
+zhomart@google.com
+mdashouk@google.com
diff --git a/cpp/telemetry/README.md b/cpp/telemetry/README.md
new file mode 100644
index 0000000..a13116d
--- /dev/null
+++ b/cpp/telemetry/README.md
@@ -0,0 +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/android.automotive.telemetryd@1.0.rc b/cpp/telemetry/android.automotive.telemetryd@1.0.rc
new file mode 100644
index 0000000..1513fa8
--- /dev/null
+++ b/cpp/telemetry/android.automotive.telemetryd@1.0.rc
@@ -0,0 +1,9 @@
+service cartelemetryd_service /system/bin/android.automotive.telemetryd@1.0
+    class core
+    user system
+    group system
+    disabled  # starts below on early-init
+
+on early-init
+    # Start the service only after initializing the properties.
+    start cartelemetryd_service
diff --git a/cpp/telemetry/android.automotive.telemetryd@1.0.xml b/cpp/telemetry/android.automotive.telemetryd@1.0.xml
new file mode 100644
index 0000000..8156466
--- /dev/null
+++ b/cpp/telemetry/android.automotive.telemetryd@1.0.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+-->
+<!-- Required VINTF manifest for AIDL servers. -->
+<manifest version="1.0" type="framework">
+    <hal format="aidl">
+        <name>android.frameworks.automotive.telemetry</name>
+        <version>1</version>
+        <fqname>ICarTelemetry/default</fqname>
+    </hal>
+</manifest>
diff --git a/cpp/telemetry/products/telemetry.mk b/cpp/telemetry/products/telemetry.mk
new file mode 100644
index 0000000..634c9a9
--- /dev/null
+++ b/cpp/telemetry/products/telemetry.mk
@@ -0,0 +1,22 @@
+# 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.
+
+# cartelemetryd service
+PRODUCT_PACKAGES += android.automotive.telemetryd@1.0
+
+# Selinux public policies for cartelemetry
+PRODUCT_PUBLIC_SEPOLICY_DIRS += packages/services/Car/cpp/telemetry/sepolicy/public
+
+# Selinux private policies for cartelemetry
+PRODUCT_PRIVATE_SEPOLICY_DIRS += packages/services/Car/cpp/telemetry/sepolicy/private
diff --git a/cpp/telemetry/sampleclient/Android.bp b/cpp/telemetry/sampleclient/Android.bp
new file mode 100644
index 0000000..17fdee0
--- /dev/null
+++ b/cpp/telemetry/sampleclient/Android.bp
@@ -0,0 +1,37 @@
+// 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-sampleclient",
+    srcs: [
+        "main.cpp",
+    ],
+    vendor: true,
+    cflags: [
+        "-Werror",
+        "-Wall",
+        "-Wno-unused-parameter",
+    ],
+    shared_libs: [
+        "android.frameworks.automotive.telemetry-V1-ndk_platform",
+        "libbase",
+        "libbinder_ndk",
+        "libutils",
+    ],
+}
diff --git a/cpp/telemetry/sampleclient/README.md b/cpp/telemetry/sampleclient/README.md
new file mode 100644
index 0000000..ebbaf16
--- /dev/null
+++ b/cpp/telemetry/sampleclient/README.md
@@ -0,0 +1,63 @@
+# ICarTelemetry Sample Client
+
+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).
+
+```
+# this goes to products/telemetry.mk
+
+PRODUCT_PACKAGES += android.automotive.telemetryd-sampleclient
+```
+
+The sampleclient doesn't automatically start during boot, to start manually, run:
+`adb shell /vendor/bin/android.automotive.telemetryd-sampleclient`.
+
+If you want to test it by running `init`, add these SELinux rules:
+
+```
+# this goes to sepolicy/private/cartelemetryd.te
+
+type cartelemetryd_sample, domain;
+type cartelemetryd_sample_exec, vendor_file_type, exec_type, file_type;
+init_daemon_domain(cartelemetryd_sample)
+```
+
+```
+# this goes to sepolicy/private/file_contexts
+
+/vendor/bin/android\.automotive\.telemetryd-sampleclient  u:object_r:cartelemetryd_sample_exec:s0
+```
+
+And create an `.rc` file:
+
+```
+# File: cartelemetryd-sampleclient.rc
+# Don't forget to add `init_rc: ["cartelemetryd-sampleclient.rc"],` to the Android.bp
+
+service cartelemetryd_sample /vendor/bin/android.automotive.telemetryd-sampleclient
+    class hal
+    user system
+    group system
+    oneshot  # run once, otherwise init keeps restarting it
+    disabled  # do not start automatically
+```
diff --git a/cpp/telemetry/sampleclient/main.cpp b/cpp/telemetry/sampleclient/main.cpp
new file mode 100644
index 0000000..e45dc65
--- /dev/null
+++ b/cpp/telemetry/sampleclient/main.cpp
@@ -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.
+ */
+
+#define LOG_TAG "cartelemetryd_sample"
+
+#include <aidl/android/frameworks/automotive/telemetry/ICarTelemetry.h>
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <android/binder_manager.h>
+#include <utils/SystemClock.h>
+
+using ::aidl::android::frameworks::automotive::telemetry::CarData;
+using ::aidl::android::frameworks::automotive::telemetry::ICarTelemetry;
+using ::android::base::StringPrintf;
+
+int main(int argc, char* argv[]) {
+    const auto started_at_millis = android::elapsedRealtime();
+
+    // 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", ICarTelemetry::descriptor);
+    LOG(INFO) << "Obtaining: " << instance;
+    std::shared_ptr<ICarTelemetry> service = ICarTelemetry::fromBinder(
+            ndk::SpAIBinder(AServiceManager_getService(instance.c_str())));
+    if (!service) {
+        LOG(ERROR) << "ICarTelemetry service not found, may be still initializing?";
+        return 1;
+    }
+
+    LOG(INFO) << "Building a CarData message, delta_since_start: "
+              << android::elapsedRealtime() - started_at_millis << " millis";
+
+    // Build a CarData message
+    // TODO(b/174608802): set a correct data ID and content
+    CarData msg;
+    msg.id = 101;
+    msg.content = {1, 0, 1, 0};
+
+    LOG(INFO) << "Sending the car data, delta_since_start: "
+              << android::elapsedRealtime() - started_at_millis << " millis";
+
+    // Send the data
+    ndk::ScopedAStatus writeStatus = service->write({msg});
+
+    if (!writeStatus.isOk()) {
+        LOG(WARNING) << "Failed to write to the service: " << writeStatus.getMessage();
+    }
+
+    // Note: On a device the delta_since_start was between 1ms to 4ms
+    //      (service side was not fully implemented yet during the test).
+    LOG(INFO) << "Finished, delta_since_start: " << android::elapsedRealtime() - started_at_millis
+              << " millis";
+
+    return 0;
+}
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/cartelemetryd.te b/cpp/telemetry/sepolicy/private/cartelemetryd.te
new file mode 100644
index 0000000..3baefb1
--- /dev/null
+++ b/cpp/telemetry/sepolicy/private/cartelemetryd.te
@@ -0,0 +1,9 @@
+### See //system/sepolicy/public/te_macros to learn about some SELinux macros.
+
+type cartelemetryd_exec, system_file_type, exec_type, file_type;
+
+binder_use(cartelemetryd)
+
+add_service(cartelemetryd, cartelemetryd_service)
+
+init_daemon_domain(cartelemetryd)
diff --git a/cpp/telemetry/sepolicy/private/file_contexts b/cpp/telemetry/sepolicy/private/file_contexts
new file mode 100644
index 0000000..0b0915d
--- /dev/null
+++ b/cpp/telemetry/sepolicy/private/file_contexts
@@ -0,0 +1 @@
+/system/bin/android\.automotive\.telemetryd@1\.0  u:object_r:cartelemetryd_exec:s0
diff --git a/cpp/telemetry/sepolicy/private/service_contexts b/cpp/telemetry/sepolicy/private/service_contexts
new file mode 100644
index 0000000..f26b5f8
--- /dev/null
+++ b/cpp/telemetry/sepolicy/private/service_contexts
@@ -0,0 +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/sepolicy/public/cartelemetryd.te b/cpp/telemetry/sepolicy/public/cartelemetryd.te
new file mode 100644
index 0000000..fab4d58
--- /dev/null
+++ b/cpp/telemetry/sepolicy/public/cartelemetryd.te
@@ -0,0 +1 @@
+type cartelemetryd, domain, coredomain;
diff --git a/cpp/telemetry/sepolicy/public/service.te b/cpp/telemetry/sepolicy/public/service.te
new file mode 100644
index 0000000..f0beee8
--- /dev/null
+++ b/cpp/telemetry/sepolicy/public/service.te
@@ -0,0 +1 @@
+type cartelemetryd_service, service_manager_type;
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
new file mode 100644
index 0000000..34a4e60
--- /dev/null
+++ b/cpp/telemetry/src/CarTelemetryImpl.cpp
@@ -0,0 +1,49 @@
+/*
+ * 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 "CarTelemetryImpl.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 ::aidl::android::frameworks::automotive::telemetry::CarData;
+
+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();
+}
+
+}  // namespace telemetry
+}  // namespace automotive
+}  // namespace android
diff --git a/cpp/telemetry/src/CarTelemetryImpl.h b/cpp/telemetry/src/CarTelemetryImpl.h
new file mode 100644
index 0000000..a3bb6c1
--- /dev/null
+++ b/cpp/telemetry/src/CarTelemetryImpl.h
@@ -0,0 +1,51 @@
+/*
+ * 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_CARTELEMETRYIMPL_H_
+#define CPP_TELEMETRY_SRC_CARTELEMETRYIMPL_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 aidl::android::frameworks::automotive::telemetry::BnCarTelemetry {
+public:
+    // Doesn't own `buffer`.
+    explicit CarTelemetryImpl(RingBuffer* buffer);
+
+    ndk::ScopedAStatus write(
+            const std::vector<aidl::android::frameworks::automotive::telemetry::CarData>& dataList)
+            override;
+
+private:
+    RingBuffer* mRingBuffer;  // not owned
+};
+
+}  // namespace telemetry
+}  // namespace automotive
+}  // namespace android
+
+#endif  // CPP_TELEMETRY_SRC_CARTELEMETRYIMPL_H_
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
new file mode 100644
index 0000000..1bd0dde
--- /dev/null
+++ b/cpp/telemetry/src/main.cpp
@@ -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.
+ */
+
+#include "TelemetryServer.h"
+
+#include <android-base/logging.h>
+
+using ::android::automotive::telemetry::TelemetryServer;
+
+// TODO(b/174608802): handle SIGQUIT/SIGTERM
+
+int main(void) {
+    LOG(INFO) << "Starting cartelemetryd";
+
+    TelemetryServer server;
+
+    // Register AIDL services. Aborts the server if fails.
+    server.registerServices();
+
+    LOG(VERBOSE) << "Service is created, joining the threadpool";
+    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/Android.bp b/cpp/watchdog/server/Android.bp
index 2afee42..a0d85b8 100644
--- a/cpp/watchdog/server/Android.bp
+++ b/cpp/watchdog/server/Android.bp
@@ -40,8 +40,8 @@
         "liblog",
         "libutils",
     ],
-    static_libs: [
-        "libgtest_prod",
+    header_libs: [
+        "libgtest_prod_headers",
     ],
 }
 
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..e0a2435 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,10 +41,10 @@
 inline const android::automotive::watchdog::internal::PerStateIoOveruseThreshold
 defaultThreshold() {
     android::automotive::watchdog::internal::PerStateIoOveruseThreshold threshold;
-    threshold.name = android::String16(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();
+    threshold.name = kDefaultThresholdName;
+    threshold.perStateWriteBytes.foregroundBytes = std::numeric_limits<int64_t>::max();
+    threshold.perStateWriteBytes.backgroundBytes = std::numeric_limits<int64_t>::max();
+    threshold.perStateWriteBytes.garageModeBytes = std::numeric_limits<int64_t>::max();
     return threshold;
 }
 
@@ -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..c724d57 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,17 +47,21 @@
 using ::android::base::Result;
 using ::android::binder::Status;
 
+// Minimum written bytes to sync the stats with the Watchdog service.
+constexpr int64_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) {
-    const auto sub = [](const uint64_t& l, const uint64_t& r) -> uint64_t {
+    const auto sub = [](const int64_t& l, const int64_t& r) -> int64_t {
         return l >= r ? (l - r) : 0;
     };
     PerStateBytes result;
@@ -76,6 +83,15 @@
     return std::make_tuple(startTime, currentEpochSeconds - startTime);
 }
 
+int64_t totalPerStateBytes(PerStateBytes perStateBytes) {
+    const auto sum = [](const int64_t& l, const int64_t& r) -> int64_t {
+        return std::numeric_limits<int64_t>::max() - l > r ? (l + r)
+                                                           : std::numeric_limits<int64_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 {};
@@ -295,7 +351,7 @@
             {.pollDurationInSecs = difftime(time, mLastSystemWideIoMonitorTime),
              .bytesInKib = diskStats.numKibWritten});
     for (const auto& threshold : mIoOveruseConfigs->systemWideAlertThresholds()) {
-        uint64_t accountedWrittenKib = 0;
+        int64_t accountedWrittenKib = 0;
         double accountedDurationInSecs = 0;
         size_t accountedPolls = 0;
         for (auto rit = mSystemWideWrittenBytes.rbegin(); rit != mSystemWideWrittenBytes.rend();
@@ -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 {};
 }
 
@@ -493,10 +564,9 @@
     if (id() == r.id()) {
         packageInfo = r.packageInfo;
     }
-    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();
+    const auto sum = [](const int64_t& l, const int64_t& r) -> int64_t {
+        return (std::numeric_limits<int64_t>::max() - l) > r ? (l + r)
+                                                             : std::numeric_limits<int64_t>::max();
     };
     writtenBytes.foregroundBytes =
             sum(writtenBytes.foregroundBytes, r.writtenBytes.foregroundBytes);
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..08c1daa 100644
--- a/cpp/watchdog/server/src/IoPerfCollection.cpp
+++ b/cpp/watchdog/server/src/IoPerfCollection.cpp
@@ -161,7 +161,7 @@
     for (const auto& stat : data.topNReads) {
         StringAppendF(&buffer, "%" PRIu32 ", %s", stat.userId, stat.packageName.c_str());
         for (int i = 0; i < UID_STATES; ++i) {
-            StringAppendF(&buffer, ", %" PRIu64 ", %.2f%%, %" PRIu64 ", %.2f%%", stat.bytes[i],
+            StringAppendF(&buffer, ", %" PRIi64 ", %.2f%%, %" PRIi64 ", %.2f%%", stat.bytes[i],
                           percentage(stat.bytes[i], data.total[READ_BYTES][i]), stat.fsync[i],
                           percentage(stat.fsync[i], data.total[FSYNC_COUNT][i]));
         }
@@ -177,7 +177,7 @@
     for (const auto& stat : data.topNWrites) {
         StringAppendF(&buffer, "%" PRIu32 ", %s", stat.userId, stat.packageName.c_str());
         for (int i = 0; i < UID_STATES; ++i) {
-            StringAppendF(&buffer, ", %" PRIu64 ", %.2f%%, %" PRIu64 ", %.2f%%", stat.bytes[i],
+            StringAppendF(&buffer, ", %" PRIi64 ", %.2f%%, %" PRIi64 ", %.2f%%", stat.bytes[i],
                           percentage(stat.bytes[i], data.total[WRITE_BYTES][i]), stat.fsync[i],
                           percentage(stat.fsync[i], data.total[FSYNC_COUNT][i]));
         }
@@ -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/IoPerfCollection.h b/cpp/watchdog/server/src/IoPerfCollection.h
index e86320f..1c40d00 100644
--- a/cpp/watchdog/server/src/IoPerfCollection.h
+++ b/cpp/watchdog/server/src/IoPerfCollection.h
@@ -49,12 +49,12 @@
     struct Stats {
         userid_t userId = 0;
         std::string packageName;
-        uint64_t bytes[UID_STATES];
-        uint64_t fsync[UID_STATES];
+        int64_t bytes[UID_STATES];
+        int64_t fsync[UID_STATES];
     };
     std::vector<Stats> topNReads = {};
     std::vector<Stats> topNWrites = {};
-    uint64_t total[METRIC_TYPES][UID_STATES] = {{0}};
+    int64_t total[METRIC_TYPES][UID_STATES] = {{0}};
 };
 
 std::string toString(const UidIoPerfData& perfData);
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..e88693a 100644
--- a/cpp/watchdog/server/src/UidIoStats.cpp
+++ b/cpp/watchdog/server/src/UidIoStats.cpp
@@ -36,6 +36,7 @@
 namespace watchdog {
 
 using ::android::base::Error;
+using ::android::base::ParseInt;
 using ::android::base::ParseUint;
 using ::android::base::ReadFileToString;
 using ::android::base::Result;
@@ -47,19 +48,19 @@
 bool parseUidIoStats(const std::string& data, UidIoUsage* usage) {
     std::vector<std::string> fields = Split(data, " ");
     if (fields.size() < 11 || !ParseUint(fields[0], &usage->uid) ||
-        !ParseUint(fields[3], &usage->ios.metrics[READ_BYTES][FOREGROUND]) ||
-        !ParseUint(fields[4], &usage->ios.metrics[WRITE_BYTES][FOREGROUND]) ||
-        !ParseUint(fields[7], &usage->ios.metrics[READ_BYTES][BACKGROUND]) ||
-        !ParseUint(fields[8], &usage->ios.metrics[WRITE_BYTES][BACKGROUND]) ||
-        !ParseUint(fields[9], &usage->ios.metrics[FSYNC_COUNT][FOREGROUND]) ||
-        !ParseUint(fields[10], &usage->ios.metrics[FSYNC_COUNT][BACKGROUND])) {
+        !ParseInt(fields[3], &usage->ios.metrics[READ_BYTES][FOREGROUND]) ||
+        !ParseInt(fields[4], &usage->ios.metrics[WRITE_BYTES][FOREGROUND]) ||
+        !ParseInt(fields[7], &usage->ios.metrics[READ_BYTES][BACKGROUND]) ||
+        !ParseInt(fields[8], &usage->ios.metrics[WRITE_BYTES][BACKGROUND]) ||
+        !ParseInt(fields[9], &usage->ios.metrics[FSYNC_COUNT][FOREGROUND]) ||
+        !ParseInt(fields[10], &usage->ios.metrics[FSYNC_COUNT][BACKGROUND])) {
         ALOGW("Invalid uid I/O stats: \"%s\"", data.c_str());
         return false;
     }
     return true;
 }
 
-uint64_t maybeDiff(uint64_t lhs, uint64_t rhs) {
+int64_t maybeDiff(int64_t lhs, int64_t rhs) {
     return lhs > rhs ? lhs - rhs : 0;
 }
 
@@ -93,8 +94,8 @@
 }
 
 std::string IoUsage::toString() const {
-    return StringPrintf("FgRdBytes:%" PRIu64 " BgRdBytes:%" PRIu64 " FgWrBytes:%" PRIu64
-                        " BgWrBytes:%" PRIu64 " FgFsync:%" PRIu64 " BgFsync:%" PRIu64,
+    return StringPrintf("FgRdBytes:%" PRIi64 " BgRdBytes:%" PRIi64 " FgWrBytes:%" PRIi64
+                        " BgWrBytes:%" PRIi64 " FgFsync:%" PRIi64 " BgFsync:%" PRIi64,
                         metrics[READ_BYTES][FOREGROUND], metrics[READ_BYTES][BACKGROUND],
                         metrics[WRITE_BYTES][FOREGROUND], metrics[WRITE_BYTES][BACKGROUND],
                         metrics[FSYNC_COUNT][FOREGROUND], metrics[FSYNC_COUNT][BACKGROUND]);
@@ -113,11 +114,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..b3257f8 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>
 
@@ -48,8 +49,8 @@
 class IoUsage {
 public:
     IoUsage() : metrics{{0}} {};
-    IoUsage(uint64_t fgRdBytes, uint64_t bgRdBytes, uint64_t fgWrBytes, uint64_t bgWrBytes,
-            uint64_t fgFsync, uint64_t bgFsync) {
+    IoUsage(int64_t fgRdBytes, int64_t bgRdBytes, int64_t fgWrBytes, int64_t bgWrBytes,
+            int64_t fgFsync, int64_t bgFsync) {
         metrics[READ_BYTES][FOREGROUND] = fgRdBytes;
         metrics[READ_BYTES][BACKGROUND] = bgRdBytes;
         metrics[WRITE_BYTES][FOREGROUND] = fgWrBytes;
@@ -61,23 +62,23 @@
     bool operator==(const IoUsage& usage) const {
         return memcmp(&metrics, &usage.metrics, sizeof(metrics)) == 0;
     }
-    uint64_t sumReadBytes() const {
+    int64_t sumReadBytes() const {
         const auto& [fgBytes, bgBytes] =
                 std::tuple(metrics[READ_BYTES][FOREGROUND], metrics[READ_BYTES][BACKGROUND]);
-        return (std::numeric_limits<uint64_t>::max() - fgBytes) > bgBytes
+        return (std::numeric_limits<int64_t>::max() - fgBytes) > bgBytes
                 ? (fgBytes + bgBytes)
-                : std::numeric_limits<uint64_t>::max();
+                : std::numeric_limits<int64_t>::max();
     }
-    uint64_t sumWriteBytes() const {
+    int64_t sumWriteBytes() const {
         const auto& [fgBytes, bgBytes] =
                 std::tuple(metrics[WRITE_BYTES][FOREGROUND], metrics[WRITE_BYTES][BACKGROUND]);
-        return (std::numeric_limits<uint64_t>::max() - fgBytes) > bgBytes
+        return (std::numeric_limits<int64_t>::max() - fgBytes) > bgBytes
                 ? (fgBytes + bgBytes)
-                : std::numeric_limits<uint64_t>::max();
+                : std::numeric_limits<int64_t>::max();
     }
     bool isZero() const;
     std::string toString() const;
-    uint64_t metrics[METRIC_TYPES][UID_STATES];
+    int64_t metrics[METRIC_TYPES][UID_STATES];
 };
 
 struct UidIoUsage {
@@ -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..89dd263 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 int64_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/packages/CarShell/AndroidManifest.xml b/packages/CarShell/AndroidManifest.xml
index a32307b..2f39dfd 100644
--- a/packages/CarShell/AndroidManifest.xml
+++ b/packages/CarShell/AndroidManifest.xml
@@ -37,6 +37,8 @@
     <uses-permission android:name="android.car.permission.CAR_DIAGNOSTICS" />
     <!-- Permission required for 'adb shell cmd car_service silent-mode' -->
     <uses-permission android:name="android.car.permission.CAR_POWER" />
+    <!-- Permission required for ATS tests - AtsCarTests#CarPowerManagerTest-->
+    <uses-permission android:name="android.car.permission.CONTROL_CAR_POWER_POLICY" />
     <!-- Permission required for 'adb shell cmd car_service day-night-mode [day|night]' -->
     <uses-permission android:name="android.car.permission.MODIFY_DAY_NIGHT_MODE" />
     <!-- Permission required for ATS tests - AtsDeviceInfo -->
diff --git a/service/Android.bp b/service/Android.bp
index ae41129..700d4e3 100644
--- a/service/Android.bp
+++ b/service/Android.bp
@@ -55,6 +55,7 @@
     // to CarSettings
     "car-admin-ui-lib",
     "Slogf",
+    "cartelemetry-protos",
 ]
 
 android_app {
diff --git a/service/AndroidManifest.xml b/service/AndroidManifest.xml
index c7e719d..af0fb97 100644
--- a/service/AndroidManifest.xml
+++ b/service/AndroidManifest.xml
@@ -370,6 +370,14 @@
          android:label="@string/car_permission_label_driving_state"
          android:description="@string/car_permission_desc_driving_state"/>
 
+    <!-- Allows an application to use car's telemetry service.
+         <p>Protection level: signature|privileged
+    -->
+    <permission android:name="android.car.permission.USE_CAR_TELEMETRY_SERVICE"
+                android:protectionLevel="signature|privileged"
+                android:label="@string/car_permission_label_use_telemetry_service"
+                android:description="@string/car_permission_desc_use_telemetry_service"/>
+
     <!-- Allows an application to request to launch the EVS previewing activity.
          <p>Protection level: signature|privileged
     -->
diff --git a/service/jni/Android.bp b/service/jni/Android.bp
index 6048d06..86f3455 100644
--- a/service/jni/Android.bp
+++ b/service/jni/Android.bp
@@ -14,6 +14,10 @@
 //
 //
 //#################################
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 cc_library_shared {
     name: "libcarservicejni",
     srcs: [
diff --git a/service/res/values-bs/strings.xml b/service/res/values-bs/strings.xml
index 7158d2c..1fd16cc 100644
--- a/service/res/values-bs/strings.xml
+++ b/service/res/values-bs/strings.xml
@@ -21,17 +21,17 @@
     <string name="car_permission_label_camera" msgid="3725702064841827180">"pristupiti kameri automobila"</string>
     <string name="car_permission_desc_camera" msgid="917024932164501426">"Pristupiti kameri(ama) automobila."</string>
     <string name="car_permission_label_energy" msgid="7409144323527821558">"pristupiti informacijama o energiji automobila"</string>
-    <string name="car_permission_desc_energy" msgid="3392963810053235407">"Pristupiti informacijama o energiji automobila."</string>
-    <string name="car_permission_label_adjust_range_remaining" msgid="839033553999920138">"podesi preostali domet automobila"</string>
-    <string name="car_permission_desc_adjust_range_remaining" msgid="2369321650437370673">"Podesi vrijednost preostalog dometa automobila."</string>
+    <string name="car_permission_desc_energy" msgid="3392963810053235407">"pristupiti informacijama o energiji automobila"</string>
+    <string name="car_permission_label_adjust_range_remaining" msgid="839033553999920138">"podesiti preostali domet automobila"</string>
+    <string name="car_permission_desc_adjust_range_remaining" msgid="2369321650437370673">"podesiti vrijednost preostalog dometa automobila"</string>
     <string name="car_permission_label_hvac" msgid="1499454192558727843">"pristupiti grijanju, ventilaciji i klimatizaciji automobila"</string>
     <string name="car_permission_desc_hvac" msgid="3754229695589774195">"Pristupiti grijanju, ventilaciji i klimatizaciji automobila."</string>
     <string name="car_permission_label_mileage" msgid="4661317074631150551">"pristupiti informacijama o pređenim kilometrima automobila"</string>
-    <string name="car_permission_desc_mileage" msgid="7179735693278681090">"Pristupiti informacijama o pređenim kilometrima."</string>
+    <string name="car_permission_desc_mileage" msgid="7179735693278681090">"pristupiti informacijama o pređenim kilometrima"</string>
     <string name="car_permission_label_speed" msgid="1149027717860529745">"očitati brzinu automobila"</string>
-    <string name="car_permission_desc_speed" msgid="2047965198165448241">"Pristupiti informacijama o brzini automobila."</string>
+    <string name="car_permission_desc_speed" msgid="2047965198165448241">"pristupiti informacijama o brzini automobila"</string>
     <string name="car_permission_label_vehicle_dynamics_state" msgid="313779267420048367">"pristupiti stanju dinamike automobila"</string>
-    <string name="car_permission_desc_vehicle_dynamics_state" msgid="8891506193446375660">"Pristupiti stanju dinamike automobila."</string>
+    <string name="car_permission_desc_vehicle_dynamics_state" msgid="8891506193446375660">"pristupiti stanju dinamike automobila"</string>
     <string name="car_permission_label_vendor_extension" msgid="7141601811734127361">"pristupiti kanalu trgovca automobilima"</string>
     <string name="car_permission_desc_vendor_extension" msgid="2970718502334714035">"Pristupiti kanalu trgovca automobilima radi razmjene posebnih informacija o automobilu."</string>
     <string name="car_permission_label_radio" msgid="6009465291685935112">"upravljati radiom automobila"</string>
@@ -47,11 +47,11 @@
     <string name="car_permission_label_mock_vehicle_hal" msgid="7198852512207405935">"emulirati HAL vozila"</string>
     <string name="car_permission_label_receive_ducking" msgid="4884538660766756573">"prijem događaja sa smanjivanjem jačine zvuka tokom govora"</string>
     <string name="car_permission_desc_receive_ducking" msgid="776376388266656512">"Dozvoljava aplikaciji da primi obavještenje kada se glasnoća smanji uslijed reproduciranja drugog zvučnog zapisa u automobilu."</string>
-    <string name="car_permission_desc_mock_vehicle_hal" msgid="5235596491098649155">"Emulirati HAL vozila u svrhu internog testiranja."</string>
-    <string name="car_permission_desc_audio_volume" msgid="536626185654307889">"Kontrolirati jačinu zvuka u automobilu."</string>
+    <string name="car_permission_desc_mock_vehicle_hal" msgid="5235596491098649155">"emulirati HAL vozila u svrhu internog testiranja"</string>
+    <string name="car_permission_desc_audio_volume" msgid="536626185654307889">"kontrolirati jačinu zvuka u automobilu"</string>
     <string name="car_permission_desc_audio_settings" msgid="7192007170677915937">"Kontrolirajte postavke zvuka automobila."</string>
     <string name="car_permission_label_control_app_blocking" msgid="9112678596919993386">"Blokiranje aplikacija"</string>
-    <string name="car_permission_desc_control_app_blocking" msgid="7539378161760696190">"Kontrolirati blokiranje aplikacija tokom vožnje."</string>
+    <string name="car_permission_desc_control_app_blocking" msgid="7539378161760696190">"kontrolirati blokiranje aplikacija tokom vožnje"</string>
     <string name="car_permission_car_navigation_manager" msgid="5895461364007854077">"Upravitelj navigacije"</string>
     <string name="car_permission_desc_car_navigation_manager" msgid="6188751054665471537">"Dostaviti podatke o navigaciji kontrolnoj tabli"</string>
     <string name="car_permission_car_display_in_cluster" msgid="4005987646292458684">"Direktno iscrtavanje na kontrolnoj ploči"</string>
diff --git a/service/res/values-gu/strings.xml b/service/res/values-gu/strings.xml
index 9ad2f2f..931b0e7 100644
--- a/service/res/values-gu/strings.xml
+++ b/service/res/values-gu/strings.xml
@@ -96,22 +96,14 @@
     <string name="car_permission_desc_driving_state" msgid="2684025262811635737">"ડ્રાઇવ કરવાની સ્થિતિના ફેરફારો વિશે સાંભળો."</string>
     <string name="car_permission_label_use_evs_service" msgid="1729276125209310607">"કાર EVS સર્વિસનો ઉપયોગ કરો"</string>
     <string name="car_permission_desc_use_evs_service" msgid="2374737642186632816">"EVS વીડિયો સ્ટ્રીમને સબ્સ્ક્રાઇબ કરો"</string>
-    <!-- no translation found for car_permission_label_request_evs_activity (3906551972883482883) -->
-    <skip />
-    <!-- no translation found for car_permission_desc_request_evs_activity (4582768053649138488) -->
-    <skip />
-    <!-- no translation found for car_permission_label_control_evs_activity (2030069860204405679) -->
-    <skip />
-    <!-- no translation found for car_permission_desc_control_evs_activity (691646545916976346) -->
-    <skip />
-    <!-- no translation found for car_permission_label_use_evs_camera (3607720208623955067) -->
-    <skip />
-    <!-- no translation found for car_permission_desc_use_evs_camera (1625845902221003985) -->
-    <skip />
-    <!-- no translation found for car_permission_label_monitor_evs_status (2091521314159379622) -->
-    <skip />
-    <!-- no translation found for car_permission_desc_monitor_evs_status (2764278897143573535) -->
-    <skip />
+    <string name="car_permission_label_request_evs_activity" msgid="3906551972883482883">"EVS પ્રીવ્યૂ પ્રવૃત્તિ માટે વિનંતી કરો"</string>
+    <string name="car_permission_desc_request_evs_activity" msgid="4582768053649138488">"EVS પ્રીવ્યૂ પ્રવૃત્તિ લૉન્ચ કરવા માટે, સિસ્ટમને વિનંતી કરો"</string>
+    <string name="car_permission_label_control_evs_activity" msgid="2030069860204405679">"EVS પ્રીવ્યૂ પ્રવૃત્તિ નિયંત્રિત કરો"</string>
+    <string name="car_permission_desc_control_evs_activity" msgid="691646545916976346">"સિસ્ટમની EVS પ્રીવ્યૂ પ્રવૃત્તિ નિયંત્રિત કરો"</string>
+    <string name="car_permission_label_use_evs_camera" msgid="3607720208623955067">"EVS કૅમેરાનો ઉપયોગ કરો"</string>
+    <string name="car_permission_desc_use_evs_camera" msgid="1625845902221003985">"EVS કૅમેરા સ્ટ્રીમને સબ્સ્ક્રાઇબ કરો"</string>
+    <string name="car_permission_label_monitor_evs_status" msgid="2091521314159379622">"EVS સેવાનું સ્ટેટસ મૉનિટર કરો"</string>
+    <string name="car_permission_desc_monitor_evs_status" msgid="2764278897143573535">"EVS સેવાના સ્ટેટસમાં થનારા ફેરફારો સાંભળો"</string>
     <string name="car_permission_label_car_engine_detailed" msgid="8911992719173587337">"કારના એન્જિનની વિગતવાર માહિતીને ઍક્સેસ કરો"</string>
     <string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"તમારી કારના એન્જિનની વિગતવાર માહિતીને ઍક્સેસ કરો."</string>
     <string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"કારના ઇંધણના દરવાજા અને ચાર્જ પોર્ટને ઍક્સેસ કરો"</string>
diff --git a/service/res/values-hi/strings.xml b/service/res/values-hi/strings.xml
index 28a9e89..6ff2cbd 100644
--- a/service/res/values-hi/strings.xml
+++ b/service/res/values-hi/strings.xml
@@ -102,7 +102,7 @@
     <string name="car_permission_label_use_evs_camera" msgid="3607720208623955067">"ईवीएस कैमरा इस्तेमाल करें"</string>
     <string name="car_permission_desc_use_evs_camera" msgid="1625845902221003985">"ईवीएस कैमरा स्ट्रीम की सदस्यता लें"</string>
     <string name="car_permission_label_monitor_evs_status" msgid="2091521314159379622">"ईवीएस सेवा के स्टेटस पर नज़र रखें"</string>
-    <string name="car_permission_desc_monitor_evs_status" msgid="2764278897143573535">"ईवीएस सेवा के स्टेटस में हुए बदलावों को सुनें"</string>
+    <string name="car_permission_desc_monitor_evs_status" msgid="2764278897143573535">"ईवीएस सेवा के स्टेटस में हुए बदलावों पर नज़र रखें"</string>
     <string name="car_permission_label_car_engine_detailed" msgid="8911992719173587337">"कार के इंजन की जानकारी ऐक्सेस कर सकता है"</string>
     <string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"आपकी कार के इंजन की पूरी जानकारी ऐक्सेस कर सकता है."</string>
     <string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"कार की ईंधन टंकी का ढक्कन और चार्जिंग पोर्ट ऐक्सेस कर सकता है"</string>
diff --git a/service/res/values-ne/strings.xml b/service/res/values-ne/strings.xml
index 826af97..adb1532 100644
--- a/service/res/values-ne/strings.xml
+++ b/service/res/values-ne/strings.xml
@@ -95,22 +95,14 @@
     <string name="car_permission_desc_driving_state" msgid="2684025262811635737">"ड्राइभिङको स्थितिमा हुने परिवर्तनहरू सुन्ने।"</string>
     <string name="car_permission_label_use_evs_service" msgid="1729276125209310607">"कार EVS सेवा प्रयोग गर्नुहोस्"</string>
     <string name="car_permission_desc_use_evs_service" msgid="2374737642186632816">"EVS भिडियो स्ट्रिमको सदस्यता लिनुहोस्"</string>
-    <!-- no translation found for car_permission_label_request_evs_activity (3906551972883482883) -->
-    <skip />
-    <!-- no translation found for car_permission_desc_request_evs_activity (4582768053649138488) -->
-    <skip />
-    <!-- no translation found for car_permission_label_control_evs_activity (2030069860204405679) -->
-    <skip />
-    <!-- no translation found for car_permission_desc_control_evs_activity (691646545916976346) -->
-    <skip />
-    <!-- no translation found for car_permission_label_use_evs_camera (3607720208623955067) -->
-    <skip />
-    <!-- no translation found for car_permission_desc_use_evs_camera (1625845902221003985) -->
-    <skip />
-    <!-- no translation found for car_permission_label_monitor_evs_status (2091521314159379622) -->
-    <skip />
-    <!-- no translation found for car_permission_desc_monitor_evs_status (2764278897143573535) -->
-    <skip />
+    <string name="car_permission_label_request_evs_activity" msgid="3906551972883482883">"EVS प्रिभ्यू गतिविधि अनुरोध गर्ने"</string>
+    <string name="car_permission_desc_request_evs_activity" msgid="4582768053649138488">"प्रणालीलाई EVS प्रिभ्यू गतिविधि सुरु गर्न अनुरोध गर्ने"</string>
+    <string name="car_permission_label_control_evs_activity" msgid="2030069860204405679">"EVS प्रिभ्यू गतिविधि नियन्त्रण गर्ने"</string>
+    <string name="car_permission_desc_control_evs_activity" msgid="691646545916976346">"प्रणालीको EVS प्रिभ्यू गतिविधि नियन्त्रण गर्ने"</string>
+    <string name="car_permission_label_use_evs_camera" msgid="3607720208623955067">"EVS क्यामेरा प्रयोग गर्ने"</string>
+    <string name="car_permission_desc_use_evs_camera" msgid="1625845902221003985">"EVS क्यामेरा स्ट्रिमको सदस्यता लिने"</string>
+    <string name="car_permission_label_monitor_evs_status" msgid="2091521314159379622">"EVS सेवाको स्थिति अनुगमन गर्ने"</string>
+    <string name="car_permission_desc_monitor_evs_status" msgid="2764278897143573535">"EVS सेवाको स्थितिमा भएका परिवर्तनबारे सूचना प्राप्त गर्ने"</string>
     <string name="car_permission_label_car_engine_detailed" msgid="8911992719173587337">"कारको इन्जिनको विस्तृत जानकारीमाथि पहुँच राख्ने"</string>
     <string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"तपाईंको कारको इन्जिनको विस्तृत जानकारीमाथि पहुँच राख्ने।"</string>
     <string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"कारको इन्धन हाल्ने ट्याङ्कीको बिर्को तथा चार्ज गर्ने पोर्टमाथि पहुँच राख्ने"</string>
diff --git a/service/res/values-ur/strings.xml b/service/res/values-ur/strings.xml
index 60b2f7d..3c2ac8e 100644
--- a/service/res/values-ur/strings.xml
+++ b/service/res/values-ur/strings.xml
@@ -95,22 +95,14 @@
     <string name="car_permission_desc_driving_state" msgid="2684025262811635737">"کار چلانے کے دوران کی تبدیلیوں کے بارے میں سنیں۔"</string>
     <string name="car_permission_label_use_evs_service" msgid="1729276125209310607">"کار کی EVS سروس استعمال کریں"</string>
     <string name="car_permission_desc_use_evs_service" msgid="2374737642186632816">"EVS ویڈیو اسٹریمز کو سبسکرائب کریں"</string>
-    <!-- no translation found for car_permission_label_request_evs_activity (3906551972883482883) -->
-    <skip />
-    <!-- no translation found for car_permission_desc_request_evs_activity (4582768053649138488) -->
-    <skip />
-    <!-- no translation found for car_permission_label_control_evs_activity (2030069860204405679) -->
-    <skip />
-    <!-- no translation found for car_permission_desc_control_evs_activity (691646545916976346) -->
-    <skip />
-    <!-- no translation found for car_permission_label_use_evs_camera (3607720208623955067) -->
-    <skip />
-    <!-- no translation found for car_permission_desc_use_evs_camera (1625845902221003985) -->
-    <skip />
-    <!-- no translation found for car_permission_label_monitor_evs_status (2091521314159379622) -->
-    <skip />
-    <!-- no translation found for car_permission_desc_monitor_evs_status (2764278897143573535) -->
-    <skip />
+    <string name="car_permission_label_request_evs_activity" msgid="3906551972883482883">"EVS پیش منظر کی سرگرمی کی درخواست کریں"</string>
+    <string name="car_permission_desc_request_evs_activity" msgid="4582768053649138488">"EVS پیش منظر کی سرگرمی کو شروع کرنے کیلئے سسٹم سے درخواست کریں"</string>
+    <string name="car_permission_label_control_evs_activity" msgid="2030069860204405679">"EVS پیش منظر کی سرگرمی کو کنٹرول کریں"</string>
+    <string name="car_permission_desc_control_evs_activity" msgid="691646545916976346">"سسٹم کی EVS پیش منظر کی سرگرمی کنٹرول کریں"</string>
+    <string name="car_permission_label_use_evs_camera" msgid="3607720208623955067">"EVS کیمرا استعمال کریں"</string>
+    <string name="car_permission_desc_use_evs_camera" msgid="1625845902221003985">"EVS کیمرے کی سلسلہ بندیوں کو سبسکرائب کریں"</string>
+    <string name="car_permission_label_monitor_evs_status" msgid="2091521314159379622">"EVS سروس کے اسٹیٹس کو مانیٹر کریں"</string>
+    <string name="car_permission_desc_monitor_evs_status" msgid="2764278897143573535">"EVS سروس کے اسٹیٹس کی تبدیلیوں کو سنیں"</string>
     <string name="car_permission_label_car_engine_detailed" msgid="8911992719173587337">"کار کے انجن کی تفصیلات تک رسائی حاصل کریں"</string>
     <string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"اپنی کار کے انجن کی تفصیلی معلومات تک رسائی حاصل کریں۔"</string>
     <string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"کار کے ایندھن کے دروازے اور چارج پورٹ تک رسائی حاصل کریں"</string>
diff --git a/service/res/values-vi/strings.xml b/service/res/values-vi/strings.xml
index e1a8da9..0851457 100644
--- a/service/res/values-vi/strings.xml
+++ b/service/res/values-vi/strings.xml
@@ -95,22 +95,14 @@
     <string name="car_permission_desc_driving_state" msgid="2684025262811635737">"Nghe những thay đổi về trạng thái Lái xe."</string>
     <string name="car_permission_label_use_evs_service" msgid="1729276125209310607">"Sử dụng dịch vụ CarEvsService"</string>
     <string name="car_permission_desc_use_evs_service" msgid="2374737642186632816">"Theo dõi luồng video của EVS"</string>
-    <!-- no translation found for car_permission_label_request_evs_activity (3906551972883482883) -->
-    <skip />
-    <!-- no translation found for car_permission_desc_request_evs_activity (4582768053649138488) -->
-    <skip />
-    <!-- no translation found for car_permission_label_control_evs_activity (2030069860204405679) -->
-    <skip />
-    <!-- no translation found for car_permission_desc_control_evs_activity (691646545916976346) -->
-    <skip />
-    <!-- no translation found for car_permission_label_use_evs_camera (3607720208623955067) -->
-    <skip />
-    <!-- no translation found for car_permission_desc_use_evs_camera (1625845902221003985) -->
-    <skip />
-    <!-- no translation found for car_permission_label_monitor_evs_status (2091521314159379622) -->
-    <skip />
-    <!-- no translation found for car_permission_desc_monitor_evs_status (2764278897143573535) -->
-    <skip />
+    <string name="car_permission_label_request_evs_activity" msgid="3906551972883482883">"Yêu cầu hoạt động xem trước qua EVS"</string>
+    <string name="car_permission_desc_request_evs_activity" msgid="4582768053649138488">"Yêu cầu hệ thống triển khai hoạt động xem trước qua EVS"</string>
+    <string name="car_permission_label_control_evs_activity" msgid="2030069860204405679">"Kiểm soát hoạt động xem trước qua EVS"</string>
+    <string name="car_permission_desc_control_evs_activity" msgid="691646545916976346">"Kiểm soát hoạt động xem trước của hệ thống qua EVS"</string>
+    <string name="car_permission_label_use_evs_camera" msgid="3607720208623955067">"Sử dụng camera qua EVS"</string>
+    <string name="car_permission_desc_use_evs_camera" msgid="1625845902221003985">"Đăng ký xem video của camera phát trực tuyến qua EVS"</string>
+    <string name="car_permission_label_monitor_evs_status" msgid="2091521314159379622">"Theo dõi sự thay đổi trạng thái của dịch vụ EVS"</string>
+    <string name="car_permission_desc_monitor_evs_status" msgid="2764278897143573535">"Theo dõi sự thay đổi trạng thái của dịch vụ EVS"</string>
     <string name="car_permission_label_car_engine_detailed" msgid="8911992719173587337">"truy cập vào thông tin chi tiết về động cơ ô tô"</string>
     <string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"Truy cập vào thông tin chi tiết về động cơ trên ô tô của bạn."</string>
     <string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"truy cập vào cổng sạc và cổng nhiên liệu của ô tô"</string>
diff --git a/service/res/values-zh-rHK/strings.xml b/service/res/values-zh-rHK/strings.xml
index e6144d8..70727f7 100644
--- a/service/res/values-zh-rHK/strings.xml
+++ b/service/res/values-zh-rHK/strings.xml
@@ -95,14 +95,14 @@
     <string name="car_permission_desc_driving_state" msgid="2684025262811635737">"聽取駕駛狀態變動。"</string>
     <string name="car_permission_label_use_evs_service" msgid="1729276125209310607">"使用 Car EVS Service"</string>
     <string name="car_permission_desc_use_evs_service" msgid="2374737642186632816">"訂閱電動車影片串流"</string>
-    <string name="car_permission_label_request_evs_activity" msgid="3906551972883482883">"要求電動車預覽活動"</string>
-    <string name="car_permission_desc_request_evs_activity" msgid="4582768053649138488">"要求系統啟動電動車預覽活動"</string>
-    <string name="car_permission_label_control_evs_activity" msgid="2030069860204405679">"控制電動車預覽活動"</string>
-    <string name="car_permission_desc_control_evs_activity" msgid="691646545916976346">"控制系統的電動車預覽活動"</string>
-    <string name="car_permission_label_use_evs_camera" msgid="3607720208623955067">"使用電動車攝影機"</string>
-    <string name="car_permission_desc_use_evs_camera" msgid="1625845902221003985">"訂閱電動車攝影機串流"</string>
-    <string name="car_permission_label_monitor_evs_status" msgid="2091521314159379622">"監察電動車服務的狀態"</string>
-    <string name="car_permission_desc_monitor_evs_status" msgid="2764278897143573535">"聽取電動車服務的狀態變更"</string>
+    <string name="car_permission_label_request_evs_activity" msgid="3906551972883482883">"要求 EVS 預覽活動"</string>
+    <string name="car_permission_desc_request_evs_activity" msgid="4582768053649138488">"要求系統啟動 EVS 預覽活動"</string>
+    <string name="car_permission_label_control_evs_activity" msgid="2030069860204405679">"控制 EVS 預覽活動"</string>
+    <string name="car_permission_desc_control_evs_activity" msgid="691646545916976346">"控制系統的 EVS 預覽活動"</string>
+    <string name="car_permission_label_use_evs_camera" msgid="3607720208623955067">"使用 EVS 攝影機"</string>
+    <string name="car_permission_desc_use_evs_camera" msgid="1625845902221003985">"訂閱 EVS 攝影機串流"</string>
+    <string name="car_permission_label_monitor_evs_status" msgid="2091521314159379622">"監察 EVS 服務的狀態"</string>
+    <string name="car_permission_desc_monitor_evs_status" msgid="2764278897143573535">"留意 EVS 服務狀態變更"</string>
     <string name="car_permission_label_car_engine_detailed" msgid="8911992719173587337">"存取汽車引擎詳情"</string>
     <string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"存取汽車引擎詳情。"</string>
     <string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"存取汽車油箱蓋及充電埠"</string>
diff --git a/service/res/values/config.xml b/service/res/values/config.xml
index 4a92f48..fb893c5 100644
--- a/service/res/values/config.xml
+++ b/service/res/values/config.xml
@@ -368,6 +368,7 @@
         <item>storage_monitoring</item>
         <item>vehicle_map_service</item>
         <item>car_evs_service</item>
+        <item>car_telemetry_service</item>
     </string-array>
 
     <!-- Configuration to enable passenger support.
diff --git a/service/res/values/strings.xml b/service/res/values/strings.xml
index 1ab9d21..671140e 100644
--- a/service/res/values/strings.xml
+++ b/service/res/values/strings.xml
@@ -175,6 +175,11 @@
     <!-- Permission text: apps can listen to driving state changes [CHAR LIMIT=NONE] -->
     <string name="car_permission_desc_driving_state">Listen to Driving state changes.</string>
 
+    <!-- Permission text: apps can collect metrics. [CHAR LIMIT=NONE] -->
+    <string name="car_permission_label_use_telemetry_service">Use Car Telemetry Service</string>
+    <!-- Permission text: apps can collect metrics. [CHAR LIMIT=NONE] -->
+    <string name="car_permission_desc_use_telemetry_service">Collect car system health data.</string>
+
     <!-- Permission text: apps can control EVS stream state. [CHAR LIMIT=NONE] -->
     <string name="car_permission_label_use_evs_service">Use Car EVS Service</string>
     <!-- Permission text: apps can control EVS stream state. [CHAR LIMIT=NONE] -->
diff --git a/service/src/com/android/car/AppFocusService.java b/service/src/com/android/car/AppFocusService.java
index 674d7ff..477529d 100644
--- a/service/src/com/android/car/AppFocusService.java
+++ b/service/src/com/android/car/AppFocusService.java
@@ -208,7 +208,9 @@
                 // ignore as listener doesn't own focus.
                 return;
             }
-            if (mFocusOwners.contains(appType)) {
+            // Because this code will run as part of unit tests on older platform, we can't use
+            // APIs such as {@link SparseArray#contains} that are added with API 30.
+            if (mFocusOwners.indexOfKey(appType) >= 0) {
                 mFocusOwners.remove(appType);
                 mActiveAppTypes.remove(appType);
                 info.removeOwnedAppType(appType);
@@ -278,7 +280,9 @@
      */
     public boolean isFocusOwner(int uid, int pid, int appType) {
         synchronized (mLock) {
-            if (mFocusOwners.contains(appType)) {
+            // Because this code will run as part of unit tests on older platform, we can't use
+            // APIs such as {@link SparseArray#contains} that are added with API 30.
+            if (mFocusOwners.indexOfKey(appType) >= 0) {
                 OwnershipClientInfo clientInfo = mFocusOwners.get(appType);
                 return clientInfo.getUid() == uid && clientInfo.getPid() == pid;
             }
diff --git a/service/src/com/android/car/CarFeatureController.java b/service/src/com/android/car/CarFeatureController.java
index 38ed695..ef67bb2 100644
--- a/service/src/com/android/car/CarFeatureController.java
+++ b/service/src/com/android/car/CarFeatureController.java
@@ -94,6 +94,7 @@
             Car.STORAGE_MONITORING_SERVICE,
             Car.VEHICLE_MAP_SERVICE,
             Car.CAR_EVS_SERVICE,
+            Car.CAR_TELEMETRY_SERVICE,
             // All items below here are deprecated, but still could be supported
             Car.CAR_INSTRUMENT_CLUSTER_SERVICE
     ));
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/ICarImpl.java b/service/src/com/android/car/ICarImpl.java
index 4335b3c..52d03fc 100644
--- a/service/src/com/android/car/ICarImpl.java
+++ b/service/src/com/android/car/ICarImpl.java
@@ -67,6 +67,7 @@
 import com.android.car.power.CarPowerManagementService;
 import com.android.car.stats.CarStatsService;
 import com.android.car.systeminterface.SystemInterface;
+import com.android.car.telemetry.CarTelemetryService;
 import com.android.car.user.CarUserNoticeService;
 import com.android.car.user.CarUserService;
 import com.android.car.util.LimitedTimingsTraceLog;
@@ -131,6 +132,7 @@
     private final CarDevicePolicyService mCarDevicePolicyService;
     private final ClusterHomeService mClusterHomeService;
     private final CarEvsService mCarEvsService;
+    private final CarTelemetryService mCarTelemetryService;
 
     private final CarServiceBase[] mAllServices;
 
@@ -348,6 +350,12 @@
             mCarEvsService = null;
         }
 
+        if (mFeatureController.isFeatureEnabled(Car.CAR_TELEMETRY_SERVICE)) {
+            mCarTelemetryService = new CarTelemetryService(serviceContext);
+        } else {
+            mCarTelemetryService = null;
+        }
+
         // Be careful with order. Service depending on other service should be inited later.
         List<CarServiceBase> allServices = new ArrayList<>();
         allServices.add(mFeatureController);
@@ -382,6 +390,7 @@
         allServices.add(mCarDevicePolicyService);
         addServiceIfNonNull(allServices, mClusterHomeService);
         addServiceIfNonNull(allServices, mCarEvsService);
+        addServiceIfNonNull(allServices, mCarTelemetryService);
 
         // Always put mCarExperimentalFeatureServiceController in last.
         addServiceIfNonNull(allServices, mCarExperimentalFeatureServiceController);
@@ -617,6 +626,8 @@
                 return mClusterHomeService;
             case Car.CAR_EVS_SERVICE:
                 return mCarEvsService;
+            case Car.CAR_TELEMETRY_SERVICE:
+                return mCarTelemetryService;
             default:
                 IBinder service = null;
                 if (mCarExperimentalFeatureServiceController != null) {
diff --git a/service/src/com/android/car/admin/CarDevicePolicyService.java b/service/src/com/android/car/admin/CarDevicePolicyService.java
index db7fe83..4425936 100644
--- a/service/src/com/android/car/admin/CarDevicePolicyService.java
+++ b/service/src/com/android/car/admin/CarDevicePolicyService.java
@@ -25,6 +25,8 @@
 import android.car.admin.ICarDevicePolicyService;
 import android.car.user.UserCreationResult;
 import android.car.user.UserRemovalResult;
+import android.car.user.UserStartResult;
+import android.car.user.UserStopResult;
 import android.content.pm.UserInfo;
 import android.os.UserManager;
 import android.sysprop.CarProperties;
@@ -102,6 +104,17 @@
     }
 
     @Override
+    public void startUserInBackground(@UserIdInt int userId,
+            AndroidFuture<UserStartResult> receiver) {
+        mCarUserService.startUserInBackground(userId, receiver);
+    }
+
+    @Override
+    public void stopUser(@UserIdInt int userId, AndroidFuture<UserStopResult> receiver) {
+        mCarUserService.stopUser(userId, receiver);
+    }
+
+    @Override
     public void dump(@NonNull IndentingPrintWriter writer) {
         checkHasDumpPermissionGranted("dump()");
 
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/power/CarPowerManagementService.java b/service/src/com/android/car/power/CarPowerManagementService.java
index 773a626..cdb2670 100644
--- a/service/src/com/android/car/power/CarPowerManagementService.java
+++ b/service/src/com/android/car/power/CarPowerManagementService.java
@@ -54,7 +54,6 @@
 import android.os.UserManager;
 import android.util.AtomicFile;
 import android.util.IndentingPrintWriter;
-import android.util.Slog;
 import android.util.SparseArray;
 
 import com.android.car.CarLocalServices;
@@ -75,6 +74,7 @@
 import com.android.internal.os.IResultReceiver;
 import com.android.internal.util.Preconditions;
 import com.android.internal.util.function.pooled.PooledLambda;
+import com.android.server.utils.Slogf;
 
 import java.io.BufferedReader;
 import java.io.BufferedWriter;
@@ -217,6 +217,8 @@
     private String mCurrentPowerPolicyGroupId;
     @GuardedBy("mLock")
     private boolean mIsPowerPolicyLocked;
+    @GuardedBy("mLock")
+    private boolean mHasControlOverDaemon;
 
     @GuardedBy("mLock")
     @Nullable
@@ -248,7 +250,7 @@
          */
         @Override
         public void onCallbackDied(T listener) {
-            Slog.i(TAG, "binderDied " + listener.asBinder());
+            Slogf.i(TAG, "binderDied %s", listener.asBinder());
             mActionOnDeath.take(listener);
         }
     }
@@ -278,14 +280,18 @@
         mSwitchGuestUserBeforeSleep = resources.getBoolean(
                 R.bool.config_switchGuestUserBeforeGoingSleep);
         if (mShutdownPrepareTimeMs < MIN_MAX_GARAGE_MODE_DURATION_MS) {
-            Slog.w(TAG,
-                    "maxGarageModeRunningDurationInSecs smaller than minimum required, resource:"
-                    + mShutdownPrepareTimeMs + "(ms) while should exceed:"
-                    +  MIN_MAX_GARAGE_MODE_DURATION_MS + "(ms), Ignore resource.");
+            Slogf.w(TAG,
+                    "maxGarageModeRunningDurationInSecs smaller than minimum required, "
+                    + "resource:%d(ms) while should exceed:%d(ms), Ignore resource.",
+                    mShutdownPrepareTimeMs, MIN_MAX_GARAGE_MODE_DURATION_MS);
             mShutdownPrepareTimeMs = MIN_MAX_GARAGE_MODE_DURATION_MS;
         }
         mUserService = carUserService;
         mCarPowerPolicyDaemon = powerPolicyDaemon;
+        if (powerPolicyDaemon != null) {
+            // For testing purpose
+            mHasControlOverDaemon = true;
+        }
         mWifiManager = context.getSystemService(WifiManager.class);
         mWifiStateFile = new AtomicFile(
                 new File(mSystemInterface.getSystemCarDir(), WIFI_STATE_FILENAME));
@@ -325,7 +331,7 @@
             // Initialize CPMS in WAIT_FOR_VHAL state
             onApPowerStateChange(CpmsState.WAIT_FOR_VHAL, CarPowerStateListener.WAIT_FOR_VHAL);
         } else {
-            Slog.w(TAG, "Vehicle hal does not support power state yet.");
+            Slogf.w(TAG, "Vehicle hal does not support power state yet.");
             onApPowerStateChange(CpmsState.ON, CarPowerStateListener.ON);
         }
         mSystemInterface.startDisplayStateMonitoring(this);
@@ -397,7 +403,7 @@
         }
         handleWaitForVhal(new CpmsState(CpmsState.WAIT_FOR_VHAL,
                 CarPowerStateListener.WAIT_FOR_VHAL));
-        Slog.d(TAG, "setStateForTesting(): mIsBooting is set to false and power state is switched "
+        Slogf.d(TAG, "setStateForTesting(): mIsBooting is set to false and power state is switched "
                 + "to Wait For Vhal");
     }
 
@@ -429,10 +435,10 @@
             state = mPendingPowerStates.peekFirst();
             mPendingPowerStates.clear();
             if (state == null) {
-                Slog.e(TAG, "Null power state was requested");
+                Slogf.e(TAG, "Null power state was requested");
                 return;
             }
-            Slog.i(TAG, "doHandlePowerStateChange: newState=" + state.name());
+            Slogf.i(TAG, "doHandlePowerStateChange: newState=%s", state.name());
             if (!needPowerStateChangeLocked(state)) {
                 return;
             }
@@ -441,7 +447,7 @@
             mCurrentState = state;
         }
         mHandler.cancelProcessingComplete();
-        Slog.i(TAG, "setCurrentState " + state.toString());
+        Slogf.i(TAG, "setCurrentState %s", state);
         CarStatsLogHelper.logPowerState(state.mState);
         switch (state.mState) {
             case CpmsState.WAIT_FOR_VHAL:
@@ -500,7 +506,8 @@
                     CarLocalServices.getService(CarUserNoticeService.class);
             if (currentUserInfo != null && currentUserInfo.isGuest()
                     && carUserNoticeService != null) {
-                Slog.i(TAG, "Car user notice service will ignore all messages before user switch.");
+                Slogf.i(TAG, "Car user notice service will ignore all messages before user "
+                        + "switch.");
                 Intent intent = new Intent();
                 intent.setComponent(new ComponentName(mContext.getPackageName(),
                         ContinuousBlankActivity.class.getName()));
@@ -509,7 +516,7 @@
                 carUserNoticeService.ignoreUserNotice(currentUserId);
             }
         } catch (Exception e) {
-            Slog.w(TAG, "Cannot ignore user notice for current user", e);
+            Slogf.w(TAG, e, "Cannot ignore user notice for current user");
         }
     }
 
@@ -540,7 +547,7 @@
 
         synchronized (mLock) {
             if (mIsBooting) {
-                Slog.d(TAG, "handleOn(): called on boot");
+                Slogf.d(TAG, "handleOn(): called on boot");
                 mIsBooting = false;
                 return;
             }
@@ -549,7 +556,7 @@
         try {
             mUserService.onResume();
         } catch (Exception e) {
-            Slog.e(TAG, "Could not switch user on resume", e);
+            Slogf.e(TAG, e, "Could not switch user on resume");
         }
     }
 
@@ -561,11 +568,11 @@
         }
 
         try {
-            Slog.i(TAG, "Factory resetting as it was delayed by user");
+            Slogf.i(TAG, "Factory resetting as it was delayed by user");
             callback.send(/* resultCode= */ 0, /* resultData= */ null);
             return true;
         } catch (Exception e) {
-            Slog.wtf(TAG, "Should have factory reset, but failed", e);
+            Slogf.wtf(TAG, e, "Should have factory reset, but failed");
             return false;
         }
     }
@@ -577,8 +584,8 @@
                     .getDefaultPowerPolicyForState(mCurrentPowerPolicyGroupId, state);
         }
         if (policy == null && fallbackPolicyId == null) {
-            Slog.w(TAG, "No default power policy for " + PolicyReader.powerStateToString(state)
-                    + " is found");
+            Slogf.w(TAG, "No default power policy for %s is found",
+                    PolicyReader.powerStateToString(state));
             return;
         }
         String policyId = policy == null ? fallbackPolicyId : policy.getPolicyId();
@@ -615,7 +622,7 @@
                     || !newState.mCanSleep;
             mGarageModeShouldExitImmediately = !newState.mCanPostpone;
         }
-        Slog.i(TAG,
+        Slogf.i(TAG,
                 (newState.mCanPostpone
                 ? "starting shutdown prepare with Garage Mode"
                         : "starting shutdown prepare without Garage Mode"));
@@ -627,7 +634,7 @@
 
     // Simulate system shutdown to Deep Sleep
     private void simulateShutdownPrepare() {
-        Slog.i(TAG, "starting shutdown prepare");
+        Slogf.i(TAG, "starting shutdown prepare");
         sendPowerManagerEvent(CarPowerStateListener.SHUTDOWN_PREPARE);
         mHal.sendShutdownPrepare();
         doHandlePreprocessing();
@@ -666,9 +673,9 @@
         if (forceReboot) {
             PowerManager powerManager = mContext.getSystemService(PowerManager.class);
             if (powerManager == null) {
-                Slog.wtf(TAG, "No PowerManager. Cannot reboot.");
+                Slogf.wtf(TAG, "No PowerManager. Cannot reboot.");
             } else {
-                Slog.i(TAG, "GarageMode has completed. Forcing reboot.");
+                Slogf.i(TAG, "GarageMode has completed. Forcing reboot.");
                 powerManager.reboot("GarageModeReboot");
                 throw new AssertionError("Should not return from PowerManager.reboot()");
             }
@@ -689,7 +696,7 @@
         boolean needToRestore = readWifiModifiedState();
         if (needToRestore) {
             if (!mWifiManager.isWifiEnabled()) {
-                Slog.i(TAG, "Wifi has been enabled to restore the last setting");
+                Slogf.i(TAG, "Wifi has been enabled to restore the last setting");
                 mWifiManager.setWifiEnabled(true);
             }
             // Update the persistent data as wifi is not modified by car framework.
@@ -706,7 +713,7 @@
         if (!wifiEnabled) return;
 
         mWifiManager.setWifiEnabled(false);
-        Slog.i(TAG, "Wifi has been disabled and the last setting was saved");
+        Slogf.i(TAG, "Wifi has been disabled and the last setting was saved");
     }
 
     private void saveWifiModifiedState(boolean forciblyDisabled) {
@@ -714,7 +721,7 @@
         try {
             fos = mWifiStateFile.startWrite();
         } catch (IOException e) {
-            Slog.e(TAG, "Cannot create " + WIFI_STATE_FILENAME, e);
+            Slogf.e(TAG, e, "Cannot create %s", WIFI_STATE_FILENAME);
             return;
         }
 
@@ -726,7 +733,7 @@
             mWifiStateFile.finishWrite(fos);
         } catch (IOException e) {
             mWifiStateFile.failWrite(fos);
-            Slog.e(TAG, "Writing " + WIFI_STATE_FILENAME + " failed", e);
+            Slogf.e(TAG, e, "Writing %s failed", WIFI_STATE_FILENAME);
         }
     }
 
@@ -747,7 +754,7 @@
             }
         } catch (IOException e) {
             // If a file named wifi_state doesn't exist, we will not modify Wifi at system start.
-            Slog.w(TAG, "Failed to read " + WIFI_STATE_FILENAME + ": " + e);
+            Slogf.w(TAG, "Failed to read %s: %s", WIFI_STATE_FILENAME, e);
             return false;
         }
         if (invalidState) {
@@ -780,21 +787,30 @@
                 pollingCount =
                         (shutdownPrepareTimeOverrideInSecs * 1000 / intervalMs)
                                 + 1;
-                Slog.i(TAG, "Garage mode duration overridden secs:"
-                        + shutdownPrepareTimeOverrideInSecs);
+                Slogf.i(TAG, "Garage mode duration overridden secs: %d",
+                        shutdownPrepareTimeOverrideInSecs);
             }
         }
-        Slog.i(TAG, "processing before shutdown expected for: "
-                + mShutdownPrepareTimeMs + " ms, adding polling:" + pollingCount);
+        Slogf.i(TAG, "processing before shutdown expected for: %dms, adding polling:%d",
+                mShutdownPrepareTimeMs, pollingCount);
+        boolean allAreComplete;
         synchronized (mLock) {
             mProcessingStartTime = SystemClock.elapsedRealtime();
             releaseTimerLocked();
-            mTimer = new Timer();
-            mTimerActive = true;
-            mTimer.scheduleAtFixedRate(
-                    new ShutdownProcessingTimerTask(pollingCount),
-                    0 /*delay*/,
-                    intervalMs);
+            allAreComplete = mListenersWeAreWaitingFor.isEmpty();
+            if (allAreComplete) {
+                Slogf.i(TAG, "Listener queue is empty, don't start polling");
+            } else {
+                mTimer = new Timer();
+                mTimerActive = true;
+                mTimer.scheduleAtFixedRate(
+                        new ShutdownProcessingTimerTask(pollingCount),
+                        /* delay= */ 0,
+                        intervalMs);
+            }
+        }
+        if (allAreComplete) {
+            signalComplete();
         }
         // allowUserSwitch value doesn't matter for onSuspend = true
         mUserService.onSuspend();
@@ -847,7 +863,7 @@
                 listener.onStateChanged(newState);
             } catch (RemoteException e) {
                 // It's likely the connection snapped. Let binder death handle the situation.
-                Slog.e(TAG, "onStateChanged() call failed", e);
+                Slogf.e(TAG, e, "onStateChanged() call failed");
             }
         }
         listenerList.finishBroadcast();
@@ -879,7 +895,7 @@
             // Any wakeup time from before is no longer valid.
             mNextWakeupSec = 0;
         }
-        Slog.i(TAG, "Resuming after suspending");
+        Slogf.i(TAG, "Resuming after suspending");
         mSystemInterface.refreshDisplayBrightness();
         onApPowerStateChange(CpmsState.WAIT_FOR_VHAL, nextListenerState);
     }
@@ -888,7 +904,7 @@
         if (mCurrentState == null) {
             return true;
         } else if (mCurrentState.equals(newState)) {
-            Slog.d(TAG, "Requested state is already in effect: " + newState.name());
+            Slogf.d(TAG, "Requested state is already in effect: %s", newState.name());
             return false;
         }
 
@@ -922,13 +938,13 @@
                         || newState.mState == CpmsState.WAIT_FOR_VHAL);
                 break;
             default:
-                Slog.e(TAG, "Unexpected current state:  currentState="
-                        + mCurrentState.name() + ", newState=" + newState.name());
+                Slogf.e(TAG, "Unexpected current state: currentState=%s, newState=%s",
+                        mCurrentState.name(), newState.name());
                 transitionAllowed = true;
         }
         if (!transitionAllowed) {
-            Slog.e(TAG, "Requested power transition is not allowed: "
-                    + mCurrentState.name() + " --> " + newState.name());
+            Slogf.e(TAG, "Requested power transition is not allowed: %s --> %s",
+                    mCurrentState.name(), newState.name());
         }
         return transitionAllowed;
     }
@@ -939,7 +955,7 @@
             releaseTimerLocked();
             if (!mShutdownOnFinish && mLastSleepEntryTime > mProcessingStartTime) {
                 // entered sleep after processing start. So this could be duplicate request.
-                Slog.w(TAG, "Duplicate sleep entry request, ignore");
+                Slogf.w(TAG, "Duplicate sleep entry request, ignore");
                 return;
             }
             listenerState = mShutdownOnFinish
@@ -959,7 +975,7 @@
     }
 
     private void doHandleMainDisplayStateChange(boolean on) {
-        Slog.w(TAG, "Unimplemented:  doHandleMainDisplayStateChange() - on = " + on);
+        Slogf.w(TAG, "Unimplemented:  doHandleMainDisplayStateChange() - on = %b", on);
     }
 
     /**
@@ -1030,13 +1046,13 @@
     public void scheduleNextWakeupTime(int seconds) {
         ICarImpl.assertPermission(mContext, Car.PERMISSION_CAR_POWER);
         if (seconds < 0) {
-            Slog.w(TAG, "Next wake up time is negative. Ignoring!");
+            Slogf.w(TAG, "Next wake up time is negative. Ignoring!");
             return;
         }
         boolean timedWakeupAllowed = mHal.isTimedWakeupAllowed();
         synchronized (mLock) {
             if (!timedWakeupAllowed) {
-                Slog.w(TAG, "Setting timed wakeups are disabled in HAL. Skipping");
+                Slogf.w(TAG, "Setting timed wakeups are disabled in HAL. Skipping");
                 mNextWakeupSec = 0;
                 return;
             }
@@ -1044,7 +1060,7 @@
                 // The new value is sooner than the old value. Take the new value.
                 mNextWakeupSec = seconds;
             } else {
-                Slog.d(TAG, "Tried to schedule next wake up, but already had shorter "
+                Slogf.d(TAG, "Tried to schedule next wake up, but already had shorter "
                         + "scheduled time");
             }
         }
@@ -1116,7 +1132,7 @@
     }
 
     void notifySilentModeChange(boolean silent) {
-        Slog.i(TAG, "Silent mode is set to " + silent);
+        Slogf.i(TAG, "Silent mode is set to %b", silent);
         mSilentModeHandler.updateKernelSilentMode(silent);
         if (silent) {
             applyPreemptivePowerPolicy(PolicyReader.POWER_POLICY_ID_NO_USER_INTERACTION);
@@ -1154,48 +1170,59 @@
                 if (!mShutdownOnFinish) {
                     if (mLastSleepEntryTime > mProcessingStartTime
                             && mLastSleepEntryTime < SystemClock.elapsedRealtime()) {
-                        Slog.i(TAG, "signalComplete: Already slept!");
+                        Slogf.i(TAG, "signalComplete: Already slept!");
                         return;
                     }
                 }
                 powerHandler = mHandler;
             }
-            Slog.i(TAG, "Apps are finished, call handleProcessingComplete()");
+            Slogf.i(TAG, "Apps are finished, call handleProcessingComplete()");
             powerHandler.handleProcessingComplete();
         }
     }
 
     private void initializePowerPolicy() {
-        Slog.i(TAG, "CPMS is taking control from carpowerpolicyd");
+        Slogf.i(TAG, "CPMS is taking control from carpowerpolicyd");
         ICarPowerPolicySystemNotification daemon;
-        String currentPowerPolicyId;
         synchronized (mLock) {
             daemon = mCarPowerPolicyDaemon;
-            currentPowerPolicyId = mCurrentPowerPolicyId;
         }
+        PolicyState state;
         if (daemon != null) {
-            PolicyState state;
             try {
                 state = daemon.notifyCarServiceReady();
-                String errMsg = setCurrentPowerPolicyGroup(state.policyGroupId);
-                if (errMsg != null) {
-                    Slog.w(TAG, errMsg);
-                }
             } catch (RemoteException e) {
-                Slog.e(TAG, "Failed to tell car power policy daemon that CarService is ready", e);
+                Slogf.e(TAG, e, "Failed to tell car power policy daemon that CarService is ready");
                 return;
             }
-            if (currentPowerPolicyId == null || currentPowerPolicyId.isEmpty()) {
-                String errorMsg = applyPowerPolicy(state.policyId, false);
-                if (errorMsg != null) {
-                    Slog.w(TAG, "Cannot apply power policy: " + errorMsg);
-                }
-            } else if (state.policyId.equals(currentPowerPolicyId)) {
-                notifyPowerPolicyChangeToDaemon(currentPowerPolicyId);
-            }
         } else {
-            Slog.w(TAG, "Failed to notify car service is ready. car power policy daemon is not "
+            Slogf.w(TAG, "Failed to notify car service is ready. car power policy daemon is not "
                     + "available");
+            return;
+        }
+
+        String currentPowerPolicyId;
+        String currentPolicyGroupId;
+        synchronized (mLock) {
+            mHasControlOverDaemon = true;
+            currentPowerPolicyId = mCurrentPowerPolicyId;
+            currentPolicyGroupId = mCurrentPowerPolicyGroupId;
+        }
+        // In case where CPMS received power state change before taking control from the daemon.
+        notifyPowerPolicyChangeToDaemon(currentPowerPolicyId);
+        // If the current power policy or the policy group has been modified by CPMS, we ignore
+        // the power policy or the policy group passed from car power policy daemon.
+        if (currentPowerPolicyId == null || currentPowerPolicyId.isEmpty()) {
+            String errorMsg = applyPowerPolicy(state.policyId, false);
+            if (errorMsg != null) {
+                Slogf.w(TAG, "Cannot apply power policy: %s", errorMsg);
+            }
+        }
+        if (currentPolicyGroupId == null || currentPolicyGroupId.isEmpty()) {
+            String errMsg = setCurrentPowerPolicyGroup(state.policyGroupId);
+            if (errMsg != null) {
+                Slogf.w(TAG, "Cannot set power policy group: %s", errMsg);
+            }
         }
         mSilentModeHandler.init();
     }
@@ -1218,8 +1245,8 @@
         }
         synchronized (mLock) {
             if (mIsPowerPolicyLocked) {
-                Slog.i(TAG, "Power policy is locked. The request policy(" + policyId
-                        + ") will be applied when power policy becomes unlocked");
+                Slogf.i(TAG, "Power policy is locked. The request policy(%s) will be applied when "
+                        + "power policy becomes unlocked", policyId);
                 mPendingPowerPolicyId = policyId;
                 return null;
             }
@@ -1227,7 +1254,7 @@
         }
         mPowerComponentHandler.applyPowerPolicy(policy);
         notifyPowerPolicyChange(policyId, upToDaemon);
-        Slog.i(TAG, "The current power policy is " + policyId);
+        Slogf.i(TAG, "The current power policy is %s", policyId);
         return null;
     }
 
@@ -1245,7 +1272,7 @@
         }
         mPowerComponentHandler.applyPowerPolicy(policy);
         notifyPowerPolicyChange(policyId, true);
-        Slog.i(TAG, "The current power policy is " + policyId);
+        Slogf.i(TAG, "The current power policy is %s", policyId);
         return null;
     }
 
@@ -1253,7 +1280,7 @@
         String policyId;
         synchronized (mLock) {
             if (!mIsPowerPolicyLocked) {
-                Slog.w(TAG, "Failed to cancel system power policy: the current policy is not the "
+                Slogf.w(TAG, "Failed to cancel system power policy: the current policy is not the "
                         + "system power policy");
                 return;
             }
@@ -1262,24 +1289,29 @@
         }
         String errMsg = applyPowerPolicy(policyId, true);
         if (errMsg != null) {
-            Slog.w(TAG, "Failed to cancel system power policy: " + errMsg);
+            Slogf.w(TAG, "Failed to cancel system power policy: %s", errMsg);
         }
     }
 
     private void notifyPowerPolicyChangeToDaemon(String policyId) {
         ICarPowerPolicySystemNotification daemon;
+        boolean hadPendingPolicyNotification;
         synchronized (mLock) {
             daemon = mCarPowerPolicyDaemon;
-        }
-        if (daemon == null) {
-            Slog.e(TAG, "Failed to notify car power policy daemon: the daemon is not ready");
-            return;
+            if (daemon == null) {
+                Slogf.e(TAG, "Failed to notify car power policy daemon: the daemon is not ready");
+                return;
+            }
+            if (!mHasControlOverDaemon) {
+                Slogf.w(TAG, "Notifying policy change is deferred: CPMS has not yet taken control");
+                return;
+            }
         }
         try {
             daemon.notifyPowerPolicyChange(policyId);
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Failed to notify car power policy daemon of a new power policy("
-                    + policyId + ")", e);
+        } catch (RemoteException | IllegalStateException e) {
+            Slogf.e(TAG, e, "Failed to notify car power policy daemon of a new power policy(%s)",
+                    policyId);
         }
     }
 
@@ -1295,7 +1327,7 @@
                 ? mPolicyReader.getPreemptivePowerPolicy(policyId)
                 : mPolicyReader.getPowerPolicy(policyId);
         if (appliedPolicy == null) {
-            Slog.wtf(TAG, "The new power policy(" + policyId + ") should exist");
+            Slogf.wtf(TAG, "The new power policy(%s) should exist", policyId);
         }
         int idx = mPowerPolicyListeners.beginBroadcast();
         while (idx-- > 0) {
@@ -1309,7 +1341,7 @@
                 listener.onPolicyChanged(appliedPolicy, accumulatedPolicy);
             } catch (RemoteException e) {
                 // It's likely the connection snapped. Let binder death handle the situation.
-                Slog.e(TAG, "onPolicyChanged() call failed: policyId = " + policyId, e);
+                Slogf.e(TAG, e, "onPolicyChanged() call failed: policyId = %s", policyId);
             }
         }
         mPowerPolicyListeners.finishBroadcast();
@@ -1320,7 +1352,7 @@
         String errMsg = applyPreemptivePowerPolicy(
                 PolicyReader.POWER_POLICY_ID_NO_USER_INTERACTION);
         if (errMsg != null) {
-            Slog.w(TAG, errMsg);
+            Slogf.w(TAG, errMsg);
         }
     }
 
@@ -1339,12 +1371,12 @@
             synchronized (mLock) {
                 mConnectionInProgress = false;
             }
-            Slog.e(TAG, "Cannot reconnect to car power policyd daemon after retrying "
-                    + CAR_POWER_POLICY_DAEMON_BIND_MAX_RETRY + " times");
+            Slogf.e(TAG, "Cannot reconnect to car power policyd daemon after retrying %d times",
+                    CAR_POWER_POLICY_DAEMON_BIND_MAX_RETRY);
             return;
         }
         if (makeBinderConnection()) {
-            Slog.i(TAG, "Connected to car power policy daemon");
+            Slogf.i(TAG, "Connected to car power policy daemon");
             initializePowerPolicy();
             return;
         }
@@ -1358,20 +1390,20 @@
         long currentTimeMs = SystemClock.uptimeMillis();
         IBinder binder = ServiceManager.getService(CAR_POWER_POLICY_DAEMON_INTERFACE);
         if (binder == null) {
-            Slog.w(TAG, "Finding car power policy daemon failed. Power policy management is not "
+            Slogf.w(TAG, "Finding car power policy daemon failed. Power policy management is not "
                     + "supported");
             return false;
         }
         long elapsedTimeMs = SystemClock.uptimeMillis() - currentTimeMs;
         if (elapsedTimeMs > CAR_POWER_POLICY_DAEMON_FIND_MARGINAL_TIME_MS) {
-            Slog.wtf(TAG, "Finding car power policy daemon took too long(" + elapsedTimeMs + "ms)");
+            Slogf.wtf(TAG, "Finding car power policy daemon took too long(%dms)", elapsedTimeMs);
         }
 
         ICarPowerPolicySystemNotification daemon =
                 ICarPowerPolicySystemNotification.Stub.asInterface(binder);
         if (daemon == null) {
-            Slog.w(TAG, "Getting car power policy daemon interface failed. Power policy management "
-                    + "is not supported");
+            Slogf.w(TAG, "Getting car power policy daemon interface failed. Power policy management"
+                    + " is not supported");
             return false;
         }
         synchronized (mLock) {
@@ -1392,11 +1424,12 @@
 
         @Override
         public void binderDied() {
-            Slog.w(TAG, "Car power policy daemon died: reconnecting");
+            Slogf.w(TAG, "Car power policy daemon died: reconnecting");
             unlinkToDeath();
             mDaemon = null;
             synchronized (mLock) {
                 mCarPowerPolicyDaemon = null;
+                mHasControlOverDaemon = false;
             }
             mHandler.sendMessageDelayed(PooledLambda.obtainMessage(
                     CarPowerManagementService::connectToDaemonHelper,
@@ -1410,14 +1443,14 @@
             }
             IBinder binder = mDaemon.asBinder();
             if (binder == null) {
-                Slog.w(TAG, "Linking to binder death recipient skipped");
+                Slogf.w(TAG, "Linking to binder death recipient skipped");
                 return;
             }
             try {
                 binder.linkToDeath(this, 0);
             } catch (RemoteException e) {
                 mDaemon = null;
-                Slog.w(TAG, "Linking to binder death recipient failed: " + e);
+                Slogf.w(TAG, e, "Linking to binder death recipient failed: %s");
             }
         }
 
@@ -1427,7 +1460,7 @@
             }
             IBinder binder = mDaemon.asBinder();
             if (binder == null) {
-                Slog.w(TAG, "Unlinking from binder death recipient skipped");
+                Slogf.w(TAG, "Unlinking from binder death recipient skipped");
                 return;
             }
             binder.unlinkToDeath(this, 0);
@@ -1489,7 +1522,7 @@
         public void handleMessage(Message msg) {
             CarPowerManagementService service = mService.get();
             if (service == null) {
-                Slog.i(TAG, "handleMessage null service");
+                Slogf.i(TAG, "handleMessage null service");
                 return;
             }
             switch (msg.what) {
@@ -1548,7 +1581,7 @@
         long totalWaitDurationMs = 0;
 
         while (true) {
-            Slog.i(TAG, "Entering Suspend to RAM");
+            Slogf.i(TAG, "Entering Suspend to RAM");
             boolean suspendSucceeded = mSystemInterface.enterDeepSleep();
             if (suspendSucceeded) {
                 return true;
@@ -1559,7 +1592,7 @@
             // We failed to suspend. Block the thread briefly and try again.
             synchronized (mLock) {
                 if (mPendingPowerStates.isEmpty()) {
-                    Slog.w(TAG, "Failed to Suspend; will retry after " + retryIntervalMs + "ms.");
+                    Slogf.w(TAG, "Failed to Suspend; will retry after %dms", retryIntervalMs);
                     try {
                         mLock.wait(retryIntervalMs);
                     } catch (InterruptedException ignored) {
@@ -1570,14 +1603,14 @@
                 }
                 // Check for a new power state now, before going around the loop again
                 if (!mPendingPowerStates.isEmpty()) {
-                    Slog.i(TAG, "Terminating the attempt to Suspend to RAM");
+                    Slogf.i(TAG, "Terminating the attempt to Suspend to RAM");
                     return false;
                 }
             }
         }
         // Too many failures trying to suspend. Shut down.
-        Slog.w(TAG, "Could not Suspend to RAM after " + totalWaitDurationMs
-                + "ms long trial. Shutting down.");
+        Slogf.w(TAG, "Could not Suspend to RAM after %dms long trial. Shutting down.",
+                totalWaitDurationMs);
         mSystemInterface.shutdown();
         return false;
     }
@@ -1951,7 +1984,7 @@
     // We simulate this behavior by calling wait().
     // We continue from wait() when forceSimulatedResume() is called.
     private void simulateSleepByWaiting() {
-        Slog.i(TAG, "Starting to simulate Deep Sleep by waiting");
+        Slogf.i(TAG, "Starting to simulate Deep Sleep by waiting");
         synchronized (mSimulationWaitObject) {
             while (!mWakeFromSimulatedSleep) {
                 try {
@@ -1962,7 +1995,7 @@
             }
             mInSimulatedDeepSleepMode = false;
         }
-        Slog.i(TAG, "Exit Deep Sleep simulation");
+        Slogf.i(TAG, "Exit Deep Sleep simulation");
     }
 
     private int getMaxSuspendWaitDurationConfig() {
diff --git a/service/src/com/android/car/power/PolicyReader.java b/service/src/com/android/car/power/PolicyReader.java
index 61e63d5..ca36b1d 100644
--- a/service/src/com/android/car/power/PolicyReader.java
+++ b/service/src/com/android/car/power/PolicyReader.java
@@ -34,7 +34,6 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.IndentingPrintWriter;
-import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
 import android.util.Xml;
@@ -42,6 +41,7 @@
 import com.android.car.CarLog;
 import com.android.car.CarServiceUtils;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.utils.Slogf;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -307,8 +307,8 @@
         try (InputStream inputStream = new FileInputStream(VENDOR_POLICY_PATH)) {
             readPowerPolicyFromXml(inputStream);
         } catch (IOException | XmlPullParserException | PolicyXmlException e) {
-            Slog.w(TAG, "Proceed without registered policies: failed to parse "
-                    + VENDOR_POLICY_PATH + ": " + e);
+            Slogf.w(TAG, "Proceed without registered policies: failed to parse %s: %s",
+                    VENDOR_POLICY_PATH, e);
         }
     }
 
diff --git a/service/src/com/android/car/power/PowerComponentHandler.java b/service/src/com/android/car/power/PowerComponentHandler.java
index c73696e..573d62e 100644
--- a/service/src/com/android/car/power/PowerComponentHandler.java
+++ b/service/src/com/android/car/power/PowerComponentHandler.java
@@ -45,8 +45,6 @@
 import android.os.ServiceManager;
 import android.util.AtomicFile;
 import android.util.IndentingPrintWriter;
-import android.util.Log;
-import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
 
@@ -55,6 +53,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IVoiceInteractionManagerService;
+import com.android.server.utils.Slogf;
 
 import java.io.BufferedReader;
 import java.io.BufferedWriter;
@@ -122,12 +121,12 @@
                 PowerComponentMediator mediator = factory.createPowerComponent(component);
                 String componentName = powerComponentToString(component);
                 if (mediator == null) {
-                    Slog.w(TAG, "Power component(" + componentName + ") is not valid or doesn't "
-                            + "need a mediator");
+                    Slogf.w(TAG, "Power component(%s) is not valid or doesn't need a mediator",
+                            componentName);
                     continue;
                 }
                 if (!mediator.isComponentAvailable()) {
-                    Slog.w(TAG, "Power component(" + componentName + ") is not available");
+                    Slogf.w(TAG, "Power component(%s) is not available", componentName);
                     continue;
                 }
                 mediator.init();
@@ -258,7 +257,7 @@
         }
         PowerComponentMediator mediator = mPowerComponentMediators.get(component);
         if (mediator == null) {
-            Slog.w(TAG, powerComponentToString(component) + " doesn't have a mediator");
+            Slogf.w(TAG, "%s doesn't have a mediator", powerComponentToString(component));
             return modified;
         }
         if (modified && mediator.isControlledBySystem()) {
@@ -286,7 +285,7 @@
             // Behave as if there are no forced-off components.
             return;
         } catch (IOException e) {
-            Slog.w(TAG, "Failed to read " + FORCED_OFF_COMPONENTS_FILENAME + ": " + e);
+            Slogf.w(TAG, "Failed to read %s: %s", FORCED_OFF_COMPONENTS_FILENAME, e);
             return;
         }
         if (invalid) {
@@ -299,7 +298,7 @@
         try {
             fos = mOffComponentsByUserFile.startWrite();
         } catch (IOException e) {
-            Slog.e(TAG, "Cannot create " + FORCED_OFF_COMPONENTS_FILENAME, e);
+            Slogf.e(TAG, e, "Cannot create %s", FORCED_OFF_COMPONENTS_FILENAME);
             return;
         }
 
@@ -316,7 +315,7 @@
             mOffComponentsByUserFile.finishWrite(fos);
         } catch (IOException e) {
             mOffComponentsByUserFile.failWrite(fos);
-            Slog.e(TAG, "Writing " + FORCED_OFF_COMPONENTS_FILENAME + " failed", e);
+            Slogf.e(TAG, e, "Writing %s failed", FORCED_OFF_COMPONENTS_FILENAME);
         }
     }
 
@@ -340,14 +339,6 @@
         }
     }
 
-    private void logd(String messageFormat, Object... args) {
-        if (Log.isLoggable(TAG, Log.DEBUG)) {
-            // TODO(b/182476140): Replace with formatted Slog.
-            String message = String.format(messageFormat, args);
-            Slog.d(TAG, message);
-        }
-    }
-
     abstract static class PowerComponentMediator {
         protected int mComponentId;
 
@@ -390,7 +381,7 @@
         @Override
         public void setEnabled(boolean enabled) {
             mSystemInterface.setDisplayState(enabled);
-            logd("Display power component is %s", enabled ? "on" : "off");
+            Slogf.d(TAG, "Display power component is %s", enabled ? "on" : "off");
         }
     }
 
@@ -442,7 +433,7 @@
         @Override
         public void setEnabled(boolean enabled) {
             mWifiManager.setWifiEnabled(enabled);
-            logd("Wifi power component is %s", enabled ? "on" : "off");
+            Slogf.d(TAG, "Wifi power component is %s", enabled ? "on" : "off");
         }
     }
 
@@ -475,10 +466,9 @@
             try {
                 mVoiceInteractionManagerService.setDisabled(!enabled);
                 mIsEnabled = enabled;
-                logd("Voice Interaction power component is %s", enabled ? "on" : "off");
+                Slogf.d(TAG, "Voice Interaction power component is %s", enabled ? "on" : "off");
             } catch (RemoteException e) {
-                Slog.w(TAG, "IVoiceInteractionManagerService.setDisabled(" + !enabled + ") failed",
-                        e);
+                Slogf.w(TAG, e, "IVoiceInteractionManagerService.setDisabled(%b) failed", !enabled);
             }
         }
     }
@@ -531,7 +521,7 @@
         @Override
         public void setEnabled(boolean enabled) {
             // No op
-            Slog.w(TAG, "Bluetooth power is controlled by "
+            Slogf.w(TAG, "Bluetooth power is controlled by "
                     + "com.android.car.BluetoothDeviceConnectionPolicy");
         }
     }
@@ -584,7 +574,7 @@
         @Override
         public void setEnabled(boolean enabled) {
             // No op
-            Slog.w(TAG, "NFC power isn't controlled by CPMS");
+            Slogf.w(TAG, "NFC power isn't controlled by CPMS");
         }
     }
 
@@ -636,7 +626,7 @@
         @Override
         public void setEnabled(boolean enabled) {
             // No op
-            Slog.w(TAG, "Location power isn controlled by GNSS HAL");
+            Slogf.w(TAG, "Location power isn controlled by GNSS HAL");
         }
     }
 
@@ -689,7 +679,7 @@
                 case PowerComponent.CPU:
                     return null;
                 default:
-                    Slog.w(TAG, "Unknown component(" + component + ")");
+                    Slogf.w(TAG, "Unknown component(%d)", component);
                     return null;
             }
         }
diff --git a/service/src/com/android/car/power/SilentModeHandler.java b/service/src/com/android/car/power/SilentModeHandler.java
index e12978a..2c557f8 100644
--- a/service/src/com/android/car/power/SilentModeHandler.java
+++ b/service/src/com/android/car/power/SilentModeHandler.java
@@ -21,11 +21,11 @@
 import android.os.FileObserver;
 import android.os.SystemProperties;
 import android.util.IndentingPrintWriter;
-import android.util.Slog;
 
 import com.android.car.CarLog;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.utils.Slogf;
 
 import libcore.io.IoUtils;
 
@@ -89,12 +89,12 @@
         }
         switch (bootReason) {
             case FORCED_SILENT:
-                Slog.i(TAG, "Starting in forced silent mode");
+                Slogf.i(TAG, "Starting in forced silent mode");
                 mForcedMode = true;
                 mSilentModeByHwState = true;
                 break;
             case FORCED_NON_SILENT:
-                Slog.i(TAG, "Starting in forced non-silent mode");
+                Slogf.i(TAG, "Starting in forced non-silent mode");
                 mForcedMode = true;
                 mSilentModeByHwState = false;
                 break;
@@ -110,8 +110,8 @@
         }
         if (forcedMode) {
             updateKernelSilentMode(mSilentModeByHwState);
-            Slog.i(TAG, "Now in forced mode: monitoring " + mHwStateMonitoringFileName
-                    + " is disabled");
+            Slogf.i(TAG, "Now in forced mode: monitoring %s is disabled",
+                    mHwStateMonitoringFileName);
         } else {
             startMonitoringSilentModeHwState();
         }
@@ -153,10 +153,10 @@
             String value = silent ? VALUE_SILENT_MODE : VALUE_NON_SILENT_MODE;
             writer.write(value);
             writer.flush();
-            Slog.i(TAG, mKernelSilentModeFileName + " is updated to " + value);
+            Slogf.i(TAG, "%s is updated to %s", mKernelSilentModeFileName, value);
         } catch (IOException e) {
-            Slog.w(TAG, "Failed to update " + mKernelSilentModeFileName + " to "
-                    + (silent ? VALUE_SILENT_MODE : VALUE_NON_SILENT_MODE));
+            Slogf.w(TAG, "Failed to update %s to %s", mKernelSilentModeFileName,
+                    silent ? VALUE_SILENT_MODE : VALUE_NON_SILENT_MODE);
         }
     }
 
@@ -172,7 +172,7 @@
                 switchToNonForcedMode();
                 break;
             default:
-                Slog.w(TAG, "Unsupported silent mode: " + silentMode);
+                Slogf.w(TAG, "Unsupported silent mode: %s", silentMode);
         }
     }
 
@@ -192,16 +192,16 @@
             updateKernelSilentMode(silentMode);
             mService.notifySilentModeChange(silentMode);
         }
-        Slog.i(TAG, "Now in forced " + (silentMode ? "silent" : "non-silent") + " mode: monitoring "
-                + mHwStateMonitoringFileName + " is disabled");
+        Slogf.i(TAG, "Now in forced %s mode: monitoring %s is disabled",
+                silentMode ? "silent" : "non-silent", mHwStateMonitoringFileName);
     }
 
     private void switchToNonForcedMode() {
         boolean updated = false;
         synchronized (mLock) {
             if (mForcedMode) {
-                Slog.i(TAG, "Now in non forced mode: monitoring " + mHwStateMonitoringFileName
-                        + " is started");
+                Slogf.i(TAG, "Now in non forced mode: monitoring %s is started",
+                        mHwStateMonitoringFileName);
                 mForcedMode = false;
                 updated = true;
             }
@@ -214,8 +214,8 @@
     private void startMonitoringSilentModeHwState() {
         File monitorFile = new File(mHwStateMonitoringFileName);
         if (!monitorFile.exists()) {
-            Slog.w(TAG, "Failed to start monitoring Silent Mode HW state: "
-                    + mHwStateMonitoringFileName + " doesn't exist");
+            Slogf.w(TAG, "Failed to start monitoring Silent Mode HW state: %s doesn't exist",
+                    mHwStateMonitoringFileName);
             return;
         }
         FileObserver fileObserver = new FileObserver(monitorFile, FileObserver.MODIFY) {
@@ -234,10 +234,10 @@
                         String contents = IoUtils.readFileAsString(mHwStateMonitoringFileName)
                                 .trim();
                         mSilentModeByHwState = VALUE_SILENT_MODE.equals(contents);
-                        Slog.i(TAG, mHwStateMonitoringFileName + " indicates "
-                                + (mSilentModeByHwState ? "silent" : "non-silent") + " mode");
+                        Slogf.i(TAG, "%s indicates %s mode", mHwStateMonitoringFileName,
+                                mSilentModeByHwState ? "silent" : "non-silent");
                     } catch (Exception e) {
-                        Slog.w(TAG, "Failed to read " + mHwStateMonitoringFileName, e);
+                        Slogf.w(TAG, e, "Failed to read %s: %s", mHwStateMonitoringFileName);
                         return;
                     }
                     newSilentMode = mSilentModeByHwState;
diff --git a/service/src/com/android/car/telemetry/CarTelemetryService.java b/service/src/com/android/car/telemetry/CarTelemetryService.java
new file mode 100644
index 0000000..b68e020
--- /dev/null
+++ b/service/src/com/android/car/telemetry/CarTelemetryService.java
@@ -0,0 +1,86 @@
+/*
+ * 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.telemetry;
+
+import android.annotation.NonNull;
+import android.car.Car;
+import android.car.telemetry.ICarTelemetryService;
+import android.car.telemetry.ICarTelemetryServiceListener;
+import android.content.Context;
+import android.util.IndentingPrintWriter;
+import android.util.Slog;
+
+import com.android.car.CarServiceBase;
+
+/**
+ * CarTelemetryService manages OEM telemetry collection, processing and communication
+ * with a data upload service.
+ */
+public class CarTelemetryService extends ICarTelemetryService.Stub implements CarServiceBase {
+
+    private static final boolean DEBUG = false;
+    private static final String TAG = CarTelemetryService.class.getSimpleName();
+
+    private final Context mContext;
+
+    private ICarTelemetryServiceListener mListener;
+
+    public CarTelemetryService(Context context) {
+        mContext = context;
+    }
+
+    @Override
+    public void init() {
+        // nothing to do
+    }
+
+    @Override
+    public void release() {
+        // nothing to do
+    }
+
+    @Override
+    public void dump(IndentingPrintWriter writer) {
+        writer.println("Car Telemetry service");
+    }
+
+    /**
+     * Registers a listener with CarTelemetryService for the service to send data to cloud app.
+     */
+    @Override
+    public void setListener(@NonNull ICarTelemetryServiceListener listener) {
+        // TODO(b/150978930): verify that only a hardcoded app can set the listener
+        mContext.enforceCallingOrSelfPermission(
+                Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE, "setListener");
+        if (DEBUG) {
+            Slog.d(TAG, "Setting the listener for car telemetry service");
+        }
+        mListener = listener;
+    }
+
+    /**
+     * Clears the listener registered with CarTelemetryService.
+     */
+    @Override
+    public void clearListener() {
+        mContext.enforceCallingOrSelfPermission(
+                Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE, "setListener");
+        if (DEBUG) {
+            Slog.d(TAG, "Clearing listener");
+        }
+        mListener = null;
+    }
+}
diff --git a/service/src/com/android/car/telemetry/LogFilter.java b/service/src/com/android/car/telemetry/LogFilter.java
new file mode 100644
index 0000000..8f72fe8
--- /dev/null
+++ b/service/src/com/android/car/telemetry/LogFilter.java
@@ -0,0 +1,205 @@
+/*
+ * 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.telemetry;
+
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.annotation.IntDef;
+import android.util.Log;
+
+import com.android.car.telemetry.TelemetryProto.LogListener;
+import com.android.car.telemetry.TelemetryProto.Manifest;
+
+import java.lang.annotation.Retention;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+/**
+ * Encapsulates a filter that can be matched on log entries. Contains builder function to create
+ * list of LogFilter given the loglistener.
+ */
+public final class LogFilter {
+
+    private static final String TAG = LogFilter.class.getSimpleName();
+
+    /**
+     * Types of filters to match against the logs. Different filter types will match in different
+     * ways.
+     */
+    @Retention(SOURCE)
+    @IntDef({FILTER_TAG, SUBSTRING})
+    @interface FilterType {}
+    static final int FILTER_TAG = 0;
+    static final int SUBSTRING = 1;
+
+    private final int mLogListenerIndex;
+    private final @FilterType int mFilterType;
+    private final String mFilter;
+
+    LogFilter(int logListenerIndex, @FilterType int filterType, String filter) {
+        this.mLogListenerIndex = logListenerIndex;
+        this.mFilterType = filterType;
+        if (filter == null) {
+            throw new NullPointerException("Null filter");
+        }
+        this.mFilter = filter;
+    }
+
+    int getLogListenerIndex() {
+        return mLogListenerIndex;
+    }
+
+    @LogFilter.FilterType int getFilterType() {
+        return mFilterType;
+    }
+
+    String getFilter() {
+        return mFilter;
+    }
+
+    /**
+     * Creates a LogFilter instance.
+     *
+     * @param logListenerIndex the index of the logListener associated with the filter.
+     * @param filterType the type of filter.
+     * @param filter the value of the filter.
+     * @return created LogFilter instance.
+     */
+    static LogFilter create(int logListenerIndex, @FilterType int filterType, String filter) {
+        return new LogFilter(logListenerIndex, filterType, filter);
+    }
+
+    /**
+     * Builds a List of {@link LogFilter} instances from {@link Manifest} and the logListener name.
+     *
+     * @param manifest {@link Manifest} file that contains list of logListeners.
+     * @param logListenerName the name of the logListener as registered in the {@link Manifest}.
+     * @return List of {@link LogFilter} instances.
+     */
+    static List<LogFilter> buildFilters(Manifest manifest, String logListenerName) {
+        int logListenerIndex = getLogListenerIndexFromName(manifest, logListenerName);
+        if (logListenerIndex == -1) {
+            Log.w(TAG, "log listener with name " + logListenerName
+                    + " does not exist in manifest.");
+            return Collections.unmodifiableList(new ArrayList<>());
+        }
+
+        List<LogFilter> result = new ArrayList<>();
+        result.addAll(
+                manifest.getLogListeners(logListenerIndex).getTagsList().stream()
+                .map(tag -> LogFilter.create(logListenerIndex, FILTER_TAG, tag))
+                .collect(Collectors.toList()));
+        result.addAll(
+                manifest.getLogListeners(logListenerIndex).getTypesList().stream()
+                .map(LogFilter::typeToFilter)
+                .filter(Optional::isPresent)
+                .map(filter ->
+                        LogFilter.create(logListenerIndex, SUBSTRING, filter.get()))
+                .collect(Collectors.toList()));
+        return Collections.unmodifiableList(result);
+    }
+
+    /**
+     * Gets the index of the {@link LogListener} in the manifest's list of logListeners.
+     * Returns -1 if not found.
+     *
+     * @param manifest the {@link Manifest} that contains the definitions of the logListeners.
+     * @param logListenerName name of the {@link LogListener} to get index for.
+     * @return index of the logListener, -1 if not found.
+     */
+    static int getLogListenerIndexFromName(Manifest manifest, String logListenerName) {
+        for (int i = 0; i < manifest.getLogListenersCount(); i++) {
+            LogListener listener = manifest.getLogListeners(i);
+            if (listener.getName().equals(logListenerName)) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    /**
+     * Converts log message filter {@link LogListener.Type} to corresponding filter string.
+     *
+     * @param type the filter {@link LogListener.Type}.
+     * @return the corresponding filter string for the logListener type.
+     */
+    private static Optional<String> typeToFilter(LogListener.Type type) {
+        switch (type) {
+            case TYPE_UNSPECIFIED:
+                return Optional.empty();
+            case EXCEPTIONS:
+                return Optional.of("Exception: ");
+        }
+        return Optional.empty();
+    }
+
+    /**
+     * Matches in different ways against {@link LogcatReader.LogEntry} components depending on
+     * filterType.
+     *
+     * @param entry the {@link LogcatReader.LogEntry} whose components will be matched against the
+     *       filters.
+     * @return boolean denoting whether the filter can match the {@link LogcatReader.LogEntry}.
+     */
+    boolean matches(LogcatReader.LogEntry entry) {
+        switch (getFilterType()) {
+            case FILTER_TAG:
+                return entry.mTag.equals(getFilter());
+            case SUBSTRING:
+                return entry.mMessage.contains(getFilter());
+        }
+        return false;
+    }
+
+    @Override
+    public String toString() {
+        return "LogFilter{"
+                + "mLogListenerIndex=" + mLogListenerIndex + ", "
+                + "mFilterType=" + mFilterType + ", "
+                + "mFilter=" + mFilter
+                + "}";
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o == this) {
+            return true;
+        }
+        if (o instanceof LogFilter) {
+            LogFilter that = (LogFilter) o;
+            return this.mLogListenerIndex == that.getLogListenerIndex()
+                    && this.mFilterType == that.getFilterType()
+                    && this.mFilter.equals(that.getFilter());
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        int hash = 1;
+        hash *= 1000003;
+        hash ^= mLogListenerIndex;
+        hash *= 1000003;
+        hash ^= Integer.hashCode(mFilterType);
+        hash *= 1000003;
+        hash ^= mFilter.hashCode();
+        return hash;
+    }
+}
+
diff --git a/service/src/com/android/car/telemetry/LogcatReader.java b/service/src/com/android/car/telemetry/LogcatReader.java
new file mode 100644
index 0000000..a83a72e
--- /dev/null
+++ b/service/src/com/android/car/telemetry/LogcatReader.java
@@ -0,0 +1,302 @@
+/*
+ * 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.telemetry;
+
+import android.annotation.Nullable;
+import android.net.LocalSocket;
+import android.net.LocalSocketAddress;
+import android.os.Bundle;
+import android.os.Process;
+import android.util.Log;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStreamWriter;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.StandardCharsets;
+import java.util.HashSet;
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.ObjIntConsumer;
+import java.util.function.Supplier;
+
+/**
+ * Reads Android logs while there are log listeners registered to it and sends the event through the
+ * mLogEventConsumer.
+ */
+public class LogcatReader {
+    private static final String TAG = LogcatReader.class.getSimpleName();
+
+    // TODO(b/180515554). Find a proper place for LOG_* constants.
+    // They will be used in ScriptExecutor as well.
+    /** The value of key to retrieve log seconds since epoch time from bundle. */
+    private static final String LOG_SEC_KEY = "log.sec";
+
+    /** The value of key to retrieve log nanoseconds from bundle. */
+    private static final String LOG_NSEC_KEY = "log.nsec";
+
+    /** The value of key to retrieve log tag from bundle. */
+    private static final String LOG_TAG_KEY = "log.tag";
+
+    /** The value of key to retrieve log message from bundle. */
+    private static final String LOG_MESSAGE_KEY = "log.message";
+
+    // Defined in system/core/liblog/include/log/log_read.h
+    private static final int LOGGER_ENTRY_MAX_LEN = 5 * 1024;
+
+    // The entry sizes differ in Android 10. See
+    // https://cs.android.com/android/platform/superproject/+/android-10.0.0_r30:system/core/liblog/include/log/log_read.h
+    public static final int ENTRY_V1_SIZE = 20;
+    public static final int ENTRY_V2_V3_SIZE = 24;
+    public static final int ENTRY_V4_SIZE = 28;
+
+    private final AtomicBoolean mRunning = new AtomicBoolean(false);
+
+    private final ObjIntConsumer<Bundle> mLogEventConsumer;
+    private final Supplier<LocalSocket> mLocalSocketSupplier;
+    private final Executor mExecutor;
+
+    private final HashSet<LogFilter> mLogFilters = new HashSet<>();
+
+    private synchronized boolean logFiltersEmpty() {
+        return mLogFilters.isEmpty();
+    }
+
+    /**
+     * Replicates {@code struct log_msg} from system/core/liblog/include/log/log_read.h and {@code
+     * struct AndroidLogEntry} from system/core/liblog/include/log/logprint.h.
+     */
+    static class LogEntry {
+        long mTvSec; // seconds since Epoch
+        long mTvNSec; // nanoseconds
+        int mPid; // generating process's pid
+        long mTid; // generating process's tid
+        long mUid; // generating process's uid
+        int mPriority; // log priority, e.g. {@link Log#INFO}.
+        String mTag;
+        String mMessage;
+
+        /**
+         * Parses raw bytes received from {@code logd}.
+         *
+         * @param data raw bytes
+         * @param readSize number of bytes received from logd.
+         */
+        @Nullable
+        static LogEntry parse(byte[] data, int readSize) {
+            // Parsing log_msg struct defined in system/core/liblog/include/log/log_read.h.
+            // Only first headerSize is used to create LogEntry. Following message messageSize bytes
+            // define log message.
+            ByteBuffer dataBytes = ByteBuffer.wrap(data);
+            dataBytes.order(ByteOrder.LITTLE_ENDIAN);
+            int messageSize = dataBytes.getShort();
+            int headerSize = dataBytes.getShort();
+            if (readSize < messageSize + headerSize) {
+                Log.w(
+                        TAG, "Invalid log message size "
+                        + (messageSize + headerSize)
+                        + ", received only "
+                        + readSize);
+                return null;
+            }
+            LogEntry entry = new LogEntry();
+            entry.mPid = dataBytes.getInt();
+            entry.mTid = dataBytes.getInt();
+            entry.mTvSec = dataBytes.getInt();
+            entry.mTvNSec = dataBytes.getInt();
+            if (headerSize >= ENTRY_V2_V3_SIZE) {
+                dataBytes.position(dataBytes.position() + 4); // lid is not used here.
+            }
+            if (headerSize >= ENTRY_V4_SIZE) {
+                entry.mUid = dataBytes.getInt();
+            }
+
+            // Parsing log message.
+            // See android_log_processLogBuffer() in system/core/liblog/logprint.cpp for details.
+            // message format: <priority:1><tag:N>\0<message:N>\0
+            // TODO(b/180516393): improve message parsing that were not transferred
+            // from the cpp above. Also verify this mechanism is ok from selinux perspective.
+
+            if (messageSize < 3) {
+                Log.w(TAG, "Log message is too small, size=" + messageSize);
+                return null;
+            }
+            if (headerSize != dataBytes.position()) {
+                Log.w(TAG, "Invalid header size " + headerSize + ", expected "
+                        + dataBytes.position());
+                return null;
+            }
+            int msgStart = -1;
+            int msgEnd = -1;
+            for (int i = 1; i < messageSize; i++) {
+                if (data[headerSize + i] == 0) {
+                    if (msgStart == -1) {
+                        msgStart = i + 1 + headerSize;
+                    } else {
+                        msgEnd = i + headerSize;
+                        break;
+                    }
+                }
+            }
+            if (msgStart == -1) {
+                Log.w(TAG, "Invalid log message");
+                return null;
+            }
+            if (msgEnd == -1) {
+                msgEnd = Math.max(msgStart, messageSize - 1);
+            }
+            entry.mPriority = data[headerSize];
+            entry.mTag =
+                new String(data, headerSize + 1, msgStart - headerSize - 2,
+                    StandardCharsets.US_ASCII);
+            entry.mMessage =
+                    new String(data, msgStart, msgEnd - msgStart, StandardCharsets.US_ASCII);
+            return entry;
+        }
+    }
+
+    /**
+     * Constructs {@link LogcatReader}.
+     *
+     * @param logEventConsumer a consumer that's called when a filter matches a log.
+     * @param localSocketSupplier a supplier for LocalSocket to connect logd.
+     * @param executor an {@link Executor} to run the LogcatReader instance.
+     */
+    public LogcatReader(
+            ObjIntConsumer<Bundle> logEventConsumer,
+            Supplier<LocalSocket> localSocketSupplier,
+            Executor executor) {
+        this.mLogEventConsumer = logEventConsumer;
+        this.mLocalSocketSupplier = localSocketSupplier;
+        this.mExecutor = executor;
+    }
+
+    /** Runs {@link LogcatReader}. */
+    private void run() {
+        Log.d(TAG, "Running LogcatReader");
+
+        // Under the hood, logcat receives logs from logd, to remove the middleman, LogcatReader
+        // doesn't run logcat, but directly connects to logd socket and parses raw logs.
+        try (LocalSocket socket = mLocalSocketSupplier.get()) {
+            // Connect to /dev/socket/logdr
+            socket.connect(new LocalSocketAddress("logdr", LocalSocketAddress.Namespace.RESERVED));
+            try (OutputStreamWriter writer = new OutputStreamWriter(socket.getOutputStream());
+                  InputStream reader = socket.getInputStream()) {
+                // Ask for streaming log and set tail=1 to get only new logs.
+                // See system/core/liblog/logd_reader.cpp for example on how to interact with logdr.
+                writer.write("stream tail=1");
+                writer.flush();
+                Log.d(TAG, "Sent request to logd and awaiting for logs");
+
+                byte[] data = new byte[LOGGER_ENTRY_MAX_LEN + 1];
+                while (!logFiltersEmpty()) {
+                    int n = reader.read(data, 0, LOGGER_ENTRY_MAX_LEN);
+                    if (n == -1) {
+                        Log.e(TAG, "Disconnected from logd");
+                        return;
+                    }
+                    LogEntry entry = LogEntry.parse(data, n);
+                    if (entry == null) {
+                        continue;
+                    }
+
+                    // Ignore the logs from the telemetry service. This
+                    // makes sure a recursive log storm does not happen - i.e. app produces a log
+                    // which in turn executes a code path that produces another log.
+                    if (entry.mUid == Process.myUid()) {
+                        continue;
+                    }
+                    // Check if it's running before processing the logs, because by the time we get
+                    // here ScriptExecutor might get disconnected.
+                    if (!mRunning.get()) {
+                        Log.d(TAG, "Not running anymore, exiting.");
+                        return;
+                    }
+                    // Keep track of which logListener an event for this entry has been sent,
+                    // so that the same entry isn't sent multiple times for the same logListener
+                    // if its multiple filters match.
+                    HashSet<Integer> sentLogListenerIndices = new HashSet<>();
+                    for (LogFilter filter : mLogFilters) {
+                        if (!sentLogListenerIndices.contains(filter.getLogListenerIndex())
+                                && filter.matches(entry)) {
+                            sentLogListenerIndices.add(filter.getLogListenerIndex());
+                            sendLogEvent(filter.getLogListenerIndex(), entry);
+                        }
+                    }
+                }
+                Log.d(TAG, "Log filters are empty, exiting.");
+            }
+        } catch (IOException e) {
+            Log.e(TAG, "Failed to connect to logd", e);
+        } finally {
+            mRunning.set(false);
+        }
+    }
+
+    /**
+     * Sends the log event to the through the mLogEventConsumer.
+     *
+     * @param logListenerIndex the index of the logListener, whose function will receive the log
+     *     event.
+     * @param entry the LogEntry instance to be bundled up and sent as event.
+     */
+    private void sendLogEvent(int logListenerIndex, LogEntry entry) {
+        Bundle event = new Bundle();
+        event.putLong(LOG_SEC_KEY, entry.mTvSec);
+        event.putLong(LOG_NSEC_KEY, entry.mTvNSec);
+        event.putString(LOG_TAG_KEY, entry.mTag);
+        event.putString(LOG_MESSAGE_KEY, entry.mMessage);
+        mLogEventConsumer.accept(event, logListenerIndex);
+    }
+
+    /**
+     * Subscribes the list of {@link LogFilter} instances.
+     *
+     * @param newLogFilters the list of new log filters to be added to mLogFilters.
+     */
+    synchronized void subscribeLogFilters(List<LogFilter> newLogFilters) {
+        mLogFilters.addAll(newLogFilters);
+    }
+
+    /**
+     * Unsubscribes all {@link LogFilter} associated with the logListenerIndex
+     *
+     * @param logListenerIndex the index of the logListener to unregister.
+     */
+    synchronized void unsubscribeLogListener(int logListenerIndex) {
+        mLogFilters.removeIf(lf -> lf.getLogListenerIndex() == logListenerIndex);
+    }
+
+    /** Starts the run method in a new thread if logcatReader isn't running already. */
+    void startAsyncIfNotStarted() {
+        if (mRunning.compareAndSet(false, true)) {
+            mExecutor.execute(this::run);
+        }
+    }
+
+    /** Gracefully stops {@link LogcatReader}. */
+    synchronized void stop() {
+        mRunning.set(false);
+        mLogFilters.clear();
+    }
+
+    /** Builds a {@link LocalSocket} to read from {@code logdr}. */
+    static LocalSocket buildLocalSocket() {
+        return new LocalSocket(LocalSocket.SOCKET_SEQPACKET);
+    }
+}
diff --git a/service/src/com/android/car/telemetry/proto/Android.bp b/service/src/com/android/car/telemetry/proto/Android.bp
new file mode 100644
index 0000000..7a8088d
--- /dev/null
+++ b/service/src/com/android/car/telemetry/proto/Android.bp
@@ -0,0 +1,22 @@
+// 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.
+
+java_library_static {
+    name: "cartelemetry-protos",
+    host_supported: true,
+    proto: {
+        type: "lite",
+    },
+    srcs: ["*.proto"],
+}
diff --git a/service/src/com/android/car/telemetry/proto/telemetry.proto b/service/src/com/android/car/telemetry/proto/telemetry.proto
new file mode 100644
index 0000000..422c4a2
--- /dev/null
+++ b/service/src/com/android/car/telemetry/proto/telemetry.proto
@@ -0,0 +1,269 @@
+/*
+ * 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.
+ */
+
+syntax = "proto2";
+
+package com.android.car.telemetry;
+
+option java_package = "com.android.car.telemetry";
+option java_outer_classname = "TelemetryProto";
+
+// A Tpk (Telemetry Package) manifest.
+// It contains a script and the rules how the script runs on devices.
+message Manifest {
+  // Required.
+  // Name of the Tpk, used to distinguish TPKs.
+  // Only alphanumeric and _ characters are allowed. Minimum length is 3 chars.
+  optional string name = 1;
+
+  // Required.
+  // A script that is executed in devices. Must contain all the functions
+  // defined in the listeners below.
+  optional string script = 2;
+
+  // Required.
+  // A unique version number of the Tpk, used to distinguish between Tpks of the
+  // same name.
+  optional int32 version = 3;
+
+  // Telemetry service subscribes these listeners to vehicle property change events.
+  // The functions must be present in the script.
+  repeated PropertyChangeListener property_change_listeners = 4;
+
+  // Telemetry service subscribes these listeners to Android log (logcat) events.
+  // The functions must be present in the script.
+  repeated LogListener log_listeners = 5;
+
+  // Telemetry service subscribes these listeners to stats anomalies, such as
+  // Alerts, Alarms, stats out-of-space anomaly and historical data anomaly.
+  // The number of listener is limited to 10 by statsd.
+  // The functions must be present in the script.
+  repeated StatsListener stats_listeners = 6;
+
+  // Dumpsys result listeners can be registered to retrieve dump report for
+  // specified report types.
+  // The functions must be present in the script.
+  repeated DumpsysResultListener dumpsys_result_listeners = 7;
+
+  // This timer executes functions when a specific event happens.
+  // The functions must be present in the script.
+  repeated Timer timers = 8;
+}
+
+// A function that is executed when a vehicle property change events occur.
+message PropertyChangeListener {
+  // Required.
+  // See packages/services/Car/car-lib/src/android/car/VehiclePropertyIds.java
+  optional int32 vehicle_property_id = 1;
+
+  // See
+  // packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java
+  // look for constants SENSOR_RATE_*;
+  optional float read_rate = 2;
+
+  // Required.
+  optional string function_name = 3;
+}
+
+// A function that receives Android log (logcat) events. The log event is passed
+// as a string argument to the function.
+//
+// The LogListener will receive log events if the log event matches any of the
+// "types" or "tags".
+//
+// Please see ../README.md for details.
+//
+// Example:
+//   function processException(log_event_line) ... end
+message LogListener {
+  enum Type {
+    // Default value.
+    TYPE_UNSPECIFIED = 0;
+
+    // Matches logged exceptions.
+    EXCEPTIONS = 1;
+  }
+
+  // Required.
+  // Unique name of the logListener within the Tpk. Used to
+  // subscribe/unsubscribe from log events.
+  optional string name = 1;
+
+  // Required.
+  // Name of the function to be executed when the logs the logListener has a
+  // match with its filters
+  optional string function_name = 2;
+
+  // Message types to listen to in LogCat.
+  // At least one "types" or "tags" must be provided.
+  repeated Type types = 3;
+
+  // Tags to listen to in LogCat.
+  // At least one "types" or "tags" must be provided.
+  repeated string tags = 4;
+}
+
+// A function that receives stats reports.
+//
+// LogProcessor App provides limited statsd support. Please see
+// logprocessor/README.md for details.
+//
+// Script function semantics:
+//    -- Param report_list is a table with 1-1 mapping
+//                         to ConfigMetricsReportList.
+//    -- Param extra_stats is a table { config_uid, config_key, ... } coming
+//    --  from PendingIntent described in StatsManager#setBroadcastSubscriber().
+//    function onStatdEvent(report_list, extra_stats) end
+message StatsListener {
+  // Function to be called when the subscriptions below get triggered.
+  optional string function_name = 1;
+
+  // Serialized StatsdConfig defined in
+  // AOSP/frameworks/base/cmds/statsd/src/statsd_config.proto
+  // NOTE: Config ID and other generated IDs must be unique within this
+  //       statsd_config.
+  optional bytes statsd_config = 2;
+
+  // Subscribe the telemetry service to stats broadcast events: Alarms and Alerts.
+  // Subscriptions must be defined in the statsd_config as
+  // BroadcastSubscriberDetails.
+  // NOTE: It must be unique within the statsd_config.
+  optional int64 subscriber_id = 3;
+}
+
+// A function that receives Dumpsys reports.
+//
+// The DumpsysResultListener will receive dumpsys reports of the specified
+// DumpsysReportType
+message DumpsysResultListener {
+  // Names of the system services that's allowed to run dumpsys on.
+  enum SystemServiceName {
+    UNSPECIFIED = 0;
+
+    // Matches car service com.android.car.CarService.
+    CAR_SERVICE = 1;
+
+    // Matches package manager service
+    // com.android.server.pm.PackageManagerService.
+    PACKAGE = 2;  //
+  }
+
+  // Required.
+  // Unique name of the DumpsysResultListener within the Tpk.
+  // Used to register the specific DumpsysResultListener needed.
+  optional string name = 1;
+
+  // Required.
+  // Name of the function to be executed when a dumpsys report of the specified
+  // system services are retrieved.
+  optional string function_name = 2;
+
+  // Required.
+  // System service to run dumpsys for.
+  optional SystemServiceName system_service_name = 3;
+
+  // Extra arguments for dumpsys
+  optional string arguments = 4;
+}
+
+// Runs a script function after some delay when an event happens.
+message Timer {
+  enum TriggerType {
+    TRIGGER_TYPE_UNSPECIFIED = 0;
+
+    // When the device boots up.
+    //
+    // Note that when device wakes up from a deep sleep (suspend to ram), this
+    // trigger doesn't work.
+    //
+    // It doesn't guarantee immediate activation of the timer right after the
+    // boot, only after the system and the script is ready.
+    // "initial_delay_millis" parameter denotes a delay from boot (not from
+    // the script is initialized and ready).
+    TRIGGER_TYPE_BOOT = 1;
+  }
+
+  // Required.
+  // Name of the script function that will be executed.
+  optional string function_name = 1;
+
+  // Required.
+  // An event trigger that activates this timer.
+  optional TriggerType trigger_type = 2;
+
+  // Required.
+  // Delay after the trigger event before initial executing the function.
+  //
+  // Be careful putting long delays (>30 minutes), because the average
+  // driving duration might be shorter than the delay, and the function
+  // will be executed less frequently than desired amount.
+  optional int64 initial_delay_millis = 3;
+
+  // The max number of times the function will be periodically executed.
+  // When this number of execution is reached, the timer will be stopped.
+  // 0 means the timer is disabled.
+  // -1 means the timer never stops.
+  optional int32 max_count = 4;
+
+  // Time between successive function executions if max_count >= 2.
+  optional int64 period_millis = 5;
+}
+
+// A message that encapsulates different types of error or log metadata as
+// for informational and debugging purposes.
+//
+// When the app crashes or the system restarts, the timers will reset.
+message SecularTrendsLog {
+  // Required.
+  optional int64 timestamp = 1;
+
+  enum LogType {
+    // Default enum.
+    UNSPECIFIED = 0;
+
+    // Used when manifest is misconfigurated, such as missing required fields.
+    MANIFEST_MISCONFIGURATION = 1;
+
+    // Used when a Java service throws an exception.
+    JAVA_EXCEPTION = 2;
+
+    // Used when LogProc service failes to bind to sandbox service.
+    SANDBOX_SERVICE_CONNECTION_FAILURE = 3;
+
+    // Used when an error occurs in the native code.
+    NATIVE_RUNTIME_ERROR = 4;
+
+    // Used when an error occurs while executing the Lua script (such as
+    // errors returned by lua_pcall)
+    LUA_RUNTIME_ERROR = 5;
+
+    // Used when a service that telemetry service depends on crashes or fails.
+    DEPENDENT_SERVICE_FAILURE = 6;
+  }
+
+  // Required.
+  // A type that indicates where the log is generated.
+  optional LogType type = 2;
+
+  // Required.
+  // Human readable message explaining what’s wrong or how to fix it.
+  optional string message = 3;
+
+  // Optional.
+  // If there is an exception, there will be stack trace. However this
+  // information is not always available.
+  optional string stack_trace = 4;
+}
diff --git a/service/src/com/android/car/user/CarUserService.java b/service/src/com/android/car/user/CarUserService.java
index 685a7ce..4e24b56 100644
--- a/service/src/com/android/car/user/CarUserService.java
+++ b/service/src/com/android/car/user/CarUserService.java
@@ -44,6 +44,8 @@
 import android.car.user.UserCreationResult;
 import android.car.user.UserIdentificationAssociationResponse;
 import android.car.user.UserRemovalResult;
+import android.car.user.UserStartResult;
+import android.car.user.UserStopResult;
 import android.car.user.UserSwitchResult;
 import android.car.userlib.HalCallback;
 import android.car.userlib.UserHalHelper;
@@ -110,6 +112,7 @@
 import com.android.internal.util.FunctionalUtils;
 import com.android.internal.util.Preconditions;
 import com.android.internal.util.UserIcons;
+import com.android.server.utils.Slogf;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -964,10 +967,10 @@
             sendUserSwitchResult(receiver, UserSwitchResult.STATUS_NOT_SWITCHABLE);
             return;
         }
-        mHandler.post(()-> switchUserInternal(targetUser, timeoutMs, receiver));
+        mHandler.post(() -> handleSwitchUser(targetUser, timeoutMs, receiver));
     }
 
-    private void switchUserInternal(@NonNull UserInfo targetUser, int timeoutMs,
+    private void handleSwitchUser(@NonNull UserInfo targetUser, int timeoutMs,
             @NonNull AndroidFuture<UserSwitchResult> receiver) {
         int currentUser = ActivityManager.getCurrentUser();
         int targetUserId = targetUser.id;
@@ -1129,10 +1132,10 @@
                         + " can only remove itself");
             }
         }
-        mHandler.post(()-> removeUserInternal(userId, hasCallerRestrictions, receiver));
+        mHandler.post(() -> handleRemoveUser(userId, hasCallerRestrictions, receiver));
     }
 
-    private void removeUserInternal(@UserIdInt int userId, boolean hasCallerRestrictions,
+    private void handleRemoveUser(@UserIdInt int userId, boolean hasCallerRestrictions,
             AndroidFuture<UserRemovalResult> receiver) {
         UserInfo userInfo = mUserManager.getUserInfo(userId);
         if (userInfo == null) {
@@ -1301,12 +1304,12 @@
         EventLog.writeEvent(EventLogTags.CAR_USER_SVC_CREATE_USER_REQ,
                 UserHelperLite.safeName(name), userType, flags, timeoutMs,
                 hasCallerRestrictions ? 1 : 0);
-        mHandler.post(() -> createUserInternal(name, userType, flags, timeoutMs, receiver,
+        mHandler.post(() -> handleCreateUser(name, userType, flags, timeoutMs, receiver,
                 hasCallerRestrictions));
 
     }
 
-    private void createUserInternal(@Nullable String name, @NonNull String userType,
+    private void handleCreateUser(@Nullable String name, @NonNull String userType,
             @UserInfoFlag int flags, int timeoutMs,
             @NonNull AndroidFuture<UserCreationResult> receiver,
             boolean hasCallerRestrictions) {
@@ -1840,6 +1843,58 @@
     }
 
     /**
+     * Starts the specified user in the background.
+     *
+     * @param userId user to start in background
+     * @param receiver to post results
+     */
+    public void startUserInBackground(@UserIdInt int userId,
+            @NonNull AndroidFuture<UserStartResult> receiver) {
+        checkManageOrCreateUsersPermission("startUserInBackground");
+        EventLog.writeEvent(EventLogTags.CAR_USER_SVC_START_USER_IN_BACKGROUND_REQ, userId);
+
+        mHandler.post(() -> handleStartUserInBackground(userId, receiver));
+    }
+
+    private void handleStartUserInBackground(@UserIdInt int userId,
+            @NonNull AndroidFuture<UserStartResult> receiver) {
+        // If the requested user is the current user, do nothing and return success.
+        if (ActivityManager.getCurrentUser() == userId) {
+            sendUserStartResult(
+                    userId, UserStartResult.STATUS_SUCCESSFUL_USER_IS_CURRENT_USER, receiver);
+            return;
+        }
+        // If requested user does not exist, return error.
+        if (mUserManager.getUserInfo(userId) == null) {
+            Slogf.w(TAG, "User %d does not exist", userId);
+            sendUserStartResult(userId, UserStartResult.STATUS_USER_DOES_NOT_EXIST, receiver);
+            return;
+        }
+
+        try {
+            if (!mAm.startUserInBackground(userId)) {
+                Slogf.w(TAG, "Failed to start user %d in background", userId);
+                sendUserStartResult(userId, UserStartResult.STATUS_ANDROID_FAILURE, receiver);
+                return;
+            }
+        } catch (RemoteException e) {
+            Slogf.w(TAG, e, "Failed to start user %d in background", userId);
+        }
+
+        // TODO(b/181331178): We are not updating mBackgroundUsersToRestart or
+        // mBackgroundUsersRestartedHere, which were only used for the garage mode. Consider
+        // renaming them to make it more clear.
+        sendUserStartResult(userId, UserStartResult.STATUS_SUCCESSFUL, receiver);
+    }
+
+    private void sendUserStartResult(@UserIdInt int userId, @UserStartResult.Status int result,
+            @NonNull AndroidFuture<UserStartResult> receiver) {
+        EventLog.writeEvent(
+                EventLogTags.CAR_USER_SVC_START_USER_IN_BACKGROUND_RESP, userId, result);
+        receiver.complete(new UserStartResult(result));
+    }
+
+    /**
      * Starts all background users that were active in system.
      *
      * @return list of background users started successfully.
@@ -1891,36 +1946,67 @@
     }
 
     /**
-     * Stops all background users that were active in system.
+     * Stops the specified background user.
+     *
+     * @param userId user to stop
+     * @param receiver to post results
+     */
+    public void stopUser(@UserIdInt int userId, @NonNull AndroidFuture<UserStopResult> receiver) {
+        mHandler.post(() -> handleStopUser(userId, receiver));
+    }
+
+    private void handleStopUser(
+            @UserIdInt int userId, @NonNull AndroidFuture<UserStopResult> receiver) {
+        @UserStopResult.Status int userStopStatus = stopBackgroundUserInternal(userId);
+        sendUserStopResult(userStopStatus, receiver);
+    }
+
+    private void sendUserStopResult(@UserStopResult.Status int result,
+            @NonNull AndroidFuture<UserStopResult> receiver) {
+        // TODO(b/181331178): Add event log calls.
+        receiver.complete(new UserStopResult(result));
+    }
+
+    private @UserStopResult.Status int stopBackgroundUserInternal(@UserIdInt int userId) {
+        try {
+            int r = mAm.stopUserWithDelayedLocking(userId, true, null);
+            switch(r) {
+                case ActivityManager.USER_OP_SUCCESS:
+                    return UserStopResult.STATUS_SUCCESSFUL;
+                case ActivityManager.USER_OP_ERROR_IS_SYSTEM:
+                    Slogf.w(TAG, "Cannot stop the system user: %d", userId);
+                    return UserStopResult.STATUS_FAILURE_SYSTEM_USER;
+                case ActivityManager.USER_OP_IS_CURRENT:
+                    Slogf.w(TAG, "Cannot stop the current user: %d", userId);
+                    return UserStopResult.STATUS_FAILURE_CURRENT_USER;
+                case ActivityManager.USER_OP_UNKNOWN_USER:
+                    Slogf.w(TAG, "Cannot stop the user that does not exist: %d", userId);
+                    return UserStopResult.STATUS_USER_DOES_NOT_EXIST;
+                default:
+                    Slogf.w(TAG, "stopUser failed, user: %d, err: %d", userId, r);
+            }
+        } catch (RemoteException e) {
+            // ignore the exception
+            Slogf.w(TAG, e, "error while stopping user: %d", userId);
+        }
+        return UserStopResult.STATUS_ANDROID_FAILURE;
+    }
+
+    /**
+     * Stops a background user.
      *
      * @return whether stopping succeeds.
      */
     public boolean stopBackgroundUser(@UserIdInt int userId) {
-        if (userId == UserHandle.USER_SYSTEM) {
-            return false;
-        }
-        if (userId == ActivityManager.getCurrentUser()) {
-            Slog.i(TAG, "stopBackgroundUser, already a FG user:" + userId);
-            return false;
-        }
-        try {
-            int r = mAm.stopUserWithDelayedLocking(userId, true, null);
-            if (r == ActivityManager.USER_OP_SUCCESS) {
-                synchronized (mLockUser) {
-                    Integer user = userId;
-                    mBackgroundUsersRestartedHere.remove(user);
-                }
-            } else if (r == ActivityManager.USER_OP_IS_CURRENT) {
-                return false;
-            } else {
-                Slog.i(TAG, "stopBackgroundUser failed, user:" + userId + " err:" + r);
-                return false;
+        @UserStopResult.Status int userStopStatus = stopBackgroundUserInternal(userId);
+        if (UserStopResult.isSuccess(userStopStatus)) {
+            // Remove the stopped user from the mBackgroundUserRestartedHere list.
+            synchronized (mLockUser) {
+                mBackgroundUsersRestartedHere.remove(Integer.valueOf(userId));
             }
-        } catch (RemoteException e) {
-            // ignore
-            Slog.w(TAG, "error while stopping user", e);
+            return true;
         }
-        return true;
+        return false;
     }
 
     /**
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/CarEvsCameraPreviewApp/Android.bp b/tests/CarEvsCameraPreviewApp/Android.bp
index eb5f704..99f0505 100644
--- a/tests/CarEvsCameraPreviewApp/Android.bp
+++ b/tests/CarEvsCameraPreviewApp/Android.bp
@@ -13,6 +13,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 //
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 android_app {
     name: "CarEvsCameraPreviewApp",
 
diff --git a/tests/CarEvsCameraPreviewApp/src/com/google/android/car/evs/jni/Android.bp b/tests/CarEvsCameraPreviewApp/src/com/google/android/car/evs/jni/Android.bp
index 59cdedc..d06620a 100644
--- a/tests/CarEvsCameraPreviewApp/src/com/google/android/car/evs/jni/Android.bp
+++ b/tests/CarEvsCameraPreviewApp/src/com/google/android/car/evs/jni/Android.bp
@@ -14,6 +14,10 @@
 //
 //
 //#################################
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 cc_library_shared {
     name: "libcarevsglrenderer_jni",
 
diff --git a/tests/CarLibTests/src/android/car/CarTelemetryManagerTest.java b/tests/CarLibTests/src/android/car/CarTelemetryManagerTest.java
new file mode 100644
index 0000000..90b5f7a
--- /dev/null
+++ b/tests/CarLibTests/src/android/car/CarTelemetryManagerTest.java
@@ -0,0 +1,80 @@
+/*
+ * 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;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.Application;
+import android.car.telemetry.CarTelemetryManager;
+import android.car.testapi.CarTelemetryController;
+import android.car.testapi.FakeCar;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.internal.DoNotInstrument;
+
+import java.util.concurrent.Executor;
+
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
+public class CarTelemetryManagerTest {
+    @Rule
+    public MockitoRule rule = MockitoJUnit.rule();
+
+    private static final Executor DIRECT_EXECUTOR = Runnable::run;
+
+    private CarTelemetryController mCarTelemetryController;
+    private CarTelemetryManager mCarTelemetryManager;
+    @Mock
+    private CarTelemetryManager.CarTelemetryResultsListener mListener;
+
+
+
+    @Before
+    public void setUp() {
+        Application context = ApplicationProvider.getApplicationContext();
+        FakeCar fakeCar = FakeCar.createFakeCar(context);
+        Car carApi = fakeCar.getCar();
+
+        mCarTelemetryManager =
+                (CarTelemetryManager) carApi.getCarManager(Car.CAR_TELEMETRY_SERVICE);
+        mCarTelemetryController = fakeCar.getCarTelemetryController();
+    }
+
+    @Test
+    public void setListener_shouldSucceed() {
+        mCarTelemetryManager.setListener(DIRECT_EXECUTOR, mListener);
+
+        assertThat(mCarTelemetryController.isListenerSet()).isTrue();
+    }
+
+    @Test
+    public void clearListener_shouldSucceed() {
+        mCarTelemetryManager.setListener(DIRECT_EXECUTOR, mListener);
+        mCarTelemetryManager.clearListener();
+
+        assertThat(mCarTelemetryController.isListenerSet()).isFalse();
+    }
+}
diff --git a/tests/CarSecurityPermissionTest/src/com/android/car/admin/CarDevicePolicyManagerPermissionTest.java b/tests/CarSecurityPermissionTest/src/com/android/car/admin/CarDevicePolicyManagerPermissionTest.java
index 51c1ea3..f1d4b52 100644
--- a/tests/CarSecurityPermissionTest/src/com/android/car/admin/CarDevicePolicyManagerPermissionTest.java
+++ b/tests/CarSecurityPermissionTest/src/com/android/car/admin/CarDevicePolicyManagerPermissionTest.java
@@ -74,4 +74,11 @@
         assertThat(e.getMessage()).contains(CREATE_USERS);
         assertThat(e.getMessage()).contains(MANAGE_USERS);
     }
+    @Test
+    public void testStartUserInBackgrounPermission() throws Exception {
+        Exception e = expectThrows(SecurityException.class,
+                () -> mManager.startUserInBackground(UserHandle.of(100)));
+        assertThat(e.getMessage()).contains(CREATE_USERS);
+        assertThat(e.getMessage()).contains(MANAGE_USERS);
+    }
 }
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/NetworkPreferenceApp/res/layout/manager.xml b/tests/NetworkPreferenceApp/res/layout/manager.xml
index 9b545ee..fe06102 100644
--- a/tests/NetworkPreferenceApp/res/layout/manager.xml
+++ b/tests/NetworkPreferenceApp/res/layout/manager.xml
@@ -212,6 +212,26 @@
                 android:layout_height="wrap_content"
                 android:layout_weight="1"
                 android:text="@string/button_apply_wifi_configuration"/>
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:weightSum="2">
+                <Button
+                    style="@style/Button"
+                    android:id="@+id/connectToOemPaidWifiButton"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_weight="1"
+                    android:text="@string/button_connect_to_oem_paid_wifi"/>
+                <Button
+                    style="@style/Button"
+                    android:id="@+id/connectToOemPrivateWifiButton"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_weight="1"
+                    android:text="@string/button_connect_to_oem_private_wifi"/>
+            </LinearLayout>
         </LinearLayout>
     </LinearLayout>
 </LinearLayout>
diff --git a/tests/NetworkPreferenceApp/res/values/strings.xml b/tests/NetworkPreferenceApp/res/values/strings.xml
index 38deeae..3b333e7 100644
--- a/tests/NetworkPreferenceApp/res/values/strings.xml
+++ b/tests/NetworkPreferenceApp/res/values/strings.xml
@@ -35,9 +35,11 @@
   <string name="label_metric_total_oem_managed" translatable="false">Total OEM Managed</string>
 
   <!-- Wifi PANS SSID control labels -->
-  <string name="oem_paid_wifi_ssids_text" translatable="false">Wifi SSIDs with OEM_PAID capability (comma separated)</string>
-  <string name="oem_private_wifi_ssids_text" translatable="false">Wifi SSIDs with OEM_PRIVATE capability (comma separated)</string>
-  <string name="button_apply_wifi_configuration" translatable="false">Apply WiFi changes</string>
+  <string name="oem_paid_wifi_ssids_text" translatable="false">Wifi SSIDs with OEM_PAID capability (ssid1[:password],ssid2[:password2],...))</string>
+  <string name="oem_private_wifi_ssids_text" translatable="false">Wifi SSIDs with OEM_PRIVATE capability (ssid1[:password],ssid2[:password2],...))</string>
+  <string name="button_apply_wifi_configuration" translatable="false">Apply WiFi suggestions</string>
+  <string name="button_connect_to_oem_paid_wifi" translatable="false">Connect to OEM_PAID wifi</string>
+  <string name="button_connect_to_oem_private_wifi" translatable="false">Connect to OEM_PRIVATE wifi</string>
 
   <string name="no" translatable="false">No</string>
   <string name="on" translatable="false">On</string>
diff --git a/tests/NetworkPreferenceApp/src/com/google/android/car/networking/preferenceupdater/fragments/ManagerFragment.java b/tests/NetworkPreferenceApp/src/com/google/android/car/networking/preferenceupdater/fragments/ManagerFragment.java
index 3ffb851..dc396c0 100644
--- a/tests/NetworkPreferenceApp/src/com/google/android/car/networking/preferenceupdater/fragments/ManagerFragment.java
+++ b/tests/NetworkPreferenceApp/src/com/google/android/car/networking/preferenceupdater/fragments/ManagerFragment.java
@@ -15,13 +15,18 @@
  */
 package com.google.android.car.networking.preferenceupdater.fragments;
 
+import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PAID;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID;
 import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK;
 import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY;
 import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY;
 
 import android.content.Context;
+import android.net.ConnectivityManager;
 import android.net.NetworkIdentity;
+import android.net.NetworkRequest;
 import android.net.NetworkTemplate;
 import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiManager;
@@ -63,6 +68,7 @@
     private OemNetworkPreferencesAdapter mOemNetworkPreferencesAdapter;
     private CarDriverDistractionManagerAdapter mCarDriverDistractionManagerAdapter;
     private WifiManager mWifiManager;
+    private ConnectivityManager mConnectivityManager;
 
     // Metric Display components
     private MetricDisplay mMetricDisplay;
@@ -94,6 +100,8 @@
     private Button mApplyConfigurationBtn;
     private Button mResetNetworkPreferencesBtn;
     private Button mApplyWifiCapabilitiesBtn;
+    private Button mConnectToOemPaidWifiBtn;
+    private Button mConnectToOemPrivateWifiBtn;
 
     // Wifi SSIDs
     private EditText mOEMPaidWifiSSIDsEditText;
@@ -122,6 +130,7 @@
         mMetricDisplay.startWatching();
 
         mWifiManager = context.getSystemService(WifiManager.class);
+        mConnectivityManager = context.getSystemService(ConnectivityManager.class);
 
         return v;
     }
@@ -146,6 +155,8 @@
         mApplyConfigurationBtn = v.findViewById(R.id.applyConfigurationBtn);
         mResetNetworkPreferencesBtn = v.findViewById(R.id.resetNetworkPreferencesBtn);
         mApplyWifiCapabilitiesBtn = v.findViewById(R.id.applyWifiCapabilitiesButton);
+        mConnectToOemPaidWifiBtn = v.findViewById(R.id.connectToOemPaidWifiButton);
+        mConnectToOemPrivateWifiBtn = v.findViewById(R.id.connectToOemPrivateWifiButton);
         // Since our Metric Display is going to be alive, we want to pass our TextView components
         // into MetricDisplay instance to simplify refresh logic.
         mOemPaidRxBytesTextView = v.findViewById(R.id.oemPaidRxBytesTextView);
@@ -184,6 +195,29 @@
                 (buttonView, isChecked) ->
                         mPersonalStorage.saveReapplyPansOnBootCompleteState(true));
         mResetNetworkPreferencesBtn.setOnClickListener(view -> resetNetworkPreferences());
+        mConnectToOemPaidWifiBtn.setOnClickListener(view -> onConnectWifiBtnClick(true));
+        mConnectToOemPrivateWifiBtn.setOnClickListener(view -> onConnectWifiBtnClick(false));
+    }
+
+    private void onConnectWifiBtnClick(boolean isOemPaid) {
+        try {
+            mConnectivityManager.requestNetwork(
+                    new NetworkRequest.Builder()
+                            .addTransportType(TRANSPORT_WIFI)
+                            .addCapability(
+                                    isOemPaid
+                                            ? NET_CAPABILITY_OEM_PAID
+                                            : NET_CAPABILITY_OEM_PRIVATE)
+                            .build(),
+                    new ConnectivityManager.NetworkCallback());
+        } catch (Exception ex) {
+            Toast.makeText(getActivity(), ex.toString(), Toast.LENGTH_SHORT).show();
+            String msg =
+                    String.format(
+                            "Attempt to connect to wifi with %s cabaility failed!",
+                            isOemPaid ? "OEM_PAID" : "OEM_PRIVATE");
+            Log.e(TAG, msg, ex);
+        }
     }
 
     private void resetNetworkPreferences() {
@@ -217,28 +251,34 @@
         mCurrentPANSStatusTextView.setText(status ? "Yes" : "No");
     }
 
+    private WifiNetworkSuggestion buildWifiSuggestion(String ssid, boolean isOemPaid) {
+        WifiNetworkSuggestion.Builder builder = new WifiNetworkSuggestion.Builder();
+        String[] elements = ssid.split(":");
+        builder.setSsid(WifiInfo.sanitizeSsid(elements[0]));
+        if (elements.length > 1) {
+            builder.setWpa2Passphrase(elements[1]);
+        }
+        if (isOemPaid) {
+            builder.setOemPaid(true);
+        } else {
+            builder.setOemPrivate(true);
+        }
+        return builder.build();
+    }
+
     private void onApplyWifiCapabilitiesBtnClick() {
         Log.d(TAG, "Applying WiFi settings");
         Set<String> ssidsWithOemPaid = Utils.toSet(mOEMPaidWifiSSIDsEditText.getText().toString());
         Set<String> ssidsWithOemPrivate =
                 Utils.toSet(mOEMPrivateWifiSSIDsEditText.getText().toString());
-
         try {
             ArrayList<WifiNetworkSuggestion> list = new ArrayList<>();
             for (String ssid : ssidsWithOemPaid) {
-                list.add(
-                        new WifiNetworkSuggestion.Builder()
-                                .setSsid(WifiInfo.sanitizeSsid(ssid))
-                                .setOemPaid(true)
-                                .build());
+                list.add(buildWifiSuggestion(ssid, true));
             }
 
             for (String ssid : ssidsWithOemPrivate) {
-                list.add(
-                        new WifiNetworkSuggestion.Builder()
-                                .setSsid(WifiInfo.sanitizeSsid(ssid))
-                                .setOemPrivate(true)
-                                .build());
+                list.add(buildWifiSuggestion(ssid, false));
             }
 
             mWifiManager.removeNetworkSuggestions(new ArrayList<>());
diff --git a/tests/android_car_api_test/src/android/car/apitest/CarDevicePolicyManagerTest.java b/tests/android_car_api_test/src/android/car/apitest/CarDevicePolicyManagerTest.java
index b3973aa..8ce4c27 100644
--- a/tests/android_car_api_test/src/android/car/apitest/CarDevicePolicyManagerTest.java
+++ b/tests/android_car_api_test/src/android/car/apitest/CarDevicePolicyManagerTest.java
@@ -27,6 +27,7 @@
 import android.car.admin.CarDevicePolicyManager;
 import android.car.admin.CreateUserResult;
 import android.car.admin.RemoveUserResult;
+import android.car.admin.StartUserInBackgroundResult;
 import android.content.Context;
 import android.content.pm.UserInfo;
 import android.os.PowerManager;
@@ -63,11 +64,10 @@
 
         UserInfo user = createUser();
         Log.d(TAG, "removing user " + user.toFullString());
-
         RemoveUserResult result = mCarDpm.removeUser(user.getUserHandle());
         Log.d(TAG, "result: " + result);
 
-        assertWithMessage("Failed to remove user%s: %s", user.toFullString(), result)
+        assertWithMessage("Result of removeUser %s: %s", user.toFullString(), result)
                 .that(result.isSuccess()).isTrue();
     }
 
@@ -122,7 +122,6 @@
         String name = "CarDevicePolicyManagerTest.testCreateUser";
         int type = CarDevicePolicyManager.USER_TYPE_REGULAR;
         Log.d(TAG, "creating new user with name " + name + " and type " + type);
-
         CreateUserResult result = mCarDpm.createUser(name, type);
         Log.d(TAG, "result: " + result);
         UserHandle user = result.getUserHandle();
@@ -138,6 +137,25 @@
     }
 
     @Test
+    public void testStartUserInBackground() throws Exception {
+        assertInitialUserIsAdmin();
+
+        UserInfo user = createUser();
+        Log.d(TAG, "starting user in background " + user.toFullString());
+        StartUserInBackgroundResult result = mCarDpm.startUserInBackground(user.getUserHandle());
+        Log.d(TAG, "result: " + result);
+
+        try {
+            assertWithMessage("Result of startUserInBackground %s: %s", user.toFullString(), result)
+                    .that(result.isSuccess()).isTrue();
+        } finally {
+            // Clean up the created user.
+            removeUser(user.id);
+            waitForUserRemoval(user.id);
+        }
+    }
+
+    @Test
     public void testLockNow_safe() throws Exception {
         lockNowTest(/* safe= */ true);
     }
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/admin/CarDevicePolicyManagerUnitTest.java b/tests/carservice_unit_test/src/android/car/admin/CarDevicePolicyManagerUnitTest.java
index 19ffc0b..230dda9 100644
--- a/tests/carservice_unit_test/src/android/car/admin/CarDevicePolicyManagerUnitTest.java
+++ b/tests/carservice_unit_test/src/android/car/admin/CarDevicePolicyManagerUnitTest.java
@@ -32,6 +32,7 @@
 import android.car.test.util.UserTestingHelper.UserInfoBuilder;
 import android.car.user.UserCreationResult;
 import android.car.user.UserRemovalResult;
+import android.car.user.UserStartResult;
 import android.content.pm.UserInfo;
 import android.os.RemoteException;
 import android.os.UserHandle;
@@ -125,6 +126,34 @@
         assertThrows(SecurityException.class, () -> mMgr.createUser("TheDude", 100));
     }
 
+    @Test
+    public void testStartUserInBackground_success() throws Exception {
+        mockStartUserInBackground(100, UserStartResult.STATUS_SUCCESSFUL);
+
+        StartUserInBackgroundResult result = mMgr.startUserInBackground(UserHandle.of(100));
+
+        assertThat(result.isSuccess()).isTrue();
+        assertThat(result.getStatus()).isEqualTo(StartUserInBackgroundResult.STATUS_SUCCESS);
+    }
+
+    @Test
+    public void testStartUserInBackground_remoteException() throws Exception {
+        doThrow(new RemoteException("D'OH!"))
+                .when(mService).startUserInBackground(eq(100), notNull());
+        mockHandleRemoteExceptionFromCarServiceWithDefaultValue(mCar);
+
+        StartUserInBackgroundResult result = mMgr.startUserInBackground(UserHandle.of(100));
+
+        assertThat(result.isSuccess()).isFalse();
+        assertThat(result.getStatus())
+                .isEqualTo(StartUserInBackgroundResult.STATUS_FAILURE_GENERIC);
+    }
+
+    @Test
+    public void testStartUserInBackground_nullUser() {
+        assertThrows(NullPointerException.class, () -> mMgr.startUserInBackground(null));
+    }
+
     private void mockRemoveUser(@UserIdInt int userId, int status) throws Exception {
         doAnswer((invocation) -> {
             @SuppressWarnings("unchecked")
@@ -144,4 +173,14 @@
             return null;
         }).when(mService).createUser(eq(name), eq(user.id), notNull());
     }
+
+    private void mockStartUserInBackground(@UserIdInt int userId, int status) throws Exception {
+        doAnswer((invocation) -> {
+            @SuppressWarnings("unchecked")
+            AndroidFuture<UserStartResult> future =
+                    (AndroidFuture<UserStartResult>) invocation.getArguments()[1];
+            future.complete(new UserStartResult(status));
+            return null;
+        }).when(mService).startUserInBackground(eq(userId), notNull());
+    }
 }
diff --git a/tests/carservice_unit_test/src/android/car/admin/StartUserInBackgroundResultTest.java b/tests/carservice_unit_test/src/android/car/admin/StartUserInBackgroundResultTest.java
new file mode 100644
index 0000000..595687b
--- /dev/null
+++ b/tests/carservice_unit_test/src/android/car/admin/StartUserInBackgroundResultTest.java
@@ -0,0 +1,65 @@
+/*
+ * 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.admin;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.car.user.UserStartResult;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+@RunWith(Parameterized.class)
+public final class StartUserInBackgroundResultTest {
+    private final int mUserStartStatus;
+    private final int mStartUserStatus;
+    private final boolean mIsSuccess;
+
+    public StartUserInBackgroundResultTest(
+            int userStartStatus, int startUserStatus, boolean isSuccess) {
+        mUserStartStatus = userStartStatus;
+        mStartUserStatus = startUserStatus;
+        mIsSuccess = isSuccess;
+    }
+
+    @Parameterized.Parameters
+    public static Collection provideParams() {
+        return Arrays.asList(
+                new Object[][] {
+                        {UserStartResult.STATUS_SUCCESSFUL,
+                                StartUserInBackgroundResult.STATUS_SUCCESS, true},
+                        {UserStartResult.STATUS_SUCCESSFUL_USER_IS_CURRENT_USER,
+                                StartUserInBackgroundResult.STATUS_SUCCESS_CURRENT_USER, true},
+                        {UserStartResult.STATUS_ANDROID_FAILURE,
+                                StartUserInBackgroundResult.STATUS_FAILURE_GENERIC, false},
+                        {UserStartResult.STATUS_USER_DOES_NOT_EXIST,
+                                StartUserInBackgroundResult.STATUS_FAILURE_USER_DOES_NOT_EXIST,
+                                false}
+                });
+    }
+
+    @Test
+    public void testStatus() {
+        StartUserInBackgroundResult result = new StartUserInBackgroundResult(mUserStartStatus);
+
+        assertThat(result.getStatus()).isEqualTo(mStartUserStatus);
+        assertThat(result.isSuccess()).isEqualTo(mIsSuccess);
+    }
+}
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/admin/CarDevicePolicyServiceTest.java b/tests/carservice_unit_test/src/com/android/car/admin/CarDevicePolicyServiceTest.java
index da1eb72..ed37a9b 100644
--- a/tests/carservice_unit_test/src/com/android/car/admin/CarDevicePolicyServiceTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/admin/CarDevicePolicyServiceTest.java
@@ -26,6 +26,8 @@
 import android.car.test.mocks.AbstractExtendedMockitoTestCase;
 import android.car.user.UserCreationResult;
 import android.car.user.UserRemovalResult;
+import android.car.user.UserStartResult;
+import android.car.user.UserStopResult;
 import android.content.pm.UserInfo;
 import android.content.pm.UserInfo.UserInfoFlag;
 import android.os.UserManager;
@@ -44,11 +46,13 @@
 
     private CarDevicePolicyService mService;
 
-    private AndroidFuture<UserRemovalResult> mUserRemovalResult =
-            new AndroidFuture<UserRemovalResult>();
+    private AndroidFuture<UserRemovalResult> mUserRemovalResult = new AndroidFuture<>();
 
-    private AndroidFuture<UserCreationResult> mUserCreationResult =
-            new AndroidFuture<UserCreationResult>();
+    private AndroidFuture<UserCreationResult> mUserCreationResult = new AndroidFuture<>();
+
+    private AndroidFuture<UserStartResult> mUserStartResult = new AndroidFuture<>();
+
+    private AndroidFuture<UserStopResult> mUserStopResult = new AndroidFuture<>();
 
     @Before
     public void setFixtures() {
@@ -104,4 +108,18 @@
         verify(mCarUserService).createUser(eq("name"), eq(userType), eq(flags),
                 /* timeoutMs= */ anyInt(), eq(mUserCreationResult));
     }
+
+    @Test
+    public void testStartUserInBackground() {
+        mService.startUserInBackground(42, mUserStartResult);
+
+        verify(mCarUserService).startUserInBackground(42, mUserStartResult);
+    }
+
+    @Test
+    public void testStopUser() {
+        mService.stopUser(42, mUserStopResult);
+
+        verify(mCarUserService).stopUser(42, mUserStopResult);
+    }
 }
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/power/CarPowerManagementServiceUnitTest.java b/tests/carservice_unit_test/src/com/android/car/power/CarPowerManagementServiceUnitTest.java
index b7151eb..80d9dd7 100644
--- a/tests/carservice_unit_test/src/com/android/car/power/CarPowerManagementServiceUnitTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/power/CarPowerManagementServiceUnitTest.java
@@ -356,6 +356,41 @@
         mPowerSignalListener.waitFor(PowerHalService.SET_DEEP_SLEEP_EXIT, WAIT_TIMEOUT_MS);
     }
 
+    @Test
+    public void testShutdownPostponeAfterSuspend() throws Exception {
+        // Start in the ON state
+        mPowerHal.setCurrentPowerState(new PowerState(VehicleApPowerStateReq.ON, 0));
+        mPowerSignalListener.waitFor(PowerHalService.SET_ON, WAIT_TIMEOUT_MS);
+        mPowerHal.setCurrentPowerState(new PowerState(VehicleApPowerStateReq.SHUTDOWN_PREPARE,
+                VehicleApPowerStateShutdownParam.CAN_SLEEP));
+        mDisplayInterface.waitForDisplayOff(WAIT_TIMEOUT_MS);
+        assertStateReceivedForShutdownOrSleepWithPostpone(PowerHalService.SET_DEEP_SLEEP_ENTRY);
+        mPowerSignalListener.waitFor(PowerHalService.SET_DEEP_SLEEP_ENTRY, WAIT_TIMEOUT_MS);
+
+        // Send the finished signal
+        mPowerHal.setCurrentPowerState(new PowerState(VehicleApPowerStateReq.FINISHED, 0));
+        mSystemStateInterface.setWakeupCausedByTimer(true);
+        mSystemStateInterface.waitForSleepEntryAndWakeup(WAIT_TIMEOUT_MS);
+        assertStateReceived(PowerHalService.SET_DEEP_SLEEP_EXIT, 0);
+        mPowerSignalListener.waitFor(PowerHalService.SET_DEEP_SLEEP_EXIT, WAIT_TIMEOUT_MS);
+        mService.scheduleNextWakeupTime(WAKE_UP_DELAY);
+        // Second processing after wakeup
+        assertThat(mDisplayInterface.isDisplayEnabled()).isFalse();
+
+        mService.setStateForWakeUp();
+
+        mPowerHal.setCurrentPowerState(new PowerState(VehicleApPowerStateReq.ON, 0));
+        mDisplayInterface.waitForDisplayOn(WAIT_TIMEOUT_MS);
+        // Should wait until Handler has finished ON processing
+        CarServiceUtils.runOnLooperSync(mService.getHandlerThread().getLooper(), () -> { });
+
+        mPowerHal.setCurrentPowerState(new PowerState(VehicleApPowerStateReq.SHUTDOWN_PREPARE,
+                VehicleApPowerStateShutdownParam.CAN_SLEEP));
+
+        // Should suspend within timeout
+        mPowerSignalListener.waitFor(PowerHalService.SET_DEEP_SLEEP_ENTRY, WAIT_TIMEOUT_MS);
+    }
+
     /**
      * This test case tests the same scenario as {@link #testUserSwitchingOnResume_differentUser()},
      * but indirectly triggering {@code switchUserOnResumeIfNecessary()} through HAL events.
diff --git a/tests/carservice_unit_test/src/com/android/car/user/CarUserServiceTest.java b/tests/carservice_unit_test/src/com/android/car/user/CarUserServiceTest.java
index adafb57..fcdc6ef 100644
--- a/tests/carservice_unit_test/src/com/android/car/user/CarUserServiceTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/user/CarUserServiceTest.java
@@ -72,6 +72,8 @@
 import android.car.user.UserCreationResult;
 import android.car.user.UserIdentificationAssociationResponse;
 import android.car.user.UserRemovalResult;
+import android.car.user.UserStartResult;
+import android.car.user.UserStopResult;
 import android.car.user.UserSwitchResult;
 import android.car.userlib.HalCallback;
 import android.car.userlib.HalCallback.HalCallbackStatus;
@@ -528,9 +530,9 @@
         UserInfo user2Info = new UserInfo(user2, "user2", NO_USER_INFO_FLAGS);
         UserInfo user3Info = new UserInfo(user3, "user3", NO_USER_INFO_FLAGS);
 
-        doReturn(user1Info).when(mMockedUserManager).getUserInfo(user1);
-        doReturn(user2Info).when(mMockedUserManager).getUserInfo(user2);
-        doReturn(user3Info).when(mMockedUserManager).getUserInfo(user3);
+        when(mMockedUserManager.getUserInfo(user1)).thenReturn(user1Info);
+        when(mMockedUserManager.getUserInfo(user2)).thenReturn(user2Info);
+        when(mMockedUserManager.getUserInfo(user3)).thenReturn(user3Info);
 
         mockGetCurrentUser(user1);
         sendUserUnlockedEvent(UserHandle.USER_SYSTEM);
@@ -540,40 +542,108 @@
         sendUserUnlockedEvent(user1);
         mockGetCurrentUser(user3);
         sendUserUnlockedEvent(user3);
+        mockStopUserWithDelayedLocking(user3, ActivityManager.USER_OP_IS_CURRENT);
 
-        assertEquals(new Integer[]{user3, user2},
-                mCarUserService.getBackgroundUsersToRestart().toArray());
+        assertThat(mCarUserService.getBackgroundUsersToRestart()).containsExactly(user2, user3);
 
-        doReturn(true).when(mMockedIActivityManager).startUserInBackground(user2);
-        doReturn(true).when(mMockedIActivityManager).unlockUser(user2,
-                null, null, null);
-        assertEquals(new Integer[]{user2},
-                mCarUserService.startAllBackgroundUsers().toArray());
+        when(mMockedIActivityManager.startUserInBackground(user2)).thenReturn(true);
+        when(mMockedIActivityManager.unlockUser(user2, null, null, null)).thenReturn(true);
+        assertThat(mCarUserService.startAllBackgroundUsers()).containsExactly(user2);
         sendUserUnlockedEvent(user2);
-        assertEquals(new Integer[]{user3, user2},
-                mCarUserService.getBackgroundUsersToRestart().toArray());
+        assertThat(mCarUserService.getBackgroundUsersToRestart()).containsExactly(user2, user3);
 
-        doReturn(ActivityManager.USER_OP_SUCCESS).when(mMockedIActivityManager).stopUser(user2,
-                true, null);
+        when(mMockedIActivityManager.stopUser(user2, true, null))
+                .thenReturn(ActivityManager.USER_OP_SUCCESS);
         // should not stop the current fg user
-        assertFalse(mCarUserService.stopBackgroundUser(user3));
-        assertTrue(mCarUserService.stopBackgroundUser(user2));
-        assertEquals(new Integer[]{user3, user2},
-                mCarUserService.getBackgroundUsersToRestart().toArray());
-        assertEquals(new Integer[]{user3, user2},
-                mCarUserService.getBackgroundUsersToRestart().toArray());
+        assertThat(mCarUserService.stopBackgroundUser(user3)).isFalse();
+        assertThat(mCarUserService.stopBackgroundUser(user2)).isTrue();
+        assertThat(mCarUserService.getBackgroundUsersToRestart()).containsExactly(user2, user3);
+        assertThat(mCarUserService.getBackgroundUsersToRestart()).containsExactly(user2, user3);
     }
 
     @Test
-    public void testStopBackgroundUserForSystemUser() {
-        assertFalse(mCarUserService.stopBackgroundUser(UserHandle.USER_SYSTEM));
+    public void testStopUser_success() throws Exception {
+        int userId = 101;
+        UserInfo userInfo = new UserInfo(userId, "user101", NO_USER_INFO_FLAGS);
+        mockStopUserWithDelayedLocking(userId, ActivityManager.USER_OP_SUCCESS);
+
+        AndroidFuture<UserStopResult> userStopResult = new AndroidFuture<>();
+        mCarUserService.stopUser(userId, userStopResult);
+
+        assertThat(getResult(userStopResult).getStatus())
+                .isEqualTo(UserStopResult.STATUS_SUCCESSFUL);
+        assertThat(getResult(userStopResult).isSuccess()).isTrue();
     }
 
     @Test
-    public void testStopBackgroundUserForFgUser() throws RemoteException {
-        int user1 = 101;
-        mockGetCurrentUser(user1);
-        assertFalse(mCarUserService.stopBackgroundUser(UserHandle.USER_SYSTEM));
+    public void testStopUser_fail() throws Exception {
+        int userId = 101;
+        UserInfo userInfo = new UserInfo(userId, "user101", NO_USER_INFO_FLAGS);
+        mockStopUserWithDelayedLockingThrowsRemoteException(userId);
+
+        AndroidFuture<UserStopResult> userStopResult = new AndroidFuture<>();
+        mCarUserService.stopUser(userId, userStopResult);
+
+        assertThat(getResult(userStopResult).getStatus())
+                .isEqualTo(UserStopResult.STATUS_ANDROID_FAILURE);
+        assertThat(getResult(userStopResult).isSuccess()).isFalse();
+    }
+
+    @Test
+    public void testStopUser_userDoesNotExist() throws Exception {
+        int userId = 101;
+        UserInfo userInfo = new UserInfo(userId, "user101", NO_USER_INFO_FLAGS);
+        mockStopUserWithDelayedLocking(userId, ActivityManager.USER_OP_UNKNOWN_USER);
+
+        AndroidFuture<UserStopResult> userStopResult = new AndroidFuture<>();
+        mCarUserService.stopUser(userId, userStopResult);
+
+        assertThat(getResult(userStopResult).getStatus())
+                .isEqualTo(UserStopResult.STATUS_USER_DOES_NOT_EXIST);
+        assertThat(getResult(userStopResult).isSuccess()).isFalse();
+    }
+
+    @Test
+    public void testStopUser_systemUser() throws Exception {
+        mockStopUserWithDelayedLocking(
+                UserHandle.USER_SYSTEM, ActivityManager.USER_OP_ERROR_IS_SYSTEM);
+
+        AndroidFuture<UserStopResult> userStopResult = new AndroidFuture<>();
+        mCarUserService.stopUser(UserHandle.USER_SYSTEM, userStopResult);
+
+        assertThat(getResult(userStopResult).getStatus())
+                .isEqualTo(UserStopResult.STATUS_FAILURE_SYSTEM_USER);
+        assertThat(getResult(userStopResult).isSuccess()).isFalse();
+    }
+
+    @Test
+    public void testStopUser_currentUser() throws Exception {
+        int userId = 101;
+        UserInfo userInfo = new UserInfo(userId, "user101", NO_USER_INFO_FLAGS);
+        mockStopUserWithDelayedLocking(userId, ActivityManager.USER_OP_IS_CURRENT);
+
+        AndroidFuture<UserStopResult> userStopResult = new AndroidFuture<>();
+        mCarUserService.stopUser(userId, userStopResult);
+
+        assertThat(getResult(userStopResult).getStatus())
+                .isEqualTo(UserStopResult.STATUS_FAILURE_CURRENT_USER);
+        assertThat(getResult(userStopResult).isSuccess()).isFalse();
+    }
+
+    @Test
+    public void testStopBackgroundUserForSystemUser() throws Exception {
+        mockStopUserWithDelayedLocking(
+                UserHandle.USER_SYSTEM, ActivityManager.USER_OP_ERROR_IS_SYSTEM);
+
+        assertThat(mCarUserService.stopBackgroundUser(UserHandle.USER_SYSTEM)).isFalse();
+    }
+
+    @Test
+    public void testStopBackgroundUserForFgUser() throws Exception {
+        int userId = 101;
+        mockStopUserWithDelayedLocking(userId, ActivityManager.USER_OP_IS_CURRENT);
+
+        assertThat(mCarUserService.stopBackgroundUser(userId)).isFalse();
     }
 
     @Test
@@ -1775,6 +1845,80 @@
     }
 
     @Test
+    public void testStartUserInBackground_success() throws Exception {
+        int userId = 101;
+        UserInfo userInfo = new UserInfo(userId, "user1", NO_USER_INFO_FLAGS);
+        mockCurrentUser(mRegularUser);
+        mockUmGetUserInfo(mMockedUserManager, userInfo);
+        mockAmStartUserInBackground(userId, true);
+
+        AndroidFuture<UserStartResult> userStartResult = new AndroidFuture<>();
+        mCarUserService.startUserInBackground(userId, userStartResult);
+
+        assertThat(getResult(userStartResult).getStatus())
+                .isEqualTo(UserStartResult.STATUS_SUCCESSFUL);
+        assertThat(getResult(userStartResult).isSuccess()).isTrue();
+    }
+
+    @Test
+    public void testStartUserInBackground_permissionDenied() throws Exception {
+        int userId = 101;
+        mockManageUsersPermission(android.Manifest.permission.MANAGE_USERS, false);
+        mockManageUsersPermission(android.Manifest.permission.CREATE_USERS, false);
+
+        AndroidFuture<UserStartResult> userStartResult = new AndroidFuture<>();
+        assertThrows(SecurityException.class,
+                () -> mCarUserService.startUserInBackground(userId, userStartResult));
+    }
+
+    @Test
+    public void testStartUserInBackground_fail() throws Exception {
+        int userId = 101;
+        UserInfo userInfo = new UserInfo(userId, "user1", NO_USER_INFO_FLAGS);
+        mockCurrentUser(mRegularUser);
+        mockUmGetUserInfo(mMockedUserManager, userInfo);
+        mockAmStartUserInBackground(userId, false);
+
+        AndroidFuture<UserStartResult> userStartResult = new AndroidFuture<>();
+        mCarUserService.startUserInBackground(userId, userStartResult);
+
+        assertThat(getResult(userStartResult).getStatus())
+                .isEqualTo(UserStartResult.STATUS_ANDROID_FAILURE);
+        assertThat(getResult(userStartResult).isSuccess()).isFalse();
+    }
+
+    @Test
+    public void testStartUserInBackground_currentUser() throws Exception {
+        int userId = 101;
+        UserInfo userInfo = new UserInfo(userId, "user1", NO_USER_INFO_FLAGS);
+        mockGetCurrentUser(userId);
+        mockUmGetUserInfo(mMockedUserManager, userInfo);
+        mockAmStartUserInBackground(userId, true);
+
+        AndroidFuture<UserStartResult> userStartResult = new AndroidFuture<>();
+        mCarUserService.startUserInBackground(userId, userStartResult);
+
+        assertThat(getResult(userStartResult).getStatus())
+                .isEqualTo(UserStartResult.STATUS_SUCCESSFUL_USER_IS_CURRENT_USER);
+        assertThat(getResult(userStartResult).isSuccess()).isTrue();
+    }
+
+    @Test
+    public void testStartUserInBackground_userDoesNotExist() throws Exception {
+        int userId = 101;
+        mockCurrentUser(mRegularUser);
+        when(mMockedUserManager.getUserInfo(userId)).thenReturn(null);
+        mockAmStartUserInBackground(userId, true);
+
+        AndroidFuture<UserStartResult> userStartResult = new AndroidFuture<>();
+        mCarUserService.startUserInBackground(userId, userStartResult);
+
+        assertThat(getResult(userStartResult).getStatus())
+                .isEqualTo(UserStartResult.STATUS_USER_DOES_NOT_EXIST);
+        assertThat(getResult(userStartResult).isSuccess()).isFalse();
+    }
+
+    @Test
     public void testIsHalSupported() throws Exception {
         when(mUserHal.isSupported()).thenReturn(true);
         assertThat(mCarUserService.isUserHalSupported()).isTrue();
@@ -2286,6 +2430,11 @@
         mockGetCurrentUser(user.id);
     }
 
+    private void mockAmStartUserInBackground(@UserIdInt int userId, boolean result)
+            throws Exception {
+        when(mMockedIActivityManager.startUserInBackground(userId)).thenReturn(result);
+    }
+
     private void mockAmSwitchUser(@NonNull UserInfo user, boolean result) throws Exception {
         when(mMockedIActivityManager.switchUser(user.id)).thenReturn(result);
     }
@@ -2318,6 +2467,18 @@
                 /* listener= */ null);
     }
 
+    private void mockStopUserWithDelayedLocking(@UserIdInt int userId, int result)
+            throws Exception {
+        when(mMockedIActivityManager.stopUserWithDelayedLocking(userId, true, null))
+                .thenReturn(result);
+    }
+
+    private void mockStopUserWithDelayedLockingThrowsRemoteException(@UserIdInt int userId)
+            throws Exception {
+        when(mMockedIActivityManager.stopUserWithDelayedLocking(userId, true, null))
+                .thenThrow(new RemoteException());
+    }
+
     private void mockHalGetInitialInfo(@UserIdInt int currentUserId,
             @NonNull InitialUserInfoResponse response) {
         UsersInfo usersInfo = newUsersInfo(currentUserId);
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;
+    }
+}