Add foreground service for reliable broadcast reception
Android 8.0+ blocks implicit broadcasts to manifest-registered receivers. Added ListenerService that dynamically registers the NtfyReceiver, ensuring LOCATE commands from the ntfy app are always received. Also handles POST_NOTIFICATIONS permission. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
2f54b5a085
commit
f4c41b1851
3 changed files with 111 additions and 0 deletions
|
|
@ -6,6 +6,9 @@
|
||||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
|
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
|
||||||
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
|
|
@ -28,6 +31,11 @@
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
|
<service
|
||||||
|
android:name=".ListenerService"
|
||||||
|
android:foregroundServiceType="location"
|
||||||
|
android:exported="false" />
|
||||||
|
|
||||||
<receiver
|
<receiver
|
||||||
android:name=".NtfyReceiver"
|
android:name=".NtfyReceiver"
|
||||||
android:exported="true">
|
android:exported="true">
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,80 @@
|
||||||
|
package com.example.helios_location_finder
|
||||||
|
|
||||||
|
import android.app.Notification
|
||||||
|
import android.app.NotificationChannel
|
||||||
|
import android.app.NotificationManager
|
||||||
|
import android.app.PendingIntent
|
||||||
|
import android.app.Service
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.IntentFilter
|
||||||
|
import android.os.IBinder
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
|
||||||
|
class ListenerService : Service() {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "ListenerService"
|
||||||
|
private const val CHANNEL_ID = "helios_listener"
|
||||||
|
private const val NOTIFICATION_ID = 1
|
||||||
|
|
||||||
|
fun start(context: android.content.Context) {
|
||||||
|
val intent = Intent(context, ListenerService::class.java)
|
||||||
|
ContextCompat.startForegroundService(context, intent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val receiver = NtfyReceiver()
|
||||||
|
|
||||||
|
override fun onCreate() {
|
||||||
|
super.onCreate()
|
||||||
|
createNotificationChannel()
|
||||||
|
startForeground(NOTIFICATION_ID, buildNotification())
|
||||||
|
|
||||||
|
val filter = IntentFilter("io.heckel.ntfy.MESSAGE_RECEIVED")
|
||||||
|
ContextCompat.registerReceiver(
|
||||||
|
this, receiver, filter, ContextCompat.RECEIVER_EXPORTED
|
||||||
|
)
|
||||||
|
Log.d(TAG, "Listener service started, receiver registered")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
unregisterReceiver(receiver)
|
||||||
|
Log.d(TAG, "Listener service stopped, receiver unregistered")
|
||||||
|
super.onDestroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||||
|
return START_STICKY
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBind(intent: Intent?): IBinder? = null
|
||||||
|
|
||||||
|
private fun createNotificationChannel() {
|
||||||
|
val channel = NotificationChannel(
|
||||||
|
CHANNEL_ID,
|
||||||
|
"Helios Tracker",
|
||||||
|
NotificationManager.IMPORTANCE_LOW
|
||||||
|
).apply {
|
||||||
|
description = "Keeps Helios Tracker listening for LOCATE commands"
|
||||||
|
setShowBadge(false)
|
||||||
|
}
|
||||||
|
getSystemService(NotificationManager::class.java).createNotificationChannel(channel)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun buildNotification(): Notification {
|
||||||
|
val pendingIntent = PendingIntent.getActivity(
|
||||||
|
this, 0,
|
||||||
|
Intent(this, MainActivity::class.java),
|
||||||
|
PendingIntent.FLAG_IMMUTABLE
|
||||||
|
)
|
||||||
|
|
||||||
|
return Notification.Builder(this, CHANNEL_ID)
|
||||||
|
.setContentTitle("Helios Tracker")
|
||||||
|
.setContentText("Lauscht auf LOCATE-Anfragen")
|
||||||
|
.setSmallIcon(android.R.drawable.ic_menu_mylocation)
|
||||||
|
.setContentIntent(pendingIntent)
|
||||||
|
.setOngoing(true)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -50,6 +50,7 @@ class MainActivity : ComponentActivity() {
|
||||||
|
|
||||||
private val foregroundGranted = mutableStateOf(false)
|
private val foregroundGranted = mutableStateOf(false)
|
||||||
private val backgroundGranted = mutableStateOf(false)
|
private val backgroundGranted = mutableStateOf(false)
|
||||||
|
private val serviceRunning = mutableStateOf(false)
|
||||||
|
|
||||||
private val foregroundPermissionLauncher = registerForActivityResult(
|
private val foregroundPermissionLauncher = registerForActivityResult(
|
||||||
ActivityResultContracts.RequestMultiplePermissions()
|
ActivityResultContracts.RequestMultiplePermissions()
|
||||||
|
|
@ -62,11 +63,19 @@ class MainActivity : ComponentActivity() {
|
||||||
ActivityResultContracts.RequestPermission()
|
ActivityResultContracts.RequestPermission()
|
||||||
) { granted ->
|
) { granted ->
|
||||||
backgroundGranted.value = granted
|
backgroundGranted.value = granted
|
||||||
|
if (granted) startListenerService()
|
||||||
|
}
|
||||||
|
|
||||||
|
private val notificationPermissionLauncher = registerForActivityResult(
|
||||||
|
ActivityResultContracts.RequestPermission()
|
||||||
|
) { granted ->
|
||||||
|
if (granted) startListenerService()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
checkPermissions()
|
checkPermissions()
|
||||||
|
startListenerService()
|
||||||
|
|
||||||
val listenTopic = mutableStateOf(Prefs.getListenTopic(this))
|
val listenTopic = mutableStateOf(Prefs.getListenTopic(this))
|
||||||
val replyTopic = mutableStateOf(Prefs.getReplyTopic(this))
|
val replyTopic = mutableStateOf(Prefs.getReplyTopic(this))
|
||||||
|
|
@ -133,6 +142,20 @@ class MainActivity : ComponentActivity() {
|
||||||
backgroundPermissionLauncher.launch(Manifest.permission.ACCESS_BACKGROUND_LOCATION)
|
backgroundPermissionLauncher.launch(Manifest.permission.ACCESS_BACKGROUND_LOCATION)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun startListenerService() {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
if (ContextCompat.checkSelfPermission(
|
||||||
|
this, Manifest.permission.POST_NOTIFICATIONS
|
||||||
|
) != PackageManager.PERMISSION_GRANTED
|
||||||
|
) {
|
||||||
|
notificationPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ListenerService.start(this)
|
||||||
|
serviceRunning.value = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue