Skip to content

Commit ef9b76e

Browse files
committed
feat: Support manual foregroundServiceType via serviceTypes in startService #328
1 parent 03e83ae commit ef9b76e

File tree

9 files changed

+158
-24
lines changed

9 files changed

+158
-24
lines changed

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ object PreferencesKey {
1616
const val FOREGROUND_SERVICE_STATUS_PREFS = prefix + "FOREGROUND_SERVICE_STATUS"
1717
const val FOREGROUND_SERVICE_ACTION = "foregroundServiceAction"
1818

19+
// service types
20+
const val FOREGROUND_SERVICE_TYPES_PREFS = prefix + "FOREGROUND_SERVICE_TYPES"
21+
const val FOREGROUND_SERVICE_TYPES = "serviceTypes"
22+
1923
// notification options
2024
const val NOTIFICATION_OPTIONS_PREFS = prefix + "NOTIFICATION_OPTIONS"
2125
const val SERVICE_ID = "serviceId"
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package com.pravera.flutter_foreground_task.models
2+
3+
import android.content.Context
4+
import android.content.pm.ServiceInfo
5+
import android.os.Build
6+
import com.pravera.flutter_foreground_task.PreferencesKey
7+
8+
data class ForegroundServiceTypes(val value: Int) {
9+
companion object {
10+
fun getData(context: Context): ForegroundServiceTypes {
11+
val prefs = context.getSharedPreferences(
12+
PreferencesKey.FOREGROUND_SERVICE_TYPES_PREFS, Context.MODE_PRIVATE)
13+
14+
val value = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
15+
prefs.getInt(PreferencesKey.FOREGROUND_SERVICE_TYPES, ServiceInfo.FOREGROUND_SERVICE_TYPE_MANIFEST)
16+
} else {
17+
prefs.getInt(PreferencesKey.FOREGROUND_SERVICE_TYPES, 0) // none
18+
}
19+
20+
return ForegroundServiceTypes(value = value)
21+
}
22+
23+
fun setData(context: Context, map: Map<*, *>?) {
24+
val prefs = context.getSharedPreferences(
25+
PreferencesKey.FOREGROUND_SERVICE_TYPES_PREFS, Context.MODE_PRIVATE)
26+
27+
var value = 0
28+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
29+
val serviceTypes = map?.get(PreferencesKey.FOREGROUND_SERVICE_TYPES) as? List<*>
30+
if (serviceTypes != null) {
31+
for (serviceType in serviceTypes) {
32+
getForegroundServiceTypeFlag(serviceType)?.let {
33+
value = value or it
34+
}
35+
}
36+
}
37+
}
38+
39+
// not none type
40+
if (value > 0) {
41+
with(prefs.edit()) {
42+
putInt(PreferencesKey.FOREGROUND_SERVICE_TYPES, value)
43+
commit()
44+
}
45+
}
46+
}
47+
48+
fun clearData(context: Context) {
49+
val prefs = context.getSharedPreferences(
50+
PreferencesKey.FOREGROUND_SERVICE_TYPES_PREFS, Context.MODE_PRIVATE)
51+
52+
with(prefs.edit()) {
53+
clear()
54+
commit()
55+
}
56+
}
57+
58+
private fun getForegroundServiceTypeFlag(type: Any?): Int? {
59+
return when (type) {
60+
0 -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA else null
61+
1 -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) ServiceInfo.FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE else null
62+
2 -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC else null
63+
3 -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) ServiceInfo.FOREGROUND_SERVICE_TYPE_HEALTH else null
64+
4 -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION else null
65+
5 -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK else null
66+
6 -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION else null
67+
7 -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE else null
68+
8 -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) ServiceInfo.FOREGROUND_SERVICE_TYPE_PHONE_CALL else null
69+
9 -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) ServiceInfo.FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING else null
70+
10 -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) ServiceInfo.FOREGROUND_SERVICE_TYPE_SHORT_SERVICE else null
71+
11 -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE else null
72+
12 -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) ServiceInfo.FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED else null
73+
else -> null
74+
}
75+
}
76+
}
77+
}

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

