1 /*
2  * Copyright 2021 HIMSA II K/S - www.himsa.com.
3  * Represented by EHIMA - www.ehima.com
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 
18 /* Volume Control Interface */
19 
20 #include <base/functional/bind.h>
21 #include <base/location.h>
22 #include <bluetooth/log.h>
23 #include <hardware/bluetooth.h>
24 #include <hardware/bt_vc.h>
25 
26 #include "bta_vc_api.h"
27 #include "btif_common.h"
28 #include "btif_profile_storage.h"
29 #include "stack/include/main_thread.h"
30 #include "types/raw_address.h"
31 
32 using base::Bind;
33 using base::Unretained;
34 using bluetooth::vc::ConnectionState;
35 using bluetooth::vc::VolumeControlCallbacks;
36 using bluetooth::vc::VolumeControlInterface;
37 using namespace bluetooth;
38 
39 namespace {
40 std::unique_ptr<VolumeControlInterface> vc_instance;
41 std::atomic_bool initialized = false;
42 
43 class VolumeControlInterfaceImpl : public VolumeControlInterface,
44                                    public VolumeControlCallbacks {
45   ~VolumeControlInterfaceImpl() override = default;
46 
Init(VolumeControlCallbacks * callbacks)47   void Init(VolumeControlCallbacks* callbacks) override {
48     this->callbacks_ = callbacks;
49     do_in_main_thread(
50         FROM_HERE,
51         Bind(&VolumeControl::Initialize, this,
52              jni_thread_wrapper(
53                  Bind(&btif_storage_load_bonded_volume_control_devices))));
54 
55     /* It might be not yet initialized, but setting this flag here is safe,
56      * because other calls will check this and the native instance
57      */
58     initialized = true;
59   }
60 
OnConnectionState(ConnectionState state,const RawAddress & address)61   void OnConnectionState(ConnectionState state,
62                          const RawAddress& address) override {
63     do_in_jni_thread(Bind(&VolumeControlCallbacks::OnConnectionState,
64                           Unretained(callbacks_), state, address));
65   }
66 
OnVolumeStateChanged(const RawAddress & address,uint8_t volume,bool mute,bool isAutonomous)67   void OnVolumeStateChanged(const RawAddress& address, uint8_t volume,
68                             bool mute, bool isAutonomous) override {
69     do_in_jni_thread(Bind(&VolumeControlCallbacks::OnVolumeStateChanged,
70                           Unretained(callbacks_), address, volume, mute,
71                           isAutonomous));
72   }
73 
OnGroupVolumeStateChanged(int group_id,uint8_t volume,bool mute,bool isAutonomous)74   void OnGroupVolumeStateChanged(int group_id, uint8_t volume, bool mute,
75                                  bool isAutonomous) override {
76     do_in_jni_thread(Bind(&VolumeControlCallbacks::OnGroupVolumeStateChanged,
77                           Unretained(callbacks_), group_id, volume, mute,
78                           isAutonomous));
79   }
80 
OnDeviceAvailable(const RawAddress & address,uint8_t num_offset)81   void OnDeviceAvailable(const RawAddress& address,
82                          uint8_t num_offset) override {
83     do_in_jni_thread(Bind(&VolumeControlCallbacks::OnDeviceAvailable,
84                           Unretained(callbacks_), address, num_offset));
85   }
86 
87   /* Callbacks for Volume Offset Control Service (VOCS) - Extended Audio Outputs
88    */
89 
OnExtAudioOutVolumeOffsetChanged(const RawAddress & address,uint8_t ext_output_id,int16_t offset)90   void OnExtAudioOutVolumeOffsetChanged(const RawAddress& address,
91                                         uint8_t ext_output_id,
92                                         int16_t offset) override {
93     do_in_jni_thread(
94         Bind(&VolumeControlCallbacks::OnExtAudioOutVolumeOffsetChanged,
95              Unretained(callbacks_), address, ext_output_id, offset));
96   }
97 
OnExtAudioOutLocationChanged(const RawAddress & address,uint8_t ext_output_id,uint32_t location)98   void OnExtAudioOutLocationChanged(const RawAddress& address,
99                                     uint8_t ext_output_id,
100                                     uint32_t location) override {
101     do_in_jni_thread(Bind(&VolumeControlCallbacks::OnExtAudioOutLocationChanged,
102                           Unretained(callbacks_), address, ext_output_id,
103                           location));
104   }
105 
OnExtAudioOutDescriptionChanged(const RawAddress & address,uint8_t ext_output_id,std::string descr)106   void OnExtAudioOutDescriptionChanged(const RawAddress& address,
107                                        uint8_t ext_output_id,
108                                        std::string descr) override {
109     do_in_jni_thread(
110         Bind(&VolumeControlCallbacks::OnExtAudioOutDescriptionChanged,
111              Unretained(callbacks_), address, ext_output_id, descr));
112   }
113 
Connect(const RawAddress & address)114   void Connect(const RawAddress& address) override {
115     if (!initialized || !VolumeControl::IsVolumeControlRunning()) {
116       log::verbose(
117           "call ignored, due to already started cleanup procedure or service "
118           "being not read");
119       return;
120     }
121 
122     do_in_main_thread(FROM_HERE,
123                       Bind(&VolumeControl::Connect,
124                            Unretained(VolumeControl::Get()), address));
125   }
126 
Disconnect(const RawAddress & address)127   void Disconnect(const RawAddress& address) override {
128     if (!initialized || !VolumeControl::IsVolumeControlRunning()) {
129       log::verbose(
130           "call ignored, due to already started cleanup procedure or service "
131           "being not read");
132       return;
133     }
134     do_in_main_thread(FROM_HERE,
135                       Bind(&VolumeControl::Disconnect,
136                            Unretained(VolumeControl::Get()), address));
137   }
138 
SetVolume(std::variant<RawAddress,int> addr_or_group_id,uint8_t volume)139   void SetVolume(std::variant<RawAddress, int> addr_or_group_id,
140                  uint8_t volume) override {
141     if (!initialized || !VolumeControl::IsVolumeControlRunning()) {
142       log::verbose(
143           "call ignored, due to already started cleanup procedure or service "
144           "being not read");
145       return;
146     }
147 
148     do_in_main_thread(FROM_HERE, Bind(&VolumeControl::SetVolume,
149                                       Unretained(VolumeControl::Get()),
150                                       std::move(addr_or_group_id), volume));
151   }
152 
Mute(std::variant<RawAddress,int> addr_or_group_id)153   void Mute(std::variant<RawAddress, int> addr_or_group_id) override {
154     if (!initialized || !VolumeControl::IsVolumeControlRunning()) {
155       log::verbose(
156           "call ignored, due to already started cleanup procedure or service "
157           "being not read");
158       return;
159     }
160 
161     do_in_main_thread(
162         FROM_HERE, Bind(&VolumeControl::Mute, Unretained(VolumeControl::Get()),
163                         std::move(addr_or_group_id)));
164   }
165 
Unmute(std::variant<RawAddress,int> addr_or_group_id)166   void Unmute(std::variant<RawAddress, int> addr_or_group_id) override {
167     if (!initialized || !VolumeControl::IsVolumeControlRunning()) {
168       log::verbose(
169           "call ignored, due to already started cleanup procedure or service "
170           "being not read");
171       return;
172     }
173 
174     do_in_main_thread(FROM_HERE, Bind(&VolumeControl::UnMute,
175                                       Unretained(VolumeControl::Get()),
176                                       std::move(addr_or_group_id)));
177   }
178 
RemoveDevice(const RawAddress & address)179   void RemoveDevice(const RawAddress& address) override {
180     if (!initialized || !VolumeControl::IsVolumeControlRunning()) {
181       log::verbose(
182           "call ignored, due to already started cleanup procedure or service "
183           "being not read");
184       return;
185     }
186 
187     /* RemoveDevice can be called on devices that don't have HA enabled */
188     if (VolumeControl::IsVolumeControlRunning()) {
189       do_in_main_thread(FROM_HERE,
190                         Bind(&VolumeControl::Remove,
191                              Unretained(VolumeControl::Get()), address));
192     }
193   }
194 
GetExtAudioOutVolumeOffset(const RawAddress & address,uint8_t ext_output_id)195   void GetExtAudioOutVolumeOffset(const RawAddress& address,
196                                   uint8_t ext_output_id) override {
197     if (!initialized || !VolumeControl::IsVolumeControlRunning()) {
198       log::verbose(
199           "call ignored, due to already started cleanup procedure or service "
200           "being not read");
201       return;
202     }
203 
204     do_in_main_thread(
205         FROM_HERE,
206         Bind(&VolumeControl::GetExtAudioOutVolumeOffset,
207              Unretained(VolumeControl::Get()), address, ext_output_id));
208   }
209 
SetExtAudioOutVolumeOffset(const RawAddress & address,uint8_t ext_output_id,int16_t offset_val)210   void SetExtAudioOutVolumeOffset(const RawAddress& address,
211                                   uint8_t ext_output_id,
212                                   int16_t offset_val) override {
213     if (!initialized || !VolumeControl::IsVolumeControlRunning()) {
214       log::verbose(
215           "call ignored, due to already started cleanup procedure or service "
216           "being not read");
217       return;
218     }
219 
220     do_in_main_thread(FROM_HERE,
221                       Bind(&VolumeControl::SetExtAudioOutVolumeOffset,
222                            Unretained(VolumeControl::Get()), address,
223                            ext_output_id, offset_val));
224   }
225 
GetExtAudioOutLocation(const RawAddress & address,uint8_t ext_output_id)226   void GetExtAudioOutLocation(const RawAddress& address,
227                               uint8_t ext_output_id) override {
228     if (!initialized || !VolumeControl::IsVolumeControlRunning()) {
229       log::verbose(
230           "call ignored, due to already started cleanup procedure or service "
231           "being not read");
232       return;
233     }
234 
235     do_in_main_thread(FROM_HERE, Bind(&VolumeControl::GetExtAudioOutLocation,
236                                       Unretained(VolumeControl::Get()), address,
237                                       ext_output_id));
238   }
239 
SetExtAudioOutLocation(const RawAddress & address,uint8_t ext_output_id,uint32_t location)240   void SetExtAudioOutLocation(const RawAddress& address, uint8_t ext_output_id,
241                               uint32_t location) override {
242     if (!initialized || !VolumeControl::IsVolumeControlRunning()) {
243       log::verbose(
244           "call ignored, due to already started cleanup procedure or service "
245           "being not read");
246       return;
247     }
248 
249     do_in_main_thread(FROM_HERE, Bind(&VolumeControl::SetExtAudioOutLocation,
250                                       Unretained(VolumeControl::Get()), address,
251                                       ext_output_id, location));
252   }
253 
GetExtAudioOutDescription(const RawAddress & address,uint8_t ext_output_id)254   void GetExtAudioOutDescription(const RawAddress& address,
255                                  uint8_t ext_output_id) override {
256     if (!initialized || !VolumeControl::IsVolumeControlRunning()) {
257       log::verbose(
258           "call ignored, due to already started cleanup procedure or service "
259           "being not read");
260       return;
261     }
262 
263     do_in_main_thread(FROM_HERE, Bind(&VolumeControl::GetExtAudioOutDescription,
264                                       Unretained(VolumeControl::Get()), address,
265                                       ext_output_id));
266   }
267 
SetExtAudioOutDescription(const RawAddress & address,uint8_t ext_output_id,std::string descr)268   void SetExtAudioOutDescription(const RawAddress& address,
269                                  uint8_t ext_output_id,
270                                  std::string descr) override {
271     if (!initialized || !VolumeControl::IsVolumeControlRunning()) {
272       log::verbose(
273           "call ignored, due to already started cleanup procedure or service "
274           "being not read");
275       return;
276     }
277 
278     do_in_main_thread(FROM_HERE, Bind(&VolumeControl::SetExtAudioOutDescription,
279                                       Unretained(VolumeControl::Get()), address,
280                                       ext_output_id, descr));
281   }
282 
Cleanup(void)283   void Cleanup(void) override {
284     if (!initialized || !VolumeControl::IsVolumeControlRunning()) {
285       log::verbose(
286           "call ignored, due to already started cleanup procedure or service "
287           "being not read");
288       return;
289     }
290 
291     initialized = false;
292     do_in_main_thread(FROM_HERE, Bind(&VolumeControl::CleanUp));
293   }
294 
295  private:
296   VolumeControlCallbacks* callbacks_;
297 };
298 
299 } /* namespace */
300 
btif_volume_control_get_interface(void)301 VolumeControlInterface* btif_volume_control_get_interface(void) {
302   if (!vc_instance) vc_instance.reset(new VolumeControlInterfaceImpl());
303 
304   return vc_instance.get();
305 }
306