From 1a2f5138a9f9f57ee41bf09657e7fccf11973b9a Mon Sep 17 00:00:00 2001 From: Kavish Devar Date: Wed, 22 Oct 2025 12:52:46 +0530 Subject: [PATCH] android: parse device info --- .../librepods/services/AirPodsService.kt | 7 +- .../librepods/utils/AACPManager.kt | 65 +++++++++++++++++-- 2 files changed, 63 insertions(+), 9 deletions(-) diff --git a/android/app/src/main/java/me/kavishdevar/librepods/services/AirPodsService.kt b/android/app/src/main/java/me/kavishdevar/librepods/services/AirPodsService.kt index 0f3e72c..e8dc010 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/services/AirPodsService.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/services/AirPodsService.kt @@ -926,8 +926,11 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList ) } - override fun onDeviceMetadataReceived(deviceMetadata: ByteArray) { - + override fun onDeviceInformationReceived(deviceInformation: AACPManager.Companion.AirPodsInformation) { + Log.d( + "AirPodsParser", + "Device Information: name: ${deviceInformation.name}, modelNumber: ${deviceInformation.modelNumber}, manufacturer: ${deviceInformation.manufacturer}, serialNumber: ${deviceInformation.serialNumber}, version1: ${deviceInformation.version1}, version2: ${deviceInformation.version2}, hardwareRevision: ${deviceInformation.hardwareRevision}, updaterIdentifier: ${deviceInformation.updaterIdentifier}, leftSerialNumber: ${deviceInformation.leftSerialNumber}, rightSerialNumber: ${deviceInformation.rightSerialNumber}, version3: ${deviceInformation.version3}" + ) } @SuppressLint("NewApi") diff --git a/android/app/src/main/java/me/kavishdevar/librepods/utils/AACPManager.kt b/android/app/src/main/java/me/kavishdevar/librepods/utils/AACPManager.kt index 2d04472..cc2a965 100644 --- a/android/app/src/main/java/me/kavishdevar/librepods/utils/AACPManager.kt +++ b/android/app/src/main/java/me/kavishdevar/librepods/utils/AACPManager.kt @@ -42,7 +42,7 @@ class AACPManager { const val CONTROL_COMMAND: Byte = 0x09 const val EAR_DETECTION: Byte = 0x06 const val CONVERSATION_AWARENESS: Byte = 0x4B - const val DEVICE_METADATA: Byte = 0x1D + const val INFORMATION: Byte = 0x1D const val RENAME: Byte = 0x1E const val HEADTRACKING: Byte = 0x17 const val PROXIMITY_KEYS_REQ: Byte = 0x30 @@ -181,6 +181,20 @@ class AACPManager { val info2: Byte, var type: String? ) + + data class AirPodsInformation( + val name: String, + val modelNumber: String, + val manufacturer: String, + val serialNumber: String, + val version1: String, + val version2: String, + val hardwareRevision: String, + val updaterIdentifier: String, + val leftSerialNumber: String, + val rightSerialNumber: String, + val version3: String + ) } var controlCommandStatusList: MutableList = @@ -239,7 +253,7 @@ class AACPManager { fun onEarDetectionReceived(earDetection: ByteArray) fun onConversationAwarenessReceived(conversationAwareness: ByteArray) fun onControlCommandReceived(controlCommand: ByteArray) - fun onDeviceMetadataReceived(deviceMetadata: ByteArray) + fun onDeviceInformationReceived(deviceInformation: AirPodsInformation) fun onHeadTrackingReceived(headTracking: ByteArray) fun onUnknownPacketReceived(packet: ByteArray) fun onProximityKeysReceived(proximityKeys: ByteArray) @@ -481,10 +495,6 @@ class AACPManager { callback?.onConversationAwarenessReceived(packet) } - Opcodes.DEVICE_METADATA -> { - callback?.onDeviceMetadataReceived(packet) - } - Opcodes.HEADTRACKING -> { if (packet.size < 70) { Log.w( @@ -584,8 +594,14 @@ class AACPManager { eqData = FloatArray(8) { i -> eq1.get(i) } Log.d(TAG, "EQ Data set to: ${eqData.toList()}, eqOnPhone: $eqOnPhone, eqOnMedia: $eqOnMedia") } - + + Opcodes.INFORMATION -> { + Log.e(TAG, "Parsing Information Packet") + val information = parseInformationPacket(packet) + callback?.onDeviceInformationReceived(information) + } else -> { + Log.d(TAG, "Unknown opcode received: ${opcode.toHexString()}") callback?.onUnknownPacketReceived(packet) } } @@ -1218,4 +1234,39 @@ class AACPManager { connectedDevices = listOf() audioSource = null } + + fun parseInformationPacket(packet: ByteArray): AirPodsInformation { + val data = packet.sliceArray(6 until packet.size) + + var index = 0 + while (index < data.size && data[index] != 0x00.toByte()) index++ + + val strings = mutableListOf() + while (index < data.size) { + // skip 0x00 bytes + while (index < data.size && data[index] == 0x00.toByte()) index++ + if (index >= data.size) break + val start = index + // find next 0x00 byte + while (index < data.size && data[index] != 0x00.toByte()) index++ + val str = data.sliceArray(start until index).decodeToString() + strings.add(str) + } + + strings.removeAt(0) // I'm too lazy to adjust, just removing the first empty string + + return AirPodsInformation( + name = strings.getOrNull(0) ?: "", + modelNumber = strings.getOrNull(1) ?: "", + manufacturer = strings.getOrNull(2) ?: "", + serialNumber = strings.getOrNull(3) ?: "", + version1 = strings.getOrNull(4) ?: "", + version2 = strings.getOrNull(5) ?: "", + hardwareRevision = strings.getOrNull(6) ?: "", + updaterIdentifier = strings.getOrNull(7) ?: "", + leftSerialNumber = strings.getOrNull(8) ?: "", + rightSerialNumber = strings.getOrNull(9) ?: "", + version3 = strings.getOrNull(10) ?: "", + ) + } }