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)