diff --git a/android/app/src/main/java/me/kavishdevar/aln/AirPodsService.kt b/android/app/src/main/java/me/kavishdevar/aln/AirPodsService.kt index 4c7b2bf..47dcf73 100644 --- a/android/app/src/main/java/me/kavishdevar/aln/AirPodsService.kt +++ b/android/app/src/main/java/me/kavishdevar/aln/AirPodsService.kt @@ -22,6 +22,7 @@ import android.util.Log import androidx.core.app.NotificationCompat import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay import kotlinx.coroutines.launch import org.lsposed.hiddenapibypass.HiddenApiBypass @@ -35,8 +36,8 @@ class AirPodsService: Service() { return LocalBinder() } - fun showPopup(context: Context, name: String) { - val window = Window(context) + fun showPopup(service: Service, name: String) { + val window = Window(service.applicationContext) window.open(name, batteryNotification) } @@ -47,6 +48,7 @@ class AirPodsService: Service() { intent.getParcelableExtra("android.bluetooth.device.extra.DEVICE", BluetoothDevice::class.java) val action = intent.action val context = context?.applicationContext + val name = context?.getSharedPreferences("settings", MODE_PRIVATE)?.getString("name", bluetoothDevice?.name) if (bluetoothDevice != null && action != null && !action.isEmpty()) { Log.d("BluetoothReceiver", "Received broadcast") if (BluetoothDevice.ACTION_ACL_CONNECTED == action) { @@ -54,7 +56,7 @@ class AirPodsService: Service() { if (bluetoothDevice.uuids.contains(uuid)) { Log.d("AirPodsService", "Service started") val intent = Intent(AirPodsNotifications.AIRPODS_CONNECTION_DETECTED) - intent.putExtra("name", bluetoothDevice.name) + intent.putExtra("name", name) intent.putExtra("device", bluetoothDevice) context?.sendBroadcast(intent) } @@ -104,8 +106,9 @@ class AirPodsService: Service() { registerReceiver(object: BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { - showPopup(context!!, intent!!.getStringExtra("name")!!) - device = intent.getParcelableExtra("device", BluetoothDevice::class.java)!! + val name = this@AirPodsService.getSharedPreferences("settings", MODE_PRIVATE).getString("name", device?.name) + device = intent?.getParcelableExtra("device", BluetoothDevice::class.java)!! + showPopup(this@AirPodsService, name.toString()) connectToSocket(device!!) sendBroadcast(Intent(AirPodsNotifications.AIRPODS_CONNECTED).apply { putExtra("device", device) @@ -188,12 +191,18 @@ class AirPodsService: Service() { this@AirPodsService.device = device isConnected = true socket.let { it -> - it.outputStream.write(Enums.HANDSHAKE.value) - it.outputStream.flush() - it.outputStream.write(Enums.SET_SPECIFIC_FEATURES.value) - it.outputStream.flush() - it.outputStream.write(Enums.REQUEST_NOTIFICATIONS.value) - it.outputStream.flush() + CoroutineScope(Dispatchers.IO).launch { + it.outputStream.write(Enums.HANDSHAKE.value) + it.outputStream.flush() + delay(500) + it.outputStream.write(Enums.SET_SPECIFIC_FEATURES.value) + it.outputStream.flush() + delay(500) + it.outputStream.write(Enums.REQUEST_NOTIFICATIONS.value) + it.outputStream.flush() + Log.d("AirPodsService","This should run first") + } + Log.d("AirPodsService","This should run later") sendBroadcast( Intent(AirPodsNotifications.AIRPODS_CONNECTED) .putExtra("device", device) diff --git a/android/app/src/main/java/me/kavishdevar/aln/AirPodsSettingsScreen.kt b/android/app/src/main/java/me/kavishdevar/aln/AirPodsSettingsScreen.kt index 730b6f4..3c5a18d 100644 --- a/android/app/src/main/java/me/kavishdevar/aln/AirPodsSettingsScreen.kt +++ b/android/app/src/main/java/me/kavishdevar/aln/AirPodsSettingsScreen.kt @@ -4,6 +4,7 @@ import android.annotation.SuppressLint import android.bluetooth.BluetoothDevice import android.content.BroadcastReceiver import android.content.Context +import android.content.Context.MODE_PRIVATE import android.content.Intent import android.content.IntentFilter import android.content.SharedPreferences @@ -82,6 +83,7 @@ import androidx.compose.ui.unit.sp import androidx.navigation.NavController import com.primex.core.ExperimentalToolkitApi import com.primex.core.blur.newBackgroundBlur +import me.kavishdevar.aln.AirPodsService import kotlin.math.roundToInt @Composable @@ -90,7 +92,12 @@ fun BatteryView() { @Suppress("DEPRECATION") val batteryReceiver = remember { object : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { - batteryStatus.value = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { intent.getParcelableArrayListExtra("data", Battery::class.java) } else { intent.getParcelableArrayListExtra("data") }?.toList() ?: listOf() + batteryStatus.value = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + intent.getParcelableArrayListExtra("data", Battery::class.java) + } else { + intent.getParcelableArrayListExtra("data") + }?.toList() ?: listOf() } } } @@ -99,7 +106,11 @@ fun BatteryView() { LaunchedEffect(context) { val batteryIntentFilter = IntentFilter(AirPodsNotifications.BATTERY_DATA) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - context.registerReceiver(batteryReceiver, batteryIntentFilter, Context.RECEIVER_EXPORTED) + context.registerReceiver( + batteryReceiver, + batteryIntentFilter, + Context.RECEIVER_EXPORTED + ) } } @@ -482,7 +493,7 @@ fun AirPodsSettingsScreen(device: BluetoothDevice?, service: AirPodsService?, CenterAlignedTopAppBar( title = { Text( - text = if (device != null) device.name else "", + text = if (device != null) LocalContext.current.getSharedPreferences("settings", MODE_PRIVATE).getString("name", device.name).toString() else "", color = if (MaterialTheme.colorScheme.surface.luminance() < 0.5) Color.White else Color.Black, ) }, diff --git a/android/app/src/main/java/me/kavishdevar/aln/CustomDevice.kt b/android/app/src/main/java/me/kavishdevar/aln/CustomDevice.kt index f88783a..61468e5 100644 --- a/android/app/src/main/java/me/kavishdevar/aln/CustomDevice.kt +++ b/android/app/src/main/java/me/kavishdevar/aln/CustomDevice.kt @@ -53,9 +53,10 @@ class CustomDevice : ComponentActivity() { HiddenApiBypass.addHiddenApiExemptions("Landroid/bluetooth/BluetoothSocket;") val manager = getSystemService(BLUETOOTH_SERVICE) as BluetoothManager // val device: BluetoothDevice = manager.adapter.getRemoteDevice("EC:D6:F4:3D:89:B8") - val device: BluetoothDevice = manager.adapter.getRemoteDevice("E0:90:8F:D9:94:73") + val device: BluetoothDevice = manager.adapter.getRemoteDevice("DE:F4:C6:A3:CD:7A") // val socket = device.createInsecureL2capChannel(31) + val batteryLevel = remember { mutableStateOf("") } // socket.outputStream.write(byteArrayOf(0x12,0x3B,0x00,0x02, 0x00)) // socket.outputStream.write(byteArrayOf(0x12, 0x3A, 0x00, 0x01, 0x00, 0x08,0x01)) @@ -66,9 +67,12 @@ class CustomDevice : ComponentActivity() { gatt.services.forEach { service -> Log.d("GATT", "Service UUID: ${service.uuid}") service.characteristics.forEach { characteristic -> - Log.d("GATT", " Characteristic UUID: ${characteristic.uuid}") + characteristic.descriptors.forEach { descriptor -> + Log.d("GATT", " Descriptor UUID: ${descriptor.uuid}: ${gatt.readDescriptor(descriptor)}") + } } } + } } @@ -115,12 +119,13 @@ class CustomDevice : ComponentActivity() { } Button(onClick = { - val characteristicUuid = "4f860002-943b-49ef-bed4-2f730304427a" - val value = byteArrayOf(0x01, 0x00, 0x02) +// val characteristicUuid = "4f860002-943b-49ef-bed4-2f730304427a" +// val value = byteArrayOf(0x01, 0x00, 0x02) + +// sendWriteRequest(gatt, characteristicUuid, value) - sendWriteRequest(gatt, characteristicUuid, value) }) { - Text("Play Sound") + Text("batteryLevel.value") } } } diff --git a/android/app/src/main/java/me/kavishdevar/aln/MainActivity.kt b/android/app/src/main/java/me/kavishdevar/aln/MainActivity.kt index 746d027..3e3a316 100644 --- a/android/app/src/main/java/me/kavishdevar/aln/MainActivity.kt +++ b/android/app/src/main/java/me/kavishdevar/aln/MainActivity.kt @@ -77,7 +77,6 @@ fun Main() { context.registerReceiver(disconnectReceiver, IntentFilter(AirPodsNotifications.AIRPODS_DISCONNECTED), Context.RECEIVER_NOT_EXPORTED) - // UI logic NavHost( navController = navController, @@ -127,7 +126,8 @@ fun Main() { context.bindService(Intent(context, AirPodsService::class.java), serviceConnection, Context.BIND_AUTO_CREATE) - if (airPodsService.value?.isConnected == true) { + val alreadyConnected = remember { mutableStateOf(false) } + if (airPodsService.value?.isConnected == true && !alreadyConnected.value) { Log.d("ALN", "Connected") navController.navigate("settings") } else { diff --git a/android/app/src/main/java/me/kavishdevar/aln/Window.kt b/android/app/src/main/java/me/kavishdevar/aln/Window.kt index e060f1c..633c8b5 100644 --- a/android/app/src/main/java/me/kavishdevar/aln/Window.kt +++ b/android/app/src/main/java/me/kavishdevar/aln/Window.kt @@ -14,17 +14,19 @@ import android.view.WindowManager import android.view.animation.AccelerateInterpolator import android.view.animation.DecelerateInterpolator import android.widget.ImageButton +import android.widget.LinearLayout import android.widget.TextView import android.widget.VideoView +import androidx.lifecycle.findViewTreeLifecycleOwner +import androidx.lifecycle.setViewTreeLifecycleOwner import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.MainScope import kotlinx.coroutines.delay import kotlinx.coroutines.launch import java.lang.Exception -class Window @SuppressLint("InflateParams") constructor( - context: Context -) { +@SuppressLint("InflateParams") +class Window (context: Context) { private val mView: View private val mParams: WindowManager.LayoutParams = WindowManager.LayoutParams( (context.resources.displayMetrics.widthPixels * 0.95).toInt(), @@ -50,6 +52,11 @@ class Window @SuppressLint("InflateParams") constructor( close() } + val ll = mView.findViewById(R.id.linear_layout) + ll.setOnClickListener { + close() + } + ll.setViewTreeLifecycleOwner(mView.findViewTreeLifecycleOwner()) mWindowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager } @@ -62,17 +69,81 @@ class Window @SuppressLint("InflateParams") constructor( mWindowManager.addView(mView, mParams) mView.findViewById(R.id.name).text = name val vid = mView.findViewById(R.id.video) + vid.setVideoPath("android.resource://me.kavishdevar.aln/" + R.raw.connected) vid.resolveAdjustedSize(vid.width, vid.height) vid.start() vid.setOnCompletionListener { vid.start() } - -// receive battery broadcast and set to R.id.battery - val batteryText = mView.findViewById(R.id.battery) - val batteryList = batteryNotification.getBattery() - batteryText.text = "Why are the battery levels zero :( " + batteryList[0].level.toString() + "%" + " " + batteryList[0].status + " " + batteryList[1].level.toString() + "%" + " " + batteryList[1].status + " " + batteryList[2].level.toString() + "%" + " " + batteryList[2].status + + val batteryStatus = batteryNotification.getBattery() + val batteryLeftText = mView.findViewById(R.id.left_battery) + val batteryRightText = mView.findViewById(R.id.right_battery) + val batteryCaseText = mView.findViewById(R.id.case_battery) + + batteryLeftText.text = batteryStatus.find { it.component == BatteryComponent.LEFT }?.let { + "\uDBC3\uDC8E ${it.level}%" + } ?: "" + batteryRightText.text = batteryStatus.find { it.component == BatteryComponent.RIGHT }?.let { + "\uDBC3\uDC8D ${it.level}%" + } ?: "" + batteryCaseText.text = batteryStatus.find { it.component == BatteryComponent.CASE }?.let { + "\uDBC3\uDE6C ${it.level}%" + } ?: "" +// bText.text = +// batteryStatus.joinToString(separator = "") { +// when (it.component) { +// BatteryComponent.LEFT -> "\uDBC6\uDCE5 ${it.level}%" +// BatteryComponent.RIGHT -> "\uDBC6\uDCE8 ${it.level}%" +// BatteryComponent.CASE -> "\uDBC6\uDCE6 ${it.level}%" +// else -> "" +// } +// } + +// composeView.setContent { +// Row ( +// modifier = Modifier +// .fillMaxWidth(), +// horizontalArrangement = Arrangement.Center, +// verticalAlignment = Alignment.CenterVertically +// ) { +// Row ( +// modifier = Modifier +// .fillMaxWidth(0.5f), +// horizontalArrangement = Arrangement.SpaceBetween +// ){ +// val left = batteryStatus.find { it.component == BatteryComponent.LEFT } +// val right = batteryStatus.find { it.component == BatteryComponent.RIGHT } +// if ((right?.status == BatteryStatus.CHARGING && left?.status == BatteryStatus.CHARGING) || (left?.status == BatteryStatus.NOT_CHARGING && right?.status == BatteryStatus.NOT_CHARGING)) +// { +// BatteryIndicator(right.level.let { left.level.coerceAtMost(it) }, left.status == BatteryStatus.CHARGING) +// } +// else { +// Row { +// if (left?.status != BatteryStatus.DISCONNECTED) { +// Text(text = "\uDBC6\uDCE5", fontFamily = FontFamily(Font(R.font.sf_pro))) +// BatteryIndicator(left?.level ?: 0, left?.status == BatteryStatus.CHARGING) +// Spacer(modifier = Modifier.width(16.dp)) +// } +// if (right?.status != BatteryStatus.DISCONNECTED) { +// Text(text = "\uDBC6\uDCE8", fontFamily = FontFamily(Font(R.font.sf_pro))) +// BatteryIndicator(right?.level ?: 0, right?.status == BatteryStatus.CHARGING) +// } +// } +// } +// } +// Row ( +// modifier = Modifier +// .fillMaxWidth(), +// horizontalArrangement = Arrangement.Center +// ) { +// val case = +// batteryStatus.find { it.component == BatteryComponent.CASE } +// BatteryIndicator(case?.level ?: 0) +// } +// } +// } // Slide-up animation val displayMetrics = mView.context.resources.displayMetrics diff --git a/android/app/src/main/res/layout/popup_window.xml b/android/app/src/main/res/layout/popup_window.xml index 4e55f44..6979d5b 100644 --- a/android/app/src/main/res/layout/popup_window.xml +++ b/android/app/src/main/res/layout/popup_window.xml @@ -5,6 +5,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="16.dp" + android:id="@+id/linear_layout" android:orientation="vertical" android:background="@drawable/shape"> @@ -12,6 +13,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="end" + android:id="@+id/constraint_layout" android:paddingBottom="48dp" android:orientation="horizontal"> @@ -49,15 +51,59 @@ android:contentDescription="AirPods" android:src="@raw/connected" tools:ignore="HardcodedText" /> - + android:orientation="horizontal"> + + + + + + + + + + + + \ No newline at end of file