mirror of
https://github.com/kavishdevar/librepods.git
synced 2026-02-16 06:08:12 +00:00
android: disable audio profiles when not in ear; add a debug screen
This commit is contained in:
@@ -1,13 +1,17 @@
|
||||
package me.kavishdevar.aln
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.bluetooth.BluetoothAdapter
|
||||
import android.bluetooth.BluetoothDevice
|
||||
import android.bluetooth.BluetoothManager
|
||||
import android.bluetooth.BluetoothProfile
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.content.ServiceConnection
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.IBinder
|
||||
import android.os.ParcelUuid
|
||||
@@ -30,6 +34,7 @@ import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
@@ -53,8 +58,8 @@ class MainActivity : ComponentActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
enableEdgeToEdge()
|
||||
|
||||
setContent {
|
||||
val topAppBarTitle = remember { mutableStateOf("AirPods Pro") }
|
||||
ALNTheme {
|
||||
Scaffold (
|
||||
containerColor = if (MaterialTheme.colorScheme.surface.luminance() < 0.5) Color(
|
||||
@@ -66,7 +71,7 @@ class MainActivity : ComponentActivity() {
|
||||
CenterAlignedTopAppBar(
|
||||
title = {
|
||||
Text(
|
||||
text = "AirPods Pro Settings",
|
||||
text = topAppBarTitle.value,
|
||||
color = if (MaterialTheme.colorScheme.surface.luminance() < 0.5) Color.White else Color.Black,
|
||||
)
|
||||
},
|
||||
@@ -80,7 +85,7 @@ class MainActivity : ComponentActivity() {
|
||||
)
|
||||
}
|
||||
) { innerPadding ->
|
||||
Main(innerPadding)
|
||||
Main(innerPadding, topAppBarTitle)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -90,7 +95,7 @@ class MainActivity : ComponentActivity() {
|
||||
@SuppressLint("MissingPermission")
|
||||
@OptIn(ExperimentalPermissionsApi::class)
|
||||
@Composable
|
||||
fun Main(paddingValues: PaddingValues) {
|
||||
fun Main(paddingValues: PaddingValues, topAppBarTitle: MutableState<String>) {
|
||||
val bluetoothConnectPermissionState = rememberPermissionState(
|
||||
permission = "android.permission.BLUETOOTH_CONNECT"
|
||||
)
|
||||
@@ -100,38 +105,21 @@ fun Main(paddingValues: PaddingValues) {
|
||||
val uuid: ParcelUuid = ParcelUuid.fromString("74ec2172-0bad-4d01-8f77-997b2be0722a")
|
||||
val bluetoothManager = getSystemService(context, BluetoothManager::class.java)
|
||||
val bluetoothAdapter = bluetoothManager?.adapter
|
||||
val devices = bluetoothAdapter?.bondedDevices
|
||||
val airpodsDevice = remember { mutableStateOf<BluetoothDevice?>(null) }
|
||||
|
||||
val airPodsService = remember { mutableStateOf<AirPodsService?>(null) }
|
||||
val navController = rememberNavController()
|
||||
|
||||
if (devices != null) {
|
||||
for (device in devices) {
|
||||
if (device.uuids.contains(uuid)) {
|
||||
bluetoothAdapter.getProfileProxy(context, object : BluetoothProfile.ServiceListener {
|
||||
override fun onServiceConnected(profile: Int, proxy: BluetoothProfile) {
|
||||
if (profile == BluetoothProfile.A2DP) {
|
||||
val connectedDevices = proxy.connectedDevices
|
||||
if (connectedDevices.isNotEmpty()) {
|
||||
airpodsDevice.value = device
|
||||
if (context.getSystemService(AirPodsService::class.java) == null || context.getSystemService(AirPodsService::class.java)?.isRunning != true) {
|
||||
context.startService(Intent(context, AirPodsService::class.java).apply {
|
||||
putExtra("device", device)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
bluetoothAdapter.closeProfileProxy(profile, proxy)
|
||||
}
|
||||
|
||||
override fun onServiceDisconnected(profile: Int) { }
|
||||
}, BluetoothProfile.A2DP)
|
||||
}
|
||||
val disconnectReceiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context?, intent: Intent?) {
|
||||
navController.navigate("notConnected")
|
||||
}
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
context.registerReceiver(disconnectReceiver, IntentFilter(AirPodsNotifications.AIRPODS_DISCONNECTED),
|
||||
Context.RECEIVER_NOT_EXPORTED)
|
||||
}
|
||||
|
||||
val airPodsService = remember { mutableStateOf<AirPodsService?>(null) }
|
||||
|
||||
// Service connection for AirPodsService
|
||||
val serviceConnection = object : ServiceConnection {
|
||||
override fun onServiceConnected(name: ComponentName, service: IBinder) {
|
||||
val binder = service as AirPodsService.LocalBinder
|
||||
@@ -144,16 +132,88 @@ fun Main(paddingValues: PaddingValues) {
|
||||
}
|
||||
}
|
||||
|
||||
val intent = Intent(context, AirPodsService::class.java)
|
||||
context.bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE)
|
||||
// Function to check if AirPods are connected
|
||||
fun checkIfAirPodsConnected() {
|
||||
val devices = bluetoothAdapter?.bondedDevices
|
||||
devices?.forEach { device ->
|
||||
if (device.uuids.contains(uuid)) {
|
||||
bluetoothAdapter.getProfileProxy(context, object : BluetoothProfile.ServiceListener {
|
||||
override fun onServiceConnected(profile: Int, proxy: BluetoothProfile) {
|
||||
if (profile == BluetoothProfile.A2DP) {
|
||||
val connectedDevices = proxy.connectedDevices
|
||||
if (connectedDevices.isNotEmpty()) {
|
||||
airpodsDevice.value = device
|
||||
val sharedPreferences = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
|
||||
topAppBarTitle.value = sharedPreferences.getString("name", device.name) ?: device.name
|
||||
// Start AirPods service if not running
|
||||
if (context.getSystemService(AirPodsService::class.java)?.isRunning != true) {
|
||||
context.startService(Intent(context, AirPodsService::class.java).apply {
|
||||
putExtra("device", device)
|
||||
})
|
||||
context.bindService(Intent(context, AirPodsService::class.java), serviceConnection, Context.BIND_AUTO_CREATE)
|
||||
}
|
||||
} else {
|
||||
airpodsDevice.value = null
|
||||
}
|
||||
}
|
||||
bluetoothAdapter.closeProfileProxy(profile, proxy)
|
||||
}
|
||||
|
||||
override fun onServiceDisconnected(profile: Int) {}
|
||||
}, BluetoothProfile.A2DP)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BroadcastReceiver to listen for connection state changes
|
||||
val bluetoothReceiver = remember {
|
||||
object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context?, intent: Intent?) {
|
||||
val action = intent?.action
|
||||
val device = intent?.getParcelableExtra<BluetoothDevice>(BluetoothDevice.EXTRA_DEVICE)
|
||||
if (action == BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED) {
|
||||
when (intent.getIntExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE, -1)) {
|
||||
BluetoothAdapter.STATE_CONNECTED -> {
|
||||
if (device?.uuids?.contains(uuid) == true) {
|
||||
airpodsDevice.value = device
|
||||
checkIfAirPodsConnected()
|
||||
}
|
||||
}
|
||||
BluetoothAdapter.STATE_DISCONNECTED -> {
|
||||
if (device?.uuids?.contains(uuid) == true) {
|
||||
airpodsDevice.value = null
|
||||
// Show not connected screen when AirPods disconnect
|
||||
navController.navigate("notConnected")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Register the receiver in LaunchedEffect
|
||||
LaunchedEffect(Unit) {
|
||||
val filter = IntentFilter().apply {
|
||||
addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED)
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
context.registerReceiver(bluetoothReceiver, filter, Context.RECEIVER_NOT_EXPORTED)
|
||||
}
|
||||
|
||||
// Initial check for AirPods connection
|
||||
checkIfAirPodsConnected()
|
||||
}
|
||||
|
||||
// UI logic
|
||||
NavHost(
|
||||
navController = navController,
|
||||
startDestination = "notConnected",
|
||||
enterTransition = { slideInHorizontally(initialOffsetX = { it }, animationSpec = tween(300)) }, // Slide in from the right
|
||||
exitTransition = { slideOutHorizontally(targetOffsetX = { -it }, animationSpec = tween(300)) }, // Slide out to the left
|
||||
popEnterTransition = { slideInHorizontally(initialOffsetX = { -it }, animationSpec = tween(300)) }, // Slide in from the left
|
||||
popExitTransition = { slideOutHorizontally(targetOffsetX = { it }, animationSpec = tween(300)) } // Slide out to the right
|
||||
){
|
||||
enterTransition = { slideInHorizontally(initialOffsetX = { it }, animationSpec = tween(300)) },
|
||||
exitTransition = { slideOutHorizontally(targetOffsetX = { -it }, animationSpec = tween(300)) },
|
||||
popEnterTransition = { slideInHorizontally(initialOffsetX = { -it }, animationSpec = tween(300)) },
|
||||
popExitTransition = { slideOutHorizontally(targetOffsetX = { it }, animationSpec = tween(300)) }
|
||||
) {
|
||||
composable("notConnected") {
|
||||
Text("Not Connected...")
|
||||
}
|
||||
@@ -169,17 +229,17 @@ fun Main(paddingValues: PaddingValues) {
|
||||
DebugScreen(navController = navController)
|
||||
}
|
||||
}
|
||||
|
||||
// Automatically navigate to settings screen if AirPods are connected
|
||||
if (airpodsDevice.value != null) {
|
||||
LaunchedEffect(Unit) {
|
||||
navController.navigate("settings") {
|
||||
popUpTo("notConnected") { inclusive = true }
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
Text("No AirPods connected")
|
||||
}
|
||||
return
|
||||
} else {
|
||||
// Permission is not granted, request it
|
||||
Column (
|
||||
|
||||
Reference in New Issue
Block a user