@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,40 @@
|
|
|
1
|
+
package com.transistorsoft.tsbackgroundfetch;
|
|
2
|
+
|
|
3
|
+
import android.content.BroadcastReceiver;
|
|
4
|
+
import android.content.Context;
|
|
5
|
+
import android.content.Intent;
|
|
6
|
+
import android.os.PowerManager;
|
|
7
|
+
import android.util.Log;
|
|
8
|
+
|
|
9
|
+
import static android.content.Context.POWER_SERVICE;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Created by chris on 2018-01-11.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
public class FetchAlarmReceiver extends BroadcastReceiver {
|
|
16
|
+
|
|
17
|
+
@Override
|
|
18
|
+
public void onReceive(final Context context, Intent intent) {
|
|
19
|
+
PowerManager powerManager = (PowerManager) context.getSystemService(POWER_SERVICE);
|
|
20
|
+
final PowerManager.WakeLock wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, BackgroundFetch.TAG + "::" + intent.getAction());
|
|
21
|
+
// WakeLock expires in MAX_TIME + 4s buffer.
|
|
22
|
+
wakeLock.acquire((BGTask.MAX_TIME + 4000));
|
|
23
|
+
|
|
24
|
+
final String taskId = intent.getAction();
|
|
25
|
+
|
|
26
|
+
final FetchJobService.CompletionHandler completionHandler = new FetchJobService.CompletionHandler() {
|
|
27
|
+
@Override
|
|
28
|
+
public void finish() {
|
|
29
|
+
if (wakeLock.isHeld()) {
|
|
30
|
+
wakeLock.release();
|
|
31
|
+
Log.d(BackgroundFetch.TAG, "- FetchAlarmReceiver finish");
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
BGTask task = new BGTask(context, taskId, completionHandler, 0);
|
|
37
|
+
|
|
38
|
+
BackgroundFetch.getInstance(context.getApplicationContext()).onFetch(task);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
package com.transistorsoft.tsbackgroundfetch;
|
|
2
|
+
|
|
3
|
+
import android.annotation.TargetApi;
|
|
4
|
+
import android.app.job.JobParameters;
|
|
5
|
+
import android.app.job.JobService;
|
|
6
|
+
import android.os.PersistableBundle;
|
|
7
|
+
import android.util.Log;
|
|
8
|
+
|
|
9
|
+
import java.util.ArrayList;
|
|
10
|
+
import java.util.List;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Created by chris on 2018-01-11.
|
|
14
|
+
*/
|
|
15
|
+
@TargetApi(21)
|
|
16
|
+
public class FetchJobService extends JobService {
|
|
17
|
+
|
|
18
|
+
// Queue of recently run jobs.
|
|
19
|
+
private static final List<ExecutedJob> sExecutedJobs = new ArrayList<>();
|
|
20
|
+
|
|
21
|
+
@Override
|
|
22
|
+
public boolean onStartJob(final JobParameters params) {
|
|
23
|
+
PersistableBundle extras = params.getExtras();
|
|
24
|
+
long scheduleAt = extras.getLong("scheduled_at");
|
|
25
|
+
long dt = System.currentTimeMillis() - scheduleAt;
|
|
26
|
+
// Scheduled < 1s ago? Ignore.
|
|
27
|
+
if (dt < 1000) {
|
|
28
|
+
// JobScheduler always immediately fires an initial event on Periodic jobs -- We IGNORE these.
|
|
29
|
+
jobFinished(params, false);
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
final String taskId = extras.getString(BackgroundFetchConfig.FIELD_TASK_ID);
|
|
34
|
+
|
|
35
|
+
// Is this a duplicate event?
|
|
36
|
+
// JobScheduler has a bug in Android N that causes duplicate Jobs to fire within a few milliseconds.
|
|
37
|
+
// We keep a Queue of the last 5 tasks so we can see if this task has executed in the last 5000ms.
|
|
38
|
+
synchronized (sExecutedJobs) {
|
|
39
|
+
for (ExecutedJob job : sExecutedJobs) {
|
|
40
|
+
if (job.isDuplicate(taskId)) {
|
|
41
|
+
Log.d(BackgroundFetch.TAG, "- Caught duplicate Job " + taskId + ": [IGNORED]");
|
|
42
|
+
jobFinished(params, false);
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
// Not found? Add this task to the Queue.
|
|
47
|
+
sExecutedJobs.add(new ExecutedJob(taskId));
|
|
48
|
+
if (sExecutedJobs.size() > 5) {
|
|
49
|
+
sExecutedJobs.remove(0);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
// Good to go: Execute the task.
|
|
53
|
+
CompletionHandler completionHandler = () -> {
|
|
54
|
+
Log.d(BackgroundFetch.TAG, "- jobFinished");
|
|
55
|
+
jobFinished(params, false);
|
|
56
|
+
};
|
|
57
|
+
BGTask task = new BGTask(this, taskId, completionHandler, params.getJobId());
|
|
58
|
+
BackgroundFetch.getInstance(getApplicationContext()).onFetch(task);
|
|
59
|
+
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
@Override
|
|
64
|
+
public boolean onStopJob(final JobParameters params) {
|
|
65
|
+
Log.d(BackgroundFetch.TAG, "- onStopJob");
|
|
66
|
+
|
|
67
|
+
PersistableBundle extras = params.getExtras();
|
|
68
|
+
final String taskId = extras.getString(BackgroundFetchConfig.FIELD_TASK_ID);
|
|
69
|
+
|
|
70
|
+
BGTask task = BGTask.getTask(taskId);
|
|
71
|
+
if (task != null) {
|
|
72
|
+
task.onTimeout(getApplicationContext());
|
|
73
|
+
}
|
|
74
|
+
jobFinished(params, false);
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
public interface CompletionHandler {
|
|
79
|
+
void finish();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
private static class ExecutedJob {
|
|
83
|
+
private static final long OFFSET_TIME = 5000L;
|
|
84
|
+
|
|
85
|
+
private final String mTaskId;
|
|
86
|
+
private final long mTimestamp;
|
|
87
|
+
|
|
88
|
+
ExecutedJob(String taskId) {
|
|
89
|
+
mTaskId = taskId;
|
|
90
|
+
mTimestamp = System.currentTimeMillis();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
boolean isDuplicate(String taskId) {
|
|
94
|
+
if (!taskId.equalsIgnoreCase(mTaskId)) {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
long dt = System.currentTimeMillis() - mTimestamp;
|
|
98
|
+
return (dt < OFFSET_TIME);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
@Override
|
|
102
|
+
public String toString() {
|
|
103
|
+
return "[LastJob taskId: " + mTaskId + ", timestamp: " + mTimestamp + "]";
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
package com.transistorsoft.tsbackgroundfetch;
|
|
2
|
+
|
|
3
|
+
import android.os.Handler;
|
|
4
|
+
import android.os.Looper;
|
|
5
|
+
import android.util.Log;
|
|
6
|
+
|
|
7
|
+
import androidx.annotation.NonNull;
|
|
8
|
+
import androidx.lifecycle.DefaultLifecycleObserver;
|
|
9
|
+
import androidx.lifecycle.LifecycleOwner;
|
|
10
|
+
import androidx.lifecycle.ProcessLifecycleOwner;
|
|
11
|
+
|
|
12
|
+
import java.util.ArrayList;
|
|
13
|
+
import java.util.List;
|
|
14
|
+
import java.util.concurrent.atomic.AtomicBoolean;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Component for managing app life-cycle changes, including headless-mode.
|
|
18
|
+
*/
|
|
19
|
+
public class LifecycleManager implements DefaultLifecycleObserver, Runnable {
|
|
20
|
+
private static LifecycleManager sInstance;
|
|
21
|
+
|
|
22
|
+
public static LifecycleManager getInstance() {
|
|
23
|
+
if (sInstance == null) {
|
|
24
|
+
sInstance = getInstanceSynchronized();
|
|
25
|
+
}
|
|
26
|
+
return sInstance;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
private static synchronized LifecycleManager getInstanceSynchronized() {
|
|
30
|
+
if (sInstance == null) sInstance = new LifecycleManager();
|
|
31
|
+
return sInstance;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
private final List<OnHeadlessChangeCallback> mHeadlessChangeCallbacks = new ArrayList<>();
|
|
35
|
+
private final List<OnStateChangeCallback> mStateChangeCallbacks = new ArrayList<>();
|
|
36
|
+
private final Handler mHandler;
|
|
37
|
+
private Runnable mHeadlessChangeEvent;
|
|
38
|
+
|
|
39
|
+
private final AtomicBoolean mIsBackground = new AtomicBoolean(true);
|
|
40
|
+
private final AtomicBoolean mIsHeadless = new AtomicBoolean(true);
|
|
41
|
+
private final AtomicBoolean mStarted = new AtomicBoolean(false);
|
|
42
|
+
private final AtomicBoolean mPaused = new AtomicBoolean(false);
|
|
43
|
+
|
|
44
|
+
private LifecycleManager() {
|
|
45
|
+
mHandler = new Handler(Looper.getMainLooper());
|
|
46
|
+
onHeadlessChange(isHeadless -> {
|
|
47
|
+
if (isHeadless) {
|
|
48
|
+
Log.d(BackgroundFetch.TAG, "☯️ HeadlessMode? " + isHeadless);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Temporarily disable responding to pause/resume events. This was placed here for handling TSLocationManagerActivity events
|
|
55
|
+
* whose presentation causes onPause / onResume events that we don't want to react to.
|
|
56
|
+
*/
|
|
57
|
+
public void pause() {
|
|
58
|
+
mPaused.set(true);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Re-engage responding to pause/resume events.
|
|
63
|
+
*/
|
|
64
|
+
public void resume() {
|
|
65
|
+
mPaused.set(false);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Are we in the background?
|
|
69
|
+
* @return boolean
|
|
70
|
+
*/
|
|
71
|
+
public boolean isBackground() {
|
|
72
|
+
return mIsBackground.get();
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Are we headless
|
|
76
|
+
* @return boolean
|
|
77
|
+
*/
|
|
78
|
+
public boolean isHeadless() {
|
|
79
|
+
return mIsHeadless.get();
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Explicitly state that we are headless. Probably called when MainActivity is known to have been destroyed.
|
|
83
|
+
* @param value boolean
|
|
84
|
+
*/
|
|
85
|
+
public void setHeadless(boolean value) {
|
|
86
|
+
mIsHeadless.set(value);
|
|
87
|
+
if (mIsHeadless.get()) {
|
|
88
|
+
Log.d(BackgroundFetch.TAG,"☯️ HeadlessMode? " + mIsHeadless);
|
|
89
|
+
}
|
|
90
|
+
if (mHeadlessChangeEvent != null) {
|
|
91
|
+
mHandler.removeCallbacks(mHeadlessChangeEvent);
|
|
92
|
+
mStarted.set(true);
|
|
93
|
+
fireHeadlessChangeListeners();
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Register Headless-mode change listener.
|
|
98
|
+
*/
|
|
99
|
+
public void onHeadlessChange(OnHeadlessChangeCallback callback) {
|
|
100
|
+
if (mStarted.get()) {
|
|
101
|
+
callback.onChange(mIsHeadless.get());
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
synchronized (mHeadlessChangeCallbacks) {
|
|
105
|
+
mHeadlessChangeCallbacks.add(callback);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Register pause/resume listener.
|
|
110
|
+
*/
|
|
111
|
+
public void onStateChange(OnStateChangeCallback callback) {
|
|
112
|
+
synchronized (mStateChangeCallbacks) {
|
|
113
|
+
mStateChangeCallbacks.add(callback);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Regiser the LifecycleObserver
|
|
119
|
+
*/
|
|
120
|
+
@Override
|
|
121
|
+
public void run() {
|
|
122
|
+
ProcessLifecycleOwner.get().getLifecycle().addObserver(this);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
@Override
|
|
126
|
+
public void onCreate(@NonNull LifecycleOwner owner) {
|
|
127
|
+
Log.d(BackgroundFetch.TAG,"☯️ onCreate");
|
|
128
|
+
// If this 50ms Timer fires before onStart, we are headless
|
|
129
|
+
mHeadlessChangeEvent = new Runnable() {
|
|
130
|
+
@Override public void run() {
|
|
131
|
+
mStarted.set(true);
|
|
132
|
+
fireHeadlessChangeListeners();
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
mHandler.postDelayed(mHeadlessChangeEvent, 50);
|
|
137
|
+
mIsHeadless.set(true);
|
|
138
|
+
mIsBackground.set(true);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
@Override
|
|
142
|
+
public void onStart(@NonNull LifecycleOwner owner) {
|
|
143
|
+
Log.d(BackgroundFetch.TAG, "☯️ onStart");
|
|
144
|
+
// Cancel StateChange Timer.
|
|
145
|
+
if (mPaused.get()) {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
if (mHeadlessChangeEvent != null) {
|
|
149
|
+
mHandler.removeCallbacks(mHeadlessChangeEvent);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
mStarted.set(true);
|
|
153
|
+
mIsHeadless.set(false);
|
|
154
|
+
mIsBackground.set(false);
|
|
155
|
+
|
|
156
|
+
// Fire listeners.
|
|
157
|
+
fireHeadlessChangeListeners();
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
@Override
|
|
161
|
+
public void onDestroy(@NonNull LifecycleOwner owner) {
|
|
162
|
+
Log.d(BackgroundFetch.TAG, "☯️ onDestroy");
|
|
163
|
+
mIsBackground.set(true);
|
|
164
|
+
mIsHeadless.set(true);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
@Override
|
|
168
|
+
public void onStop(@NonNull LifecycleOwner owner) {
|
|
169
|
+
Log.d(BackgroundFetch.TAG, "☯️ onStop");
|
|
170
|
+
if (mPaused.compareAndSet(true, false)) {
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
mIsBackground.set(true);
|
|
174
|
+
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
@Override
|
|
178
|
+
public void onPause(@NonNull LifecycleOwner owner) {
|
|
179
|
+
Log.d(BackgroundFetch.TAG, "☯️ onPause");
|
|
180
|
+
mIsBackground.set(true);
|
|
181
|
+
fireStateChangeListeners(false);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
@Override
|
|
185
|
+
public void onResume(@NonNull LifecycleOwner owner) {
|
|
186
|
+
Log.d(BackgroundFetch.TAG, "☯️ onResume");
|
|
187
|
+
if (mPaused.get()) {
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
mIsBackground.set(false);
|
|
191
|
+
mIsHeadless.set(false);
|
|
192
|
+
fireStateChangeListeners(true);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/// Fire pause/resume change listeners
|
|
196
|
+
private void fireStateChangeListeners(boolean isForeground) {
|
|
197
|
+
synchronized (mStateChangeCallbacks) {
|
|
198
|
+
for (OnStateChangeCallback callback : mStateChangeCallbacks) {
|
|
199
|
+
callback.onChange(isForeground);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/// Fire headless mode change listeners.
|
|
205
|
+
private void fireHeadlessChangeListeners() {
|
|
206
|
+
if (mHeadlessChangeEvent != null) {
|
|
207
|
+
mHandler.removeCallbacks(mHeadlessChangeEvent);
|
|
208
|
+
mHeadlessChangeEvent = null;
|
|
209
|
+
}
|
|
210
|
+
synchronized (mHeadlessChangeCallbacks) {
|
|
211
|
+
for (OnHeadlessChangeCallback callback : mHeadlessChangeCallbacks) {
|
|
212
|
+
callback.onChange(mIsHeadless.get());
|
|
213
|
+
}
|
|
214
|
+
mHeadlessChangeCallbacks.clear();
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
public interface OnHeadlessChangeCallback {
|
|
219
|
+
void onChange(boolean isHeadless);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
public interface OnStateChangeCallback {
|
|
223
|
+
void onChange(boolean isForeground);
|
|
224
|
+
}
|
|
225
|
+
}
|
package/app.plugin.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
module.exports = require('./expo/plugin/build');
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
declare module "react-native-background-fetch" {
|
|
2
|
+
|
|
3
|
+
interface AbstractConfig {
|
|
4
|
+
/**
|
|
5
|
+
* [Android only] Set false to continue background-fetch events after user terminates the app. Default to true.
|
|
6
|
+
*/
|
|
7
|
+
stopOnTerminate?:boolean;
|
|
8
|
+
/**
|
|
9
|
+
* [Android only] Set true to initiate background-fetch events when the device is rebooted. Defaults to false.
|
|
10
|
+
*/
|
|
11
|
+
startOnBoot?:boolean;
|
|
12
|
+
/**
|
|
13
|
+
* [Android only] Set true to enable Headless mechanism for handling fetch events after app termination.
|
|
14
|
+
*/
|
|
15
|
+
enableHeadless?:boolean;
|
|
16
|
+
/**
|
|
17
|
+
* [Android only]
|
|
18
|
+
*/
|
|
19
|
+
forceAlarmManager?:boolean;
|
|
20
|
+
/**
|
|
21
|
+
* [Android only] Set detailed description of the kind of network your job requires.
|
|
22
|
+
*
|
|
23
|
+
* If your job doesn't need a network connection, you don't need to use this option, as the default is [[BackgroundFetch.NEWORK_TYPE_NONE]].
|
|
24
|
+
*
|
|
25
|
+
* Calling this method defines network as a strict requirement for your job. If the network requested is not available your job will never run.
|
|
26
|
+
*/
|
|
27
|
+
requiredNetworkType?:NetworkType;
|
|
28
|
+
/**
|
|
29
|
+
* [Android only] Specify that to run this job, the device's battery level must not be low.
|
|
30
|
+
*
|
|
31
|
+
* This defaults to false. If true, the job will only run when the battery level is not low, which is generally the point where the user is given a "low battery" warning.
|
|
32
|
+
*/
|
|
33
|
+
requiresBatteryNotLow?:boolean;
|
|
34
|
+
/**
|
|
35
|
+
* [Android only] Specify that to run this job, the device's available storage must not be low.
|
|
36
|
+
*
|
|
37
|
+
* This defaults to false. If true, the job will only run when the device is not in a low storage state, which is generally the point where the user is given a "low storage" warning.
|
|
38
|
+
*/
|
|
39
|
+
requiresStorageNotLow?:boolean;
|
|
40
|
+
/**
|
|
41
|
+
* [Android only] Specify that to run this job, the device must be charging (or be a non-battery-powered device connected to permanent power, such as Android TV devices). This defaults to false.
|
|
42
|
+
*/
|
|
43
|
+
requiresCharging?:boolean;
|
|
44
|
+
/**
|
|
45
|
+
* [Android only] When set true, ensure that this job will not run if the device is in active use.
|
|
46
|
+
*
|
|
47
|
+
* The default state is false: that is, the for the job to be runnable even when someone is interacting with the device.
|
|
48
|
+
*
|
|
49
|
+
* This state is a loose definition provided by the system. In general, it means that the device is not currently being used interactively, and has not been in use for some time. As such, it is a good time to perform resource heavy jobs. Bear in mind that battery usage will still be attributed to your application, and surfaced to the user in battery stats.
|
|
50
|
+
*/
|
|
51
|
+
requiresDeviceIdle?:boolean;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
interface TaskConfig extends AbstractConfig {
|
|
55
|
+
/**
|
|
56
|
+
* The name of the task. This will be used with [[BackgroundFetch.finish]] to signal task-completion.
|
|
57
|
+
*/
|
|
58
|
+
taskId:string;
|
|
59
|
+
/**
|
|
60
|
+
* The minimum interval in milliseconds to execute this task.
|
|
61
|
+
*/
|
|
62
|
+
delay:number;
|
|
63
|
+
/**
|
|
64
|
+
* Whether this task will continue executing or just a "one-shot".
|
|
65
|
+
*/
|
|
66
|
+
periodic?:boolean;
|
|
67
|
+
/**
|
|
68
|
+
* When set true, ensure that this job will run if the device has network connection.
|
|
69
|
+
*/
|
|
70
|
+
requiresNetworkConnectivity?:boolean;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
interface BackgroundFetchConfig extends AbstractConfig {
|
|
74
|
+
/**
|
|
75
|
+
* The minimum interval in minutes to execute background fetch events. Defaults to 15 minutes. Minimum is 15 minutes.
|
|
76
|
+
*/
|
|
77
|
+
minimumFetchInterval?:number;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* | BackgroundFetchStatus | Description |
|
|
82
|
+
* |------------------------------------|-------------------------------------------------|
|
|
83
|
+
* | BackgroundFetch.STATUS_RESTRICTED | Background fetch updates are unavailable and the user cannot enable them again. For example, this status can occur when parental controls are in effect for the current user. |
|
|
84
|
+
* | BackgroundFetch.STATUS_DENIED | The user explicitly disabled background behavior for this app or for the whole system. |
|
|
85
|
+
* | BackgroundFetch.STATUS_AVAILABLE | Background fetch is available and enabled. |
|
|
86
|
+
*/
|
|
87
|
+
type BackgroundFetchStatus = 0 | 1 | 2;
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* | NetworkType | Description |
|
|
91
|
+
* |---------------------------------------|---------------------------------------------------------------|
|
|
92
|
+
* | BackgroundFetch.NETWORK_TYPE_NONE | This job doesn't care about network constraints, either any or none. |
|
|
93
|
+
* | BackgroundFetch.NETWORK_TYPE_ANY | This job requires network connectivity. |
|
|
94
|
+
* | BackgroundFetch.NETWORK_TYPE_CELLULAR | This job requires network connectivity that is a cellular network. |
|
|
95
|
+
* | BackgroundFetch.NETWORK_TYPE_UNMETERED | This job requires network connectivity that is unmetered. |
|
|
96
|
+
* | BackgroundFetch.NETWORK_TYPE_NOT_ROAMING | This job requires network connectivity that is not roaming. |
|
|
97
|
+
*/
|
|
98
|
+
type NetworkType = 0 | 1 | 2 | 3 | 4;
|
|
99
|
+
|
|
100
|
+
export interface HeadlessEvent {
|
|
101
|
+
/**
|
|
102
|
+
* The name of the task. This will be used with [[BackgroundFetch.finish]] to signal task-completion.
|
|
103
|
+
*/
|
|
104
|
+
taskId: string;
|
|
105
|
+
/**
|
|
106
|
+
* Whether this event is a timeout event or not. If true stop all processing and call [[BackgroundFetch.finish]] immediately.
|
|
107
|
+
*/
|
|
108
|
+
timeout: boolean;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* BackgroundFetch is a module to receive periodic callbacks (min every 15 min) while your app is running in the background or terminated.
|
|
113
|
+
*/
|
|
114
|
+
export default class BackgroundFetch {
|
|
115
|
+
/**
|
|
116
|
+
* Background fetch updates are unavailable and the user cannot enable them again. For example, this status can occur when parental controls are in effect for the current user.
|
|
117
|
+
*/
|
|
118
|
+
static STATUS_RESTRICTED: BackgroundFetchStatus;
|
|
119
|
+
/**
|
|
120
|
+
* The user explicitly disabled background behavior for this app or for the whole system.
|
|
121
|
+
*/
|
|
122
|
+
static STATUS_DENIED: BackgroundFetchStatus;
|
|
123
|
+
/**
|
|
124
|
+
* Background fetch is available and enabled.
|
|
125
|
+
*/
|
|
126
|
+
static STATUS_AVAILABLE: BackgroundFetchStatus;
|
|
127
|
+
/**
|
|
128
|
+
* This job doesn't care about network constraints, either any or none.
|
|
129
|
+
*/
|
|
130
|
+
static NETWORK_TYPE_NONE: NetworkType;
|
|
131
|
+
/**
|
|
132
|
+
* This job requires network connectivity.
|
|
133
|
+
*/
|
|
134
|
+
static NETWORK_TYPE_ANY: NetworkType;
|
|
135
|
+
/**
|
|
136
|
+
* This job requires network connectivity that is a cellular network.
|
|
137
|
+
*/
|
|
138
|
+
static NETWORK_TYPE_CELLULAR: NetworkType;
|
|
139
|
+
/**
|
|
140
|
+
* This job requires network connectivity that is unmetered.
|
|
141
|
+
*/
|
|
142
|
+
static NETWORK_TYPE_UNMETERED: NetworkType;
|
|
143
|
+
/**
|
|
144
|
+
* This job requires network connectivity that is not roaming.
|
|
145
|
+
*/
|
|
146
|
+
static NETWORK_TYPE_NOT_ROAMING: NetworkType;
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Initial configuration of BackgroundFetch, including config-options and Fetch-callback. The [[start]] method will automatically be executed.
|
|
150
|
+
*/
|
|
151
|
+
static configure(config:BackgroundFetchConfig, onEvent:(taskId:string) => void, onTimeout?:(taskId:string) => void):Promise<BackgroundFetchStatus>;
|
|
152
|
+
/**
|
|
153
|
+
* Add an extra fetch event listener in addition to the one initially provided to [[configure]].
|
|
154
|
+
* @event
|
|
155
|
+
*/
|
|
156
|
+
static scheduleTask(config:TaskConfig):Promise<boolean>;
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Start subscribing to fetch events.
|
|
160
|
+
*/
|
|
161
|
+
static start():Promise<BackgroundFetchStatus>;
|
|
162
|
+
/**
|
|
163
|
+
* Stop subscribing to fetch events.
|
|
164
|
+
*/
|
|
165
|
+
static stop(taskId?:string):Promise<boolean>;
|
|
166
|
+
/**
|
|
167
|
+
* You must execute [[finish]] within your fetch-callback to signal completion of your task.
|
|
168
|
+
*/
|
|
169
|
+
static finish(taskId?:string):void;
|
|
170
|
+
/**
|
|
171
|
+
* Query the BackgroundFetch API status
|
|
172
|
+
*
|
|
173
|
+
* | BackgroundFetchStatus | Description |
|
|
174
|
+
* |------------------------------------|-------------------------------------------------|
|
|
175
|
+
* | BackgroundFetch.STATUS_RESTRICTED | Background fetch updates are unavailable and the user cannot enable them again. For example, this status can occur when parental controls are in effect for the current user. |
|
|
176
|
+
* | BackgroundFetch.STATUS_DENIED | The user explicitly disabled background behavior for this app or for the whole system. |
|
|
177
|
+
* | BackgroundFetch.STATUS_AVAILABLE | Background fetch is available and enabled. |
|
|
178
|
+
*/
|
|
179
|
+
static status(callback?:(status:BackgroundFetchStatus) => void):Promise<BackgroundFetchStatus>;
|
|
180
|
+
/**
|
|
181
|
+
* [Android only] Register a function to execute when the app is terminated. Requires stopOnTerminate: false.
|
|
182
|
+
*/
|
|
183
|
+
static registerHeadlessTask(task:(event:HeadlessEvent) => Promise<void>):void;
|
|
184
|
+
}
|
|
185
|
+
}
|
package/index.js
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
NativeModules,
|
|
5
|
+
NativeEventEmitter,
|
|
6
|
+
AppRegistry
|
|
7
|
+
} from "react-native";
|
|
8
|
+
|
|
9
|
+
const RNBackgroundFetch = NativeModules.RNBackgroundFetch;
|
|
10
|
+
const EventEmitter = new NativeEventEmitter(RNBackgroundFetch);
|
|
11
|
+
|
|
12
|
+
const EVENT_FETCH = "fetch";
|
|
13
|
+
const TAG = "RNBackgroundFetch";
|
|
14
|
+
const EVENTS = ["fetch"];
|
|
15
|
+
|
|
16
|
+
const STATUS_RESTRICTED = 0;
|
|
17
|
+
const STATUS_DENIED = 1;
|
|
18
|
+
const STATUS_AVAILABLE = 2;
|
|
19
|
+
|
|
20
|
+
const NETWORK_TYPE_NONE = 0;
|
|
21
|
+
const NETWORK_TYPE_ANY = 1;
|
|
22
|
+
const NETWORK_TYPE_UNMETERED = 2;
|
|
23
|
+
const NETWORK_TYPE_NOT_ROAMING = 3;
|
|
24
|
+
const NETWORK_TYPE_CELLULAR = 4;
|
|
25
|
+
|
|
26
|
+
export default class BackgroundFetch {
|
|
27
|
+
static get STATUS_RESTRICTED() { return STATUS_RESTRICTED; }
|
|
28
|
+
static get STATUS_DENIED() { return STATUS_DENIED; }
|
|
29
|
+
static get STATUS_AVAILABLE() { return STATUS_AVAILABLE; }
|
|
30
|
+
|
|
31
|
+
static get NETWORK_TYPE_NONE() { return NETWORK_TYPE_NONE; }
|
|
32
|
+
static get NETWORK_TYPE_ANY() { return NETWORK_TYPE_ANY; }
|
|
33
|
+
static get NETWORK_TYPE_UNMETERED() { return NETWORK_TYPE_UNMETERED; }
|
|
34
|
+
static get NETWORK_TYPE_NOT_ROAMING() { return NETWORK_TYPE_NOT_ROAMING; }
|
|
35
|
+
static get NETWORK_TYPE_CELLULAR() { return NETWORK_TYPE_CELLULAR; }
|
|
36
|
+
|
|
37
|
+
static configure(config, onEvent, onTimeout) {
|
|
38
|
+
if (typeof(onEvent) !== 'function') {
|
|
39
|
+
throw "BackgroundFetch requires an event callback at 2nd argument";
|
|
40
|
+
}
|
|
41
|
+
if (typeof(onTimeout) !== 'function') {
|
|
42
|
+
console.warn("[BackgroundFetch] configure: You did not provide a 3rd argument onTimeout callback. This callback is a signal from the OS that your allowed background time is about to expire. Use this callback to finish what you're doing and immediately call BackgroundFetch.finish(taskId)");
|
|
43
|
+
onTimeout = (taskId) => {
|
|
44
|
+
console.warn('[BackgroundFetch] default onTimeout callback fired. You should provide your own onTimeout callback to .configure(options, onEvent, onTimeout)');
|
|
45
|
+
BackgroundFetch.finish(taskId);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
EventEmitter.removeAllListeners(EVENT_FETCH);
|
|
49
|
+
|
|
50
|
+
EventEmitter.addListener(EVENT_FETCH, (event) => {
|
|
51
|
+
if (!event.timeout) {
|
|
52
|
+
onEvent(event.taskId);
|
|
53
|
+
} else {
|
|
54
|
+
onTimeout(event.taskId);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
config = config || {};
|
|
59
|
+
|
|
60
|
+
return new Promise((resolve, reject) => {
|
|
61
|
+
let success = (status) => { resolve(status) };
|
|
62
|
+
let failure = (status) => { reject(status) };
|
|
63
|
+
RNBackgroundFetch.configure(config, success, failure);
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
static scheduleTask(config) {
|
|
68
|
+
return new Promise((resolve, reject) => {
|
|
69
|
+
let success = (success) => { resolve(success) }
|
|
70
|
+
let failure = (error) => { reject(error) }
|
|
71
|
+
RNBackgroundFetch.scheduleTask(config, success, failure);
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Register HeadlessTask
|
|
77
|
+
*/
|
|
78
|
+
static registerHeadlessTask(task) {
|
|
79
|
+
AppRegistry.registerHeadlessTask("BackgroundFetch", () => task);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
static start() {
|
|
83
|
+
return new Promise((resolve, reject) => {
|
|
84
|
+
let success = (status) => {
|
|
85
|
+
resolve(status);
|
|
86
|
+
}
|
|
87
|
+
let failure = (error) => {
|
|
88
|
+
reject(error);
|
|
89
|
+
}
|
|
90
|
+
RNBackgroundFetch.start(success, failure);
|
|
91
|
+
})
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
static stop(taskId) {
|
|
95
|
+
return new Promise((resolve, reject) => {
|
|
96
|
+
let success = (success) => {
|
|
97
|
+
resolve(success);
|
|
98
|
+
}
|
|
99
|
+
let failure = (error) => {
|
|
100
|
+
reject(error);
|
|
101
|
+
}
|
|
102
|
+
RNBackgroundFetch.stop(taskId, success, failure);
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
static finish(taskId) {
|
|
107
|
+
RNBackgroundFetch.finish(taskId);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
static status(callback) {
|
|
111
|
+
if (typeof(callback) === 'function') {
|
|
112
|
+
return RNBackgroundFetch.status(callback);
|
|
113
|
+
}
|
|
114
|
+
return new Promise((resolve, reject) => {
|
|
115
|
+
RNBackgroundFetch.status(resolve);
|
|
116
|
+
})
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
//
|
|
2
|
+
// RNBackgroundGeolocation+AppDelegate.m
|
|
3
|
+
// RNBackgroundGeolocationSample
|
|
4
|
+
//
|
|
5
|
+
// Created by Christopher Scott on 2016-08-01.
|
|
6
|
+
// Copyright © 2016 Facebook. All rights reserved.
|
|
7
|
+
//
|
|
8
|
+
|
|
9
|
+
#import <Foundation/Foundation.h>
|
|
10
|
+
#import "AppDelegate.h"
|
|
11
|
+
#import <TSBackgroundFetch/TSBackgroundFetch.h>
|
|
12
|
+
|
|
13
|
+
@implementation AppDelegate(AppDelegate)
|
|
14
|
+
|
|
15
|
+
-(void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
|
|
16
|
+
{
|
|
17
|
+
NSLog(@"RNBackgroundFetch AppDelegate received fetch event");
|
|
18
|
+
TSBackgroundFetch *fetchManager = [TSBackgroundFetch sharedInstance];
|
|
19
|
+
[fetchManager performFetchWithCompletionHandler:completionHandler applicationState:application.applicationState];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
@end
|