@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.
Files changed (46) hide show
  1. package/.yarnignore +7 -0
  2. package/LICENSE +21 -0
  3. package/README.md +502 -0
  4. package/RNBackgroundFetch.podspec +25 -0
  5. package/android/build.gradle +40 -0
  6. package/android/proguard-rules.pro +2 -0
  7. package/android/src/main/AndroidManifest.xml +15 -0
  8. package/android/src/main/java/com/transistorsoft/rnbackgroundfetch/HeadlessTask.java +119 -0
  9. package/android/src/main/java/com/transistorsoft/rnbackgroundfetch/RNBackgroundFetchModule.java +185 -0
  10. package/android/src/main/java/com/transistorsoft/rnbackgroundfetch/RNBackgroundFetchPackage.java +29 -0
  11. package/android/src/main/java/com/transistorsoft/tsbackgroundfetch/BGTask.java +305 -0
  12. package/android/src/main/java/com/transistorsoft/tsbackgroundfetch/BackgroundFetch.java +297 -0
  13. package/android/src/main/java/com/transistorsoft/tsbackgroundfetch/BackgroundFetchConfig.java +362 -0
  14. package/android/src/main/java/com/transistorsoft/tsbackgroundfetch/BootReceiver.java +24 -0
  15. package/android/src/main/java/com/transistorsoft/tsbackgroundfetch/FetchAlarmReceiver.java +40 -0
  16. package/android/src/main/java/com/transistorsoft/tsbackgroundfetch/FetchJobService.java +106 -0
  17. package/android/src/main/java/com/transistorsoft/tsbackgroundfetch/LifecycleManager.java +225 -0
  18. package/app.plugin.js +1 -0
  19. package/index.d.ts +185 -0
  20. package/index.js +119 -0
  21. package/ios/RNBackgroundFetch/RNBackgroundFetch+AppDelegate.m +22 -0
  22. package/ios/RNBackgroundFetch/RNBackgroundFetch.h +18 -0
  23. package/ios/RNBackgroundFetch/RNBackgroundFetch.m +167 -0
  24. package/ios/RNBackgroundFetch/TSBackgroundFetch.xcframework/Info.plist +57 -0
  25. package/ios/RNBackgroundFetch/TSBackgroundFetch.xcframework/ios-arm64_armv7/TSBackgroundFetch.framework/Headers/TSBackgroundFetch.h +47 -0
  26. package/ios/RNBackgroundFetch/TSBackgroundFetch.xcframework/ios-arm64_armv7/TSBackgroundFetch.framework/Info.plist +0 -0
  27. package/ios/RNBackgroundFetch/TSBackgroundFetch.xcframework/ios-arm64_armv7/TSBackgroundFetch.framework/Modules/module.modulemap +6 -0
  28. package/ios/RNBackgroundFetch/TSBackgroundFetch.xcframework/ios-arm64_armv7/TSBackgroundFetch.framework/TSBackgroundFetch +0 -0
  29. package/ios/RNBackgroundFetch/TSBackgroundFetch.xcframework/ios-arm64_i386_x86_64-simulator/TSBackgroundFetch.framework/Headers/TSBackgroundFetch.h +47 -0
  30. package/ios/RNBackgroundFetch/TSBackgroundFetch.xcframework/ios-arm64_i386_x86_64-simulator/TSBackgroundFetch.framework/Info.plist +0 -0
  31. package/ios/RNBackgroundFetch/TSBackgroundFetch.xcframework/ios-arm64_i386_x86_64-simulator/TSBackgroundFetch.framework/Modules/module.modulemap +6 -0
  32. package/ios/RNBackgroundFetch/TSBackgroundFetch.xcframework/ios-arm64_i386_x86_64-simulator/TSBackgroundFetch.framework/TSBackgroundFetch +0 -0
  33. package/ios/RNBackgroundFetch/TSBackgroundFetch.xcframework/ios-arm64_i386_x86_64-simulator/TSBackgroundFetch.framework/_CodeSignature/CodeDirectory +0 -0
  34. package/ios/RNBackgroundFetch/TSBackgroundFetch.xcframework/ios-arm64_i386_x86_64-simulator/TSBackgroundFetch.framework/_CodeSignature/CodeRequirements +0 -0
  35. package/ios/RNBackgroundFetch/TSBackgroundFetch.xcframework/ios-arm64_i386_x86_64-simulator/TSBackgroundFetch.framework/_CodeSignature/CodeRequirements-1 +0 -0
  36. package/ios/RNBackgroundFetch/TSBackgroundFetch.xcframework/ios-arm64_i386_x86_64-simulator/TSBackgroundFetch.framework/_CodeSignature/CodeResources +132 -0
  37. package/ios/RNBackgroundFetch/TSBackgroundFetch.xcframework/ios-arm64_i386_x86_64-simulator/TSBackgroundFetch.framework/_CodeSignature/CodeSignature +0 -0
  38. package/ios/RNBackgroundFetch/TSBackgroundFetch.xcframework/ios-arm64_x86_64-maccatalyst/TSBackgroundFetch.framework/Headers/TSBackgroundFetch.h +47 -0
  39. package/ios/RNBackgroundFetch/TSBackgroundFetch.xcframework/ios-arm64_x86_64-maccatalyst/TSBackgroundFetch.framework/Modules/module.modulemap +6 -0
  40. package/ios/RNBackgroundFetch/TSBackgroundFetch.xcframework/ios-arm64_x86_64-maccatalyst/TSBackgroundFetch.framework/Resources/Info.plist +52 -0
  41. package/ios/RNBackgroundFetch/TSBackgroundFetch.xcframework/ios-arm64_x86_64-maccatalyst/TSBackgroundFetch.framework/TSBackgroundFetch +0 -0
  42. package/ios/RNBackgroundFetch.xcodeproj/project.pbxproj +310 -0
  43. package/ios/RNBackgroundFetch.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
  44. package/ios/RNBackgroundFetch.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
  45. package/ios/RNBackgroundFetch.xcodeproj/project.xcworkspace/xcuserdata/chris.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  46. 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
+ }
@@ -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
+ }
@@ -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
+ }