rn-bg-actions 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2020 Rapsssito
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,211 @@
1
+ <p align="center">
2
+ <img src="https://i.imgur.com/G8BUzdZ.png" />
3
+ <br></br>
4
+ <img src="https://github.com/Rapsssito/react-native-background-actions/workflows/Release/badge.svg" />
5
+ <img src="https://img.shields.io/npm/dw/react-native-background-actions" />
6
+ <img src="https://img.shields.io/npm/v/react-native-background-actions?color=gr&label=npm%20version" />
7
+ </p>
8
+
9
+ React Native background service library for running **background tasks forever in Android & iOS**. Schedule a background job that will run your JavaScript when your app is in the background or foreground.
10
+
11
+ ### WARNING
12
+ - **Android**: This library relies on React Native's [`HeadlessJS`](https://facebook.github.io/react-native/docs/headless-js-android) for Android. Before building your JS task, make sure to read all the [documentation](https://facebook.github.io/react-native/docs/headless-js-android). The jobs will run even if the app has been closed. [In Android 12+](https://developer.android.com/guide/components/foreground-services#background-start-restrictions) you will not be able to launch background tasks from the background. A notification will be shown when the task is running, it is not possible to start the service without it. The notification will only be visible in Android.
13
+
14
+ - **iOS**: This library relies on iOS's [`UIApplication beginBackgroundTaskWithName` method](https://developer.apple.com/documentation/uikit/uiapplication/1623051-beginbackgroundtaskwithname?language=objc), which **won't keep your app in the background forever** by itself. However, you can rely on other libraries like [`react-native-track-player`](https://github.com/react-native-kit/react-native-track-player) that use audio, geolocalization, etc. to keep your app alive in the background while you excute the JS from this library.
15
+
16
+
17
+ ## Table of Contents <!-- omit in toc -->
18
+
19
+ - [React Native / Android / iOS compatibility](#react-native--android--ios-compatibility)
20
+ - [Install](#install)
21
+ - [Usage](#usage)
22
+ - [Example Code](#example-code)
23
+ - [Options](#options)
24
+ - [taskIconOptions](#taskiconoptions)
25
+ - [taskProgressBarOptions](#taskprogressbaroptions)
26
+ - [Deep Linking](#deep-linking)
27
+ - [Events](#events)
28
+ - ['expiration'](#expiration)
29
+ - [Maintainers](#maintainers)
30
+ - [Acknowledgments](#acknowledgments)
31
+ - [License](#license)
32
+
33
+ ## React Native / Android / iOS compatibility
34
+ To use this module you need to ensure you are using the correct version of React Native. If you are using an Android (targetSdkVersion) version lower than 31 (introduced in React Native 0.68.0) you will need to upgrade before attempting to use `react-native-background-actions`'s latest version.
35
+
36
+ | Version | React Native version | Android (targetSdkVersion) version | iOS version |
37
+ | ------- | -------------------- | ---------------------------------- | ------------ |
38
+ | `4.X.X` | `>= Unknown` | `>= 34` | `>= Unknown` |
39
+ | `3.X.X` | `>= Unknown` | `>= 31` | `>= Unknown` |
40
+ | `2.6.7` | `>= Unknown` | `>= Unknown` | `>= Unknown` |
41
+
42
+ ## Install
43
+
44
+ Go to [INSTALL.md](./INSTALL.md) to see the how to install, compatibility with RN and Linking process.
45
+
46
+ ## Usage
47
+
48
+ ### Example Code
49
+
50
+ ```js
51
+ import BackgroundService from 'react-native-background-actions';
52
+
53
+ const sleep = (time) => new Promise((resolve) => setTimeout(() => resolve(), time));
54
+
55
+ // You can do anything in your task such as network requests, timers and so on,
56
+ // as long as it doesn't touch UI. Once your task completes (i.e. the promise is resolved),
57
+ // React Native will go into "paused" mode (unless there are other tasks running,
58
+ // or there is a foreground app).
59
+ const veryIntensiveTask = async (taskDataArguments) => {
60
+ // Example of an infinite loop task
61
+ const { delay } = taskDataArguments;
62
+ await new Promise( async (resolve) => {
63
+ for (let i = 0; BackgroundService.isRunning(); i++) {
64
+ console.log(i);
65
+ await sleep(delay);
66
+ }
67
+ });
68
+ };
69
+
70
+ const options = {
71
+ taskName: 'Example',
72
+ taskTitle: 'ExampleTask title',
73
+ taskDesc: 'ExampleTask description',
74
+ taskIcon: {
75
+ name: 'ic_launcher',
76
+ type: 'mipmap',
77
+ },
78
+ color: '#ff00ff',
79
+ linkingURI: 'yourSchemeHere://chat/jane', // See Deep Linking for more info
80
+ parameters: {
81
+ delay: 1000,
82
+ },
83
+ };
84
+
85
+
86
+ await BackgroundService.start(veryIntensiveTask, options);
87
+ await BackgroundService.updateNotification({taskDesc: 'New ExampleTask description'}); // Only Android, iOS will ignore this call
88
+ // iOS will also run everything here in the background until .stop() is called
89
+ await BackgroundService.stop();
90
+ ```
91
+ > If you call stop() on background no new tasks will be able to be started!
92
+ > Don't call .start() twice, as it will stop performing previous background tasks and start a new one.
93
+ > If .start() is called on the backgound, it will not have any effect.
94
+
95
+ ### Options
96
+ | Property | Type | Description |
97
+ | ------------- | ----------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
98
+ | `taskName` | `<string>` | Task name for identification. |
99
+ | `taskTitle` | `<string>` | **Android Required**. Notification title. |
100
+ | `taskDesc` | `<string>` | **Android Required**. Notification description. |
101
+ | `taskIcon` | [`<taskIconOptions>`](#taskIconOptions) | **Android Required**. Notification icon. |
102
+ | `color` | `<string>` | Notification color. **Default**: `"#ffffff"`. |
103
+ | `linkingURI` | `<string>` | Link that will be called when the notification is clicked. Example: `"yourSchemeHere://chat/jane"`. See [Deep Linking](#deep-linking) for more info. **Default**: `undefined`. |
104
+ | `progressBar` | [`<taskProgressBarOptions>`](#taskProgressBarOptions) | Notification progress bar. |
105
+ | `parameters` | `<any>` | Parameters to pass to the task. |
106
+
107
+ #### taskIconOptions
108
+ **Android only**
109
+ | Property | Type | Description |
110
+ | --------- | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
111
+ | `name` | `<string>` | **Required**. Icon name in res/ folder. Ex: `ic_launcher`. |
112
+ | `type` | `<string>` | **Required**. Icon type in res/ folder. Ex: `mipmap`. |
113
+ | `package` | `<string>` | Icon package where to search the icon. Ex: `com.example.package`. **It defaults to the app's package. It is highly recommended to leave like that.** |
114
+
115
+ Example:
116
+
117
+ ![photo5837026843969041365](https://user-images.githubusercontent.com/44206249/72532521-de49e280-3873-11ea-8bf6-00618bcb82ab.jpg)
118
+
119
+ #### taskProgressBarOptions
120
+ **Android only**
121
+ | Property | Type | Description |
122
+ | --------------- | ----------- | --------------------------------------------- |
123
+ | `max` | `<number>` | **Required**. Maximum value. |
124
+ | `value` | `<number>` | **Required**. Current value. |
125
+ | `indeterminate` | `<boolean>` | Display the progress status as indeterminate. |
126
+
127
+ Example:
128
+
129
+ ![ProgressBar](https://developer.android.com/images/ui/notifications/notification-progressbar_2x.png)
130
+
131
+ ### Deep Linking
132
+ **Android only**
133
+
134
+ To handle incoming links when the notification is clicked by the user, first you need to modify your **`android/app/src/main/AndroidManifest.xml`** and add an `<intent-filter>` (fill `yourSchemeHere` with the name you prefer):
135
+ ```xml
136
+ <manifest ... >
137
+ ...
138
+ <application ... >
139
+ <activity
140
+ ...
141
+ android:launchMode="singleTask"> // Add this if not present
142
+ ...
143
+ <intent-filter android:label="filter_react_native">
144
+ <action android:name="android.intent.action.VIEW" />
145
+ <category android:name="android.intent.category.DEFAULT" />
146
+ <category android:name="android.intent.category.BROWSABLE" />
147
+ <data android:scheme="yourSchemeHere" />
148
+ </intent-filter>
149
+ </application>
150
+ </manifest>
151
+ ```
152
+
153
+ You must provide a `linkingURI` in the BackgroundService's [options](#options) that matches the scheme you just added to **`android/app/src/main/AndroidManifest.xml`**:
154
+ ```js
155
+ const options = {
156
+ taskName: 'Example',
157
+ taskTitle: 'ExampleTask title',
158
+ taskDesc: 'ExampleTask description',
159
+ taskIcon: {
160
+ name: 'ic_launcher',
161
+ type: 'mipmap',
162
+ },
163
+ color: '#ff00ff',
164
+ linkingURI: 'yourSchemeHere://chat/jane', // Add this
165
+ parameters: {
166
+ delay: 1000,
167
+ },
168
+ };
169
+
170
+
171
+ await BackgroundService.start(veryIntensiveTask, options);
172
+ ```
173
+
174
+ React Native provides a `Linking` class to get notified of incoming links. Your JavaScript code must then listen to the url using React Native `Linking` class:
175
+ ```js
176
+ import { Linking } from 'react-native';
177
+
178
+ Linking.addEventListener('url', handleOpenURL);
179
+
180
+ function handleOpenURL(evt) {
181
+ // Will be called when the notification is pressed
182
+ console.log(evt.url);
183
+ // do something
184
+ }
185
+ ```
186
+
187
+ ### Events
188
+ #### 'expiration'
189
+ **iOS only**
190
+ Listen for the iOS-only expiration handler that allows you to 'clean up' shortly before the app’s remaining background time reaches 0. Check the iOS [documentation](https://developer.apple.com/documentation/uikit/uiapplication/1623031-beginbackgroundtask) for more info.
191
+
192
+ ```js
193
+ BackgroundService.on('expiration', () => {
194
+ console.log('I am being closed :(');
195
+ });
196
+
197
+ await BackgroundService.start(veryIntensiveTask, options);
198
+
199
+ ```
200
+
201
+ ## Maintainers
202
+
203
+ * [Rapsssito](https://github.com/rapsssito) [[Support me :heart:](https://github.com/sponsors/Rapsssito)]
204
+
205
+ ## Acknowledgments
206
+
207
+ * iOS part originally forked from [react-native-background-timer](https://github.com/ocetnik/react-native-background-timer)
208
+
209
+ ## License
210
+
211
+ The library is released under the MIT license. For more information see [`LICENSE`](/LICENSE).
@@ -0,0 +1,24 @@
1
+ apply plugin: 'com.android.library'
2
+
3
+ def safeExtGet(prop, fallback) {
4
+ rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
5
+ }
6
+
7
+ android {
8
+ compileSdkVersion safeExtGet('compileSdkVersion', 34)
9
+
10
+ defaultConfig {
11
+ minSdkVersion safeExtGet('minSdkVersion', 16)
12
+ targetSdkVersion safeExtGet('targetSdkVersion', 34)
13
+ }
14
+
15
+ // Conditional for compatibility with AGP <4.2.
16
+ if (project.android.hasProperty("namespace")) {
17
+ namespace = "com.asterinet.react.bgactions"
18
+ }
19
+ }
20
+
21
+ dependencies {
22
+ //noinspection GradleDynamicVersion
23
+ implementation 'com.facebook.react:react-native:+' // From node_modules
24
+ }
@@ -0,0 +1,6 @@
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.asterinet.react.bgactions">
2
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
3
+ <application>
4
+ <service android:name=".RNBackgroundActionsTask"/>
5
+ </application>
6
+ </manifest>
@@ -0,0 +1,91 @@
1
+ package com.asterinet.react.bgactions;
2
+
3
+ import android.app.Notification;
4
+ import android.app.NotificationManager;
5
+ import android.content.Context;
6
+ import android.content.Intent;
7
+
8
+ import androidx.annotation.NonNull;
9
+
10
+ import com.facebook.react.bridge.Promise;
11
+ import com.facebook.react.bridge.ReactApplicationContext;
12
+ import com.facebook.react.bridge.ReactContext;
13
+ import com.facebook.react.bridge.ReactContextBaseJavaModule;
14
+ import com.facebook.react.bridge.ReactMethod;
15
+ import com.facebook.react.bridge.ReadableMap;
16
+
17
+ @SuppressWarnings("WeakerAccess")
18
+ public class BackgroundActionsModule extends ReactContextBaseJavaModule {
19
+
20
+ private static final String TAG = "RNBackgroundActions";
21
+
22
+ private final ReactContext reactContext;
23
+
24
+ private Intent currentServiceIntent;
25
+
26
+ public BackgroundActionsModule(ReactApplicationContext reactContext) {
27
+ super(reactContext);
28
+ this.reactContext = reactContext;
29
+ }
30
+
31
+ @NonNull
32
+ @Override
33
+ public String getName() {
34
+ return TAG;
35
+ }
36
+
37
+ @SuppressWarnings("unused")
38
+ @ReactMethod
39
+ public void start(@NonNull final ReadableMap options, @NonNull final Promise promise) {
40
+ try {
41
+ // Stop any other intent
42
+ if (currentServiceIntent != null) reactContext.stopService(currentServiceIntent);
43
+ // Create the service
44
+ currentServiceIntent = new Intent(reactContext, RNBackgroundActionsTask.class);
45
+ // Get the task info from the options
46
+ final BackgroundTaskOptions bgOptions = new BackgroundTaskOptions(reactContext, options);
47
+ currentServiceIntent.putExtras(bgOptions.getExtras());
48
+ // Start the task
49
+ reactContext.startService(currentServiceIntent);
50
+ promise.resolve(null);
51
+ } catch (Exception e) {
52
+ promise.reject(e);
53
+ }
54
+ }
55
+
56
+ @SuppressWarnings("unused")
57
+ @ReactMethod
58
+ public void stop(@NonNull final Promise promise) {
59
+ if (currentServiceIntent != null)
60
+ reactContext.stopService(currentServiceIntent);
61
+ promise.resolve(null);
62
+ }
63
+
64
+ @SuppressWarnings("unused")
65
+ @ReactMethod
66
+ public void updateNotification(@NonNull final ReadableMap options, @NonNull final Promise promise) {
67
+ // Get the task info from the options
68
+ try {
69
+ final BackgroundTaskOptions bgOptions = new BackgroundTaskOptions(reactContext, options);
70
+ final Notification notification = RNBackgroundActionsTask.buildNotification(reactContext, bgOptions);
71
+ final NotificationManager notificationManager = (NotificationManager) reactContext.getSystemService(Context.NOTIFICATION_SERVICE);
72
+ notificationManager.notify(RNBackgroundActionsTask.SERVICE_NOTIFICATION_ID, notification);
73
+ } catch (Exception e) {
74
+ promise.reject(e);
75
+ return;
76
+ }
77
+ promise.resolve(null);
78
+ }
79
+
80
+ @SuppressWarnings("unused")
81
+ @ReactMethod
82
+ public void addListener(String eventName) {
83
+ // Keep: Required for RN built in Event Emitter Calls.
84
+ }
85
+
86
+ @SuppressWarnings("unused")
87
+ @ReactMethod
88
+ public void removeListeners(Integer count) {
89
+ // Keep: Required for RN built in Event Emitter Calls.
90
+ }
91
+ }
@@ -0,0 +1,26 @@
1
+ package com.asterinet.react.bgactions;
2
+
3
+ import java.util.Arrays;
4
+ import java.util.Collections;
5
+ import java.util.List;
6
+
7
+ import com.facebook.react.ReactPackage;
8
+ import com.facebook.react.bridge.NativeModule;
9
+ import com.facebook.react.bridge.ReactApplicationContext;
10
+ import com.facebook.react.uimanager.ViewManager;
11
+
12
+ import androidx.annotation.NonNull;
13
+
14
+ @SuppressWarnings("unused")
15
+ public class BackgroundActionsPackage implements ReactPackage {
16
+ @Override
17
+ public @NonNull List<NativeModule> createNativeModules(@NonNull ReactApplicationContext reactContext) {
18
+ //noinspection ArraysAsListWithZeroOrOneArgument
19
+ return Arrays.<NativeModule>asList(new BackgroundActionsModule(reactContext));
20
+ }
21
+
22
+ @Override
23
+ public @NonNull List<ViewManager> createViewManagers(@NonNull ReactApplicationContext reactContext) {
24
+ return Collections.emptyList();
25
+ }
26
+ }
@@ -0,0 +1,104 @@
1
+ package com.asterinet.react.bgactions;
2
+
3
+ import android.graphics.Color;
4
+ import android.os.Bundle;
5
+
6
+ import androidx.annotation.ColorInt;
7
+ import androidx.annotation.IdRes;
8
+ import androidx.annotation.NonNull;
9
+ import androidx.annotation.Nullable;
10
+
11
+ import com.facebook.react.bridge.Arguments;
12
+ import com.facebook.react.bridge.ReactContext;
13
+ import com.facebook.react.bridge.ReadableMap;
14
+
15
+ public final class BackgroundTaskOptions {
16
+ private final Bundle extras;
17
+
18
+ public BackgroundTaskOptions(@NonNull final Bundle extras) {
19
+ this.extras = extras;
20
+ }
21
+
22
+ public BackgroundTaskOptions(@NonNull final ReactContext reactContext, @NonNull final ReadableMap options) {
23
+ // Create extras
24
+ extras = Arguments.toBundle(options);
25
+ if (extras == null)
26
+ throw new IllegalArgumentException("Could not convert arguments to bundle");
27
+ // Get taskTitle
28
+ try {
29
+ if (options.getString("taskTitle") == null)
30
+ throw new IllegalArgumentException();
31
+ } catch (Exception e) {
32
+ throw new IllegalArgumentException("Task title cannot be null");
33
+ }
34
+ // Get taskDesc
35
+ try {
36
+ if (options.getString("taskDesc") == null)
37
+ throw new IllegalArgumentException();
38
+ } catch (Exception e) {
39
+ throw new IllegalArgumentException("Task description cannot be null");
40
+ }
41
+ // Get iconInt
42
+ try {
43
+ final ReadableMap iconMap = options.getMap("taskIcon");
44
+ if (iconMap == null)
45
+ throw new IllegalArgumentException();
46
+ final String iconName = iconMap.getString("name");
47
+ final String iconType = iconMap.getString("type");
48
+ String iconPackage;
49
+ try {
50
+ iconPackage = iconMap.getString("package");
51
+ if (iconPackage == null)
52
+ throw new IllegalArgumentException();
53
+ } catch (Exception e) {
54
+ // Get the current package as default
55
+ iconPackage = reactContext.getPackageName();
56
+ }
57
+ final int iconInt = reactContext.getResources().getIdentifier(iconName, iconType, iconPackage);
58
+ extras.putInt("iconInt", iconInt);
59
+ if (iconInt == 0)
60
+ throw new IllegalArgumentException();
61
+ } catch (Exception e) {
62
+ throw new IllegalArgumentException("Task icon not found");
63
+ }
64
+ // Get color
65
+ try {
66
+ final String color = options.getString("color");
67
+ extras.putInt("color", Color.parseColor(color));
68
+ } catch (Exception e) {
69
+ extras.putInt("color", Color.parseColor("#ffffff"));
70
+ }
71
+ }
72
+
73
+ public Bundle getExtras() {
74
+ return extras;
75
+ }
76
+
77
+ public String getTaskTitle() {
78
+ return extras.getString("taskTitle", "");
79
+ }
80
+
81
+ public String getTaskDesc() {
82
+ return extras.getString("taskDesc", "");
83
+ }
84
+
85
+ @IdRes
86
+ public int getIconInt() {
87
+ return extras.getInt("iconInt");
88
+ }
89
+
90
+ @ColorInt
91
+ public int getColor() {
92
+ return extras.getInt("color");
93
+ }
94
+
95
+ @Nullable
96
+ public String getLinkingURI() {
97
+ return extras.getString("linkingURI");
98
+ }
99
+
100
+ @Nullable
101
+ public Bundle getProgressBar() {
102
+ return extras.getBundle("progressBar");
103
+ }
104
+ }
@@ -0,0 +1,120 @@
1
+ package com.asterinet.react.bgactions;
2
+
3
+ import android.annotation.SuppressLint;
4
+ import android.app.Notification;
5
+ import android.app.NotificationChannel;
6
+ import android.app.NotificationManager;
7
+ import android.app.PendingIntent;
8
+ import android.content.Context;
9
+ import android.content.Intent;
10
+ import android.content.pm.PackageManager;
11
+ import android.content.pm.ServiceInfo;
12
+ import android.net.Uri;
13
+ import android.os.Build;
14
+ import android.os.Bundle;
15
+ import android.Manifest;
16
+
17
+ import androidx.annotation.NonNull;
18
+ import androidx.annotation.Nullable;
19
+ import androidx.core.app.NotificationCompat;
20
+ import androidx.core.content.ContextCompat;
21
+
22
+ import com.facebook.react.HeadlessJsTaskService;
23
+ import com.facebook.react.bridge.Arguments;
24
+ import com.facebook.react.jstasks.HeadlessJsTaskConfig;
25
+
26
+ final public class RNBackgroundActionsTask extends HeadlessJsTaskService {
27
+
28
+ public static final int SERVICE_NOTIFICATION_ID = 92901;
29
+ private static final String CHANNEL_ID = "RN_BACKGROUND_ACTIONS_CHANNEL";
30
+
31
+ @SuppressLint("UnspecifiedImmutableFlag")
32
+ @NonNull
33
+ public static Notification buildNotification(@NonNull Context context, @NonNull final BackgroundTaskOptions bgOptions) {
34
+ // Get info
35
+ final String taskTitle = bgOptions.getTaskTitle();
36
+ final String taskDesc = bgOptions.getTaskDesc();
37
+ final int iconInt = bgOptions.getIconInt();
38
+ final int color = bgOptions.getColor();
39
+ final String linkingURI = bgOptions.getLinkingURI();
40
+ Intent notificationIntent;
41
+ if (linkingURI != null) {
42
+ notificationIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(linkingURI));
43
+ } else {
44
+ //as RN works on single activity architecture - we don't need to find current activity on behalf of react context
45
+ notificationIntent = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_LAUNCHER);
46
+ }
47
+ final PendingIntent contentIntent;
48
+ if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
49
+ contentIntent = PendingIntent.getActivity(context,0, notificationIntent, PendingIntent.FLAG_MUTABLE | PendingIntent.FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT);
50
+ } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
51
+ contentIntent = PendingIntent.getActivity(context, 0, notificationIntent, PendingIntent.FLAG_MUTABLE);
52
+ } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
53
+ contentIntent = PendingIntent.getActivity(context, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
54
+ } else {
55
+ contentIntent = PendingIntent.getActivity(context, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
56
+ }
57
+ final NotificationCompat.Builder builder = new NotificationCompat.Builder(context, CHANNEL_ID)
58
+ .setContentTitle(taskTitle)
59
+ .setContentText(taskDesc)
60
+ .setSmallIcon(iconInt)
61
+ .setContentIntent(contentIntent)
62
+ .setOngoing(true)
63
+ .setPriority(NotificationCompat.PRIORITY_MIN)
64
+ .setColor(color);
65
+
66
+ final Bundle progressBarBundle = bgOptions.getProgressBar();
67
+ if (progressBarBundle != null) {
68
+ final int progressMax = (int) Math.floor(progressBarBundle.getDouble("max"));
69
+ final int progressCurrent = (int) Math.floor(progressBarBundle.getDouble("value"));
70
+ final boolean progressIndeterminate = progressBarBundle.getBoolean("indeterminate");
71
+ builder.setProgress(progressMax, progressCurrent, progressIndeterminate);
72
+ }
73
+ return builder.build();
74
+ }
75
+
76
+ @Override
77
+ protected @Nullable
78
+ HeadlessJsTaskConfig getTaskConfig(Intent intent) {
79
+ final Bundle extras = intent.getExtras();
80
+ if (extras != null) {
81
+ return new HeadlessJsTaskConfig(extras.getString("taskName"), Arguments.fromBundle(extras), 0, true);
82
+ }
83
+ return null;
84
+ }
85
+
86
+ @Override
87
+ public int onStartCommand(Intent intent, int flags, int startId) {
88
+ final Bundle extras = intent.getExtras();
89
+ if (extras == null) {
90
+ throw new IllegalArgumentException("Extras cannot be null");
91
+ }
92
+ final BackgroundTaskOptions bgOptions = new BackgroundTaskOptions(extras);
93
+ createNotificationChannel(bgOptions.getTaskTitle(), bgOptions.getTaskDesc()); // Necessary creating channel for API 26+
94
+ // Create the notification
95
+ final Notification notification = buildNotification(this, bgOptions);
96
+
97
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
98
+ startForeground(SERVICE_NOTIFICATION_ID, notification);
99
+ } else {
100
+ if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED &&
101
+ ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED
102
+ ) {
103
+ startForeground(SERVICE_NOTIFICATION_ID, notification,
104
+ ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION);
105
+ }
106
+ }
107
+
108
+ return super.onStartCommand(intent, flags, startId);
109
+ }
110
+
111
+ private void createNotificationChannel(@NonNull final String taskTitle, @NonNull final String taskDesc) {
112
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
113
+ final int importance = NotificationManager.IMPORTANCE_LOW;
114
+ final NotificationChannel channel = new NotificationChannel(CHANNEL_ID, taskTitle, importance);
115
+ channel.setDescription(taskDesc);
116
+ final NotificationManager notificationManager = getSystemService(NotificationManager.class);
117
+ notificationManager.createNotificationChannel(channel);
118
+ }
119
+ }
120
+ }
@@ -0,0 +1,6 @@
1
+ #import <React/RCTBridgeModule.h>
2
+ #import <React/RCTEventEmitter.h>
3
+
4
+ @interface RNBackgroundActions : RCTEventEmitter<RCTBridgeModule>
5
+
6
+ @end
@@ -0,0 +1,53 @@
1
+ @import UIKit;
2
+ #import "RNBackgroundActions.h"
3
+
4
+ @implementation RNBackgroundActions {
5
+ UIBackgroundTaskIdentifier bgTask;
6
+ }
7
+
8
+ RCT_EXPORT_MODULE()
9
+ - (NSArray<NSString *> *)supportedEvents
10
+ {
11
+ return @[@"expiration"];
12
+ }
13
+
14
+ - (void) _start
15
+ {
16
+ [self _stop];
17
+ bgTask = [[UIApplication sharedApplication] beginBackgroundTaskWithName:@"RNBackgroundActions" expirationHandler:^{
18
+ [self onExpiration];
19
+ [[UIApplication sharedApplication] endBackgroundTask: self->bgTask];
20
+ self->bgTask = UIBackgroundTaskInvalid;
21
+ }];
22
+ }
23
+
24
+ - (void) _stop
25
+ {
26
+ if (bgTask != UIBackgroundTaskInvalid) {
27
+ [[UIApplication sharedApplication] endBackgroundTask:bgTask];
28
+ bgTask = UIBackgroundTaskInvalid;
29
+ }
30
+ }
31
+
32
+ - (void)onExpiration
33
+ {
34
+ [self sendEventWithName:@"expiration"
35
+ body:@{}];
36
+ }
37
+
38
+ RCT_EXPORT_METHOD(start:(NSDictionary *)options
39
+ resolver:(RCTPromiseResolveBlock)resolve
40
+ rejecter:(RCTPromiseRejectBlock)reject)
41
+ {
42
+ [self _start];
43
+ resolve(nil);
44
+ }
45
+
46
+ RCT_EXPORT_METHOD(stop:(RCTPromiseResolveBlock)resolve
47
+ rejecter:(RCTPromiseRejectBlock)reject)
48
+ {
49
+ [self _stop];
50
+ resolve(nil);
51
+ }
52
+
53
+ @end