Merge "Add profile user test to KS" into rvc-dev
diff --git a/car-lib/src/android/car/Car.java b/car-lib/src/android/car/Car.java
index 68f6d8f..4ba587f 100644
--- a/car-lib/src/android/car/Car.java
+++ b/car-lib/src/android/car/Car.java
@@ -1565,7 +1565,8 @@
     }
 
     /** @hide */
-    Handler getEventHandler() {
+    @VisibleForTesting
+    public Handler getEventHandler() {
         return mEventHandler;
     }
 
diff --git a/car-lib/src/android/car/CarAppFocusManager.java b/car-lib/src/android/car/CarAppFocusManager.java
index ef9ddf4..3d4d90d 100644
--- a/car-lib/src/android/car/CarAppFocusManager.java
+++ b/car-lib/src/android/car/CarAppFocusManager.java
@@ -21,6 +21,8 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.ref.WeakReference;
@@ -122,7 +124,8 @@
     /**
      * @hide
      */
-    CarAppFocusManager(Car car, IBinder service) {
+    @VisibleForTesting
+    public CarAppFocusManager(Car car, IBinder service) {
         super(car);
         mService = IAppFocus.Stub.asInterface(service);
     }
diff --git a/car-lib/src/android/car/app/CarActivityView.java b/car-lib/src/android/car/app/CarActivityView.java
index b63d08b..6a538d6 100644
--- a/car-lib/src/android/car/app/CarActivityView.java
+++ b/car-lib/src/android/car/app/CarActivityView.java
@@ -39,6 +39,7 @@
     // volatile, since mUserActivityViewCallback can be accessed from Main and Binder thread.
     @Nullable private volatile StateCallback mUserActivityViewCallback;
 
+    @Nullable private Car mCar;
     @Nullable private CarUxRestrictionsManager mUxRestrictionsManager;
 
     private int mVirtualDisplayId = Display.INVALID_DISPLAY;
@@ -59,21 +60,6 @@
             Context context, AttributeSet attrs, int defStyle, boolean singleTaskInstance) {
         super(context, attrs, defStyle, singleTaskInstance);
         super.setCallback(new CarActivityViewCallback());
-        Car.createCar(mContext, /*handler=*/ null,
-                Car.CAR_WAIT_TIMEOUT_DO_NOT_WAIT,
-                (car, ready) -> {
-                    // Expect to be called in the main thread, since passed a 'null' handler
-                    // in Car.createCar().
-                    if (!ready) return;
-                    mUxRestrictionsManager = (CarUxRestrictionsManager) car.getCarManager(
-                            Car.CAR_UX_RESTRICTION_SERVICE);
-                    if (mVirtualDisplayId != Display.INVALID_DISPLAY) {
-                        // When the CarService is reconnected, we'd like to report the physical
-                        // display id again, since the previously reported mapping could be gone.
-                        reportPhysicalDisplayId(
-                                mUxRestrictionsManager, mVirtualDisplayId, mContext.getDisplayId());
-                    }
-                });
     }
 
     @Override
@@ -154,4 +140,30 @@
             }
         }
     }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        mCar = Car.createCar(mContext, /*handler=*/ null,
+                Car.CAR_WAIT_TIMEOUT_DO_NOT_WAIT,
+                (car, ready) -> {
+                    // Expect to be called in the main thread, since passed a 'null' handler
+                    // in Car.createCar().
+                    if (!ready) return;
+                    mUxRestrictionsManager = (CarUxRestrictionsManager) car.getCarManager(
+                            Car.CAR_UX_RESTRICTION_SERVICE);
+                    if (mVirtualDisplayId != Display.INVALID_DISPLAY) {
+                        // When the CarService is reconnected, we'd like to report the physical
+                        // display id again, since the previously reported mapping could be gone.
+                        reportPhysicalDisplayId(
+                                mUxRestrictionsManager, mVirtualDisplayId, mContext.getDisplayId());
+                    }
+                });
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        if (mCar != null) mCar.disconnect();
+    }
 }
diff --git a/car-lib/src/android/car/watchdog/CarWatchdogManager.java b/car-lib/src/android/car/watchdog/CarWatchdogManager.java
index c49a81a..fff1283 100644
--- a/car-lib/src/android/car/watchdog/CarWatchdogManager.java
+++ b/car-lib/src/android/car/watchdog/CarWatchdogManager.java
@@ -96,8 +96,8 @@
      * respond by calling {@link CarWatchdogManager.tellClientAlive} within timeout. If they don't
      * respond, car watchdog server reports the current state and kills them.
      *
-     * <p>Before car watchdog server kills the client, it calls onPrepareProcessKill to allow them
-     * to prepare the termination. They will be killed in 1 second.
+     * <p>Before car watchdog server kills the client, it calls onPrepareProcessTermination to allow
+     * them to prepare the termination. They will be killed in 1 second.
      */
     public abstract static class CarWatchdogClientCallback {
         /**
@@ -124,8 +124,6 @@
          * <p>The callback method is called at the Executor which is specifed in {@link
          * #registerClient}.
          */
-        // TODO(b/150006093): After adding a callback to ICarWatchdogClient, subsequent
-        // implementation should be done in CarWatchdogService and CarWatchdogManager.
         public void onPrepareProcessTermination() {}
     }
 
@@ -305,6 +303,20 @@
         }
     }
 
+    private void notifyProcessTermination() {
+        CarWatchdogClientCallback client;
+        Executor executor;
+        synchronized (mLock) {
+            if (mRegisteredClient == null) {
+                Log.w(TAG, "Cannot notify the client. The client has not been registered.");
+                return;
+            }
+            client = mRegisteredClient;
+            executor = mCallbackExecutor;
+        }
+        executor.execute(() -> client.onPrepareProcessTermination());
+    }
+
     /** @hide */
     private static final class ICarWatchdogClientImpl extends ICarWatchdogClient.Stub {
         private final WeakReference<CarWatchdogManager> mManager;
@@ -322,6 +334,14 @@
         }
 
         @Override
+        public void prepareProcessTermination() {
+            CarWatchdogManager manager = mManager.get();
+            if (manager != null) {
+                manager.notifyProcessTermination();
+            }
+        }
+
+        @Override
         public int getInterfaceVersion() {
             return this.VERSION;
         }
diff --git a/computepipe/runner/RunnerComponent.cpp b/computepipe/runner/RunnerComponent.cpp
index b8375a5..d3d1482 100644
--- a/computepipe/runner/RunnerComponent.cpp
+++ b/computepipe/runner/RunnerComponent.cpp
@@ -49,6 +49,7 @@
     config.set_input_config_id(mInputConfigId);
     config.set_termination_id(mTerminationId);
     config.set_offload_id(mOffloadId);
+    config.set_profiling_type(mProfilingType);
     for (auto it : mOutputConfigs) {
         (*config.mutable_output_options())[it.first] = it.second;
     }
diff --git a/computepipe/runner/client_interface/DebuggerImpl.cpp b/computepipe/runner/client_interface/DebuggerImpl.cpp
index ffd4820..67d516a 100644
--- a/computepipe/runner/client_interface/DebuggerImpl.cpp
+++ b/computepipe/runner/client_interface/DebuggerImpl.cpp
@@ -37,7 +37,7 @@
 
 using ::ndk::ScopedAStatus;
 
