From c0656a88e620e7813fea498538de89a7fddd0dec Mon Sep 17 00:00:00 2001
From: Ahmad Khalil <khalilahmad@google.com>
Date: Sat, 9 Dec 2023 21:05:36 +0000
Subject: [PATCH] Introducing VibratorControlService

We're adding part of the framework's implementation for
IVibratorControlService, which will allow registering and unregistering
a VibrationController. We're also registering the service through the
VibratorManagerService.

Bug: 305961689
Test: atest FrameworksVibratorServicesTests
Change-Id: Ic95c1e5b73106f9c89f02277966586c6c0855e4f
---
 core/java/android/os/vibrator/flags.aconfig   |   7 ++
 services/core/Android.bp                      |   1 +
 .../vibrator/VibratorControlService.java      | 105 ++++++++++++++++++
 .../vibrator/VibratorControllerHolder.java    |  70 ++++++++++++
 .../vibrator/VibratorManagerService.java      |   7 ++
 services/manifest_services.xml                |   5 +
 .../vibrator/VibratorControlServiceTest.java  |  63 +++++++++++
 .../VibratorControllerHolderTest.java         |  72 ++++++++++++
 .../vibrator/VibratorManagerServiceTest.java  |   7 +-
 .../vibrator/FakeVibratorController.java      |  58 ++++++++++
 10 files changed, 392 insertions(+), 3 deletions(-)
 create mode 100644 services/core/java/com/android/server/vibrator/VibratorControlService.java
 create mode 100644 services/core/java/com/android/server/vibrator/VibratorControllerHolder.java
 create mode 100644 services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java
 create mode 100644 services/tests/vibrator/src/com/android/server/vibrator/VibratorControllerHolderTest.java
 create mode 100644 services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorController.java

diff --git a/core/java/android/os/vibrator/flags.aconfig b/core/java/android/os/vibrator/flags.aconfig
index 69d86a6604ad..437668c9a7de 100644
--- a/core/java/android/os/vibrator/flags.aconfig
+++ b/core/java/android/os/vibrator/flags.aconfig
@@ -44,3 +44,10 @@ flag {
     description: "Enables the independent keyboard vibration settings feature"
     bug: "289107579"
 }
