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