leaudio: Add support for Mute/Unmute

Bug: 231580214
Test: atest --host bluetooth_vc_test
Sponsor: @jpawlowski
Change-Id: Ib894ab01916d7eb989db6a0c28b79b321dd82ad6
(cherry picked from commit 636dc13c93a5d18589b010afe7e105c550eab17e)
diff --git a/android/app/jni/com_android_bluetooth_vc.cpp b/android/app/jni/com_android_bluetooth_vc.cpp
index cc1e296..ad31918 100644
--- a/android/app/jni/com_android_bluetooth_vc.cpp
+++ b/android/app/jni/com_android_bluetooth_vc.cpp
@@ -357,6 +357,60 @@
   sVolumeControlInterface->SetVolume(group_id, volume);
 }
 
+static void muteNative(JNIEnv* env, jobject object, jbyteArray address) {
+  if (!sVolumeControlInterface) {
+    LOG(ERROR) << __func__
+               << ": Failed to get the Bluetooth Volume Control Interface";
+    return;
+  }
+
+  jbyte* addr = env->GetByteArrayElements(address, nullptr);
+  if (!addr) {
+    jniThrowIOException(env, EINVAL);
+    return;
+  }
+
+  RawAddress* tmpraw = (RawAddress*)addr;
+  sVolumeControlInterface->Mute(*tmpraw);
+  env->ReleaseByteArrayElements(address, addr, 0);
+}
+
+static void muteGroupNative(JNIEnv* env, jobject object, jint group_id) {
+  if (!sVolumeControlInterface) {
+    LOG(ERROR) << __func__
+               << ": Failed to get the Bluetooth Volume Control Interface";
+    return;
+  }
+  sVolumeControlInterface->Mute(group_id);
+}
+
+static void unmuteNative(JNIEnv* env, jobject object, jbyteArray address) {
+  if (!sVolumeControlInterface) {
+    LOG(ERROR) << __func__
+               << ": Failed to get the Bluetooth Volume Control Interface";
+    return;
+  }
+
+  jbyte* addr = env->GetByteArrayElements(address, nullptr);
+  if (!addr) {
+    jniThrowIOException(env, EINVAL);
+    return;
+  }
+
+  RawAddress* tmpraw = (RawAddress*)addr;
+  sVolumeControlInterface->Unmute(*tmpraw);
+  env->ReleaseByteArrayElements(address, addr, 0);
+}
+
+static void unmuteGroupNative(JNIEnv* env, jobject object, jint group_id) {
+  if (!sVolumeControlInterface) {
+    LOG(ERROR) << __func__
+               << ": Failed to get the Bluetooth Volume Control Interface";
+    return;
+  }
+  sVolumeControlInterface->Unmute(group_id);
+}
+
 /* Native methods for exterbak audio outputs */
 static jboolean getExtAudioOutVolumeOffsetNative(JNIEnv* env, jobject object,
                                                  jbyteArray address,
@@ -494,6 +548,10 @@
      (void*)disconnectVolumeControlNative},
     {"setVolumeNative", "([BI)V", (void*)setVolumeNative},
     {"setVolumeGroupNative", "(II)V", (void*)setVolumeGroupNative},
+    {"muteNative", "([B)V", (void*)muteNative},
+    {"muteGroupNative", "(I)V", (void*)muteGroupNative},
+    {"unmuteNative", "([B)V", (void*)unmuteNative},
+    {"unmuteGroupNative", "(I)V", (void*)unmuteGroupNative},
     {"getExtAudioOutVolumeOffsetNative", "([BI)Z",
      (void*)getExtAudioOutVolumeOffsetNative},
     {"setExtAudioOutVolumeOffsetNative", "([BII)Z",
diff --git a/android/app/src/com/android/bluetooth/le_audio/LeAudioService.java b/android/app/src/com/android/bluetooth/le_audio/LeAudioService.java
index 2b99a80..edb3f17 100644
--- a/android/app/src/com/android/bluetooth/le_audio/LeAudioService.java
+++ b/android/app/src/com/android/bluetooth/le_audio/LeAudioService.java
@@ -176,6 +176,8 @@
 
     private BroadcastReceiver mBondStateChangedReceiver;
     private BroadcastReceiver mConnectionStateChangedReceiver;
+    private BroadcastReceiver mMuteStateChangedReceiver;
+    private int mStoredRingerMode = -1;
     private Handler mHandler = new Handler(Looper.getMainLooper());
 
     private final Map<Integer, Integer> mBroadcastStateMap = new HashMap<>();
@@ -239,6 +241,11 @@
         filter.addAction(BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED);
         mConnectionStateChangedReceiver = new ConnectionStateChangedReceiver();
         registerReceiver(mConnectionStateChangedReceiver, filter);
+        filter = new IntentFilter();
+        filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION);
+        mMuteStateChangedReceiver = new MuteStateChangedReceiver();
+        registerReceiver(mMuteStateChangedReceiver, filter);
+
         mLeAudioCallbacks = new RemoteCallbackList<IBluetoothLeAudioCallback>();
 
         int tmapRoleMask =
