@capgo/capacitor-background-task 8.1.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/CapgoCapacitorBackgroundTask.podspec +17 -0
- package/LICENSE +373 -0
- package/Package.swift +28 -0
- package/README.md +421 -0
- package/android/build.gradle +61 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/app/capgo/backgroundtask/BackgroundTask.java +226 -0
- package/android/src/main/java/app/capgo/backgroundtask/BackgroundTaskPlugin.java +132 -0
- package/android/src/main/java/app/capgo/backgroundtask/BackgroundTaskWorker.java +25 -0
- package/android/src/main/res/.gitkeep +0 -0
- package/dist/docs.json +407 -0
- package/dist/esm/definitions.d.ts +192 -0
- package/dist/esm/definitions.js +29 -0
- package/dist/esm/definitions.js.map +1 -0
- package/dist/esm/index.d.ts +5 -0
- package/dist/esm/index.js +141 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/web.d.ts +13 -0
- package/dist/esm/web.js +52 -0
- package/dist/esm/web.js.map +1 -0
- package/dist/plugin.cjs.js +228 -0
- package/dist/plugin.cjs.js.map +1 -0
- package/dist/plugin.js +231 -0
- package/dist/plugin.js.map +1 -0
- package/ios/Sources/BackgroundTaskPlugin/BackgroundTask.swift +269 -0
- package/ios/Sources/BackgroundTaskPlugin/BackgroundTaskPlugin.swift +101 -0
- package/ios/Tests/BackgroundTaskPluginTests/BackgroundTaskTests.swift +18 -0
- package/package.json +89 -0
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
package app.capgo.backgroundtask;
|
|
2
|
+
|
|
3
|
+
import android.content.Context;
|
|
4
|
+
import android.content.SharedPreferences;
|
|
5
|
+
import androidx.work.Configuration;
|
|
6
|
+
import androidx.work.Constraints;
|
|
7
|
+
import androidx.work.Data;
|
|
8
|
+
import androidx.work.ExistingPeriodicWorkPolicy;
|
|
9
|
+
import androidx.work.NetworkType;
|
|
10
|
+
import androidx.work.PeriodicWorkRequest;
|
|
11
|
+
import androidx.work.WorkManager;
|
|
12
|
+
import com.getcapacitor.JSArray;
|
|
13
|
+
import com.getcapacitor.JSObject;
|
|
14
|
+
import com.getcapacitor.Logger;
|
|
15
|
+
import java.util.ArrayList;
|
|
16
|
+
import java.util.HashSet;
|
|
17
|
+
import java.util.Iterator;
|
|
18
|
+
import java.util.List;
|
|
19
|
+
import java.util.Set;
|
|
20
|
+
import java.util.UUID;
|
|
21
|
+
import java.util.concurrent.TimeUnit;
|
|
22
|
+
import org.json.JSONArray;
|
|
23
|
+
import org.json.JSONException;
|
|
24
|
+
import org.json.JSONObject;
|
|
25
|
+
|
|
26
|
+
public class BackgroundTask {
|
|
27
|
+
|
|
28
|
+
static final String EXTRA_TASK_NAME = "taskName";
|
|
29
|
+
static final int STATUS_RESTRICTED = 1;
|
|
30
|
+
static final int STATUS_AVAILABLE = 2;
|
|
31
|
+
static final int RESULT_SUCCESS = 1;
|
|
32
|
+
static final int DEFAULT_MINIMUM_INTERVAL_MINUTES = 12 * 60;
|
|
33
|
+
static final int MINIMUM_INTERVAL_MINUTES = 15;
|
|
34
|
+
|
|
35
|
+
private static final String PREFS_NAME = "CapgoBackgroundTask";
|
|
36
|
+
private static final String KEY_TASK_NAMES = "taskNames";
|
|
37
|
+
private static final String KEY_TASK_PREFIX = "task.";
|
|
38
|
+
private static final String KEY_PENDING_RUNS = "pendingRuns";
|
|
39
|
+
private static final String KEY_LAST_RESULT_PREFIX = "lastResult.";
|
|
40
|
+
private static final String WORK_TAG = "capgo_background_task";
|
|
41
|
+
private static volatile boolean workManagerInitialized = false;
|
|
42
|
+
|
|
43
|
+
public void registerTask(Context context, String taskName, int minimumInterval, boolean requiresNetwork) throws JSONException {
|
|
44
|
+
int normalizedInterval = normalizeInterval(minimumInterval);
|
|
45
|
+
JSONObject config = new JSONObject();
|
|
46
|
+
config.put("taskName", taskName);
|
|
47
|
+
config.put("minimumInterval", normalizedInterval);
|
|
48
|
+
config.put("requiresNetwork", requiresNetwork);
|
|
49
|
+
|
|
50
|
+
Set<String> taskNames = getTaskNames(context);
|
|
51
|
+
taskNames.add(taskName);
|
|
52
|
+
prefs(context).edit().putStringSet(KEY_TASK_NAMES, taskNames).putString(KEY_TASK_PREFIX + taskName, config.toString()).apply();
|
|
53
|
+
|
|
54
|
+
scheduleTask(context, taskName, normalizedInterval, requiresNetwork);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
public void unregisterTask(Context context, String taskName) {
|
|
58
|
+
Set<String> taskNames = getTaskNames(context);
|
|
59
|
+
taskNames.remove(taskName);
|
|
60
|
+
prefs(context).edit().putStringSet(KEY_TASK_NAMES, taskNames).remove(KEY_TASK_PREFIX + taskName).apply();
|
|
61
|
+
initializeWorkManager(context);
|
|
62
|
+
WorkManager.getInstance(context).cancelUniqueWork(uniqueWorkName(taskName));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
public boolean isTaskRegistered(Context context, String taskName) {
|
|
66
|
+
return getTaskNames(context).contains(taskName);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
public JSArray getRegisteredTasks(Context context) {
|
|
70
|
+
JSArray tasks = new JSArray();
|
|
71
|
+
for (String taskName : getTaskNames(context)) {
|
|
72
|
+
tasks.put(taskName);
|
|
73
|
+
}
|
|
74
|
+
return tasks;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
public JSArray getPendingRuns(Context context) {
|
|
78
|
+
return toJSArray(readPendingRuns(context));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
public int getStatus() {
|
|
82
|
+
return STATUS_AVAILABLE;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
public boolean triggerTaskWorkerForTesting(Context context) {
|
|
86
|
+
boolean triggered = false;
|
|
87
|
+
for (String taskName : getTaskNames(context)) {
|
|
88
|
+
recordAndDispatchRun(context, taskName, true);
|
|
89
|
+
triggered = true;
|
|
90
|
+
}
|
|
91
|
+
return triggered;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
public void finish(Context context, String taskId, String taskName, int result) {
|
|
95
|
+
removePendingRun(context, taskId);
|
|
96
|
+
if (taskName != null && !taskName.isEmpty()) {
|
|
97
|
+
prefs(context).edit().putInt(KEY_LAST_RESULT_PREFIX + taskName, result).apply();
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
static void recordAndDispatchRun(Context context, String taskName, boolean isTest) {
|
|
102
|
+
JSObject run = new JSObject();
|
|
103
|
+
long timestamp = System.currentTimeMillis();
|
|
104
|
+
String taskId = UUID.randomUUID().toString();
|
|
105
|
+
|
|
106
|
+
run.put("taskName", taskName);
|
|
107
|
+
run.put("taskId", taskId);
|
|
108
|
+
run.put("timestamp", timestamp);
|
|
109
|
+
run.put("test", isTest);
|
|
110
|
+
|
|
111
|
+
addPendingRun(context, run);
|
|
112
|
+
BackgroundTaskPlugin.dispatchRun(run);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
private void scheduleTask(Context context, String taskName, int minimumInterval, boolean requiresNetwork) {
|
|
116
|
+
initializeWorkManager(context);
|
|
117
|
+
|
|
118
|
+
Constraints.Builder constraintsBuilder = new Constraints.Builder();
|
|
119
|
+
constraintsBuilder.setRequiredNetworkType(requiresNetwork ? NetworkType.CONNECTED : NetworkType.NOT_REQUIRED);
|
|
120
|
+
|
|
121
|
+
Data inputData = new Data.Builder().putString(EXTRA_TASK_NAME, taskName).build();
|
|
122
|
+
PeriodicWorkRequest workRequest = new PeriodicWorkRequest.Builder(
|
|
123
|
+
BackgroundTaskWorker.class,
|
|
124
|
+
normalizeInterval(minimumInterval),
|
|
125
|
+
TimeUnit.MINUTES
|
|
126
|
+
)
|
|
127
|
+
.setInputData(inputData)
|
|
128
|
+
.setConstraints(constraintsBuilder.build())
|
|
129
|
+
.addTag(WORK_TAG)
|
|
130
|
+
.addTag(uniqueWorkName(taskName))
|
|
131
|
+
.build();
|
|
132
|
+
|
|
133
|
+
WorkManager.getInstance(context).enqueueUniquePeriodicWork(
|
|
134
|
+
uniqueWorkName(taskName),
|
|
135
|
+
ExistingPeriodicWorkPolicy.UPDATE,
|
|
136
|
+
workRequest
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
private static synchronized void initializeWorkManager(Context context) {
|
|
141
|
+
if (workManagerInitialized) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
try {
|
|
146
|
+
Configuration config = new Configuration.Builder().setMinimumLoggingLevel(android.util.Log.INFO).build();
|
|
147
|
+
WorkManager.initialize(context.getApplicationContext(), config);
|
|
148
|
+
} catch (IllegalStateException exception) {
|
|
149
|
+
Logger.debug("BackgroundTask WorkManager already initialized");
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
workManagerInitialized = true;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
private static int normalizeInterval(int minimumInterval) {
|
|
156
|
+
if (minimumInterval <= 0) {
|
|
157
|
+
return DEFAULT_MINIMUM_INTERVAL_MINUTES;
|
|
158
|
+
}
|
|
159
|
+
return Math.max(MINIMUM_INTERVAL_MINUTES, minimumInterval);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
private static String uniqueWorkName(String taskName) {
|
|
163
|
+
return WORK_TAG + "." + taskName;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
private static SharedPreferences prefs(Context context) {
|
|
167
|
+
return context.getApplicationContext().getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
private static Set<String> getTaskNames(Context context) {
|
|
171
|
+
return new HashSet<>(prefs(context).getStringSet(KEY_TASK_NAMES, new HashSet<>()));
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
private static synchronized void addPendingRun(Context context, JSObject run) {
|
|
175
|
+
JSONArray runs = readPendingRuns(context);
|
|
176
|
+
runs.put(run);
|
|
177
|
+
prefs(context).edit().putString(KEY_PENDING_RUNS, runs.toString()).apply();
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
private static synchronized void removePendingRun(Context context, String taskId) {
|
|
181
|
+
JSONArray runs = readPendingRuns(context);
|
|
182
|
+
JSONArray nextRuns = new JSONArray();
|
|
183
|
+
|
|
184
|
+
for (int index = 0; index < runs.length(); index++) {
|
|
185
|
+
JSONObject run = runs.optJSONObject(index);
|
|
186
|
+
if (run == null || taskId.equals(run.optString("taskId"))) {
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
nextRuns.put(run);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
prefs(context).edit().putString(KEY_PENDING_RUNS, nextRuns.toString()).apply();
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
private static JSONArray readPendingRuns(Context context) {
|
|
196
|
+
String rawRuns = prefs(context).getString(KEY_PENDING_RUNS, "[]");
|
|
197
|
+
try {
|
|
198
|
+
return new JSONArray(rawRuns);
|
|
199
|
+
} catch (JSONException exception) {
|
|
200
|
+
return new JSONArray();
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
private static JSArray toJSArray(JSONArray array) {
|
|
205
|
+
JSArray result = new JSArray();
|
|
206
|
+
for (int index = 0; index < array.length(); index++) {
|
|
207
|
+
Object value = array.opt(index);
|
|
208
|
+
if (value instanceof JSONObject) {
|
|
209
|
+
result.put(toJSObject((JSONObject) value));
|
|
210
|
+
} else {
|
|
211
|
+
result.put(value);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return result;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
static JSObject toJSObject(JSONObject object) {
|
|
218
|
+
JSObject result = new JSObject();
|
|
219
|
+
Iterator<String> keys = object.keys();
|
|
220
|
+
while (keys.hasNext()) {
|
|
221
|
+
String key = keys.next();
|
|
222
|
+
result.put(key, object.opt(key));
|
|
223
|
+
}
|
|
224
|
+
return result;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
package app.capgo.backgroundtask;
|
|
2
|
+
|
|
3
|
+
import com.getcapacitor.JSArray;
|
|
4
|
+
import com.getcapacitor.JSObject;
|
|
5
|
+
import com.getcapacitor.Plugin;
|
|
6
|
+
import com.getcapacitor.PluginCall;
|
|
7
|
+
import com.getcapacitor.PluginMethod;
|
|
8
|
+
import com.getcapacitor.annotation.CapacitorPlugin;
|
|
9
|
+
import java.lang.ref.WeakReference;
|
|
10
|
+
import org.json.JSONException;
|
|
11
|
+
import org.json.JSONObject;
|
|
12
|
+
|
|
13
|
+
@CapacitorPlugin(name = "BackgroundTask")
|
|
14
|
+
public class BackgroundTaskPlugin extends Plugin {
|
|
15
|
+
|
|
16
|
+
private static WeakReference<BackgroundTaskPlugin> activePlugin = new WeakReference<>(null);
|
|
17
|
+
private final BackgroundTask implementation = new BackgroundTask();
|
|
18
|
+
|
|
19
|
+
@Override
|
|
20
|
+
public void load() {
|
|
21
|
+
activePlugin = new WeakReference<>(this);
|
|
22
|
+
emitPendingRuns();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
@PluginMethod
|
|
26
|
+
public void registerTask(PluginCall call) {
|
|
27
|
+
String taskName = call.getString("taskName");
|
|
28
|
+
if (isBlank(taskName)) {
|
|
29
|
+
call.reject("taskName is required");
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
JSObject options = call.getObject("options", new JSObject());
|
|
34
|
+
int minimumInterval = options.optInt("minimumInterval", BackgroundTask.DEFAULT_MINIMUM_INTERVAL_MINUTES);
|
|
35
|
+
boolean requiresNetwork = options.optBoolean("requiresNetwork", true);
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
implementation.registerTask(getContext(), taskName, minimumInterval, requiresNetwork);
|
|
39
|
+
call.resolve();
|
|
40
|
+
} catch (JSONException exception) {
|
|
41
|
+
call.reject("Unable to register background task", exception);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
@PluginMethod
|
|
46
|
+
public void unregisterTask(PluginCall call) {
|
|
47
|
+
String taskName = call.getString("taskName");
|
|
48
|
+
if (isBlank(taskName)) {
|
|
49
|
+
call.reject("taskName is required");
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
implementation.unregisterTask(getContext(), taskName);
|
|
54
|
+
call.resolve();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
@PluginMethod
|
|
58
|
+
public void isTaskRegistered(PluginCall call) {
|
|
59
|
+
String taskName = call.getString("taskName");
|
|
60
|
+
if (isBlank(taskName)) {
|
|
61
|
+
call.reject("taskName is required");
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
JSObject response = new JSObject();
|
|
66
|
+
response.put("value", implementation.isTaskRegistered(getContext(), taskName));
|
|
67
|
+
call.resolve(response);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
@PluginMethod
|
|
71
|
+
public void getRegisteredTasks(PluginCall call) {
|
|
72
|
+
JSObject response = new JSObject();
|
|
73
|
+
response.put("tasks", implementation.getRegisteredTasks(getContext()));
|
|
74
|
+
call.resolve(response);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
@PluginMethod
|
|
78
|
+
public void getPendingTaskRuns(PluginCall call) {
|
|
79
|
+
JSObject response = new JSObject();
|
|
80
|
+
response.put("tasks", implementation.getPendingRuns(getContext()));
|
|
81
|
+
call.resolve(response);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
@PluginMethod
|
|
85
|
+
public void getStatus(PluginCall call) {
|
|
86
|
+
JSObject response = new JSObject();
|
|
87
|
+
response.put("status", implementation.getStatus());
|
|
88
|
+
call.resolve(response);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
@PluginMethod
|
|
92
|
+
public void triggerTaskWorkerForTesting(PluginCall call) {
|
|
93
|
+
JSObject response = new JSObject();
|
|
94
|
+
response.put("value", implementation.triggerTaskWorkerForTesting(getContext()));
|
|
95
|
+
call.resolve(response);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
@PluginMethod
|
|
99
|
+
public void finish(PluginCall call) {
|
|
100
|
+
String taskId = call.getString("taskId");
|
|
101
|
+
if (isBlank(taskId)) {
|
|
102
|
+
call.reject("taskId is required");
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
implementation.finish(getContext(), taskId, call.getString("taskName"), call.getInt("result", BackgroundTask.RESULT_SUCCESS));
|
|
107
|
+
call.resolve();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
static void dispatchRun(JSObject run) {
|
|
111
|
+
BackgroundTaskPlugin plugin = activePlugin.get();
|
|
112
|
+
if (plugin != null) {
|
|
113
|
+
plugin.notifyListeners("backgroundTask", run, true);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
private void emitPendingRuns() {
|
|
118
|
+
JSArray pendingRuns = implementation.getPendingRuns(getContext());
|
|
119
|
+
for (int index = 0; index < pendingRuns.length(); index++) {
|
|
120
|
+
Object run = pendingRuns.opt(index);
|
|
121
|
+
if (run instanceof JSObject) {
|
|
122
|
+
notifyListeners("backgroundTask", (JSObject) run, true);
|
|
123
|
+
} else if (run instanceof JSONObject) {
|
|
124
|
+
notifyListeners("backgroundTask", BackgroundTask.toJSObject((JSONObject) run), true);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
private static boolean isBlank(String value) {
|
|
130
|
+
return value == null || value.trim().isEmpty();
|
|
131
|
+
}
|
|
132
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
package app.capgo.backgroundtask;
|
|
2
|
+
|
|
3
|
+
import android.content.Context;
|
|
4
|
+
import androidx.annotation.NonNull;
|
|
5
|
+
import androidx.work.Worker;
|
|
6
|
+
import androidx.work.WorkerParameters;
|
|
7
|
+
|
|
8
|
+
public class BackgroundTaskWorker extends Worker {
|
|
9
|
+
|
|
10
|
+
public BackgroundTaskWorker(@NonNull Context context, @NonNull WorkerParameters workerParameters) {
|
|
11
|
+
super(context, workerParameters);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
@NonNull
|
|
15
|
+
@Override
|
|
16
|
+
public Result doWork() {
|
|
17
|
+
String taskName = getInputData().getString(BackgroundTask.EXTRA_TASK_NAME);
|
|
18
|
+
if (taskName == null || taskName.trim().isEmpty()) {
|
|
19
|
+
return Result.failure();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
BackgroundTask.recordAndDispatchRun(getApplicationContext(), taskName, false);
|
|
23
|
+
return Result.success();
|
|
24
|
+
}
|
|
25
|
+
}
|
|
File without changes
|