Skip to content

Commit 131a4ac

Browse files
authored
Add TTL notification display control (#1479)
* Notifications were being batches and displayed by android standBy * For cases where notification sent time + TTL is more than the current time display notification, otherwise no * Add sentTime and TTL data to Notification * Logic added for both GCM and HMS
1 parent 3bcc63f commit 131a4ac

15 files changed

+264
-73
lines changed

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

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,12 @@
2727

2828
package com.onesignal;
2929

30+
import static com.onesignal.GenerateNotification.BUNDLE_KEY_ACTION_ID;
31+
import static com.onesignal.OSUtils.isStringNotEmpty;
32+
3033
import android.content.ContentValues;
3134
import android.content.Context;
3235
import android.database.Cursor;
33-
import android.os.Build;
3436
import android.os.Bundle;
3537

3638
import androidx.annotation.NonNull;
@@ -47,9 +49,6 @@
4749

4850
import java.util.Set;
4951

50-
import static com.onesignal.GenerateNotification.BUNDLE_KEY_ACTION_ID;
51-
import static com.onesignal.OSUtils.isStringNotEmpty;
52-
5352
/** Processes the Bundle received from a push.
5453
* This class handles both processing bundles from a BroadcastReceiver or from a Service
5554
* - Entry points are processBundleFromReceiver or ProcessFromFCMIntentService respectively
@@ -249,8 +248,8 @@ private static void saveNotification(OSNotificationGenerationJob notificationJob
249248
values.put(NotificationTable.COLUMN_NAME_MESSAGE, notificationJob.getBody().toString());
250249

251250
// Set expire_time
252-
long sentTime = jsonPayload.optLong("google.sent_time", OneSignal.getTime().getCurrentThreadTimeMillis()) / 1_000L;
253-
int ttl = jsonPayload.optInt("google.ttl", OSNotificationRestoreWorkManager.DEFAULT_TTL_IF_NOT_IN_PAYLOAD);
251+
long sentTime = jsonPayload.optLong(OSNotificationController.GOOGLE_SENT_TIME_KEY, OneSignal.getTime().getCurrentThreadTimeMillis()) / 1_000L;
252+
int ttl = jsonPayload.optInt(OSNotificationController.GOOGLE_TTL_KEY, OSNotificationRestoreWorkManager.DEFAULT_TTL_IF_NOT_IN_PAYLOAD);
254253
long expireTime = sentTime + ttl;
255254
values.put(NotificationTable.COLUMN_NAME_EXPIRE_TIME, expireTime);
256255

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,6 @@ private static void markNotificationsConsumed(Context context, Intent intent, On
206206
} else
207207
whereStr = NotificationTable.COLUMN_NAME_ANDROID_NOTIFICATION_ID + " = " + intent.getIntExtra(BUNDLE_KEY_ANDROID_NOTIFICATION_ID, 0);
208208

209-
210209
clearStatusBarNotifications(context, writableDb, summaryGroup);
211210
writableDb.update(NotificationTable.TABLE_NAME, newContentValuesWithConsumed(intent), whereStr, whereArgs);
212211
BadgeCountUpdater.update(writableDb, context);

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

Lines changed: 52 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@
4040

4141
import static com.onesignal.GenerateNotification.BUNDLE_KEY_ACTION_ID;
4242
import static com.onesignal.NotificationBundleProcessor.PUSH_ADDITIONAL_DATA_KEY;
43+
import static com.onesignal.OSNotificationController.GOOGLE_SENT_TIME_KEY;
44+
import static com.onesignal.OSNotificationController.GOOGLE_TTL_KEY;
45+
import static com.onesignal.OneSignalHmsEventBridge.HMS_SENT_TIME_KEY;
46+
import static com.onesignal.OneSignalHmsEventBridge.HMS_TTL_KEY;
4347

4448
/**
4549
* The notification the user received
@@ -87,21 +91,16 @@ public class OSNotification {
8791
private int priority;
8892
private String rawPayload;
8993

94+
private long sentTime;
95+
private int ttl;
96+
9097
protected OSNotification() {
9198
}
9299

93100
OSNotification(@NonNull JSONObject payload) {
94101
this(null, payload, 0);
95102
}
96103

97-
OSNotification(@NonNull JSONObject payload, int androidNotificationId) {
98-
this(null, payload, androidNotificationId);
99-
}
100-
101-
OSNotification(@Nullable List<OSNotification> groupedNotifications, @NonNull JSONObject payload) {
102-
this(groupedNotifications, payload, 0);
103-
}
104-
105104
OSNotification(@Nullable List<OSNotification> groupedNotifications, @NonNull JSONObject jsonPayload, int androidNotificationId) {
106105
initPayloadData(jsonPayload);
107106
this.groupedNotifications = groupedNotifications;
@@ -144,6 +143,18 @@ private void initPayloadData(JSONObject currentJsonPayload) {
144143
return;
145144
}
146145

146+
long currentTime = OneSignal.getTime().getCurrentThreadTimeMillis();
147+
if (currentJsonPayload.has(GOOGLE_TTL_KEY)) {
148+
sentTime = currentJsonPayload.optLong(GOOGLE_SENT_TIME_KEY, currentTime) / 1_000;
149+
ttl = currentJsonPayload.optInt(GOOGLE_TTL_KEY, OSNotificationRestoreWorkManager.DEFAULT_TTL_IF_NOT_IN_PAYLOAD);
150+
} else if (currentJsonPayload.has(HMS_TTL_KEY)) {
151+
sentTime = currentJsonPayload.optLong(HMS_SENT_TIME_KEY, currentTime) / 1_000;
152+
ttl = currentJsonPayload.optInt(HMS_TTL_KEY, OSNotificationRestoreWorkManager.DEFAULT_TTL_IF_NOT_IN_PAYLOAD);
153+
} else {
154+
sentTime = currentTime / 1_000;
155+
ttl = OSNotificationRestoreWorkManager.DEFAULT_TTL_IF_NOT_IN_PAYLOAD;
156+
}
157+
147158
notificationId = customJson.optString("i");
148159
templateId = customJson.optString("ti");
149160
templateName = customJson.optString("tn");
@@ -243,6 +254,8 @@ OSNotification copy() {
243254
.setCollapseId(collapseId)
244255
.setPriority(priority)
245256
.setRawPayload(rawPayload)
257+
.setSenttime(sentTime)
258+
.setTTL(ttl)
246259
.build();
247260
}
248261

@@ -451,6 +464,22 @@ void setRawPayload(String rawPayload) {
451464
this.rawPayload = rawPayload;
452465
}
453466

467+
public long getSentTime() {
468+
return sentTime;
469+
}
470+
471+
private void setSentTime(long sentTime) {
472+
this.sentTime = sentTime;
473+
}
474+
475+
public int getTtl() {
476+
return ttl;
477+
}
478+
479+
private void setTtl(int ttl) {
480+
this.ttl = ttl;
481+
}
482+
454483
public JSONObject toJSONObject() {
455484
JSONObject mainObj = new JSONObject();
456485

@@ -631,6 +660,9 @@ public static class OSNotificationBuilder {
631660
private int priority;
632661
private String rawPayload;
633662

663+
private long sentTime;
664+
private int ttl;
665+
634666
public OSNotificationBuilder() {
635667
}
636668

@@ -759,6 +791,16 @@ public OSNotificationBuilder setRawPayload(String rawPayload) {
759791
return this;
760792
}
761793

794+
public OSNotificationBuilder setSenttime(long sentTime) {
795+
this.sentTime = sentTime;
796+
return this;
797+
}
798+
799+
public OSNotificationBuilder setTTL(int ttl) {
800+
this.ttl = ttl;
801+
return this;
802+
}
803+
762804
public OSNotification build() {
763805
OSNotification payload = new OSNotification();
764806
payload.setNotificationExtender(notificationExtender);
@@ -786,6 +828,8 @@ public OSNotification build() {
786828
payload.setCollapseId(collapseId);
787829
payload.setPriority(priority);
788830
payload.setRawPayload(rawPayload);
831+
payload.setSentTime(sentTime);
832+
payload.setTtl(ttl);
789833
return payload;
790834
}
791835
}

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

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@
2727

2828
package com.onesignal;
2929

30+
import static com.onesignal.OSUtils.isStringNotEmpty;
31+
import static com.onesignal.OneSignalHmsEventBridge.HMS_SENT_TIME_KEY;
32+
import static com.onesignal.OneSignalHmsEventBridge.HMS_TTL_KEY;
33+
3034
import android.content.Context;
3135

3236
import androidx.annotation.Nullable;
@@ -35,12 +39,12 @@
3539

3640
import org.json.JSONObject;
3741

38-
import static com.onesignal.OSUtils.isStringNotEmpty;
39-
4042
public class OSNotificationController {
4143

4244
// The extension service app AndroidManifest.xml meta data tag key name
4345
private static final String EXTENSION_SERVICE_META_DATA_TAG_NAME = "com.onesignal.NotificationServiceExtension";
46+
static final String GOOGLE_SENT_TIME_KEY = "google.sent_time";
47+
static final String GOOGLE_TTL_KEY = "google.ttl";
4448

4549
private final CallbackToFutureAdapter.Completer<ListenableWorker.Result> callbackCompleter;
4650
private final OSNotificationGenerationJob notificationJob;
@@ -55,12 +59,12 @@ public class OSNotificationController {
5559
}
5660

5761
OSNotificationController(CallbackToFutureAdapter.Completer<ListenableWorker.Result> callbackCompleter,
58-
Context context, JSONObject jsonPayload, boolean restoring, boolean fromBackgroundLogic, Long timestamp) {
62+
Context context, OSNotification notification, JSONObject jsonPayload, boolean restoring, boolean fromBackgroundLogic, Long timestamp) {
5963
this.callbackCompleter = callbackCompleter;
6064
this.restoring = restoring;
6165
this.fromBackgroundLogic = fromBackgroundLogic;
6266

63-
notificationJob = createNotificationJobFromCurrent(context, jsonPayload, timestamp);
67+
notificationJob = createNotificationJobFromCurrent(context, notification, jsonPayload, timestamp);
6468
}
6569

6670
/**
@@ -69,11 +73,12 @@ public class OSNotificationController {
6973
* <br/><br/>
7074
* @see OSNotificationGenerationJob
7175
*/
72-
private OSNotificationGenerationJob createNotificationJobFromCurrent(Context context, JSONObject jsonPayload, Long timestamp) {
76+
private OSNotificationGenerationJob createNotificationJobFromCurrent(Context context, OSNotification notification, JSONObject jsonPayload, Long timestamp) {
7377
OSNotificationGenerationJob notificationJob = new OSNotificationGenerationJob(callbackCompleter, context);
7478
notificationJob.setJsonPayload(jsonPayload);
7579
notificationJob.setShownTimeStamp(timestamp);
7680
notificationJob.setRestoring(restoring);
81+
notificationJob.setNotification(notification);
7782
return notificationJob;
7883
}
7984

@@ -88,13 +93,14 @@ private OSNotificationGenerationJob createNotificationJobFromCurrent(Context con
8893
void processNotification(OSNotification originalNotification, @Nullable OSNotification notification) {
8994
if (notification != null) {
9095
boolean display = isStringNotEmpty(notification.getBody());
91-
if (!display) {
92-
// Save as processed to prevent possible duplicate calls from canonical ids
93-
notDisplayNotificationLogic(originalNotification);
94-
} else {
96+
boolean withinTtl = isNotificationWithinTTL();
97+
if (display && withinTtl) {
9598
// Set modified notification
9699
notificationJob.setNotification(notification);
97100
NotificationBundleProcessor.processJobForDisplay(this, fromBackgroundLogic);
101+
} else {
102+
// Save as processed to prevent possible duplicate calls from canonical ids
103+
notDisplayNotificationLogic(originalNotification);
98104
}
99105
// Delay to prevent CPU spikes
100106
// Normally more than one notification is restored at a time
@@ -120,6 +126,19 @@ private void notDisplayNotificationLogic(OSNotification originalNotification) {
120126
}
121127
}
122128

129+
public boolean isNotificationWithinTTL() {
130+
boolean useTtl = OneSignal.getRemoteParamController().isRestoreTTLFilterActive();
131+
if (!useTtl)
132+
return true;
133+
134+
long currentTimeInSeconds = OneSignal.getTime().getCurrentThreadTimeMillis() / 1_000;
135+
long sentTime = notificationJob.getNotification().getSentTime();
136+
// If available TTL times comes in seconds, by default is 3 days in seconds
137+
int ttl = notificationJob.getNotification().getTtl();
138+
139+
return sentTime + ttl > currentTimeInSeconds;
140+
}
141+
123142
public OSNotificationGenerationJob getNotificationJob() {
124143
return notificationJob;
125144
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ static void processNotificationData(CallbackToFutureAdapter.Completer<Listenable
140140
Context context, int androidNotificationId, JSONObject jsonPayload,
141141
boolean isRestoring, Long timestamp) {
142142
OSNotification notification = new OSNotification(null, jsonPayload, androidNotificationId);
143-
OSNotificationController controller = new OSNotificationController(completer, context, jsonPayload, isRestoring, true, timestamp);
143+
OSNotificationController controller = new OSNotificationController(completer, context, notification, jsonPayload, isRestoring, true, timestamp);
144144
OSNotificationReceivedEvent notificationReceived = new OSNotificationReceivedEvent(controller, notification);
145145

146146
if (OneSignal.remoteNotificationReceivedHandler != null)

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

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,7 @@ void saveRemoteParams(OneSignalRemoteParams.Params remoteParams,
2323
OneSignalPrefs.PREFS_GT_FIREBASE_TRACKING_ENABLED,
2424
remoteParams.firebaseAnalytics
2525
);
26-
OneSignalPrefs.saveBool(
27-
OneSignalPrefs.PREFS_ONESIGNAL,
28-
OneSignalPrefs.PREFS_OS_RESTORE_TTL_FILTER,
29-
remoteParams.restoreTTLFilter
30-
);
26+
saveRestoreTTLFilter(remoteParams.restoreTTLFilter);
3127
OneSignalPrefs.saveBool(
3228
OneSignalPrefs.PREFS_ONESIGNAL,
3329
OneSignalPrefs.PREFS_OS_CLEAR_GROUP_SUMMARY_CLICK,
@@ -82,6 +78,18 @@ void clearRemoteParams() {
8278
remoteParams = null;
8379
}
8480

81+
private void saveRestoreTTLFilter(boolean restoreTTLFilter) {
82+
OneSignalPrefs.saveBool(
83+
OneSignalPrefs.PREFS_ONESIGNAL,
84+
OneSignalPrefs.PREFS_OS_RESTORE_TTL_FILTER,
85+
remoteParams.restoreTTLFilter
86+
);
87+
}
88+
89+
boolean isRestoreTTLFilterActive() {
90+
return OneSignalPrefs.getBool(OneSignalPrefs.PREFS_ONESIGNAL, OneSignalPrefs.PREFS_OS_RESTORE_TTL_FILTER, true);
91+
}
92+
8593
private void saveReceiveReceiptEnabled(boolean receiveReceiptEnabled) {
8694
OneSignalPrefs.saveBool(
8795
OneSignalPrefs.PREFS_ONESIGNAL,

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -451,7 +451,7 @@ static StringBuilder recentUninteractedWithNotificationsWhere() {
451451
NotificationTable.COLUMN_NAME_IS_SUMMARY + " = 0"
452452
);
453453

454-
boolean useTtl = OneSignalPrefs.getBool(OneSignalPrefs.PREFS_ONESIGNAL, OneSignalPrefs.PREFS_OS_RESTORE_TTL_FILTER,true);
454+
boolean useTtl = OneSignal.getRemoteParamController().isRestoreTTLFilterActive();
455455
if (useTtl) {
456456
String expireTimeWhere = " AND " + NotificationTable.COLUMN_NAME_EXPIRE_TIME + " > " + currentTimeSec;
457457
where.append(expireTimeWhere);

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

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88

99
import com.huawei.hms.push.RemoteMessage;
1010

11+
import org.json.JSONException;
12+
import org.json.JSONObject;
13+
1114
import java.util.concurrent.atomic.AtomicBoolean;
1215

1316
/**
@@ -20,6 +23,9 @@
2023
*/
2124
public class OneSignalHmsEventBridge {
2225

26+
public static final String HMS_TTL_KEY = "hms.ttl";
27+
public static final String HMS_SENT_TIME_KEY = "hms.sent_time";
28+
2329
private static final AtomicBoolean firstToken = new AtomicBoolean(true);
2430

2531
/**
@@ -44,6 +50,15 @@ public static void onNewToken(@NonNull Context context, @NonNull String token) {
4450
}
4551

4652
public static void onMessageReceived(@NonNull Context context, @NonNull RemoteMessage message) {
47-
NotificationPayloadProcessorHMS.processDataMessageReceived(context, message.getData());
53+
String data = message.getData();
54+
try {
55+
JSONObject messageDataJSON = new JSONObject(message.getData());
56+
messageDataJSON.put(HMS_TTL_KEY, message.getTtl());
57+
messageDataJSON.put(HMS_SENT_TIME_KEY, message.getSentTime());
58+
data = messageDataJSON.toString();
59+
} catch (JSONException e) {
60+
OneSignal.Log(OneSignal.LOG_LEVEL.ERROR, "OneSignalHmsEventBridge error when trying to create RemoteMessage data JSON");
61+
}
62+
NotificationPayloadProcessorHMS.processDataMessageReceived(context, data);
4863
}
4964
}

OneSignalSDK/unittest/src/test/java/com/onesignal/MockOSTimeImpl.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,11 @@ public void advanceSystemTimeBy(long sec) {
4444
setMockedTime(getCurrentTimeMillis() + ms);
4545
}
4646

47+
public void advanceThreadTimeBy(long sec) {
48+
long ms = sec * 1_000L;
49+
setMockedCurrentThreadTimeMillis(getCurrentThreadTimeMillis() + ms);
50+
}
51+
4752
public void advanceSystemAndElapsedTimeBy(long sec) {
4853
long ms = sec * 1_000L;
4954
setMockedElapsedTime(getCurrentTimeMillis() + ms);

OneSignalSDK/unittest/src/test/java/com/onesignal/OneSignalPackagePrivateHelper.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@
88

99
import androidx.annotation.NonNull;
1010
import androidx.annotation.Nullable;
11+
import androidx.concurrent.futures.CallbackToFutureAdapter;
12+
import androidx.work.ListenableWorker;
1113

14+
import com.huawei.hms.push.RemoteMessage;
1215
import com.onesignal.influence.data.OSTrackerFactory;
1316

1417
import org.json.JSONArray;
@@ -28,6 +31,10 @@
2831
import static org.robolectric.Shadows.shadowOf;
2932

3033
public class OneSignalPackagePrivateHelper {
34+
35+
public static final String GOOGLE_SENT_TIME_KEY = OSNotificationController.GOOGLE_SENT_TIME_KEY;
36+
public static final String GOOGLE_TTL_KEY = OSNotificationController.GOOGLE_TTL_KEY;
37+
3138
public static final String IN_APP_MESSAGES_JSON_KEY = com.onesignal.OSInAppMessageController.IN_APP_MESSAGES_JSON_KEY;
3239

3340
public static final long MIN_ON_SESSION_TIME_MILLIS = com.onesignal.OneSignal.MIN_ON_SESSION_TIME_MILLIS;
@@ -179,6 +186,10 @@ public static void FCMBroadcastReceiver_onReceived_withBundle(Context context, B
179186
threadAndTaskWait();
180187
}
181188

189+
public static void HMSEventBridge_onMessageReceive(final Context context, final RemoteMessage message) {
190+
OneSignalHmsEventBridge.onMessageReceived(context, message);
191+
}
192+
182193
public static void HMSProcessor_processDataMessageReceived(final Context context, final String jsonStrPayload) {
183194
NotificationPayloadProcessorHMS.processDataMessageReceived(context, jsonStrPayload);
184195
}

0 commit comments

Comments
 (0)