@@ -330,6 +337,8 @@
         mBondStateChangedReceiver = null;
         unregisterReceiver(mConnectionStateChangedReceiver);
         mConnectionStateChangedReceiver = null;
+        unregisterReceiver(mMuteStateChangedReceiver);
+        mMuteStateChangedReceiver = null;
 
         // Destroy state machines and stop handler thread
         synchronized (mStateMachines) {
@@ -1542,6 +1551,39 @@
         }
     }
 
+    private synchronized boolean isSilentModeEnabled() {
+        return mStoredRingerMode != AudioManager.RINGER_MODE_NORMAL;
+    }
+
+    private class MuteStateChangedReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (!AudioManager.RINGER_MODE_CHANGED_ACTION.equals(intent.getAction())) {
+                return;
+            }
+
+            final String action = intent.getAction();
+            if (action.equals(AudioManager.RINGER_MODE_CHANGED_ACTION)) {
+                int ringerMode = intent.getIntExtra(AudioManager.EXTRA_RINGER_MODE, -1);
+
+                if (ringerMode < 0 || ringerMode == mStoredRingerMode) return;
+
+                mStoredRingerMode = ringerMode;
+                int activeGroupId = getActiveGroupId();
+                if (activeGroupId == LE_AUDIO_GROUP_ID_INVALID) return;
+
+                VolumeControlService service = mServiceFactory.getVolumeControlService();
+                if (service == null) return;
+
+                if (isSilentModeEnabled()) {
+                    service.muteGroup(activeGroupId);
+                } else {
+                    service.unmuteGroup(activeGroupId);
+                }
+            }
+        }
+    }
+
    /**
      * Check whether can connect to a peer device.
      * The check considers a number of factors during the evaluation.
diff --git a/android/app/src/com/android/bluetooth/vc/VolumeControlNativeInterface.java b/android/app/src/com/android/bluetooth/vc/VolumeControlNativeInterface.java
index 421405c..0a50c6d 100644
--- a/android/app/src/com/android/bluetooth/vc/VolumeControlNativeInterface.java
+++ b/android/app/src/com/android/bluetooth/vc/VolumeControlNativeInterface.java
@@ -117,6 +117,44 @@
         setVolumeGroupNative(groupId, volume);
     }
 
+     /**
+     * Mute the VolumeControl volume
+     * @param device
+     * @param unmute
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public void mute(BluetoothDevice device) {
+        muteNative(getByteAddress(device));
+    }
+
+    /**
+     * Mute the VolumeControl volume in the group
+     * @param groupId
+     * @param unmute
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public void muteGroup(int groupId) {
+        muteGroupNative(groupId);
+    }
+
+    /**
+     * Unmute the VolumeControl volume
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public void unmute(BluetoothDevice device) {
+        unmuteNative(getByteAddress(device));
+    }
+
+     /**
+     * Unmute the VolumeControl volume group
+     * @param groupId
+     * @param unmute
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public void unmuteGroup(int groupId) {
+        unmuteGroupNative(groupId);
+    }
+
     /**
      * Gets external audio output volume offset from a remote device.
      *
@@ -333,6 +371,10 @@
     private native boolean disconnectVolumeControlNative(byte[] address);
     private native void setVolumeNative(byte[] address, int volume);
     private native void setVolumeGroupNative(int groupId, int volume);
+    private native void muteNative(byte[] address);
+    private native void muteGroupNative(int groupId);
+    private native void unmuteNative(byte[] address);
+    private native void unmuteGroupNative(int groupId);
     private native boolean getExtAudioOutVolumeOffsetNative(byte[] address, int externalOutputId);
     private native boolean setExtAudioOutVolumeOffsetNative(byte[] address, int externalOutputId,
                                                                 int offset);
diff --git a/android/app/src/com/android/bluetooth/vc/VolumeControlService.java b/android/app/src/com/android/bluetooth/vc/VolumeControlService.java
index 5b03839..e04c2e6 100644
--- a/android/app/src/com/android/bluetooth/vc/VolumeControlService.java
+++ b/android/app/src/com/android/bluetooth/vc/VolumeControlService.java
@@ -582,6 +582,34 @@
         mVolumeControlNativeInterface.setVolumeGroup(groupId, volume);
     }
 
+    /**
+     * {@hide}
+     */
+    public void mute(BluetoothDevice device) {
+        mVolumeControlNativeInterface.mute(device);
+    }
+
+    /**
+     * {@hide}
+     */
+    public void muteGroup(int groupId) {
+        mVolumeControlNativeInterface.muteGroup(groupId);
+    }
+
+    /**
+     * {@hide}
+     */
+    public void unmute(BluetoothDevice device) {
+        mVolumeControlNativeInterface.unmute(device);
+    }
+
+    /**
+     * {@hide}
+     */
+    public void unmuteGroup(int groupId) {
+        mVolumeControlNativeInterface.unmuteGroup(groupId);
+    }
+
     void handleVolumeControlChanged(BluetoothDevice device, int groupId,
                                     int volume, boolean mute, boolean isAutonomous) {
         if (!isAutonomous) {
@@ -1114,6 +1142,76 @@
         }
 
         @Override
+        public void mute(BluetoothDevice device,  AttributionSource source,
+                SynchronousResultReceiver receiver) {
+            try {
+                Objects.requireNonNull(device, "device cannot be null");
+                Objects.requireNonNull(source, "source cannot be null");
+                Objects.requireNonNull(receiver, "receiver cannot be null");
+
+                VolumeControlService service = getService(source);
+                if (service != null) {
+                    service.mute(device);
+                }
+                receiver.send(null);
+            } catch (RuntimeException e) {
+                receiver.propagateException(e);
+            }
+        }
+
+        @Override
+        public void muteGroup(int groupId, AttributionSource source,
+                SynchronousResultReceiver receiver) {
+            try {
+                Objects.requireNonNull(source, "source cannot be null");
+                Objects.requireNonNull(receiver, "receiver cannot be null");
+
+                VolumeControlService service = getService(source);
+                if (service != null) {
+                    service.muteGroup(groupId);
+                }
+                receiver.send(null);
+            } catch (RuntimeException e) {
+                receiver.propagateException(e);
+            }
+        }
+
+        @Override
+        public void unmute(BluetoothDevice device,  AttributionSource source,
+                SynchronousResultReceiver receiver) {
+            try {
+                Objects.requireNonNull(device, "device cannot be null");
+                Objects.requireNonNull(source, "source cannot be null");
+                Objects.requireNonNull(receiver, "receiver cannot be null");
+
+                VolumeControlService service = getService(source);
+                if (service != null) {
+                    service.unmute(device);
+                }
+                receiver.send(null);
+            } catch (RuntimeException e) {
+                receiver.propagateException(e);
+            }
+        }
+
+        @Override
+        public void unmuteGroup(int groupId,  AttributionSource source,
+                SynchronousResultReceiver receiver) {
+            try {
+                Objects.requireNonNull(source, "source cannot be null");
+                Objects.requireNonNull(receiver, "receiver cannot be null");
+
+                VolumeControlService service = getService(source);
+                if (service != null) {
+                    service.unmuteGroup(groupId);
+                }
+                receiver.send(null);
+            } catch (RuntimeException e) {
+                receiver.propagateException(e);
+            }
+        }
+
+        @Override
         public void registerCallback(IBluetoothVolumeControlCallback callback,
                 AttributionSource source, SynchronousResultReceiver receiver) {
             try {
diff --git a/system/binder/android/bluetooth/IBluetoothVolumeControl.aidl b/system/binder/android/bluetooth/IBluetoothVolumeControl.aidl
index 9049260..eb6ddd1 100644
--- a/system/binder/android/bluetooth/IBluetoothVolumeControl.aidl
+++ b/system/binder/android/bluetooth/IBluetoothVolumeControl.aidl
@@ -52,6 +52,16 @@
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)")
     void setVolumeGroup(int group_id, int volume, in AttributionSource attributionSource, in SynchronousResultReceiver receiver);
 
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)")
+    void mute(in BluetoothDevice device, in AttributionSource attributionSource, in SynchronousResultReceiver receiver);
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)")
+    void muteGroup(int group_id, in AttributionSource attributionSource, in SynchronousResultReceiver receiver);
+
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)")
+    void unmute(in BluetoothDevice device, in AttributionSource attributionSource, in SynchronousResultReceiver receiver);
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)")
+    void unmuteGroup(int group_id, in AttributionSource attributionSource, in SynchronousResultReceiver receiver);
+
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT,android.Manifest.permission.BLUETOOTH_PRIVILEGED})")
     void registerCallback(in IBluetoothVolumeControlCallback callback, in AttributionSource attributionSource, in SynchronousResultReceiver receiver);
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT,android.Manifest.permission.BLUETOOTH_PRIVILEGED})")
diff --git a/system/bta/Android.bp b/system/bta/Android.bp
index fb40322..146b821 100644
--- a/system/bta/Android.bp
+++ b/system/bta/Android.bp
@@ -400,6 +400,7 @@
         "packages/modules/Bluetooth/system",
         "packages/modules/Bluetooth/system/bta/include",
         "packages/modules/Bluetooth/system/bta/test/common",
