Bluetooth: Enable avrcp volume up down

This enables volume up / down command in blueship. The volume change
command in playerctl doesn't work so we directly use D-Bus interface.

For implementation simplicity, this change also SetRemoteAddress when
the device is paired.

BUG=b:438364604
TEST=run test_avrcp_volume_up_down

Change-Id: Ia8a632180c5a802aa7290537831b7d6399562d24
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/chameleon/+/6844397
Reviewed-by: John Lai <[email protected]>
Commit-Queue: Yun-hao Chung <[email protected]>
Tested-by: Yun-hao Chung <[email protected]>
diff --git a/chameleond/bluetooth_grpc/blueship/server/audio.py b/chameleond/bluetooth_grpc/blueship/server/audio.py
index 953c884..a0f163e 100644
--- a/chameleond/bluetooth_grpc/blueship/server/audio.py
+++ b/chameleond/bluetooth_grpc/blueship/server/audio.py
@@ -14,6 +14,10 @@
 import grpc
 
 
+# Delta of each volume change
+VOLUME_DELTA = 5
+
+
 class AudioService(audio_grpc_aio.AudioServicer):
     """Service to trigger Chameleon Audio procedures."""
 
@@ -97,7 +101,13 @@
         request: audio_pb2.SendAvrcpCmdRequest,
         context: grpc.ServicerContext,
     ) -> empty_pb2.Empty:
-        if request.cmd == audio_pb2.AUDIO_PLAYER_PLAY:
+        if request.cmd == audio_pb2.AUDIO_PLAYER_VOL_UP:
+            self.ctx.btd.SetVolumeDelta(VOLUME_DELTA)
+            return empty_pb2.Empty()
+        elif request.cmd == audio_pb2.AUDIO_PLAYER_VOL_DOWN:
+            self.ctx.btd.SetVolumeDelta(-VOLUME_DELTA)
+            return empty_pb2.Empty()
+        elif request.cmd == audio_pb2.AUDIO_PLAYER_PLAY:
             cmd = "play"
         elif request.cmd == audio_pb2.AUDIO_PLAYER_PAUSE:
             cmd = "pause"
@@ -107,13 +117,6 @@
             cmd = "next"
         elif request.cmd == audio_pb2.AUDIO_PLAYER_PREV:
             cmd = "prev"
-        # For some reason playerctl volume change is not working
-        #
-        # elif request.cmd == audio_pb2.AUDIO_PLAYER_VOL_UP:
-        #    cmd = 'volume 0.1+'
-        # elif request.cmd == audio_pb2.AUDIO_PLAYER_VOL_DOWN:
-        #    cmd = 'volume 0.1-'
-        #
         else:
             logging.error("Unrecognized command %s", request.cmd)
             return empty_pb2.Empty()
diff --git a/chameleond/bluetooth_grpc/blueship/server/manager.py b/chameleond/bluetooth_grpc/blueship/server/manager.py
index 9dac478..8dc8158 100644
--- a/chameleond/bluetooth_grpc/blueship/server/manager.py
+++ b/chameleond/bluetooth_grpc/blueship/server/manager.py
@@ -130,24 +130,9 @@
 
             return True
 
-        def _check_player_cmd_supported():
-            UNSUPPORTED_PLAYER_CMDS = [
-                "AUDIO_PLAYER_VOL_UP",
-                "AUDIO_PLAYER_VOL_DOWN",
-            ]
-            for cmd in UNSUPPORTED_PLAYER_CMDS:
-                if (
-                    cmd
-                    in req_config_dict["audio"]["supportedPlayerCommands"][
-                        "playerCmdList"
-                    ]["playerCmdList"]
-                ):
-                    logging.warning("cmd %s is not supported.", cmd)
-                    return False
-
-            return True
-
-        check_funcs = [_check_codec_supported, _check_player_cmd_supported]
+        check_funcs = [
+            _check_codec_supported,
+        ]
 
         for func in check_funcs:
             try:
diff --git a/chameleond/bluetooth_grpc/blueship/server/power.py b/chameleond/bluetooth_grpc/blueship/server/power.py
index c71a23a..5096c2b 100644
--- a/chameleond/bluetooth_grpc/blueship/server/power.py
+++ b/chameleond/bluetooth_grpc/blueship/server/power.py
@@ -38,7 +38,6 @@
         time.sleep(1)
 
         if self.bredr_only and self.connected_device is not None:
-            self.ctx.btd.SetRemoteAddress(self.connected_device)
             logging.info("Reconnect to %s", self.connected_device)
             self.ctx.btd.Connect()
 
diff --git a/chameleond/bluetooth_grpc/blueship/server/security.py b/chameleond/bluetooth_grpc/blueship/server/security.py
index de38735..a512f06 100644
--- a/chameleond/bluetooth_grpc/blueship/server/security.py
+++ b/chameleond/bluetooth_grpc/blueship/server/security.py
@@ -330,6 +330,7 @@
             logging.info(
                 "Pairing Mode: %s is paired. Stopping pairing mode", address
             )
+            self.ctx.btd.SetRemoteAddress(address)
             asyncio.run_coroutine_threadsafe(
                 # Use |None| to indicate pairing is done.
                 self.pairing_events.put((None, None)),
diff --git a/chameleond/utils/bluetooth_audio.py b/chameleond/utils/bluetooth_audio.py
index b0e8634..1873047 100644
--- a/chameleond/utils/bluetooth_audio.py
+++ b/chameleond/utils/bluetooth_audio.py
@@ -393,6 +393,8 @@
     WP_BLUEZ_AAC_CONFIG = "bluez-aac-properties.conf"
     WP_BLUEZ_SWB_CONFIG = "bluez-swb-properties.conf"
 
+    VOLUME_MAX = 127
+
     # The pipewire related processes:
     # - pipewire: the audio server process
     # - wireplumber: an auxiliary process for configuring pipewire
@@ -1810,6 +1812,35 @@
 
         return SystemTools.Output(self.PLAYERCTL, "-p", player, *args).strip()
 
+    def _GetTransporProperyInterface(self):
+        device_path = self.remote_address.replace(":", "_")
+        path = f"/org/bluez/hci0/dev_{device_path}/fd0"
+
+        obj = self._dbus_system_bus.get_object("org.bluez", path)
+        if obj is None:
+            return None
+        return dbus.Interface(obj, "org.freedesktop.DBus.Properties")
+
+    def SetVolumeDelta(self, volume_delta):
+        """Changes the volume by delta if possible.
+
+        Args:
+            volume_delta: the delta to change.
+        """
+        transport = self._GetTransporProperyInterface()
+        if transport is None:
+            logging.error("Unable to find the audio transport")
+            return
+
+        volume = int(transport.Get("org.bluez.MediaTransport1", "Volume"))
+        new_volume = max(min(volume + volume_delta, self.VOLUME_MAX), 0)
+        logging.debug(f"Setting volume={volume} -> {new_volume}")
+        transport.Set(
+            "org.bluez.MediaTransport1",
+            "Volume",
+            dbus.UInt16(new_volume, variant_level=1),
+        )
+
     def SendMediaPlayerCommand(self, command):
         """Execute command towards the given player.