+
+flag {
+    namespace: "haptics"
+    name: "adaptive_haptics_enabled"
+    description: "Enables the adaptive haptics feature"
+    bug: "305961689"
+}
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 20a3b9ada85d..b4cf34e00c53 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -154,6 +154,7 @@ java_library_static {
 
     static_libs: [
         "android.frameworks.location.altitude-V1-java", // AIDL
+        "android.frameworks.vibrator-V1-java", // AIDL
         "android.hardware.authsecret-V1.0-java",
         "android.hardware.authsecret-V1-java",
         "android.hardware.boot-V1.0-java", // HIDL
diff --git a/services/core/java/com/android/server/vibrator/VibratorControlService.java b/services/core/java/com/android/server/vibrator/VibratorControlService.java
new file mode 100644
index 000000000000..2eeb903bb551
--- /dev/null
+++ b/services/core/java/com/android/server/vibrator/VibratorControlService.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2023 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.server.vibrator;
+
+import android.annotation.NonNull;
+import android.annotation.SuppressLint;
+import android.frameworks.vibrator.IVibratorControlService;
+import android.frameworks.vibrator.IVibratorController;
+import android.frameworks.vibrator.VibrationParam;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import java.util.Objects;
+
+/**
+ * Implementation of {@link IVibratorControlService} which allows the registration of
+ * {@link IVibratorController} to set and receive vibration params.
+ *
+ * @hide
+ */
+public final class VibratorControlService extends IVibratorControlService.Stub {
+    private static final String TAG = "VibratorControlService";
+
+    private final VibratorControllerHolder mVibratorControllerHolder;
+    private final Object mLock;
+
+    public VibratorControlService(VibratorControllerHolder vibratorControllerHolder, Object lock) {
+        mVibratorControllerHolder = vibratorControllerHolder;
+        mLock = lock;
+    }
+
+    @Override
+    public void registerVibratorController(IVibratorController controller)
+            throws RemoteException {
+        synchronized (mLock) {
+            mVibratorControllerHolder.setVibratorController(controller);
+        }
+    }
+
+    @Override
+    public void unregisterVibratorController(@NonNull IVibratorController controller)
+            throws RemoteException {
+        Objects.requireNonNull(controller);
+
+        synchronized (mLock) {
+            if (mVibratorControllerHolder.getVibratorController() == null) {
+                Slog.w(TAG, "Received request to unregister IVibratorController = "
+                        + controller + ", but no controller was previously registered. Request "
+                        + "Ignored.");
+                return;
+            }
+            if (!Objects.equals(mVibratorControllerHolder.getVibratorController().asBinder(),
+                    controller.asBinder())) {
+                Slog.wtf(TAG, "Failed to unregister IVibratorController. The provided "
+                        + "controller doesn't match the registered one. " + this);
+                return;
+            }
+            mVibratorControllerHolder.setVibratorController(null);
+        }
+    }
+
+    @Override
+    public void setVibrationParams(
+            @SuppressLint("ArrayReturn") VibrationParam[] params, IVibratorController token)
+            throws RemoteException {
+        // TODO(b/305939964): Add set vibration implementation.
+    }
+
+    @Override
+    public void clearVibrationParams(int types, IVibratorController token) throws RemoteException {
+        // TODO(b/305939964): Add clear vibration implementation.
+    }
+
+    @Override
+    public void onRequestVibrationParamsComplete(
+            IBinder requestToken, @SuppressLint("ArrayReturn") VibrationParam[] result)
+            throws RemoteException {
+        // TODO(305942827): Cache the vibration params in VibrationScaler
+    }
+
+    @Override
+    public int getInterfaceVersion() throws RemoteException {
+        return this.VERSION;
+    }
+
+    @Override
+    public String getInterfaceHash() throws RemoteException {
+        return this.HASH;
+    }
+}
diff --git a/services/core/java/com/android/server/vibrator/VibratorControllerHolder.java b/services/core/java/com/android/server/vibrator/VibratorControllerHolder.java
new file mode 100644
index 000000000000..63e69db9480f
--- /dev/null
+++ b/services/core/java/com/android/server/vibrator/VibratorControllerHolder.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2023 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.server.vibrator;
+
+import android.annotation.NonNull;
+import android.frameworks.vibrator.IVibratorController;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Slog;
+
+/**
+ * Holder class for {@link IVibratorController}.
+ *
+ * @hide
+ */
+public final class VibratorControllerHolder implements IBinder.DeathRecipient {
+    private static final String TAG = "VibratorControllerHolder";
+
+    private IVibratorController mVibratorController;
+
+    public IVibratorController getVibratorController() {
+        return mVibratorController;
+    }
+
+    /**
+     * Sets the {@link IVibratorController} in {@link VibratorControllerHolder} to the new
+     * controller. This will also take care of registering and unregistering death notifications
+     * for the cached {@link IVibratorController}.
+     */
+    public void setVibratorController(IVibratorController controller) {
+        try {
+            if (mVibratorController != null) {
+                mVibratorController.asBinder().unlinkToDeath(this, 0);
+            }
+            mVibratorController = controller;
+            if (mVibratorController != null) {
+                mVibratorController.asBinder().linkToDeath(this, 0);
+            }
+        } catch (RemoteException e) {
+            Slog.wtf(TAG, "Failed to set IVibratorController: " + this, e);
+        }
+    }
+
+    @Override
+    public void binderDied(@NonNull IBinder deadBinder) {
+        if (deadBinder == mVibratorController.asBinder()) {
+            setVibratorController(null);
+        }
+    }
+
+    @Override
+    public void binderDied() {
+        // Should not be used as binderDied(IBinder who) is overridden.
+        Slog.wtf(TAG, "binderDied() called unexpectedly.");
+    }
+}
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index 7d4bd3baf613..fc824abd80f5 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -91,6 +91,8 @@ import java.util.function.Function;
 public class VibratorManagerService extends IVibratorManagerService.Stub {
     private static final String TAG = "VibratorManagerService";
     private static final String EXTERNAL_VIBRATOR_SERVICE = "external_vibrator_service";
+    private static final String VIBRATOR_CONTROL_SERVICE =
+            "android.frameworks.vibrator.IVibratorControlService/default";
     private static final boolean DEBUG = false;
     private static final VibrationAttributes DEFAULT_ATTRIBUTES =
             new VibrationAttributes.Builder().build();
@@ -269,6 +271,11 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
         context.registerReceiver(mIntentReceiver, filter, Context.RECEIVER_NOT_EXPORTED);
 
         injector.addService(EXTERNAL_VIBRATOR_SERVICE, new ExternalVibratorService());
+        if (ServiceManager.isDeclared(VIBRATOR_CONTROL_SERVICE)) {
+            injector.addService(VIBRATOR_CONTROL_SERVICE,
+                    new VibratorControlService(new VibratorControllerHolder(), mLock));
+        }
+
     }
 
     /** Finish initialization at boot phase {@link SystemService#PHASE_SYSTEM_SERVICES_READY}. */
diff --git a/services/manifest_services.xml b/services/manifest_services.xml
index 76389154a885..eae159fe9e89 100644
--- a/services/manifest_services.xml
+++ b/services/manifest_services.xml
@@ -4,4 +4,9 @@
         <version>1</version>
         <fqname>IAltitudeService/default</fqname>
     </hal>
+    <hal format="aidl">
+        <name>android.frameworks.vibrator</name>
+        <version>1</version>
+        <fqname>IVibratorControlService/default</fqname>
+    </hal>
 </manifest>
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java
new file mode 100644
index 000000000000..49efd1bdd92a
--- /dev/null
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.vibrator;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.RemoteException;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public class VibratorControlServiceTest {
+
+    private VibratorControlService mVibratorControlService;
+    private final Object mLock = new Object();
+
+    @Before
+    public void setUp() throws Exception {
+        mVibratorControlService = new VibratorControlService(new VibratorControllerHolder(), mLock);
+    }
+
+    @Test
+    public void testRegisterVibratorController() throws RemoteException {
+        FakeVibratorController fakeController = new FakeVibratorController();
+        mVibratorControlService.registerVibratorController(fakeController);
+
+        assertThat(fakeController.isLinkedToDeath).isTrue();
+    }
+
+    @Test
+    public void testUnregisterVibratorController_providingTheRegisteredController_performsRequest()
+            throws RemoteException {
+        FakeVibratorController fakeController = new FakeVibratorController();
+        mVibratorControlService.registerVibratorController(fakeController);
+        mVibratorControlService.unregisterVibratorController(fakeController);
+        assertThat(fakeController.isLinkedToDeath).isFalse();
+    }
+
+    @Test
+    public void testUnregisterVibratorController_providingAnInvalidController_ignoresRequest()
+            throws RemoteException {
+        FakeVibratorController fakeController1 = new FakeVibratorController();
+        FakeVibratorController fakeController2 = new FakeVibratorController();
+        mVibratorControlService.registerVibratorController(fakeController1);
+
+        mVibratorControlService.unregisterVibratorController(fakeController2);
+        assertThat(fakeController1.isLinkedToDeath).isTrue();
+    }
+}
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControllerHolderTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControllerHolderTest.java
new file mode 100644
index 000000000000..79abe21a301d
--- /dev/null
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControllerHolderTest.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.vibrator;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.RemoteException;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public class VibratorControllerHolderTest {
+
+    private final FakeVibratorController mFakeVibratorController = new FakeVibratorController();
+    private VibratorControllerHolder mVibratorControllerHolder;
+
+    @Before
+    public void setUp() throws Exception {
+        mVibratorControllerHolder = new VibratorControllerHolder();
+    }
+
+    @Test
+    public void testSetVibratorController_linksVibratorControllerToDeath() throws RemoteException {
+        mVibratorControllerHolder.setVibratorController(mFakeVibratorController);
+        assertThat(mVibratorControllerHolder.getVibratorController())
+                .isEqualTo(mFakeVibratorController);
+        assertThat(mFakeVibratorController.isLinkedToDeath).isTrue();
+    }
+
+    @Test
+    public void testSetVibratorController_setControllerToNull_unlinksVibratorControllerToDeath()
+            throws RemoteException {
+        mVibratorControllerHolder.setVibratorController(mFakeVibratorController);
+        mVibratorControllerHolder.setVibratorController(null);
+        assertThat(mFakeVibratorController.isLinkedToDeath).isFalse();
+        assertThat(mVibratorControllerHolder.getVibratorController()).isNull();
+    }
+
+    @Test
+    public void testBinderDied_withValidController_unlinksVibratorControllerToDeath()
+            throws RemoteException {
+        mVibratorControllerHolder.setVibratorController(mFakeVibratorController);
+        mVibratorControllerHolder.binderDied(mFakeVibratorController);
+        assertThat(mFakeVibratorController.isLinkedToDeath).isFalse();
+        assertThat(mVibratorControllerHolder.getVibratorController()).isNull();
+    }
+
+    @Test
+    public void testBinderDied_withInvalidController_ignoresRequest()
+            throws RemoteException {
+        mVibratorControllerHolder.setVibratorController(mFakeVibratorController);
+        FakeVibratorController imposterVibratorController = new FakeVibratorController();
+        mVibratorControllerHolder.binderDied(imposterVibratorController);
+        assertThat(mFakeVibratorController.isLinkedToDeath).isTrue();
+        assertThat(mVibratorControllerHolder.getVibratorController())
+                .isEqualTo(mFakeVibratorController);
+    }
+}
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
index 4e9bbe0a28fe..d6b2116e2682 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -307,9 +307,10 @@ public class VibratorManagerServiceTest {
 
                     @Override
                     void addService(String name, IBinder service) {
-                        Object serviceInstance = service;
-                        mExternalVibratorService =
-                                (VibratorManagerService.ExternalVibratorService) serviceInstance;
+                        if (service instanceof VibratorManagerService.ExternalVibratorService) {
+                            mExternalVibratorService =
+                                    (VibratorManagerService.ExternalVibratorService) service;
+                        }
                     }
 
                     HapticFeedbackVibrationProvider createHapticFeedbackVibrationProvider(
diff --git a/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorController.java b/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorController.java
new file mode 100644
index 000000000000..7e235870cedc
--- /dev/null
+++ b/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorController.java
@@ -0,0 +1,58 @@
+/*
+ * 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.server.vibrator;
+
+import android.annotation.NonNull;
+import android.frameworks.vibrator.IVibratorController;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+/**
+ * Provides a fake implementation of {@link android.frameworks.vibrator.IVibratorController} for
+ * testing.
+ */
+public final class FakeVibratorController extends IVibratorController.Stub {
+
+    public boolean isLinkedToDeath = false;
+
+    @Override
+    public void requestVibrationParams(int i, long l, IBinder iBinder) throws RemoteException {
+
+    }
+
+    @Override
+    public int getInterfaceVersion() throws RemoteException {
+        return 0;
+    }
+
+    @Override
+    public String getInterfaceHash() throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public void linkToDeath(@NonNull DeathRecipient recipient, int flags) {
+        super.linkToDeath(recipient, flags);
+        isLinkedToDeath = true;
+    }
+
+    @Override
+    public boolean unlinkToDeath(@NonNull DeathRecipient recipient, int flags) {
+        isLinkedToDeath = false;
+        return super.unlinkToDeath(recipient, flags);
+    }
+}
-- 
GitLab