+        "packages/modules/Bluetooth/system/gd",
         "packages/modules/Bluetooth/system/stack/include",
     ],
     srcs : [
diff --git a/system/bta/include/bta_vc_api.h b/system/bta/include/bta_vc_api.h
index b8913eb..3b2db1b 100644
--- a/system/bta/include/bta_vc_api.h
+++ b/system/bta/include/bta_vc_api.h
@@ -39,6 +39,9 @@
   virtual void Disconnect(const RawAddress& address) = 0;
   virtual void SetVolume(std::variant<RawAddress, int> addr_or_group_id,
                          uint8_t volume) = 0;
+  virtual void Mute(std::variant<RawAddress, int> addr_or_group_id) = 0;
+  virtual void UnMute(std::variant<RawAddress, int> addr_or_group_id) = 0;
+
   /* Volume Offset Control Service (VOCS) */
   virtual void SetExtAudioOutVolumeOffset(const RawAddress& address,
                                           uint8_t ext_output_id,
diff --git a/system/bta/vc/vc.cc b/system/bta/vc/vc.cc
index 4ecbb8e..d81f00c 100644
--- a/system/bta/vc/vc.cc
+++ b/system/bta/vc/vc.cc
@@ -32,6 +32,8 @@
 #include "bta_vc_api.h"
 #include "btif_storage.h"
 #include "devices.h"
+#include "gd/common/strings.h"
+#include "osi/include/log.h"
 #include "osi/include/osi.h"
 #include "types/bluetooth/uuid.h"
 #include "types/raw_address.h"
@@ -711,6 +713,61 @@
                                      is_autonomous, opcode, arguments, devices);
   }
 
