1 /*
2  * Copyright 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at:
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include "le_audio_health_status.h"
18 
19 #include <bluetooth/log.h>
20 #include <frameworks/proto_logging/stats/enums/bluetooth/enums.pb.h>
21 #include <stdio.h>
22 
23 #include <algorithm>
24 #include <sstream>
25 #include <utility>
26 #include <vector>
27 
28 #include "bta/include/bta_groups.h"
29 #include "common/strings.h"
30 #include "device_groups.h"
31 #include "devices.h"
32 #include "hardware/bt_le_audio.h"
33 #include "main/shim/metrics_api.h"
34 #include "os/logging/log_adapter.h"
35 #include "types/raw_address.h"
36 
37 using bluetooth::common::ToString;
38 using bluetooth::groups::kGroupUnknown;
39 using bluetooth::le_audio::LeAudioDevice;
40 using bluetooth::le_audio::LeAudioHealthStatus;
41 using bluetooth::le_audio::LeAudioRecommendationActionCb;
42 
43 namespace bluetooth::le_audio {
44 class LeAudioHealthStatusImpl;
45 LeAudioHealthStatusImpl* instance;
46 
47 class LeAudioHealthStatusImpl : public LeAudioHealthStatus {
48 public:
LeAudioHealthStatusImpl(void)49   LeAudioHealthStatusImpl(void) { log::debug("Initiated"); }
50 
~LeAudioHealthStatusImpl(void)51   ~LeAudioHealthStatusImpl(void) { clear_module(); }
52 
RegisterCallback(LeAudioRecommendationActionCb cb)53   void RegisterCallback(LeAudioRecommendationActionCb cb) override {
54     register_callback(std::move(cb));
55   }
56 
RemoveStatistics(const RawAddress & address,int group_id)57   void RemoveStatistics(const RawAddress& address, int group_id) override {
58     log::debug("{}, group_id: {}", address, group_id);
59     remove_device(address);
60     remove_group(group_id);
61   }
62 
AddStatisticForDevice(const LeAudioDevice * device,LeAudioHealthDeviceStatType type)63   void AddStatisticForDevice(const LeAudioDevice* device,
64                              LeAudioHealthDeviceStatType type) override {
65     if (device == nullptr) {
66       log::error("device is null");
67       return;
68     }
69 
70     const RawAddress& address = device->address_;
71     log::debug("{}, {}", address, ToString(type));
72 
73     auto dev = find_device(address);
74     if (dev == nullptr) {
75       add_device(address);
76       dev = find_device(address);
77       if (dev == nullptr) {
78         log::error("Could not add device {}", address);
79         return;
80       }
81     }
82     // log counter metrics
83     log_counter_metrics_for_device(type, device->allowlist_flag_);
84 
85     LeAudioHealthBasedAction action;
86     switch (type) {
87       case LeAudioHealthDeviceStatType::VALID_DB:
88         dev->is_valid_service_ = true;
89         action = LeAudioHealthBasedAction::NONE;
90         break;
91       case LeAudioHealthDeviceStatType::INVALID_DB:
92         dev->is_valid_service_ = false;
93         action = LeAudioHealthBasedAction::DISABLE;
94         break;
95       case LeAudioHealthDeviceStatType::INVALID_CSIS:
96         dev->is_valid_group_member_ = false;
97         action = LeAudioHealthBasedAction::DISABLE;
98         break;
99       case LeAudioHealthDeviceStatType::VALID_CSIS:
100         dev->is_valid_group_member_ = true;
101         action = LeAudioHealthBasedAction::NONE;
102         break;
103     }
104 
105     if (dev->latest_recommendation_ != action) {
106       dev->latest_recommendation_ = action;
107       send_recommendation_for_device(address, action);
108       return;
109     }
110   }
111 
AddStatisticForGroup(const LeAudioDeviceGroup * device_group,LeAudioHealthGroupStatType type)112   void AddStatisticForGroup(const LeAudioDeviceGroup* device_group,
113                             LeAudioHealthGroupStatType type) override {
114     if (device_group == nullptr) {
115       log::error("device_group is null");
116       return;
117     }
118 
119     int group_id = device_group->group_id_;
120     log::debug("group_id: {}, {}", group_id, ToString(type));
121 
122     auto group = find_group(group_id);
123     if (group == nullptr) {
124       add_group(group_id);
125       group = find_group(group_id);
126       if (group == nullptr) {
127         log::error("Could not add group {}", group_id);
128         return;
129       }
130     }
131 
132     LeAudioDevice* device = device_group->GetFirstDevice();
133     if (device == nullptr) {
134       log::error("Front device is null. Number of devices: {}", device_group->Size());
135       return;
136     }
137     // log counter metrics
138     log_counter_metrics_for_group(type, device->allowlist_flag_);
139 
140     switch (type) {
141       case LeAudioHealthGroupStatType::STREAM_CREATE_SUCCESS:
142         group->stream_success_cnt_++;
143         if (group->latest_recommendation_ == LeAudioHealthBasedAction::NONE) {
144           return;
145         }
146         break;
147       case LeAudioHealthGroupStatType::STREAM_CREATE_CIS_FAILED:
148         group->stream_cis_failures_cnt_++;
149         group->stream_failures_cnt_++;
150         break;
151       case LeAudioHealthGroupStatType::STREAM_CREATE_SIGNALING_FAILED:
152         group->stream_signaling_failures_cnt_++;
153         group->stream_failures_cnt_++;
154         break;
155       case LeAudioHealthGroupStatType::STREAM_CONTEXT_NOT_AVAILABLE:
156         group->stream_context_not_avail_cnt_++;
157         break;
158     }
159 
160     LeAudioHealthBasedAction action = LeAudioHealthBasedAction::NONE;
161     if (group->stream_success_cnt_ == 0) {
162       /* Never succeed in stream creation */
163       if (group->stream_failures_cnt_ >= MAX_ALLOWED_FAILURES_IN_A_ROW_WITHOUT_SUCCESS) {
164         action = LeAudioHealthBasedAction::DISABLE;
165       } else if (group->stream_context_not_avail_cnt_ >=
166                  MAX_ALLOWED_FAILURES_IN_A_ROW_WITHOUT_SUCCESS) {
167         action = LeAudioHealthBasedAction::INACTIVATE_GROUP;
168         group->stream_context_not_avail_cnt_ = 0;
169       }
170     } else {
171       /* Had some success before */
172       if ((100 * group->stream_failures_cnt_ / group->stream_success_cnt_) >=
173           THRESHOLD_FOR_DISABLE_CONSIDERATION) {
174         action = LeAudioHealthBasedAction::CONSIDER_DISABLING;
175       } else if (group->stream_context_not_avail_cnt_ >=
176                  MAX_ALLOWED_FAILURES_IN_A_ROW_WITHOUT_SUCCESS) {
177         action = LeAudioHealthBasedAction::INACTIVATE_GROUP;
178         group->stream_context_not_avail_cnt_ = 0;
179       }
180     }
181 
182     if (group->latest_recommendation_ != action) {
183       group->latest_recommendation_ = action;
184       send_recommendation_for_group(group_id, action);
185     }
186   }
187 
Dump(int fd)188   void Dump(int fd) {
189     dprintf(fd, "  LeAudioHealthStats: \n    groups:");
190     for (const auto& g : group_stats_) {
191       dumpsys_group(fd, g);
192     }
193     dprintf(fd, "\n    devices: ");
194     for (const auto& dev : devices_stats_) {
195       dumpsys_dev(fd, dev);
196     }
197     dprintf(fd, "\n");
198   }
199 
200 private:
201   static constexpr int MAX_ALLOWED_FAILURES_IN_A_ROW_WITHOUT_SUCCESS = 3;
202   static constexpr int THRESHOLD_FOR_DISABLE_CONSIDERATION = 70;
203 
204   std::vector<LeAudioRecommendationActionCb> callbacks_;
205   std::vector<device_stats> devices_stats_;
206   std::vector<group_stats> group_stats_;
207 
dumpsys_group(int fd,const group_stats & group)208   void dumpsys_group(int fd, const group_stats& group) {
209     std::stringstream stream;
210 
211     stream << "\n group_id: " << group.group_id_ << ": " << group.latest_recommendation_
212            << ", success: " << group.stream_success_cnt_
213            << ", fail total: " << group.stream_failures_cnt_
214            << ", fail cis: " << group.stream_cis_failures_cnt_
215            << ", fail signaling: " << group.stream_signaling_failures_cnt_
216            << ", context not avail: " << group.stream_context_not_avail_cnt_;
217 
218     dprintf(fd, "%s", stream.str().c_str());
219   }
220 
dumpsys_dev(int fd,const device_stats & dev)221   void dumpsys_dev(int fd, const device_stats& dev) {
222     std::stringstream stream;
223 
224     stream << "\n " << ADDRESS_TO_LOGGABLE_STR(dev.address_) << ": " << dev.latest_recommendation_
225            << (dev.is_valid_service_ ? " service: OK" : " service : NOK")
226            << (dev.is_valid_group_member_ ? " csis: OK" : " csis : NOK");
227 
228     dprintf(fd, "%s", stream.str().c_str());
229   }
230 
clear_module(void)231   void clear_module(void) {
232     devices_stats_.clear();
233     group_stats_.clear();
234     callbacks_.clear();
235   }
236 
send_recommendation_for_device(const RawAddress & address,LeAudioHealthBasedAction recommendation)237   void send_recommendation_for_device(const RawAddress& address,
238                                       LeAudioHealthBasedAction recommendation) {
239     log::debug("{}, {}", address, ToString(recommendation));
240     /* Notify new user about known groups */
241     for (auto& cb : callbacks_) {
242       cb.Run(address, kGroupUnknown, recommendation);
243     }
244   }
245 
send_recommendation_for_group(int group_id,const LeAudioHealthBasedAction recommendation)246   void send_recommendation_for_group(int group_id, const LeAudioHealthBasedAction recommendation) {
247     log::debug("group_id: {}, {}", group_id, ToString(recommendation));
248     /* Notify new user about known groups */
249     for (auto& cb : callbacks_) {
250       cb.Run(RawAddress::kEmpty, group_id, recommendation);
251     }
252   }
253 
add_device(const RawAddress & address)254   void add_device(const RawAddress& address) { devices_stats_.emplace_back(device_stats(address)); }
255 
add_group(int group_id)256   void add_group(int group_id) { group_stats_.emplace_back(group_stats(group_id)); }
257 
remove_group(int group_id)258   void remove_group(int group_id) {
259     if (group_id == kGroupUnknown) {
260       return;
261     }
262     auto iter = std::find_if(group_stats_.begin(), group_stats_.end(),
263                              [group_id](const auto& g) { return g.group_id_ == group_id; });
264     if (iter != group_stats_.end()) {
265       group_stats_.erase(iter);
266     }
267   }
268 
remove_device(const RawAddress & address)269   void remove_device(const RawAddress& address) {
270     auto iter = std::find_if(devices_stats_.begin(), devices_stats_.end(),
271                              [address](const auto& d) { return d.address_ == address; });
272     if (iter != devices_stats_.end()) {
273       devices_stats_.erase(iter);
274     }
275   }
276 
register_callback(LeAudioRecommendationActionCb cb)277   void register_callback(LeAudioRecommendationActionCb cb) { callbacks_.push_back(std::move(cb)); }
278 
find_device(const RawAddress & address)279   device_stats* find_device(const RawAddress& address) {
280     auto iter = std::find_if(devices_stats_.begin(), devices_stats_.end(),
281                              [address](const auto& d) { return d.address_ == address; });
282     if (iter == devices_stats_.end()) {
283       return nullptr;
284     }
285 
286     return &(*iter);
287   }
288 
find_group(int group_id)289   group_stats* find_group(int group_id) {
290     auto iter = std::find_if(group_stats_.begin(), group_stats_.end(),
291                              [group_id](const auto& g) { return g.group_id_ == group_id; });
292     if (iter == group_stats_.end()) {
293       return nullptr;
294     }
295 
296     return &(*iter);
297   }
298 
log_counter_metrics_for_device(LeAudioHealthDeviceStatType type,bool in_allowlist)299   void log_counter_metrics_for_device(LeAudioHealthDeviceStatType type, bool in_allowlist) {
300     log::debug("in_allowlist: {}, type: {}", in_allowlist, ToString(type));
301     android::bluetooth::CodePathCounterKeyEnum key;
302     if (in_allowlist) {
303       switch (type) {
304         case LeAudioHealthDeviceStatType::VALID_DB:
305         case LeAudioHealthDeviceStatType::VALID_CSIS:
306           key = android::bluetooth::CodePathCounterKeyEnum::
307                   LE_AUDIO_ALLOWLIST_DEVICE_HEALTH_STATUS_GOOD;
308           break;
309         case LeAudioHealthDeviceStatType::INVALID_DB:
310           key = android::bluetooth::CodePathCounterKeyEnum::
311                   LE_AUDIO_ALLOWLIST_DEVICE_HEALTH_STATUS_BAD_INVALID_DB;
312           break;
313         case LeAudioHealthDeviceStatType::INVALID_CSIS:
314           key = android::bluetooth::CodePathCounterKeyEnum::
315                   LE_AUDIO_ALLOWLIST_DEVICE_HEALTH_STATUS_BAD_INVALID_CSIS;
316           break;
317         default:
318           log::error("Metric unhandled {}", type);
319           return;
320       }
321     } else {
322       switch (type) {
323         case LeAudioHealthDeviceStatType::VALID_DB:
324         case LeAudioHealthDeviceStatType::VALID_CSIS:
325           key = android::bluetooth::CodePathCounterKeyEnum::
326                   LE_AUDIO_NONALLOWLIST_DEVICE_HEALTH_STATUS_GOOD;
327           break;
328         case LeAudioHealthDeviceStatType::INVALID_DB:
329           key = android::bluetooth::CodePathCounterKeyEnum::
330                   LE_AUDIO_NONALLOWLIST_DEVICE_HEALTH_STATUS_BAD_INVALID_DB;
331           break;
332         case LeAudioHealthDeviceStatType::INVALID_CSIS:
333           key = android::bluetooth::CodePathCounterKeyEnum::
334                   LE_AUDIO_NONALLOWLIST_DEVICE_HEALTH_STATUS_BAD_INVALID_CSIS;
335           break;
336         default:
337           log::error("Metric unhandled {}", type);
338           return;
339       }
340     }
341     bluetooth::shim::CountCounterMetrics(key, 1);
342   }
343 
log_counter_metrics_for_group(LeAudioHealthGroupStatType type,bool in_allowlist)344   void log_counter_metrics_for_group(LeAudioHealthGroupStatType type, bool in_allowlist) {
345     log::debug("in_allowlist: {}, type: {}", in_allowlist, ToString(type));
346     android::bluetooth::CodePathCounterKeyEnum key;
347     if (in_allowlist) {
348       switch (type) {
349         case LeAudioHealthGroupStatType::STREAM_CREATE_SUCCESS:
350           key = android::bluetooth::CodePathCounterKeyEnum::
351                   LE_AUDIO_ALLOWLIST_GROUP_HEALTH_STATUS_GOOD;
352           break;
353         case LeAudioHealthGroupStatType::STREAM_CREATE_CIS_FAILED:
354           key = android::bluetooth::CodePathCounterKeyEnum::
355                   LE_AUDIO_ALLOWLIST_GROUP_HEALTH_STATUS_BAD_ONCE_CIS_FAILED;
356           break;
357         case LeAudioHealthGroupStatType::STREAM_CREATE_SIGNALING_FAILED:
358           key = android::bluetooth::CodePathCounterKeyEnum::
359                   LE_AUDIO_ALLOWLIST_GROUP_HEALTH_STATUS_BAD_ONCE_SIGNALING_FAILED;
360           break;
361         default:
362           log::error("Metric unhandled {}", type);
363           return;
364       }
365     } else {
366       switch (type) {
367         case LeAudioHealthGroupStatType::STREAM_CREATE_SUCCESS:
368           key = android::bluetooth::CodePathCounterKeyEnum::
369                   LE_AUDIO_NONALLOWLIST_GROUP_HEALTH_STATUS_GOOD;
370           break;
371         case LeAudioHealthGroupStatType::STREAM_CREATE_CIS_FAILED:
372           key = android::bluetooth::CodePathCounterKeyEnum::
373                   LE_AUDIO_NONALLOWLIST_GROUP_HEALTH_STATUS_BAD_ONCE_CIS_FAILED;
374           break;
375         case LeAudioHealthGroupStatType::STREAM_CREATE_SIGNALING_FAILED:
376           key = android::bluetooth::CodePathCounterKeyEnum::
377                   LE_AUDIO_NONALLOWLIST_GROUP_HEALTH_STATUS_BAD_ONCE_SIGNALING_FAILED;
378           break;
379         default:
380           log::error("Metric unhandled {}", type);
381           return;
382       }
383     }
384     bluetooth::shim::CountCounterMetrics(key, 1);
385   }
386 };
387 }  // namespace bluetooth::le_audio
388 
Get(void)389 LeAudioHealthStatus* LeAudioHealthStatus::Get(void) {
390   if (instance) {
391     return instance;
392   }
393   instance = new LeAudioHealthStatusImpl();
394   return instance;
395 }
396 
DebugDump(int fd)397 void LeAudioHealthStatus::DebugDump(int fd) {
398   if (instance) {
399     instance->Dump(fd);
400   }
401 }
402 
Cleanup(void)403 void LeAudioHealthStatus::Cleanup(void) {
404   if (!instance) {
405     return;
406   }
407   auto ptr = instance;
408   instance = nullptr;
409   delete ptr;
410 }
411