mirror of
https://github.com/kavishdevar/librepods.git
synced 2026-04-27 16:45:38 +00:00
android: improve connection handling
This commit is contained in:
@@ -15,7 +15,7 @@ android {
|
|||||||
minSdk = 28
|
minSdk = 28
|
||||||
targetSdk = 36
|
targetSdk = 36
|
||||||
versionCode = 8
|
versionCode = 8
|
||||||
versionName = "0.2.0-alpha"
|
versionName = "0.2.0-beta.1"
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
|
|||||||
@@ -20,8 +20,6 @@ package me.kavishdevar.librepods.screens
|
|||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.compose.animation.animateColorAsState
|
|
||||||
import androidx.compose.animation.core.tween
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.gestures.detectDragGesturesAfterLongPress
|
import androidx.compose.foundation.gestures.detectDragGesturesAfterLongPress
|
||||||
import androidx.compose.foundation.gestures.detectTapGestures
|
import androidx.compose.foundation.gestures.detectTapGestures
|
||||||
@@ -35,17 +33,10 @@ import androidx.compose.foundation.layout.fillMaxSize
|
|||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material3.Checkbox
|
|
||||||
import androidx.compose.material3.CheckboxDefaults
|
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.HorizontalDivider
|
|
||||||
import androidx.compose.material3.Slider
|
|
||||||
import androidx.compose.material3.SliderDefaults
|
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.DisposableEffect
|
import androidx.compose.runtime.DisposableEffect
|
||||||
@@ -59,8 +50,6 @@ import androidx.compose.runtime.setValue
|
|||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.draw.scale
|
|
||||||
import androidx.compose.ui.draw.shadow
|
|
||||||
import androidx.compose.ui.geometry.Offset
|
import androidx.compose.ui.geometry.Offset
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.input.pointer.pointerInput
|
import androidx.compose.ui.input.pointer.pointerInput
|
||||||
@@ -88,7 +77,6 @@ import kotlinx.coroutines.launch
|
|||||||
import me.kavishdevar.librepods.R
|
import me.kavishdevar.librepods.R
|
||||||
import me.kavishdevar.librepods.composables.NavigationButton
|
import me.kavishdevar.librepods.composables.NavigationButton
|
||||||
import me.kavishdevar.librepods.composables.StyledDropdown
|
import me.kavishdevar.librepods.composables.StyledDropdown
|
||||||
import me.kavishdevar.librepods.composables.StyledIconButton
|
|
||||||
import me.kavishdevar.librepods.composables.StyledScaffold
|
import me.kavishdevar.librepods.composables.StyledScaffold
|
||||||
import me.kavishdevar.librepods.composables.StyledSlider
|
import me.kavishdevar.librepods.composables.StyledSlider
|
||||||
import me.kavishdevar.librepods.composables.StyledToggle
|
import me.kavishdevar.librepods.composables.StyledToggle
|
||||||
|
|||||||
@@ -67,7 +67,6 @@ import androidx.compose.ui.unit.dp
|
|||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
import androidx.navigation.compose.rememberNavController
|
import androidx.navigation.compose.rememberNavController
|
||||||
import com.kyant.backdrop.backdrops.layerBackdrop
|
|
||||||
import com.kyant.backdrop.backdrops.rememberLayerBackdrop
|
import com.kyant.backdrop.backdrops.rememberLayerBackdrop
|
||||||
import com.kyant.backdrop.drawBackdrop
|
import com.kyant.backdrop.drawBackdrop
|
||||||
import com.kyant.backdrop.highlight.Highlight
|
import com.kyant.backdrop.highlight.Highlight
|
||||||
@@ -370,7 +369,9 @@ fun AirPodsSettingsScreen(dev: BluetoothDevice?, service: AirPodsService,
|
|||||||
Spacer(Modifier.height(32.dp))
|
Spacer(Modifier.height(32.dp))
|
||||||
StyledButton(
|
StyledButton(
|
||||||
onClick = { navController.navigate("troubleshooting") },
|
onClick = { navController.navigate("troubleshooting") },
|
||||||
backdrop = backdrop
|
backdrop = backdrop,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth(0.9f)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = "Troubleshoot Connection",
|
text = "Troubleshoot Connection",
|
||||||
@@ -378,7 +379,26 @@ fun AirPodsSettingsScreen(dev: BluetoothDevice?, service: AirPodsService,
|
|||||||
fontSize = 16.sp,
|
fontSize = 16.sp,
|
||||||
fontWeight = FontWeight.Medium,
|
fontWeight = FontWeight.Medium,
|
||||||
fontFamily = FontFamily(Font(R.font.sf_pro)),
|
fontFamily = FontFamily(Font(R.font.sf_pro)),
|
||||||
color = if (isSystemInDarkTheme()) Color.White else Color.Black
|
color = if (isSystemInDarkTheme()) Color.White else Color.Black
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Spacer(Modifier.height(16.dp))
|
||||||
|
StyledButton(
|
||||||
|
onClick = {
|
||||||
|
service.reconnectFromSavedMac()
|
||||||
|
},
|
||||||
|
backdrop = backdrop,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth(0.9f)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.reconnect_to_last_device),
|
||||||
|
style = TextStyle(
|
||||||
|
fontSize = 16.sp,
|
||||||
|
fontWeight = FontWeight.Medium,
|
||||||
|
fontFamily = FontFamily(Font(R.font.sf_pro)),
|
||||||
|
color = if (isSystemInDarkTheme()) Color.White else Color.Black
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,9 +54,9 @@ import androidx.compose.ui.unit.sp
|
|||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
import com.kyant.backdrop.backdrops.layerBackdrop
|
import com.kyant.backdrop.backdrops.layerBackdrop
|
||||||
import com.kyant.backdrop.backdrops.rememberLayerBackdrop
|
import com.kyant.backdrop.backdrops.rememberLayerBackdrop
|
||||||
|
import dev.chrisbanes.haze.HazeState
|
||||||
import dev.chrisbanes.haze.hazeSource
|
import dev.chrisbanes.haze.hazeSource
|
||||||
import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi
|
import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi
|
||||||
import dev.chrisbanes.haze.rememberHazeState
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@@ -97,7 +97,7 @@ fun HearingAidScreen(navController: NavController) {
|
|||||||
mutableStateOf((aidStatus?.value?.getOrNull(1) == 0x01.toByte()) && (assistStatus?.value?.getOrNull(0) == 0x01.toByte()))
|
mutableStateOf((aidStatus?.value?.getOrNull(1) == 0x01.toByte()) && (assistStatus?.value?.getOrNull(0) == 0x01.toByte()))
|
||||||
}
|
}
|
||||||
|
|
||||||
var hazeStateS = rememberHazeState()
|
val hazeStateS = remember { mutableStateOf(HazeState()) } // dont question this. i could possibly use something other than initializing it with an empty state and then replacing it with the the one provided by the scaffold
|
||||||
|
|
||||||
StyledScaffold(
|
StyledScaffold(
|
||||||
title = stringResource(R.string.hearing_aid),
|
title = stringResource(R.string.hearing_aid),
|
||||||
@@ -112,7 +112,7 @@ fun HearingAidScreen(navController: NavController) {
|
|||||||
.padding(horizontal = 16.dp),
|
.padding(horizontal = 16.dp),
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
) {
|
) {
|
||||||
hazeStateS = hazeState
|
hazeStateS.value = hazeState
|
||||||
Spacer(modifier = Modifier.height(spacerHeight))
|
Spacer(modifier = Modifier.height(spacerHeight))
|
||||||
|
|
||||||
val hearingAidListener = remember {
|
val hearingAidListener = remember {
|
||||||
@@ -288,7 +288,7 @@ fun HearingAidScreen(navController: NavController) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
hazeState = hazeStateS,
|
hazeState = hazeStateS.value,
|
||||||
// backdrop = backdrop
|
// backdrop = backdrop
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -193,7 +193,7 @@ fun TroubleshootingScreen(navController: NavController) {
|
|||||||
LaunchedEffect(currentStep) {
|
LaunchedEffect(currentStep) {
|
||||||
instructionText = when (currentStep) {
|
instructionText = when (currentStep) {
|
||||||
0 -> "First, let's ensure Xposed module is properly configured. Tap the button below to check Xposed scope settings."
|
0 -> "First, let's ensure Xposed module is properly configured. Tap the button below to check Xposed scope settings."
|
||||||
1 -> "Please put your AirPods in the case and close it, so they disconnect completely."
|
1 -> "Please put your AirPods in the case and close it, so they disconnectForCD completely."
|
||||||
2 -> "Preparing to collect logs... Please wait."
|
2 -> "Preparing to collect logs... Please wait."
|
||||||
3 -> "Now, open the AirPods case and connect your AirPods. Logs are being collected. Connection will be detected automatically, or you can manually stop logging when you're done."
|
3 -> "Now, open the AirPods case and connect your AirPods. Logs are being collected. Connection will be detected automatically, or you can manually stop logging when you're done."
|
||||||
4 -> "Log collection complete! You can now save or share the logs."
|
4 -> "Log collection complete! You can now save or share the logs."
|
||||||
|
|||||||
@@ -88,9 +88,9 @@ import me.kavishdevar.librepods.constants.StemAction
|
|||||||
import me.kavishdevar.librepods.constants.isHeadTrackingData
|
import me.kavishdevar.librepods.constants.isHeadTrackingData
|
||||||
import me.kavishdevar.librepods.utils.AACPManager
|
import me.kavishdevar.librepods.utils.AACPManager
|
||||||
import me.kavishdevar.librepods.utils.AACPManager.Companion.StemPressType
|
import me.kavishdevar.librepods.utils.AACPManager.Companion.StemPressType
|
||||||
|
import me.kavishdevar.librepods.utils.ATTManager
|
||||||
import me.kavishdevar.librepods.utils.AirPodsInstance
|
import me.kavishdevar.librepods.utils.AirPodsInstance
|
||||||
import me.kavishdevar.librepods.utils.AirPodsModels
|
import me.kavishdevar.librepods.utils.AirPodsModels
|
||||||
import me.kavishdevar.librepods.utils.ATTManager
|
|
||||||
import me.kavishdevar.librepods.utils.BLEManager
|
import me.kavishdevar.librepods.utils.BLEManager
|
||||||
import me.kavishdevar.librepods.utils.BluetoothConnectionManager
|
import me.kavishdevar.librepods.utils.BluetoothConnectionManager
|
||||||
import me.kavishdevar.librepods.utils.CrossDevice
|
import me.kavishdevar.librepods.utils.CrossDevice
|
||||||
@@ -229,6 +229,9 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
|
|||||||
private var handleIncomingCallOnceConnected = false
|
private var handleIncomingCallOnceConnected = false
|
||||||
|
|
||||||
lateinit var bleManager: BLEManager
|
lateinit var bleManager: BLEManager
|
||||||
|
|
||||||
|
private lateinit var socket: BluetoothSocket
|
||||||
|
|
||||||
private val bleStatusListener = object : BLEManager.AirPodsStatusListener {
|
private val bleStatusListener = object : BLEManager.AirPodsStatusListener {
|
||||||
@SuppressLint("NewApi")
|
@SuppressLint("NewApi")
|
||||||
override fun onDeviceStatusChanged(
|
override fun onDeviceStatusChanged(
|
||||||
@@ -1032,8 +1035,9 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
|
|||||||
byteArrayOf(0x00)
|
byteArrayOf(0x00)
|
||||||
)
|
)
|
||||||
// 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
|
// 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
|
||||||
Log.d(TAG, "Another device started playing audio, listening for audio config changes again")
|
// Log.d(TAG, "Another device started playing audio, listening for audio config changes again")
|
||||||
MediaController.pausedForOtherDevice = false
|
// MediaController.pausedForOtherDevice = false
|
||||||
|
// future me: what the heck is this? this just means it will not be taking over again if audio source doesn't change???
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1842,7 +1846,6 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
|
|||||||
notificationManager.notify(1, updatedNotification)
|
notificationManager.notify(1, updatedNotification)
|
||||||
notificationManager.cancel(2)
|
notificationManager.cancel(2)
|
||||||
} else if (!config.bleOnlyMode && !socket.isConnected && isConnectedLocally) {
|
} else if (!config.bleOnlyMode && !socket.isConnected && isConnectedLocally) {
|
||||||
Log.d(TAG, "<LogCollector:Complete:Failed> Socket not connected")
|
|
||||||
showSocketConnectionFailureNotification("Socket created, but not connected. Is the Bluetooth process hooked?")
|
showSocketConnectionFailureNotification("Socket created, but not connected. Is the Bluetooth process hooked?")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2135,10 +2138,10 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
|
|||||||
val name = context?.getSharedPreferences("settings", MODE_PRIVATE)
|
val name = context?.getSharedPreferences("settings", MODE_PRIVATE)
|
||||||
?.getString("name", bluetoothDevice?.name)
|
?.getString("name", bluetoothDevice?.name)
|
||||||
if (bluetoothDevice != null && action != null && !action.isEmpty()) {
|
if (bluetoothDevice != null && action != null && !action.isEmpty()) {
|
||||||
Log.d(TAG, "Received bluetooth connection broadcast")
|
Log.d(TAG, "Received bluetooth connection broadcast: action=$action")
|
||||||
if (ServiceManager.getService()?.isConnectedLocally == true) {
|
if (ServiceManager.getService()?.isConnectedLocally == true) {
|
||||||
Log.d(TAG, "Device is already connected locally, ignoring broadcast")
|
Log.d(TAG, "Device is already connected locally, checking if we should keep audio connected")
|
||||||
ServiceManager.getService()?.manuallyCheckForAudioSource()
|
if (ServiceManager.getService()?.socket?.isConnected == true) ServiceManager.getService()?.manuallyCheckForAudioSource() else Log.d(TAG, "We're not connected, ignoring")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (BluetoothDevice.ACTION_ACL_CONNECTED == action) {
|
if (BluetoothDevice.ACTION_ACL_CONNECTED == action) {
|
||||||
@@ -2175,14 +2178,14 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
|
|||||||
return START_STICKY
|
return START_STICKY
|
||||||
}
|
}
|
||||||
|
|
||||||
private lateinit var socket: BluetoothSocket
|
|
||||||
|
|
||||||
fun manuallyCheckForAudioSource() {
|
fun manuallyCheckForAudioSource() {
|
||||||
val shouldResume = MediaController.getMusicActive()
|
val shouldResume = MediaController.getMusicActive() // todo: for some reason we lose this info after disconnecting, probably android dispatches some event. haven't investigated yet.
|
||||||
if ((earDetectionNotification.status[0] != 0.toByte() && earDetectionNotification.status[1] != 0.toByte()) || disconnectedBecauseReversed || otherDeviceTookOver) {
|
if (airpodsInstance == null) return
|
||||||
|
Log.d(TAG, "disconnectedBecauseReversed: $disconnectedBecauseReversed, otherDeviceTookOver: $otherDeviceTookOver")
|
||||||
|
if ((earDetectionNotification.status[0] != 0.toByte() && earDetectionNotification.status[1] != 0.toByte()) || disconnectedBecauseReversed || otherDeviceTookOver) {
|
||||||
Log.d(
|
Log.d(
|
||||||
TAG,
|
TAG,
|
||||||
"For some reason, Android connected to the audio profile itself even after disconnecting. Disconnecting audio profile again!"
|
"For some reason, Android connected to the audio profile itself even after disconnecting. Disconnecting audio profile again! I will resume: $shouldResume"
|
||||||
)
|
)
|
||||||
disconnectAudio(this, device, shouldResume = shouldResume)
|
disconnectAudio(this, device, shouldResume = shouldResume)
|
||||||
}
|
}
|
||||||
@@ -2378,7 +2381,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
|
|||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("MissingPermission", "UnspecifiedRegisterReceiverFlag")
|
@SuppressLint("MissingPermission", "UnspecifiedRegisterReceiverFlag")
|
||||||
fun connectToSocket(device: BluetoothDevice) {
|
fun connectToSocket(device: BluetoothDevice, manual: Boolean = false) {
|
||||||
Log.d(TAG, "<LogCollector:Start> Connecting to socket")
|
Log.d(TAG, "<LogCollector:Start> Connecting to socket")
|
||||||
HiddenApiBypass.addHiddenApiExemptions("Landroid/bluetooth/BluetoothSocket;")
|
HiddenApiBypass.addHiddenApiExemptions("Landroid/bluetooth/BluetoothSocket;")
|
||||||
val uuid: ParcelUuid = ParcelUuid.fromString("74ec2172-0bad-4d01-8f77-997b2be0722a")
|
val uuid: ParcelUuid = ParcelUuid.fromString("74ec2172-0bad-4d01-8f77-997b2be0722a")
|
||||||
@@ -2387,7 +2390,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
|
|||||||
createBluetoothSocket(device, uuid)
|
createBluetoothSocket(device, uuid)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "Failed to create BluetoothSocket: ${e.message}")
|
Log.e(TAG, "Failed to create BluetoothSocket: ${e.message}")
|
||||||
showSocketConnectionFailureNotification("Failed to create Bluetooth socket: ${e.message}")
|
showSocketConnectionFailureNotification("Failed to create Bluetooth socket: ${e.localizedMessage}")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2431,15 +2434,29 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
|
|||||||
)
|
)
|
||||||
Log.d(TAG, "<LogCollector:Complete:Success> Socket connected")
|
Log.d(TAG, "<LogCollector:Complete:Success> Socket connected")
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.d(TAG, "<LogCollector:Complete:Failed> Socket not connected")
|
Log.d(TAG, "<LogCollector:Complete:Failed> Socket not connected, ${e.message}")
|
||||||
showSocketConnectionFailureNotification("Socket created, but not connected. Is the Bluetooth process hooked?")
|
if (manual) {
|
||||||
throw e
|
sendToast(
|
||||||
|
"Couldn't connect to socket: ${e.localizedMessage}"
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
showSocketConnectionFailureNotification("Couldn't connect to socket: ${e.localizedMessage}")
|
||||||
|
}
|
||||||
|
return@withTimeout
|
||||||
|
// throw e // lol how did i not catch this before... gonna comment this line instead of removing to preserve history
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!socket.isConnected) {
|
}
|
||||||
Log.d(TAG, "<LogCollector:Complete:Failed> Socket not connected")
|
if (!socket.isConnected) {
|
||||||
showSocketConnectionFailureNotification("Socket created, but not connected. Is the Bluetooth process hooked?")
|
Log.d(TAG, "<LogCollector:Complete:Failed> Socket not connected")
|
||||||
|
if (manual) {
|
||||||
|
sendToast(
|
||||||
|
"Couldn't connect to socket: timeout."
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
showSocketConnectionFailureNotification("Couldn't connect to socket: Timeout")
|
||||||
}
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
this@AirPodsService.device = device
|
this@AirPodsService.device = device
|
||||||
socket.let {
|
socket.let {
|
||||||
@@ -2519,7 +2536,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
|
|||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
Log.d(TAG, "Failed to connect to socket: ${e.message}")
|
Log.d(TAG, "Failed to connect to socket: ${e.message}")
|
||||||
showSocketConnectionFailureNotification("Failed to establish connection: ${e.message}")
|
showSocketConnectionFailureNotification("Failed to establish connection: ${e.localizedMessage}")
|
||||||
isConnectedLocally = false
|
isConnectedLocally = false
|
||||||
this@AirPodsService.device = device
|
this@AirPodsService.device = device
|
||||||
updateNotificationContent(false)
|
updateNotificationContent(false)
|
||||||
@@ -2527,7 +2544,7 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun disconnect() {
|
fun disconnectForCD() {
|
||||||
if (!this::socket.isInitialized) return
|
if (!this::socket.isInitialized) return
|
||||||
socket.close()
|
socket.close()
|
||||||
MediaController.pausedWhileTakingOver = false
|
MediaController.pausedWhileTakingOver = false
|
||||||
@@ -2552,6 +2569,33 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
|
|||||||
CrossDevice.isAvailable = true
|
CrossDevice.isAvailable = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun disconnectAirPods() {
|
||||||
|
if (!this::socket.isInitialized) return
|
||||||
|
socket.close()
|
||||||
|
isConnectedLocally = false
|
||||||
|
aacpManager.disconnected()
|
||||||
|
attManager?.disconnect()
|
||||||
|
updateNotificationContent(false)
|
||||||
|
sendBroadcast(Intent(AirPodsNotifications.AIRPODS_DISCONNECTED))
|
||||||
|
|
||||||
|
val bluetoothAdapter = getSystemService(BluetoothManager::class.java).adapter
|
||||||
|
bluetoothAdapter.getProfileProxy(this, object : BluetoothProfile.ServiceListener {
|
||||||
|
override fun onServiceConnected(profile: Int, proxy: BluetoothProfile) {
|
||||||
|
if (profile == BluetoothProfile.A2DP) {
|
||||||
|
val connectedDevices = proxy.connectedDevices
|
||||||
|
if (connectedDevices.isNotEmpty()) {
|
||||||
|
MediaController.sendPause()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bluetoothAdapter.closeProfileProxy(profile, proxy)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onServiceDisconnected(profile: Int) {}
|
||||||
|
}, BluetoothProfile.A2DP)
|
||||||
|
Log.d(TAG, "Disconnected AirPods upon user request")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
val earDetectionNotification = AirPodsNotifications.EarDetection()
|
val earDetectionNotification = AirPodsNotifications.EarDetection()
|
||||||
val ancNotification = AirPodsNotifications.ANC()
|
val ancNotification = AirPodsNotifications.ANC()
|
||||||
val batteryNotification = AirPodsNotifications.BatteryNotification()
|
val batteryNotification = AirPodsNotifications.BatteryNotification()
|
||||||
@@ -2750,6 +2794,19 @@ class AirPodsService : Service(), SharedPreferences.OnSharedPreferenceChangeList
|
|||||||
isHeadTrackingActive = false
|
isHeadTrackingActive = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("MissingPermission")
|
||||||
|
fun reconnectFromSavedMac(){
|
||||||
|
val bluetoothAdapter = getSystemService(BluetoothManager::class.java).adapter
|
||||||
|
device = bluetoothAdapter.bondedDevices.find {
|
||||||
|
it.address == macAddress
|
||||||
|
}
|
||||||
|
if (device != null) {
|
||||||
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
connectToSocket(device!!, manual = true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Int.dpToPx(): Int {
|
private fun Int.dpToPx(): Int {
|
||||||
|
|||||||
@@ -200,7 +200,7 @@ object CrossDevice {
|
|||||||
notifyAirPodsDisconnectedRemotely(ServiceManager.getService()?.applicationContext!!)
|
notifyAirPodsDisconnectedRemotely(ServiceManager.getService()?.applicationContext!!)
|
||||||
break
|
break
|
||||||
} else if (packet.contentEquals(CrossDevicePackets.REQUEST_DISCONNECT.packet) || packet.contentEquals(CrossDevicePackets.REQUEST_DISCONNECT.packet + CrossDevicePackets.AIRPODS_DATA_HEADER.packet)) {
|
} else if (packet.contentEquals(CrossDevicePackets.REQUEST_DISCONNECT.packet) || packet.contentEquals(CrossDevicePackets.REQUEST_DISCONNECT.packet + CrossDevicePackets.AIRPODS_DATA_HEADER.packet)) {
|
||||||
ServiceManager.getService()?.disconnect()
|
ServiceManager.getService()?.disconnectForCD()
|
||||||
disconnectionRequested = true
|
disconnectionRequested = true
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
delay(1000)
|
delay(1000)
|
||||||
|
|||||||
@@ -204,4 +204,6 @@
|
|||||||
<string name="ppe">EN 352 Protection</string>
|
<string name="ppe">EN 352 Protection</string>
|
||||||
<string name="workspace_use_description">EN 352 Protection limits the maximum level of media to 82 dBA, and meets applicable EN 352 Standard requirements for personal hearing protection.</string>
|
<string name="workspace_use_description">EN 352 Protection limits the maximum level of media to 82 dBA, and meets applicable EN 352 Standard requirements for personal hearing protection.</string>
|
||||||
<string name="environmental_noise">Environmental Noise</string>
|
<string name="environmental_noise">Environmental Noise</string>
|
||||||
|
<string name="reconnect_to_last_device">Reconnect to last connected device</string>
|
||||||
|
<string name="disconnect">Disconnect</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
Reference in New Issue
Block a user