+  void MuteUnmute(std::variant<RawAddress, int> addr_or_group_id, bool mute) {
+    std::vector<uint8_t> arg;
+
+    uint8_t opcode = mute ? kControlPointOpcodeMute : kControlPointOpcodeUnmute;
+
+    if (std::holds_alternative<RawAddress>(addr_or_group_id)) {
+      LOG_DEBUG("Address: %s: ",
+                (std::get<RawAddress>(addr_or_group_id)).ToString().c_str());
+      std::vector<RawAddress> devices = {
+          std::get<RawAddress>(addr_or_group_id)};
+
+      PrepareVolumeControlOperation(devices, bluetooth::groups::kGroupUnknown,
+                                    false, opcode, arg);
+    } else {
+      /* Handle group change */
+      auto group_id = std::get<int>(addr_or_group_id);
+      LOG_DEBUG("group: %d", group_id);
+      auto csis_api = CsisClient::Get();
+      if (!csis_api) {
+        LOG(ERROR) << __func__ << " Csis is not there";
+        return;
+      }
+
+      auto devices = csis_api->GetDeviceList(group_id);
+      for (auto it = devices.begin(); it != devices.end();) {
+        auto dev = volume_control_devices_.FindByAddress(*it);
+        if (!dev || !dev->IsConnected()) {
+          it = devices.erase(it);
+        } else {
+          it++;
+        }
+      }
+
+      if (devices.empty()) {
+        LOG(ERROR) << __func__ << " group id : " << group_id
+                   << " is not connected? ";
+        return;
+      }
+
+      PrepareVolumeControlOperation(devices, group_id, false, opcode, arg);
+    }
+
+    StartQueueOperation();
+  }
+
+  void Mute(std::variant<RawAddress, int> addr_or_group_id) override {
+    LOG_DEBUG();
+    MuteUnmute(addr_or_group_id, true /* mute */);
+  }
+
+  void UnMute(std::variant<RawAddress, int> addr_or_group_id) override {
+    LOG_DEBUG();
+    MuteUnmute(addr_or_group_id, false /* mute */);
+  }
+
   void SetVolume(std::variant<RawAddress, int> addr_or_group_id,
                  uint8_t volume) override {
     DLOG(INFO) << __func__ << " vol: " << +volume;
diff --git a/system/bta/vc/vc_test.cc b/system/bta/vc/vc_test.cc
index e90ceee..7225f59 100644
--- a/system/bta/vc/vc_test.cc
+++ b/system/bta/vc/vc_test.cc
@@ -919,6 +919,20 @@
   VolumeControl::Get()->SetVolume(test_address, 0x10);
 }
 
