Dev-hwang / flutter_foreground_task

This plugin is used to implement a foreground service on the Android platform.
https://pub.dev/packages/flutter_foreground_task
MIT License
140 stars 105 forks source link

Foreground Service Type's Default value is set to 0 if shared prefs is empty #224

Closed lloydk26 closed 2 months ago

lloydk26 commented 3 months ago

I'd like to report a problem with the latest version. Our app intermittently encounters the InvalidForegroundServiceTypeException, despite us having already specified the ForegroundServiceType.

Fatal Exception: java.lang.RuntimeException
Unable to create service com.pravera.flutter_foreground_task.service.ForegroundService: android.app.InvalidForegroundServiceTypeException: Starting FGS with type none callerApp=ProcessRecord{19153a 16493:com.angkas.customer/u0a406} targetSDK=34 has been prohibited

Notice in our initialization of Foreground Task that RemoteMessaging is already set as our ServiceType

  void _initForegroundTask() {
    FlutterForegroundTask.init(
      androidNotificationOptions: AndroidNotificationOptions(
        foregroundServiceType: AndroidForegroundServiceType.REMOTE_MESSAGING, //foregroundServiceType is already set to REMOTE_MESSAGING
        channelId: NotificationChannels.processBookingId,
        channelName: NotificationChannels.processBookingName,
        channelImportance: NotificationChannelImportance.HIGH,
        priority: NotificationPriority.HIGH,
        iconData: const NotificationIconData(
          resType: ResourceType.mipmap,
          resPrefix: ResourcePrefix.ic,
          name: 'launcher',
        ),
        buttons: [],
      ),
      iosNotificationOptions: const IOSNotificationOptions(),
      foregroundTaskOptions: const ForegroundTaskOptions(
        autoRunOnBoot: true,
        allowWifiLock: true,
      ),
    );
  }
}

We also added the necessary permissions in our AndroidManifest.xml file

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="com.angkas.cax">

   <uses-permission android:name="android.permission.INTERNET"/>
   <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
   <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
   <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
   <uses-permission android:name="android.permission.FOREGROUND_SERVICE_REMOTE_MESSAGING" />
   <uses-permission android:name="android.permission.CAMERA" />
   <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
   <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="32" />

        <service android:name="com.pravera.flutter_foreground_task.service.ForegroundService"
            android:foregroundServiceType="remoteMessaging" />

Upon reviewing NotificationOptions.kt, I noticed that we're setting the default value of foregroundServiceType to 0. This is problematic because it can lead to race conditions when fetching this value, and 0 is not a valid Foreground Service Type for Android 14 devices when using a Foreground Service.

fun putData(context: Context, map: Map<*, *>?) {
            val prefs = context.getSharedPreferences(
                PrefsKey.NOTIFICATION_OPTIONS_PREFS, Context.MODE_PRIVATE)

            val foregroundServiceType = map?.get(PrefsKey.FOREGROUND_SERVICE_TYPE) as? Int ?: 0
            val id = map?.get(PrefsKey.NOTIFICATION_ID) as? Int ?: 1000
            val channelId = map?.get(PrefsKey.NOTIFICATION_CHANNEL_ID) as? String
            val channelName = map?.get(PrefsKey.NOTIFICATION_CHANNEL_NAME) as? String
            val channelDesc = map?.get(PrefsKey.NOTIFICATION_CHANNEL_DESC) as? String
            val channelImportance = map?.get(PrefsKey.NOTIFICATION_CHANNEL_IMPORTANCE) as? Int ?: 3
            val priority = map?.get(PrefsKey.NOTIFICATION_PRIORITY) as? Int ?: 0
            val contentTitle = map?.get(PrefsKey.NOTIFICATION_CONTENT_TITLE) as? String ?: ""
            val contentText = map?.get(PrefsKey.NOTIFICATION_CONTENT_TEXT) as? String ?: ""
            val enableVibration = map?.get(PrefsKey.ENABLE_VIBRATION) as? Boolean ?: false
            val playSound = map?.get(PrefsKey.PLAY_SOUND) as? Boolean ?: false
            val showWhen = map?.get(PrefsKey.SHOW_WHEN) as? Boolean ?: false
            val isSticky = map?.get(PrefsKey.IS_STICKY) as? Boolean ?: true
            val visibility = map?.get(PrefsKey.VISIBILITY) as? Int ?: 1

            val iconData = map?.get(PrefsKey.ICON_DATA) as? Map<*, *>
            var iconDataJson: String? = null
            if (iconData != null) {
                iconDataJson = JSONObject(iconData).toString()
            }

            val buttons = map?.get(PrefsKey.BUTTONS) as? List<*>
            var buttonsJson: String? = null
            if (buttons != null) {
                buttonsJson = JSONArray(buttons).toString()
            }

            with(prefs.edit()) {
                putInt(PrefsKey.FOREGROUND_SERVICE_TYPE, foregroundServiceType)
                putInt(PrefsKey.NOTIFICATION_ID, id)
                putString(PrefsKey.NOTIFICATION_CHANNEL_ID, channelId)
                putString(PrefsKey.NOTIFICATION_CHANNEL_NAME, channelName)
                putString(PrefsKey.NOTIFICATION_CHANNEL_DESC, channelDesc)
                putInt(PrefsKey.NOTIFICATION_CHANNEL_IMPORTANCE, channelImportance)
                putInt(PrefsKey.NOTIFICATION_PRIORITY, priority)
                putString(PrefsKey.NOTIFICATION_CONTENT_TITLE, contentTitle)
                putString(PrefsKey.NOTIFICATION_CONTENT_TEXT, contentText)
                putBoolean(PrefsKey.ENABLE_VIBRATION, enableVibration)
                putBoolean(PrefsKey.PLAY_SOUND, playSound)
                putBoolean(PrefsKey.SHOW_WHEN, showWhen)
                putBoolean(PrefsKey.IS_STICKY, isSticky)
                putInt(PrefsKey.VISIBILITY, visibility)
                putString(PrefsKey.ICON_DATA, iconDataJson)
                putString(PrefsKey.BUTTONS, buttonsJson)
                commit()
            }
        }

Additionally, in ForegroundServiceManager.kt, the cache is cleared after stopping the Foreground Service, which increases the likelihood of the ForegroundServiceType being reset to 0.

    fun stop(context: Context): Boolean {
        // If the service is not running, the stop function is not executed.
        if (!ForegroundService.isRunningService) return false

        try {
            val nIntent = Intent(context, ForegroundService::class.java)
            ForegroundServiceStatus.putData(context, ForegroundServiceAction.STOP)
            ForegroundTaskOptions.clearData(context)
            NotificationOptions.clearData(context)
            ContextCompat.startForegroundService(context, nIntent)
        } catch (e: Exception) {
            return false
        }

        return true
    }

I strongly recommend addressing this issue, as it is causing some users of our app to encounter the aforementioned exception.

Dev-hwang commented 3 months ago

Version 6.4.0 uses all flags specified in the manifest attribute without specifying the foregroundServiceType on the dart side. Thank you for reporting the issue.