1 // Copyright 2019 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "chrome/browser/sharing/sharing_metrics.h"
6 
7 #include "base/metrics/histogram_functions.h"
8 #include "base/strings/strcat.h"
9 #include "base/strings/string_number_conversions.h"
10 #include "chrome/browser/sharing/sharing_device_registration_result.h"
11 #include "components/version_info/version_info.h"
12 #include "content/public/browser/browser_thread.h"
13 
14 namespace {
GetEnumStringValue(SharingFeatureName feature)15 const char* GetEnumStringValue(SharingFeatureName feature) {
16   DCHECK(feature != SharingFeatureName::kUnknown)
17       << "Feature needs to be specified for metrics logging.";
18 
19   switch (feature) {
20     case SharingFeatureName::kUnknown:
21       return "Unknown";
22     case SharingFeatureName::kClickToCall:
23       return "ClickToCall";
24     case SharingFeatureName::kSharedClipboard:
25       return "SharedClipboard";
26   }
27 }
28 
29 // Maps SharingChannelType enum values to strings used as histogram
30 // suffixes. Keep in sync with "SharingChannelType" in histograms.xml.
SharingChannelTypeToString(SharingChannelType channel_type)31 std::string SharingChannelTypeToString(SharingChannelType channel_type) {
32   switch (channel_type) {
33     case SharingChannelType::kUnknown:
34       return "Unknown";
35     case SharingChannelType::kFcmVapid:
36       return "FcmVapid";
37     case SharingChannelType::kFcmSenderId:
38       return "FcmSenderId";
39     case SharingChannelType::kServer:
40       return "Server";
41     case SharingChannelType::kWebRtc:
42       return "WebRTC";
43   }
44 }
45 
46 // Maps SharingDevicePlatform enum values to strings used as histogram
47 // suffixes. Keep in sync with "SharingDevicePlatform" in histograms.xml.
DevicePlatformToString(SharingDevicePlatform device_platform)48 std::string DevicePlatformToString(SharingDevicePlatform device_platform) {
49   switch (device_platform) {
50     case SharingDevicePlatform::kAndroid:
51       return "Android";
52     case SharingDevicePlatform::kChromeOS:
53       return "ChromeOS";
54     case SharingDevicePlatform::kIOS:
55       return "iOS";
56     case SharingDevicePlatform::kLinux:
57       return "Linux";
58     case SharingDevicePlatform::kMac:
59       return "Mac";
60     case SharingDevicePlatform::kWindows:
61       return "Windows";
62     case SharingDevicePlatform::kServer:
63       return "Server";
64     case SharingDevicePlatform::kUnknown:
65       return "Unknown";
66   }
67 }
68 
69 // Maps pulse intervals to strings used as histogram suffixes. Keep in sync with
70 // "SharingPulseInterval" in histograms.xml.
PulseIntervalToString(base::TimeDelta pulse_interval)71 std::string PulseIntervalToString(base::TimeDelta pulse_interval) {
72   if (pulse_interval < base::TimeDelta::FromHours(4))
73     return "PulseIntervalShort";
74   if (pulse_interval > base::TimeDelta::FromHours(12))
75     return "PulseIntervalLong";
76   return "PulseIntervalMedium";
77 }
78 
79 // Major Chrome version comparison with the receiver device.
80 // These values are logged to UMA. Entries should not be renumbered and numeric
81 // values should never be reused. Please keep in sync with
82 // "SharingMajorVersionComparison" in enums.xml.
83 enum class SharingMajorVersionComparison {
84   kUnknown = 0,
85   kSenderIsLower = 1,
86   kSame = 2,
87   kSenderIsHigher = 3,
88   kMaxValue = kSenderIsHigher,
89 };
90 }  // namespace
91 
SharingSendMessageResultToString(SharingSendMessageResult result)92 std::string SharingSendMessageResultToString(SharingSendMessageResult result) {
93   switch (result) {
94     case SharingSendMessageResult::kSuccessful:
95       return "Successful";
96     case SharingSendMessageResult::kDeviceNotFound:
97       return "DeviceNotFound";
98     case SharingSendMessageResult::kNetworkError:
99       return "NetworkError";
100     case SharingSendMessageResult::kPayloadTooLarge:
101       return "PayloadTooLarge";
102     case SharingSendMessageResult::kAckTimeout:
103       return "AckTimeout";
104     case SharingSendMessageResult::kInternalError:
105       return "InternalError";
106     case SharingSendMessageResult::kEncryptionError:
107       return "EncryptionError";
108     case SharingSendMessageResult::kCommitTimeout:
109       return "CommitTimeout";
110   }
111 }
112 
SharingPayloadCaseToMessageType(chrome_browser_sharing::SharingMessage::PayloadCase payload_case)113 chrome_browser_sharing::MessageType SharingPayloadCaseToMessageType(
114     chrome_browser_sharing::SharingMessage::PayloadCase payload_case) {
115   switch (payload_case) {
116     case chrome_browser_sharing::SharingMessage::PAYLOAD_NOT_SET:
117       return chrome_browser_sharing::UNKNOWN_MESSAGE;
118     case chrome_browser_sharing::SharingMessage::kPingMessage:
119       return chrome_browser_sharing::PING_MESSAGE;
120     case chrome_browser_sharing::SharingMessage::kAckMessage:
121       return chrome_browser_sharing::ACK_MESSAGE;
122     case chrome_browser_sharing::SharingMessage::kClickToCallMessage:
123       return chrome_browser_sharing::CLICK_TO_CALL_MESSAGE;
124     case chrome_browser_sharing::SharingMessage::kSharedClipboardMessage:
125       return chrome_browser_sharing::SHARED_CLIPBOARD_MESSAGE;
126     case chrome_browser_sharing::SharingMessage::kSmsFetchRequest:
127       return chrome_browser_sharing::SMS_FETCH_REQUEST;
128     case chrome_browser_sharing::SharingMessage::kRemoteCopyMessage:
129       return chrome_browser_sharing::REMOTE_COPY_MESSAGE;
130     case chrome_browser_sharing::SharingMessage::kPeerConnectionOfferMessage:
131       return chrome_browser_sharing::PEER_CONNECTION_OFFER_MESSAGE;
132     case chrome_browser_sharing::SharingMessage::
133         kPeerConnectionIceCandidatesMessage:
134       return chrome_browser_sharing::PEER_CONNECTION_ICE_CANDIDATES_MESSAGE;
135     case chrome_browser_sharing::SharingMessage::kDiscoveryRequest:
136       return chrome_browser_sharing::DISCOVERY_REQUEST;
137     case chrome_browser_sharing::SharingMessage::kWebRtcSignalingFrame:
138       return chrome_browser_sharing::WEB_RTC_SIGNALING_FRAME;
139   }
140   // For proto3 enums unrecognized enum values are kept when parsing, and a new
141   // payload case received over the network would not default to
142   // PAYLOAD_NOT_SET. Explicitly return UNKNOWN_MESSAGE here to handle this
143   // case.
144   return chrome_browser_sharing::UNKNOWN_MESSAGE;
145 }
146 
SharingMessageTypeToString(chrome_browser_sharing::MessageType message_type)147 const std::string& SharingMessageTypeToString(
148     chrome_browser_sharing::MessageType message_type) {
149   // For proto3 enums unrecognized enum values are kept when parsing and their
150   // name is an empty string. We don't want to use that as a histogram suffix.
151   if (!chrome_browser_sharing::MessageType_IsValid(message_type)) {
152     return chrome_browser_sharing::MessageType_Name(
153         chrome_browser_sharing::UNKNOWN_MESSAGE);
154   }
155   return chrome_browser_sharing::MessageType_Name(message_type);
156 }
157 
GenerateSharingTraceId()158 int GenerateSharingTraceId() {
159   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
160   static int next_id = 0;
161   return next_id++;
162 }
163 
LogSharingMessageReceived(chrome_browser_sharing::SharingMessage::PayloadCase payload_case)164 void LogSharingMessageReceived(
165     chrome_browser_sharing::SharingMessage::PayloadCase payload_case) {
166   base::UmaHistogramExactLinear("Sharing.MessageReceivedType",
167                                 SharingPayloadCaseToMessageType(payload_case),
168                                 chrome_browser_sharing::MessageType_ARRAYSIZE);
169 }
170 
LogSharingRegistrationResult(SharingDeviceRegistrationResult result)171 void LogSharingRegistrationResult(SharingDeviceRegistrationResult result) {
172   base::UmaHistogramEnumeration("Sharing.DeviceRegistrationResult", result);
173 }
174 
LogSharingUnegistrationResult(SharingDeviceRegistrationResult result)175 void LogSharingUnegistrationResult(SharingDeviceRegistrationResult result) {
176   base::UmaHistogramEnumeration("Sharing.DeviceUnregistrationResult", result);
177 }
178 
LogSharingDevicesToShow(SharingFeatureName feature,const char * histogram_suffix,int count)179 void LogSharingDevicesToShow(SharingFeatureName feature,
180                              const char* histogram_suffix,
181                              int count) {
182   auto* feature_str = GetEnumStringValue(feature);
183   // Explicitly log both the base and the suffixed histogram because the base
184   // aggregation is not automatically generated.
185   base::UmaHistogramExactLinear(
186       base::StrCat({"Sharing.", feature_str, "DevicesToShow"}), count,
187       /*value_max=*/20);
188   if (!histogram_suffix)
189     return;
190   base::UmaHistogramExactLinear(
191       base::StrCat(
192           {"Sharing.", feature_str, "DevicesToShow.", histogram_suffix}),
193       count,
194       /*value_max=*/20);
195 }
196 
LogSharingAppsToShow(SharingFeatureName feature,const char * histogram_suffix,int count)197 void LogSharingAppsToShow(SharingFeatureName feature,
198                           const char* histogram_suffix,
199                           int count) {
200   auto* feature_str = GetEnumStringValue(feature);
201   // Explicitly log both the base and the suffixed histogram because the base
202   // aggregation is not automatically generated.
203   base::UmaHistogramExactLinear(
204       base::StrCat({"Sharing.", feature_str, "AppsToShow"}), count,
205       /*value_max=*/20);
206   if (!histogram_suffix)
207     return;
208   base::UmaHistogramExactLinear(
209       base::StrCat({"Sharing.", feature_str, "AppsToShow.", histogram_suffix}),
210       count,
211       /*value_max=*/20);
212 }
213 
LogSharingSelectedIndex(SharingFeatureName feature,const char * histogram_suffix,int index,SharingIndexType index_type)214 void LogSharingSelectedIndex(SharingFeatureName feature,
215                              const char* histogram_suffix,
216                              int index,
217                              SharingIndexType index_type) {
218   auto* feature_str = GetEnumStringValue(feature);
219   // Explicitly log both the base and the suffixed histogram because the base
220   // aggregation is not automatically generated.
221   std::string name = base::StrCat(
222       {"Sharing.", feature_str, "Selected",
223        (index_type == SharingIndexType::kDevice) ? "Device" : "App", "Index"});
224   base::UmaHistogramExactLinear(name, index, /*value_max=*/20);
225   if (!histogram_suffix)
226     return;
227   base::UmaHistogramExactLinear(base::StrCat({name, ".", histogram_suffix}),
228                                 index,
229                                 /*value_max=*/20);
230 }
231 
LogSharingMessageAckTime(chrome_browser_sharing::MessageType message_type,SharingDevicePlatform receiver_device_platform,SharingChannelType channel_type,base::TimeDelta time)232 void LogSharingMessageAckTime(chrome_browser_sharing::MessageType message_type,
233                               SharingDevicePlatform receiver_device_platform,
234                               SharingChannelType channel_type,
235                               base::TimeDelta time) {
236   std::string type_suffixed_name = base::StrCat(
237       {"Sharing.MessageAckTime.", SharingMessageTypeToString(message_type)});
238   std::string platform_suffixed_name =
239       base::StrCat({"Sharing.MessageAckTime.",
240                     DevicePlatformToString(receiver_device_platform), ".",
241                     SharingMessageTypeToString(message_type)});
242   std::string channel_suffixed_name = base::StrCat(
243       {"Sharing.MessageAckTime.", SharingChannelTypeToString(channel_type)});
244   switch (message_type) {
245     case chrome_browser_sharing::MessageType::UNKNOWN_MESSAGE:
246     case chrome_browser_sharing::MessageType::PING_MESSAGE:
247     case chrome_browser_sharing::MessageType::CLICK_TO_CALL_MESSAGE:
248     case chrome_browser_sharing::MessageType::SHARED_CLIPBOARD_MESSAGE:
249     case chrome_browser_sharing::MessageType::PEER_CONNECTION_OFFER_MESSAGE:
250     case chrome_browser_sharing::MessageType::
251         PEER_CONNECTION_ICE_CANDIDATES_MESSAGE:
252       base::UmaHistogramMediumTimes(type_suffixed_name, time);
253       base::UmaHistogramMediumTimes(platform_suffixed_name, time);
254       base::UmaHistogramMediumTimes(channel_suffixed_name, time);
255       break;
256     case chrome_browser_sharing::MessageType::SMS_FETCH_REQUEST:
257     case chrome_browser_sharing::MessageType::DISCOVERY_REQUEST:
258     case chrome_browser_sharing::MessageType::WEB_RTC_SIGNALING_FRAME:
259       base::UmaHistogramCustomTimes(
260           type_suffixed_name, time,
261           /*min=*/base::TimeDelta::FromMilliseconds(1),
262           /*max=*/base::TimeDelta::FromMinutes(10), /*buckets=*/50);
263       base::UmaHistogramCustomTimes(
264           platform_suffixed_name, time,
265           /*min=*/base::TimeDelta::FromMilliseconds(1),
266           /*max=*/base::TimeDelta::FromMinutes(10), /*buckets=*/50);
267       base::UmaHistogramCustomTimes(
268           channel_suffixed_name, time,
269           /*min=*/base::TimeDelta::FromMilliseconds(1),
270           /*max=*/base::TimeDelta::FromMinutes(10), /*buckets=*/50);
271       break;
272     case chrome_browser_sharing::MessageType::ACK_MESSAGE:
273     default:
274       // For proto3 enums unrecognized enum values are kept, so message_type may
275       // not fall into any switch case. However, as an ack message, original
276       // message type should always be known.
277       NOTREACHED();
278   }
279 }
280 
LogSharingMessageHandlerTime(chrome_browser_sharing::MessageType message_type,base::TimeDelta time_taken)281 void LogSharingMessageHandlerTime(
282     chrome_browser_sharing::MessageType message_type,
283     base::TimeDelta time_taken) {
284   base::UmaHistogramMediumTimes(
285       base::StrCat({"Sharing.MessageHandlerTime.",
286                     SharingMessageTypeToString(message_type)}),
287       time_taken);
288 }
289 
LogSharingDeviceLastUpdatedAge(chrome_browser_sharing::MessageType message_type,base::TimeDelta age)290 void LogSharingDeviceLastUpdatedAge(
291     chrome_browser_sharing::MessageType message_type,
292     base::TimeDelta age) {
293   constexpr char kBase[] = "Sharing.DeviceLastUpdatedAge";
294   int hours = age.InHours();
295   base::UmaHistogramCounts1000(kBase, hours);
296   base::UmaHistogramCounts1000(
297       base::StrCat({kBase, ".", SharingMessageTypeToString(message_type)}),
298       hours);
299 }
300 
LogSharingDeviceLastUpdatedAgeWithResult(SharingSendMessageResult result,base::TimeDelta age)301 void LogSharingDeviceLastUpdatedAgeWithResult(SharingSendMessageResult result,
302                                               base::TimeDelta age) {
303   base::UmaHistogramCounts1000(
304       base::StrCat({"Sharing.DeviceLastUpdatedAgeWithResult.",
305                     SharingSendMessageResultToString(result)}),
306       age.InHours());
307 }
308 
LogSharingVersionComparison(chrome_browser_sharing::MessageType message_type,const std::string & receiver_version)309 void LogSharingVersionComparison(
310     chrome_browser_sharing::MessageType message_type,
311     const std::string& receiver_version) {
312   int sender_major = 0;
313   base::StringToInt(version_info::GetMajorVersionNumber(), &sender_major);
314 
315   // The |receiver_version| has optional modifiers e.g. "1.2.3.4 canary" so we
316   // do not parse it with base::Version.
317   int receiver_major = 0;
318   base::StringToInt(receiver_version, &receiver_major);
319 
320   SharingMajorVersionComparison result;
321   if (sender_major == 0 || sender_major == INT_MIN || sender_major == INT_MAX ||
322       receiver_major == 0 || receiver_major == INT_MIN ||
323       receiver_major == INT_MAX) {
324     result = SharingMajorVersionComparison::kUnknown;
325   } else if (sender_major < receiver_major) {
326     result = SharingMajorVersionComparison::kSenderIsLower;
327   } else if (sender_major == receiver_major) {
328     result = SharingMajorVersionComparison::kSame;
329   } else {
330     result = SharingMajorVersionComparison::kSenderIsHigher;
331   }
332   constexpr char kBase[] = "Sharing.MajorVersionComparison";
333   base::UmaHistogramEnumeration(kBase, result);
334   base::UmaHistogramEnumeration(
335       base::StrCat({kBase, ".", SharingMessageTypeToString(message_type)}),
336       result);
337 }
338 
LogSharingDialogShown(SharingFeatureName feature,SharingDialogType type)339 void LogSharingDialogShown(SharingFeatureName feature, SharingDialogType type) {
340   base::UmaHistogramEnumeration(
341       base::StrCat({"Sharing.", GetEnumStringValue(feature), "DialogShown"}),
342       type);
343 }
344 
LogSendSharingMessageResult(chrome_browser_sharing::MessageType message_type,SharingDevicePlatform receiving_device_platform,SharingChannelType channel_type,base::TimeDelta pulse_interval,SharingSendMessageResult result)345 void LogSendSharingMessageResult(
346     chrome_browser_sharing::MessageType message_type,
347     SharingDevicePlatform receiving_device_platform,
348     SharingChannelType channel_type,
349     base::TimeDelta pulse_interval,
350     SharingSendMessageResult result) {
351   const std::string metric_prefix = "Sharing.SendMessageResult";
352 
353   base::UmaHistogramEnumeration(metric_prefix, result);
354 
355   base::UmaHistogramEnumeration(
356       base::StrCat(
357           {metric_prefix, ".", SharingMessageTypeToString(message_type)}),
358       result);
359 
360   base::UmaHistogramEnumeration(
361       base::StrCat({metric_prefix, ".",
362                     DevicePlatformToString(receiving_device_platform)}),
363       result);
364 
365   base::UmaHistogramEnumeration(
366       base::StrCat({metric_prefix, ".",
367                     DevicePlatformToString(receiving_device_platform), ".",
368                     SharingMessageTypeToString(message_type)}),
369       result);
370 
371   base::UmaHistogramEnumeration(
372       base::StrCat(
373           {metric_prefix, ".", SharingChannelTypeToString(channel_type)}),
374       result);
375 
376   // There is no "invalid" bucket so only log valid pulse intervals.
377   if (!pulse_interval.is_zero()) {
378     base::UmaHistogramEnumeration(
379         base::StrCat(
380             {metric_prefix, ".", PulseIntervalToString(pulse_interval)}),
381         result);
382     base::UmaHistogramEnumeration(
383         base::StrCat({metric_prefix, ".",
384                       DevicePlatformToString(receiving_device_platform), ".",
385                       PulseIntervalToString(pulse_interval)}),
386         result);
387   }
388 }
389 
LogSendSharingAckMessageResult(chrome_browser_sharing::MessageType message_type,SharingDevicePlatform ack_receiver_device_type,SharingChannelType channel_type,SharingSendMessageResult result)390 void LogSendSharingAckMessageResult(
391     chrome_browser_sharing::MessageType message_type,
392     SharingDevicePlatform ack_receiver_device_type,
393     SharingChannelType channel_type,
394     SharingSendMessageResult result) {
395   const std::string metric_prefix = "Sharing.SendAckMessageResult";
396 
397   base::UmaHistogramEnumeration(metric_prefix, result);
398 
399   base::UmaHistogramEnumeration(
400       base::StrCat(
401           {metric_prefix, ".", SharingMessageTypeToString(message_type)}),
402       result);
403 
404   base::UmaHistogramEnumeration(
405       base::StrCat({metric_prefix, ".",
406                     DevicePlatformToString(ack_receiver_device_type)}),
407       result);
408 
409   base::UmaHistogramEnumeration(
410       base::StrCat({metric_prefix, ".",
411                     DevicePlatformToString(ack_receiver_device_type), ".",
412                     SharingMessageTypeToString(message_type)}),
413       result);
414 
415   base::UmaHistogramEnumeration(
416       base::StrCat(
417           {metric_prefix, ".", SharingChannelTypeToString(channel_type)}),
418       result);
419 }
420 
LogSharedClipboardSelectedTextSize(size_t size)421 void LogSharedClipboardSelectedTextSize(size_t size) {
422   base::UmaHistogramCounts100000("Sharing.SharedClipboardSelectedTextSize",
423                                  size);
424 }
425 
LogSharedClipboardRetries(int retries,SharingSendMessageResult result)426 void LogSharedClipboardRetries(int retries, SharingSendMessageResult result) {
427   constexpr char kBase[] = "Sharing.SharedClipboardRetries";
428   base::UmaHistogramExactLinear(kBase, retries, /*value_max=*/20);
429   base::UmaHistogramExactLinear(
430       base::StrCat({kBase, ".", SharingSendMessageResultToString(result)}),
431       retries,
432       /*value_max=*/20);
433 }
434 
LogRemoteCopyHandleMessageResult(RemoteCopyHandleMessageResult result)435 void LogRemoteCopyHandleMessageResult(RemoteCopyHandleMessageResult result) {
436   base::UmaHistogramEnumeration("Sharing.RemoteCopyHandleMessageResult",
437                                 result);
438 }
439 
LogRemoteCopyReceivedTextSize(size_t size)440 void LogRemoteCopyReceivedTextSize(size_t size) {
441   base::UmaHistogramCounts100000("Sharing.RemoteCopyReceivedTextSize", size);
442 }
443 
LogRemoteCopyReceivedImageSizeBeforeDecode(size_t size)444 void LogRemoteCopyReceivedImageSizeBeforeDecode(size_t size) {
445   base::UmaHistogramCounts10M("Sharing.RemoteCopyReceivedImageSizeBeforeDecode",
446                               size);
447 }
448 
LogRemoteCopyReceivedImageSizeAfterDecode(size_t size)449 void LogRemoteCopyReceivedImageSizeAfterDecode(size_t size) {
450   base::UmaHistogramCustomCounts(
451       "Sharing.RemoteCopyReceivedImageSizeAfterDecode", size, 1, 100000000, 50);
452 }
453 
LogRemoteCopyLoadImageStatusCode(int code)454 void LogRemoteCopyLoadImageStatusCode(int code) {
455   base::UmaHistogramSparse("Sharing.RemoteCopyLoadImageStatusCode", code);
456 }
457 
LogRemoteCopyLoadImageTime(base::TimeDelta time)458 void LogRemoteCopyLoadImageTime(base::TimeDelta time) {
459   base::UmaHistogramMediumTimes("Sharing.RemoteCopyLoadImageTime", time);
460 }
461 
LogRemoteCopyDecodeImageTime(base::TimeDelta time)462 void LogRemoteCopyDecodeImageTime(base::TimeDelta time) {
463   base::UmaHistogramMediumTimes("Sharing.RemoteCopyDecodeImageTime", time);
464 }
465 
LogRemoteCopyResizeImageTime(base::TimeDelta time)466 void LogRemoteCopyResizeImageTime(base::TimeDelta time) {
467   base::UmaHistogramMediumTimes("Sharing.RemoteCopyResizeImageTime", time);
468 }
469 
LogRemoteCopyWriteTime(base::TimeDelta time,bool is_image)470 void LogRemoteCopyWriteTime(base::TimeDelta time, bool is_image) {
471   if (is_image)
472     base::UmaHistogramMediumTimes("Sharing.RemoteCopyWriteImageTime", time);
473   else
474     base::UmaHistogramMediumTimes("Sharing.RemoteCopyWriteTextTime", time);
475 }
476 
LogRemoteCopyWriteDetectionTime(base::TimeDelta time,bool is_image)477 void LogRemoteCopyWriteDetectionTime(base::TimeDelta time, bool is_image) {
478   if (is_image)
479     base::UmaHistogramTimes("Sharing.RemoteCopyWriteImageDetectionTime", time);
480   else
481     base::UmaHistogramTimes("Sharing.RemoteCopyWriteTextDetectionTime", time);
482 }
483 
LogSharingDeviceInfoAvailable(bool available)484 void LogSharingDeviceInfoAvailable(bool available) {
485   base::UmaHistogramBoolean("Sharing.DeviceInfoAvailable", available);
486 }
487