Skip to content

Commit 4f51367

Browse files
committed
Add Receive Receipt delay.
* Add random delay from 0 to 25 seconds * Add OSDelayTaskController for future delays * Add MockDelayTaskController for test approaches * Refactor Notification Work Manager to extends ListenableWorker. This adds future completion. Complete future after sending receive receipts * Refactor Restoration Work Manager to not init another work manager, since Notification Work Manager nows expects a future when Restoration work is done, it will finish notification work manager that will end on a FutureGarbageCollectedException, avoid this exception by running everything on the same work thread
1 parent 20c7f2a commit 4f51367

18 files changed

+296
-69
lines changed

OneSignalSDK/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ buildscript {
88
targetSdkVersion: 28
99
]
1010
androidGradlePluginVersion = '3.6.2'
11+
androidConcurrentFutures = '1.1.0'
1112
googleServicesGradlePluginVersion = '4.3.2'
1213
huaweiAgconnectVersion = '1.2.1.301'
1314
huaweiHMSPushVersion = '4.0.2.300'

OneSignalSDK/onesignal/build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ dependencies {
4949
// Required for GoogleApiAvailability
5050
implementation 'com.google.android.gms:play-services-base:[17.0.0, 17.6.99]'
5151

52+
implementation("androidx.concurrent:concurrent-futures:$androidConcurrentFutures")
53+
5254
// firebase-messaging:18.0.0 is the last version before going to AndroidX
5355
// firebase-messaging:19.0.0 is the first version using AndroidX
5456
api 'com.google.firebase:firebase-messaging:[19.0.0, 22.0.99]'

OneSignalSDK/onesignal/src/main/java/com/onesignal/NotificationBundleProcessor.java

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@
3636
import androidx.annotation.NonNull;
3737
import androidx.annotation.Nullable;
3838
import androidx.annotation.WorkerThread;
39+
import androidx.concurrent.futures.CallbackToFutureAdapter;
40+
import androidx.work.ListenableWorker;
3941

4042
import com.onesignal.OneSignalDbContract.NotificationTable;
4143

@@ -97,9 +99,10 @@ public void onResult(boolean result) {
9799
osNotificationId,
98100
finalAndroidNotificationId,
99101
jsonStrPayload,
100-
isRestoring,
101102
shownTimeStamp,
102-
false);
103+
isRestoring,
104+
false,
105+
true);
103106

104107
// Delay to prevent CPU spikes.
105108
// Normally more than one notification is restored at a time.
@@ -131,7 +134,7 @@ static int processJobForDisplay(OSNotificationController notificationController,
131134
}
132135

