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