fix: notification permission (#914)
This commit is contained in:
@@ -2,6 +2,8 @@ package tech.lolli.toolbox
|
|||||||
|
|
||||||
import android.app.*
|
import android.app.*
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.content.pm.ServiceInfo
|
||||||
|
import android.graphics.drawable.Icon
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
@@ -48,19 +50,22 @@ class ForegroundService : Service() {
|
|||||||
|
|
||||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||||
try {
|
try {
|
||||||
|
// Check notification permission for Android 13+
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU &&
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU &&
|
||||||
androidx.core.content.ContextCompat.checkSelfPermission(
|
androidx.core.content.ContextCompat.checkSelfPermission(
|
||||||
this, android.Manifest.permission.POST_NOTIFICATIONS
|
this, android.Manifest.permission.POST_NOTIFICATIONS
|
||||||
) != android.content.pm.PackageManager.PERMISSION_GRANTED
|
) != android.content.pm.PackageManager.PERMISSION_GRANTED
|
||||||
) {
|
) {
|
||||||
Log.w("ForegroundService", "Notification permission denied. Stopping service.")
|
Log.w("ForegroundService", "Notification permission denied. Stopping service gracefully.")
|
||||||
stopForegroundService()
|
// Don't call stopForegroundService() here as we haven't started foreground yet
|
||||||
|
stopSelf()
|
||||||
return START_NOT_STICKY
|
return START_NOT_STICKY
|
||||||
}
|
}
|
||||||
|
|
||||||
if (intent == null) {
|
if (intent == null) {
|
||||||
Log.w("ForegroundService", "onStartCommand called with null intent")
|
Log.w("ForegroundService", "onStartCommand called with null intent")
|
||||||
stopForegroundService()
|
// Don't call stopForegroundService() here as we haven't started foreground yet
|
||||||
|
stopSelf()
|
||||||
return START_NOT_STICKY
|
return START_NOT_STICKY
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,33 +106,62 @@ class ForegroundService : Service() {
|
|||||||
|
|
||||||
private fun createNotificationChannel() {
|
private fun createNotificationChannel() {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
val manager = getSystemService(NotificationManager::class.java)
|
try {
|
||||||
if (manager == null) {
|
val manager = getSystemService(NotificationManager::class.java)
|
||||||
Log.e("ForegroundService", "Failed to get NotificationManager")
|
if (manager == null) {
|
||||||
return
|
Log.e("ForegroundService", "Failed to get NotificationManager")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val serviceChannel = NotificationChannel(
|
||||||
|
chanId,
|
||||||
|
"ForegroundServiceChannel",
|
||||||
|
NotificationManager.IMPORTANCE_DEFAULT
|
||||||
|
).apply {
|
||||||
|
description = "For foreground service"
|
||||||
|
}
|
||||||
|
manager.createNotificationChannel(serviceChannel)
|
||||||
|
Log.d("ForegroundService", "Notification channel created successfully")
|
||||||
|
} catch (e: Exception) {
|
||||||
|
logError("Failed to create notification channel", e)
|
||||||
}
|
}
|
||||||
val serviceChannel = NotificationChannel(
|
|
||||||
chanId,
|
|
||||||
"ForegroundServiceChannel",
|
|
||||||
NotificationManager.IMPORTANCE_DEFAULT
|
|
||||||
).apply {
|
|
||||||
description = "For foreground service"
|
|
||||||
}
|
|
||||||
manager.createNotificationChannel(serviceChannel)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun ensureForeground(notification: Notification) {
|
private fun ensureForeground(notification: Notification) {
|
||||||
try {
|
try {
|
||||||
|
// Double-check notification permission before starting foreground service
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU &&
|
||||||
|
androidx.core.content.ContextCompat.checkSelfPermission(
|
||||||
|
this, android.Manifest.permission.POST_NOTIFICATIONS
|
||||||
|
) != android.content.pm.PackageManager.PERMISSION_GRANTED
|
||||||
|
) {
|
||||||
|
Log.w("ForegroundService", "Cannot start foreground service without notification permission")
|
||||||
|
stopSelf()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (!isFgStarted) {
|
if (!isFgStarted) {
|
||||||
startForeground(NOTIFICATION_ID, notification)
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
|
startForeground(NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC)
|
||||||
|
} else {
|
||||||
|
startForeground(NOTIFICATION_ID, notification)
|
||||||
|
}
|
||||||
isFgStarted = true
|
isFgStarted = true
|
||||||
|
Log.d("ForegroundService", "Foreground service started successfully")
|
||||||
} else {
|
} else {
|
||||||
val nm = getSystemService(NotificationManager::class.java)
|
val nm = getSystemService(NotificationManager::class.java)
|
||||||
nm?.notify(NOTIFICATION_ID, notification)
|
if (nm != null) {
|
||||||
|
nm.notify(NOTIFICATION_ID, notification)
|
||||||
|
} else {
|
||||||
|
Log.w("ForegroundService", "NotificationManager is null, cannot update notification")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} catch (e: SecurityException) {
|
||||||
|
logError("Security exception when starting foreground service (likely missing permission)", e)
|
||||||
|
stopSelf()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
logError("Failed to start/update foreground", e)
|
logError("Failed to start/update foreground", e)
|
||||||
|
// Don't stop the service for other exceptions, just log them
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,21 +177,22 @@ class ForegroundService : Service() {
|
|||||||
val builder = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
val builder = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
Notification.Builder(this, chanId)
|
Notification.Builder(this, chanId)
|
||||||
} else {
|
} else {
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
Notification.Builder(this)
|
Notification.Builder(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use the earliest session's start time for chronometer
|
// Use the earliest session's start time for chronometer
|
||||||
val earliestStartTime = sessions.minOfOrNull { it.startWhen } ?: System.currentTimeMillis()
|
val earliestStartTime = sessions.minOfOrNull { it.startWhen } ?: System.currentTimeMillis()
|
||||||
|
|
||||||
val title = when {
|
val title = when (count) {
|
||||||
count == 0 -> "Server Box"
|
0 -> "Server Box"
|
||||||
count == 1 -> sessions.first().title
|
1 -> sessions.first().title
|
||||||
else -> "SSH sessions: $count active"
|
else -> "SSH sessions: $count active"
|
||||||
}
|
}
|
||||||
|
|
||||||
val contentText = when {
|
val contentText = when (count) {
|
||||||
count == 0 -> "Ready for connections"
|
0 -> "Ready for connections"
|
||||||
count == 1 -> {
|
1 -> {
|
||||||
val session = sessions.first()
|
val session = sessions.first()
|
||||||
"${session.subtitle} · ${session.status}"
|
"${session.subtitle} · ${session.status}"
|
||||||
}
|
}
|
||||||
@@ -189,7 +224,13 @@ class ForegroundService : Service() {
|
|||||||
.setOngoing(true)
|
.setOngoing(true)
|
||||||
.setOnlyAlertOnce(true)
|
.setOnlyAlertOnce(true)
|
||||||
.setContentIntent(pendingIntent)
|
.setContentIntent(pendingIntent)
|
||||||
.addAction(android.R.drawable.ic_delete, "Stop All", stopPending)
|
.addAction(
|
||||||
|
Notification.Action.Builder(
|
||||||
|
Icon.createWithResource(this, android.R.drawable.ic_delete),
|
||||||
|
"Stop All",
|
||||||
|
stopPending
|
||||||
|
).build()
|
||||||
|
)
|
||||||
|
|
||||||
if (style != null) {
|
if (style != null) {
|
||||||
notification.setStyle(style)
|
notification.setStyle(style)
|
||||||
@@ -260,7 +301,10 @@ class ForegroundService : Service() {
|
|||||||
|
|
||||||
private fun stopForegroundService() {
|
private fun stopForegroundService() {
|
||||||
try {
|
try {
|
||||||
stopForeground(true)
|
if (isFgStarted) {
|
||||||
|
stopForeground(STOP_FOREGROUND_REMOVE)
|
||||||
|
isFgStarted = false
|
||||||
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
logError("Error stopping foreground", e)
|
logError("Error stopping foreground", e)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -105,19 +105,24 @@ class MainActivity: FlutterFragmentActivity() {
|
|||||||
private fun reqPerm() {
|
private fun reqPerm() {
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) return
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) return
|
||||||
|
|
||||||
// Check if we already have the permission to avoid unnecessary prompts
|
try {
|
||||||
if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS)
|
// Check if we already have the permission to avoid unnecessary prompts
|
||||||
!= PackageManager.PERMISSION_GRANTED) {
|
if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS)
|
||||||
try {
|
!= PackageManager.PERMISSION_GRANTED) {
|
||||||
|
// Check if we should show rationale
|
||||||
|
if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.POST_NOTIFICATIONS)) {
|
||||||
|
android.util.Log.i("MainActivity", "User previously denied notification permission")
|
||||||
|
}
|
||||||
|
|
||||||
ActivityCompat.requestPermissions(
|
ActivityCompat.requestPermissions(
|
||||||
this,
|
this,
|
||||||
arrayOf(Manifest.permission.POST_NOTIFICATIONS),
|
arrayOf(Manifest.permission.POST_NOTIFICATIONS),
|
||||||
123,
|
123,
|
||||||
)
|
)
|
||||||
} catch (e: Exception) {
|
|
||||||
// Log error but don't crash
|
|
||||||
android.util.Log.e("MainActivity", "Failed to request permissions: ${e.message}")
|
|
||||||
}
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
// Log error but don't crash
|
||||||
|
android.util.Log.e("MainActivity", "Failed to request permissions: ${e.message}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -163,13 +168,37 @@ class MainActivity: FlutterFragmentActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
val filter = IntentFilter(ACTION_STOP_ALL_CONNECTIONS)
|
val filter = IntentFilter(ACTION_STOP_ALL_CONNECTIONS)
|
||||||
registerReceiver(stopAllReceiver, filter)
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
ContextCompat.registerReceiver(this, stopAllReceiver, filter, ContextCompat.RECEIVER_NOT_EXPORTED)
|
||||||
|
} else {
|
||||||
|
registerReceiver(stopAllReceiver, filter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRequestPermissionsResult(
|
||||||
|
requestCode: Int,
|
||||||
|
permissions: Array<out String>,
|
||||||
|
grantResults: IntArray
|
||||||
|
) {
|
||||||
|
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||||
|
if (requestCode == 123) {
|
||||||
|
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||||
|
android.util.Log.i("MainActivity", "Notification permission granted")
|
||||||
|
} else {
|
||||||
|
android.util.Log.w("MainActivity", "Notification permission denied")
|
||||||
|
// Optionally inform user about the limitation
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
stopAllReceiver?.let {
|
stopAllReceiver?.let {
|
||||||
unregisterReceiver(it)
|
try {
|
||||||
|
unregisterReceiver(it)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
android.util.Log.e("MainActivity", "Failed to unregister receiver: ${e.message}")
|
||||||
|
}
|
||||||
stopAllReceiver = null
|
stopAllReceiver = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user