• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2020 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 package com.android.bluetooth.le_audio;
19 
20 import android.bluetooth.BluetoothDevice;
21 import android.bluetooth.BluetoothLeAudioCodecConfig;
22 import android.bluetooth.BluetoothLeBroadcastMetadata;
23 
24 import java.util.List;
25 
26 /**
27  * Stack event sent via a callback from JNI to Java, or generated internally by the LeAudio State
28  * Machine.
29  */
30 public class LeAudioStackEvent {
31     // Event types for STACK_EVENT message (coming from native in bt_le_audio.h)
32     private static final int EVENT_TYPE_NONE = 0;
33     public static final int EVENT_TYPE_CONNECTION_STATE_CHANGED = 1;
34     public static final int EVENT_TYPE_GROUP_STATUS_CHANGED = 2;
35     public static final int EVENT_TYPE_GROUP_NODE_STATUS_CHANGED = 3;
36     public static final int EVENT_TYPE_AUDIO_CONF_CHANGED = 4;
37     public static final int EVENT_TYPE_SINK_AUDIO_LOCATION_AVAILABLE = 5;
38     public static final int EVENT_TYPE_AUDIO_LOCAL_CODEC_CONFIG_CAPA_CHANGED = 6;
39     public static final int EVENT_TYPE_AUDIO_GROUP_CURRENT_CODEC_CONFIG_CHANGED = 7;
40     public static final int EVENT_TYPE_AUDIO_GROUP_SELECTABLE_CODEC_CONFIG_CHANGED = 8;
41     public static final int EVENT_TYPE_NATIVE_INITIALIZED = 9;
42     public static final int EVENT_TYPE_HEALTH_BASED_DEV_RECOMMENDATION = 10;
43     public static final int EVENT_TYPE_HEALTH_BASED_GROUP_RECOMMENDATION = 11;
44     public static final int EVENT_TYPE_UNICAST_MONITOR_MODE_STATUS = 12;
45     public static final int EVENT_TYPE_GROUP_STREAM_STATUS_CHANGED = 13;
46     // -------- DO NOT PUT ANY NEW UNICAST EVENTS BELOW THIS LINE-------------
47     public static final int EVENT_TYPE_UNICAST_MAX = 14;
48 
49     // Broadcast related events
50     public static final int EVENT_TYPE_BROADCAST_CREATED = EVENT_TYPE_UNICAST_MAX + 1;
51     public static final int EVENT_TYPE_BROADCAST_DESTROYED = EVENT_TYPE_UNICAST_MAX + 2;
52     public static final int EVENT_TYPE_BROADCAST_STATE = EVENT_TYPE_UNICAST_MAX + 3;
53     public static final int EVENT_TYPE_BROADCAST_METADATA_CHANGED = EVENT_TYPE_UNICAST_MAX + 4;
54     public static final int EVENT_TYPE_BROADCAST_AUDIO_SESSION_CREATED = EVENT_TYPE_UNICAST_MAX + 5;
55 
56     // Do not modify without updating the HAL bt_le_audio.h files.
57     // Match up with GroupStatus enum of bt_le_audio.h
58     static final int CONNECTION_STATE_DISCONNECTED = 0;
59     static final int CONNECTION_STATE_CONNECTING = 1;
60     static final int CONNECTION_STATE_CONNECTED = 2;
61     static final int CONNECTION_STATE_DISCONNECTING = 3;
62 
63     // Health based recommendation
64     static final int HEALTH_RECOMMENDATION_ACTION_NONE = 0;
65     static final int HEALTH_RECOMMENDATION_ACTION_DISABLE = 1;
66     static final int HEALTH_RECOMMENDATION_ACTION_CONSIDER_DISABLING = 2;
67     static final int HEALTH_RECOMMENDATION_ACTION_INACTIVATE_GROUP = 3;
68 
69     static final int GROUP_STATUS_INACTIVE = 0;
70     static final int GROUP_STATUS_ACTIVE = 1;
71     static final int GROUP_STATUS_TURNED_IDLE_DURING_CALL = 2;
72 
73     static final int GROUP_NODE_ADDED = 1;
74     static final int GROUP_NODE_REMOVED = 2;
75 
76     // Do not modify without updating the HAL bt_le_audio.h files.
77     // Match up with BroadcastState enum of bt_le_audio.h
78     static final int BROADCAST_STATE_STOPPED = 0;
79     static final int BROADCAST_STATE_CONFIGURING = 1;
80     static final int BROADCAST_STATE_PAUSED = 2;
81     static final int BROADCAST_STATE_ENABLING = 3;
82     static final int BROADCAST_STATE_DISABLING = 4;
83     static final int BROADCAST_STATE_STOPPING = 5;
84     static final int BROADCAST_STATE_STREAMING = 6;
85 
86     // Do not modify without updating the HAL bt_le_audio.h files.
87     // Match up with UnicastMonitorModeStatus enum of bt_le_audio.h
88     static final int STATUS_LOCAL_STREAM_REQUESTED = 0;
89     static final int STATUS_LOCAL_STREAM_STREAMING = 1;
90     static final int STATUS_LOCAL_STREAM_SUSPENDED = 2;
91     static final int STATUS_LOCAL_STREAM_REQUESTED_NO_CONTEXT_VALIDATE = 3;
92 
93     // Do not modify without updating le_audio_types.h
94     // Match up with defines of le_audio_types.h
95     static final int DIRECTION_SINK = 1;
96     static final int DIRECTION_SOURCE = 2;
97 
98     static final int GROUP_STREAM_STATUS_IDLE = 0;
99     static final int GROUP_STREAM_STATUS_STREAMING = 1;
100 
101     public int type = EVENT_TYPE_NONE;
102     public BluetoothDevice device;
103     public int valueInt1 = 0;
104     public int valueInt2 = 0;
105     public int valueInt3 = 0;
106     public int valueInt4 = 0;
107     public int valueInt5 = 0;
108     public boolean valueBool1 = false;
109     public BluetoothLeAudioCodecConfig valueCodec1;
110     public BluetoothLeAudioCodecConfig valueCodec2;
111     public List<BluetoothLeAudioCodecConfig> valueCodecList1;
112     public List<BluetoothLeAudioCodecConfig> valueCodecList2;
113     public BluetoothLeBroadcastMetadata broadcastMetadata;
114 
LeAudioStackEvent(int type)115     LeAudioStackEvent(int type) {
116         this.type = type;
117     }
118 
119     @Override
toString()120     public String toString() {
121         // event dump
122         StringBuilder result = new StringBuilder();
123         result.append("LeAudioStackEvent {type:").append(eventTypeToString(type));
124         result.append(", device:").append(device);
125 
126         if (type != EVENT_TYPE_AUDIO_LOCAL_CODEC_CONFIG_CAPA_CHANGED) {
127             result.append(", value1:").append(eventTypeValue1ToString(type, valueInt1));
128             result.append(", value2:").append(eventTypeValue2ToString(type, valueInt2));
129             result.append(", value3:").append(eventTypeValue3ToString(type, valueInt3));
130             result.append(", value4:").append(eventTypeValue4ToString(type, valueInt4));
131             result.append(", value5:").append(eventTypeValue5ToString(type, valueInt5));
132             result.append(", valueBool1:").append(eventTypeValueBool1ToString(type, valueBool1));
133         } else {
134             result.append(", valueCodecList1:")
135                     .append(eventTypeValueCodecList1ToString(type, valueCodecList1));
136             result.append(", valueCodecList2:")
137                     .append(eventTypeValueCodecList2ToString(type, valueCodecList2));
138         }
139 
140         if (type == EVENT_TYPE_AUDIO_GROUP_CURRENT_CODEC_CONFIG_CHANGED) {
141             result.append(", valueCodec1:").append(eventTypeValueCodec1ToString(type, valueCodec1));
142             result.append(", valueCodec2:").append(eventTypeValueCodec2ToString(type, valueCodec2));
143         }
144 
145         if (type == EVENT_TYPE_AUDIO_GROUP_SELECTABLE_CODEC_CONFIG_CHANGED) {
146             result.append(", valueCodecList1:")
147                     .append(eventTypeValueCodecList1ToString(type, valueCodecList1));
148             result.append(", valueCodecList2:")
149                     .append(eventTypeValueCodecList2ToString(type, valueCodecList2));
150         }
151 
152         if (type == EVENT_TYPE_BROADCAST_METADATA_CHANGED) {
153             result.append(", broadcastMetadata:")
154                     .append(eventTypeValueBroadcastMetadataToString(broadcastMetadata));
155         }
156         result.append("}");
157         return result.toString();
158     }
159 
eventTypeToString(int type)160     private static String eventTypeToString(int type) {
161         switch (type) {
162             case EVENT_TYPE_NONE:
163                 return "EVENT_TYPE_NONE";
164             case EVENT_TYPE_CONNECTION_STATE_CHANGED:
165                 return "EVENT_TYPE_CONNECTION_STATE_CHANGED";
166             case EVENT_TYPE_GROUP_STATUS_CHANGED:
167                 return "EVENT_TYPE_GROUP_STATUS_CHANGED";
168             case EVENT_TYPE_GROUP_NODE_STATUS_CHANGED:
169                 return "EVENT_TYPE_GROUP_NODE_STATUS_CHANGED";
170             case EVENT_TYPE_AUDIO_CONF_CHANGED:
171                 return "EVENT_TYPE_AUDIO_CONF_CHANGED";
172             case EVENT_TYPE_SINK_AUDIO_LOCATION_AVAILABLE:
173                 return "EVENT_TYPE_SINK_AUDIO_LOCATION_AVAILABLE";
174             case EVENT_TYPE_BROADCAST_CREATED:
175                 return "EVENT_TYPE_BROADCAST_CREATED";
176             case EVENT_TYPE_BROADCAST_DESTROYED:
177                 return "EVENT_TYPE_BROADCAST_DESTROYED";
178             case EVENT_TYPE_BROADCAST_STATE:
179                 return "EVENT_TYPE_BROADCAST_STATE";
180             case EVENT_TYPE_BROADCAST_METADATA_CHANGED:
181                 return "EVENT_TYPE_BROADCAST_METADATA_CHANGED";
182             case EVENT_TYPE_BROADCAST_AUDIO_SESSION_CREATED:
183                 return "EVENT_TYPE_BROADCAST_AUDIO_SESSION_CREATED";
184             case EVENT_TYPE_AUDIO_LOCAL_CODEC_CONFIG_CAPA_CHANGED:
185                 return "EVENT_TYPE_AUDIO_LOCAL_CODEC_CONFIG_CAPA_CHANGED";
186             case EVENT_TYPE_AUDIO_GROUP_CURRENT_CODEC_CONFIG_CHANGED:
187                 return "EVENT_TYPE_AUDIO_GROUP_CURRENT_CODEC_CONFIG_CHANGED";
188             case EVENT_TYPE_AUDIO_GROUP_SELECTABLE_CODEC_CONFIG_CHANGED:
189                 return "EVENT_TYPE_AUDIO_GROUP_SELECTABLE_CODEC_CONFIG_CHANGED";
190             case EVENT_TYPE_NATIVE_INITIALIZED:
191                 return "EVENT_TYPE_NATIVE_INITIALIZED";
192             case EVENT_TYPE_HEALTH_BASED_DEV_RECOMMENDATION:
193                 return "EVENT_TYPE_HEALTH_BASED_DEV_RECOMMENDATION";
194             case EVENT_TYPE_HEALTH_BASED_GROUP_RECOMMENDATION:
195                 return "EVENT_TYPE_HEALTH_BASED_GROUP_RECOMMENDATION";
196             case EVENT_TYPE_UNICAST_MONITOR_MODE_STATUS:
197                 return "EVENT_TYPE_UNICAST_MONITOR_MODE_STATUS";
198             case EVENT_TYPE_GROUP_STREAM_STATUS_CHANGED:
199                 return "EVENT_TYPE_GROUP_STREAM_STATUS_CHANGED";
200             default:
201                 return "EVENT_TYPE_UNKNOWN:" + type;
202         }
203     }
204 
eventTypeValue1ToString(int type, int value)205     private static String eventTypeValue1ToString(int type, int value) {
206         switch (type) {
207             case EVENT_TYPE_CONNECTION_STATE_CHANGED:
208                 switch (value) {
209                     case CONNECTION_STATE_DISCONNECTED:
210                         return "CONNECTION_STATE_DISCONNECTED";
211                     case CONNECTION_STATE_CONNECTING:
212                         return "CONNECTION_STATE_CONNECTING";
213                     case CONNECTION_STATE_CONNECTED:
214                         return "CONNECTION_STATE_CONNECTED";
215                     case CONNECTION_STATE_DISCONNECTING:
216                         return "CONNECTION_STATE_DISCONNECTING";
217                     default:
218                         return "UNKNOWN";
219                 }
220             case EVENT_TYPE_GROUP_NODE_STATUS_CHANGED:
221                 // same as EVENT_TYPE_GROUP_STATUS_CHANGED
222             case EVENT_TYPE_AUDIO_GROUP_CURRENT_CODEC_CONFIG_CHANGED:
223             case EVENT_TYPE_AUDIO_GROUP_SELECTABLE_CODEC_CONFIG_CHANGED:
224                 // same as EVENT_TYPE_GROUP_STATUS_CHANGED
225             case EVENT_TYPE_GROUP_STREAM_STATUS_CHANGED:
226                 // same as EVENT_TYPE_GROUP_STATUS_CHANGED
227             case EVENT_TYPE_GROUP_STATUS_CHANGED:
228                 return "{group_id:" + Integer.toString(value) + "}";
229             case EVENT_TYPE_AUDIO_CONF_CHANGED:
230                 // FIXME: It should have proper direction names here
231                 return "{direction:" + value + "}";
232             case EVENT_TYPE_SINK_AUDIO_LOCATION_AVAILABLE:
233                 return "{sink_audio_location:" + value + "}";
234             case EVENT_TYPE_BROADCAST_CREATED:
235                 // same as EVENT_TYPE_BROADCAST_STATE
236             case EVENT_TYPE_BROADCAST_DESTROYED:
237                 // same as EVENT_TYPE_BROADCAST_STATE
238             case EVENT_TYPE_BROADCAST_METADATA_CHANGED:
239                 // same as EVENT_TYPE_BROADCAST_STATE
240             case EVENT_TYPE_BROADCAST_STATE:
241                 return "{broadcastId:" + value + "}";
242             case EVENT_TYPE_HEALTH_BASED_GROUP_RECOMMENDATION:
243                 return "{group_id: " + value + "}";
244             case EVENT_TYPE_HEALTH_BASED_DEV_RECOMMENDATION:
245                 switch (value) {
246                     case HEALTH_RECOMMENDATION_ACTION_DISABLE:
247                         return "ACTION_DISABLE";
248                     case HEALTH_RECOMMENDATION_ACTION_CONSIDER_DISABLING:
249                         return "ACTION_CONSIDER_DISABLING";
250                     case HEALTH_RECOMMENDATION_ACTION_INACTIVATE_GROUP:
251                         return "ACTION_INACTIVATE_GROUP";
252                     default:
253                         return "UNKNOWN";
254                 }
255             case EVENT_TYPE_UNICAST_MONITOR_MODE_STATUS:
256                 switch (value) {
257                     case DIRECTION_SINK:
258                         return "DIRECTION_SINK";
259                     case DIRECTION_SOURCE:
260                         return "DIRECTION_SOURCE";
261                     default:
262                         return "UNKNOWN";
263                 }
264             default:
265                 break;
266         }
267         return Integer.toString(value);
268     }
269 
eventTypeValue2ToString(int type, int value)270     private static String eventTypeValue2ToString(int type, int value) {
271         switch (type) {
272             case EVENT_TYPE_GROUP_STATUS_CHANGED:
273                 switch (value) {
274                     case GROUP_STATUS_ACTIVE:
275                         return "GROUP_STATUS_ACTIVE";
276                     case GROUP_STATUS_INACTIVE:
277                         return "GROUP_STATUS_INACTIVE";
278                     case GROUP_STATUS_TURNED_IDLE_DURING_CALL:
279                         return "GROUP_STATUS_TURNED_IDLE_DURING_CALL";
280                     default:
281                         break;
282                 }
283                 break;
284             case EVENT_TYPE_GROUP_NODE_STATUS_CHANGED:
285                 switch (value) {
286                     case GROUP_NODE_ADDED:
287                         return "GROUP_NODE_ADDED";
288                     case GROUP_NODE_REMOVED:
289                         return "GROUP_NODE_REMOVED";
290                     default:
291                         return "UNKNOWN";
292                 }
293             case EVENT_TYPE_AUDIO_CONF_CHANGED:
294                 return "{group_id:" + Integer.toString(value) + "}";
295             case EVENT_TYPE_BROADCAST_STATE:
296                 return "{state:" + broadcastStateToString(value) + "}";
297             case EVENT_TYPE_HEALTH_BASED_GROUP_RECOMMENDATION:
298                 switch (value) {
299                     case HEALTH_RECOMMENDATION_ACTION_DISABLE:
300                         return "ACTION_DISABLE";
301                     case HEALTH_RECOMMENDATION_ACTION_CONSIDER_DISABLING:
302                         return "ACTION_CONSIDER_DISABLING";
303                     case HEALTH_RECOMMENDATION_ACTION_INACTIVATE_GROUP:
304                         return "ACTION_INACTIVATE_GROUP";
305                     default:
306                         return "UNKNOWN";
307                 }
308             case EVENT_TYPE_UNICAST_MONITOR_MODE_STATUS:
309                 switch (value) {
310                     case STATUS_LOCAL_STREAM_REQUESTED:
311                         return "STATUS_LOCAL_STREAM_REQUESTED";
312                     case STATUS_LOCAL_STREAM_STREAMING:
313                         return "STATUS_LOCAL_STREAM_STREAMING";
314                     case STATUS_LOCAL_STREAM_SUSPENDED:
315                         return "STATUS_LOCAL_STREAM_SUSPENDED";
316                     case STATUS_LOCAL_STREAM_REQUESTED_NO_CONTEXT_VALIDATE:
317                         return "STATUS_LOCAL_STREAM_REQUESTED_NO_CONTEXT_VALIDATE";
318                     default:
319                         return "UNKNOWN";
320                 }
321             case EVENT_TYPE_GROUP_STREAM_STATUS_CHANGED:
322                 switch (value) {
323                     case GROUP_STREAM_STATUS_IDLE:
324                         return "GROUP_STREAM_STATUS_IDLE";
325                     case GROUP_STREAM_STATUS_STREAMING:
326                         return "GROUP_STREAM_STATUS_STREAMING";
327                     default:
328                         return "UNKNOWN";
329                 }
330             default:
331                 break;
332         }
333         return Integer.toString(value);
334     }
335 
eventTypeValue3ToString(int type, int value)336     private static String eventTypeValue3ToString(int type, int value) {
337         switch (type) {
338             case EVENT_TYPE_AUDIO_CONF_CHANGED:
339                 // FIXME: It should have proper location names here
340                 return "{snk_audio_loc:" + value + "}";
341             default:
342                 break;
343         }
344         return Integer.toString(value);
345     }
346 
eventTypeValue4ToString(int type, int value)347     private static String eventTypeValue4ToString(int type, int value) {
348         switch (type) {
349             case EVENT_TYPE_AUDIO_CONF_CHANGED:
350                 // FIXME: It should have proper location names here
351                 return "{src_audio_loc:" + value + "}";
352             default:
353                 break;
354         }
355         return Integer.toString(value);
356     }
357 
eventTypeValue5ToString(int type, int value)358     private static String eventTypeValue5ToString(int type, int value) {
359         switch (type) {
360             case EVENT_TYPE_AUDIO_CONF_CHANGED:
361                 return "{available_contexts:" + Integer.toBinaryString(value) + "}";
362             default:
363                 break;
364         }
365         return Integer.toString(value);
366     }
367 
eventTypeValueBool1ToString(int type, boolean value)368     private static String eventTypeValueBool1ToString(int type, boolean value) {
369         switch (type) {
370             case EVENT_TYPE_BROADCAST_AUDIO_SESSION_CREATED:
371                 // same as EVENT_TYPE_BROADCAST_CREATED
372             case EVENT_TYPE_BROADCAST_CREATED:
373                 return "{success:" + value + "}";
374             default:
375                 return "<unused>";
376         }
377     }
378 
eventTypeValueCodec1ToString( int type, BluetoothLeAudioCodecConfig value)379     private static String eventTypeValueCodec1ToString(
380             int type, BluetoothLeAudioCodecConfig value) {
381         switch (type) {
382             case EVENT_TYPE_AUDIO_GROUP_CURRENT_CODEC_CONFIG_CHANGED:
383                 return "{input codec =" + value + "}";
384             default:
385                 return "<unused>";
386         }
387     }
388 
eventTypeValueCodec2ToString( int type, BluetoothLeAudioCodecConfig value)389     private static String eventTypeValueCodec2ToString(
390             int type, BluetoothLeAudioCodecConfig value) {
391         switch (type) {
392             case EVENT_TYPE_AUDIO_GROUP_CURRENT_CODEC_CONFIG_CHANGED:
393                 return "{output codec =" + value + "}";
394             default:
395                 return "<unused>";
396         }
397     }
398 
eventTypeValueCodecList1ToString( int type, List<BluetoothLeAudioCodecConfig> value)399     private static String eventTypeValueCodecList1ToString(
400             int type, List<BluetoothLeAudioCodecConfig> value) {
401         String valueStr = "";
402         switch (type) {
403             case EVENT_TYPE_AUDIO_LOCAL_CODEC_CONFIG_CAPA_CHANGED:
404                 for (BluetoothLeAudioCodecConfig n : value) {
405                     valueStr = valueStr.concat(n.toString() + "\n");
406                 }
407                 return "{input local capa codec = \n" + valueStr + "}";
408             case EVENT_TYPE_AUDIO_GROUP_SELECTABLE_CODEC_CONFIG_CHANGED:
409                 for (BluetoothLeAudioCodecConfig n : value) {
410                     valueStr = valueStr.concat(n.toString() + "\n");
411                 }
412                 return "{input selectable codec =" + valueStr + "}";
413             default:
414                 return "<unused>";
415         }
416     }
417 
eventTypeValueCodecList2ToString( int type, List<BluetoothLeAudioCodecConfig> value)418     private static String eventTypeValueCodecList2ToString(
419             int type, List<BluetoothLeAudioCodecConfig> value) {
420         String valueStr = "";
421         switch (type) {
422             case EVENT_TYPE_AUDIO_LOCAL_CODEC_CONFIG_CAPA_CHANGED:
423                 for (BluetoothLeAudioCodecConfig n : value) {
424                     valueStr = valueStr.concat(n.toString() + "\n");
425                 }
426                 return "{output local capa codec = \n" + valueStr + "}";
427             case EVENT_TYPE_AUDIO_GROUP_SELECTABLE_CODEC_CONFIG_CHANGED:
428                 for (BluetoothLeAudioCodecConfig n : value) {
429                     valueStr = valueStr.concat(n.toString() + "\n");
430                 }
431                 return "{output selectable codec =" + valueStr + "}";
432             default:
433                 return "<unused>";
434         }
435     }
436 
broadcastStateToString(int state)437     private static String broadcastStateToString(int state) {
438         switch (state) {
439             case BROADCAST_STATE_STOPPED:
440                 return "BROADCAST_STATE_STOPPED";
441             case BROADCAST_STATE_CONFIGURING:
442                 return "BROADCAST_STATE_CONFIGURING";
443             case BROADCAST_STATE_PAUSED:
444                 return "BROADCAST_STATE_PAUSED";
445             case BROADCAST_STATE_ENABLING:
446                 return "BROADCAST_STATE_ENABLING";
447             case BROADCAST_STATE_DISABLING:
448                 return "BROADCAST_STATE_DISABLING";
449             case BROADCAST_STATE_STOPPING:
450                 return "BROADCAST_STATE_STOPPING";
451             case BROADCAST_STATE_STREAMING:
452                 return "BROADCAST_STATE_STREAMING";
453             default:
454                 return "UNKNOWN";
455         }
456     }
457 
eventTypeValueBroadcastMetadataToString( BluetoothLeBroadcastMetadata meta)458     private static String eventTypeValueBroadcastMetadataToString(
459             BluetoothLeBroadcastMetadata meta) {
460         return meta.toString();
461     }
462 
encodeHexString(byte[] pduData)463     protected static String encodeHexString(byte[] pduData) {
464         StringBuilder out = new StringBuilder(pduData.length * 2);
465         for (int i = 0; i < pduData.length; i++) {
466             // MS-nibble first
467             out.append(Integer.toString((pduData[i] >> 4) & 0x0f, 16));
468             out.append(Integer.toString(pduData[i] & 0x0f, 16));
469         }
470         return out.toString();
471     }
472 }
473