@ammarahmed/react-native-background-fetch 4.2.1
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/.yarnignore +7 -0
- package/LICENSE +21 -0
- package/README.md +502 -0
- package/RNBackgroundFetch.podspec +25 -0
- package/android/build.gradle +40 -0
- package/android/proguard-rules.pro +2 -0
- package/android/src/main/AndroidManifest.xml +15 -0
- package/android/src/main/java/com/transistorsoft/rnbackgroundfetch/HeadlessTask.java +119 -0
- package/android/src/main/java/com/transistorsoft/rnbackgroundfetch/RNBackgroundFetchModule.java +185 -0
- package/android/src/main/java/com/transistorsoft/rnbackgroundfetch/RNBackgroundFetchPackage.java +29 -0
- package/android/src/main/java/com/transistorsoft/tsbackgroundfetch/BGTask.java +305 -0
- package/android/src/main/java/com/transistorsoft/tsbackgroundfetch/BackgroundFetch.java +297 -0
- package/android/src/main/java/com/transistorsoft/tsbackgroundfetch/BackgroundFetchConfig.java +362 -0
- package/android/src/main/java/com/transistorsoft/tsbackgroundfetch/BootReceiver.java +24 -0
- package/android/src/main/java/com/transistorsoft/tsbackgroundfetch/FetchAlarmReceiver.java +40 -0
- package/android/src/main/java/com/transistorsoft/tsbackgroundfetch/FetchJobService.java +106 -0
- package/android/src/main/java/com/transistorsoft/tsbackgroundfetch/LifecycleManager.java +225 -0
- package/app.plugin.js +1 -0
- package/index.d.ts +185 -0
- package/index.js +119 -0
- package/ios/RNBackgroundFetch/RNBackgroundFetch+AppDelegate.m +22 -0
- package/ios/RNBackgroundFetch/RNBackgroundFetch.h +18 -0
- package/ios/RNBackgroundFetch/RNBackgroundFetch.m +167 -0
- package/ios/RNBackgroundFetch/TSBackgroundFetch.xcframework/Info.plist +57 -0
- package/ios/RNBackgroundFetch/TSBackgroundFetch.xcframework/ios-arm64_armv7/TSBackgroundFetch.framework/Headers/TSBackgroundFetch.h +47 -0
- package/ios/RNBackgroundFetch/TSBackgroundFetch.xcframework/ios-arm64_armv7/TSBackgroundFetch.framework/Info.plist +0 -0
- package/ios/RNBackgroundFetch/TSBackgroundFetch.xcframework/ios-arm64_armv7/TSBackgroundFetch.framework/Modules/module.modulemap +6 -0
- package/ios/RNBackgroundFetch/TSBackgroundFetch.xcframework/ios-arm64_armv7/TSBackgroundFetch.framework/TSBackgroundFetch +0 -0
- package/ios/RNBackgroundFetch/TSBackgroundFetch.xcframework/ios-arm64_i386_x86_64-simulator/TSBackgroundFetch.framework/Headers/TSBackgroundFetch.h +47 -0
- package/ios/RNBackgroundFetch/TSBackgroundFetch.xcframework/ios-arm64_i386_x86_64-simulator/TSBackgroundFetch.framework/Info.plist +0 -0
- package/ios/RNBackgroundFetch/TSBackgroundFetch.xcframework/ios-arm64_i386_x86_64-simulator/TSBackgroundFetch.framework/Modules/module.modulemap +6 -0
- package/ios/RNBackgroundFetch/TSBackgroundFetch.xcframework/ios-arm64_i386_x86_64-simulator/TSBackgroundFetch.framework/TSBackgroundFetch +0 -0
- package/ios/RNBackgroundFetch/TSBackgroundFetch.xcframework/ios-arm64_i386_x86_64-simulator/TSBackgroundFetch.framework/_CodeSignature/CodeDirectory +0 -0
- package/ios/RNBackgroundFetch/TSBackgroundFetch.xcframework/ios-arm64_i386_x86_64-simulator/TSBackgroundFetch.framework/_CodeSignature/CodeRequirements +0 -0
- package/ios/RNBackgroundFetch/TSBackgroundFetch.xcframework/ios-arm64_i386_x86_64-simulator/TSBackgroundFetch.framework/_CodeSignature/CodeRequirements-1 +0 -0
- package/ios/RNBackgroundFetch/TSBackgroundFetch.xcframework/ios-arm64_i386_x86_64-simulator/TSBackgroundFetch.framework/_CodeSignature/CodeResources +132 -0
- package/ios/RNBackgroundFetch/TSBackgroundFetch.xcframework/ios-arm64_i386_x86_64-simulator/TSBackgroundFetch.framework/_CodeSignature/CodeSignature +0 -0
- package/ios/RNBackgroundFetch/TSBackgroundFetch.xcframework/ios-arm64_x86_64-maccatalyst/TSBackgroundFetch.framework/Headers/TSBackgroundFetch.h +47 -0
- package/ios/RNBackgroundFetch/TSBackgroundFetch.xcframework/ios-arm64_x86_64-maccatalyst/TSBackgroundFetch.framework/Modules/module.modulemap +6 -0
- package/ios/RNBackgroundFetch/TSBackgroundFetch.xcframework/ios-arm64_x86_64-maccatalyst/TSBackgroundFetch.framework/Resources/Info.plist +52 -0
- package/ios/RNBackgroundFetch/TSBackgroundFetch.xcframework/ios-arm64_x86_64-maccatalyst/TSBackgroundFetch.framework/TSBackgroundFetch +0 -0
- package/ios/RNBackgroundFetch.xcodeproj/project.pbxproj +310 -0
- package/ios/RNBackgroundFetch.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
- package/ios/RNBackgroundFetch.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
- package/ios/RNBackgroundFetch.xcodeproj/project.xcworkspace/xcuserdata/chris.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- package/package.json +32 -0
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
package com.transistorsoft.rnbackgroundfetch;
|
|
2
|
+
|
|
3
|
+
import android.content.Context;
|
|
4
|
+
import android.os.Handler;
|
|
5
|
+
import android.util.Log;
|
|
6
|
+
|
|
7
|
+
import com.facebook.react.ReactApplication;
|
|
8
|
+
import com.facebook.react.ReactInstanceManager;
|
|
9
|
+
import com.facebook.react.ReactNativeHost;
|
|
10
|
+
import com.facebook.react.bridge.ReactContext;
|
|
11
|
+
import com.facebook.react.bridge.UiThreadUtil;
|
|
12
|
+
import com.facebook.react.bridge.WritableMap;
|
|
13
|
+
import com.facebook.react.bridge.WritableNativeMap;
|
|
14
|
+
import com.facebook.react.jstasks.HeadlessJsTaskConfig;
|
|
15
|
+
import com.facebook.react.jstasks.HeadlessJsTaskContext;
|
|
16
|
+
import com.facebook.react.jstasks.HeadlessJsTaskEventListener;
|
|
17
|
+
import com.transistorsoft.tsbackgroundfetch.BGTask;
|
|
18
|
+
import com.transistorsoft.tsbackgroundfetch.BackgroundFetch;
|
|
19
|
+
import com.facebook.react.common.LifecycleState;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Created by chris on 2018-01-17.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
public class HeadlessTask implements HeadlessJsTaskEventListener {
|
|
26
|
+
private static String HEADLESS_TASK_NAME = "BackgroundFetch";
|
|
27
|
+
private static Handler mHandler = new Handler();
|
|
28
|
+
private ReactNativeHost mReactNativeHost;
|
|
29
|
+
private HeadlessJsTaskContext mActiveTaskContext;
|
|
30
|
+
|
|
31
|
+
public HeadlessTask(Context context, BGTask task) {
|
|
32
|
+
try {
|
|
33
|
+
ReactApplication reactApplication = ((ReactApplication) context.getApplicationContext());
|
|
34
|
+
mReactNativeHost = reactApplication.getReactNativeHost();
|
|
35
|
+
} catch (AssertionError | ClassCastException e) {
|
|
36
|
+
Log.e(BackgroundFetch.TAG, "Failed to fetch ReactApplication. Task ignored.");
|
|
37
|
+
return; // <-- Do nothing. Just return
|
|
38
|
+
}
|
|
39
|
+
WritableMap clientEvent = new WritableNativeMap();
|
|
40
|
+
clientEvent.putString("taskId", task.getTaskId());
|
|
41
|
+
clientEvent.putBoolean("timeout", task.getTimedOut());
|
|
42
|
+
HeadlessJsTaskConfig config = new HeadlessJsTaskConfig(HEADLESS_TASK_NAME, clientEvent, 30000);
|
|
43
|
+
startTask(config);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
public void finish() {
|
|
47
|
+
if (mActiveTaskContext != null) {
|
|
48
|
+
mActiveTaskContext.removeTaskEventListener(this);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
@Override
|
|
52
|
+
public void onHeadlessJsTaskStart(int taskId) {
|
|
53
|
+
Log.d(BackgroundFetch.TAG,"onHeadlessJsTaskStart: " + taskId);
|
|
54
|
+
}
|
|
55
|
+
@Override
|
|
56
|
+
public void onHeadlessJsTaskFinish(int taskId) {
|
|
57
|
+
Log.d(BackgroundFetch.TAG, "onHeadlessJsTaskFinish: " + taskId);
|
|
58
|
+
mActiveTaskContext.removeTaskEventListener(this);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Start a task. This method handles starting a new React instance if required.
|
|
63
|
+
*
|
|
64
|
+
* Has to be called on the UI thread.
|
|
65
|
+
*
|
|
66
|
+
* @param taskConfig describes what task to start and the parameters to pass to it
|
|
67
|
+
*/
|
|
68
|
+
protected void startTask(final HeadlessJsTaskConfig taskConfig) {
|
|
69
|
+
UiThreadUtil.assertOnUiThread();
|
|
70
|
+
final ReactInstanceManager reactInstanceManager = mReactNativeHost.getReactInstanceManager();
|
|
71
|
+
ReactContext reactContext = reactInstanceManager.getCurrentReactContext();
|
|
72
|
+
if (reactContext == null) {
|
|
73
|
+
reactInstanceManager.addReactInstanceEventListener(new ReactInstanceManager.ReactInstanceEventListener() {
|
|
74
|
+
@Override
|
|
75
|
+
public void onReactContextInitialized(final ReactContext reactContext) {
|
|
76
|
+
// Hack to fix unknown problem executing asynchronous BackgroundTask when ReactContext is created *first time*. Fixed by adding short delay before #invokeStartTask
|
|
77
|
+
mHandler.postDelayed(new Runnable() {
|
|
78
|
+
@Override
|
|
79
|
+
public void run() {
|
|
80
|
+
invokeStartTask(reactContext, taskConfig);
|
|
81
|
+
}
|
|
82
|
+
}, 500);
|
|
83
|
+
reactInstanceManager.removeReactInstanceEventListener(this);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
if (!reactInstanceManager.hasStartedCreatingInitialContext()) {
|
|
87
|
+
reactInstanceManager.createReactContextInBackground();
|
|
88
|
+
}
|
|
89
|
+
} else {
|
|
90
|
+
invokeStartTask(reactContext, taskConfig);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
private void invokeStartTask(ReactContext reactContext, final HeadlessJsTaskConfig taskConfig) {
|
|
95
|
+
if (reactContext.getLifecycleState() == LifecycleState.RESUMED) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
final HeadlessJsTaskContext headlessJsTaskContext = HeadlessJsTaskContext.getInstance(reactContext);
|
|
99
|
+
headlessJsTaskContext.addTaskEventListener(this);
|
|
100
|
+
mActiveTaskContext = headlessJsTaskContext;
|
|
101
|
+
try {
|
|
102
|
+
UiThreadUtil.runOnUiThread(new Runnable() {
|
|
103
|
+
@Override
|
|
104
|
+
public void run() {
|
|
105
|
+
try {
|
|
106
|
+
int taskId = headlessJsTaskContext.startTask(taskConfig);
|
|
107
|
+
} catch (IllegalStateException exception) {
|
|
108
|
+
Log.e(BackgroundFetch.TAG, "Headless task attempted to run in the foreground. Task ignored.");
|
|
109
|
+
return; // <-- Do nothing. Just return
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
} catch (IllegalStateException exception) {
|
|
114
|
+
Log.e(BackgroundFetch.TAG, "Headless task attempted to run in the foreground. Task ignored.");
|
|
115
|
+
return; // <-- Do nothing. Just return
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
}
|
|
119
|
+
}
|
package/android/src/main/java/com/transistorsoft/rnbackgroundfetch/RNBackgroundFetchModule.java
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
package com.transistorsoft.rnbackgroundfetch;
|
|
2
|
+
|
|
3
|
+
import android.app.Activity;
|
|
4
|
+
import android.content.Intent;
|
|
5
|
+
import android.util.Log;
|
|
6
|
+
|
|
7
|
+
import com.facebook.react.bridge.*;
|
|
8
|
+
import com.facebook.react.modules.core.RCTNativeAppEventEmitter;
|
|
9
|
+
import com.transistorsoft.tsbackgroundfetch.BackgroundFetch;
|
|
10
|
+
import com.transistorsoft.tsbackgroundfetch.BackgroundFetchConfig;
|
|
11
|
+
import com.transistorsoft.tsbackgroundfetch.LifecycleManager;
|
|
12
|
+
|
|
13
|
+
public class RNBackgroundFetchModule extends ReactContextBaseJavaModule implements ActivityEventListener, LifecycleEventListener {
|
|
14
|
+
public static final String TAG = "RNBackgroundFetch";
|
|
15
|
+
private static final String EVENT_FETCH = "fetch";
|
|
16
|
+
private static final String JOB_SERVICE_CLASS = HeadlessTask.class.getName();
|
|
17
|
+
private static final String FETCH_TASK_ID = "react-native-background-fetch";
|
|
18
|
+
private boolean initialized = false;
|
|
19
|
+
|
|
20
|
+
public RNBackgroundFetchModule(ReactApplicationContext reactContext) {
|
|
21
|
+
super(reactContext);
|
|
22
|
+
Log.d(BackgroundFetch.TAG, "[RNBackgroundFetch initialize]");
|
|
23
|
+
BackgroundFetch.getInstance(reactContext.getApplicationContext());
|
|
24
|
+
reactContext.addLifecycleEventListener(this);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
@Override
|
|
28
|
+
public String getName() {
|
|
29
|
+
return TAG;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
@ReactMethod
|
|
33
|
+
public void configure(ReadableMap options, final Callback success, final Callback failure) {
|
|
34
|
+
BackgroundFetch adapter = getAdapter();
|
|
35
|
+
|
|
36
|
+
BackgroundFetch.Callback callback = new BackgroundFetch.Callback() {
|
|
37
|
+
@Override public void onFetch(String taskId) {
|
|
38
|
+
WritableMap params = new WritableNativeMap();
|
|
39
|
+
params.putString("taskId", taskId);
|
|
40
|
+
params.putBoolean("timeout", false);
|
|
41
|
+
getReactApplicationContext().getJSModule(RCTNativeAppEventEmitter.class).emit(EVENT_FETCH, params);
|
|
42
|
+
}
|
|
43
|
+
@Override public void onTimeout(String taskId) {
|
|
44
|
+
WritableMap params = new WritableNativeMap();
|
|
45
|
+
params.putString("taskId", taskId);
|
|
46
|
+
params.putBoolean("timeout", true);
|
|
47
|
+
getReactApplicationContext().getJSModule(RCTNativeAppEventEmitter.class).emit(EVENT_FETCH, params);
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
adapter.configure(buildConfig(options)
|
|
51
|
+
.setTaskId(FETCH_TASK_ID)
|
|
52
|
+
.setIsFetchTask(true)
|
|
53
|
+
.build(), callback);
|
|
54
|
+
|
|
55
|
+
success.invoke(BackgroundFetch.STATUS_AVAILABLE);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
@ReactMethod
|
|
59
|
+
public void scheduleTask(ReadableMap options, final Callback success, final Callback failure) {
|
|
60
|
+
BackgroundFetch adapter = getAdapter();
|
|
61
|
+
adapter.scheduleTask(buildConfig(options).build());
|
|
62
|
+
success.invoke(true);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
@ReactMethod
|
|
66
|
+
public void start(Callback success, Callback failure) {
|
|
67
|
+
BackgroundFetch adapter = getAdapter();
|
|
68
|
+
adapter.start(FETCH_TASK_ID);
|
|
69
|
+
success.invoke(adapter.status());
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
@ReactMethod
|
|
73
|
+
public void stop(String taskId, Callback success, Callback failure) {
|
|
74
|
+
if (taskId == null) taskId = FETCH_TASK_ID;
|
|
75
|
+
BackgroundFetch adapter = getAdapter();
|
|
76
|
+
adapter.stop(taskId);
|
|
77
|
+
success.invoke(true);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
@ReactMethod
|
|
81
|
+
public void status(Callback success) {
|
|
82
|
+
BackgroundFetch adapter = getAdapter();
|
|
83
|
+
success.invoke(adapter.status());
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
@ReactMethod
|
|
87
|
+
public void finish(String taskId) {
|
|
88
|
+
BackgroundFetch adapter = getAdapter();
|
|
89
|
+
adapter.finish(taskId);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
@ReactMethod
|
|
93
|
+
public void addListener(String event) {
|
|
94
|
+
// Keep: Required for RN built-in NativeEventEmitter calls.
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
@ReactMethod
|
|
98
|
+
public void removeListeners(Integer count) {
|
|
99
|
+
// Keep: Required for RN built-in NativeEventEmitter calls.
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
@Override
|
|
103
|
+
public void onHostResume() {
|
|
104
|
+
if (!initialized) {
|
|
105
|
+
initializeBackgroundFetch();
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
@Override
|
|
110
|
+
public void onHostPause() {
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
@Override
|
|
114
|
+
public void onNewIntent(Intent intent) {
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
@Override
|
|
118
|
+
public void onHostDestroy() {
|
|
119
|
+
LifecycleManager.getInstance().setHeadless(true);
|
|
120
|
+
initialized = false;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
@Override
|
|
124
|
+
public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent data) {
|
|
125
|
+
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
private BackgroundFetchConfig.Builder buildConfig(ReadableMap options) {
|
|
129
|
+
BackgroundFetchConfig.Builder config = new BackgroundFetchConfig.Builder();
|
|
130
|
+
if (options.hasKey(BackgroundFetchConfig.FIELD_MINIMUM_FETCH_INTERVAL)) {
|
|
131
|
+
config.setMinimumFetchInterval(options.getInt(BackgroundFetchConfig.FIELD_MINIMUM_FETCH_INTERVAL));
|
|
132
|
+
}
|
|
133
|
+
if (options.hasKey(BackgroundFetchConfig.FIELD_TASK_ID)) {
|
|
134
|
+
config.setTaskId(options.getString(BackgroundFetchConfig.FIELD_TASK_ID));
|
|
135
|
+
}
|
|
136
|
+
if (options.hasKey(BackgroundFetchConfig.FIELD_DELAY)) {
|
|
137
|
+
Integer delay = options.getInt(BackgroundFetchConfig.FIELD_DELAY);
|
|
138
|
+
config.setDelay(delay.longValue());
|
|
139
|
+
}
|
|
140
|
+
if (options.hasKey(BackgroundFetchConfig.FIELD_STOP_ON_TERMINATE)) {
|
|
141
|
+
config.setStopOnTerminate(options.getBoolean(BackgroundFetchConfig.FIELD_STOP_ON_TERMINATE));
|
|
142
|
+
}
|
|
143
|
+
if (options.hasKey(BackgroundFetchConfig.FIELD_FORCE_ALARM_MANAGER)) {
|
|
144
|
+
config.setForceAlarmManager(options.getBoolean(BackgroundFetchConfig.FIELD_FORCE_ALARM_MANAGER));
|
|
145
|
+
}
|
|
146
|
+
if (options.hasKey(BackgroundFetchConfig.FIELD_START_ON_BOOT)) {
|
|
147
|
+
config.setStartOnBoot(options.getBoolean(BackgroundFetchConfig.FIELD_START_ON_BOOT));
|
|
148
|
+
}
|
|
149
|
+
if (options.hasKey("enableHeadless") && options.getBoolean("enableHeadless")) {
|
|
150
|
+
config.setJobService(JOB_SERVICE_CLASS);
|
|
151
|
+
}
|
|
152
|
+
if (options.hasKey(BackgroundFetchConfig.FIELD_REQUIRED_NETWORK_TYPE)) {
|
|
153
|
+
config.setRequiredNetworkType(options.getInt(BackgroundFetchConfig.FIELD_REQUIRED_NETWORK_TYPE));
|
|
154
|
+
}
|
|
155
|
+
if (options.hasKey(BackgroundFetchConfig.FIELD_REQUIRES_BATTERY_NOT_LOW)) {
|
|
156
|
+
config.setRequiresBatteryNotLow(options.getBoolean(BackgroundFetchConfig.FIELD_REQUIRES_BATTERY_NOT_LOW));
|
|
157
|
+
}
|
|
158
|
+
if (options.hasKey(BackgroundFetchConfig.FIELD_REQUIRES_CHARGING)) {
|
|
159
|
+
config.setRequiresCharging(options.getBoolean(BackgroundFetchConfig.FIELD_REQUIRES_CHARGING));
|
|
160
|
+
}
|
|
161
|
+
if (options.hasKey(BackgroundFetchConfig.FIELD_REQUIRES_DEVICE_IDLE)) {
|
|
162
|
+
config.setRequiresDeviceIdle(options.getBoolean(BackgroundFetchConfig.FIELD_REQUIRES_DEVICE_IDLE));
|
|
163
|
+
}
|
|
164
|
+
if (options.hasKey(BackgroundFetchConfig.FIELD_REQUIRES_STORAGE_NOT_LOW)) {
|
|
165
|
+
config.setRequiresStorageNotLow(options.getBoolean(BackgroundFetchConfig.FIELD_REQUIRES_STORAGE_NOT_LOW));
|
|
166
|
+
}
|
|
167
|
+
if (options.hasKey(BackgroundFetchConfig.FIELD_PERIODIC)) {
|
|
168
|
+
config.setPeriodic(options.getBoolean(BackgroundFetchConfig.FIELD_PERIODIC));
|
|
169
|
+
}
|
|
170
|
+
return config;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
private void initializeBackgroundFetch() {
|
|
174
|
+
Activity activity = getCurrentActivity();
|
|
175
|
+
if (activity == null) {
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
initialized = true;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
private BackgroundFetch getAdapter() {
|
|
182
|
+
return BackgroundFetch.getInstance(getReactApplicationContext());
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
}
|
package/android/src/main/java/com/transistorsoft/rnbackgroundfetch/RNBackgroundFetchPackage.java
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
package com.transistorsoft.rnbackgroundfetch;
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.ReactPackage;
|
|
4
|
+
import com.facebook.react.bridge.JavaScriptModule;
|
|
5
|
+
import com.facebook.react.bridge.NativeModule;
|
|
6
|
+
import com.facebook.react.bridge.ReactApplicationContext;
|
|
7
|
+
import com.facebook.react.uimanager.ViewManager;
|
|
8
|
+
|
|
9
|
+
import java.util.ArrayList;
|
|
10
|
+
import java.util.Collections;
|
|
11
|
+
import java.util.List;
|
|
12
|
+
|
|
13
|
+
public class RNBackgroundFetchPackage implements ReactPackage {
|
|
14
|
+
@Override
|
|
15
|
+
public List<NativeModule> createNativeModules (ReactApplicationContext reactContext) {
|
|
16
|
+
List<NativeModule> modules = new ArrayList<>();
|
|
17
|
+
modules.add(new RNBackgroundFetchModule(reactContext));
|
|
18
|
+
return modules;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
public List<Class<? extends JavaScriptModule>> createJSModules() {
|
|
22
|
+
return Collections.emptyList();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
@Override
|
|
26
|
+
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
|
|
27
|
+
return Collections.emptyList();
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
package com.transistorsoft.tsbackgroundfetch;
|
|
2
|
+
|
|
3
|
+
import android.annotation.SuppressLint;
|
|
4
|
+
import android.app.AlarmManager;
|
|
5
|
+
import android.app.PendingIntent;
|
|
6
|
+
import android.app.job.JobInfo;
|
|
7
|
+
import android.app.job.JobScheduler;
|
|
8
|
+
import android.content.ComponentName;
|
|
9
|
+
import android.content.Context;
|
|
10
|
+
import android.content.Intent;
|
|
11
|
+
import android.os.Build;
|
|
12
|
+
import android.os.PersistableBundle;
|
|
13
|
+
import android.util.Log;
|
|
14
|
+
|
|
15
|
+
import org.json.JSONException;
|
|
16
|
+
import org.json.JSONObject;
|
|
17
|
+
|
|
18
|
+
import java.lang.reflect.Constructor;
|
|
19
|
+
import java.lang.reflect.InvocationTargetException;
|
|
20
|
+
import java.lang.reflect.Method;
|
|
21
|
+
import java.util.ArrayList;
|
|
22
|
+
import java.util.HashMap;
|
|
23
|
+
import java.util.List;
|
|
24
|
+
import java.util.Map;
|
|
25
|
+
import java.util.concurrent.TimeUnit;
|
|
26
|
+
|
|
27
|
+
public class BGTask {
|
|
28
|
+
static int MAX_TIME = 60000;
|
|
29
|
+
|
|
30
|
+
private static final List<BGTask> mTasks = new ArrayList<>();
|
|
31
|
+
|
|
32
|
+
static BGTask getTask(String taskId) {
|
|
33
|
+
synchronized (mTasks) {
|
|
34
|
+
for (BGTask task : mTasks) {
|
|
35
|
+
if (task.hasTaskId(taskId)) return task;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
static void addTask(BGTask task) {
|
|
42
|
+
synchronized (mTasks) {
|
|
43
|
+
mTasks.add(task);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
static void removeTask(String taskId) {
|
|
48
|
+
synchronized (mTasks) {
|
|
49
|
+
BGTask found = null;
|
|
50
|
+
for (BGTask task : mTasks) {
|
|
51
|
+
if (task.hasTaskId(taskId)) {
|
|
52
|
+
found = task;
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (found != null) {
|
|
57
|
+
mTasks.remove(found);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
static void clear() {
|
|
63
|
+
synchronized (mTasks) {
|
|
64
|
+
mTasks.clear();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
private FetchJobService.CompletionHandler mCompletionHandler;
|
|
69
|
+
private String mTaskId;
|
|
70
|
+
private int mJobId;
|
|
71
|
+
private Runnable mTimeoutTask;
|
|
72
|
+
private boolean mTimedout = false;
|
|
73
|
+
|
|
74
|
+
BGTask(final Context context, String taskId, FetchJobService.CompletionHandler handler, int jobId) {
|
|
75
|
+
mTaskId = taskId;
|
|
76
|
+
mCompletionHandler = handler;
|
|
77
|
+
mJobId = jobId;
|
|
78
|
+
|
|
79
|
+
mTimeoutTask = new Runnable() {
|
|
80
|
+
@Override public void run() {
|
|
81
|
+
onTimeout(context);
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
BackgroundFetch.getUiHandler().postDelayed(mTimeoutTask, MAX_TIME);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
public boolean getTimedOut() {
|
|
88
|
+
return mTimedout;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
public String getTaskId() { return mTaskId; }
|
|
92
|
+
|
|
93
|
+
int getJobId() { return mJobId; }
|
|
94
|
+
|
|
95
|
+
boolean hasTaskId(String taskId) {
|
|
96
|
+
return ((mTaskId != null) && mTaskId.equalsIgnoreCase(taskId));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
void setCompletionHandler(FetchJobService.CompletionHandler handler) {
|
|
100
|
+
mCompletionHandler = handler;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
void finish() {
|
|
104
|
+
if (mCompletionHandler != null) {
|
|
105
|
+
mCompletionHandler.finish();
|
|
106
|
+
}
|
|
107
|
+
if (mTimeoutTask != null) {
|
|
108
|
+
BackgroundFetch.getUiHandler().removeCallbacks(mTimeoutTask);
|
|
109
|
+
}
|
|
110
|
+
mCompletionHandler = null;
|
|
111
|
+
removeTask(mTaskId);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
static void reschedule(Context context, BackgroundFetchConfig existing, BackgroundFetchConfig config) {
|
|
115
|
+
BGTask existingTask = BGTask.getTask(existing.getTaskId());
|
|
116
|
+
if (existingTask != null) {
|
|
117
|
+
existingTask.finish();
|
|
118
|
+
}
|
|
119
|
+
cancel(context, existing.getTaskId(), existing.getJobId());
|
|
120
|
+
|
|
121
|
+
schedule(context, config);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
static void schedule(Context context, BackgroundFetchConfig config) {
|
|
125
|
+
Log.d(BackgroundFetch.TAG, config.toString());
|
|
126
|
+
|
|
127
|
+
long interval = (config.isFetchTask()) ? (TimeUnit.MINUTES.toMillis(config.getMinimumFetchInterval())) : config.getDelay();
|
|
128
|
+
|
|
129
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && !config.getForceAlarmManager()) {
|
|
130
|
+
// API 21+ uses new JobScheduler API
|
|
131
|
+
|
|
132
|
+
JobScheduler jobScheduler = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
|
|
133
|
+
@SuppressLint("WrongConstant")
|
|
134
|
+
JobInfo.Builder builder = new JobInfo.Builder(config.getJobId(), new ComponentName(context, FetchJobService.class))
|
|
135
|
+
.setRequiredNetworkType(config.getRequiredNetworkType())
|
|
136
|
+
.setRequiresDeviceIdle(config.getRequiresDeviceIdle())
|
|
137
|
+
.setRequiresCharging(config.getRequiresCharging())
|
|
138
|
+
.setPersisted(config.getStartOnBoot() && !config.getStopOnTerminate());
|
|
139
|
+
|
|
140
|
+
if (config.getPeriodic()) {
|
|
141
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
|
142
|
+
builder.setPeriodic(interval, interval);
|
|
143
|
+
} else {
|
|
144
|
+
builder.setPeriodic(interval);
|
|
145
|
+
}
|
|
146
|
+
} else {
|
|
147
|
+
builder.setMinimumLatency(interval);
|
|
148
|
+
}
|
|
149
|
+
PersistableBundle extras = new PersistableBundle();
|
|
150
|
+
extras.putString(BackgroundFetchConfig.FIELD_TASK_ID, config.getTaskId());
|
|
151
|
+
extras.putLong("scheduled_at", System.currentTimeMillis());
|
|
152
|
+
|
|
153
|
+
builder.setExtras(extras);
|
|
154
|
+
|
|
155
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
156
|
+
builder.setRequiresStorageNotLow(config.getRequiresStorageNotLow());
|
|
157
|
+
builder.setRequiresBatteryNotLow(config.getRequiresBatteryNotLow());
|
|
158
|
+
}
|
|
159
|
+
if (jobScheduler != null) {
|
|
160
|
+
jobScheduler.schedule(builder.build());
|
|
161
|
+
}
|
|
162
|
+
} else {
|
|
163
|
+
// Everyone else get AlarmManager
|
|
164
|
+
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
|
|
165
|
+
if (alarmManager != null) {
|
|
166
|
+
PendingIntent pi = getAlarmPI(context, config.getTaskId());
|
|
167
|
+
long delay = System.currentTimeMillis() + interval;
|
|
168
|
+
if (config.getPeriodic()) {
|
|
169
|
+
alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, delay, interval, pi);
|
|
170
|
+
} else {
|
|
171
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
172
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
|
173
|
+
if (alarmManager.canScheduleExactAlarms()) {
|
|
174
|
+
alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, delay, pi);
|
|
175
|
+
} else {
|
|
176
|
+
alarmManager.setAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, delay, pi);
|
|
177
|
+
}
|
|
178
|
+
} else {
|
|
179
|
+
alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, delay, pi);
|
|
180
|
+
}
|
|
181
|
+
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
|
182
|
+
alarmManager.setExact(AlarmManager.RTC_WAKEUP, delay, pi);
|
|
183
|
+
} else {
|
|
184
|
+
alarmManager.set(AlarmManager.RTC_WAKEUP, delay, pi);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
void onTimeout(Context context) {
|
|
192
|
+
mTimedout = true;
|
|
193
|
+
Log.d(BackgroundFetch.TAG, "[BGTask] timeout: " + mTaskId);
|
|
194
|
+
|
|
195
|
+
BackgroundFetch adapter = BackgroundFetch.getInstance(context);
|
|
196
|
+
|
|
197
|
+
if (!LifecycleManager.getInstance().isHeadless()) {
|
|
198
|
+
BackgroundFetch.Callback callback = adapter.getFetchCallback();
|
|
199
|
+
if (callback != null) {
|
|
200
|
+
callback.onTimeout(mTaskId);
|
|
201
|
+
}
|
|
202
|
+
} else {
|
|
203
|
+
BackgroundFetchConfig config = adapter.getConfig(mTaskId);
|
|
204
|
+
if (config != null) {
|
|
205
|
+
if (config.getJobService() != null) {
|
|
206
|
+
try {
|
|
207
|
+
fireHeadlessEvent(context, config);
|
|
208
|
+
} catch (BGTask.Error error) {
|
|
209
|
+
Log.e(BackgroundFetch.TAG, "Headless task error: " + error.getMessage());
|
|
210
|
+
}
|
|
211
|
+
} else {
|
|
212
|
+
adapter.finish(mTaskId);
|
|
213
|
+
}
|
|
214
|
+
} else {
|
|
215
|
+
Log.e(BackgroundFetch.TAG, "[BGTask] failed to load config for taskId: " + mTaskId);
|
|
216
|
+
adapter.finish(mTaskId);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Fire a headless background-fetch event by reflecting an instance of Config.jobServiceClass.
|
|
222
|
+
// Will attempt to reflect upon two different forms of Headless class:
|
|
223
|
+
// 1: new HeadlessTask(context, taskId)
|
|
224
|
+
// or
|
|
225
|
+
// 2: new HeadlessTask().onFetch(context, taskId);
|
|
226
|
+
//
|
|
227
|
+
void fireHeadlessEvent(Context context, BackgroundFetchConfig config) throws Error {
|
|
228
|
+
try {
|
|
229
|
+
// Get class via reflection.
|
|
230
|
+
Class<?> HeadlessClass = Class.forName(config.getJobService());
|
|
231
|
+
Class[] types = { Context.class, BGTask.class };
|
|
232
|
+
Object[] params = { context, this};
|
|
233
|
+
try {
|
|
234
|
+
// 1: new HeadlessTask(context, taskId);
|
|
235
|
+
Constructor<?> constructor = HeadlessClass.getDeclaredConstructor(types);
|
|
236
|
+
constructor.newInstance(params);
|
|
237
|
+
} catch (NoSuchMethodException e) {
|
|
238
|
+
// 2: new HeadlessTask().onFetch(context, taskId);
|
|
239
|
+
Constructor<?> constructor = HeadlessClass.getConstructor();
|
|
240
|
+
Object instance = constructor.newInstance();
|
|
241
|
+
Method onFetch = instance.getClass().getDeclaredMethod("onFetch", types);
|
|
242
|
+
onFetch.invoke(instance, params);
|
|
243
|
+
}
|
|
244
|
+
} catch (ClassNotFoundException e) {
|
|
245
|
+
throw new Error(e.getMessage());
|
|
246
|
+
} catch (NoSuchMethodException e) {
|
|
247
|
+
throw new Error(e.getMessage());
|
|
248
|
+
} catch (IllegalAccessException e) {
|
|
249
|
+
throw new Error(e.getMessage());
|
|
250
|
+
} catch (InstantiationException e) {
|
|
251
|
+
throw new Error(e.getMessage());
|
|
252
|
+
} catch (InvocationTargetException e) {
|
|
253
|
+
throw new Error(e.getMessage());
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
static void cancel(Context context, String taskId, int jobId) {
|
|
258
|
+
Log.i(BackgroundFetch.TAG, "- cancel taskId=" + taskId + ", jobId=" + jobId);
|
|
259
|
+
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && (jobId != 0)) {
|
|
260
|
+
JobScheduler jobScheduler = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
|
|
261
|
+
if (jobScheduler != null) {
|
|
262
|
+
jobScheduler.cancel(jobId);
|
|
263
|
+
}
|
|
264
|
+
} else {
|
|
265
|
+
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
|
|
266
|
+
if (alarmManager != null) {
|
|
267
|
+
alarmManager.cancel(BGTask.getAlarmPI(context, taskId));
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
static PendingIntent getAlarmPI(Context context, String taskId) {
|
|
273
|
+
Intent intent = new Intent(context, FetchAlarmReceiver.class);
|
|
274
|
+
intent.setAction(taskId);
|
|
275
|
+
return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT|PendingIntent.FLAG_IMMUTABLE);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
public String toString() {
|
|
279
|
+
return "[BGTask taskId=" + mTaskId + "]";
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
public Map<String, Object> toMap() {
|
|
283
|
+
Map<String, Object> map = new HashMap<>();
|
|
284
|
+
map.put("taskId", mTaskId);
|
|
285
|
+
map.put("timeout", mTimedout);
|
|
286
|
+
return map;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
public JSONObject toJson() {
|
|
290
|
+
JSONObject json = new JSONObject();
|
|
291
|
+
try {
|
|
292
|
+
json.put("taskId", mTaskId);
|
|
293
|
+
json.put("timeout", mTimedout);
|
|
294
|
+
} catch (JSONException e) {
|
|
295
|
+
e.printStackTrace();
|
|
296
|
+
}
|
|
297
|
+
return json;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
static class Error extends RuntimeException {
|
|
301
|
+
public Error(String msg) {
|
|
302
|
+
super(msg);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|