Skip to content

Commit

Permalink
Add TTL notification display control (#1479)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
Jeasmine authored Nov 12, 2021
1 parent 3bcc63f commit 131a4ac
Show file tree
Hide file tree
Showing 15 changed files with 264 additions and 73 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,12 @@

package com.onesignal;

import static com.onesignal.GenerateNotification.BUNDLE_KEY_ACTION_ID;
import static com.onesignal.OSUtils.isStringNotEmpty;

import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.os.Build;
import android.os.Bundle;

import androidx.annotation.NonNull;
Expand All @@ -47,9 +49,6 @@

import java.util.Set;

import static com.onesignal.GenerateNotification.BUNDLE_KEY_ACTION_ID;
import static com.onesignal.OSUtils.isStringNotEmpty;

/** Processes the Bundle received from a push.
* This class handles both processing bundles from a BroadcastReceiver or from a Service
* - Entry points are processBundleFromReceiver or ProcessFromFCMIntentService respectively
Expand Down Expand Up @@ -249,8 +248,8 @@ private static void saveNotification(OSNotificationGenerationJob notificationJob
values.put(NotificationTable.COLUMN_NAME_MESSAGE, notificationJob.getBody().toString());

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,6 @@ private static void markNotificationsConsumed(Context context, Intent intent, On
} else
whereStr = NotificationTable.COLUMN_NAME_ANDROID_NOTIFICATION_ID + " = " + intent.getIntExtra(BUNDLE_KEY_ANDROID_NOTIFICATION_ID, 0);


clearStatusBarNotifications(context, writableDb, summaryGroup);
writableDb.update(NotificationTable.TABLE_NAME, newContentValuesWithConsumed(intent), whereStr, whereArgs);
BadgeCountUpdater.update(writableDb, context);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@

import static com.onesignal.GenerateNotification.BUNDLE_KEY_ACTION_ID;
import static com.onesignal.NotificationBundleProcessor.PUSH_ADDITIONAL_DATA_KEY;
import static com.onesignal.OSNotificationController.GOOGLE_SENT_TIME_KEY;
import static com.onesignal.OSNotificationController.GOOGLE_TTL_KEY;
import static com.onesignal.OneSignalHmsEventBridge.HMS_SENT_TIME_KEY;
import static com.onesignal.OneSignalHmsEventBridge.HMS_TTL_KEY;

/**
* The notification the user received
Expand Down Expand Up @@ -87,21 +91,16 @@ public class OSNotification {
private int priority;
private String rawPayload;

private long sentTime;
private int ttl;

protected OSNotification() {
}

OSNotification(@NonNull JSONObject payload) {
this(null, payload, 0);
}

OSNotification(@NonNull JSONObject payload, int androidNotificationId) {
this(null, payload, androidNotificationId);
}

OSNotification(@Nullable List<OSNotification> groupedNotifications, @NonNull JSONObject payload) {
this(groupedNotifications, payload, 0);
}

OSNotification(@Nullable List<OSNotification> groupedNotifications, @NonNull JSONObject jsonPayload, int androidNotificationId) {
initPayloadData(jsonPayload);
this.groupedNotifications = groupedNotifications;
Expand Down Expand Up @@ -144,6 +143,18 @@ private void initPayloadData(JSONObject currentJsonPayload) {
return;
}

long currentTime = OneSignal.getTime().getCurrentThreadTimeMillis();
if (currentJsonPayload.has(GOOGLE_TTL_KEY)) {
sentTime = currentJsonPayload.optLong(GOOGLE_SENT_TIME_KEY, currentTime) / 1_000;
ttl = currentJsonPayload.optInt(GOOGLE_TTL_KEY, OSNotificationRestoreWorkManager.DEFAULT_TTL_IF_NOT_IN_PAYLOAD);
} else if (currentJsonPayload.has(HMS_TTL_KEY)) {
sentTime = currentJsonPayload.optLong(HMS_SENT_TIME_KEY, currentTime) / 1_000;
ttl = currentJsonPayload.optInt(HMS_TTL_KEY, OSNotificationRestoreWorkManager.DEFAULT_TTL_IF_NOT_IN_PAYLOAD);
} else {
sentTime = currentTime / 1_000;
ttl = OSNotificationRestoreWorkManager.DEFAULT_TTL_IF_NOT_IN_PAYLOAD;
}

notificationId = customJson.optString("i");
templateId = customJson.optString("ti");
templateName = customJson.optString("tn");
Expand Down Expand Up @@ -243,6 +254,8 @@ OSNotification copy() {
.setCollapseId(collapseId)
.setPriority(priority)
.setRawPayload(rawPayload)
.setSenttime(sentTime)
.setTTL(ttl)
.build();
}

Expand Down Expand Up @@ -451,6 +464,22 @@ void setRawPayload(String rawPayload) {
this.rawPayload = rawPayload;
}

public long getSentTime() {
return sentTime;
}

private void setSentTime(long sentTime) {
this.sentTime = sentTime;
}

public int getTtl() {
return ttl;
}

private void setTtl(int ttl) {
this.ttl = ttl;
}

public JSONObject toJSONObject() {
JSONObject mainObj = new JSONObject();

Expand Down Expand Up @@ -631,6 +660,9 @@ public static class OSNotificationBuilder {
private int priority;
private String rawPayload;

private long sentTime;
private int ttl;

public OSNotificationBuilder() {
}

Expand Down Expand Up @@ -759,6 +791,16 @@ public OSNotificationBuilder setRawPayload(String rawPayload) {
return this;
}

public OSNotificationBuilder setSenttime(long sentTime) {
this.sentTime = sentTime;
return this;
}

public OSNotificationBuilder setTTL(int ttl) {
this.ttl = ttl;
return this;
}

public OSNotification build() {
OSNotification payload = new OSNotification();
payload.setNotificationExtender(notificationExtender);
Expand Down Expand Up @@ -786,6 +828,8 @@ public OSNotification build() {
payload.setCollapseId(collapseId);
payload.setPriority(priority);
payload.setRawPayload(rawPayload);
payload.setSentTime(sentTime);
payload.setTtl(ttl);
return payload;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@

package com.onesignal;

import static com.onesignal.OSUtils.isStringNotEmpty;
import static com.onesignal.OneSignalHmsEventBridge.HMS_SENT_TIME_KEY;
import static com.onesignal.OneSignalHmsEventBridge.HMS_TTL_KEY;

import android.content.Context;

import androidx.annotation.Nullable;
Expand All @@ -35,12 +39,12 @@

import org.json.JSONObject;

import static com.onesignal.OSUtils.isStringNotEmpty;

public class OSNotificationController {

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

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

OSNotificationController(CallbackToFutureAdapter.Completer<ListenableWorker.Result> callbackCompleter,
Context context, JSONObject jsonPayload, boolean restoring, boolean fromBackgroundLogic, Long timestamp) {
Context context, OSNotification notification, JSONObject jsonPayload, boolean restoring, boolean fromBackgroundLogic, Long timestamp) {
this.callbackCompleter = callbackCompleter;
this.restoring = restoring;
this.fromBackgroundLogic = fromBackgroundLogic;

notificationJob = createNotificationJobFromCurrent(context, jsonPayload, timestamp);
notificationJob = createNotificationJobFromCurrent(context, notification, jsonPayload, timestamp);
}

/**
Expand All @@ -69,11 +73,12 @@ public class OSNotificationController {
* <br/><br/>
* @see OSNotificationGenerationJob
*/
private OSNotificationGenerationJob createNotificationJobFromCurrent(Context context, JSONObject jsonPayload, Long timestamp) {
private OSNotificationGenerationJob createNotificationJobFromCurrent(Context context, OSNotification notification, JSONObject jsonPayload, Long timestamp) {
OSNotificationGenerationJob notificationJob = new OSNotificationGenerationJob(callbackCompleter, context);
notificationJob.setJsonPayload(jsonPayload);
notificationJob.setShownTimeStamp(timestamp);
notificationJob.setRestoring(restoring);
notificationJob.setNotification(notification);
return notificationJob;
}

Expand All @@ -88,13 +93,14 @@ private OSNotificationGenerationJob createNotificationJobFromCurrent(Context con
void processNotification(OSNotification originalNotification, @Nullable OSNotification notification) {
if (notification != null) {
boolean display = isStringNotEmpty(notification.getBody());
if (!display) {
// Save as processed to prevent possible duplicate calls from canonical ids
notDisplayNotificationLogic(originalNotification);
} else {
boolean withinTtl = isNotificationWithinTTL();
if (display && withinTtl) {
// Set modified notification
notificationJob.setNotification(notification);
NotificationBundleProcessor.processJobForDisplay(this, fromBackgroundLogic);
} else {
// Save as processed to prevent possible duplicate calls from canonical ids
notDisplayNotificationLogic(originalNotification);
}
// Delay to prevent CPU spikes
// Normally more than one notification is restored at a time
Expand All @@ -120,6 +126,19 @@ private void notDisplayNotificationLogic(OSNotification originalNotification) {
}
}

public boolean isNotificationWithinTTL() {
boolean useTtl = OneSignal.getRemoteParamController().isRestoreTTLFilterActive();
if (!useTtl)
return true;

long currentTimeInSeconds = OneSignal.getTime().getCurrentThreadTimeMillis() / 1_000;
long sentTime = notificationJob.getNotification().getSentTime();
// If available TTL times comes in seconds, by default is 3 days in seconds
int ttl = notificationJob.getNotification().getTtl();

return sentTime + ttl > currentTimeInSeconds;
}

public OSNotificationGenerationJob getNotificationJob() {
return notificationJob;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ static void processNotificationData(CallbackToFutureAdapter.Completer<Listenable
Context context, int androidNotificationId, JSONObject jsonPayload,
boolean isRestoring, Long timestamp) {
OSNotification notification = new OSNotification(null, jsonPayload, androidNotificationId);
OSNotificationController controller = new OSNotificationController(completer, context, jsonPayload, isRestoring, true, timestamp);
OSNotificationController controller = new OSNotificationController(completer, context, notification, jsonPayload, isRestoring, true, timestamp);
OSNotificationReceivedEvent notificationReceived = new OSNotificationReceivedEvent(controller, notification);

if (OneSignal.remoteNotificationReceivedHandler != null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,7 @@ void saveRemoteParams(OneSignalRemoteParams.Params remoteParams,
OneSignalPrefs.PREFS_GT_FIREBASE_TRACKING_ENABLED,
remoteParams.firebaseAnalytics
);
OneSignalPrefs.saveBool(
OneSignalPrefs.PREFS_ONESIGNAL,
OneSignalPrefs.PREFS_OS_RESTORE_TTL_FILTER,
remoteParams.restoreTTLFilter
);
saveRestoreTTLFilter(remoteParams.restoreTTLFilter);
OneSignalPrefs.saveBool(
OneSignalPrefs.PREFS_ONESIGNAL,
OneSignalPrefs.PREFS_OS_CLEAR_GROUP_SUMMARY_CLICK,
Expand Down Expand Up @@ -82,6 +78,18 @@ void clearRemoteParams() {
remoteParams = null;
}

private void saveRestoreTTLFilter(boolean restoreTTLFilter) {
OneSignalPrefs.saveBool(
OneSignalPrefs.PREFS_ONESIGNAL,
OneSignalPrefs.PREFS_OS_RESTORE_TTL_FILTER,
remoteParams.restoreTTLFilter
);
}

boolean isRestoreTTLFilterActive() {
return OneSignalPrefs.getBool(OneSignalPrefs.PREFS_ONESIGNAL, OneSignalPrefs.PREFS_OS_RESTORE_TTL_FILTER, true);
}

private void saveReceiveReceiptEnabled(boolean receiveReceiptEnabled) {
OneSignalPrefs.saveBool(
OneSignalPrefs.PREFS_ONESIGNAL,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -451,7 +451,7 @@ static StringBuilder recentUninteractedWithNotificationsWhere() {
NotificationTable.COLUMN_NAME_IS_SUMMARY + " = 0"
);

boolean useTtl = OneSignalPrefs.getBool(OneSignalPrefs.PREFS_ONESIGNAL, OneSignalPrefs.PREFS_OS_RESTORE_TTL_FILTER,true);
boolean useTtl = OneSignal.getRemoteParamController().isRestoreTTLFilterActive();
if (useTtl) {
String expireTimeWhere = " AND " + NotificationTable.COLUMN_NAME_EXPIRE_TIME + " > " + currentTimeSec;
where.append(expireTimeWhere);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@

import com.huawei.hms.push.RemoteMessage;

import org.json.JSONException;
import org.json.JSONObject;

import java.util.concurrent.atomic.AtomicBoolean;

/**
Expand All @@ -20,6 +23,9 @@
*/
public class OneSignalHmsEventBridge {

public static final String HMS_TTL_KEY = "hms.ttl";
public static final String HMS_SENT_TIME_KEY = "hms.sent_time";

private static final AtomicBoolean firstToken = new AtomicBoolean(true);

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

public static void onMessageReceived(@NonNull Context context, @NonNull RemoteMessage message) {
NotificationPayloadProcessorHMS.processDataMessageReceived(context, message.getData());
String data = message.getData();
try {
JSONObject messageDataJSON = new JSONObject(message.getData());
messageDataJSON.put(HMS_TTL_KEY, message.getTtl());
messageDataJSON.put(HMS_SENT_TIME_KEY, message.getSentTime());
data = messageDataJSON.toString();
} catch (JSONException e) {
OneSignal.Log(OneSignal.LOG_LEVEL.ERROR, "OneSignalHmsEventBridge error when trying to create RemoteMessage data JSON");
}
NotificationPayloadProcessorHMS.processDataMessageReceived(context, data);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ public void advanceSystemTimeBy(long sec) {
setMockedTime(getCurrentTimeMillis() + ms);
}

public void advanceThreadTimeBy(long sec) {
long ms = sec * 1_000L;
setMockedCurrentThreadTimeMillis(getCurrentThreadTimeMillis() + ms);
}

public void advanceSystemAndElapsedTimeBy(long sec) {
long ms = sec * 1_000L;
setMockedElapsedTime(getCurrentTimeMillis() + ms);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.concurrent.futures.CallbackToFutureAdapter;
import androidx.work.ListenableWorker;

import com.huawei.hms.push.RemoteMessage;
import com.onesignal.influence.data.OSTrackerFactory;

import org.json.JSONArray;
Expand All @@ -28,6 +31,10 @@
import static org.robolectric.Shadows.shadowOf;

public class OneSignalPackagePrivateHelper {

public static final String GOOGLE_SENT_TIME_KEY = OSNotificationController.GOOGLE_SENT_TIME_KEY;
public static final String GOOGLE_TTL_KEY = OSNotificationController.GOOGLE_TTL_KEY;

public static final String IN_APP_MESSAGES_JSON_KEY = com.onesignal.OSInAppMessageController.IN_APP_MESSAGES_JSON_KEY;

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

public static void HMSEventBridge_onMessageReceive(final Context context, final RemoteMessage message) {
OneSignalHmsEventBridge.onMessageReceived(context, message);
}

public static void HMSProcessor_processDataMessageReceived(final Context context, final String jsonStrPayload) {
NotificationPayloadProcessorHMS.processDataMessageReceived(context, jsonStrPayload);
}
Expand Down
Loading

0 comments on commit 131a4ac

Please sign in to comment.