add popup window when connected; automatically open socket in background

This commit is contained in:
Kavish Devar
2024-11-29 23:35:46 +05:30
parent 58de49d1b1
commit 3e0de6f011
21 changed files with 1587 additions and 571 deletions

View File

@@ -1,9 +1,6 @@
package me.kavishdevar.aln
import android.annotation.SuppressLint
import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothManager
import android.bluetooth.BluetoothProfile
import android.content.BroadcastReceiver
import android.content.ComponentName
import android.content.Context
@@ -13,7 +10,6 @@ import android.content.ServiceConnection
import android.os.Build
import android.os.Bundle
import android.os.IBinder
import android.os.ParcelUuid
import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
@@ -22,28 +18,16 @@ import androidx.compose.animation.core.tween
import androidx.compose.animation.slideInHorizontally
import androidx.compose.animation.slideOutHorizontally
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.CenterAlignedTopAppBar
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
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
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.luminance
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.PreviewLightDark
import androidx.compose.ui.unit.dp
import androidx.core.content.ContextCompat
import androidx.core.content.ContextCompat.getSystemService
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
@@ -51,98 +35,35 @@ import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.isGranted
import com.google.accompanist.permissions.rememberPermissionState
import com.google.accompanist.permissions.shouldShowRationale
import com.primex.core.ExperimentalToolkitApi
import me.kavishdevar.aln.ui.theme.ALNTheme
@ExperimentalMaterial3Api
class MainActivity : ComponentActivity() {
@OptIn(ExperimentalToolkitApi::class)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
val topAppBarTitle = remember { mutableStateOf("AirPods Pro") }
ALNTheme {
val navController = rememberNavController()
registerReceiver(object: BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent) {
val bluetoothDevice =
intent.getParcelableExtra("android.bluetooth.device.extra.DEVICE", BluetoothDevice::class.java)
val action = intent.action
// Airpods filter
if (bluetoothDevice != null && action != null && !action.isEmpty()) {
Log.d("BluetoothReceiver", "Received broadcast")
// Airpods connected, show notification.
if (BluetoothDevice.ACTION_ACL_CONNECTED == action) {
val uuid = ParcelUuid.fromString("74ec2172-0bad-4d01-8f77-997b2be0722a")
if (bluetoothDevice.uuids.contains(uuid)) {
topAppBarTitle.value = bluetoothDevice.name
}
// start service
startService(Intent(context, AirPodsService::class.java).apply {
putExtra("device", bluetoothDevice)
})
Log.d("AirPodsService", "Service started")
context?.sendBroadcast(Intent(AirPodsNotifications.AIRPODS_CONNECTED))
}
// Airpods disconnected, remove notification but leave the scanner going.
if (BluetoothDevice.ACTION_ACL_DISCONNECTED == action
|| BluetoothDevice.ACTION_ACL_DISCONNECT_REQUESTED == action
) {
topAppBarTitle.value = "AirPods Pro"
// stop service
stopService(Intent(context, AirPodsService::class.java))
Log.d("AirPodsService", "Service stopped")
}
}
}
}, BluetoothReceiver.buildFilter())
Scaffold (
containerColor = if (MaterialTheme.colorScheme.surface.luminance() < 0.5) Color(
0xFF000000
) else Color(
0xFFF2F2F7
),
topBar = {
CenterAlignedTopAppBar(
title = {
Text(
text = topAppBarTitle.value,
color = if (MaterialTheme.colorScheme.surface.luminance() < 0.5) Color.White else Color.Black,
)
},
colors = TopAppBarDefaults.centerAlignedTopAppBarColors(
containerColor = if (MaterialTheme.colorScheme.surface.luminance() < 0.5) Color(
0xFF000000
) else Color(
0xFFF2F2F7
),
)
)
}
) { innerPadding ->
Main(innerPadding, topAppBarTitle)
}
Main()
startService(Intent(this, AirPodsService::class.java))
}
}
}
}
@SuppressLint("MissingPermission")
@SuppressLint("MissingPermission", "InlinedApi")
@OptIn(ExperimentalPermissionsApi::class)
@Composable
fun Main(paddingValues: PaddingValues, topAppBarTitle: MutableState<String>) {
fun Main() {
val bluetoothConnectPermissionState = rememberPermissionState(
permission = "android.permission.BLUETOOTH_CONNECT"
)
if (bluetoothConnectPermissionState.status.isGranted) {
val context = LocalContext.current
val uuid: ParcelUuid = ParcelUuid.fromString("74ec2172-0bad-4d01-8f77-997b2be0722a")
val bluetoothManager = getSystemService(context, BluetoothManager::class.java)
val bluetoothAdapter = bluetoothManager?.adapter
val airpodsDevice = remember { mutableStateOf<BluetoothDevice?>(null) }
val airPodsService = remember { mutableStateOf<AirPodsService?>(null) }
val navController = rememberNavController()
@@ -157,58 +78,6 @@ fun Main(paddingValues: PaddingValues, topAppBarTitle: MutableState<String>) {
Context.RECEIVER_NOT_EXPORTED)
}
// Service connection for AirPodsService
val serviceConnection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName, service: IBinder) {
val binder = service as AirPodsService.LocalBinder
airPodsService.value = binder.getService()
Log.d("AirPodsService", "Service connected")
}
override fun onServiceDisconnected(name: ComponentName) {
airPodsService.value = null
}
}
// 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)?.isConnected != 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)
}
}
}
// Register the receiver in LaunchedEffect
LaunchedEffect(Unit) {
// Initial check for AirPods connection
checkIfAirPodsConnected()
}
// UI logic
NavHost(
navController = navController,
@@ -223,8 +92,7 @@ fun Main(paddingValues: PaddingValues, topAppBarTitle: MutableState<String>) {
}
composable("settings") {
AirPodsSettingsScreen(
paddingValues,
airpodsDevice.value,
device = airPodsService.value?.device,
service = airPodsService.value,
navController = navController
)
@@ -234,30 +102,37 @@ fun Main(paddingValues: PaddingValues, topAppBarTitle: MutableState<String>) {
}
}
ContextCompat.registerReceiver(
context,
object : BroadcastReceiver() {
@SuppressLint("UnspecifiedRegisterReceiverFlag")
override fun onReceive(context: Context?, intent: Intent) {
Log.d("PLEASE NAVIGATE", "TO SETTINGS")
navController.navigate("settings") {
popUpTo("notConnected") { inclusive = true }
}
}
},
IntentFilter(AirPodsNotifications.AIRPODS_CONNECTED),
ContextCompat.RECEIVER_NOT_EXPORTED
)
val receiver = object: BroadcastReceiver() {
override fun onReceive(p0: Context?, p1: Intent?) {
navController.navigate("settings")
navController.popBackStack("notConnected", inclusive = true)
}
}
// Automatically navigate to settings screen if AirPods are connected
if (airpodsDevice.value != null) {
LaunchedEffect(Unit) {
navController.navigate("settings") {
popUpTo("notConnected") { inclusive = true }
context.registerReceiver(receiver, IntentFilter(AirPodsNotifications.AIRPODS_CONNECTED),
Context.RECEIVER_EXPORTED)
val serviceConnection = remember {
object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
val binder = service as AirPodsService.LocalBinder
airPodsService.value = binder.getService()
}
override fun onServiceDisconnected(name: ComponentName?) {
airPodsService.value = null
}
}
}
context.bindService(Intent(context, AirPodsService::class.java), serviceConnection, Context.BIND_AUTO_CREATE)
if (airPodsService.value?.isConnected == true) {
Log.d("ALN", "Connected")
navController.navigate("settings")
} else {
Text("No AirPods connected")
Log.d("ALN", "Not connected")
navController.navigate("notConnected")
}
} else {
// Permission is not granted, request it
@@ -277,10 +152,4 @@ fun Main(paddingValues: PaddingValues, topAppBarTitle: MutableState<String>) {
}
}
}
}
@PreviewLightDark
@Composable
fun PreviewAirPodsSettingsScreen() {
AirPodsSettingsScreen(paddingValues = PaddingValues(0.dp), device = null, service = null, navController = rememberNavController())
}