@@ -88,9 +88,9 @@ import me.kavishdevar.librepods.constants.StemAction
8888import me.kavishdevar.librepods.constants.isHeadTrackingData
8989import me.kavishdevar.librepods.utils.AACPManager
9090import me.kavishdevar.librepods.utils.AACPManager.Companion.StemPressType
91+ import me.kavishdevar.librepods.utils.ATTManager
9192import me.kavishdevar.librepods.utils.AirPodsInstance
9293import me.kavishdevar.librepods.utils.AirPodsModels
93- import me.kavishdevar.librepods.utils.ATTManager
9494import me.kavishdevar.librepods.utils.BLEManager
9595import me.kavishdevar.librepods.utils.BluetoothConnectionManager
9696import me.kavishdevar.librepods.utils.CrossDevice
@@ -229,6 +229,9 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
229229 private var handleIncomingCallOnceConnected = false
230230
231231 lateinit var bleManager: BLEManager
232+
233+ private lateinit var socket: BluetoothSocket
234+
232235 private val bleStatusListener = object : BLEManager.AirPodsStatusListener {
233236 @SuppressLint("NewApi")
234237 override fun onDeviceStatusChanged(
@@ -973,7 +976,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
973976 config.airpodsVersion3 = deviceInformation.version3
974977 config.airpodsHardwareRevision = deviceInformation.hardwareRevision
975978 config.airpodsUpdaterIdentifier = deviceInformation.updaterIdentifier
976-
979+
977980 val model = AirPodsModels.getModelByModelNumber(config.airpodsModelNumber)
978981 if (model != null) {
979982 airpodsInstance = AirPodsInstance(
@@ -1032,8 +1035,9 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
10321035 byteArrayOf(0x00)
10331036 )
10341037 // this also means that the other device has start playing the audio, and if that's true, we can again start listening for audio config changes
1035- Log.d(TAG, "Another device started playing audio, listening for audio config changes again")
1036- MediaController.pausedForOtherDevice = false
1038+ // Log.d(TAG, "Another device started playing audio, listening for audio config changes again")
1039+ // MediaController.pausedForOtherDevice = false
1040+ // future me: what the heck is this? this just means it will not be taking over again if audio source doesn't change???
10371041 }
10381042 }
10391043
@@ -1842,7 +1846,6 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
18421846 notificationManager.notify(1, updatedNotification)
18431847 notificationManager.cancel(2)
18441848 } else if (!config.bleOnlyMode && !socket.isConnected && isConnectedLocally) {
1845- Log.d(TAG, "<LogCollector:Complete:Failed> Socket not connected")
18461849 showSocketConnectionFailureNotification("Socket created, but not connected. Is the Bluetooth process hooked?")
18471850 }
18481851 }
@@ -2135,10 +2138,10 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
21352138 val name = context?.getSharedPreferences("settings", MODE_PRIVATE)
21362139 ?.getString("name", bluetoothDevice?.name)
21372140 if (bluetoothDevice != null && action != null && !action.isEmpty()) {
2138- Log.d(TAG, "Received bluetooth connection broadcast")
2141+ Log.d(TAG, "Received bluetooth connection broadcast: action=$action ")
21392142 if (ServiceManager.getService()?.isConnectedLocally == true) {
2140- Log.d(TAG, "Device is already connected locally, ignoring broadcast ")
2141- ServiceManager.getService()?.manuallyCheckForAudioSource()
2143+ Log.d(TAG, "Device is already connected locally, checking if we should keep audio connected ")
2144+ if ( ServiceManager.getService()?.socket?.isConnected == true) ServiceManager.getService()?. manuallyCheckForAudioSource() else Log.d(TAG, "We're not connected, ignoring" )
21422145 return
21432146 }
21442147 if (BluetoothDevice.ACTION_ACL_CONNECTED == action) {
@@ -2175,14 +2178,14 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
21752178 return START_STICKY
21762179 }
21772180
2178- private lateinit var socket: BluetoothSocket
2179-
21802181 fun manuallyCheckForAudioSource() {
2181- val shouldResume = MediaController.getMusicActive()
2182- if ((earDetectionNotification.status[0] != 0.toByte() && earDetectionNotification.status[1] != 0.toByte()) || disconnectedBecauseReversed || otherDeviceTookOver) {
2182+ val shouldResume = MediaController.getMusicActive() // todo: for some reason we lose this info after disconnecting, probably android dispatches some event. haven't investigated yet.
2183+ if (airpodsInstance == null) return
2184+ Log.d(TAG, "disconnectedBecauseReversed: $disconnectedBecauseReversed, otherDeviceTookOver: $otherDeviceTookOver")
2185+ if ((earDetectionNotification.status[0] != 0.toByte() && earDetectionNotification.status[1] != 0.toByte()) || disconnectedBecauseReversed || otherDeviceTookOver) {
21832186 Log.d(
21842187 TAG,
2185- "For some reason, Android connected to the audio profile itself even after disconnecting. Disconnecting audio profile again!"
2188+ "For some reason, Android connected to the audio profile itself even after disconnecting. Disconnecting audio profile again! I will resume: $shouldResume "
21862189 )
21872190 disconnectAudio(this, device, shouldResume = shouldResume)
21882191 }
@@ -2378,7 +2381,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
23782381 }
23792382
23802383 @SuppressLint("MissingPermission", "UnspecifiedRegisterReceiverFlag")
2381- fun connectToSocket(device: BluetoothDevice) {
2384+ fun connectToSocket(device: BluetoothDevice, manual: Boolean = false ) {
23822385 Log.d(TAG, "<LogCollector:Start> Connecting to socket")
23832386 HiddenApiBypass.addHiddenApiExemptions("Landroid/bluetooth/BluetoothSocket;")
23842387 val uuid: ParcelUuid = ParcelUuid.fromString("74ec2172-0bad-4d01-8f77-997b2be0722a")
@@ -2387,7 +2390,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
23872390 createBluetoothSocket(device, uuid)
23882391 } catch (e: Exception) {
23892392 Log.e(TAG, "Failed to create BluetoothSocket: ${e.message}")
2390- showSocketConnectionFailureNotification("Failed to create Bluetooth socket: ${e.message }")
2393+ showSocketConnectionFailureNotification("Failed to create Bluetooth socket: ${e.localizedMessage }")
23912394 return
23922395 }
23932396
@@ -2431,15 +2434,29 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
24312434 )
24322435 Log.d(TAG, "<LogCollector:Complete:Success> Socket connected")
24332436 } catch (e: Exception) {
2434- Log.d(TAG, "<LogCollector:Complete:Failed> Socket not connected")
2435- showSocketConnectionFailureNotification("Socket created, but not connected. Is the Bluetooth process hooked?")
2436- throw e
2437+ Log.d(TAG, "<LogCollector:Complete:Failed> Socket not connected, ${e.message}")
2438+ if (manual) {
2439+ sendToast(
2440+ "Couldn't connect to socket: ${e.localizedMessage}"
2441+ )
2442+ } else {
2443+ showSocketConnectionFailureNotification("Couldn't connect to socket: ${e.localizedMessage}")
2444+ }
2445+ return@withTimeout
2446+ // throw e // lol how did i not catch this before... gonna comment this line instead of removing to preserve history
24372447 }
24382448 }
2439- if (!socket.isConnected) {
2440- Log.d(TAG, "<LogCollector:Complete:Failed> Socket not connected")
2441- showSocketConnectionFailureNotification("Socket created, but not connected. Is the Bluetooth process hooked?")
2449+ }
2450+ if (!socket.isConnected) {
2451+ Log.d(TAG, "<LogCollector:Complete:Failed> Socket not connected")
2452+ if (manual) {
2453+ sendToast(
2454+ "Couldn't connect to socket: timeout."
2455+ )
2456+ } else {
2457+ showSocketConnectionFailureNotification("Couldn't connect to socket: Timeout")
24422458 }
2459+ return
24432460 }
24442461 this@AirPodsService.device = device
24452462 socket.let {
@@ -2519,15 +2536,15 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
25192536 } catch (e: Exception) {
25202537 e.printStackTrace()
25212538 Log.d(TAG, "Failed to connect to socket: ${e.message}")
2522- showSocketConnectionFailureNotification("Failed to establish connection: ${e.message }")
2539+ showSocketConnectionFailureNotification("Failed to establish connection: ${e.localizedMessage }")
25232540 isConnectedLocally = false
25242541 this@AirPodsService.device = device
25252542 updateNotificationContent(false)
25262543 }
25272544 }
25282545 }
25292546
2530- fun disconnect () {
2547+ fun disconnectForCD () {
25312548 if (!this::socket.isInitialized) return
25322549 socket.close()
25332550 MediaController.pausedWhileTakingOver = false
@@ -2552,6 +2569,33 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
25522569 CrossDevice.isAvailable = true
25532570 }
25542571
2572+ fun disconnectAirPods() {
2573+ if (!this::socket.isInitialized) return
2574+ socket.close()
2575+ isConnectedLocally = false
2576+ aacpManager.disconnected()
2577+ attManager?.disconnect()
2578+ updateNotificationContent(false)
2579+ sendBroadcast(Intent(AirPodsNotifications.AIRPODS_DISCONNECTED))
2580+
2581+ val bluetoothAdapter = getSystemService(BluetoothManager::class.java).adapter
2582+ bluetoothAdapter.getProfileProxy(this, object : BluetoothProfile.ServiceListener {
2583+ override fun onServiceConnected(profile: Int, proxy: BluetoothProfile) {
2584+ if (profile == BluetoothProfile.A2DP) {
2585+ val connectedDevices = proxy.connectedDevices
2586+ if (connectedDevices.isNotEmpty()) {
2587+ MediaController.sendPause()
2588+ }
2589+ }
2590+ bluetoothAdapter.closeProfileProxy(profile, proxy)
2591+ }
2592+
2593+ override fun onServiceDisconnected(profile: Int) {}
2594+ }, BluetoothProfile.A2DP)
2595+ Log.d(TAG, "Disconnected AirPods upon user request")
2596+
2597+ }
2598+
25552599 val earDetectionNotification = AirPodsNotifications.EarDetection()
25562600 val ancNotification = AirPodsNotifications.ANC()
25572601 val batteryNotification = AirPodsNotifications.BatteryNotification()
@@ -2750,6 +2794,19 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
27502794 isHeadTrackingActive = false
27512795 }
27522796
2797+ @SuppressLint("MissingPermission")
2798+ fun reconnectFromSavedMac(){
2799+ val bluetoothAdapter = getSystemService(BluetoothManager::class.java).adapter
2800+ device = bluetoothAdapter.bondedDevices.find {
2801+ it.address == macAddress
2802+ }
2803+ if (device != null) {
2804+ CoroutineScope(Dispatchers.IO).launch {
2805+ connectToSocket(device!!, manual = true)
2806+ }
2807+ }
2808+ }
2809+
27532810}
27542811
27552812private fun Int.dpToPx(): Int {
0 commit comments