react-native-notify-kit 9.4.0 → 9.6.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/README.md +200 -5
- package/android/build.gradle +1 -0
- package/android/src/androidTest/java/app/notifee/core/DoScheduledWorkOrderingTest.java +234 -0
- package/android/src/androidTest/java/app/notifee/core/RebootRecoveryTest.java +569 -0
- package/android/src/androidTest/java/app/notifee/core/database/TimingWorkDataRepository.java +56 -0
- package/android/src/androidTest/java/app/notifee/core/database/WorkDataRepositoryRaceTest.java +221 -0
- package/android/src/androidTest/res/drawable/test_icon.xml +14 -0
- package/android/src/main/java/app/notifee/core/AlarmPermissionBroadcastReceiver.java +17 -6
- package/android/src/main/java/app/notifee/core/InitProvider.java +109 -2
- package/android/src/main/java/app/notifee/core/NotifeeAlarmManager.java +385 -93
- package/android/src/main/java/app/notifee/core/NotificationAlarmReceiver.java +16 -5
- package/android/src/main/java/app/notifee/core/NotificationManager.java +153 -96
- package/android/src/main/java/app/notifee/core/Preferences.java +9 -0
- package/android/src/main/java/app/notifee/core/RebootBroadcastReceiver.java +20 -5
- package/android/src/main/java/app/notifee/core/database/WorkDataRepository.java +38 -34
- package/android/src/main/kotlin/io/invertase/notifee/HeadlessTask.kt +3 -2
- package/android/src/test/java/app/notifee/core/ForegroundServiceTest.java +251 -4
- package/android/src/test/java/app/notifee/core/InitProviderBootCheckTest.java +81 -0
- package/android/src/test/java/app/notifee/core/NotifeeAlarmManagerHandleStaleTest.java +242 -0
- package/android/src/test/java/app/notifee/core/NotifeeAlarmManagerSetAlarmClockTest.java +264 -0
- package/android/src/test/java/app/notifee/core/database/WorkDataRepositoryFutureContractTest.java +279 -0
- package/android/src/test/java/app/notifee/core/model/TimestampTriggerModelTest.java +199 -3
- package/android/src/test/java/io/invertase/notifee/HeadlessTaskConfigTest.kt +34 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/src/version.ts +1 -1
|
@@ -543,15 +543,13 @@ class NotificationManager {
|
|
|
543
543
|
task -> {
|
|
544
544
|
if (notificationType == NOTIFICATION_TYPE_TRIGGER
|
|
545
545
|
|| notificationType == NOTIFICATION_TYPE_ALL) {
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
},
|
|
554
|
-
LISTENING_CACHED_THREAD_POOL);
|
|
546
|
+
// Chain the Room delete onto the alarm-manager cancel so the outer
|
|
547
|
+
// future — and therefore the JS Promise — only completes after Room
|
|
548
|
+
// has drained. Fixes upstream invertase/notifee#549.
|
|
549
|
+
return Futures.transformAsync(
|
|
550
|
+
NotifeeAlarmManager.cancelAllNotifications(),
|
|
551
|
+
ignored -> WorkDataRepository.getInstance(getApplicationContext()).deleteAll(),
|
|
552
|
+
LISTENING_CACHED_THREAD_POOL);
|
|
555
553
|
}
|
|
556
554
|
return Futures.immediateFuture(null);
|
|
557
555
|
},
|
|
@@ -613,9 +611,11 @@ class NotificationManager {
|
|
|
613
611
|
}))
|
|
614
612
|
.continueWith(
|
|
615
613
|
task -> {
|
|
616
|
-
// delete
|
|
614
|
+
// Chain the Room delete so the outer future — and therefore the JS
|
|
615
|
+
// Promise — only completes after Room has drained. Fixes upstream
|
|
616
|
+
// invertase/notifee#549 for the per-id cancel path.
|
|
617
617
|
if (notificationType != NOTIFICATION_TYPE_DISPLAYED) {
|
|
618
|
-
WorkDataRepository.getInstance(getApplicationContext()).deleteByIds(ids);
|
|
618
|
+
return WorkDataRepository.getInstance(getApplicationContext()).deleteByIds(ids);
|
|
619
619
|
}
|
|
620
620
|
return Futures.immediateFuture(null);
|
|
621
621
|
},
|
|
@@ -691,56 +691,75 @@ class NotificationManager {
|
|
|
691
691
|
|
|
692
692
|
static ListenableFuture<Void> createTriggerNotification(
|
|
693
693
|
NotificationModel notificationModel, Bundle triggerBundle) {
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
694
|
+
int triggerType = ObjectUtils.getInt(triggerBundle.get("type"));
|
|
695
|
+
ListenableFuture<Void> scheduleFuture;
|
|
696
|
+
switch (triggerType) {
|
|
697
|
+
case 0:
|
|
698
|
+
scheduleFuture = createTimestampTriggerNotification(notificationModel, triggerBundle);
|
|
699
|
+
break;
|
|
700
|
+
case 1:
|
|
701
|
+
scheduleFuture = createIntervalTriggerNotification(notificationModel, triggerBundle);
|
|
702
|
+
break;
|
|
703
|
+
default:
|
|
704
|
+
scheduleFuture = Futures.immediateFuture(null);
|
|
705
|
+
break;
|
|
706
|
+
}
|
|
705
707
|
|
|
708
|
+
return Futures.transform(
|
|
709
|
+
scheduleFuture,
|
|
710
|
+
unused -> {
|
|
706
711
|
EventBus.post(
|
|
707
712
|
new NotificationEvent(
|
|
708
713
|
NotificationEvent.TYPE_TRIGGER_NOTIFICATION_CREATED, notificationModel));
|
|
709
|
-
|
|
710
714
|
return null;
|
|
711
|
-
}
|
|
715
|
+
},
|
|
716
|
+
LISTENING_CACHED_THREAD_POOL);
|
|
712
717
|
}
|
|
713
718
|
|
|
714
|
-
|
|
719
|
+
// Returns a future that completes only after the Room insert has persisted AND
|
|
720
|
+
// WorkManager has enqueued the periodic work. Chaining Room-first guarantees the
|
|
721
|
+
// worker reads the row on its first fire even if the process is killed between
|
|
722
|
+
// the JS Promise resolving and WorkManager scheduling. Fixes upstream
|
|
723
|
+
// invertase/notifee#549 for the interval-trigger path.
|
|
724
|
+
static ListenableFuture<Void> createIntervalTriggerNotification(
|
|
715
725
|
NotificationModel notificationModel, Bundle triggerBundle) {
|
|
716
726
|
IntervalTriggerModel trigger = IntervalTriggerModel.fromBundle(triggerBundle);
|
|
717
727
|
String uniqueWorkName = "trigger:" + notificationModel.getId();
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
728
|
+
Context context = getApplicationContext();
|
|
729
|
+
|
|
730
|
+
ListenableFuture<Void> insertFuture =
|
|
731
|
+
WorkDataRepository.insertTriggerNotification(
|
|
732
|
+
context, notificationModel, triggerBundle, false);
|
|
733
|
+
|
|
734
|
+
return Futures.transform(
|
|
735
|
+
insertFuture,
|
|
736
|
+
unused -> {
|
|
737
|
+
WorkManager workManager = WorkManager.getInstance(context);
|
|
738
|
+
Data.Builder workDataBuilder =
|
|
739
|
+
new Data.Builder()
|
|
740
|
+
.putString(Worker.KEY_WORK_TYPE, Worker.WORK_TYPE_NOTIFICATION_TRIGGER)
|
|
741
|
+
.putString(Worker.KEY_WORK_REQUEST, Worker.WORK_REQUEST_PERIODIC)
|
|
742
|
+
.putString("id", notificationModel.getId());
|
|
743
|
+
|
|
744
|
+
long interval = trigger.getInterval();
|
|
745
|
+
PeriodicWorkRequest.Builder workRequestBuilder =
|
|
746
|
+
new PeriodicWorkRequest.Builder(Worker.class, interval, trigger.getTimeUnit())
|
|
747
|
+
.setInitialDelay(interval, trigger.getTimeUnit());
|
|
748
|
+
workRequestBuilder.addTag(Worker.WORK_TYPE_NOTIFICATION_TRIGGER);
|
|
749
|
+
workRequestBuilder.addTag(uniqueWorkName);
|
|
750
|
+
workRequestBuilder.setInputData(workDataBuilder.build());
|
|
751
|
+
workManager.enqueueUniquePeriodicWork(
|
|
752
|
+
uniqueWorkName, ExistingPeriodicWorkPolicy.UPDATE, workRequestBuilder.build());
|
|
753
|
+
return null;
|
|
754
|
+
},
|
|
755
|
+
LISTENING_CACHED_THREAD_POOL);
|
|
741
756
|
}
|
|
742
757
|
|
|
743
|
-
|
|
758
|
+
// Returns a future that completes only after the Room insert has persisted AND
|
|
759
|
+
// the AlarmManager / WorkManager schedule call has run. Same rationale as the
|
|
760
|
+
// interval path above. Fixes upstream invertase/notifee#549 for the timestamp
|
|
761
|
+
// trigger path (AlarmManager by default since 9.1.12).
|
|
762
|
+
static ListenableFuture<Void> createTimestampTriggerNotification(
|
|
744
763
|
NotificationModel notificationModel, Bundle triggerBundle) {
|
|
745
764
|
TimestampTriggerModel trigger = TimestampTriggerModel.fromBundle(triggerBundle);
|
|
746
765
|
|
|
@@ -749,52 +768,56 @@ class NotificationManager {
|
|
|
749
768
|
long delay = trigger.getDelay();
|
|
750
769
|
int interval = trigger.getInterval();
|
|
751
770
|
|
|
752
|
-
// Save in DB
|
|
753
771
|
Data.Builder workDataBuilder =
|
|
754
772
|
new Data.Builder()
|
|
755
773
|
.putString(Worker.KEY_WORK_TYPE, Worker.WORK_TYPE_NOTIFICATION_TRIGGER)
|
|
756
774
|
.putString("id", notificationModel.getId());
|
|
757
775
|
|
|
758
776
|
Boolean withAlarmManager = trigger.getWithAlarmManager();
|
|
777
|
+
Context context = getApplicationContext();
|
|
778
|
+
|
|
779
|
+
ListenableFuture<Void> insertFuture =
|
|
780
|
+
WorkDataRepository.insertTriggerNotification(
|
|
781
|
+
context, notificationModel, triggerBundle, withAlarmManager);
|
|
782
|
+
|
|
783
|
+
return Futures.transform(
|
|
784
|
+
insertFuture,
|
|
785
|
+
unused -> {
|
|
786
|
+
if (withAlarmManager) {
|
|
787
|
+
NotifeeAlarmManager.scheduleTimestampTriggerNotification(notificationModel, trigger);
|
|
788
|
+
return null;
|
|
789
|
+
}
|
|
759
790
|
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
workRequestBuilder.addTag(Worker.WORK_TYPE_NOTIFICATION_TRIGGER);
|
|
791
|
-
workRequestBuilder.addTag(uniqueWorkName);
|
|
792
|
-
workRequestBuilder.setInitialDelay(delay, TimeUnit.SECONDS);
|
|
793
|
-
workDataBuilder.putString(Worker.KEY_WORK_REQUEST, Worker.WORK_REQUEST_PERIODIC);
|
|
794
|
-
workRequestBuilder.setInputData(workDataBuilder.build());
|
|
795
|
-
workManager.enqueueUniquePeriodicWork(
|
|
796
|
-
uniqueWorkName, ExistingPeriodicWorkPolicy.UPDATE, workRequestBuilder.build());
|
|
797
|
-
}
|
|
791
|
+
WorkManager workManager = WorkManager.getInstance(context);
|
|
792
|
+
|
|
793
|
+
// WorkManager - One time trigger
|
|
794
|
+
if (interval == -1) {
|
|
795
|
+
OneTimeWorkRequest.Builder workRequestBuilder =
|
|
796
|
+
new OneTimeWorkRequest.Builder(Worker.class);
|
|
797
|
+
workRequestBuilder.addTag(Worker.WORK_TYPE_NOTIFICATION_TRIGGER);
|
|
798
|
+
workRequestBuilder.addTag(uniqueWorkName);
|
|
799
|
+
workDataBuilder.putString(Worker.KEY_WORK_REQUEST, Worker.WORK_REQUEST_ONE_TIME);
|
|
800
|
+
workRequestBuilder.setInputData(workDataBuilder.build());
|
|
801
|
+
workRequestBuilder.setInitialDelay(delay, TimeUnit.SECONDS);
|
|
802
|
+
workManager.enqueueUniqueWork(
|
|
803
|
+
uniqueWorkName, ExistingWorkPolicy.REPLACE, workRequestBuilder.build());
|
|
804
|
+
} else {
|
|
805
|
+
// WorkManager - repeat trigger
|
|
806
|
+
PeriodicWorkRequest.Builder workRequestBuilder =
|
|
807
|
+
new PeriodicWorkRequest.Builder(
|
|
808
|
+
Worker.class, trigger.getInterval(), trigger.getTimeUnit());
|
|
809
|
+
|
|
810
|
+
workRequestBuilder.addTag(Worker.WORK_TYPE_NOTIFICATION_TRIGGER);
|
|
811
|
+
workRequestBuilder.addTag(uniqueWorkName);
|
|
812
|
+
workRequestBuilder.setInitialDelay(delay, TimeUnit.SECONDS);
|
|
813
|
+
workDataBuilder.putString(Worker.KEY_WORK_REQUEST, Worker.WORK_REQUEST_PERIODIC);
|
|
814
|
+
workRequestBuilder.setInputData(workDataBuilder.build());
|
|
815
|
+
workManager.enqueueUniquePeriodicWork(
|
|
816
|
+
uniqueWorkName, ExistingPeriodicWorkPolicy.UPDATE, workRequestBuilder.build());
|
|
817
|
+
}
|
|
818
|
+
return null;
|
|
819
|
+
},
|
|
820
|
+
LISTENING_CACHED_THREAD_POOL);
|
|
798
821
|
}
|
|
799
822
|
|
|
800
823
|
static ListenableFuture<List<Bundle>> getDisplayedNotifications() {
|
|
@@ -982,17 +1005,51 @@ class NotificationManager {
|
|
|
982
1005
|
new ExtendedListenableFuture<>(result)
|
|
983
1006
|
.addOnCompleteListener(
|
|
984
1007
|
(e2, _unused) -> {
|
|
985
|
-
completer.set(Result.success());
|
|
986
1008
|
if (e2 != null) {
|
|
987
1009
|
Logger.e(TAG, "Failed to display notification", e2);
|
|
1010
|
+
completer.set(Result.success());
|
|
1011
|
+
return;
|
|
1012
|
+
}
|
|
1013
|
+
String workerRequestType = data.getString(Worker.KEY_WORK_REQUEST);
|
|
1014
|
+
if (workerRequestType != null
|
|
1015
|
+
&& workerRequestType.equals(Worker.WORK_REQUEST_ONE_TIME)) {
|
|
1016
|
+
// DO NOT reorder — completer.set must only run after the
|
|
1017
|
+
// delete future completes, otherwise WorkManager may start
|
|
1018
|
+
// a new work instance that reads the stale row. Previously
|
|
1019
|
+
// completer.set fired before the delete was enqueued, leaving
|
|
1020
|
+
// a zombie row that reboot recovery would resurrect as a
|
|
1021
|
+
// ghost alarm. See #549 audit Part B, Caller #5.
|
|
1022
|
+
//
|
|
1023
|
+
// Note: CallbackToFutureAdapter.Completer.set() returns
|
|
1024
|
+
// false on double-set, it does NOT throw. This is an
|
|
1025
|
+
// androidx contract. Do not refactor under the assumption
|
|
1026
|
+
// that double-set is dangerous.
|
|
1027
|
+
Futures.addCallback(
|
|
1028
|
+
WorkDataRepository.getInstance(getApplicationContext())
|
|
1029
|
+
.deleteById(id),
|
|
1030
|
+
new FutureCallback<Void>() {
|
|
1031
|
+
@Override
|
|
1032
|
+
public void onSuccess(Void unused) {
|
|
1033
|
+
completer.set(Result.success());
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
@Override
|
|
1037
|
+
public void onFailure(@NonNull Throwable t) {
|
|
1038
|
+
// Notification was already displayed; a failed
|
|
1039
|
+
// delete leaves an orphan row that the next
|
|
1040
|
+
// cancelAll or app restart will clean up. Still
|
|
1041
|
+
// report success so WorkManager doesn't retry
|
|
1042
|
+
// the already-displayed notification.
|
|
1043
|
+
Logger.e(
|
|
1044
|
+
TAG,
|
|
1045
|
+
"Failed to delete one-time trigger row after" + " display",
|
|
1046
|
+
new Exception(t));
|
|
1047
|
+
completer.set(Result.success());
|
|
1048
|
+
}
|
|
1049
|
+
},
|
|
1050
|
+
LISTENING_CACHED_THREAD_POOL);
|
|
988
1051
|
} else {
|
|
989
|
-
|
|
990
|
-
if (workerRequestType != null
|
|
991
|
-
&& workerRequestType.equals(Worker.WORK_REQUEST_ONE_TIME)) {
|
|
992
|
-
// delete database entry if work is a one-time request
|
|
993
|
-
WorkDataRepository.getInstance(getApplicationContext())
|
|
994
|
-
.deleteById(id);
|
|
995
|
-
}
|
|
1052
|
+
completer.set(Result.success());
|
|
996
1053
|
}
|
|
997
1054
|
},
|
|
998
1055
|
LISTENING_CACHED_THREAD_POOL);
|
|
@@ -22,6 +22,15 @@ import android.content.SharedPreferences;
|
|
|
22
22
|
|
|
23
23
|
class Preferences {
|
|
24
24
|
private static final String PREFERENCES_FILE = "app.notifee.core";
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* SharedPreferences key storing the value of {@code Settings.Global.BOOT_COUNT} observed on the
|
|
28
|
+
* most recent successful app init. Compared against the current {@code BOOT_COUNT} to detect a
|
|
29
|
+
* reboot since the last app run — the core mechanism of the OEM cold-start recovery added for
|
|
30
|
+
* upstream invertase/notifee#734.
|
|
31
|
+
*/
|
|
32
|
+
static final String LAST_KNOWN_BOOT_COUNT_KEY = "notifee_last_known_boot_count";
|
|
33
|
+
|
|
25
34
|
private static Preferences sharedInstance = new Preferences();
|
|
26
35
|
private SharedPreferences preferences;
|
|
27
36
|
|
|
@@ -20,20 +20,35 @@ package app.notifee.core;
|
|
|
20
20
|
import android.content.BroadcastReceiver;
|
|
21
21
|
import android.content.Context;
|
|
22
22
|
import android.content.Intent;
|
|
23
|
-
import android.util.Log;
|
|
24
23
|
|
|
25
24
|
/*
|
|
26
25
|
* This is invoked when the phone restarts to ensure that all notifications created by the alarm manager
|
|
27
26
|
* are rescheduled correctly, as Android removes all scheduled alarms when the phone shuts down.
|
|
28
27
|
*/
|
|
29
28
|
public class RebootBroadcastReceiver extends BroadcastReceiver {
|
|
29
|
+
private static final String TAG = "RebootReceiver";
|
|
30
|
+
|
|
30
31
|
@Override
|
|
31
32
|
public void onReceive(Context context, Intent intent) {
|
|
32
33
|
PendingResult pendingResult = goAsync();
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
// Tracks whether the synchronous section successfully handed off to the
|
|
35
|
+
// async reschedule path. If not, the finally block must call finish() to
|
|
36
|
+
// avoid leaving the broadcast unterminated — Android will otherwise kill
|
|
37
|
+
// the process after ~10s and race subsequent reboot broadcasts.
|
|
38
|
+
boolean asyncHandoffSucceeded = false;
|
|
39
|
+
try {
|
|
40
|
+
Logger.i(TAG, "Received reboot event");
|
|
41
|
+
if (ContextHolder.getApplicationContext() == null) {
|
|
42
|
+
ContextHolder.setApplicationContext(context.getApplicationContext());
|
|
43
|
+
}
|
|
44
|
+
new NotifeeAlarmManager().rescheduleNotifications(pendingResult);
|
|
45
|
+
asyncHandoffSucceeded = true;
|
|
46
|
+
} catch (Throwable t) {
|
|
47
|
+
Logger.e(TAG, "Failed to reschedule notifications after reboot", t);
|
|
48
|
+
} finally {
|
|
49
|
+
if (!asyncHandoffSucceeded && pendingResult != null) {
|
|
50
|
+
pendingResult.finish();
|
|
51
|
+
}
|
|
36
52
|
}
|
|
37
|
-
new NotifeeAlarmManager().rescheduleNotifications(pendingResult);
|
|
38
53
|
}
|
|
39
54
|
}
|
|
@@ -20,13 +20,16 @@ package app.notifee.core.database;
|
|
|
20
20
|
import android.content.Context;
|
|
21
21
|
import android.os.Bundle;
|
|
22
22
|
import androidx.annotation.NonNull;
|
|
23
|
+
import androidx.annotation.VisibleForTesting;
|
|
23
24
|
import app.notifee.core.model.NotificationModel;
|
|
24
25
|
import app.notifee.core.utility.ObjectUtils;
|
|
25
26
|
import com.google.common.util.concurrent.ListenableFuture;
|
|
27
|
+
import com.google.common.util.concurrent.ListeningExecutorService;
|
|
26
28
|
import java.util.List;
|
|
27
29
|
|
|
28
30
|
public class WorkDataRepository {
|
|
29
|
-
private WorkDataDao mWorkDataDao;
|
|
31
|
+
private final WorkDataDao mWorkDataDao;
|
|
32
|
+
private final ListeningExecutorService mExecutor;
|
|
30
33
|
private static WorkDataRepository mInstance;
|
|
31
34
|
|
|
32
35
|
public static @NonNull WorkDataRepository getInstance(@NonNull Context context) {
|
|
@@ -42,52 +45,56 @@ public class WorkDataRepository {
|
|
|
42
45
|
public WorkDataRepository(Context context) {
|
|
43
46
|
NotifeeCoreDatabase db = NotifeeCoreDatabase.getDatabase(context);
|
|
44
47
|
mWorkDataDao = db.workDao();
|
|
48
|
+
mExecutor = NotifeeCoreDatabase.databaseWriteListeningExecutor;
|
|
45
49
|
}
|
|
46
50
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
51
|
+
@VisibleForTesting
|
|
52
|
+
WorkDataRepository(@NonNull WorkDataDao dao, @NonNull ListeningExecutorService executor) {
|
|
53
|
+
mWorkDataDao = dao;
|
|
54
|
+
mExecutor = executor;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Submit a DAO write to the listening executor and return a future that
|
|
58
|
+
// completes when the DAO call returns (success or exception). Using the
|
|
59
|
+
// submit(Runnable, result) overload avoids the Callable<Object> inference
|
|
60
|
+
// that would otherwise require a cast at every call site.
|
|
61
|
+
private @NonNull ListenableFuture<Void> submitWrite(@NonNull Runnable work) {
|
|
62
|
+
return mExecutor.submit(work, (Void) null);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
public @NonNull ListenableFuture<Void> insert(WorkDataEntity workData) {
|
|
66
|
+
return submitWrite(() -> mWorkDataDao.insert(workData));
|
|
52
67
|
}
|
|
53
68
|
|
|
54
69
|
public ListenableFuture<WorkDataEntity> getWorkDataById(String id) {
|
|
55
|
-
return
|
|
56
|
-
() -> mWorkDataDao.getWorkDataById(id));
|
|
70
|
+
return mExecutor.submit(() -> mWorkDataDao.getWorkDataById(id));
|
|
57
71
|
}
|
|
58
72
|
|
|
59
73
|
public ListenableFuture<List<WorkDataEntity>> getAllWithAlarmManager(Boolean withAlarmManager) {
|
|
60
|
-
return
|
|
61
|
-
() -> mWorkDataDao.getAllWithAlarmManager(withAlarmManager));
|
|
74
|
+
return mExecutor.submit(() -> mWorkDataDao.getAllWithAlarmManager(withAlarmManager));
|
|
62
75
|
}
|
|
63
76
|
|
|
64
77
|
public ListenableFuture<List<WorkDataEntity>> getAll() {
|
|
65
|
-
return
|
|
78
|
+
return mExecutor.submit(() -> mWorkDataDao.getAll());
|
|
66
79
|
}
|
|
67
80
|
|
|
68
|
-
public
|
|
69
|
-
|
|
70
|
-
() -> {
|
|
71
|
-
mWorkDataDao.deleteById(id);
|
|
72
|
-
});
|
|
81
|
+
public @NonNull ListenableFuture<Void> deleteById(String id) {
|
|
82
|
+
return submitWrite(() -> mWorkDataDao.deleteById(id));
|
|
73
83
|
}
|
|
74
84
|
|
|
75
|
-
public
|
|
76
|
-
|
|
77
|
-
() -> {
|
|
78
|
-
mWorkDataDao.deleteByIds(ids);
|
|
79
|
-
});
|
|
85
|
+
public @NonNull ListenableFuture<Void> deleteByIds(List<String> ids) {
|
|
86
|
+
return submitWrite(() -> mWorkDataDao.deleteByIds(ids));
|
|
80
87
|
}
|
|
81
88
|
|
|
82
|
-
public
|
|
83
|
-
|
|
84
|
-
() -> {
|
|
85
|
-
mWorkDataDao.deleteAll();
|
|
86
|
-
});
|
|
89
|
+
public @NonNull ListenableFuture<Void> deleteAll() {
|
|
90
|
+
return submitWrite(() -> mWorkDataDao.deleteAll());
|
|
87
91
|
}
|
|
88
92
|
|
|
89
|
-
public static
|
|
90
|
-
|
|
93
|
+
public static @NonNull ListenableFuture<Void> insertTriggerNotification(
|
|
94
|
+
@NonNull Context context,
|
|
95
|
+
NotificationModel notificationModel,
|
|
96
|
+
Bundle triggerBundle,
|
|
97
|
+
Boolean withAlarmManager) {
|
|
91
98
|
WorkDataEntity workData =
|
|
92
99
|
new WorkDataEntity(
|
|
93
100
|
notificationModel.getId(),
|
|
@@ -95,13 +102,10 @@ public class WorkDataRepository {
|
|
|
95
102
|
ObjectUtils.bundleToBytes(triggerBundle),
|
|
96
103
|
withAlarmManager);
|
|
97
104
|
|
|
98
|
-
|
|
105
|
+
return getInstance(context).insert(workData);
|
|
99
106
|
}
|
|
100
107
|
|
|
101
|
-
public
|
|
102
|
-
|
|
103
|
-
() -> {
|
|
104
|
-
mWorkDataDao.update(workData);
|
|
105
|
-
});
|
|
108
|
+
public @NonNull ListenableFuture<Void> update(WorkDataEntity workData) {
|
|
109
|
+
return submitWrite(() -> mWorkDataDao.update(workData));
|
|
106
110
|
}
|
|
107
111
|
}
|
|
@@ -204,8 +204,9 @@ class HeadlessTask {
|
|
|
204
204
|
private val params: WritableMap
|
|
205
205
|
|
|
206
206
|
init {
|
|
207
|
-
params.
|
|
208
|
-
|
|
207
|
+
val copied = params.copy()
|
|
208
|
+
copied.putInt("taskId", taskId)
|
|
209
|
+
this.params = copied
|
|
209
210
|
}
|
|
210
211
|
|
|
211
212
|
val taskConfig: HeadlessJsTaskConfig
|