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