Lines changed: 7 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import android.annotation.SuppressLint
44
import android.app.*
55
import android.content.*
66
import android.content.pm.PackageManager
7-
import android.content.pm.ServiceInfo
87
import android.graphics.Color
98
import android.net.wifi.WifiManager
109
import android.os.*
@@ -19,11 +18,9 @@ import com.pravera.flutter_foreground_task.FlutterForegroundTaskLifecycleListene
1918
import com.pravera.flutter_foreground_task.RequestCode
2019
import com.pravera.flutter_foreground_task.models.*
2120
import com.pravera.flutter_foreground_task.utils.ForegroundServiceUtils
22-
import kotlinx.coroutines.*
2321
import kotlinx.coroutines.flow.MutableStateFlow
2422
import kotlinx.coroutines.flow.asStateFlow
2523
import kotlinx.coroutines.flow.update
26-
import java.util.*
2724

2825
/**
2926
* A service class for implementing foreground service.
@@ -81,6 +78,7 @@ class ForegroundService : Service() {
8178
}
8279

8380
private lateinit var foregroundServiceStatus: ForegroundServiceStatus
81+
private lateinit var foregroundServiceTypes: ForegroundServiceTypes
8482
private lateinit var foregroundTaskOptions: ForegroundTaskOptions
8583
private lateinit var foregroundTaskData: ForegroundTaskData
8684
private lateinit var notificationOptions: NotificationOptions
@@ -228,25 +226,14 @@ class ForegroundService : Service() {
228226

229227
private fun loadDataFromPreferences() {
230228
foregroundServiceStatus = ForegroundServiceStatus.getData(applicationContext)
231-
232-
if (::foregroundTaskOptions.isInitialized) {
233-
prevForegroundTaskOptions = foregroundTaskOptions
234-
}
229+
foregroundServiceTypes = ForegroundServiceTypes.getData(applicationContext)
230+
if (::foregroundTaskOptions.isInitialized) { prevForegroundTaskOptions = foregroundTaskOptions }
235231
foregroundTaskOptions = ForegroundTaskOptions.getData(applicationContext)
236-
237-
if (::foregroundTaskData.isInitialized) {
238-
prevForegroundTaskData = foregroundTaskData
239-
}
232+
if (::foregroundTaskData.isInitialized) { prevForegroundTaskData = foregroundTaskData }
240233
foregroundTaskData = ForegroundTaskData.getData(applicationContext)
241-
242-
if (::notificationOptions.isInitialized) {
243-
prevNotificationOptions = notificationOptions
244-
}
234+
if (::notificationOptions.isInitialized) { prevNotificationOptions = notificationOptions }
245235
notificationOptions = NotificationOptions.getData(applicationContext)
246-
247-
if (::notificationContent.isInitialized) {
248-
prevNotificationContent = notificationContent
249-
}
236+
if (::notificationContent.isInitialized) { prevNotificationContent = notificationContent }
250237
notificationContent = NotificationContent.getData(applicationContext)
251238
}
252239

@@ -278,11 +265,7 @@ class ForegroundService : Service() {
278265
val serviceId = notificationOptions.serviceId
279266
val notification = createNotification()
280267
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
281-
startForeground(
282-
serviceId,
283-
notification,
284-
ServiceInfo.FOREGROUND_SERVICE_TYPE_MANIFEST
285-
)
268+
startForeground(serviceId, notification, foregroundServiceTypes.value)
286269
} else {
287270
startForeground(serviceId, notification)
288271
}

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import com.pravera.flutter_foreground_task.errors.ServiceAlreadyStartedException
77
import com.pravera.flutter_foreground_task.errors.ServiceNotStartedException
88
import com.pravera.flutter_foreground_task.models.ForegroundServiceAction
99
import com.pravera.flutter_foreground_task.models.ForegroundServiceStatus
10+
import com.pravera.flutter_foreground_task.models.ForegroundServiceTypes
1011
import com.pravera.flutter_foreground_task.models.ForegroundTaskData
1112
import com.pravera.flutter_foreground_task.models.ForegroundTaskOptions
1213
import com.pravera.flutter_foreground_task.models.NotificationContent
@@ -28,6 +29,7 @@ class ForegroundServiceManager {
2829
val nIntent = Intent(context, ForegroundService::class.java)
2930
val argsMap = arguments as? Map<*, *>
3031
ForegroundServiceStatus.setData(context, ForegroundServiceAction.API_START)
32+
ForegroundServiceTypes.setData(context, argsMap)
3133
NotificationOptions.setData(context, argsMap)
3234
ForegroundTaskOptions.setData(context, argsMap)
3335
ForegroundTaskData.setData(context, argsMap)
@@ -69,6 +71,7 @@ class ForegroundServiceManager {
6971

7072
val nIntent = Intent(context, ForegroundService::class.java)
7173
ForegroundServiceStatus.setData(context, ForegroundServiceAction.API_STOP)
74+
ForegroundServiceTypes.clearData(context)
7275
NotificationOptions.clearData(context)
7376
ForegroundTaskOptions.clearData(context)
7477
ForegroundTaskData.clearData(context)

lib/flutter_foreground_task.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import 'errors/service_already_started_exception.dart';
1010
import 'errors/service_not_initialized_exception.dart';
1111
import 'errors/service_not_started_exception.dart';
1212
import 'errors/service_timeout_exception.dart';
13+
import 'models/foreground_service_types.dart';
1314
import 'models/foreground_task_options.dart';
1415
import 'models/notification_button.dart';
1516
import 'models/notification_icon.dart';
@@ -23,6 +24,7 @@ export 'errors/service_already_started_exception.dart';
2324
export 'errors/service_not_initialized_exception.dart';
2425
export 'errors/service_not_started_exception.dart';
2526
export 'errors/service_timeout_exception.dart';
27+
export 'models/foreground_service_types.dart';
2628
export 'models/foreground_task_event_action.dart';
2729
export 'models/foreground_task_options.dart';
2830
export 'models/notification_button.dart';
@@ -96,6 +98,7 @@ class FlutterForegroundTask {
9698
/// Start the foreground service.
9799
static Future<ServiceRequestResult> startService({
98100
int? serviceId,
101+
List<ForegroundServiceTypes>? serviceTypes,
99102
required String notificationTitle,
100103
required String notificationText,
101104
NotificationIcon? notificationIcon,
@@ -117,6 +120,7 @@ class FlutterForegroundTask {
117120
iosNotificationOptions: iosNotificationOptions!,
118121
foregroundTaskOptions: foregroundTaskOptions!,
119122
serviceId: serviceId,
123+
serviceTypes: serviceTypes,
120124
notificationTitle: notificationTitle,
121125
notificationText: notificationText,
122126
notificationIcon: notificationIcon,

lib/flutter_foreground_task_method_channel.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import 'package:flutter/widgets.dart';
88
import 'package:platform/platform.dart';
99

1010
import 'flutter_foreground_task_platform_interface.dart';
11+
import 'models/foreground_service_types.dart';
1112
import 'models/foreground_task_options.dart';
1213
import 'models/notification_button.dart';
1314
import 'models/notification_icon.dart';
@@ -37,6 +38,7 @@ class MethodChannelFlutterForegroundTask extends FlutterForegroundTaskPlatform {
3738
required IOSNotificationOptions iosNotificationOptions,
3839
required ForegroundTaskOptions foregroundTaskOptions,
3940
int? serviceId,
41+
List<ForegroundServiceTypes>? serviceTypes,
4042
required String notificationTitle,
4143
required String notificationText,
4244
NotificationIcon? notificationIcon,
@@ -46,6 +48,7 @@ class MethodChannelFlutterForegroundTask extends FlutterForegroundTaskPlatform {
4648
}) async {
4749
final Map<String, dynamic> optionsJson = ServiceStartOptions(
4850
serviceId: serviceId,
51+
serviceTypes: serviceTypes,
4952
androidNotificationOptions: androidNotificationOptions,
5053
iosNotificationOptions: iosNotificationOptions,
5154
foregroundTaskOptions: foregroundTaskOptions,

lib/flutter_foreground_task_platform_interface.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
22

33
import 'flutter_foreground_task_method_channel.dart';
4+
import 'models/foreground_service_types.dart';
45
import 'models/foreground_task_options.dart';
56
import 'models/notification_button.dart';
67
import 'models/notification_icon.dart';
@@ -37,6 +38,7 @@ abstract class FlutterForegroundTaskPlatform extends PlatformInterface {
3738
required IOSNotificationOptions iosNotificationOptions,
3839
required ForegroundTaskOptions foregroundTaskOptions,
3940
int? serviceId,
41+
List<ForegroundServiceTypes>? serviceTypes,
4042
required String notificationTitle,
4143
required String notificationText,
4244
NotificationIcon? notificationIcon,
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/// https://developer.android.com/about/versions/14/changes/fgs-types-required#system-exempted
2+
class ForegroundServiceTypes {
3+
/// Constructs an instance of [ForegroundServiceTypes].
4+
const ForegroundServiceTypes(this.rawValue);
5+
6+
/// Continue to access the camera from the background, such as video chat apps that allow for multitasking.
7+
static const camera = ForegroundServiceTypes(0);
8+
9+
/// Interactions with external devices that require a Bluetooth, NFC, IR, USB, or network connection.
10+
static const connectedDevice = ForegroundServiceTypes(1);
11+
12+
/// Data transfer operations, such as the following:
13+
///
14+
/// * Data upload or download
15+
/// * Backup-and-restore operations
16+
/// * Import or export operations
17+
/// * Fetch data
18+
/// * Local file processing
19+
/// * Transfer data between a device and the cloud over a network
20+
static const dataSync = ForegroundServiceTypes(2);
21+
22+
/// Any long-running use cases to support apps in the fitness category such as exercise trackers.
23+
static const health = ForegroundServiceTypes(3);
24+
25+
/// Long-running use cases that require location access, such as navigation and location sharing.
26+
static const location = ForegroundServiceTypes(4);
27+
28+
/// Continue audio or video playback from the background. Support Digital Video Recording (DVR) functionality on Android TV.
29+
static const mediaPlayback = ForegroundServiceTypes(5);
30+
31+
/// Project content to non-primary display or external device using the MediaProjection APIs. This content doesn't have to be exclusively media content.
32+
static const mediaProjection = ForegroundServiceTypes(6);
33+
34+
/// Continue microphone capture from the background, such as voice recorders or communication apps.
35+
static const microphone = ForegroundServiceTypes(7);
36+
37+
/// Continue an ongoing call using the ConnectionService APIs.
38+
static const phoneCall = ForegroundServiceTypes(8);
39+
40+
/// Transfer text messages from one device to another. Assists with continuity of a user's messaging tasks when they switch devices.
41+
static const remoteMessaging = ForegroundServiceTypes(9);
42+
43+
/// Quickly finish critical work that cannot be interrupted or postponed.
44+
static const shortService = ForegroundServiceTypes(10);
45+
46+
/// Covers any valid foreground service use cases that aren't covered by the other foreground service types.
47+
static const specialUse = ForegroundServiceTypes(11);
48+
49+
/// Reserved for system applications and specific system integrations, to continue to use foreground services.
50+
static const systemExempted = ForegroundServiceTypes(12);
51+
52+
/// The raw value of [ForegroundServiceTypes].
53+
final int rawValue;
54+
}

lib/models/service_options.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import 'dart:ui';
22

33
import 'package:platform/platform.dart';
44

5+
import 'foreground_service_types.dart';
56
import 'foreground_task_options.dart';
67
import 'notification_button.dart';
78
import 'notification_icon.dart';
@@ -10,6 +11,7 @@ import 'notification_options.dart';
1011
class ServiceStartOptions {
1112
const ServiceStartOptions({
1213
this.serviceId,
14+
this.serviceTypes,
1315
required this.androidNotificationOptions,
1416
required this.iosNotificationOptions,
1517
required this.foregroundTaskOptions,
@@ -22,6 +24,7 @@ class ServiceStartOptions {
2224
});
2325

2426
final int? serviceId;
27+
final List<ForegroundServiceTypes>? serviceTypes;
2528
final AndroidNotificationOptions androidNotificationOptions;
2629
final IOSNotificationOptions iosNotificationOptions;
2730
final ForegroundTaskOptions foregroundTaskOptions;
@@ -35,6 +38,7 @@ class ServiceStartOptions {
3538
Map<String, dynamic> toJson(Platform platform) {
3639
final Map<String, dynamic> json = {
3740
'serviceId': serviceId,
41+
'serviceTypes': serviceTypes?.map((e) => e.rawValue).toList(),
3842
...foregroundTaskOptions.toJson(),
3943
'notificationContentTitle': notificationContentTitle,
4044
'notificationContentText': notificationContentText,

0 commit comments

Comments
 (0)