+TEST_F(VolumeControlValueSetTest, test_mute) {
+  std::vector<uint8_t> mute({0x06, 0x00});
+  EXPECT_CALL(gatt_queue,
+              WriteCharacteristic(conn_id, 0x0024, mute, GATT_WRITE, _, _));
+  VolumeControl::Get()->Mute(test_address);
+}
+
+TEST_F(VolumeControlValueSetTest, test_unmute) {
+  std::vector<uint8_t> unmute({0x05, 0x00});
+  EXPECT_CALL(gatt_queue,
+              WriteCharacteristic(conn_id, 0x0024, unmute, GATT_WRITE, _, _));
+  VolumeControl::Get()->UnMute(test_address);
+}
+
 TEST_F(VolumeControlValueSetTest, test_set_ext_audio_out_volume_offset) {
   std::vector<uint8_t> expected_data({0x01, 0x00, 0x34, 0x12});
   EXPECT_CALL(gatt_queue, WriteCharacteristic(conn_id, 0x0088, expected_data,
diff --git a/system/btif/src/btif_vc.cc b/system/btif/src/btif_vc.cc
index 28aaa34..00b6b8e 100644
--- a/system/btif/src/btif_vc.cc
+++ b/system/btif/src/btif_vc.cc
@@ -143,6 +143,20 @@
                                       std::move(addr_or_group_id), volume));
   }
 
+  void Mute(std::variant<RawAddress, int> addr_or_group_id) override {
+    DVLOG(2) << __func__;
+    do_in_main_thread(
+        FROM_HERE, Bind(&VolumeControl::Mute, Unretained(VolumeControl::Get()),
+                        std::move(addr_or_group_id)));
+  }
+
+  void Unmute(std::variant<RawAddress, int> addr_or_group_id) override {
+    DVLOG(2) << __func__;
+    do_in_main_thread(FROM_HERE, Bind(&VolumeControl::UnMute,
+                                      Unretained(VolumeControl::Get()),
+                                      std::move(addr_or_group_id)));
+  }
+
   void RemoveDevice(const RawAddress& address) override {
     DVLOG(2) << __func__ << " address: " << address;
 
diff --git a/system/include/hardware/bt_vc.h b/system/include/hardware/bt_vc.h
index 18fd764..090ff9c 100644
--- a/system/include/hardware/bt_vc.h
+++ b/system/include/hardware/bt_vc.h
@@ -86,6 +86,12 @@
   /** Set the volume */
   virtual void SetVolume(std::variant<RawAddress, int> addr_or_group_id,
                          uint8_t volume) = 0;
+  /** Mute the volume */
+  virtual void Mute(std::variant<RawAddress, int> addr_or_group_id) = 0;
+
+  /** Unmute the volume */
+  virtual void Unmute(std::variant<RawAddress, int> addr_or_group_id) = 0;
+
   virtual void GetExtAudioOutVolumeOffset(const RawAddress& address,
                                           uint8_t ext_output_id) = 0;
   virtual void SetExtAudioOutVolumeOffset(const RawAddress& address,