fix: ensure foreground service + notification permission on Android 14

- startService() before bindService() so Android 14 allows foreground
  promotion (bind-only no longer satisfies the "started from foreground"
  requirement since API 34)
- smarter POST_NOTIFICATIONS flow: re-ask once, then redirect to app
  notification settings instead of silently doing nothing after denial
- track first-ask via SharedPreferences so the settings redirect only
  triggers on a second-or-later launch after denial

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
HoneyandClaude Sonnet 4.6 committed 2026-05-05 22:42:11 +02:00
1 parent 0e17d2e5db
commit 0ee2a62adb
1 file changed
+47 -3
@@ -2,9 +2,12 @@ package com.novasonic.app
import android.Manifest
import android.content.ComponentName
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.provider.Settings
import android.view.KeyEvent
import android.webkit.WebChromeClient
import android.webkit.WebResourceRequest
@@ -37,6 +40,10 @@ class MainActivity : AppCompatActivity(), MusicService.StateListener {
webBridge = WebBridge { mediaController }
configureWebView()
requestNotificationPermission()
// Explicitly start the service before binding so Android 14 lets it
// promote to foreground (bindService alone doesn't satisfy the
// "started from foreground" requirement for foreground services).
startService(Intent(this, MusicService::class.java))
connectToService()
val url = BuildConfig.NOVASONIC_URL.ifEmpty { "https://demo.navidrome.org" }
@@ -176,13 +183,50 @@ class MainActivity : AppCompatActivity(), MusicService.StateListener {
// ─── Permissions ──────────────────────────────────────────────────────────
private fun requestNotificationPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU &&
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) return
when {
ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS)
!= PackageManager.PERMISSION_GRANTED
) {
== PackageManager.PERMISSION_GRANTED -> return
// System won't show the dialog again after one denial — send user to settings.
!ActivityCompat.shouldShowRequestPermissionRationale(
this, Manifest.permission.POST_NOTIFICATIONS
) && _notifPermissionAskedBefore() -> openNotificationSettings()
else -> {
markNotifPermissionAsked()
ActivityCompat.requestPermissions(
this, arrayOf(Manifest.permission.POST_NOTIFICATIONS), 42
)
}
}
}
override fun onRequestPermissionsResult(
requestCode: Int, permissions: Array<out String>, grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == 42 &&
grantResults.firstOrNull() == PackageManager.PERMISSION_DENIED
) {
// Denied — open settings so the user can flip the toggle themselves.
openNotificationSettings()
}
}
private fun openNotificationSettings() {
startActivity(
Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
data = Uri.fromParts("package", packageName, null)
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}
)
}
private fun _notifPermissionAskedBefore() =
getSharedPreferences("ns_prefs", MODE_PRIVATE).getBoolean("notif_asked", false)
private fun markNotifPermissionAsked() =
getSharedPreferences("ns_prefs", MODE_PRIVATE).edit()
.putBoolean("notif_asked", true).apply()
}