Skip to content

Commit 25ffd65

Browse files
committed
feat: Allow onNotificationPressed to trigger without SYSTEM_ALERT_WINDOW permission
1 parent 1589b62 commit 25ffd65

File tree

4 files changed

+97
-80
lines changed

4 files changed

+97
-80
lines changed

android/src/main/kotlin/com/pravera/flutter_foreground_task/FlutterForegroundTaskPlugin.kt

+13-1
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
package com.pravera.flutter_foreground_task
22

3+
import android.content.Intent
34
import com.pravera.flutter_foreground_task.service.ForegroundService
45
import com.pravera.flutter_foreground_task.service.ForegroundServiceManager
56
import com.pravera.flutter_foreground_task.service.NotificationPermissionManager
67
import com.pravera.flutter_foreground_task.service.ServiceProvider
78
import io.flutter.embedding.engine.plugins.FlutterPlugin
89
import io.flutter.embedding.engine.plugins.activity.ActivityAware
910
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
11+
import io.flutter.plugin.common.PluginRegistry.NewIntentListener
1012

1113
/** FlutterForegroundTaskPlugin */
12-
class FlutterForegroundTaskPlugin : FlutterPlugin, ActivityAware, ServiceProvider {
14+
class FlutterForegroundTaskPlugin : FlutterPlugin, ActivityAware, ServiceProvider, NewIntentListener {
1315
companion object {
1416
fun addTaskLifecycleListener(listener: FlutterForegroundTaskLifecycleListener) {
1517
ForegroundService.addTaskLifecycleListener(listener)
@@ -44,7 +46,11 @@ class FlutterForegroundTaskPlugin : FlutterPlugin, ActivityAware, ServiceProvide
4446
methodCallHandler.setActivity(binding.activity)
4547
binding.addRequestPermissionsResultListener(notificationPermissionManager)
4648
binding.addActivityResultListener(methodCallHandler)
49+
binding.addOnNewIntentListener(this)
4750
activityBinding = binding
51+
52+
val intent = binding.activity.intent
53+
ForegroundService.handleNotificationContentIntent(intent)
4854
}
4955

5056
override fun onDetachedFromActivityForConfigChanges() {
@@ -58,10 +64,16 @@ class FlutterForegroundTaskPlugin : FlutterPlugin, ActivityAware, ServiceProvide
5864
override fun onDetachedFromActivity() {
5965
activityBinding?.removeRequestPermissionsResultListener(notificationPermissionManager)
6066
activityBinding?.removeActivityResultListener(methodCallHandler)
67+
activityBinding?.removeOnNewIntentListener(this)
6168
activityBinding = null
6269
methodCallHandler.setActivity(null)
6370
}
6471

72+
override fun onNewIntent(intent: Intent): Boolean {
73+
ForegroundService.handleNotificationContentIntent(intent)
74+
return true
75+
}
76+
6577
override fun getNotificationPermissionManager() = notificationPermissionManager
6678

6779
override fun getForegroundServiceManager() = foregroundServiceManager

android/src/main/kotlin/com/pravera/flutter_foreground_task/RequestCode.kt

+1-2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,5 @@ object RequestCode {
1313
// service
1414
const val SET_RESTART_SERVICE_ALARM = 300
1515
const val NOTIFICATION_PRESSED = 301
16-
const val NOTIFICATION_PRESSED_BROADCAST = 302
17-
const val NOTIFICATION_DISMISSED_BROADCAST = 203
16+
const val NOTIFICATION_DISMISSED = 302
1817
}

android/src/main/kotlin/com/pravera/flutter_foreground_task/service/ForegroundService.kt

+82-63
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,12 @@ import com.pravera.flutter_foreground_task.FlutterForegroundTaskLifecycleListene
1919
import com.pravera.flutter_foreground_task.RequestCode
2020
import com.pravera.flutter_foreground_task.models.*
2121
import com.pravera.flutter_foreground_task.utils.ForegroundServiceUtils
22-
import com.pravera.flutter_foreground_task.utils.PluginUtils
2322
import kotlinx.coroutines.*
2423
import kotlinx.coroutines.flow.MutableStateFlow
2524
import kotlinx.coroutines.flow.asStateFlow
2625
import kotlinx.coroutines.flow.update
2726
import java.util.*
2827

29-
3028
/**
3129
* A service class for implementing foreground service.
3230
*
@@ -37,31 +35,52 @@ class ForegroundService : Service() {
3735
companion object {
3836
private val TAG = ForegroundService::class.java.simpleName
3937

40-
private const val ACTION_RECEIVE_DATA = "onReceiveData"
41-
private const val ACTION_NOTIFICATION_BUTTON_PRESSED = "onNotificationButtonPressed"
4238
private const val ACTION_NOTIFICATION_PRESSED = "onNotificationPressed"
4339
private const val ACTION_NOTIFICATION_DISMISSED = "onNotificationDismissed"
44-
private const val INTENT_DATA_FIELD_NAME = "data"
40+
private const val ACTION_NOTIFICATION_BUTTON_PRESSED = "onNotificationButtonPressed"
41+
private const val ACTION_RECEIVE_DATA = "onReceiveData"
42+
private const val INTENT_DATA_NAME = "intentData"
4543

4644
private val _isRunningServiceState = MutableStateFlow(false)
4745
val isRunningServiceState = _isRunningServiceState.asStateFlow()
4846

49-
private var foregroundTask: ForegroundTask? = null
47+
private var task: ForegroundTask? = null
5048
private var taskLifecycleListeners = ForegroundTaskLifecycleListeners()
5149

52-
fun sendData(data: Any?) {
53-
if (isRunningServiceState.value) {
54-
foregroundTask?.invokeMethod(ACTION_RECEIVE_DATA, data)
55-
}
56-
}
57-
5850
fun addTaskLifecycleListener(listener: FlutterForegroundTaskLifecycleListener) {
5951
taskLifecycleListeners.addListener(listener)
6052
}
6153

6254
fun removeTaskLifecycleListener(listener: FlutterForegroundTaskLifecycleListener) {
6355
taskLifecycleListeners.removeListener(listener)
6456
}
57+
58+
fun handleNotificationContentIntent(intent: Intent?) {
59+
if (intent == null) return
60+
61+
try {
62+
// Check if the given intent is a LaunchIntent.
63+
val isLaunchIntent = (intent.action == Intent.ACTION_MAIN) &&
64+
intent.categories.contains(Intent.CATEGORY_LAUNCHER)
65+
if (!isLaunchIntent) {
66+
// Log.d(TAG, "not LaunchIntent")
67+
return
68+
}
69+
70+
val data = intent.getStringExtra(INTENT_DATA_NAME)
71+
if (data == ACTION_NOTIFICATION_PRESSED) {
72+
task?.invokeMethod(data, null)
73+
}
74+
} catch (e: Exception) {
75+
Log.e(TAG, e.message, e)
76+
}
77+
}
78+
79+
fun sendData(data: Any?) {
80+
if (isRunningServiceState.value) {
81+
task?.invokeMethod(ACTION_RECEIVE_DATA, data)
82+
}
83+
}
6584
}
6685

6786
private lateinit var foregroundServiceStatus: ForegroundServiceStatus
@@ -80,22 +99,20 @@ class ForegroundService : Service() {
8099
// A broadcast receiver that handles intents that occur in the foreground service.
81100
private var broadcastReceiver = object : BroadcastReceiver() {
82101
override fun onReceive(context: Context?, intent: Intent?) {
83-
try {
84-
// No intent ??
85-
if (intent == null) {
86-
throw Exception("Intent is null.")
87-
}
102+
if (intent == null) return
88103

104+
try {
89105
// This intent has not sent from the current package.
90106
val iPackageName = intent.`package`
91107
val cPackageName = packageName
92108
if (iPackageName != cPackageName) {
93-
throw Exception("This intent has not sent from the current package. ($iPackageName != $cPackageName)")
109+
Log.d(TAG, "This intent has not sent from the current package. ($iPackageName != $cPackageName)")
110+
return
94111
}
95112

96113
val action = intent.action ?: return
97-
val data = intent.getStringExtra(INTENT_DATA_FIELD_NAME)
98-
foregroundTask?.invokeMethod(action, data)
114+
val data = intent.getStringExtra(INTENT_DATA_NAME)
115+
task?.invokeMethod(action, data)
99116
} catch (e: Exception) {
100117
Log.e(TAG, e.message, e)
101118
}
@@ -284,43 +301,40 @@ class ForegroundService : Service() {
284301
}
285302

286303
private fun createNotification(): Notification {
287-
// notification
288-
val channelId = notificationOptions.channelId
289-
290304
// notification icon
291305
val icon = notificationContent.icon
292306
val iconResId = getIconResId(icon)
293307
val iconBackgroundColor = icon?.backgroundColorRgb?.let(::getRgbColor)
294308

295309
// notification intent
296-
val pendingIntent = getPendingIntent()
297-
val deletePendingIntent = getDeletePendingIntent()
310+
val contentIntent = getContentIntent()
311+
val deleteIntent = getDeleteIntent()
298312

299-
// notification action
300-
var needsUpdateButtons = false
313+
// notification actions
314+
var needsRebuildButtons = false
301315
val prevButtons = prevNotificationContent?.buttons
302316
val currButtons = notificationContent.buttons
303317
if (prevButtons != null) {
304318
if (prevButtons.size != currButtons.size) {
305-
needsUpdateButtons = true
319+
needsRebuildButtons = true
306320
} else {
307321
for (i in currButtons.indices) {
308322
if (prevButtons[i] != currButtons[i]) {
309-
needsUpdateButtons = true
323+
needsRebuildButtons = true
310324
break
311325
}
312326
}
313327
}
314328
} else {
315-
needsUpdateButtons = true
329+
needsRebuildButtons = true
316330
}
317331

318332
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
319-
val builder = Notification.Builder(this, channelId)
333+
val builder = Notification.Builder(this, notificationOptions.channelId)
320334
builder.setOngoing(true)
321335
builder.setShowWhen(notificationOptions.showWhen)
322336
builder.setSmallIcon(iconResId)
323-
builder.setContentIntent(pendingIntent)
337+
builder.setContentIntent(contentIntent)
324338
builder.setContentTitle(notificationContent.title)
325339
builder.setContentText(notificationContent.text)
326340
builder.style = Notification.BigTextStyle()
@@ -333,21 +347,21 @@ class ForegroundService : Service() {
333347
builder.setForegroundServiceBehavior(Notification.FOREGROUND_SERVICE_IMMEDIATE)
334348
}
335349
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
336-
builder.setDeleteIntent(deletePendingIntent)
350+
builder.setDeleteIntent(deleteIntent)
337351
}
338352

339-
val actions = buildNotificationActions(notificationContent.buttons, needsUpdateButtons)
353+
val actions = buildNotificationActions(currButtons, needsRebuildButtons)
340354
for (action in actions) {
341355
builder.addAction(action)
342356
}
343357

344358
return builder.build()
345359
} else {
346-
val builder = NotificationCompat.Builder(this, channelId)
360+
val builder = NotificationCompat.Builder(this, notificationOptions.channelId)
347361
builder.setOngoing(true)
348362
builder.setShowWhen(notificationOptions.showWhen)
349363
builder.setSmallIcon(iconResId)
350-
builder.setContentIntent(pendingIntent)
364+
builder.setContentIntent(contentIntent)
351365
builder.setContentTitle(notificationContent.title)
352366
builder.setContentText(notificationContent.text)
353367
builder.setStyle(NotificationCompat.BigTextStyle().bigText(notificationContent.text))
@@ -364,7 +378,7 @@ class ForegroundService : Service() {
364378
}
365379
builder.priority = notificationOptions.priority
366380

367-
val actions = buildNotificationCompatActions(notificationContent.buttons, needsUpdateButtons)
381+
val actions = buildNotificationCompatActions(currButtons, needsRebuildButtons)
368382
for (action in actions) {
369383
builder.addAction(action)
370384
}
@@ -427,7 +441,7 @@ class ForegroundService : Service() {
427441
private fun createForegroundTask() {
428442
destroyForegroundTask()
429443

430-
foregroundTask = ForegroundTask(
444+
task = ForegroundTask(
431445
context = this,
432446
serviceStatus = foregroundServiceStatus,
433447
taskData = foregroundTaskData,
@@ -437,12 +451,12 @@ class ForegroundService : Service() {
437451
}
438452

439453
private fun updateForegroundTask() {
440-
foregroundTask?.update(taskEventAction = foregroundTaskOptions.eventAction)
454+
task?.update(taskEventAction = foregroundTaskOptions.eventAction)
441455
}
442456

443457
private fun destroyForegroundTask() {
444-
foregroundTask?.destroy()
445-
foregroundTask = null
458+
task?.destroy()
459+
task = null
446460
}
447461

448462
private fun getIconResId(icon: NotificationIcon?): Int {
@@ -469,27 +483,32 @@ class ForegroundService : Service() {
469483
}
470484
}
471485

472-
private fun getPendingIntent(): PendingIntent {
473-
return if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q || PluginUtils.canDrawOverlays(applicationContext)) {
474-
val pIntent = Intent(ACTION_NOTIFICATION_PRESSED).apply {
475-
setPackage(packageName)
476-
}
477-
PendingIntent.getBroadcast(
478-
this, RequestCode.NOTIFICATION_PRESSED_BROADCAST, pIntent, PendingIntent.FLAG_IMMUTABLE)
479-
} else {
480-
val pm = applicationContext.packageManager
481-
val lIntent = pm.getLaunchIntentForPackage(applicationContext.packageName)
482-
PendingIntent.getActivity(
483-
this, RequestCode.NOTIFICATION_PRESSED, lIntent, PendingIntent.FLAG_IMMUTABLE)
486+
private fun getContentIntent(): PendingIntent {
487+
val packageManager = applicationContext.packageManager
488+
val packageName = applicationContext.packageName
489+
val intent = packageManager.getLaunchIntentForPackage(packageName)?.apply {
490+
putExtra(INTENT_DATA_NAME, ACTION_NOTIFICATION_PRESSED)
491+
}
492+
493+
var flags = PendingIntent.FLAG_UPDATE_CURRENT
494+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
495+
flags = flags or PendingIntent.FLAG_IMMUTABLE
484496
}
497+
498+
return PendingIntent.getActivity(this, RequestCode.NOTIFICATION_PRESSED, intent, flags)
485499
}
486500

487-
private fun getDeletePendingIntent(): PendingIntent {
488-
val dIntent = Intent(ACTION_NOTIFICATION_DISMISSED).apply {
501+
private fun getDeleteIntent(): PendingIntent {
502+
val intent = Intent(ACTION_NOTIFICATION_DISMISSED).apply {
489503
setPackage(packageName)
490504
}
491-
return PendingIntent.getBroadcast(
492-
this, RequestCode.NOTIFICATION_DISMISSED_BROADCAST, dIntent, PendingIntent.FLAG_IMMUTABLE)
505+
506+
var flags = PendingIntent.FLAG_UPDATE_CURRENT
507+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
508+
flags = flags or PendingIntent.FLAG_IMMUTABLE
509+
}
510+
511+
return PendingIntent.getBroadcast(this, RequestCode.NOTIFICATION_DISMISSED, intent, flags)
493512
}
494513

495514
private fun getRgbColor(rgb: String): Int? {
@@ -513,16 +532,16 @@ class ForegroundService : Service() {
513532

514533
private fun buildNotificationActions(
515534
buttons: List<NotificationButton>,
516-
needsUpdate: Boolean = false
535+
needsRebuild: Boolean = false
517536
): List<Notification.Action> {
518537
val actions = mutableListOf<Notification.Action>()
519538
for (i in buttons.indices) {
520539
val intent = Intent(ACTION_NOTIFICATION_BUTTON_PRESSED).apply {
521540
setPackage(packageName)
522-
putExtra(INTENT_DATA_FIELD_NAME, buttons[i].id)
541+
putExtra(INTENT_DATA_NAME, buttons[i].id)
523542
}
524543
var flags = PendingIntent.FLAG_IMMUTABLE
525-
if (needsUpdate) {
544+
if (needsRebuild) {
526545
flags = flags or PendingIntent.FLAG_CANCEL_CURRENT
527546
}
528547
val textColor = buttons[i].textColorRgb?.let(::getRgbColor)
@@ -542,16 +561,16 @@ class ForegroundService : Service() {
542561

543562
private fun buildNotificationCompatActions(
544563
buttons: List<NotificationButton>,
545-
needsUpdate: Boolean = false
564+
needsRebuild: Boolean = false
546565
): List<NotificationCompat.Action> {
547566
val actions = mutableListOf<NotificationCompat.Action>()
548567
for (i in buttons.indices) {
549568
val intent = Intent(ACTION_NOTIFICATION_BUTTON_PRESSED).apply {
550569
setPackage(packageName)
551-
putExtra(INTENT_DATA_FIELD_NAME, buttons[i].id)
570+
putExtra(INTENT_DATA_NAME, buttons[i].id)
552571
}
553572
var flags = PendingIntent.FLAG_IMMUTABLE
554-
if (needsUpdate) {
573+
if (needsRebuild) {
555574
flags = flags or PendingIntent.FLAG_CANCEL_CURRENT
556575
}
557576
val textColor = buttons[i].textColorRgb?.let(::getRgbColor)

lib/task_handler.dart

+1-14
Original file line numberDiff line numberDiff line change
@@ -22,20 +22,7 @@ abstract class TaskHandler {
2222
void onNotificationButtonPressed(String id) {}
2323

2424
/// Called when the notification itself is pressed.
25-
///
26-
/// - AOS: This callback is triggered only if the
27-
/// "android.permission.SYSTEM_ALERT_WINDOW" permission is declared and granted.
28-
///
29-
/// ```dart
30-
/// void requestPermission() async {
31-
/// if (!await FlutterForegroundTask.canDrawOverlays) {
32-
/// await FlutterForegroundTask.openSystemAlertWindowSettings();
33-
/// }
34-
/// }
35-
/// ```
36-
///
37-
/// - iOS: only work iOS 12+
38-
void onNotificationPressed() => FlutterForegroundTask.launchApp();
25+
void onNotificationPressed() {}
3926

4027
/// Called when the notification itself is dismissed.
4128
///

0 commit comments

Comments
 (0)