133136
@WorkerThread
134-
static int processJobForDisplay(OSNotificationController notificationController, boolean opened, boolean fromBackgroundLogic) {
137+
private static int processJobForDisplay(OSNotificationController notificationController, boolean opened, boolean fromBackgroundLogic) {
135138
OneSignal.Log(OneSignal.LOG_LEVEL.DEBUG, "Starting processJobForDisplay opened: " + opened + " fromBackgroundLogic: " + fromBackgroundLogic);
136139
OSNotificationGenerationJob notificationJob = notificationController.getNotificationJob();
137140

@@ -176,17 +179,22 @@ private static boolean shouldDisplayNotification(OSNotificationGenerationJob not
176179
*/
177180
static void processNotification(OSNotificationGenerationJob notificationJob, boolean opened, boolean notificationDisplayed) {
178181
saveNotification(notificationJob, opened);
182+
CallbackToFutureAdapter.Completer<ListenableWorker.Result> callbackCompleter = notificationJob.getCallbackCompleter();
179183

180184
if (!notificationDisplayed) {
181185
// Notification channel disable or not displayed
182186
// save notification as dismissed to avoid user re-enabling channel and notification being displayed due to restore
183187
markNotificationAsDismissed(notificationJob);
188+
189+
OneSignal.Log(OneSignal.LOG_LEVEL.DEBUG, "Process notification not displayed with callback completer: " + callbackCompleter);
190+
if (callbackCompleter != null)
191+
callbackCompleter.set(ListenableWorker.Result.success());
184192
return;
185193
}
186194

187195
// Logic for when the notification is displayed
188196
String notificationId = notificationJob.getApiNotificationId();
189-
OSReceiveReceiptController.getInstance().sendReceiveReceipt(notificationId);
197+
OSReceiveReceiptController.getInstance().sendReceiveReceipt(callbackCompleter, notificationId);
190198
OneSignal.getSessionManager().onNotificationReceived(notificationId);
191199
}
192200

@@ -425,9 +433,10 @@ public void onResult(boolean result) {
425433
osNotificationId,
426434
androidNotificationId,
427435
jsonPayload.toString(),
428-
isRestoring,
429436
timestamp,
430-
isHighPriority);
437+
isRestoring,
438+
isHighPriority,
439+
true);
431440

432441
bundleResult.setWorkManagerProcessing(true);
433442
notificationProcessingCallback.onResult(true);

OneSignalSDK/onesignal/src/main/java/com/onesignal/NotificationSummaryManager.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ private static void restoreSummary(Context context, String group) {
143143
null
144144
);
145145

146-
OSNotificationRestoreWorkManager.showNotificationsFromCursor(context, cursor, 0);
146+
OSNotificationRestoreWorkManager.showNotificationsFromCursor(context, cursor, 0, true);
147147
} catch (Throwable t) {
148148
OneSignal.Log(OneSignal.LOG_LEVEL.ERROR, "Error restoring notification records! ", t);
149149
} finally {
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package com.onesignal
2+
3+
import java.util.concurrent.ScheduledThreadPoolExecutor
4+
import java.util.concurrent.ThreadFactory
5+
import java.util.concurrent.TimeUnit
6+
import kotlin.math.roundToInt
7+
8+
open class OSDelayTaskController(private val logger: OSLogger) {
9+
private val maxDelay = 25
10+
private val minDelay = 0
11+
12+
private var scheduledThreadPoolExecutor: ScheduledThreadPoolExecutor
13+
14+
init {
15+
scheduledThreadPoolExecutor = ScheduledThreadPoolExecutor(1, JobThreadFactory())
16+
}
17+
18+
protected open fun getRandomNumber(): Int {
19+
return (Math.random() * (maxDelay - minDelay + 1) + minDelay).roundToInt()
20+
}
21+
22+
open fun delayTaskByRandom(runnable: Runnable) {
23+
val randomNum = getRandomNumber()
24+
25+
logger.debug("OSDelayTaskController delaying task $randomNum second from thread: ${Thread.currentThread()}")
26+
scheduledThreadPoolExecutor.schedule(runnable, randomNum.toLong(), TimeUnit.SECONDS)
27+
}
28+
29+
fun shutdownNow() {
30+
scheduledThreadPoolExecutor.shutdownNow()
31+
}
32+
33+
private class JobThreadFactory : ThreadFactory {
34+
private val delayThreadName = "ONE_SIGNAL_DELAY"
35+
36+
override fun newThread(runnable: Runnable): Thread {
37+
return Thread(runnable, delayThreadName)
38+
}
39+
}
40+
}

OneSignalSDK/onesignal/src/main/java/com/onesignal/OSNotificationController.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030
import android.content.Context;
3131

3232
import androidx.annotation.Nullable;
33+
import androidx.concurrent.futures.CallbackToFutureAdapter;
34+
import androidx.work.ListenableWorker;
3335

3436
import org.json.JSONObject;
3537

@@ -40,6 +42,7 @@ public class OSNotificationController {
4042
// The extension service app AndroidManifest.xml meta data tag key name
4143
private static final String EXTENSION_SERVICE_META_DATA_TAG_NAME = "com.onesignal.NotificationServiceExtension";
4244

45+
private final CallbackToFutureAdapter.Completer<ListenableWorker.Result> callbackCompleter;
4346
private final OSNotificationGenerationJob notificationJob;
4447
private boolean restoring;
4548
private boolean fromBackgroundLogic;
@@ -48,9 +51,12 @@ public class OSNotificationController {
4851
this.restoring = restoring;
4952
this.fromBackgroundLogic = fromBackgroundLogic;
5053
this.notificationJob = notificationJob;
54+
this.callbackCompleter = notificationJob.getCallbackCompleter();
5155
}
5256

53-
OSNotificationController(Context context, JSONObject jsonPayload, boolean restoring, boolean fromBackgroundLogic, Long timestamp) {
57+
OSNotificationController(CallbackToFutureAdapter.Completer<ListenableWorker.Result> callbackCompleter,
58+
Context context, JSONObject jsonPayload, boolean restoring, boolean fromBackgroundLogic, Long timestamp) {
59+
this.callbackCompleter = callbackCompleter;
5460
this.restoring = restoring;
5561
this.fromBackgroundLogic = fromBackgroundLogic;
5662

@@ -64,7 +70,7 @@ public class OSNotificationController {
6470
* @see OSNotificationGenerationJob
6571
*/
6672
private OSNotificationGenerationJob createNotificationJobFromCurrent(Context context, JSONObject jsonPayload, Long timestamp) {
67-
OSNotificationGenerationJob notificationJob = new OSNotificationGenerationJob(context);
73+
OSNotificationGenerationJob notificationJob = new OSNotificationGenerationJob(callbackCompleter, context);
6874
notificationJob.setJsonPayload(jsonPayload);
6975
notificationJob.setShownTimeStamp(timestamp);
7076
notificationJob.setRestoring(restoring);

OneSignalSDK/onesignal/src/main/java/com/onesignal/OSNotificationGenerationJob.java

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,17 @@
3030
import android.content.Context;
3131
import android.net.Uri;
3232

33-
import androidx.core.app.NotificationCompat;
33+
import androidx.annotation.Nullable;
34+
import androidx.concurrent.futures.CallbackToFutureAdapter;
35+
import androidx.work.ListenableWorker;
3436

35-
import org.json.JSONException;
3637
import org.json.JSONObject;
3738

3839
import java.security.SecureRandom;
3940

4041
public class OSNotificationGenerationJob {
4142

43+
private CallbackToFutureAdapter.Completer<ListenableWorker.Result> callbackCompleter;
4244
private OSNotification notification;
4345
private Context context;
4446
private JSONObject jsonPayload;
@@ -54,6 +56,11 @@ public class OSNotificationGenerationJob {
5456
private Uri orgSound;
5557

5658
OSNotificationGenerationJob(Context context) {
59+
this(null, context);
60+
}
61+
62+
OSNotificationGenerationJob(CallbackToFutureAdapter.Completer<ListenableWorker.Result> callbackCompleter, Context context) {
63+
this.callbackCompleter = callbackCompleter;
5764
this.context = context;
5865
}
5966

@@ -67,6 +74,11 @@ public class OSNotificationGenerationJob {
6774
this.notification = notification;
6875
}
6976

77+
@Nullable
78+
public CallbackToFutureAdapter.Completer<ListenableWorker.Result> getCallbackCompleter() {
79+
return callbackCompleter;
80+
}
81+
7082
/**
7183
* Get the notification title from the payload
7284
*/

OneSignalSDK/onesignal/src/main/java/com/onesignal/OSNotificationRestoreWorkManager.java

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ public Result doWork() {
7777
StringBuilder dbQuerySelection = OneSignalDbHelper.recentUninteractedWithNotificationsWhere();
7878
skipVisibleNotifications(context, dbQuerySelection);
7979

80-
queryAndRestoreNotificationsAndBadgeCount(context, dbHelper, dbQuerySelection);
80+
queryAndRestoreNotificationsAndBadgeCount(context, dbHelper, dbQuerySelection, false);
8181

8282
return Result.success();
8383
}
@@ -87,7 +87,8 @@ public Result doWork() {
8787
private static void queryAndRestoreNotificationsAndBadgeCount(
8888
Context context,
8989
OneSignalDbHelper dbHelper,
90-
StringBuilder dbQuerySelection) {
90+
StringBuilder dbQuerySelection,
91+
boolean needsWorkerThread) {
9192

9293
OneSignal.Log(OneSignal.LOG_LEVEL.INFO,
9394
"Querying DB for notifications to restore: " + dbQuerySelection.toString());
@@ -104,7 +105,7 @@ private static void queryAndRestoreNotificationsAndBadgeCount(
104105
OneSignalDbContract.NotificationTable._ID + " DESC", // sort order, new to old
105106
NotificationLimitManager.MAX_NUMBER_OF_NOTIFICATIONS_STR // limit
106107
);
107-
showNotificationsFromCursor(context, cursor, DELAY_BETWEEN_NOTIFICATION_RESTORES_MS);
108+
showNotificationsFromCursor(context, cursor, DELAY_BETWEEN_NOTIFICATION_RESTORES_MS, needsWorkerThread);
108109
BadgeCountUpdater.update(dbHelper, context);
109110
} catch (Throwable t) {
110111
OneSignal.Log(OneSignal.LOG_LEVEL.ERROR, "Error restoring notification records! ", t);
@@ -144,7 +145,7 @@ private static void skipVisibleNotifications(Context context, StringBuilder dbQu
144145
* @param cursor - Source cursor to generate notifications from
145146
* @param delay - Delay to slow down process to ensure we don't spike CPU and I/O on the device
146147
*/
147-
static void showNotificationsFromCursor(Context context, Cursor cursor, int delay) {
148+
static void showNotificationsFromCursor(Context context, Cursor cursor, int delay, boolean needsWorkerThread) {
148149
if (!cursor.moveToFirst())
149150
return;
150151

@@ -159,9 +160,10 @@ static void showNotificationsFromCursor(Context context, Cursor cursor, int dela
159160
osNotificationId,
160161
existingId,
161162
fullData,
162-
true,
163163
dateTime,
164-
false
164+
true,
165+
false,
166+
needsWorkerThread
165167
);
166168

167169
if (delay > 0)

0 commit comments

Comments
 (0)