Compare commits

..
3 Commits
Author SHA1 Message Date
lash404 9bead42fe3 changes 2026-11-09 16:25:55 +01:00
lash404 7fbe8b05d1 changes 2026-11-09 16:25:50 +01:00
lash404 fd1898f555 checkpoint 2026-11-09 08:19:55 +01:00
10 changed files with 258 additions and 49 deletions

No files matched your search

+1
View File
@@ -56,4 +56,5 @@ dependencies {
implementation("androidx.activity:activity-ktx:1.13.0") implementation("androidx.activity:activity-ktx:1.13.0")
implementation("androidx.navigation:navigation-compose:2.10.2") implementation("androidx.navigation:navigation-compose:2.10.2")
implementation("com.google.code.gson:gson:2.15.0")
} }
@@ -37,6 +37,8 @@ object BLEManager{
private var bluetoothGatt: BluetoothGatt? = null private var bluetoothGatt: BluetoothGatt? = null
private lateinit var bluetoothManager: BluetoothManager private lateinit var bluetoothManager: BluetoothManager
lateinit var bluetoothAdapter: BluetoothAdapter lateinit var bluetoothAdapter: BluetoothAdapter
private val writeQueue = ArrayDeque<WriteRequest>()
private var isWriting = false
val gattCallback = object : BluetoothGattCallback() { val gattCallback = object : BluetoothGattCallback() {
@RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) { override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
@@ -63,11 +65,30 @@ object BLEManager{
updateConnectionStatus(true) updateConnectionStatus(true)
} }
} }
@RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
override fun onCharacteristicWrite(gatt: BluetoothGatt?, characteristic: BluetoothGattCharacteristic?, status: Int) {
super.onCharacteristicWrite(gatt, characteristic, status)
if (status == BluetoothGatt.GATT_SUCCESS) {
Log.d("ESP32_BLE", "Write successful!")
} else {
Log.d("ESP32_BLE", "Write failed with status: $status")
}
isWriting = false
processNextWrite()
}
} }
data class Devices( data class Devices(
val deviceName: String, val deviceName: String,
val deviceMac: String val deviceMac: String
) )
private data class WriteRequest(
val serviceUuid: UUID,
val characteristicUuid: UUID,
val data: ByteArray
)
@RequiresApi(Build.VERSION_CODES.S) @RequiresApi(Build.VERSION_CODES.S)
@RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
fun init(context: Context, activity: Activity) { fun init(context: Context, activity: Activity) {
@@ -134,46 +155,45 @@ object BLEManager{
Log.d("BLE_DEBUG", "Permission BLUETOOTH_CONNECT error!") Log.d("BLE_DEBUG", "Permission BLUETOOTH_CONNECT error!")
} }
} }
fun writeToEsp32(context: Context, dataString: String?, SERVICE_UUID: UUID?, CHARACTERISTIC_UUID: UUID?) {
val gatt = bluetoothGatt
if (gatt == null) {
Log.d("ESP32_BLE", "Error: No GATT object existing (not connected).")
return
}
if (dataString == null) { @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
Log.d("ESP32_BLE", "Error: Data string is null.") fun writeToEsp32(dataString: String?, SERVICE_UUID: UUID?, CHARACTERISTIC_UUID: UUID?) {
return if (dataString == null || SERVICE_UUID == null || CHARACTERISTIC_UUID == null) return
}
val service = gatt.getService(SERVICE_UUID) val dataBytes = dataString.toByteArray(Charsets.UTF_8)
if (service == null) { val request = WriteRequest(SERVICE_UUID, CHARACTERISTIC_UUID, dataBytes)
Log.d("ESP32_BLE", "Error: Service UUID not found!")
return
}
val characteristic = service.getCharacteristic(CHARACTERISTIC_UUID) // Auftrag zur Warteschlange hinzufügen
if (characteristic == null) { writeQueue.add(request)
Log.d("ESP32_BLE", "Error: Characteristic UUID not found!")
return
}
if (ActivityCompat.checkSelfPermission(context, Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) { // Versuchen, den nächsten Auftrag zu starten, falls gerade nichts läuft
Log.d("ESP32_BLE", "Error: BLUETOOTH_CONNECT permission not granted.") processNextWrite()
return }
} @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
private fun processNextWrite() {
// Wenn bereits ein Schreibvorgang läuft oder die Queue leer ist, abbrechen
if (isWriting || writeQueue.isEmpty()) return
characteristic.value = dataString.toByteArray(Charsets.UTF_8) val gatt = bluetoothGatt ?: return
// Nächsten Auftrag holen
val request = writeQueue.removeFirst()
val service = gatt.getService(request.serviceUuid) ?: return
val characteristic = service.getCharacteristic(request.characteristicUuid) ?: return
characteristic.value = request.data
characteristic.writeType = BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE characteristic.writeType = BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE
isWriting = true // Sperren, bis der Callback kommt
val success = gatt.writeCharacteristic(characteristic) val success = gatt.writeCharacteristic(characteristic)
if (success) {
Log.d("ESP32_BLE", "Successfully started writing: $dataString") if (!success) {
} else { Log.d("ESP32_BLE", "Failed to start writing, resetting lock.")
Log.d("ESP32_BLE", "Failed to start writing (gatt busy or invalid state).") isWriting = false
processNextWrite() // Beim nächsten versuchen
} }
} }
@androidx.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
fun syncTime(context: Context) { fun syncTime(context: Context) {
val currentTime = LocalTime.now() val currentTime = LocalTime.now()
val formatterTime = DateTimeFormatter.ofPattern("HH mm ss") val formatterTime = DateTimeFormatter.ofPattern("HH mm ss")
@@ -186,7 +206,6 @@ object BLEManager{
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
writeToEsp32( writeToEsp32(
context = context,
dataString = formattedTime, dataString = formattedTime,
SERVICE_UUID = deviceUUIDs.SERVICE_UUID, SERVICE_UUID = deviceUUIDs.SERVICE_UUID,
CHARACTERISTIC_UUID = deviceUUIDs.CHAR_TIME_UUID) CHARACTERISTIC_UUID = deviceUUIDs.CHAR_TIME_UUID)
@@ -194,7 +213,6 @@ object BLEManager{
delay(150.milliseconds) delay(150.milliseconds)
writeToEsp32( writeToEsp32(
context = context,
dataString = formattedDate, dataString = formattedDate,
SERVICE_UUID = deviceUUIDs.SERVICE_UUID, SERVICE_UUID = deviceUUIDs.SERVICE_UUID,
CHARACTERISTIC_UUID = deviceUUIDs.CHAR_DATE_UUID) CHARACTERISTIC_UUID = deviceUUIDs.CHAR_DATE_UUID)
@@ -3,11 +3,13 @@ package com.example.smart_watch_android.BLE
import java.util.UUID import java.util.UUID
object DeviceUUIDs { object DeviceUUIDs {
val SERVICE_UUID: UUID? = UUID.fromString("791cadd7-de87-49f9-8986-94b7ae11940b") val SERVICE_UUID: UUID? = UUID.fromString("00004d43-0000-1000-8000-00805F9B34FB")
val CHAR_MESSAGE_UUID: UUID? = UUID.fromString("3e4eac5b-eaa2-4e0c-a705-dcac4f606e01") val CHAR_MESSAGE_UUID: UUID? = UUID.fromString("0000b331-0000-1000-8000-00805F9B34FB")
val CHAR_TIME_UUID:UUID? = UUID.fromString("3e4eac5b-eaa2-4e0c-a705-dcac4f606e02") val CHAR_TIME_UUID:UUID? = UUID.fromString("00003c4b-0000-1000-8000-00805F9B34FB")
val CHAR_DATE_UUID:UUID? = UUID.fromString("3e4eac5b-eaa2-4e0c-a705-dcac4f606e03") val CHAR_DATE_UUID:UUID? = UUID.fromString("00006ecf-0000-1000-8000-00805F9B34FB")
val CHAR_NOTIF_APP_UUID:UUID? = UUID.fromString("9ff3d4a9-2082-424d-bf34-8dfaf752ff90")
val CHAR_NOTIF_TITLE_UUID:UUID? = UUID.fromString("9ff3d4a9-2082-424d-bf34-8dfaf752ff91") val SERVICE_NOTIFICATION_UUID:UUID? = UUID.fromString("0000a7fa-0000-1000-8000-00805F9B34FB")
val CHAR_NOTIF_TEXT_UUID: UUID? = UUID.fromString("9ff3d4a9-2082-424d-bf34-8dfaf752ff92") val CHAR_NOTIF_APP_UUID:UUID? = UUID.fromString("0000375f-0000-1000-8000-00805F9B34FB")
val CHAR_NOTIF_TITLE_UUID:UUID? = UUID.fromString("00006e40-0000-1000-8000-00805F9B34FB")
val CHAR_NOTIF_TEXT_UUID: UUID? = UUID.fromString("00002eef-0000-1000-8000-00805F9B34FB")
} }
@@ -2,8 +2,6 @@ package com.example.smart_watch_android
import android.Manifest import android.Manifest
import android.bluetooth.BluetoothAdapter import android.bluetooth.BluetoothAdapter
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
@@ -20,8 +18,7 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import com.example.smart_watch_android.BLE.BLEManager import com.example.smart_watch_android.BLE.BLEManager
import com.example.smart_watch_android.BLE.DeviceUUIDs import com.example.smart_watch_android.Notification.BlacklistManager
import com.example.smart_watch_android.Notification.MyNotificationListener
import com.example.smart_watch_android.Screens.AppNavigation import com.example.smart_watch_android.Screens.AppNavigation
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
@@ -38,6 +35,7 @@ class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?,) { override fun onCreate(savedInstanceState: Bundle?,) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
BLEManager.init(context = applicationContext, activity = this) BLEManager.init(context = applicationContext, activity = this)
BlacklistManager.init(context = applicationContext)
val adapter = BLEManager.bluetoothAdapter val adapter = BLEManager.bluetoothAdapter
if (!adapter.isEnabled) { if (!adapter.isEnabled) {
val enableBtIntent = Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE) val enableBtIntent = Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE)
@@ -0,0 +1,69 @@
package com.example.smart_watch_android.Notification
import android.content.Context
import com.google.gson.Gson
import com.google.firebase.crashlytics.buildtools.reloc.com.google.common.reflect.TypeToken
import java.io.File
object BlacklistManager {
private const val FILE_NAME = "blacklist.json"
private val blacklistCache = mutableSetOf<String>()
val AppCache = mutableSetOf<String>()
private var isInitialized = false
fun init(context: Context) {
if (isInitialized) return
val loadedList = loadStringList(context)
blacklistCache.clear()
blacklistCache.addAll(loadedList)
AppCache.addAll(loadedList)
isInitialized = true
}
fun isBlacklisted(packageName: String?): Boolean {
return blacklistCache.contains(packageName)
}
fun addBlackListPackage(context: Context, packageName: String) {
if (blacklistCache.add(packageName)) {
saveStringList(context)
}
}
fun removeBlackListPackage(context: Context, packageName: String) {
if (blacklistCache.remove(packageName)) {
saveStringList(context)
}
}
fun getAllBlacklisted(): Set<String> {
return blacklistCache
}
fun addAppPackage(packageName: String) {
AppCache.add(packageName)
}
fun removeAppPackage(packageName: String) {
AppCache.remove(packageName)
}
fun getAllApps(): Set<String> {
return AppCache
}
private fun saveStringList(context: Context) {
val jsonString = Gson().toJson(blacklistCache.toList())
File(context.filesDir, FILE_NAME).writeText(jsonString)
}
private fun loadStringList(context: Context): List<String> {
val file = File(context.filesDir, FILE_NAME)
if (!file.exists()) return emptyList()
return try {
val jsonString = file.readText()
val type = object : TypeToken<List<String>>() {}.type
Gson().fromJson(jsonString, type) ?: emptyList()
} catch (e: Exception) {
emptyList()
}
}
}
@@ -1,11 +1,15 @@
package com.example.smart_watch_android.Notification package com.example.smart_watch_android.Notification
import android.content.Intent import android.Manifest
import android.service.notification.StatusBarNotification import android.service.notification.StatusBarNotification
import android.util.Log import android.util.Log
import android.service.notification.NotificationListenerService import android.service.notification.NotificationListenerService
import androidx.annotation.RequiresPermission
import com.example.smart_watch_android.BLE.BLEManager
import com.example.smart_watch_android.BLE.DeviceUUIDs
class MyNotificationListener : NotificationListenerService() { class MyNotificationListener : NotificationListenerService() {
@RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
override fun onNotificationPosted(sbn: StatusBarNotification?) { override fun onNotificationPosted(sbn: StatusBarNotification?) {
super.onNotificationPosted(sbn) super.onNotificationPosted(sbn)
val packageName = sbn?.packageName val packageName = sbn?.packageName
@@ -14,6 +18,13 @@ class MyNotificationListener : NotificationListenerService() {
val text = extras?.getString("android.text") val text = extras?.getString("android.text")
Log.d("NotificationListener", "App: $packageName | Title: $title | Text: $text") Log.d("NotificationListener", "App: $packageName | Title: $title | Text: $text")
if (!BlacklistManager.isBlacklisted(packageName)){
BLEManager.writeToEsp32(dataString = packageName, SERVICE_UUID = DeviceUUIDs.SERVICE_NOTIFICATION_UUID, CHARACTERISTIC_UUID = DeviceUUIDs.CHAR_NOTIF_APP_UUID)
BLEManager.writeToEsp32(dataString = title, SERVICE_UUID = DeviceUUIDs.SERVICE_NOTIFICATION_UUID, CHARACTERISTIC_UUID = DeviceUUIDs.CHAR_NOTIF_TITLE_UUID)
BLEManager.writeToEsp32(dataString = text, SERVICE_UUID = DeviceUUIDs.SERVICE_NOTIFICATION_UUID, CHARACTERISTIC_UUID = DeviceUUIDs.CHAR_NOTIF_TEXT_UUID)
}
} }
override fun onNotificationRemoved(sbn: StatusBarNotification?) { override fun onNotificationRemoved(sbn: StatusBarNotification?) {
super.onNotificationRemoved(sbn) super.onNotificationRemoved(sbn)
@@ -21,6 +21,12 @@ fun AppNavigation(viewModel: MainViewModel) {
val context = LocalContext.current val context = LocalContext.current
val isConnected by BLEManager.isConnected.collectAsState() val isConnected by BLEManager.isConnected.collectAsState()
val navController = rememberNavController() val navController = rememberNavController()
enum class enumScreens {
control_panel,
connect,
black_list
}
lateinit var Screens: enumScreens
LaunchedEffect(BLEManager.isConnected) { LaunchedEffect(BLEManager.isConnected) {
if (isConnected) { if (isConnected) {
navController.navigate("control_panel") { navController.navigate("control_panel") {
@@ -54,10 +60,14 @@ fun AppNavigation(viewModel: MainViewModel) {
BLEManager.disconnectEsp32(context = context) BLEManager.disconnectEsp32(context = context)
}, },
onNotificationListnerClicked = { onNotificationListnerClicked = {
val intent = Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS) //val intent = Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS)
context.startActivity(intent) //context.startActivity(intent)#
navController.navigate("black_list")
} }
) )
} }
composable("black_list") {
BlacklistScreen()
}
} }
} }
@@ -0,0 +1,89 @@
package com.example.smart_watch_android.Screens
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.example.smart_watch_android.Notification.BlacklistManager
@Composable
fun BlacklistScreen() {
val context = LocalContext.current
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
LazyColumn(
modifier = Modifier.fillMaxSize(),
contentPadding = PaddingValues(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
items(BlacklistManager.AppCache.count()) {
BlacklistManager.AppCache.forEach { app ->
AppRowItem(app)
}
}
}
}
}
@Composable
fun AppRowItem(app: String) {
var checked = true
val context = LocalContext.current
Card(
modifier = Modifier.fillMaxWidth(),
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp)
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(12.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.weight(1f)
) {
Column {
Text(
text = app,
style = MaterialTheme.typography.bodyLarge
)
Text(
text = app,
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
}
if (BlacklistManager.isBlacklisted(app)){
checked = true
}
Switch(
checked = checked,
onCheckedChange = {
BlacklistManager.addBlackListPackage(context , app)
}
)
}
}
}
@@ -107,5 +107,17 @@ fun ConnectScreen(viewModel: MainViewModel) {
fontSize = 32.sp fontSize = 32.sp
) )
} }
Spacer(modifier = Modifier.height(64.dp))
Button(
modifier = Modifier.fillMaxWidth(),
onClick = { BLEManager.updateConnectionStatus(true)}
) {
Text(
text = "fake connect",
fontSize = 32.sp
)
}
} }
} }
@@ -24,12 +24,12 @@ import androidx.compose.ui.unit.dp
import com.example.smart_watch_android.BLE.BLEManager import com.example.smart_watch_android.BLE.BLEManager
import com.example.smart_watch_android.BLE.DeviceUUIDs import com.example.smart_watch_android.BLE.DeviceUUIDs
import com.example.smart_watch_android.MainViewModel import com.example.smart_watch_android.MainViewModel
@androidx.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
@Composable @Composable
fun ControlPanelScreen( fun ControlPanelScreen(
viewModel: MainViewModel, viewModel: MainViewModel,
onDisconnectClicked: () -> Unit, onDisconnectClicked: () -> Unit,
onNotificationListnerClicked: () -> Unit onNotificationListnerClicked: @Composable () -> Unit
) { ) {
val context = LocalContext.current val context = LocalContext.current
var textInput by remember { mutableStateOf("") } var textInput by remember { mutableStateOf("") }
@@ -55,7 +55,6 @@ fun ControlPanelScreen(
onClick = { onClick = {
if (textInput.isNotEmpty()) { if (textInput.isNotEmpty()) {
BLEManager.writeToEsp32( BLEManager.writeToEsp32(
context = context,
dataString = textInput, dataString = textInput,
SERVICE_UUID = DeviceUUIDs.SERVICE_UUID, SERVICE_UUID = DeviceUUIDs.SERVICE_UUID,
CHARACTERISTIC_UUID = DeviceUUIDs.CHAR_MESSAGE_UUID, CHARACTERISTIC_UUID = DeviceUUIDs.CHAR_MESSAGE_UUID,
@@ -99,7 +98,7 @@ fun ControlPanelScreen(
colors = ButtonDefaults.buttonColors(containerColor = Color(0xFFE53935)), colors = ButtonDefaults.buttonColors(containerColor = Color(0xFFE53935)),
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) { ) {
Text("Notification Listener") Text("App BlackList")
} }
} }
} }