Add watchdog library

- Car watchdog daemon is accessed from multiple classes.
- To facilitate the implementation, a helper class is introduced.
- The helper class will be used by CarServiceHelperService to dump and
kill processes which are not responding.

Bug: 148892423
Test: atest CarWatchdogDaemonHelperTest
Change-Id: I9fa5a8695941533e0c55c6142880ddb74ee87c9d
diff --git a/service/Android.bp b/service/Android.bp
index 06d120b..ab6e4ce 100644
--- a/service/Android.bp
+++ b/service/Android.bp
@@ -31,6 +31,7 @@
 
 common_lib_deps = [
     "android.car.userlib",
+    "android.car.watchdoglib",
     "android.hidl.base-V1.0-java",
     "android.hardware.automotive.audiocontrol-V1.0-java",
     "android.hardware.automotive.vehicle-V2.0-java",
diff --git a/service/src/com/android/car/watchdog/CarWatchdogService.java b/service/src/com/android/car/watchdog/CarWatchdogService.java
index fd3a2b2..a6db100 100644
--- a/service/src/com/android/car/watchdog/CarWatchdogService.java
+++ b/service/src/com/android/car/watchdog/CarWatchdogService.java
@@ -18,22 +18,18 @@
 
 import static com.android.car.CarLog.TAG_WATCHDOG;
 
-import android.annotation.Nullable;
-import android.automotive.watchdog.ICarWatchdog;
 import android.automotive.watchdog.ICarWatchdogClient;
 import android.car.watchdog.ICarWatchdogService;
+import android.car.watchdoglib.CarWatchdogDaemonHelper;
 import android.content.Context;
 import android.os.Handler;
-import android.os.IBinder;
 import android.os.Looper;
 import android.os.RemoteException;
-import android.os.ServiceManager;
 import android.util.Log;
 
 import androidx.annotation.VisibleForTesting;
 
 import com.android.car.CarServiceBase;
-import com.android.internal.annotations.GuardedBy;
 
 import java.io.PrintWriter;
 import java.lang.ref.WeakReference;
@@ -46,81 +42,40 @@
  */
 public final class CarWatchdogService extends ICarWatchdogService.Stub implements CarServiceBase {
 
-    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;
-    private static final String CAR_WATCHDOG_DAEMON_INTERFACE =
-            "android.automotive.watchdog.ICarWatchdog/default";
-
     private final Context mContext;
     private final ICarWatchdogClientImpl mWatchdogClient;
-    private final Object mLock = new Object();
     private final Handler mMainHandler = new Handler(Looper.getMainLooper());
-    @GuardedBy("mLock")
-    private @Nullable ICarWatchdog mCarWatchdogDaemon;
-
-    private final DeathRecipient mDeathRecipient = new DeathRecipient() {
-        @Override
-        public void binderDied() {
-            Log.w(TAG_WATCHDOG, "Car watchdog daemon died: reconnecting");
-            unlinkToDeath();
-            synchronized (mLock) {
-                mCarWatchdogDaemon = null;
-            }
-            mMainHandler.postDelayed(() -> {
-                connectToDaemon(CAR_WATCHDOG_DAEMON_BIND_MAX_RETRY);
-            }, CAR_WATCHDOG_DAEMON_BIND_RETRY_INTERVAL_MS);
-        }
-    };
-
-    public CarWatchdogService(Context context) {
-        // Car watchdog daemon is found at init().
-        this(context, /* daemon= */ null);
-    }
+    private CarWatchdogDaemonHelper mCarWatchdogDaemonHelper;
+    private final CarWatchdogDaemonHelper.OnConnectionChangeListener mConnectionListener =
+            (connected) -> {
+                if (connected) {
+                    registerToDaemon();
+                }
+            };
 
     @VisibleForTesting
-    public CarWatchdogService(Context context, @Nullable ICarWatchdog daemon) {
+    public CarWatchdogService(Context context) {
         mContext = context;
-        // For testing, we use the given car watchdog daemon.
-        mCarWatchdogDaemon = daemon;
         mWatchdogClient = new ICarWatchdogClientImpl(this);
+        mCarWatchdogDaemonHelper = new CarWatchdogDaemonHelper();
     }
 
     @Override
     public void init() {
-        ICarWatchdog daemon;
-        synchronized (mLock) {
-            daemon = mCarWatchdogDaemon;
-        }
-        if (daemon == null) {
-            connectToDaemon(CAR_WATCHDOG_DAEMON_BIND_MAX_RETRY);
-        } else {
-            linkToDeath();
-            registerToDaemon(daemon);
-        }
+        mCarWatchdogDaemonHelper.addOnConnectionChangeListener(mConnectionListener);
+        mCarWatchdogDaemonHelper.connect();
     }
 
     @Override
     public void release() {
-        unlinkToDeath();
-        ICarWatchdog daemon;
-        synchronized (mLock) {
-            daemon = mCarWatchdogDaemon;
-            mCarWatchdogDaemon = null;
-        }
-        try {
-            daemon.unregisterMediator(mWatchdogClient);
-        } catch (RemoteException e) {
-            Log.w(TAG_WATCHDOG, "Cannot unregister from car watchdog daemon: " + e);
-        }
+        unregisterFromDaemon();
+        mCarWatchdogDaemonHelper.disconnect();
     }
 
     @Override
     public void dump(PrintWriter writer) {
         writer.println("*CarWatchdogService*");
-        synchronized (mLock) {
-            writer.printf("bound to car watchddog daemon: %b\n", mCarWatchdogDaemon != null);
-        }
+        // TODO(b/145556670): implement body.
     }
 
     @Override
@@ -138,113 +93,36 @@
         // TODO(b/145556670): implement body.
     }
 
-    private void connectToDaemon(int retryCount) {
-        if (retryCount <= 0) {
-            Log.e(TAG_WATCHDOG, "Cannot reconnect to car watchdog daemon after retrying "
-                    + CAR_WATCHDOG_DAEMON_BIND_MAX_RETRY + " times");
-            return;
-        }
-        if (makeBinderConnection()) {
-            Log.i(TAG_WATCHDOG, "Connected to car watchdog daemon");
-            return;
-        }
-        mMainHandler.postDelayed(() -> {
-            connectToDaemon(retryCount - 1);
-        }, CAR_WATCHDOG_DAEMON_BIND_RETRY_INTERVAL_MS);
-    }
-
-    private boolean makeBinderConnection() {
-        long currentTimeMs = System.currentTimeMillis();
-        IBinder binder = ServiceManager.getService(CAR_WATCHDOG_DAEMON_INTERFACE);
-        if (binder == null) {
-            Log.w(TAG_WATCHDOG, "Getting car watchdog daemon binder failed");
-            return false;
-        }
-        long elapsedTimeMs = System.currentTimeMillis() - currentTimeMs;
-        if (elapsedTimeMs > CAR_WATCHDOG_DAEMON_FIND_MARGINAL_TIME_MS) {
-            Log.wtf(TAG_WATCHDOG,
-                    "Finding car watchdog daemon took too long(" + elapsedTimeMs + "ms)");
-        }
-
-        ICarWatchdog daemon = ICarWatchdog.Stub.asInterface(binder);
-        if (daemon == null) {
-            Log.w(TAG_WATCHDOG, "Getting car watchdog daemon interface failed");
-            return false;
-        }
-        synchronized (mLock) {
-            mCarWatchdogDaemon = daemon;
-        }
-        linkToDeath();
-        registerToDaemon(daemon);
-        return true;
-    }
-
-    private void registerToDaemon(ICarWatchdog daemon) {
+    private void registerToDaemon() {
         try {
-            daemon.registerMediator(mWatchdogClient);
-        } catch (RemoteException e) {
-            // Nothing that we can do further.
+            mCarWatchdogDaemonHelper.registerMediator(mWatchdogClient);
+        } catch (RemoteException | IllegalArgumentException | IllegalStateException e) {
             Log.w(TAG_WATCHDOG, "Cannot register to car watchdog daemon: " + e);
-        } catch (IllegalArgumentException e) {
-            // Do nothing.
-            Log.w(TAG_WATCHDOG, "Already registered as mediator: " + e);
+        }
+    }
+
+    private void unregisterFromDaemon() {
+        try {
+            mCarWatchdogDaemonHelper.unregisterMediator(mWatchdogClient);
+        } catch (RemoteException | IllegalArgumentException | IllegalStateException e) {
+            Log.w(TAG_WATCHDOG, "Cannot unregister from car watchdog daemon: " + e);
         }
     }
 
     private void doHealthCheck(int sessionId) {
         mMainHandler.post(() -> {
-            ICarWatchdog daemon;
-            synchronized (mLock) {
-                if (mCarWatchdogDaemon == null) {
-                    return;
-                }
-                daemon = mCarWatchdogDaemon;
-            }
             try {
                 // TODO(b/145556670): Check clients status and include them in the response.
                 int[] clientsNotResponding = new int[0];
-                daemon.tellMediatorAlive(mWatchdogClient, clientsNotResponding, sessionId);
-            } catch (RemoteException e) {
+                mCarWatchdogDaemonHelper.tellMediatorAlive(mWatchdogClient, clientsNotResponding,
+                        sessionId);
+            } catch (RemoteException | IllegalArgumentException | IllegalStateException e) {
                 Log.w(TAG_WATCHDOG, "Cannot respond to car watchdog daemon (sessionId="
                         + sessionId + "): " + e);
             }
         });
     }
 
-    private void linkToDeath() {
-        IBinder binder;
-        synchronized (mLock) {
-            if (mCarWatchdogDaemon == null) {
-                return;
-            }
-            binder = mCarWatchdogDaemon.asBinder();
-        }
-        if (binder == null) {
-            Log.w(TAG_WATCHDOG, "Linking to binder death recipient skipped");
-            return;
-        }
-        try {
-            binder.linkToDeath(mDeathRecipient, 0);
-        } catch (RemoteException e) {
-            Log.w(TAG_WATCHDOG, "Linking to binder death recipient failed: " + e);
-        }
-    }
-
-    private void unlinkToDeath() {
-        IBinder binder;
-        synchronized (mLock) {
-            if (mCarWatchdogDaemon == null) {
-                return;
-            }
-            binder = mCarWatchdogDaemon.asBinder();
-        }
-        if (binder == null) {
-            Log.w(TAG_WATCHDOG, "Unlinking from binder death recipient skipped");
-            return;
-        }
-        binder.unlinkToDeath(mDeathRecipient, 0);
-    }
-
     private static final class ICarWatchdogClientImpl extends ICarWatchdogClient.Stub {
         private final WeakReference<CarWatchdogService> mService;
 
diff --git a/tests/carservice_test/Android.mk b/tests/carservice_test/Android.mk
index 10884f9..3625a1f 100644
--- a/tests/carservice_test/Android.mk
+++ b/tests/carservice_test/Android.mk
@@ -41,6 +41,7 @@
 LOCAL_STATIC_JAVA_LIBRARIES := junit
 # testng imported to use assertThrows, we can remove it once it's ported to JUnit's.
 LOCAL_STATIC_JAVA_LIBRARIES += \
+    android.car.watchdoglib \
     androidx.test.ext.junit \
     androidx.test.rules \
     android.hardware.automotive.vehicle-V2.0-java \
diff --git a/tests/carservice_test/src/com/android/car/ICarImplTest.java b/tests/carservice_test/src/com/android/car/ICarImplTest.java
index 46e30e6..d7c2ad1 100644
--- a/tests/carservice_test/src/com/android/car/ICarImplTest.java
+++ b/tests/carservice_test/src/com/android/car/ICarImplTest.java
@@ -27,7 +27,6 @@
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.spy;
 
-import android.automotive.watchdog.ICarWatchdog;
 import android.car.Car;
 import android.content.Context;
 import android.content.res.Resources;
@@ -88,6 +87,7 @@
     @Mock private SystemStateInterface mMockSystemStateInterface;
     @Mock private TimeInterface mMockTimeInterface;
     @Mock private WakeLockInterface mMockWakeLockInterface;
+    @Mock private CarWatchdogService mCarWatchdogService;
 
     private Context mContext;
     private MockitoSession mSession;
@@ -172,7 +172,7 @@
 
         ICarImpl carImpl = new ICarImpl(mContext, mMockVehicle, mFakeSystemInterface,
                 /* errorNotifier= */ null, "MockedCar", /* carUserService= */ null,
-                new CarWatchdogService(mContext, new ICarWatchdog.Default()));
+                mCarWatchdogService);
         carImpl.init();
         Car mCar = new Car(mContext, carImpl, /* handler= */ null);
 
diff --git a/tests/carservice_test/src/com/android/car/MockedCarTestBase.java b/tests/carservice_test/src/com/android/car/MockedCarTestBase.java
index 9664f07..99f5708 100644
--- a/tests/carservice_test/src/com/android/car/MockedCarTestBase.java
+++ b/tests/carservice_test/src/com/android/car/MockedCarTestBase.java
@@ -22,7 +22,6 @@
 import static org.mockito.Mockito.mock;
 
 import android.annotation.NonNull;
-import android.automotive.watchdog.ICarWatchdog;
 import android.car.Car;
 import android.car.test.CarTestManager;
 import android.car.test.CarTestManagerBinderWrapper;
@@ -103,8 +102,7 @@
     private final Map<VehiclePropConfigBuilder, VehicleHalPropertyHandler> mHalConfig =
             new HashMap<>();
     private final SparseArray<VehiclePropConfigBuilder> mPropToConfigBuilder = new SparseArray<>();
-    private final CarWatchdogService mCarWatchdogService =
-            new CarWatchdogService(getContext(), new ICarWatchdog.Default());
+    private final CarWatchdogService mCarWatchdogService = mock(CarWatchdogService.class);
 
     protected synchronized MockedVehicleHal createMockedVehicleHal() {
         return new MockedVehicleHal();
diff --git a/tests/carservice_unit_test/Android.mk b/tests/carservice_unit_test/Android.mk
index 06367f3..e6531df 100644
--- a/tests/carservice_unit_test/Android.mk
+++ b/tests/carservice_unit_test/Android.mk
@@ -44,6 +44,7 @@
 LOCAL_JAVA_LIBRARIES := \
     android.car \
     android.car.userlib \
+    android.car.watchdoglib \
     android.test.runner \
     android.test.base \
     android.test.mock \
diff --git a/tests/carservice_unit_test/src/android/car/watchdoglib/CarWatchdogDaemonHelperTest.java b/tests/carservice_unit_test/src/android/car/watchdoglib/CarWatchdogDaemonHelperTest.java
new file mode 100644
index 0000000..1087058
--- /dev/null
+++ b/tests/carservice_unit_test/src/android/car/watchdoglib/CarWatchdogDaemonHelperTest.java
@@ -0,0 +1,133 @@
+/*
+ * 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 android.car.watchdoglib;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertThrows;
+
+import android.automotive.watchdog.ICarWatchdog;
+import android.automotive.watchdog.ICarWatchdogClient;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoSession;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.mockito.quality.Strictness;
+
+import java.util.ArrayList;
+
+/**
+ * <p>This class contains unit tests for the {@link CarWatchdogDaemonHelper}.
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class CarWatchdogDaemonHelperTest {
+
+    private static final String CAR_WATCHDOG_DAEMON_INTERFACE =
+            "android.automotive.watchdog.ICarWatchdog/default";
+
+    @Mock private IBinder mBinder = new Binder();
+    private ICarWatchdog mFakeCarWatchdog = new FakeCarWatchdog();
+    private CarWatchdogDaemonHelper mCarWatchdogDaemonHelper;
+    private MockitoSession mMockSession;
+
+    @Before
+    public void setUp() {
+        mMockSession = mockitoSession()
+                .strictness(Strictness.LENIENT)
+                .spyStatic(ServiceManager.class)
+                .startMocking();
+        expectLocalWatchdogDaemonToWork();
+        mCarWatchdogDaemonHelper = new CarWatchdogDaemonHelper();
+        mCarWatchdogDaemonHelper.connect();
+    }
+
+    @After
+    public void tearDown() {
+        mMockSession.finishMocking();
+    }
+
+    /*
+     * Test that the {@link CarWatchdogDaemonHelper} throws {@code IllegalArgumentException} when
+     * trying to register already-registered client again.
+     */
+    @Test
+    public void testMultipleRegistration() throws RemoteException {
+        ICarWatchdogClient client = new ICarWatchdogClientImpl();
+        mCarWatchdogDaemonHelper.registerMediator(client);
+        assertThrows(IllegalArgumentException.class,
+                () -> mCarWatchdogDaemonHelper.registerMediator(client));
+    }
+
+    /*
+     * Test that the {@link CarWatchdogDaemonHelper} throws {@code IllegalArgumentException} when
+     * trying to unregister not-registered client.
+     */
+    @Test
+    public void testInvalidUnregistration() throws RemoteException {
+        ICarWatchdogClient client = new ICarWatchdogClientImpl();
+        assertThrows(IllegalArgumentException.class,
+                () -> mCarWatchdogDaemonHelper.unregisterMediator(client));
+    }
+
+    private void expectLocalWatchdogDaemonToWork() {
+        when(ServiceManager.getService(CAR_WATCHDOG_DAEMON_INTERFACE)).thenReturn(mBinder);
+        doReturn(mFakeCarWatchdog).when(mBinder).queryLocalInterface(anyString());
+    }
+
+    // FakeCarWatchdog mimics ICarWatchdog daemon in local process.
+    private final class FakeCarWatchdog extends ICarWatchdog.Default {
+
+        private final ArrayList<ICarWatchdogClient> mClients = new ArrayList<>();
+
+        @Override
+        public void registerMediator(ICarWatchdogClient mediator) throws RemoteException {
+            for (ICarWatchdogClient client : mClients) {
+                if (client == mediator) {
+                    throw new IllegalArgumentException("Already registered mediator");
+                }
+            }
+            mClients.add(mediator);
+        }
+
+        @Override
+        public void unregisterMediator(ICarWatchdogClient mediator) throws RemoteException {
+            for (ICarWatchdogClient client : mClients) {
+                if (client == mediator) {
+                    mClients.remove(mediator);
+                    return;
+                }
+            }
+            throw new IllegalArgumentException("Not registered mediator");
+        }
+    }
+
+    private final class ICarWatchdogClientImpl extends ICarWatchdogClient.Stub {
+        @Override
+        public void checkIfAlive(int sessionId, int timeout) {}
+    }
+}
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 3ab03e5..cf93d08 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
@@ -16,27 +16,34 @@
 
 package com.android.car.watchdog;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.verify;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.when;
 
 import android.automotive.watchdog.ICarWatchdog;
 import android.automotive.watchdog.ICarWatchdogClient;
 import android.automotive.watchdog.TimeoutLength;
 import android.content.Context;
+import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.util.Log;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
+import org.mockito.MockitoSession;
 import org.mockito.junit.MockitoJUnitRunner;
+import org.mockito.quality.Strictness;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -50,20 +57,32 @@
 public class CarWatchdogServiceTest {
 
     private static final String TAG = CarWatchdogServiceTest.class.getSimpleName();
+    private static final String CAR_WATCHDOG_DAEMON_INTERFACE =
+            "android.automotive.watchdog.ICarWatchdog/default";
+
+    private final FakeCarWatchdog mFakeCarWatchdog = new FakeCarWatchdog();
 
     @Mock private Context mMockContext;
-    @Mock private IBinder mBinder;
+    @Mock private IBinder mBinder = new Binder();
 
-    private CarWatchdogService mCarWatchdogService;
-    private FakeCarWatchdog mFakeCarWatchdog;
+    private CarWatchdogService mCarWatchdogService = new CarWatchdogService(mMockContext);
+    private MockitoSession mMockSession;
 
     /**
      * Initialize all of the objects with the @Mock annotation.
      */
     @Before
     public void setUpMocks() {
-        mFakeCarWatchdog = new FakeCarWatchdog();
-        mCarWatchdogService = new CarWatchdogService(mMockContext, mFakeCarWatchdog);
+        mMockSession = mockitoSession()
+                .strictness(Strictness.LENIENT)
+                .spyStatic(ServiceManager.class)
+                .startMocking();
+        expectLocalWatchdogDaemon();
+    }
+
+    @After
+    public void tearDown() {
+        mMockSession.finishMocking();
     }
 
     /**
@@ -77,16 +96,9 @@
         assertThat(mFakeCarWatchdog.gotResponse()).isTrue();
     }
 
-    @Test
-    public void testLinkUnlinkDeathRecipient() {
-        mCarWatchdogService.init();
-        try {
-            verify(mBinder).linkToDeath(any(), anyInt());
-        } catch (RemoteException e) {
-            // Do nothing
-        }
-        mCarWatchdogService.release();
-        verify(mBinder).unlinkToDeath(any(), anyInt());
+    private void expectLocalWatchdogDaemon() {
+        when(ServiceManager.getService(CAR_WATCHDOG_DAEMON_INTERFACE)).thenReturn(mBinder);
+        doReturn(mFakeCarWatchdog).when(mBinder).queryLocalInterface(anyString());
     }
 
     // FakeCarWatchdog mimics ICarWatchdog daemon in local process.
diff --git a/watchdog/car-watchdog-lib/Android.bp b/watchdog/car-watchdog-lib/Android.bp
new file mode 100644
index 0000000..1fc2cd7
--- /dev/null
+++ b/watchdog/car-watchdog-lib/Android.bp
@@ -0,0 +1,30 @@
+// 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.
+
+java_library {
+    name: "android.car.watchdoglib",
+    srcs: [
+        "src/**/*.java",
+    ],
+    static_libs: [
+        "androidx.preference_preference",
+        "carwatchdog_aidl_interface-java",
+    ],
+    product_variables: {
+        pdk: {
+            enabled: false,
+        },
+    },
+    installable: true,
+}
diff --git a/watchdog/car-watchdog-lib/src/android/car/watchdoglib/CarWatchdogDaemonHelper.java b/watchdog/car-watchdog-lib/src/android/car/watchdoglib/CarWatchdogDaemonHelper.java
new file mode 100644
index 0000000..ceb6123
--- /dev/null
+++ b/watchdog/car-watchdog-lib/src/android/car/watchdoglib/CarWatchdogDaemonHelper.java
@@ -0,0 +1,358 @@
+/*
+ * 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 android.car.watchdoglib;
+
+import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.automotive.watchdog.ICarWatchdog;
+import android.automotive.watchdog.ICarWatchdogClient;
+import android.automotive.watchdog.ICarWatchdogMonitor;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.Objects;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * Helper class for car watchdog daemon.
+ *
+ * @hide
+ */
+public final class CarWatchdogDaemonHelper {
+
+    private static final String TAG = CarWatchdogDaemonHelper.class.getSimpleName();
+    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;
+    private static final String CAR_WATCHDOG_DAEMON_INTERFACE =
+            "android.automotive.watchdog.ICarWatchdog/default";
+
+    private final Handler mHandler = new Handler(Looper.getMainLooper());
+    private final CopyOnWriteArrayList<OnConnectionChangeListener> mConnectionListeners =
+            new CopyOnWriteArrayList<>();
+    private final Object mLock = new Object();
+    @GuardedBy("mLock")
+    private @Nullable ICarWatchdog mCarWatchdogDaemon;
+    @GuardedBy("mLock")
+    private boolean mConnectionInProgress;
+
+    private final IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
+        @Override
+        public void binderDied() {
+            Log.w(TAG, "Car watchdog daemon died: reconnecting");
+            unlinkToDeath();
+            synchronized (mLock) {
+                mCarWatchdogDaemon = null;
+            }
+            for (OnConnectionChangeListener listener : mConnectionListeners) {
+                listener.onConnectionChange(false);
+            }
+            mHandler.sendMessageDelayed(obtainMessage(CarWatchdogDaemonHelper::connectToDaemon,
+                    CarWatchdogDaemonHelper.this, CAR_WATCHDOG_DAEMON_BIND_MAX_RETRY),
+                    CAR_WATCHDOG_DAEMON_BIND_RETRY_INTERVAL_MS);
+        }
+    };
+
+    private interface Invokable {
+        void invoke(ICarWatchdog daemon) throws RemoteException;
+    }
+
+    /**
+     * Listener to notify the state change of the connection to car watchdog daemon.
+     */
+    public interface OnConnectionChangeListener {
+        /** Gets called when car watchdog daemon is connected or disconnected. */
+        void onConnectionChange(boolean connected);
+    }
+
+    /**
+     * Connects to car watchdog daemon.
+     *
+     * <p>When it's connected, {@link OnConnectionChangeListener} is called with
+     * {@code true}.
+     */
+    public void connect() {
+        synchronized (mLock) {
+            if (mCarWatchdogDaemon != null || mConnectionInProgress) {
+                return;
+            }
+            mConnectionInProgress = true;
+        }
+        connectToDaemon(CAR_WATCHDOG_DAEMON_BIND_MAX_RETRY);
+    }
+
+    /**
+     * Disconnects from car watchdog daemon.
+     *
+     * <p>When it's disconnected, {@link OnConnectionChangeListener} is called with
+     * {@code false}.
+     */
+    public void disconnect() {
+        unlinkToDeath();
+        synchronized (mLock) {
+            mCarWatchdogDaemon = null;
+        }
+    }
+
+    /**
+     * Adds {@link OnConnectionChangeListener}.
+     *
+     * @param listener Listener to be notified when connection state changes.
+     */
+    public void addOnConnectionChangeListener(
+            @NonNull OnConnectionChangeListener listener) {
+        Objects.requireNonNull(listener, "Listener cannot be null");
+        mConnectionListeners.add(listener);
+    }
+
+    /**
+     * Removes {@link OnConnectionChangeListener}.
+     *
+     * @param listener Listener to be removed.
+     */
+    public void removeOnConnectionChangeListener(
+            @NonNull OnConnectionChangeListener listener) {
+        Objects.requireNonNull(listener, "Listener cannot be null");
+        mConnectionListeners.remove(listener);
+    }
+
+    /**
+     * Registers car watchdog client.
+     *
+     * @param client Car watchdog client to be registered.
+     * @param timeout Time within which the client should respond.
+     * @throws IllegalArgumentException If the client is already registered.
+     */
+    public void registerClient(ICarWatchdogClient client, int timeout)
+            throws IllegalArgumentException, RemoteException {
+        invokeDaemonMethod((daemon) -> daemon.registerClient(client, timeout));
+    }
+
+    /**
+     * Unregisters car watchdog client.
+     *
+     * @param client Car watchdog client to be unregistered.
+     * @throws IllegalArgumentException If the client is not registered.
+     */
+    public void unregisterClient(ICarWatchdogClient client)
+            throws IllegalArgumentException, RemoteException {
+        invokeDaemonMethod((daemon) -> daemon.unregisterClient(client));
+    }
+
+    /**
+     * Registers car watchdog client as mediator.
+     *
+     * @param mediator Car watchdog client to be registered.
+     * @throws IllegalArgumentException If the mediator is already registered.
+     */
+    public void registerMediator(ICarWatchdogClient mediator)
+            throws IllegalArgumentException, RemoteException {
+        invokeDaemonMethod((daemon) -> daemon.registerMediator(mediator));
+    }
+
+    /**
+     * Unregisters car watchdog client as mediator.
+     *
+     * @param mediator Car watchdog client to be unregistered.
+     * @throws IllegalArgumentException If the mediator is not registered.
+     */
+    public void unregisterMediator(ICarWatchdogClient mediator)
+            throws IllegalArgumentException, RemoteException  {
+        invokeDaemonMethod((daemon) -> daemon.unregisterMediator(mediator));
+    }
+
+    /**
+     * Registers car watchdog monitor.
+     *
+     * @param monitor Car watchdog monitor to be registered.
+     * @throws IllegalArgumentException If there is another monitor registered.
+     */
+    public void registerMonitor(ICarWatchdogMonitor monitor)
+            throws IllegalArgumentException, RemoteException  {
+        invokeDaemonMethod((daemon) -> daemon.registerMonitor(monitor));
+    }
+
+    /**
+     * Unregisters car watchdog monitor.
+     *
+     * @param monitor Car watchdog monitor to be unregistered.
+     * @throws IllegalArgumentException If the monitor is not registered.
+     */
+    public void unregisterMonitor(ICarWatchdogMonitor monitor)
+            throws IllegalArgumentException, RemoteException  {
+        invokeDaemonMethod((daemon) -> daemon.unregisterMonitor(monitor));
+    }
+
+    /**
+     * Tells car watchdog daemon that the client is alive.
+     *
+     * @param client Car watchdog client which has been pined by car watchdog daemon.
+     * @param sessionId Session ID that car watchdog daemon has given.
+     * @throws IllegalArgumentException If the client is not registered,
+     *                                  or session ID is not correct.
+     */
+    public void tellClientAlive(ICarWatchdogClient client, int sessionId)
+            throws IllegalArgumentException, RemoteException  {
+        invokeDaemonMethod((daemon) -> daemon.tellClientAlive(client, sessionId));
+    }
+
+    /**
+     * Tells car watchdog daemon that the mediator is alive.
+     *
+     * @param mediator Car watchdog client which has been pined by car watchdog daemon.
+     * @param clientsNotResponding Array of process ID that are not responding.
+     * @param sessionId Session ID that car watchdog daemon has given.
+     * @throws IllegalArgumentException If the client is not registered,
+     *                                  or session ID is not correct.
+     */
+    public void tellMediatorAlive(ICarWatchdogClient mediator, int[] clientsNotResponding,
+            int sessionId) throws IllegalArgumentException, RemoteException {
+        invokeDaemonMethod(
+                (daemon) -> daemon.tellMediatorAlive(mediator, clientsNotResponding, sessionId));
+    }
+
+    /**
+     * Tells car watchdog daemon that the monitor has dumped clients' process information.
+     *
+     * @param monitor Car watchdog monitor that dumped process information.
+     * @param pid ID of process that has been dumped.
+     * @throws IllegalArgumentException If the monitor is not registered.
+     */
+    public void tellDumpFinished(ICarWatchdogMonitor monitor, int pid)
+            throws IllegalArgumentException, RemoteException {
+        invokeDaemonMethod((daemon) -> daemon.tellDumpFinished(monitor, pid));
+    }
+
+    /**
+     * Tells car watchdog daemon that system power cycle has been changed.
+     *
+     * @param cycle System power cycle.
+     */
+    public void notifyPowerCycleChange(int cycle) throws RemoteException {
+        invokeDaemonMethod((daemon) -> daemon.notifyPowerCycleChange(cycle));
+    }
+
+    /**
+     * Tells car watchdog daemon that user state has been changed.
+     *
+     * @param userId User ID whose state has been changed.
+     * @param state New user state.
+     */
+    public void notifyUserStateChange(int userId, int state) throws RemoteException {
+        invokeDaemonMethod((daemon) -> daemon.notifyUserStateChange(userId, state));
+    }
+
+    private void invokeDaemonMethod(Invokable r) throws IllegalArgumentException, RemoteException {
+        ICarWatchdog daemon;
+        synchronized (mLock) {
+            if (mCarWatchdogDaemon == null) {
+                throw new IllegalStateException("Car watchdog daemon is not connected");
+            }
+            daemon = mCarWatchdogDaemon;
+        }
+        r.invoke(daemon);
+    }
+
+    private void connectToDaemon(int retryCount) {
+        if (retryCount <= 0) {
+            synchronized (mLock) {
+                mConnectionInProgress = false;
+            }
+            Log.e(TAG, "Cannot reconnect to car watchdog daemon after retrying "
+                    + CAR_WATCHDOG_DAEMON_BIND_MAX_RETRY + " times");
+            return;
+        }
+        if (makeBinderConnection()) {
+            Log.i(TAG, "Connected to car watchdog daemon");
+            return;
+        }
+        mHandler.sendMessageDelayed(obtainMessage(CarWatchdogDaemonHelper::connectToDaemon,
+                CarWatchdogDaemonHelper.this, retryCount - 1),
+                CAR_WATCHDOG_DAEMON_BIND_RETRY_INTERVAL_MS);
+    }
+
+    private boolean makeBinderConnection() {
+        long currentTimeMs = SystemClock.uptimeMillis();
+        IBinder binder = ServiceManager.getService(CAR_WATCHDOG_DAEMON_INTERFACE);
+        if (binder == null) {
+            Log.w(TAG, "Getting car watchdog daemon binder failed");
+            return false;
+        }
+        long elapsedTimeMs = SystemClock.uptimeMillis() - currentTimeMs;
+        if (elapsedTimeMs > CAR_WATCHDOG_DAEMON_FIND_MARGINAL_TIME_MS) {
+            Log.wtf(TAG, "Finding car watchdog daemon took too long(" + elapsedTimeMs + "ms)");
+        }
+
+        ICarWatchdog daemon = ICarWatchdog.Stub.asInterface(binder);
+        if (daemon == null) {
+            Log.w(TAG, "Getting car watchdog daemon interface failed");
+            return false;
+        }
+        synchronized (mLock) {
+            mCarWatchdogDaemon = daemon;
+            mConnectionInProgress = false;
+        }
+        linkToDeath();
+        for (OnConnectionChangeListener listener : mConnectionListeners) {
+            listener.onConnectionChange(true);
+        }
+        return true;
+    }
+
+    private void linkToDeath() {
+        IBinder binder;
+        synchronized (mLock) {
+            if (mCarWatchdogDaemon == null) {
+                return;
+            }
+            binder = mCarWatchdogDaemon.asBinder();
+        }
+        if (binder == null) {
+            Log.w(TAG, "Linking to binder death recipient skipped");
+            return;
+        }
+        try {
+            binder.linkToDeath(mDeathRecipient, 0);
+        } catch (RemoteException e) {
+            Log.w(TAG, "Linking to binder death recipient failed: " + e);
+        }
+    }
+
+    private void unlinkToDeath() {
+        IBinder binder;
+        synchronized (mLock) {
+            if (mCarWatchdogDaemon == null) {
+                return;
+            }
+            binder = mCarWatchdogDaemon.asBinder();
+        }
+        if (binder == null) {
+            Log.w(TAG, "Unlinking from binder death recipient skipped");
+            return;
+        }
+        binder.unlinkToDeath(mDeathRecipient, 0);
+    }
+}
diff --git a/watchdog/sepolicy/public/carwatchdog.te b/watchdog/sepolicy/public/carwatchdog.te
index fedf3b1..2cb9c5a 100644
--- a/watchdog/sepolicy/public/carwatchdog.te
+++ b/watchdog/sepolicy/public/carwatchdog.te
@@ -3,3 +3,7 @@
 
 binder_call(carwatchdogd, carwatchdogclient_domain)
 binder_call(carwatchdogclient_domain, carwatchdogd)
+
+# Configuration for system_server
+allow system_server carwatchdogd_service:service_manager find;
+binder_call(carwatchdogd, system_server)