mirror of
https://github.com/kavishdevar/librepods.git
synced 2026-06-13 20:08:55 +00:00
android: refactor AACP socket handling
This commit is contained in:
@@ -118,6 +118,7 @@ import com.kyant.backdrop.backdrops.rememberLayerBackdrop
|
||||
import dev.chrisbanes.haze.hazeSource
|
||||
import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi
|
||||
import dev.chrisbanes.haze.rememberHazeState
|
||||
import me.kavishdevar.librepods.bluetooth.BluetoothConnectionManager
|
||||
import me.kavishdevar.librepods.data.AirPodsNotifications
|
||||
import me.kavishdevar.librepods.data.ControlCommandRepository
|
||||
import me.kavishdevar.librepods.presentation.components.AppInfoCard
|
||||
@@ -541,7 +542,7 @@ fun Main() {
|
||||
Context.BIND_AUTO_CREATE
|
||||
)
|
||||
|
||||
if (airPodsService.value?.isConnected() == true) {
|
||||
if (BluetoothConnectionManager.getAACPSocket()?.isConnected == true) {
|
||||
isConnected.value = true
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -31,9 +31,8 @@ import kotlin.io.encoding.ExperimentalEncodingApi
|
||||
* constructing and parsing packets for communication with AirPods.
|
||||
*/
|
||||
class AACPManager {
|
||||
private val TAG = "AACPManager[${System.identityHashCode(this)}]"
|
||||
companion object {
|
||||
private const val TAG = "AACPManager"
|
||||
|
||||
@Suppress("unused")
|
||||
object Opcodes {
|
||||
const val SET_FEATURE_FLAGS: Byte = 0x4D
|
||||
|
||||
@@ -24,6 +24,7 @@ import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.content.SharedPreferences
|
||||
import android.content.pm.PackageManager
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.core.content.edit
|
||||
import androidx.lifecycle.ViewModel
|
||||
@@ -40,6 +41,7 @@ import me.kavishdevar.librepods.bluetooth.AACPManager
|
||||
import me.kavishdevar.librepods.bluetooth.AACPManager.Companion.ControlCommandIdentifiers
|
||||
import me.kavishdevar.librepods.bluetooth.ATTCCCDHandles
|
||||
import me.kavishdevar.librepods.bluetooth.ATTHandles
|
||||
import me.kavishdevar.librepods.bluetooth.BluetoothConnectionManager
|
||||
import me.kavishdevar.librepods.data.AirPodsInstance
|
||||
import me.kavishdevar.librepods.data.AirPodsModels
|
||||
import me.kavishdevar.librepods.data.AirPodsNotifications
|
||||
@@ -352,7 +354,7 @@ class AirPodsViewModel(
|
||||
service.let { service ->
|
||||
_uiState.update {
|
||||
it.copy(
|
||||
isLocallyConnected = service.isConnected(), battery = service.getBattery()
|
||||
isLocallyConnected = BluetoothConnectionManager.getAACPSocket()?.isConnected == true, battery = service.getBattery()
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -382,7 +384,6 @@ class AirPodsViewModel(
|
||||
|
||||
val connectionSuccessful = sharedPreferences.getBoolean("connection_successful", false)
|
||||
|
||||
val fossUpgraded = sharedPreferences.getBoolean("foss_upgraded", false)
|
||||
_uiState.update {
|
||||
it.copy(
|
||||
offListeningMode = offListeningModeEnabled,
|
||||
@@ -398,8 +399,8 @@ class AirPodsViewModel(
|
||||
}
|
||||
|
||||
// faulty update on Play caused PLAY_BUILD to be false and resulted in use of FOSS billing in Play. since FOSS is not verified, we need to give 2 weeks to verify the purchase
|
||||
|
||||
if (BuildConfig.PLAY_BUILD) {
|
||||
val fossUpgraded = sharedPreferences.getBoolean("foss_upgraded", false)
|
||||
val expiryTime = sharedPreferences.getLong("premium_expiry_time", 0L)
|
||||
val now = System.currentTimeMillis()
|
||||
|
||||
|
||||
@@ -35,9 +35,10 @@ import android.util.Log
|
||||
import androidx.annotation.RequiresApi
|
||||
import me.kavishdevar.librepods.QuickSettingsDialogActivity
|
||||
import me.kavishdevar.librepods.R
|
||||
import me.kavishdevar.librepods.bluetooth.AACPManager
|
||||
import me.kavishdevar.librepods.bluetooth.BluetoothConnectionManager
|
||||
import me.kavishdevar.librepods.data.AirPodsNotifications
|
||||
import me.kavishdevar.librepods.data.NoiseControlMode
|
||||
import me.kavishdevar.librepods.bluetooth.AACPManager
|
||||
import kotlin.io.encoding.ExperimentalEncodingApi
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.Q)
|
||||
@@ -98,7 +99,7 @@ class AirPodsQSService : TileService() {
|
||||
Log.d("AirPodsQSService", "onStartListening")
|
||||
|
||||
val service = ServiceManager.getService()
|
||||
isAirPodsConnected = service?.isConnected() == true
|
||||
isAirPodsConnected = BluetoothConnectionManager.getAACPSocket()?.isConnected == true
|
||||
currentAncMode = service?.getANC() ?: (NoiseControlMode.OFF.ordinal + 1)
|
||||
|
||||
if (currentAncMode == NoiseControlMode.OFF.ordinal + 1 && !isOffModeEnabled()) {
|
||||
|
||||
@@ -233,8 +233,6 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
|
||||
|
||||
lateinit var bleManager: BLEManager
|
||||
|
||||
private lateinit var socket: BluetoothSocket
|
||||
|
||||
companion object {
|
||||
init {
|
||||
System.loadLibrary("bluetooth_socket")
|
||||
@@ -246,7 +244,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
|
||||
override fun onDeviceStatusChanged(
|
||||
device: BLEManager.AirPodsStatus, previousStatus: BLEManager.AirPodsStatus?
|
||||
) {
|
||||
if (device.connectionState == "Disconnected" && !isConnected()) { // should never happen unless android messes up and sends us a stale broadcast
|
||||
if (device.connectionState == "Disconnected" && BluetoothConnectionManager.getAACPSocket()?.isConnected != true) { // should never happen unless android messes up and sends us a stale broadcast
|
||||
Log.d(TAG, "Seems no device has taken over, we will.")
|
||||
val bluetoothManager = getSystemService(BluetoothManager::class.java)
|
||||
val bluetoothAdapter = bluetoothManager.adapter
|
||||
@@ -258,7 +256,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
|
||||
connectToSocket(bluetoothAdapter, bluetoothDevice)
|
||||
}
|
||||
Log.d(TAG, "Device status changed")
|
||||
if (socket.isConnected) return
|
||||
if (BluetoothConnectionManager.getAACPSocket()?.isConnected == true) return
|
||||
val leftLevel = bleManager.getMostRecentStatus()?.leftBattery ?: 0
|
||||
val rightLevel = bleManager.getMostRecentStatus()?.rightBattery ?: 0
|
||||
val caseLevel = bleManager.getMostRecentStatus()?.caseBattery ?: 0
|
||||
@@ -291,7 +289,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
|
||||
getSharedPreferences("settings", MODE_PRIVATE).getString("name", "AirPods Pro")
|
||||
?: "AirPods"
|
||||
)
|
||||
if (socket.isConnected) return
|
||||
if (BluetoothConnectionManager.getAACPSocket()?.isConnected == true) return
|
||||
val leftLevel = bleManager.getMostRecentStatus()?.leftBattery ?: 0
|
||||
val rightLevel = bleManager.getMostRecentStatus()?.rightBattery ?: 0
|
||||
val caseLevel = bleManager.getMostRecentStatus()?.caseBattery ?: 0
|
||||
@@ -325,7 +323,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
|
||||
}
|
||||
|
||||
override fun onBatteryChanged(device: BLEManager.AirPodsStatus) {
|
||||
if (socket.isConnected) return
|
||||
if (BluetoothConnectionManager.getAACPSocket()?.isConnected == true) return
|
||||
val leftLevel = bleManager.getMostRecentStatus()?.leftBattery ?: 0
|
||||
val rightLevel = bleManager.getMostRecentStatus()?.rightBattery ?: 0
|
||||
val caseLevel = bleManager.getMostRecentStatus()?.caseBattery ?: 0
|
||||
@@ -1739,7 +1737,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
|
||||
|
||||
val socketFailureChannel = NotificationChannel(
|
||||
"socket_connection_failure",
|
||||
"AirPods Socket Connection Issues",
|
||||
"AirPods BluetoothConnectionManager.getAACPSocket()? Connection Issues",
|
||||
NotificationManager.IMPORTANCE_HIGH
|
||||
).apply {
|
||||
description = "Notifications about problems connecting to AirPods protocol"
|
||||
@@ -1785,7 +1783,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
|
||||
if (BuildConfig.FLAVOR != "xposed") {
|
||||
Log.w(
|
||||
TAG,
|
||||
"Not showing socket error notification to user, the service shouldn't be running if it isn't supported."
|
||||
"Not showing BluetoothConnectionManager.getAACPSocket()? error notification to user, the service shouldn't be running if it isn't supported."
|
||||
)
|
||||
return
|
||||
}
|
||||
@@ -2040,10 +2038,10 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||
)
|
||||
|
||||
if (!::socket.isInitialized) {
|
||||
if (BluetoothConnectionManager.getAACPSocket() == null) {
|
||||
return
|
||||
}
|
||||
if (connected && (config.bleOnlyMode || socket.isConnected)) {
|
||||
if (BluetoothConnectionManager.getAACPSocket()?.isConnected == true) {
|
||||
val updatedNotificationBuilder =
|
||||
NotificationCompat.Builder(this, "airpods_connection_status")
|
||||
.setSmallIcon(R.drawable.airpods)
|
||||
@@ -2091,8 +2089,8 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
|
||||
notificationManager.cancel(1)
|
||||
} else if (!connected) {
|
||||
notificationManager.cancel(2)
|
||||
} else if (!config.bleOnlyMode && !socket.isConnected) {
|
||||
showSocketConnectionFailureNotification("Socket created, but not connected. Check logs")
|
||||
} else if (!config.bleOnlyMode && BluetoothConnectionManager.getAACPSocket()?.isConnected != true) {
|
||||
showSocketConnectionFailureNotification("BluetoothConnectionManager.getAACPSocket()? created, but not connected. Check logs")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2467,8 +2465,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
|
||||
Log.d(
|
||||
TAG, "owns connection: $ownsConnection"
|
||||
)
|
||||
if (!::socket.isInitialized) return
|
||||
if (socket.isConnected) {
|
||||
if (BluetoothConnectionManager.getAACPSocket()?.isConnected == true) {
|
||||
if (!XposedRemotePrefProvider.create().getBoolean("vendor_id_hook", false) || ownsConnection == 0) {
|
||||
Log.d(TAG, "not taking over, vendorid is probably not set to apple")
|
||||
return
|
||||
@@ -2677,10 +2674,11 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
|
||||
fun connectToSocket(
|
||||
adapter: BluetoothAdapter, device: BluetoothDevice, manual: Boolean = false
|
||||
) {
|
||||
if (BluetoothConnectionManager.getAACPSocket() != null && BluetoothConnectionManager.getAACPSocket()?.isConnected == true) return
|
||||
Log.d(TAG, "<LogCollector:Start> Connecting to socket")
|
||||
val uuid: ParcelUuid = ParcelUuid.fromString("74ec2172-0bad-4d01-8f77-997b2be0722a")
|
||||
// if (!isConnectedLocally) {
|
||||
socket = try {
|
||||
val socket = try {
|
||||
createBluetoothSocket(adapter, device, uuid, 4097)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to create BluetoothSocket: ${e.message}")
|
||||
@@ -2768,7 +2766,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
|
||||
}
|
||||
}
|
||||
if (!socket.isConnected) {
|
||||
Log.d(TAG, "<LogCollector:Complete:Failed> Socket not connected")
|
||||
Log.d(TAG, "<LogCollector:Complete:Failed> socket not connected")
|
||||
if (manual) {
|
||||
sendToast(
|
||||
"Couldn't connect to socket: timeout."
|
||||
@@ -2779,13 +2777,14 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
|
||||
return
|
||||
}
|
||||
this@AirPodsService.device = device
|
||||
socket.let {
|
||||
BluetoothConnectionManager.getAACPSocket()?.let {
|
||||
aacpManager.sendPacket(aacpManager.createHandshakePacket())
|
||||
aacpManager.sendSetFeatureFlagsPacket()
|
||||
aacpManager.sendNotificationRequest()
|
||||
Log.d(TAG, "Requesting proximity keys")
|
||||
aacpManager.sendRequestProximityKeys((AACPManager.Companion.ProximityKeyType.IRK.value + AACPManager.Companion.ProximityKeyType.ENC_KEY.value).toByte())
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
delay(200)
|
||||
aacpManager.sendPacket(aacpManager.createHandshakePacket())
|
||||
delay(200)
|
||||
aacpManager.sendSetFeatureFlagsPacket()
|
||||
@@ -2813,55 +2812,53 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
|
||||
setupStemActions()
|
||||
|
||||
while (socket.isConnected) {
|
||||
socket.let { it ->
|
||||
try {
|
||||
val buffer = ByteArray(1024)
|
||||
val bytesRead = it.inputStream.read(buffer)
|
||||
var data: ByteArray
|
||||
if (bytesRead > 0) {
|
||||
data = buffer.copyOfRange(0, bytesRead)
|
||||
sendBroadcast(Intent(AirPodsNotifications.AIRPODS_DATA).apply {
|
||||
putExtra("data", buffer.copyOfRange(0, bytesRead))
|
||||
setPackage(packageName)
|
||||
})
|
||||
val bytes = buffer.copyOfRange(0, bytesRead)
|
||||
val formattedHex = bytes.joinToString(" ") { "%02X".format(it) }
|
||||
try {
|
||||
val buffer = ByteArray(1024)
|
||||
val bytesRead = it.inputStream.read(buffer)
|
||||
var data: ByteArray
|
||||
if (bytesRead > 0) {
|
||||
data = buffer.copyOfRange(0, bytesRead)
|
||||
sendBroadcast(Intent(AirPodsNotifications.AIRPODS_DATA).apply {
|
||||
putExtra("data", buffer.copyOfRange(0, bytesRead))
|
||||
setPackage(packageName)
|
||||
})
|
||||
val bytes = buffer.copyOfRange(0, bytesRead)
|
||||
val formattedHex = bytes.joinToString(" ") { "%02X".format(it) }
|
||||
// CrossDevice.sendReceivedPacket(bytes)
|
||||
updateNotificationContent(
|
||||
true,
|
||||
sharedPreferences.getString("name", device.name),
|
||||
batteryNotification.getBattery()
|
||||
)
|
||||
updateNotificationContent(
|
||||
true,
|
||||
sharedPreferences.getString("name", device.name),
|
||||
batteryNotification.getBattery()
|
||||
)
|
||||
|
||||
aacpManager.receivePacket(data)
|
||||
aacpManager.receivePacket(data)
|
||||
|
||||
if (!isHeadTrackingData(data)) {
|
||||
Log.d("AirPodsData", "Data received: $formattedHex")
|
||||
logPacket(data, "AirPods")
|
||||
}
|
||||
|
||||
} else if (bytesRead == -1) {
|
||||
Log.d("AirPods Service", "Socket closed (bytesRead = -1)")
|
||||
sendBroadcast(Intent(AirPodsNotifications.AIRPODS_DISCONNECTED).apply {
|
||||
setPackage(packageName)
|
||||
})
|
||||
aacpManager.disconnected()
|
||||
return@launch
|
||||
if (!isHeadTrackingData(data)) {
|
||||
Log.d("AirPodsData", "Data received: $formattedHex")
|
||||
logPacket(data, "AirPods")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Error reading data, we have probably disconnected.")
|
||||
e.printStackTrace()
|
||||
|
||||
} else if (bytesRead == -1) {
|
||||
Log.d("AirPods Service", "BluetoothConnectionManager.getAACPSocket()? closed (bytesRead = -1)")
|
||||
sendBroadcast(Intent(AirPodsNotifications.AIRPODS_DISCONNECTED).apply {
|
||||
setPackage(packageName)
|
||||
})
|
||||
aacpManager.disconnected()
|
||||
return@launch
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Error reading data, we have probably disconnected.")
|
||||
e.printStackTrace()
|
||||
sendBroadcast(Intent(AirPodsNotifications.AIRPODS_DISCONNECTED).apply {
|
||||
setPackage(packageName)
|
||||
})
|
||||
aacpManager.disconnected()
|
||||
return@launch
|
||||
}
|
||||
|
||||
}
|
||||
Log.d("AirPods Service", "Socket closed")
|
||||
Log.d("AirPods Service", "socket closed")
|
||||
// isConnectedLocally = false
|
||||
socket.close()
|
||||
aacpManager.disconnected()
|
||||
updateNotificationContent(false)
|
||||
sendBroadcast(Intent(AirPodsNotifications.AIRPODS_DISCONNECTED).apply {
|
||||
@@ -2871,20 +2868,19 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
Log.d(TAG, "Failed to connect to socket: ${e.message}")
|
||||
Log.d(TAG, "Failed to connect to BluetoothConnectionManager.getAACPSocket()?: ${e.message}")
|
||||
showSocketConnectionFailureNotification("Failed to establish connection: ${e.localizedMessage}")
|
||||
// isConnectedLocally = false
|
||||
this@AirPodsService.device = device
|
||||
updateNotificationContent(false)
|
||||
}
|
||||
// } else {
|
||||
// Log.d(TAG, "Already connected locally, skipping socket connection (isConnectedLocally = $isConnectedLocally, socket.isConnected = ${this::socket.isInitialized && socket.isConnected})")
|
||||
// Log.d(TAG, "Already connected locally, skipping BluetoothConnectionManager.getAACPSocket()? connection (isConnectedLocally = $isConnectedLocally, BluetoothConnectionManager.getAACPSocket()?.isConnected = ${this::BluetoothConnectionManager.getAACPSocket()?.isInitialized && BluetoothConnectionManager.getAACPSocket()?.isConnected})")
|
||||
// }
|
||||
}
|
||||
|
||||
fun disconnectForCD() {
|
||||
if (!this::socket.isInitialized) return
|
||||
socket.close()
|
||||
BluetoothConnectionManager.getAACPSocket()?.close()
|
||||
MediaController.pausedWhileTakingOver = false
|
||||
Log.d(TAG, "Disconnected from AirPods, showing island.")
|
||||
showIsland(
|
||||
@@ -2915,8 +2911,12 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
|
||||
}
|
||||
|
||||
fun disconnectAirPods() {
|
||||
if (!this::socket.isInitialized) return
|
||||
socket.close()
|
||||
if (BluetoothConnectionManager.getAACPSocket() == null) return
|
||||
try {
|
||||
BluetoothConnectionManager.getAACPSocket()?.close()
|
||||
} catch(e: Exception) {
|
||||
Log.e(TAG, "error closing aacp socket ${e.message}")
|
||||
}
|
||||
// isConnectedLocally = false
|
||||
aacpManager.disconnected()
|
||||
attManager.disconnected()
|
||||
@@ -3228,10 +3228,6 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun isConnected(): Boolean {
|
||||
return if (::socket.isInitialized) socket.isConnected else false
|
||||
}
|
||||
}
|
||||
|
||||
private fun Int.dpToPx(): Int {
|
||||
|
||||
Reference in New Issue
Block a user