From 473b6c7e58c7d01fa75b0b224c887d82df703be4 Mon Sep 17 00:00:00 2001 From: Jeasmine Nahui Date: Thu, 4 Nov 2021 13:11:32 -0300 Subject: [PATCH] Add TTL notification display control * Notifications where being batches and displayed by android standBy * For cases where notification sent time + ttl is more than the current time, don't display notification --- .../NotificationBundleProcessor.java | 11 +- .../NotificationOpenedProcessor.java | 1 - .../onesignal/OSNotificationController.java | 43 +++++++- .../onesignal/OSRemoteParamController.java | 18 ++- .../java/com/onesignal/OneSignalDbHelper.java | 2 +- .../onesignal/OneSignalHmsEventBridge.java | 17 ++- .../java/com/onesignal/MockOSTimeImpl.java | 5 + .../OneSignalPackagePrivateHelper.java | 11 ++ .../onesignal/ShadowBadgeCountUpdater.java | 4 + .../com/onesignal/ShadowHmsRemoteMessage.java | 32 ++++++ .../onesignal/GenerateNotificationRunner.java | 103 +++++++++++------- ...MessageReceivedIntegrationTestsRunner.java | 32 ++++++ .../java/com/test/onesignal/TestHelpers.java | 2 + 13 files changed, 220 insertions(+), 61 deletions(-) create mode 100644 OneSignalSDK/unittest/src/test/java/com/onesignal/ShadowHmsRemoteMessage.java diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/NotificationBundleProcessor.java b/OneSignalSDK/onesignal/src/main/java/com/onesignal/NotificationBundleProcessor.java index 2345c15f29..a7b4f41ba0 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/NotificationBundleProcessor.java +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/NotificationBundleProcessor.java @@ -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; @@ -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 @@ -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); diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/NotificationOpenedProcessor.java b/OneSignalSDK/onesignal/src/main/java/com/onesignal/NotificationOpenedProcessor.java index f0d2bedf8f..afdafd571b 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/NotificationOpenedProcessor.java +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/NotificationOpenedProcessor.java @@ -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); diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/OSNotificationController.java b/OneSignalSDK/onesignal/src/main/java/com/onesignal/OSNotificationController.java index 3c12b8f441..a6d9433de2 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/OSNotificationController.java +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/OSNotificationController.java @@ -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; @@ -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 callbackCompleter; private final OSNotificationGenerationJob notificationJob; @@ -88,13 +92,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 ttl = isNotificationWithinTTL(); + if (display && ttl) { // 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 @@ -120,6 +125,32 @@ private void notDisplayNotificationLogic(OSNotification originalNotification) { } } + public boolean isNotificationWithinTTL() { + boolean useTtl = OneSignal.getRemoteParamController().isRestoreTTLFilterActive(); + if (!useTtl) + return true; + + JSONObject jsonPayload = notificationJob.getJsonPayload(); + long currentTime = OneSignal.getTime().getCurrentThreadTimeMillis(); + long currentTimeInSeconds = currentTime / 1_000; + long sentTime; + // If available TTL times comes in seconds, by default is 3 days in seconds + int ttl; + + if (jsonPayload.has(GOOGLE_TTL_KEY)) { + sentTime = jsonPayload.optLong(GOOGLE_SENT_TIME_KEY, currentTime) / 1_000; + ttl = jsonPayload.optInt(GOOGLE_TTL_KEY, OSNotificationRestoreWorkManager.DEFAULT_TTL_IF_NOT_IN_PAYLOAD); + } else if (jsonPayload.has(HMS_TTL_KEY)) { + sentTime = jsonPayload.optLong(HMS_SENT_TIME_KEY, currentTime) / 1_000; + ttl = jsonPayload.optInt(HMS_TTL_KEY, OSNotificationRestoreWorkManager.DEFAULT_TTL_IF_NOT_IN_PAYLOAD); + } else { + // If no TTL provided display notification + return true; + } + + return sentTime + ttl > currentTimeInSeconds; + } + public OSNotificationGenerationJob getNotificationJob() { return notificationJob; } diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/OSRemoteParamController.java b/OneSignalSDK/onesignal/src/main/java/com/onesignal/OSRemoteParamController.java index 9887a5c97e..67b2e4b418 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/OSRemoteParamController.java +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/OSRemoteParamController.java @@ -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, @@ -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, diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/OneSignalDbHelper.java b/OneSignalSDK/onesignal/src/main/java/com/onesignal/OneSignalDbHelper.java index bd77a2214d..1519363394 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/OneSignalDbHelper.java +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/OneSignalDbHelper.java @@ -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); diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/OneSignalHmsEventBridge.java b/OneSignalSDK/onesignal/src/main/java/com/onesignal/OneSignalHmsEventBridge.java index b8f629d65a..5639912664 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/OneSignalHmsEventBridge.java +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/OneSignalHmsEventBridge.java @@ -8,6 +8,9 @@ import com.huawei.hms.push.RemoteMessage; +import org.json.JSONException; +import org.json.JSONObject; + import java.util.concurrent.atomic.AtomicBoolean; /** @@ -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); /** @@ -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); } } diff --git a/OneSignalSDK/unittest/src/test/java/com/onesignal/MockOSTimeImpl.java b/OneSignalSDK/unittest/src/test/java/com/onesignal/MockOSTimeImpl.java index 28bf467725..adaf8ad051 100644 --- a/OneSignalSDK/unittest/src/test/java/com/onesignal/MockOSTimeImpl.java +++ b/OneSignalSDK/unittest/src/test/java/com/onesignal/MockOSTimeImpl.java @@ -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); diff --git a/OneSignalSDK/unittest/src/test/java/com/onesignal/OneSignalPackagePrivateHelper.java b/OneSignalSDK/unittest/src/test/java/com/onesignal/OneSignalPackagePrivateHelper.java index 1e57443d1e..50dcd2e862 100644 --- a/OneSignalSDK/unittest/src/test/java/com/onesignal/OneSignalPackagePrivateHelper.java +++ b/OneSignalSDK/unittest/src/test/java/com/onesignal/OneSignalPackagePrivateHelper.java @@ -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; @@ -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; @@ -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); } diff --git a/OneSignalSDK/unittest/src/test/java/com/onesignal/ShadowBadgeCountUpdater.java b/OneSignalSDK/unittest/src/test/java/com/onesignal/ShadowBadgeCountUpdater.java index a9be945cb9..f721ecbdc1 100644 --- a/OneSignalSDK/unittest/src/test/java/com/onesignal/ShadowBadgeCountUpdater.java +++ b/OneSignalSDK/unittest/src/test/java/com/onesignal/ShadowBadgeCountUpdater.java @@ -41,4 +41,8 @@ public class ShadowBadgeCountUpdater { public static void updateCount(int count, Context context) { lastCount = count; } + + public static void resetStatics() { + lastCount = 0; + } } diff --git a/OneSignalSDK/unittest/src/test/java/com/onesignal/ShadowHmsRemoteMessage.java b/OneSignalSDK/unittest/src/test/java/com/onesignal/ShadowHmsRemoteMessage.java new file mode 100644 index 0000000000..1e213b39a3 --- /dev/null +++ b/OneSignalSDK/unittest/src/test/java/com/onesignal/ShadowHmsRemoteMessage.java @@ -0,0 +1,32 @@ +package com.onesignal; + +import androidx.annotation.Nullable; + +import com.huawei.hms.push.RemoteMessage; + +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; + +@Implements(RemoteMessage.class) +public class ShadowHmsRemoteMessage { + + @Nullable + public static String data; + public static int ttl; + public static long sentTime; + + @Implementation + public String getData() { + return data; + } + + @Implementation + public int getTtl() { + return ttl; + } + + public long getSentTime() { + return sentTime; + } + +} diff --git a/OneSignalSDK/unittest/src/test/java/com/test/onesignal/GenerateNotificationRunner.java b/OneSignalSDK/unittest/src/test/java/com/test/onesignal/GenerateNotificationRunner.java index ad2212e423..06fc897c93 100644 --- a/OneSignalSDK/unittest/src/test/java/com/test/onesignal/GenerateNotificationRunner.java +++ b/OneSignalSDK/unittest/src/test/java/com/test/onesignal/GenerateNotificationRunner.java @@ -27,6 +27,40 @@ package com.test.onesignal; +import static com.onesignal.OneSignalPackagePrivateHelper.FCMBroadcastReceiver_onReceived_withIntent; +import static com.onesignal.OneSignalPackagePrivateHelper.FCMBroadcastReceiver_processBundle; +import static com.onesignal.OneSignalPackagePrivateHelper.GenerateNotification.BUNDLE_KEY_ACTION_ID; +import static com.onesignal.OneSignalPackagePrivateHelper.GenerateNotification.BUNDLE_KEY_ANDROID_NOTIFICATION_ID; +import static com.onesignal.OneSignalPackagePrivateHelper.GenerateNotification.BUNDLE_KEY_ONESIGNAL_DATA; +import static com.onesignal.OneSignalPackagePrivateHelper.NotificationBundleProcessor.PUSH_MINIFIED_BUTTONS_LIST; +import static com.onesignal.OneSignalPackagePrivateHelper.NotificationBundleProcessor.PUSH_MINIFIED_BUTTON_ID; +import static com.onesignal.OneSignalPackagePrivateHelper.NotificationBundleProcessor.PUSH_MINIFIED_BUTTON_TEXT; +import static com.onesignal.OneSignalPackagePrivateHelper.NotificationBundleProcessor_ProcessFromFCMIntentService; +import static com.onesignal.OneSignalPackagePrivateHelper.NotificationBundleProcessor_ProcessFromFCMIntentService_NoWrap; +import static com.onesignal.OneSignalPackagePrivateHelper.NotificationOpenedProcessor_processFromContext; +import static com.onesignal.OneSignalPackagePrivateHelper.NotificationSummaryManager_updateSummaryNotificationAfterChildRemoved; +import static com.onesignal.OneSignalPackagePrivateHelper.OneSignal_getAccentColor; +import static com.onesignal.OneSignalPackagePrivateHelper.OneSignal_setTime; +import static com.onesignal.OneSignalPackagePrivateHelper.OneSignal_setupNotificationServiceExtension; +import static com.onesignal.OneSignalPackagePrivateHelper.createInternalPayloadBundle; +import static com.onesignal.ShadowOneSignalRestClient.setRemoteParamsGetHtmlResponse; +import static com.onesignal.ShadowRoboNotificationManager.getNotificationsInGroup; +import static com.test.onesignal.RestClientAsserts.assertReportReceivedAtIndex; +import static com.test.onesignal.RestClientAsserts.assertRestCalls; +import static com.test.onesignal.TestHelpers.fastColdRestartApp; +import static com.test.onesignal.TestHelpers.pauseActivity; +import static com.test.onesignal.TestHelpers.startRemoteNotificationReceivedHandlerService; +import static com.test.onesignal.TestHelpers.threadAndTaskWait; +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertNotNull; +import static junit.framework.Assert.assertNull; +import static junit.framework.Assert.assertTrue; +import static org.hamcrest.CoreMatchers.not; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertThat; +import static org.robolectric.Shadows.shadowOf; + import android.app.Activity; import android.app.Notification; import android.app.NotificationManager; @@ -75,7 +109,6 @@ import com.onesignal.ShadowOSWebView; import com.onesignal.ShadowOneSignal; import com.onesignal.ShadowOneSignalRestClient; -import com.onesignal.ShadowReceiveReceiptController; import com.onesignal.ShadowResources; import com.onesignal.ShadowRoboNotificationManager; import com.onesignal.ShadowRoboNotificationManager.PostedNotification; @@ -109,41 +142,6 @@ import java.util.Map; import java.util.concurrent.TimeUnit; -import static com.onesignal.OneSignalPackagePrivateHelper.FCMBroadcastReceiver_onReceived_withIntent; -import static com.onesignal.OneSignalPackagePrivateHelper.FCMBroadcastReceiver_processBundle; -import static com.onesignal.OneSignalPackagePrivateHelper.GenerateNotification.BUNDLE_KEY_ACTION_ID; -import static com.onesignal.OneSignalPackagePrivateHelper.GenerateNotification.BUNDLE_KEY_ANDROID_NOTIFICATION_ID; -import static com.onesignal.OneSignalPackagePrivateHelper.GenerateNotification.BUNDLE_KEY_ONESIGNAL_DATA; -import static com.onesignal.OneSignalPackagePrivateHelper.NotificationBundleProcessor.PUSH_MINIFIED_BUTTONS_LIST; -import static com.onesignal.OneSignalPackagePrivateHelper.NotificationBundleProcessor.PUSH_MINIFIED_BUTTON_ID; -import static com.onesignal.OneSignalPackagePrivateHelper.NotificationBundleProcessor.PUSH_MINIFIED_BUTTON_TEXT; -import static com.onesignal.OneSignalPackagePrivateHelper.NotificationBundleProcessor_ProcessFromFCMIntentService; -import static com.onesignal.OneSignalPackagePrivateHelper.NotificationBundleProcessor_ProcessFromFCMIntentService_NoWrap; -import static com.onesignal.OneSignalPackagePrivateHelper.NotificationOpenedProcessor_processFromContext; -import static com.onesignal.OneSignalPackagePrivateHelper.NotificationSummaryManager_updateSummaryNotificationAfterChildRemoved; -import static com.onesignal.OneSignalPackagePrivateHelper.OneSignal_getAccentColor; -import static com.onesignal.OneSignalPackagePrivateHelper.OneSignal_setDelayTaskController; -import static com.onesignal.OneSignalPackagePrivateHelper.OneSignal_setTime; -import static com.onesignal.OneSignalPackagePrivateHelper.OneSignal_setupNotificationServiceExtension; -import static com.onesignal.OneSignalPackagePrivateHelper.createInternalPayloadBundle; -import static com.onesignal.ShadowOneSignalRestClient.setRemoteParamsGetHtmlResponse; -import static com.onesignal.ShadowRoboNotificationManager.getNotificationsInGroup; -import static com.test.onesignal.RestClientAsserts.assertReportReceivedAtIndex; -import static com.test.onesignal.RestClientAsserts.assertRestCalls; -import static com.test.onesignal.TestHelpers.fastColdRestartApp; -import static com.test.onesignal.TestHelpers.pauseActivity; -import static com.test.onesignal.TestHelpers.startRemoteNotificationReceivedHandlerService; -import static com.test.onesignal.TestHelpers.threadAndTaskWait; -import static junit.framework.Assert.assertEquals; -import static junit.framework.Assert.assertFalse; -import static junit.framework.Assert.assertNotNull; -import static junit.framework.Assert.assertNull; -import static junit.framework.Assert.assertTrue; -import static org.hamcrest.CoreMatchers.not; -import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertThat; -import static org.robolectric.Shadows.shadowOf; - @Config(packageName = "com.onesignal.example", shadows = { ShadowRoboNotificationManager.class, @@ -763,7 +761,8 @@ public void shouldSetBadgesWhenRestoringNotifications() throws Exception { assertEquals(1, ShadowBadgeCountUpdater.lastCount); } - @Test public void shouldNotRestoreNotificationsIfPermissionIsDisabled() throws Exception { + @Test + public void shouldNotRestoreNotificationsIfPermissionIsDisabled() throws Exception { NotificationBundleProcessor_ProcessFromFCMIntentService(blankActivity, getBaseNotifBundle()); ShadowNotificationManagerCompat.enabled = false; @@ -946,10 +945,12 @@ private void restoreNotifications() { } private void helperShouldRestoreNotificationsPastExpireTime(boolean should) throws Exception { + long sentTime = System.currentTimeMillis(); long ttl = 60L; + Bundle bundle = getBaseNotifBundle(); - bundle.putLong("google.sent_time", System.currentTimeMillis()); - bundle.putLong("google.ttl", ttl); + bundle.putLong(OneSignalPackagePrivateHelper.GOOGLE_SENT_TIME_KEY, sentTime); + bundle.putLong(OneSignalPackagePrivateHelper.GOOGLE_TTL_KEY, ttl); NotificationBundleProcessor_ProcessFromFCMIntentService(blankActivity, bundle); restoreNotifications(); @@ -960,6 +961,7 @@ private void helperShouldRestoreNotificationsPastExpireTime(boolean should) thro // Go forward just past the TTL of the notification time.advanceSystemTimeBy(ttl + 1); restoreNotifications(); + if (should) assertEquals(1, ShadowBadgeCountUpdater.lastCount); else @@ -1234,8 +1236,8 @@ public void shouldSetExpireTimeCorrectlyFromGoogleTTL() throws Exception { long ttl = 60L; Bundle bundle = getBaseNotifBundle(); - bundle.putLong("google.sent_time", sentTime); - bundle.putLong("google.ttl", ttl); + bundle.putLong(OneSignalPackagePrivateHelper.GOOGLE_SENT_TIME_KEY, sentTime); + bundle.putLong(OneSignalPackagePrivateHelper.GOOGLE_TTL_KEY, ttl); NotificationBundleProcessor_ProcessFromFCMIntentService(blankActivity, bundle); threadAndTaskWait(); @@ -1244,6 +1246,25 @@ public void shouldSetExpireTimeCorrectlyFromGoogleTTL() throws Exception { assertEquals(sentTime + (ttl * 1_000), expireTime * 1_000); } + @Test + @Config (sdk = 23, shadows = { ShadowGenerateNotification.class }) + public void notShowNotificationPastTTL() throws Exception { + long sentTime = time.getCurrentThreadTimeMillis(); + long ttl = 60L; + + Bundle bundle = getBaseNotifBundle(); + bundle.putLong(OneSignalPackagePrivateHelper.GOOGLE_SENT_TIME_KEY, sentTime); + bundle.putLong(OneSignalPackagePrivateHelper.GOOGLE_TTL_KEY, ttl); + + // Go forward just past the TTL of the notification + time.advanceThreadTimeBy(ttl + 1); + + NotificationBundleProcessor_ProcessFromFCMIntentService(blankActivity, bundle); + threadAndTaskWait(); + + assertEquals(0, ShadowBadgeCountUpdater.lastCount); + } + @Test @Config(shadows = { ShadowGenerateNotification.class }) public void shouldSetExpireTimeCorrectlyWhenMissingFromPayload() throws Exception { diff --git a/OneSignalSDK/unittest/src/test/java/com/test/onesignal/HMSDataMessageReceivedIntegrationTestsRunner.java b/OneSignalSDK/unittest/src/test/java/com/test/onesignal/HMSDataMessageReceivedIntegrationTestsRunner.java index c479fd99a3..d8c65c7214 100644 --- a/OneSignalSDK/unittest/src/test/java/com/test/onesignal/HMSDataMessageReceivedIntegrationTestsRunner.java +++ b/OneSignalSDK/unittest/src/test/java/com/test/onesignal/HMSDataMessageReceivedIntegrationTestsRunner.java @@ -2,9 +2,14 @@ import android.annotation.SuppressLint; import android.app.Activity; +import android.os.Bundle; +import com.huawei.hms.push.RemoteMessage; +import com.onesignal.MockOSTimeImpl; import com.onesignal.OneSignalPackagePrivateHelper.NotificationPayloadProcessorHMS; +import com.onesignal.ShadowBadgeCountUpdater; import com.onesignal.ShadowGenerateNotification; +import com.onesignal.ShadowHmsRemoteMessage; import com.onesignal.ShadowNotificationManagerCompat; import com.onesignal.ShadowOSUtils; import com.onesignal.ShadowRoboNotificationManager; @@ -28,9 +33,11 @@ import java.util.UUID; +import static com.onesignal.OneSignalPackagePrivateHelper.HMSEventBridge_onMessageReceive; import static com.onesignal.OneSignalPackagePrivateHelper.HMSProcessor_processDataMessageReceived; import static com.onesignal.OneSignalPackagePrivateHelper.OSNotificationFormatHelper.PAYLOAD_OS_NOTIFICATION_ID; import static com.onesignal.OneSignalPackagePrivateHelper.OSNotificationFormatHelper.PAYLOAD_OS_ROOT_CUSTOM; +import static com.onesignal.OneSignalPackagePrivateHelper.OneSignal_setTime; import static com.test.onesignal.TestHelpers.threadAndTaskWait; import static junit.framework.Assert.assertEquals; @@ -50,6 +57,8 @@ public class HMSDataMessageReceivedIntegrationTestsRunner { private static final String ALERT_TEST_MESSAGE_BODY = "Test Message body"; + private MockOSTimeImpl time; + @BeforeClass // Runs only once, before any tests public static void setUpClass() throws Exception { ShadowLog.stream = System.out; @@ -63,6 +72,9 @@ public void beforeEachTest() throws Exception { ShadowOSUtils.supportsHMS(true); + time = new MockOSTimeImpl(); + OneSignal_setTime(time); + blankActivityController = Robolectric.buildActivity(BlankActivity.class).create(); blankActivity = blankActivityController.get(); } @@ -106,6 +118,26 @@ public void basicPayload_shouldDisplayNotification() throws Exception { assertEquals(ALERT_TEST_MESSAGE_BODY, ShadowRoboNotificationManager.getLastShadowNotif().getBigText()); } + @Test + @Config(shadows = { ShadowGenerateNotification.class, ShadowHmsRemoteMessage.class, ShadowBadgeCountUpdater.class }) + public void ttl_shouldNotDisplayNotification() throws Exception { + blankActivityController.pause(); + + long sentTime = 1_635_971_895_940L; + int ttl = 60; + + time.setMockedCurrentThreadTimeMillis(sentTime * 1_000); + + ShadowHmsRemoteMessage.data = helperBasicOSPayload(); + ShadowHmsRemoteMessage.ttl = ttl; + ShadowHmsRemoteMessage.sentTime = sentTime; + + HMSEventBridge_onMessageReceive(blankActivity, new RemoteMessage(new Bundle())); + threadAndTaskWait(); + + assertEquals(0, ShadowBadgeCountUpdater.lastCount); + } + // NOTE: More tests can be added but they would be duplicated with GenerateNotificationRunner // In 4.0.0 or later these should be written in a reusable way between HMS, FCM, and ADM } diff --git a/OneSignalSDK/unittest/src/test/java/com/test/onesignal/TestHelpers.java b/OneSignalSDK/unittest/src/test/java/com/test/onesignal/TestHelpers.java index 64b24813aa..3c580b94ec 100644 --- a/OneSignalSDK/unittest/src/test/java/com/test/onesignal/TestHelpers.java +++ b/OneSignalSDK/unittest/src/test/java/com/test/onesignal/TestHelpers.java @@ -25,6 +25,7 @@ import com.onesignal.OneSignalPackagePrivateHelper.TestOneSignalPrefs; import com.onesignal.OneSignalShadowPackageManager; import com.onesignal.OSOutcomeEvent; +import com.onesignal.ShadowBadgeCountUpdater; import com.onesignal.ShadowCustomTabsClient; import com.onesignal.ShadowDynamicTimer; import com.onesignal.ShadowFCMBroadcastReceiver; @@ -128,6 +129,7 @@ static void beforeTestInitAndCleanup() throws Exception { ShadowGenerateNotification.resetStatics(); ShadowNotificationReceivedEvent.resetStatics(); ShadowOneSignalNotificationManager.resetStatics(); + ShadowBadgeCountUpdater.resetStatics(); lastException = null; }