-constexpr std::chrono::milliseconds kProfilingDataReadTimeout = 10ms;
+constexpr std::chrono::milliseconds kProfilingDataReadTimeout = 50ms;
 
 proto::ProfilingType ToProtoProfilingType(PipeProfilingType type) {
     switch (type) {
diff --git a/computepipe/runner/engine/DefaultEngine.cpp b/computepipe/runner/engine/DefaultEngine.cpp
index 9f1531b..f10f74a 100644
--- a/computepipe/runner/engine/DefaultEngine.cpp
+++ b/computepipe/runner/engine/DefaultEngine.cpp
@@ -636,18 +636,18 @@
         mCommandQueue.pop();
         switch (ec.cmdType) {
             case EngineCommand::Type::BROADCAST_CONFIG:
-                LOG(INFO) << "Engine::Received broacast config request";
+                LOG(INFO) << "Engine::Received broadcast config request";
                 (void)broadcastClientConfig();
                 break;
             case EngineCommand::Type::BROADCAST_START_RUN:
-                LOG(INFO) << "Engine::Received broacast run request";
+                LOG(INFO) << "Engine::Received broadcast run request";
                 (void)broadcastStartRun();
                 break;
             case EngineCommand::Type::BROADCAST_INITIATE_STOP:
                 if (ec.source.find("ClientInterface") != std::string::npos) {
                     mStopFromClient = true;
                 }
-                LOG(INFO) << "Engine::Received broacast stop with flush request";
+                LOG(INFO) << "Engine::Received broadcast stop with flush request";
                 broadcastStopWithFlush();
                 break;
             case EngineCommand::Type::POLL_COMPLETE:
@@ -683,7 +683,8 @@
                 break;
             case EngineCommand::Type::READ_PROFILING:
                 std::string debugData;
-                if (mGraph && (mCurrentPhase == kConfigPhase || mCurrentPhase == kRunPhase)) {
+                if (mGraph && (mCurrentPhase == kConfigPhase || mCurrentPhase == kRunPhase
+                                || mCurrentPhase == kStopPhase)) {
                     debugData = mGraph->GetDebugInfo();
                 }
                 if (mClient) {
diff --git a/service/src/com/android/car/AppFocusService.java b/service/src/com/android/car/AppFocusService.java
index 27161a5..42f7ac9 100644
--- a/service/src/com/android/car/AppFocusService.java
+++ b/service/src/com/android/car/AppFocusService.java
@@ -51,11 +51,13 @@
 
     private final Object mLock = new Object();
 
+    @VisibleForTesting
     @GuardedBy("mLock")
-    private final ClientHolder mAllChangeClients;
+    final ClientHolder mAllChangeClients;
 
+    @VisibleForTesting
     @GuardedBy("mLock")
-    private final OwnershipClientHolder mAllOwnershipClients;
+    final OwnershipClientHolder mAllOwnershipClients;
 
     /** K: appType, V: client owning it */
     @GuardedBy("mLock")
@@ -71,16 +73,19 @@
             mAllBinderEventHandler = bInterface -> { /* nothing to do.*/ };
 
     @GuardedBy("mLock")
-    private DispatchHandler mDispatchHandler;
+    private final DispatchHandler mDispatchHandler;
 
     @GuardedBy("mLock")
-    private HandlerThread mHandlerThread;
+    private final HandlerThread mHandlerThread;
 
     public AppFocusService(Context context,
             SystemActivityMonitoringService systemActivityMonitoringService) {
         mSystemActivityMonitoringService = systemActivityMonitoringService;
         mAllChangeClients = new ClientHolder(mAllBinderEventHandler);
         mAllOwnershipClients = new OwnershipClientHolder(this);
+        mHandlerThread = new HandlerThread(AppFocusService.class.getSimpleName());
+        mHandlerThread.start();
+        mDispatchHandler = new DispatchHandler(mHandlerThread.getLooper());
     }
 
     @Override
@@ -230,11 +235,7 @@
 
     @Override
     public void init() {
-        synchronized (mLock) {
-            mHandlerThread = new HandlerThread(AppFocusService.class.getSimpleName());
-            mHandlerThread.start();
-            mDispatchHandler = new DispatchHandler(mHandlerThread.getLooper());
-        }
+        // nothing to do
     }
 
     @VisibleForTesting
@@ -247,17 +248,6 @@
     @Override
     public void release() {
         synchronized (mLock) {
-            if (mDispatchHandler == null) {
-                return;
-            }
-            mHandlerThread.quitSafely();
-            try {
-                mHandlerThread.join(1000);
-            } catch (InterruptedException e) {
-                Thread.currentThread().interrupt();
-                Log.e(CarLog.TAG_APP_FOCUS, "Timeout while waiting for handler thread to join.");
-            }
-            mDispatchHandler = null;
             mAllChangeClients.clear();
             mAllOwnershipClients.clear();
             mFocusOwners.clear();
@@ -372,13 +362,15 @@
         }
     }
 
-    private static class ClientHolder extends BinderInterfaceContainer<IAppFocusListener> {
+    @VisibleForTesting
+    static class ClientHolder extends BinderInterfaceContainer<IAppFocusListener> {
         private ClientHolder(BinderEventHandler<IAppFocusListener> holder) {
             super(holder);
         }
     }
 
-    private static class OwnershipClientHolder extends
+    @VisibleForTesting
+    static class OwnershipClientHolder extends
             BinderInterfaceContainer<IAppFocusOwnershipCallback> {
         private OwnershipClientHolder(AppFocusService service) {
             super(service);
diff --git a/service/src/com/android/car/BinderInterfaceContainer.java b/service/src/com/android/car/BinderInterfaceContainer.java
index b97db35..32f7eb9 100644
--- a/service/src/com/android/car/BinderInterfaceContainer.java
+++ b/service/src/com/android/car/BinderInterfaceContainer.java
@@ -181,9 +181,9 @@
     }
 
     private void handleBinderDeath(BinderInterface<T> bInterface) {
-        removeBinder(bInterface.binderInterface);
         if (mEventHandler != null) {
             mEventHandler.onBinderDeath(bInterface);
         }
+        removeBinder(bInterface.binderInterface);
     }
 }
diff --git a/service/src/com/android/car/watchdog/CarWatchdogService.java b/service/src/com/android/car/watchdog/CarWatchdogService.java
index d24e569..21ac3e6 100644
--- a/service/src/com/android/car/watchdog/CarWatchdogService.java
+++ b/service/src/com/android/car/watchdog/CarWatchdogService.java
@@ -107,7 +107,7 @@
     @GuardedBy("mLock")
     private final SparseArray<Boolean> mClientCheckInProgress = new SparseArray<>();
     @GuardedBy("mLock")
-    private final ArrayList<Integer> mClientsNotResponding = new ArrayList<>();
+    private final ArrayList<ClientInfo> mClientsNotResponding = new ArrayList<>();
     @GuardedBy("mMainHandler")
     private int mLastSessionId;
     @GuardedBy("mMainHandler")
@@ -324,14 +324,13 @@
         // and killed at the next response of CarWatchdogService to car watchdog daemon.
         SparseArray<ClientInfo> pingedClients = mPingedClientMap.get(timeout);
         synchronized (mLock) {
-            // Unhealthy clients are eventually removed from the list through binderDied when they
-            // are killed.
             for (int i = 0; i < pingedClients.size(); i++) {
                 ClientInfo clientInfo = pingedClients.valueAt(i);
                 if (mStoppedUser.get(clientInfo.userId)) {
                     continue;
                 }
-                mClientsNotResponding.add(clientInfo.pid);
+                mClientsNotResponding.add(clientInfo);
+                removeClientLocked(clientInfo.client.asBinder(), timeout);
             }
             mClientCheckInProgress.setValueAt(timeout, false);
         }
@@ -399,10 +398,22 @@
 
     private void reportHealthCheckResult(int sessionId) {
         int[] clientsNotResponding;
+        ArrayList<ClientInfo> clientsToNotify;
         synchronized (mLock) {
             clientsNotResponding = toIntArray(mClientsNotResponding);
+            clientsToNotify = new ArrayList<>(mClientsNotResponding);
             mClientsNotResponding.clear();
         }
+        for (int i = 0; i < clientsToNotify.size(); i++) {
+            ClientInfo clientInfo = clientsToNotify.get(i);
+            try {
+                clientInfo.client.prepareProcessTermination();
+            } catch (RemoteException e) {
+                Log.w(TAG, "Notifying prepareProcessTermination to client(pid: " + clientInfo.pid
+                        + ") failed: " + e);
+            }
+        }
+
         try {
             mCarWatchdogDaemonHelper.tellMediatorAlive(mWatchdogClient, clientsNotResponding,
                     sessionId);
@@ -500,11 +511,11 @@
     }
 
     @NonNull
-    private int[] toIntArray(@NonNull ArrayList<Integer> list) {
+    private int[] toIntArray(@NonNull ArrayList<ClientInfo> list) {
         int size = list.size();
         int[] intArray = new int[size];
         for (int i = 0; i < size; i++) {
-            intArray[i] = list.get(i);
+            intArray[i] = list.get(i).pid;
         }
         return intArray;
     }
@@ -556,6 +567,11 @@
         }
 
         @Override
+        public void prepareProcessTermination() {
+            Log.w(TAG, "CarWatchdogService is about to be killed by car watchdog daemon");
+        }
+
+        @Override
         public int getInterfaceVersion() {
             return this.VERSION;
         }
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/CarWatchdogClient.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/CarWatchdogClient.java
index 3235a60..3f696f7 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/CarWatchdogClient.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/CarWatchdogClient.java
@@ -49,7 +49,7 @@
 
         @Override
         public void onPrepareProcessTermination() {
-            Log.i(TAG, "This process is being terminated by Car watchdog");
+            Log.w(TAG, "This process is being terminated by car watchdog");
         }
     };
     private final ExecutorService mCallbackExecutor = Executors.newFixedThreadPool(1);
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 acd149a..6cf82bd 100644
--- a/tests/carservice_unit_test/src/android/car/watchdoglib/CarWatchdogDaemonHelperTest.java
+++ b/tests/carservice_unit_test/src/android/car/watchdoglib/CarWatchdogDaemonHelperTest.java
@@ -211,6 +211,9 @@
         public void checkIfAlive(int sessionId, int timeout) {}
 
         @Override
+        public void prepareProcessTermination() {}
+
+        @Override
         public int getInterfaceVersion() {
             return this.VERSION;
         }
diff --git a/tests/carservice_unit_test/src/com/android/car/AppFocusServiceTest.java b/tests/carservice_unit_test/src/com/android/car/AppFocusServiceTest.java
new file mode 100644
index 0000000..0e7731b
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/AppFocusServiceTest.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 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.
+ */
+
+package com.android.car;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.doReturn;
+
+import android.car.Car;
+import android.car.CarAppFocusManager;
+import android.car.IAppFocusListener;
+import android.car.IAppFocusOwnershipCallback;
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(MockitoJUnitRunner.class)
+public class AppFocusServiceTest {
+
+    private static final long WAIT_TIMEOUT_MS = 500;
+
+    private final Handler mMainHandler = new Handler(Looper.getMainLooper());
+
+    @Mock
+    private Context mContext;
+    @Mock
+    private SystemActivityMonitoringService mSystemActivityMonitoringService;
+    @Mock
+    private Car mCar;
+
+    private AppFocusService mService;
+    private CarAppFocusManager mCarAppFocusManager1;
+    private CarAppFocusManager mCarAppFocusManager2;
+
+    private AppFocusChangedListener mAppFocusChangedListener1 = new AppFocusChangedListener();
+
+    private AppFocusOwnershipCallback mAppFocusOwnershipCallback1 = new AppFocusOwnershipCallback();
+
+    @Before
+    public void setUp() {
+        mService = new AppFocusService(mContext, mSystemActivityMonitoringService);
+        mService.init();
+        doReturn(mMainHandler).when(mCar).getEventHandler();
+        mCarAppFocusManager1 = new CarAppFocusManager(mCar, mService.asBinder());
+        mCarAppFocusManager2 = new CarAppFocusManager(mCar, mService.asBinder());
+    }
+
+    @Test
+    public void testSingleOwner() throws Exception {
+        mCarAppFocusManager2.addFocusListener(mAppFocusChangedListener1,
+                CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION);
+
+        int r = mCarAppFocusManager1.requestAppFocus(CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION,
+                mAppFocusOwnershipCallback1);
+        assertThat(r).isEqualTo(CarAppFocusManager.APP_FOCUS_REQUEST_SUCCEEDED);
+        assertThat(mCarAppFocusManager1.isOwningFocus(mAppFocusOwnershipCallback1,
+                CarAppFocusManager.APP_FOCUS_REQUEST_SUCCEEDED)).isTrue();
+        waitForNavFocusChangeAndAssert(mAppFocusChangedListener1, true);
+
+        mAppFocusChangedListener1.resetWait();
+        mCarAppFocusManager1.abandonAppFocus(mAppFocusOwnershipCallback1,
+                CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION);
+        assertThat(mCarAppFocusManager1.isOwningFocus(mAppFocusOwnershipCallback1,
+                CarAppFocusManager.APP_FOCUS_REQUEST_SUCCEEDED)).isFalse();
+        waitForNavFocusChangeAndAssert(mAppFocusChangedListener1, false);
+    }
+
+    private void waitForNavFocusChangeAndAssert(AppFocusChangedListener listener, boolean isActive)
+            throws Exception {
+        listener.waitForEvent();
+        if (isActive) {
+            assertThat(listener.mLastActive).isTrue();
+        } else {
+            assertThat(listener.mLastActive).isFalse();
+        }
+        assertThat(listener.mLastAppType).isEqualTo(CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION);
+    }
+
+    @Test
+    public void testOwnerBinderDeath() throws Exception {
+        mCarAppFocusManager2.addFocusListener(mAppFocusChangedListener1,
+                CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION);
+
+        int r = mCarAppFocusManager1.requestAppFocus(CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION,
+                mAppFocusOwnershipCallback1);
+        assertThat(r).isEqualTo(CarAppFocusManager.APP_FOCUS_REQUEST_SUCCEEDED);
+        assertThat(mCarAppFocusManager1.isOwningFocus(mAppFocusOwnershipCallback1,
+                CarAppFocusManager.APP_FOCUS_REQUEST_SUCCEEDED)).isTrue();
+        waitForNavFocusChangeAndAssert(mAppFocusChangedListener1, true);
+
+        assertThat(mService.mAllOwnershipClients.getInterfaces()).hasSize(1);
+        BinderInterfaceContainer.BinderInterface<IAppFocusOwnershipCallback> binder =
+                mService.mAllOwnershipClients.getInterfaces().iterator().next();
+        // Now fake binder death
+        mAppFocusChangedListener1.resetWait();
+        binder.binderDied();
+        assertThat(mService.mAllOwnershipClients.getInterfaces()).isEmpty();
+        waitForNavFocusChangeAndAssert(mAppFocusChangedListener1, false);
+    }
+
+    @Test
+    public void testListenerBinderDeath() throws Exception {
+
+        mCarAppFocusManager1.addFocusListener(mAppFocusChangedListener1,
+                CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION);
+        assertThat(mService.mAllChangeClients.getInterfaces()).hasSize(1);
+        BinderInterfaceContainer.BinderInterface<IAppFocusListener> binder =
+                mService.mAllChangeClients.getInterfaces().iterator().next();
+        binder.binderDied();
+        assertThat(mService.mAllChangeClients.getInterfaces()).isEmpty();
+    }
+
+    private class AppFocusChangedListener implements CarAppFocusManager.OnAppFocusChangedListener {
+
+        private final Semaphore mSemaphore = new Semaphore(0);
+        private int mLastAppType;
+        private boolean mLastActive;
+
+        @Override
+        public void onAppFocusChanged(int appType, boolean active) {
+            mLastAppType = appType;
+            mLastActive = active;
+            mSemaphore.release();
+        }
+
+        public void waitForEvent() throws Exception {
+            assertThat(mSemaphore.tryAcquire(WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
+        }
+
+        public void resetWait() {
+            mSemaphore.drainPermits();
+        }
+    }
+
+    private class AppFocusOwnershipCallback implements
+            CarAppFocusManager.OnAppFocusOwnershipCallback {
+
+        private final Semaphore mSemaphore = new Semaphore(0);
+        private int mGrantedAppTypes;
+
+        @Override
+        public void onAppFocusOwnershipLost(int appType) {
+            mGrantedAppTypes = mGrantedAppTypes & ~appType;
+            mSemaphore.release();
+        }
+
+        @Override
+        public void onAppFocusOwnershipGranted(int appType) {
+            mGrantedAppTypes = mGrantedAppTypes | appType;
+            mSemaphore.release();
+        }
+
+        public void waitForEvent() throws Exception {
+            mSemaphore.tryAcquire(WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+        }
+
+        public void resetWait() {
+            mSemaphore.drainPermits();
+        }
+    }
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/watchdog/CarWatchdogServiceTest.java b/tests/carservice_unit_test/src/com/android/car/watchdog/CarWatchdogServiceTest.java
index cfc9802..4467d61 100644
--- a/tests/carservice_unit_test/src/com/android/car/watchdog/CarWatchdogServiceTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/watchdog/CarWatchdogServiceTest.java
@@ -226,6 +226,9 @@
         }
 
         @Override
+        public void prepareProcessTermination() {}
+
+        @Override
         public int getInterfaceVersion() {
             return this.VERSION;
         }
diff --git a/watchdog/aidl/Android.bp b/watchdog/aidl/Android.bp
index 25564c1..e17c87b 100644
--- a/watchdog/aidl/Android.bp
+++ b/watchdog/aidl/Android.bp
@@ -25,5 +25,7 @@
             enabled: true,
         },
     },
-    versions: ["1"],
+    versions: [
+        "2",
+    ],
 }
diff --git a/watchdog/aidl/aidl_api/carwatchdog_aidl_interface/2/.hash b/watchdog/aidl/aidl_api/carwatchdog_aidl_interface/2/.hash
new file mode 100644
index 0000000..f024209
--- /dev/null
+++ b/watchdog/aidl/aidl_api/carwatchdog_aidl_interface/2/.hash
@@ -0,0 +1 @@
+f7adf2ef96b380c7fde3919f565eb764986bdcdd
diff --git a/watchdog/aidl/aidl_api/carwatchdog_aidl_interface/2/android/automotive/watchdog/BootPhase.aidl b/watchdog/aidl/aidl_api/carwatchdog_aidl_interface/2/android/automotive/watchdog/BootPhase.aidl
new file mode 100644
index 0000000..f8570de
--- /dev/null
+++ b/watchdog/aidl/aidl_api/carwatchdog_aidl_interface/2/android/automotive/watchdog/BootPhase.aidl
@@ -0,0 +1,22 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.automotive.watchdog;
+@Backing(type="int") @VintfStability
+enum BootPhase {
+  BOOT_COMPLETED = 1000,
+}
diff --git a/watchdog/aidl/aidl_api/carwatchdog_aidl_interface/2/android/automotive/watchdog/ICarWatchdog.aidl b/watchdog/aidl/aidl_api/carwatchdog_aidl_interface/2/android/automotive/watchdog/ICarWatchdog.aidl
new file mode 100644
index 0000000..61d4f32
--- /dev/null
+++ b/watchdog/aidl/aidl_api/carwatchdog_aidl_interface/2/android/automotive/watchdog/ICarWatchdog.aidl
@@ -0,0 +1,31 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.automotive.watchdog;
+@VintfStability
+interface ICarWatchdog {
+  void registerClient(in android.automotive.watchdog.ICarWatchdogClient client, in android.automotive.watchdog.TimeoutLength timeout);
+  void unregisterClient(in android.automotive.watchdog.ICarWatchdogClient client);
+  void registerMediator(in android.automotive.watchdog.ICarWatchdogClient mediator);
+  void unregisterMediator(in android.automotive.watchdog.ICarWatchdogClient mediator);
+  void registerMonitor(in android.automotive.watchdog.ICarWatchdogMonitor monitor);
+  void unregisterMonitor(in android.automotive.watchdog.ICarWatchdogMonitor monitor);
+  void tellClientAlive(in android.automotive.watchdog.ICarWatchdogClient client, in int sessionId);
+  void tellMediatorAlive(in android.automotive.watchdog.ICarWatchdogClient mediator, in int[] clientsNotResponding, in int sessionId);
+  void tellDumpFinished(in android.automotive.watchdog.ICarWatchdogMonitor monitor, in int pid);
+  void notifySystemStateChange(in android.automotive.watchdog.StateType type, in int arg1, in int arg2);
+}
diff --git a/watchdog/aidl/aidl_api/carwatchdog_aidl_interface/2/android/automotive/watchdog/ICarWatchdogClient.aidl b/watchdog/aidl/aidl_api/carwatchdog_aidl_interface/2/android/automotive/watchdog/ICarWatchdogClient.aidl
new file mode 100644
index 0000000..9768565
--- /dev/null
+++ b/watchdog/aidl/aidl_api/carwatchdog_aidl_interface/2/android/automotive/watchdog/ICarWatchdogClient.aidl
@@ -0,0 +1,23 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.automotive.watchdog;
+@VintfStability
+interface ICarWatchdogClient {
+  oneway void checkIfAlive(in int sessionId, in android.automotive.watchdog.TimeoutLength timeout);
+  oneway void prepareProcessTermination();
+}
diff --git a/watchdog/aidl/aidl_api/carwatchdog_aidl_interface/2/android/automotive/watchdog/ICarWatchdogMonitor.aidl b/watchdog/aidl/aidl_api/carwatchdog_aidl_interface/2/android/automotive/watchdog/ICarWatchdogMonitor.aidl
new file mode 100644
index 0000000..f13af14
--- /dev/null
+++ b/watchdog/aidl/aidl_api/carwatchdog_aidl_interface/2/android/automotive/watchdog/ICarWatchdogMonitor.aidl
@@ -0,0 +1,22 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.automotive.watchdog;
+@VintfStability
+interface ICarWatchdogMonitor {
+  oneway void onClientsNotResponding(in int[] pids);
+}
diff --git a/watchdog/aidl/aidl_api/carwatchdog_aidl_interface/2/android/automotive/watchdog/PowerCycle.aidl b/watchdog/aidl/aidl_api/carwatchdog_aidl_interface/2/android/automotive/watchdog/PowerCycle.aidl
new file mode 100644
index 0000000..2cfd03e
--- /dev/null
+++ b/watchdog/aidl/aidl_api/carwatchdog_aidl_interface/2/android/automotive/watchdog/PowerCycle.aidl
@@ -0,0 +1,25 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.automotive.watchdog;
+@Backing(type="int") @VintfStability
+enum PowerCycle {
+  POWER_CYCLE_SHUTDOWN = 0,
+  POWER_CYCLE_SUSPEND = 1,
+  POWER_CYCLE_RESUME = 2,
+  NUM_POWER_CYLES = 3,
+}
diff --git a/watchdog/aidl/aidl_api/carwatchdog_aidl_interface/2/android/automotive/watchdog/StateType.aidl b/watchdog/aidl/aidl_api/carwatchdog_aidl_interface/2/android/automotive/watchdog/StateType.aidl
new file mode 100644
index 0000000..52cd08e
--- /dev/null
+++ b/watchdog/aidl/aidl_api/carwatchdog_aidl_interface/2/android/automotive/watchdog/StateType.aidl
@@ -0,0 +1,24 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.automotive.watchdog;
+@Backing(type="int") @VintfStability
+enum StateType {
+  POWER_CYCLE = 0,
+  USER_STATE = 1,
+  BOOT_PHASE = 2,
+}
diff --git a/watchdog/aidl/aidl_api/carwatchdog_aidl_interface/2/android/automotive/watchdog/TimeoutLength.aidl b/watchdog/aidl/aidl_api/carwatchdog_aidl_interface/2/android/automotive/watchdog/TimeoutLength.aidl
new file mode 100644
index 0000000..e4be851
--- /dev/null
+++ b/watchdog/aidl/aidl_api/carwatchdog_aidl_interface/2/android/automotive/watchdog/TimeoutLength.aidl
@@ -0,0 +1,24 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.automotive.watchdog;
+@Backing(type="int") @VintfStability
+enum TimeoutLength {
+  TIMEOUT_CRITICAL = 0,
+  TIMEOUT_MODERATE = 1,
+  TIMEOUT_NORMAL = 2,
+}
diff --git a/watchdog/aidl/aidl_api/carwatchdog_aidl_interface/2/android/automotive/watchdog/UserState.aidl b/watchdog/aidl/aidl_api/carwatchdog_aidl_interface/2/android/automotive/watchdog/UserState.aidl
new file mode 100644
index 0000000..39dcc84
--- /dev/null
+++ b/watchdog/aidl/aidl_api/carwatchdog_aidl_interface/2/android/automotive/watchdog/UserState.aidl
@@ -0,0 +1,24 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.automotive.watchdog;
+@Backing(type="int") @VintfStability
+enum UserState {
+  USER_STATE_STARTED = 0,
+  USER_STATE_STOPPED = 1,
+  NUM_USER_STATES = 2,
+}
diff --git a/watchdog/aidl/aidl_api/carwatchdog_aidl_interface/current/android/automotive/watchdog/ICarWatchdogClient.aidl b/watchdog/aidl/aidl_api/carwatchdog_aidl_interface/current/android/automotive/watchdog/ICarWatchdogClient.aidl
index d934ad0..9768565 100644
--- a/watchdog/aidl/aidl_api/carwatchdog_aidl_interface/current/android/automotive/watchdog/ICarWatchdogClient.aidl
+++ b/watchdog/aidl/aidl_api/carwatchdog_aidl_interface/current/android/automotive/watchdog/ICarWatchdogClient.aidl
@@ -19,4 +19,5 @@
 @VintfStability
 interface ICarWatchdogClient {
   oneway void checkIfAlive(in int sessionId, in android.automotive.watchdog.TimeoutLength timeout);
+  oneway void prepareProcessTermination();
 }
diff --git a/watchdog/aidl/android/automotive/watchdog/ICarWatchdogClient.aidl b/watchdog/aidl/android/automotive/watchdog/ICarWatchdogClient.aidl
index 1d33957..958b620 100644
--- a/watchdog/aidl/android/automotive/watchdog/ICarWatchdogClient.aidl
+++ b/watchdog/aidl/android/automotive/watchdog/ICarWatchdogClient.aidl
@@ -31,4 +31,9 @@
    * @param timeout             Final timeout given by the server based on client request.
    */
   void checkIfAlive(in int sessionId, in TimeoutLength timeout);
+
+  /**
+   * Notify the client that it will be forcedly terminated in 1 second.
+   */
+  void prepareProcessTermination();
 }
diff --git a/watchdog/server/src/IoPerfCollection.cpp b/watchdog/server/src/IoPerfCollection.cpp
index 0e45996..63d47ef 100644
--- a/watchdog/server/src/IoPerfCollection.cpp
+++ b/watchdog/server/src/IoPerfCollection.cpp
@@ -77,32 +77,75 @@
 }
 
 struct UidProcessStats {
+    struct ProcessInfo {
+        std::string comm = "";
+        uint64_t count = 0;
+    };
     uint64_t uid = 0;
     uint32_t ioBlockedTasksCnt = 0;
     uint32_t totalTasksCnt = 0;
     uint64_t majorFaults = 0;
+    std::vector<ProcessInfo> topNIoBlockedProcesses = {};
+    std::vector<ProcessInfo> topNMajorFaultProcesses = {};
 };
 
-std::unordered_map<uint32_t, UidProcessStats> getUidProcessStats(
-        const std::vector<ProcessStats>& processStats) {
-    std::unordered_map<uint32_t, UidProcessStats> uidProcessStats;
+std::unique_ptr<std::unordered_map<uint32_t, UidProcessStats>> getUidProcessStats(
+        const std::vector<ProcessStats>& processStats, int topNStatsPerSubCategory) {
+    std::unique_ptr<std::unordered_map<uint32_t, UidProcessStats>> uidProcessStats(
+            new std::unordered_map<uint32_t, UidProcessStats>());
     for (const auto& stats : processStats) {
         if (stats.uid < 0) {
             continue;
         }
         uint32_t uid = static_cast<uint32_t>(stats.uid);
-        if (uidProcessStats.find(uid) == uidProcessStats.end()) {
-            uidProcessStats[uid] = UidProcessStats{.uid = uid};
+        if (uidProcessStats->find(uid) == uidProcessStats->end()) {
+            (*uidProcessStats)[uid] = UidProcessStats{
+                    .uid = uid,
+                    .topNIoBlockedProcesses = std::vector<
+                            UidProcessStats::ProcessInfo>(topNStatsPerSubCategory,
+                                                          UidProcessStats::ProcessInfo{}),
+                    .topNMajorFaultProcesses = std::vector<
+                            UidProcessStats::ProcessInfo>(topNStatsPerSubCategory,
+                                                          UidProcessStats::ProcessInfo{}),
+            };
         }
-        auto& curUidProcessStats = uidProcessStats[uid];
+        auto& curUidProcessStats = (*uidProcessStats)[uid];
         // Top-level process stats has the aggregated major page faults count and this should be
         // persistent across thread creation/termination. Thus use the value from this field.
         curUidProcessStats.majorFaults += stats.process.majorFaults;
         curUidProcessStats.totalTasksCnt += stats.threads.size();
         // The process state is the same as the main thread state. Thus to avoid double counting
         // ignore the process state.
+        uint32_t ioBlockedTasksCnt = 0;
         for (const auto& threadStat : stats.threads) {
-            curUidProcessStats.ioBlockedTasksCnt += threadStat.second.state == "D" ? 1 : 0;
+            ioBlockedTasksCnt += threadStat.second.state == "D" ? 1 : 0;
+        }
+        curUidProcessStats.ioBlockedTasksCnt += ioBlockedTasksCnt;
+        for (auto it = curUidProcessStats.topNIoBlockedProcesses.begin();
+             it != curUidProcessStats.topNIoBlockedProcesses.end(); ++it) {
+            if (it->count < ioBlockedTasksCnt) {
+                curUidProcessStats.topNIoBlockedProcesses
+                        .emplace(it,
+                                 UidProcessStats::ProcessInfo{
+                                         .comm = stats.process.comm,
+                                         .count = ioBlockedTasksCnt,
+                                 });
+                curUidProcessStats.topNIoBlockedProcesses.pop_back();
+                break;
+            }
+        }
+        for (auto it = curUidProcessStats.topNMajorFaultProcesses.begin();
+             it != curUidProcessStats.topNMajorFaultProcesses.end(); ++it) {
+            if (it->count < stats.process.majorFaults) {
+                curUidProcessStats.topNMajorFaultProcesses
+                        .emplace(it,
+                                 UidProcessStats::ProcessInfo{
+                                         .comm = stats.process.comm,
+                                         .count = stats.process.majorFaults,
+                                 });
+                curUidProcessStats.topNMajorFaultProcesses.pop_back();
+                break;
+            }
         }
     }
     return uidProcessStats;
@@ -177,28 +220,42 @@
     StringAppendF(&buffer,
                   "Percentage of change in major page faults since last collection: %.2f%%\n",
                   data.majorFaultsPercentChange);
-    if (data.topNMajorFaults.size() > 0) {
+    if (data.topNMajorFaultUids.size() > 0) {
         StringAppendF(&buffer, "\nTop N major page faults:\n%s\n", std::string(24, '-').c_str());
         StringAppendF(&buffer,
                       "Android User ID, Package Name, Number of major page faults, "
                       "Percentage of total major page faults\n");
+        StringAppendF(&buffer,
+                      "\tCommand, Number of major page faults, Percentage of UID's major page "
+                      "faults\n");
     }
-    for (const auto& stat : data.topNMajorFaults) {
-        StringAppendF(&buffer, "%" PRIu32 ", %s, %" PRIu64 ", %.2f%%\n", stat.userId,
-                      stat.packageName.c_str(), stat.count,
-                      percentage(stat.count, data.totalMajorFaults));
+    for (const auto& uidStats : data.topNMajorFaultUids) {
+        StringAppendF(&buffer, "%" PRIu32 ", %s, %" PRIu64 ", %.2f%%\n", uidStats.userId,
+                      uidStats.packageName.c_str(), uidStats.count,
+                      percentage(uidStats.count, data.totalMajorFaults));
+        for (const auto& procStats : uidStats.topNProcesses) {
+            StringAppendF(&buffer, "\t%s, %" PRIu64 ", %.2f%%\n", procStats.comm.c_str(),
+                          procStats.count, percentage(procStats.count, uidStats.count));
+        }
     }
     if (data.topNIoBlockedUids.size() > 0) {
         StringAppendF(&buffer, "\nTop N I/O waiting UIDs:\n%s\n", std::string(23, '-').c_str());
         StringAppendF(&buffer,
                       "Android User ID, Package Name, Number of owned tasks waiting for I/O, "
                       "Percentage of owned tasks waiting for I/O\n");
+        StringAppendF(&buffer,
+                      "\tCommand, Number of I/O waiting tasks, Percentage of UID's tasks waiting "
+                      "for I/O\n");
     }
     for (size_t i = 0; i < data.topNIoBlockedUids.size(); ++i) {
-        const auto& stat = data.topNIoBlockedUids[i];
-        StringAppendF(&buffer, "%" PRIu32 ", %s, %" PRIu64 ", %.2f%%\n", stat.userId,
-                      stat.packageName.c_str(), stat.count,
-                      percentage(stat.count, data.topNIoBlockedUidsTotalTaskCnt[i]));
+        const auto& uidStats = data.topNIoBlockedUids[i];
+        StringAppendF(&buffer, "%" PRIu32 ", %s, %" PRIu64 ", %.2f%%\n", uidStats.userId,
+                      uidStats.packageName.c_str(), uidStats.count,
+                      percentage(uidStats.count, data.topNIoBlockedUidsTotalTaskCnt[i]));
+        for (const auto& procStats : uidStats.topNProcesses) {
+            StringAppendF(&buffer, "\t%s, %" PRIu64 ", %.2f%%\n", procStats.comm.c_str(),
+                          procStats.count, percentage(procStats.count, uidStats.count));
+        }
     }
     return buffer;
 }
@@ -663,21 +720,19 @@
 
         for (auto it = topNReads.begin(); it != topNReads.end(); ++it) {
             const UidIoUsage* curRead = *it;
-            if (curRead->ios.sumReadBytes() > curUsage.ios.sumReadBytes()) {
-                continue;
+            if (curRead->ios.sumReadBytes() < curUsage.ios.sumReadBytes()) {
+                topNReads.erase(topNReads.end() - 1);
+                topNReads.emplace(it, &curUsage);
+                break;
             }
-            topNReads.erase(topNReads.end() - 1);
-            topNReads.emplace(it, &curUsage);
-            break;
         }
         for (auto it = topNWrites.begin(); it != topNWrites.end(); ++it) {
             const UidIoUsage* curWrite = *it;
-            if (curWrite->ios.sumWriteBytes() > curUsage.ios.sumWriteBytes()) {
-                continue;
+            if (curWrite->ios.sumWriteBytes() < curUsage.ios.sumWriteBytes()) {
+                topNWrites.erase(topNWrites.end() - 1);
+                topNWrites.emplace(it, &curUsage);
+                break;
             }
-            topNWrites.erase(topNWrites.end() - 1);
-            topNWrites.emplace(it, &curUsage);
-            break;
         }
     }
 
@@ -761,15 +816,14 @@
         return Error() << "Failed to collect process stats: " << processStats.error();
     }
 
-    const auto& uidProcessStats = getUidProcessStats(*processStats);
-
+    const auto& uidProcessStats = getUidProcessStats(*processStats, mTopNStatsPerSubcategory);
     std::unordered_set<uint32_t> unmappedUids;
     // Fetch only the top N I/O blocked UIDs and UIDs with most major page faults.
     UidProcessStats temp = {};
     std::vector<const UidProcessStats*> topNIoBlockedUids(mTopNStatsPerCategory, &temp);
-    std::vector<const UidProcessStats*> topNMajorFaults(mTopNStatsPerCategory, &temp);
+    std::vector<const UidProcessStats*> topNMajorFaultUids(mTopNStatsPerCategory, &temp);
     processIoPerfData->totalMajorFaults = 0;
-    for (const auto& it : uidProcessStats) {
+    for (const auto& it : *uidProcessStats) {
         const UidProcessStats& curStats = it.second;
         if (mUidToPackageNameMapping.find(curStats.uid) == mUidToPackageNameMapping.end()) {
             unmappedUids.insert(curStats.uid);
@@ -777,21 +831,19 @@
         processIoPerfData->totalMajorFaults += curStats.majorFaults;
         for (auto it = topNIoBlockedUids.begin(); it != topNIoBlockedUids.end(); ++it) {
             const UidProcessStats* topStats = *it;
-            if (topStats->ioBlockedTasksCnt > curStats.ioBlockedTasksCnt) {
-                continue;
+            if (topStats->ioBlockedTasksCnt < curStats.ioBlockedTasksCnt) {
+                topNIoBlockedUids.erase(topNIoBlockedUids.end() - 1);
+                topNIoBlockedUids.emplace(it, &curStats);
+                break;
             }
-            topNIoBlockedUids.erase(topNIoBlockedUids.end() - 1);
-            topNIoBlockedUids.emplace(it, &curStats);
-            break;
         }
-        for (auto it = topNMajorFaults.begin(); it != topNMajorFaults.end(); ++it) {
+        for (auto it = topNMajorFaultUids.begin(); it != topNMajorFaultUids.end(); ++it) {
             const UidProcessStats* topStats = *it;
-            if (topStats->majorFaults > curStats.majorFaults) {
-                continue;
+            if (topStats->majorFaults < curStats.majorFaults) {
+                topNMajorFaultUids.erase(topNMajorFaultUids.end() - 1);
+                topNMajorFaultUids.emplace(it, &curStats);
+                break;
             }
-            topNMajorFaults.erase(topNMajorFaults.end() - 1);
-            topNMajorFaults.emplace(it, &curStats);
-            break;
         }
     }
 
@@ -807,7 +859,7 @@
             // processes is < |ro.carwatchdog.top_n_stats_per_category|.
             break;
         }
-        ProcessIoPerfData::Stats stats = {
+        ProcessIoPerfData::UidStats stats = {
                 .userId = multiuser_get_user_id(it->uid),
                 .packageName = std::to_string(it->uid),
                 .count = it->ioBlockedTasksCnt,
@@ -815,16 +867,23 @@
         if (mUidToPackageNameMapping.find(it->uid) != mUidToPackageNameMapping.end()) {
             stats.packageName = mUidToPackageNameMapping[it->uid];
         }
+        for (const auto& pIt : it->topNIoBlockedProcesses) {
+            if (pIt.count == 0) {
+                break;
+            }
+            stats.topNProcesses.emplace_back(
+                    ProcessIoPerfData::UidStats::ProcessStats{pIt.comm, pIt.count});
+        }
         processIoPerfData->topNIoBlockedUids.emplace_back(stats);
         processIoPerfData->topNIoBlockedUidsTotalTaskCnt.emplace_back(it->totalTasksCnt);
     }
-    for (const auto& it : topNMajorFaults) {
+    for (const auto& it : topNMajorFaultUids) {
         if (it->majorFaults == 0) {
             // End of non-zero elements. This case occurs when the number of UIDs with major faults
             // is < |ro.carwatchdog.top_n_stats_per_category|.
             break;
         }
-        ProcessIoPerfData::Stats stats = {
+        ProcessIoPerfData::UidStats stats = {
                 .userId = multiuser_get_user_id(it->uid),
                 .packageName = std::to_string(it->uid),
                 .count = it->majorFaults,
@@ -832,7 +891,14 @@
         if (mUidToPackageNameMapping.find(it->uid) != mUidToPackageNameMapping.end()) {
             stats.packageName = mUidToPackageNameMapping[it->uid];
         }
-        processIoPerfData->topNMajorFaults.emplace_back(stats);
+        for (const auto& pIt : it->topNMajorFaultProcesses) {
+            if (pIt.count == 0) {
+                break;
+            }
+            stats.topNProcesses.emplace_back(
+                    ProcessIoPerfData::UidStats::ProcessStats{pIt.comm, pIt.count});
+        }
+        processIoPerfData->topNMajorFaultUids.emplace_back(stats);
     }
     if (mLastMajorFaults == 0) {
         processIoPerfData->majorFaultsPercentChange = 0;
diff --git a/watchdog/server/src/IoPerfCollection.h b/watchdog/server/src/IoPerfCollection.h
index dc88114..4fef328 100644
--- a/watchdog/server/src/IoPerfCollection.h
+++ b/watchdog/server/src/IoPerfCollection.h
@@ -77,15 +77,20 @@
 
 // Performance data collected from the `/proc/[pid]/stat` and `/proc/[pid]/task/[tid]/stat` files.
 struct ProcessIoPerfData {
-    struct Stats {
+    struct UidStats {
         userid_t userId = 0;
         std::string packageName;
         uint64_t count = 0;
+        struct ProcessStats {
+            std::string comm = "";
+            uint64_t count = 0;
+        };
+        std::vector<ProcessStats> topNProcesses = {};
     };
-    std::vector<Stats> topNIoBlockedUids = {};
+    std::vector<UidStats> topNIoBlockedUids = {};
     // Total # of tasks owned by each UID in |topNIoBlockedUids|.
     std::vector<uint64_t> topNIoBlockedUidsTotalTaskCnt = {};
-    std::vector<Stats> topNMajorFaults = {};
+    std::vector<UidStats> topNMajorFaultUids = {};
     uint64_t totalMajorFaults = 0;
     // Percentage of increase/decrease in the major page faults since last collection.
     double majorFaultsPercentChange = 0.0;
diff --git a/watchdog/server/src/WatchdogProcessService.cpp b/watchdog/server/src/WatchdogProcessService.cpp
index 20f8fdf..749ed30 100644
--- a/watchdog/server/src/WatchdogProcessService.cpp
+++ b/watchdog/server/src/WatchdogProcessService.cpp
@@ -464,27 +464,32 @@
 
 Result<void> WatchdogProcessService::dumpAndKillClientsIfNotResponding(TimeoutLength timeout) {
     std::vector<int32_t> processIds;
+    std::vector<sp<ICarWatchdogClient>> clientsToNotify;
     {
         Mutex::Autolock lock(mMutex);
         PingedClientMap& clients = mPingedClients[timeout];
         for (PingedClientMap::const_iterator it = clients.cbegin(); it != clients.cend(); it++) {
             pid_t pid = -1;
             userid_t userId = -1;
-            sp<IBinder> binder = BnCarWatchdog::asBinder(it->second.client);
+            sp<ICarWatchdogClient> client = it->second.client;
+            sp<IBinder> binder = BnCarWatchdog::asBinder(client);
             std::vector<TimeoutLength> timeouts = {timeout};
-            // Unhealthy clients are eventually removed from the list through binderDied when they
-            // are killed.
             findClientAndProcessLocked(timeouts, binder,
-                                       [&](std::vector<ClientInfo>& /*clients*/,
+                                       [&](std::vector<ClientInfo>& clients,
                                            std::vector<ClientInfo>::const_iterator it) {
                                            pid = (*it).pid;
                                            userId = (*it).userId;
+                                           clients.erase(it);
                                        });
             if (pid != -1 && mStoppedUserId.count(userId) == 0) {
+                clientsToNotify.push_back(client);
                 processIds.push_back(pid);
             }
         }
     }
+    for (auto&& client : clientsToNotify) {
+        client->prepareProcessTermination();
+    }
     return dumpAndKillAllProcesses(processIds);
 }
 
diff --git a/watchdog/server/tests/IoPerfCollectionTest.cpp b/watchdog/server/tests/IoPerfCollectionTest.cpp
index fde6f2c..898447b 100644
--- a/watchdog/server/tests/IoPerfCollectionTest.cpp
+++ b/watchdog/server/tests/IoPerfCollectionTest.cpp
@@ -129,7 +129,9 @@
         }
         return isEqual;
     };
-    return std::equal(lhs.topNReads.begin(), lhs.topNReads.end(), rhs.topNReads.begin(), comp) &&
+    return lhs.topNReads.size() == rhs.topNReads.size() &&
+            std::equal(lhs.topNReads.begin(), lhs.topNReads.end(), rhs.topNReads.begin(), comp) &&
+            lhs.topNWrites.size() == rhs.topNWrites.size() &&
             std::equal(lhs.topNWrites.begin(), lhs.topNWrites.end(), rhs.topNWrites.begin(), comp);
 }
 
@@ -141,21 +143,32 @@
 
 bool isEqual(const ProcessIoPerfData& lhs, const ProcessIoPerfData& rhs) {
     if (lhs.topNIoBlockedUids.size() != rhs.topNIoBlockedUids.size() ||
-        lhs.topNMajorFaults.size() != rhs.topNMajorFaults.size() ||
+        lhs.topNMajorFaultUids.size() != rhs.topNMajorFaultUids.size() ||
         lhs.totalMajorFaults != rhs.totalMajorFaults ||
         lhs.majorFaultsPercentChange != rhs.majorFaultsPercentChange) {
         return false;
     }
-    auto comp = [&](const ProcessIoPerfData::Stats& l, const ProcessIoPerfData::Stats& r) -> bool {
-        return l.userId == r.userId && l.packageName == r.packageName && l.count == r.count;
+    auto comp = [&](const ProcessIoPerfData::UidStats& l,
+                    const ProcessIoPerfData::UidStats& r) -> bool {
+        auto comp = [&](const ProcessIoPerfData::UidStats::ProcessStats& l,
+                        const ProcessIoPerfData::UidStats::ProcessStats& r) -> bool {
+            return l.comm == r.comm && l.count == r.count;
+        };
+        return l.userId == r.userId && l.packageName == r.packageName && l.count == r.count &&
+                l.topNProcesses.size() == r.topNProcesses.size() &&
+                std::equal(l.topNProcesses.begin(), l.topNProcesses.end(), r.topNProcesses.begin(),
+                           comp);
     };
-    return std::equal(lhs.topNIoBlockedUids.begin(), lhs.topNIoBlockedUids.end(),
-                      rhs.topNIoBlockedUids.begin(), comp) &&
+    return lhs.topNIoBlockedUids.size() == lhs.topNIoBlockedUids.size() &&
+            std::equal(lhs.topNIoBlockedUids.begin(), lhs.topNIoBlockedUids.end(),
+                       rhs.topNIoBlockedUids.begin(), comp) &&
+            lhs.topNIoBlockedUidsTotalTaskCnt.size() == rhs.topNIoBlockedUidsTotalTaskCnt.size() &&
             std::equal(lhs.topNIoBlockedUidsTotalTaskCnt.begin(),
                        lhs.topNIoBlockedUidsTotalTaskCnt.end(),
                        rhs.topNIoBlockedUidsTotalTaskCnt.begin()) &&
-            std::equal(lhs.topNMajorFaults.begin(), lhs.topNMajorFaults.end(),
-                       rhs.topNMajorFaults.begin(), comp);
+            lhs.topNMajorFaultUids.size() == rhs.topNMajorFaultUids.size() &&
+            std::equal(lhs.topNMajorFaultUids.begin(), lhs.topNMajorFaultUids.end(),
+                       rhs.topNMajorFaultUids.begin(), comp);
 }
 
 bool isEqual(const IoPerfRecord& lhs, const IoPerfRecord& rhs) {
@@ -176,6 +189,9 @@
     ASSERT_TRUE(sysprop::topNStatsPerCategory().has_value());
     ASSERT_EQ(collector->mTopNStatsPerCategory, sysprop::topNStatsPerCategory().value());
 
+    ASSERT_TRUE(sysprop::topNStatsPerSubcategory().has_value());
+    ASSERT_EQ(collector->mTopNStatsPerSubcategory, sysprop::topNStatsPerSubcategory().value());
+
     ASSERT_TRUE(sysprop::boottimeCollectionInterval().has_value());
     ASSERT_EQ(std::chrono::duration_cast<std::chrono::seconds>(
                       collector->mBoottimeCollection.interval)
@@ -253,9 +269,9 @@
                                  .totalCpuTime = 26900,
                                  .ioBlockedProcessesCnt = 5,
                                  .totalProcessesCnt = 22},
-            .processIoPerfData = {.topNIoBlockedUids = {{0, "mount", 1}},
+            .processIoPerfData = {.topNIoBlockedUids = {{0, "mount", 1, {{"disk I/O", 1}}}},
                                   .topNIoBlockedUidsTotalTaskCnt = {1},
-                                  .topNMajorFaults = {{0, "mount", 5000}},
+                                  .topNMajorFaultUids = {{0, "mount", 5000, {{"disk I/O", 5000}}}},
                                   .totalMajorFaults = 5000,
                                   .majorFaultsPercentChange = 0.0},
     };
@@ -312,11 +328,12 @@
                                  .totalCpuTime = 19800,
                                  .ioBlockedProcessesCnt = 6,
                                  .totalProcessesCnt = 14},
-            .processIoPerfData = {.topNIoBlockedUids = {{0, "mount", 2}},
-                                  .topNIoBlockedUidsTotalTaskCnt = {2},
-                                  .topNMajorFaults = {{0, "mount", 11000}},
-                                  .totalMajorFaults = 11000,
-                                  .majorFaultsPercentChange = ((11000.0 - 5000.0) / 5000.0) * 100},
+            .processIoPerfData =
+                    {.topNIoBlockedUids = {{0, "mount", 2, {{"disk I/O", 2}}}},
+                     .topNIoBlockedUidsTotalTaskCnt = {2},
+                     .topNMajorFaultUids = {{0, "mount", 11000, {{"disk I/O", 11000}}}},
+                     .totalMajorFaults = 11000,
+                     .majorFaultsPercentChange = ((11000.0 - 5000.0) / 5000.0) * 100},
     };
     ret = looperStub->pollCache();
     ASSERT_TRUE(ret) << ret.error().message();
@@ -374,9 +391,9 @@
                                  .totalCpuTime = 21400,
                                  .ioBlockedProcessesCnt = 8,
                                  .totalProcessesCnt = 18},
-            .processIoPerfData = {.topNIoBlockedUids = {{0, "mount", 2}},
+            .processIoPerfData = {.topNIoBlockedUids = {{0, "mount", 2, {{"disk I/O", 2}}}},
                                   .topNIoBlockedUidsTotalTaskCnt = {2},
-                                  .topNMajorFaults = {{0, "mount", 5000}},
+                                  .topNMajorFaultUids = {{0, "mount", 5000, {{"disk I/O", 5000}}}},
                                   .totalMajorFaults = 5000,
                                   .majorFaultsPercentChange = ((5000.0 - 11000.0) / 11000.0) * 100},
     };
@@ -448,9 +465,9 @@
                                  .totalCpuTime = 4276,
                                  .ioBlockedProcessesCnt = 3,
                                  .totalProcessesCnt = 15},
-            .processIoPerfData = {.topNIoBlockedUids = {{0, "mount", 1}},
+            .processIoPerfData = {.topNIoBlockedUids = {{0, "mount", 1, {{"disk I/O", 1}}}},
                                   .topNIoBlockedUidsTotalTaskCnt = {2},
-                                  .topNMajorFaults = {{0, "mount", 4100}},
+                                  .topNMajorFaultUids = {{0, "mount", 4100, {{"disk I/O", 4100}}}},
                                   .totalMajorFaults = 4100,
                                   .majorFaultsPercentChange = ((4100.0 - 5000.0) / 5000.0) * 100},
     };
@@ -508,11 +525,12 @@
                                  .totalCpuTime = 43576,
                                  .ioBlockedProcessesCnt = 4,
                                  .totalProcessesCnt = 6},
-            .processIoPerfData = {.topNIoBlockedUids = {{0, "mount", 2}},
-                                  .topNIoBlockedUidsTotalTaskCnt = {2},
-                                  .topNMajorFaults = {{0, "mount", 44300}},
-                                  .totalMajorFaults = 44300,
-                                  .majorFaultsPercentChange = ((44300.0 - 4100.0) / 4100.0) * 100},
+            .processIoPerfData =
+                    {.topNIoBlockedUids = {{0, "mount", 2, {{"disk I/O", 2}}}},
+                     .topNIoBlockedUidsTotalTaskCnt = {2},
+                     .topNMajorFaultUids = {{0, "mount", 44300, {{"disk I/O", 44300}}}},
+                     .totalMajorFaults = 44300,
+                     .majorFaultsPercentChange = ((44300.0 - 4100.0) / 4100.0) * 100},
     };
     ret = looperStub->pollCache();
     ASSERT_TRUE(ret) << ret.error().message();
@@ -587,12 +605,12 @@
                                  .totalCpuTime = 47576,
                                  .ioBlockedProcessesCnt = 13,
                                  .totalProcessesCnt = 213},
-            .processIoPerfData = {.topNIoBlockedUids = {{0, "mount", 2}},
-                                  .topNIoBlockedUidsTotalTaskCnt = {2},
-                                  .topNMajorFaults = {{0, "mount", 49800}},
-                                  .totalMajorFaults = 49800,
-                                  .majorFaultsPercentChange =
-                                          ((49800.0 - 44300.0) / 44300.0) * 100},
+            .processIoPerfData =
+                    {.topNIoBlockedUids = {{0, "mount", 2, {{"disk I/O", 2}}}},
+                     .topNIoBlockedUidsTotalTaskCnt = {2},
+                     .topNMajorFaultUids = {{0, "mount", 49800, {{"disk I/O", 49800}}}},
+                     .totalMajorFaults = 49800,
+                     .majorFaultsPercentChange = ((49800.0 - 44300.0) / 44300.0) * 100},
     };
     ret = looperStub->pollCache();
     ASSERT_TRUE(ret) << ret.error().message();
@@ -646,12 +664,12 @@
                                  .totalCpuTime = 48376,
                                  .ioBlockedProcessesCnt = 57,
                                  .totalProcessesCnt = 157},
-            .processIoPerfData = {.topNIoBlockedUids = {{0, "mount", 2}},
-                                  .topNIoBlockedUidsTotalTaskCnt = {2},
-                                  .topNMajorFaults = {{0, "mount", 50900}},
-                                  .totalMajorFaults = 50900,
-                                  .majorFaultsPercentChange =
-                                          ((50900.0 - 49800.0) / 49800.0) * 100},
+            .processIoPerfData =
+                    {.topNIoBlockedUids = {{0, "mount", 2, {{"disk I/O", 2}}}},
+                     .topNIoBlockedUidsTotalTaskCnt = {2},
+                     .topNMajorFaultUids = {{0, "mount", 50900, {{"disk I/O", 50900}}}},
+                     .totalMajorFaults = 50900,
+                     .majorFaultsPercentChange = ((50900.0 - 49800.0) / 49800.0) * 100},
     };
     ret = looperStub->pollCache();
     ASSERT_TRUE(ret) << ret.error().message();
@@ -729,9 +747,9 @@
                                  .totalCpuTime = 20676,
                                  .ioBlockedProcessesCnt = 1,
                                  .totalProcessesCnt = 4},
-            .processIoPerfData = {.topNIoBlockedUids = {{0, "mount", 2}},
+            .processIoPerfData = {.topNIoBlockedUids = {{0, "mount", 2, {{"disk I/O", 2}}}},
                                   .topNIoBlockedUidsTotalTaskCnt = {2},
-                                  .topNMajorFaults = {{0, "mount", 5701}},
+                                  .topNMajorFaultUids = {{0, "mount", 5701, {{"disk I/O", 5701}}}},
                                   .totalMajorFaults = 5701,
                                   .majorFaultsPercentChange = ((5701.0 - 50900.0) / 50900.0) * 100},
     };
@@ -1153,6 +1171,7 @@
             .userId = 10,
             .packageName = "shared:android.uid.system",
             .count = 4,
+            .topNProcesses = {{"logd", 3}, {"system_server", 1}},
     });
     expectedProcessIoPerfData.topNIoBlockedUidsTotalTaskCnt.push_back(6);
     expectedProcessIoPerfData.topNIoBlockedUids.push_back({
@@ -1160,19 +1179,22 @@
             .userId = 0,
             .packageName = "mount",
             .count = 3,
+            .topNProcesses = {{"disk I/O", 3}},
     });
     expectedProcessIoPerfData.topNIoBlockedUidsTotalTaskCnt.push_back(3);
-    expectedProcessIoPerfData.topNMajorFaults.push_back({
+    expectedProcessIoPerfData.topNMajorFaultUids.push_back({
             // uid: 1001234
             .userId = 10,
             .packageName = "1001234",
             .count = 89765,
+            .topNProcesses = {{"tombstoned", 89765}},
     });
-    expectedProcessIoPerfData.topNMajorFaults.push_back({
+    expectedProcessIoPerfData.topNMajorFaultUids.push_back({
             // uid: 1009
             .userId = 0,
             .packageName = "mount",
             .count = 45678,
+            .topNProcesses = {{"disk I/O", 45678}},
     });
     expectedProcessIoPerfData.totalMajorFaults = 156663;
     expectedProcessIoPerfData.majorFaultsPercentChange = 0;
@@ -1185,6 +1207,7 @@
     IoPerfCollection collector;
     collector.mProcPidStat = new ProcPidStat(firstSnapshot.path);
     collector.mTopNStatsPerCategory = 2;
+    collector.mTopNStatsPerSubcategory = 2;
     ASSERT_TRUE(collector.mProcPidStat->enabled())
             << "Files under the temporary proc directory are inaccessible";
 
@@ -1221,19 +1244,22 @@
             .userId = 10,
             .packageName = "shared:android.uid.system",
             .count = 1,
+            .topNProcesses = {{"system_server", 1}},
     });
     expectedProcessIoPerfData.topNIoBlockedUidsTotalTaskCnt.push_back(3);
-    expectedProcessIoPerfData.topNMajorFaults.push_back({
+    expectedProcessIoPerfData.topNMajorFaultUids.push_back({
             // uid: 1001000
             .userId = 10,
             .packageName = "shared:android.uid.system",
             .count = 12000,
+            .topNProcesses = {{"system_server", 12000}},
     });
-    expectedProcessIoPerfData.topNMajorFaults.push_back({
+    expectedProcessIoPerfData.topNMajorFaultUids.push_back({
             // uid: 0
             .userId = 0,
             .packageName = "root",
             .count = 660,
+            .topNProcesses = {{"init", 660}},
     });
     expectedProcessIoPerfData.totalMajorFaults = 12660;
     expectedProcessIoPerfData.majorFaultsPercentChange = ((12660.0 - 156663.0) / 156663.0) * 100;
@@ -1269,11 +1295,12 @@
             {453, "453 (init) S 0 0 0 0 0 0 0 0 80 0 0 0 0 0 0 0 2 0 275\n"},
     };
     struct ProcessIoPerfData expectedProcessIoPerfData = {};
-    expectedProcessIoPerfData.topNMajorFaults.push_back({
+    expectedProcessIoPerfData.topNMajorFaultUids.push_back({
             // uid: 0
             .userId = 0,
             .packageName = "root",
             .count = 880,
+            .topNProcesses = {{"init", 880}},
     });
     expectedProcessIoPerfData.totalMajorFaults = 880;
     expectedProcessIoPerfData.majorFaultsPercentChange = 0.0;
@@ -1285,6 +1312,7 @@
 
     IoPerfCollection collector;
     collector.mTopNStatsPerCategory = 5;
+    collector.mTopNStatsPerSubcategory = 3;
     collector.mProcPidStat = new ProcPidStat(prodDir.path);
     struct ProcessIoPerfData actualProcessIoPerfData = {};
     ret = collector.collectProcessIoPerfDataLocked(&actualProcessIoPerfData);
diff --git a/watchdog/server/tests/WatchdogBinderMediatorTest.cpp b/watchdog/server/tests/WatchdogBinderMediatorTest.cpp
index 2342c27..0d1cf05 100644
--- a/watchdog/server/tests/WatchdogBinderMediatorTest.cpp
+++ b/watchdog/server/tests/WatchdogBinderMediatorTest.cpp
@@ -75,6 +75,7 @@
 class MockICarWatchdogClient : public ICarWatchdogClient {
 public:
     MOCK_METHOD(Status, checkIfAlive, (int32_t sessionId, TimeoutLength timeout), (override));
+    MOCK_METHOD(Status, prepareProcessTermination, (), (override));
     MOCK_METHOD(IBinder*, onAsBinder, (), (override));
     MOCK_METHOD(int32_t, getInterfaceVersion, (), (override));
     MOCK_METHOD(std::string, getInterfaceHash, (), (override));
diff --git a/watchdog/testclient/src/WatchdogClient.cpp b/watchdog/testclient/src/WatchdogClient.cpp
index 41a2b26..4c381c6 100644
--- a/watchdog/testclient/src/WatchdogClient.cpp
+++ b/watchdog/testclient/src/WatchdogClient.cpp
@@ -59,6 +59,11 @@
     return ndk::ScopedAStatus::ok();
 }
 
+ndk::ScopedAStatus WatchdogClient::prepareProcessTermination() {
+    ALOGI("This process is being terminated by car watchdog");
+    return ndk::ScopedAStatus::ok();
+}
+
 bool WatchdogClient::initialize(const CommandParam& param) {
     ndk::SpAIBinder binder(
             AServiceManager_getService("android.automotive.watchdog.ICarWatchdog/default"));
diff --git a/watchdog/testclient/src/WatchdogClient.h b/watchdog/testclient/src/WatchdogClient.h
index 95f11ca..3174f98 100644
--- a/watchdog/testclient/src/WatchdogClient.h
+++ b/watchdog/testclient/src/WatchdogClient.h
@@ -51,6 +51,7 @@
     explicit WatchdogClient(const ::android::sp<::android::Looper>& handlerLooper);
 
     ndk::ScopedAStatus checkIfAlive(int32_t sessionId, TimeoutLength timeout) override;
+    ndk::ScopedAStatus prepareProcessTermination() override;
 
     bool initialize(const CommandParam& param);
     void finalize();