Close #27949: Add engagement notification for inactive users

This commit is contained in:
Roger Yang 2022-11-25 12:41:01 -05:00 committed by mergify[bot]
parent 9b920a472c
commit 11efaff96c
15 changed files with 463 additions and 51 deletions

View File

@ -71,6 +71,14 @@ nimbus-validation:
settings-title:
type: string
description: The title of displayed in the Settings screen and app menu.
re-engagement-notification:
description: A feature that shows the re-enagement notification if the user is inactive.
hasExposure: true
exposureDescription: ""
variables:
enabled:
type: boolean
description: "If true, the re-engagement notification is shown to the inactive user."
search-term-groups:
description: A feature allowing the grouping of URLs around the search term that it came from.
hasExposure: true

View File

@ -206,6 +206,32 @@ events:
- https://github.com/mozilla-mobile/fenix/issues/27779
data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/27780
data_sensitivity:
- interaction
notification_emails:
- android-probes@mozilla.com
expires: 122
re_engagement_notif_tapped:
type: event
description: |
User tapped on the re-engagement notification
bugs:
- https://github.com/mozilla-mobile/fenix/issues/27949
data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/27978
data_sensitivity:
- interaction
notification_emails:
- android-probes@mozilla.com
expires: 122
re_engagement_notif_shown:
type: event
description: |
Re-engagement notification was shown to the user
bugs:
- https://github.com/mozilla-mobile/fenix/issues/27949
data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/27978
data_sensitivity:
- technical
notification_emails:

View File

@ -103,6 +103,7 @@ import org.mozilla.fenix.library.historymetadata.HistoryMetadataGroupFragmentDir
import org.mozilla.fenix.library.recentlyclosed.RecentlyClosedFragmentDirections
import org.mozilla.fenix.onboarding.DefaultBrowserNotificationWorker
import org.mozilla.fenix.onboarding.FenixOnboarding
import org.mozilla.fenix.onboarding.ReEngagementNotificationWorker
import org.mozilla.fenix.perf.MarkersActivityLifecycleCallbacks
import org.mozilla.fenix.perf.MarkersFragmentLifecycleCallbacks
import org.mozilla.fenix.perf.Performance
@ -377,6 +378,7 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
components.appStore.dispatch(AppAction.ResumedMetricsAction)
DefaultBrowserNotificationWorker.setDefaultBrowserNotificationIfNeeded(applicationContext)
ReEngagementNotificationWorker.setReEngagementNotificationIfNeeded(applicationContext)
}
}

View File

@ -261,6 +261,7 @@ class BrowserFragment : BaseBrowserFragment(), UserInteractionHandler {
}
subscribeToTabCollections()
updateLastBrowseActivity()
}
override fun onStop() {

View File

@ -6,12 +6,17 @@ package org.mozilla.fenix.home.intent
import android.content.Intent
import androidx.navigation.NavController
import mozilla.components.concept.engine.EngineSession
import mozilla.telemetry.glean.private.NoExtras
import org.mozilla.fenix.BrowserDirection
import org.mozilla.fenix.GleanMetrics.Events
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.browser.browsingmode.BrowsingMode
import org.mozilla.fenix.ext.openSetDefaultBrowserOption
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.onboarding.DefaultBrowserNotificationWorker.Companion.isDefaultBrowserNotificationIntent
import org.mozilla.fenix.onboarding.ReEngagementNotificationWorker
import org.mozilla.fenix.onboarding.ReEngagementNotificationWorker.Companion.isReEngagementNotificationIntent
/**
* When the default browser notification is tapped we need to launch [openSetDefaultBrowserOption]
@ -24,12 +29,26 @@ class DefaultBrowserIntentProcessor(
) : HomeIntentProcessor {
override fun process(intent: Intent, navController: NavController, out: Intent): Boolean {
return if (isDefaultBrowserNotificationIntent(intent)) {
activity.openSetDefaultBrowserOption()
Events.defaultBrowserNotifTapped.record(NoExtras())
true
} else {
false
return when {
isDefaultBrowserNotificationIntent(intent) -> {
Events.defaultBrowserNotifTapped.record(NoExtras())
activity.openSetDefaultBrowserOption()
true
}
isReEngagementNotificationIntent(intent) -> {
Events.reEngagementNotifTapped.record(NoExtras())
activity.browsingModeManager.mode = BrowsingMode.Private
activity.openToBrowserAndLoad(
ReEngagementNotificationWorker.NOTIFICATION_TARGET_URL,
newTab = true,
from = BrowserDirection.FromGlobal,
flags = EngineSession.LoadUrlFlags.external(),
)
true
}
else -> false
}
}
}

View File

@ -5,12 +5,9 @@
package org.mozilla.fenix.onboarding
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.os.Build
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.core.content.ContextCompat
@ -22,7 +19,6 @@ import androidx.work.WorkerParameters
import mozilla.components.service.glean.private.NoExtras
import mozilla.components.support.base.ids.SharedIdsHelper
import org.mozilla.fenix.GleanMetrics.Events
import org.mozilla.fenix.GleanMetrics.Events.marketingNotificationAllowed
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R
import org.mozilla.fenix.ext.settings
@ -36,9 +32,15 @@ class DefaultBrowserNotificationWorker(
) : Worker(context, workerParameters) {
override fun doWork(): Result {
val channelId = ensureChannelExists()
val channelId = ensureMarketingChannelExists(applicationContext)
NotificationManagerCompat.from(applicationContext)
.notify(NOTIFICATION_TAG, NOTIFICATION_ID, buildNotification(channelId))
.notify(
NOTIFICATION_TAG,
DEFAULT_BROWSER_NOTIFICATION_ID,
buildNotification(channelId),
)
Events.defaultBrowserNotifShown.record(NoExtras())
// default browser notification should only happen once
@ -81,45 +83,7 @@ class DefaultBrowserNotificationWorker(
}
}
/**
* Make sure a notification channel for default browser notification exists.
*
* Returns the channel id to be used for notifications.
*/
private fun ensureChannelExists(): String {
var channelEnabled = true
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val notificationManager: NotificationManager =
applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val channel = NotificationChannel(
NOTIFICATION_CHANNEL_ID,
applicationContext.getString(R.string.notification_marketing_channel_name),
NotificationManager.IMPORTANCE_DEFAULT,
)
notificationManager.createNotificationChannel(channel)
val existingChannel = notificationManager.getNotificationChannel(NOTIFICATION_CHANNEL_ID)
channelEnabled =
existingChannel != null && existingChannel.importance != NotificationManager.IMPORTANCE_NONE
}
@Suppress("TooGenericExceptionCaught")
val notificationsEnabled = try {
NotificationManagerCompat.from(applicationContext).areNotificationsEnabled()
} catch (e: Exception) {
false
}
marketingNotificationAllowed.set(notificationsEnabled && channelEnabled)
return NOTIFICATION_CHANNEL_ID
}
companion object {
private const val NOTIFICATION_CHANNEL_ID = "org.mozilla.fenix.default.browser.channel"
private const val NOTIFICATION_ID = 1
private const val NOTIFICATION_PENDING_INTENT_TAG = "org.mozilla.fenix.default.browser"
private const val INTENT_DEFAULT_BROWSER_NOTIFICATION = "org.mozilla.fenix.default.browser.intent"
private const val NOTIFICATION_TAG = "org.mozilla.fenix.default.browser.tag"

View File

@ -0,0 +1,60 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.fenix.onboarding
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import android.os.Build
import androidx.core.app.NotificationManagerCompat
import org.mozilla.fenix.GleanMetrics.Events.marketingNotificationAllowed
import org.mozilla.fenix.R
// Channel ID was not updated when it was renamed to marketing. Thus, we'll have to continue
// to use this ID as the marketing channel ID
private const val MARKETING_CHANNEL_ID = "org.mozilla.fenix.default.browser.channel"
// For notification that uses the marketing notification channel, IDs should be unique.
const val DEFAULT_BROWSER_NOTIFICATION_ID = 1
const val RE_ENGAGEMENT_NOTIFICATION_ID = 2
/**
* Make sure the marketing notification channel exists.
*
* Returns the channel id to be used for notifications.
*/
fun ensureMarketingChannelExists(context: Context): String {
var channelEnabled = true
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val notificationManager: NotificationManager =
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
var channel =
notificationManager.getNotificationChannel(MARKETING_CHANNEL_ID)
if (channel == null) {
channel = NotificationChannel(
MARKETING_CHANNEL_ID,
context.getString(R.string.notification_marketing_channel_name),
NotificationManager.IMPORTANCE_DEFAULT,
)
notificationManager.createNotificationChannel(channel)
}
channelEnabled = channel.importance != NotificationManager.IMPORTANCE_NONE
}
@Suppress("TooGenericExceptionCaught")
val notificationsEnabled = try {
NotificationManagerCompat.from(context).areNotificationsEnabled()
} catch (e: Exception) {
false
}
marketingNotificationAllowed.set(notificationsEnabled && channelEnabled)
return MARKETING_CHANNEL_ID
}

View File

@ -0,0 +1,139 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.fenix.onboarding
import android.app.Notification
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import androidx.annotation.VisibleForTesting
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.core.content.ContextCompat
import androidx.work.ExistingWorkPolicy
import androidx.work.OneTimeWorkRequest
import androidx.work.WorkManager
import androidx.work.Worker
import androidx.work.WorkerParameters
import mozilla.components.support.base.ids.SharedIdsHelper
import mozilla.telemetry.glean.private.NoExtras
import org.mozilla.fenix.GleanMetrics.Events
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.utils.IntentUtils
import org.mozilla.fenix.utils.Settings
import java.util.concurrent.TimeUnit
/**
* Worker that builds and schedules the re-engagement notification
*/
class ReEngagementNotificationWorker(
context: Context,
workerParameters: WorkerParameters,
) : Worker(context, workerParameters) {
override fun doWork(): Result {
val settings = applicationContext.settings()
if (isActiveUser(settings) || !settings.shouldShowReEngagementNotification()) {
return Result.success()
}
val channelId = ensureMarketingChannelExists(applicationContext)
NotificationManagerCompat.from(applicationContext)
.notify(
NOTIFICATION_TAG,
RE_ENGAGEMENT_NOTIFICATION_ID,
buildNotification(channelId),
)
// re-engagement notification should only be shown once
settings.reEngagementNotificationShown = true
Events.reEngagementNotifShown.record(NoExtras())
return Result.success()
}
private fun buildNotification(channelId: String): Notification {
val intent = Intent(applicationContext, HomeActivity::class.java)
intent.putExtra(INTENT_RE_ENGAGEMENT_NOTIFICATION, true)
val pendingIntent = PendingIntent.getActivity(
applicationContext,
SharedIdsHelper.getNextIdForTag(applicationContext, NOTIFICATION_PENDING_INTENT_TAG),
intent,
IntentUtils.defaultIntentPendingFlags,
)
with(applicationContext) {
val appName = getString(R.string.app_name)
return NotificationCompat.Builder(this, channelId)
.setSmallIcon(R.drawable.ic_status_logo)
.setContentTitle(
applicationContext.getString(R.string.notification_re_engagement_title),
)
.setContentText(
applicationContext.getString(R.string.notification_re_engagement_text, appName),
)
.setBadgeIconType(NotificationCompat.BADGE_ICON_SMALL)
.setColor(ContextCompat.getColor(this, R.color.primary_text_light_theme))
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setShowWhen(false)
.setContentIntent(pendingIntent)
.setAutoCancel(true)
.build()
}
}
companion object {
const val NOTIFICATION_TARGET_URL = "https://www.mozilla.org/firefox/privacy/"
private const val NOTIFICATION_PENDING_INTENT_TAG = "org.mozilla.fenix.re-engagement"
private const val INTENT_RE_ENGAGEMENT_NOTIFICATION = "org.mozilla.fenix.re-engagement.intent"
private const val NOTIFICATION_TAG = "org.mozilla.fenix.re-engagement.tag"
private const val NOTIFICATION_WORK_NAME = "org.mozilla.fenix.re-engagement.work"
private const val NOTIFICATION_DELAY = Settings.TWO_DAYS_MS
// We are trying to reach the users that are inactive after the initial 24 hours
private const val INACTIVE_USER_THRESHOLD = NOTIFICATION_DELAY - Settings.ONE_DAY_MS
/**
* Check if the intent is from the re-engagement notification
*/
fun isReEngagementNotificationIntent(intent: Intent) =
intent.extras?.containsKey(INTENT_RE_ENGAGEMENT_NOTIFICATION) ?: false
/**
* Schedules the re-engagement notification if needed.
*/
fun setReEngagementNotificationIfNeeded(context: Context) {
val instanceWorkManager = WorkManager.getInstance(context)
if (!context.settings().shouldSetReEngagementNotification()) {
return
}
val notificationWork = OneTimeWorkRequest.Builder(ReEngagementNotificationWorker::class.java)
.setInitialDelay(NOTIFICATION_DELAY, TimeUnit.MILLISECONDS)
.build()
instanceWorkManager.beginUniqueWork(
NOTIFICATION_WORK_NAME,
ExistingWorkPolicy.KEEP,
notificationWork,
).enqueue()
}
@VisibleForTesting
internal fun isActiveUser(settings: Settings): Boolean {
if (System.currentTimeMillis() - settings.lastBrowseActivity > INACTIVE_USER_THRESHOLD) {
return false
}
return true
}
}
}

View File

@ -77,6 +77,7 @@ class Settings(private val appContext: Context) : PreferencesHolder {
const val FOUR_HOURS_MS = 60 * 60 * 4 * 1000L
const val ONE_DAY_MS = 60 * 60 * 24 * 1000L
const val TWO_DAYS_MS = 2 * ONE_DAY_MS
const val THREE_DAYS_MS = 3 * ONE_DAY_MS
const val ONE_WEEK_MS = 60 * 60 * 24 * 7 * 1000L
const val ONE_MONTH_MS = (60 * 60 * 24 * 365 * 1000L) / 12
@ -405,10 +406,13 @@ class Settings(private val appContext: Context) : PreferencesHolder {
* Indicates the last time when the user was interacting with the [BrowserFragment],
* This is useful to determine if the user has to start on the [HomeFragment]
* or it should go directly to the [BrowserFragment].
*
* This value defaults to 0L because we want to know if the user never had any interaction
* with the [BrowserFragment]
*/
var lastBrowseActivity by longPreference(
appContext.getPreferenceKey(R.string.pref_key_last_browse_activity_time),
default = timeNowInMillis(),
default = 0L,
)
/**
@ -579,6 +583,34 @@ class Settings(private val appContext: Context) : PreferencesHolder {
return !defaultBrowserNotificationDisplayed && !isDefaultBrowserBlocking()
}
var reEngagementNotificationShown by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_re_engagement_notification_shown),
default = false,
)
/**
* Check if we should set the re-engagement notification.
*/
fun shouldSetReEngagementNotification(): Boolean {
return numberOfAppLaunches <= 1 && !reEngagementNotificationShown
}
/**
* Check if we should show the re-engagement notification.
*/
fun shouldShowReEngagementNotification(): Boolean {
return !reEngagementNotificationShown && reEngagementNotificationEnabled && !isDefaultBrowserBlocking()
}
/**
* Indicates if the re-engagement notification feature is enabled
*/
var reEngagementNotificationEnabled by lazyFeatureFlagPreference(
key = appContext.getPreferenceKey(R.string.pref_key_re_engagement_notification_enabled),
default = { FxNimbus.features.reEngagementNotification.value(appContext).enabled },
featureFlag = true,
)
val shouldUseAutoBatteryTheme by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_auto_battery_theme),
default = false,

View File

@ -67,6 +67,8 @@
<string name="pref_key_last_browse_activity_time" translatable="false">pref_key_last_browse_activity_time</string>
<string name="pref_key_last_cfr_shown_time" translatable="false">pref_key_last_cfr_shown_time</string>
<string name="pref_key_should_show_default_browser_notification" translatable="false">pref_key_should_show_default_browser_notification</string>
<string name="pref_key_re_engagement_notification_shown" translatable="false">pref_key_re_engagement_notification_shown</string>
<string name="pref_key_re_engagement_notification_enabled" translatable="false">pref_key_re_engagement_notification_enabled</string>
<string name="pref_key_is_first_run" translatable="false">pref_key_is_first_run</string>
<string name="pref_key_home_blocklist">pref_key_home_blocklist</string>

View File

@ -1072,6 +1072,11 @@
<!-- Text shown in the notification that pops up to remind the user to set fenix as default browser.
%1$s is a placeholder that will be replaced by the app name (Fenix). -->
<string name="notification_default_browser_text">Make %1$s your default browser</string>
<!-- Title shown in the notification that pops up to re-engage the user -->
<string name="notification_re_engagement_title">Try private browsing</string>
<!-- Text shown in the notification that pops up to re-engage the user.
%1$s is a placeholder that will be replaced by the app name. -->
<string name="notification_re_engagement_text">Browse with no saved cookies or history in %1$s</string>
<!-- Snackbar -->
<!-- Text shown in snackbar when user deletes a collection -->

View File

@ -10,6 +10,7 @@ import io.mockk.Called
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import mozilla.components.concept.engine.EngineSession
import mozilla.components.service.glean.testing.GleanTestRule
import mozilla.components.support.test.robolectric.testContext
import org.junit.Assert.assertFalse
@ -18,9 +19,12 @@ import org.junit.Assert.assertNull
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mozilla.fenix.BrowserDirection
import org.mozilla.fenix.GleanMetrics.Events
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.browser.browsingmode.BrowsingModeManager
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
import org.mozilla.fenix.onboarding.ReEngagementNotificationWorker
@RunWith(FenixRobolectricTestRunner::class)
class DefaultBrowserIntentProcessorTest {
@ -63,4 +67,42 @@ class DefaultBrowserIntentProcessorTest {
verify { navController wasNot Called }
verify { out wasNot Called }
}
@Test
fun `process re-engagement notification intents`() {
val navController: NavController = mockk(relaxed = true)
val out: Intent = mockk()
val activity: HomeActivity = mockk(relaxed = true)
val browsingModeManager: BrowsingModeManager = mockk(relaxed = true)
val intent = Intent().apply {
putExtra("org.mozilla.fenix.re-engagement.intent", true)
}
every { activity.applicationContext } returns testContext
every { activity.browsingModeManager } returns browsingModeManager
assertNull(Events.reEngagementNotifTapped.testGetValue())
val result = DefaultBrowserIntentProcessor(activity)
.process(intent, navController, out)
assert(result)
assertNotNull(Events.reEngagementNotifTapped.testGetValue())
verify {
activity.openToBrowserAndLoad(
searchTermOrURL = ReEngagementNotificationWorker.NOTIFICATION_TARGET_URL,
newTab = true,
from = BrowserDirection.FromGlobal,
customTabSessionId = null,
engine = null,
forceSearch = false,
flags = EngineSession.LoadUrlFlags.external(),
requestDesktopMode = false,
historyMetadata = null,
)
}
verify { navController wasNot Called }
verify { out wasNot Called }
}
}

View File

@ -0,0 +1,44 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.fenix.onboarding
import io.mockk.spyk
import junit.framework.TestCase.assertFalse
import mozilla.components.support.test.robolectric.testContext
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
import org.mozilla.fenix.utils.Settings
@RunWith(FenixRobolectricTestRunner::class)
class ReEngagementNotificationWorkerTest {
lateinit var settings: Settings
@Before
fun setUp() {
settings = Settings(testContext)
}
@Test
fun `GIVEN last browser activity THEN determine if the user is active correctly`() {
val localSetting = spyk(settings)
localSetting.lastBrowseActivity = System.currentTimeMillis()
assert(ReEngagementNotificationWorker.isActiveUser(localSetting))
localSetting.lastBrowseActivity = System.currentTimeMillis() - Settings.FOUR_HOURS_MS
assert(ReEngagementNotificationWorker.isActiveUser(localSetting))
localSetting.lastBrowseActivity = System.currentTimeMillis() - Settings.ONE_DAY_MS
assertFalse(ReEngagementNotificationWorker.isActiveUser(localSetting))
localSetting.lastBrowseActivity = 0
assertFalse(ReEngagementNotificationWorker.isActiveUser(localSetting))
localSetting.lastBrowseActivity = -1000
assertFalse(ReEngagementNotificationWorker.isActiveUser(localSetting))
}
}

View File

@ -768,6 +768,66 @@ class SettingsTest {
assertFalse(localSetting.shouldShowDefaultBrowserNotification())
}
@Test
fun `GIVEN re-engagement notification shown and number of app launch THEN should set re-engagement notification returns correct value`() {
val localSetting = spyk(settings)
localSetting.reEngagementNotificationShown = false
localSetting.numberOfAppLaunches = 0
assert(localSetting.shouldSetReEngagementNotification())
localSetting.numberOfAppLaunches = 1
assert(localSetting.shouldSetReEngagementNotification())
localSetting.numberOfAppLaunches = 2
assertFalse(localSetting.shouldSetReEngagementNotification())
localSetting.reEngagementNotificationShown = true
localSetting.numberOfAppLaunches = 0
assertFalse(localSetting.shouldSetReEngagementNotification())
}
@Test
fun `GIVEN re-engagement notification shown and is default browser THEN should show re-engagement notification returns correct value`() {
val localSetting = spyk(settings)
every { localSetting.isDefaultBrowserBlocking() } returns false
every { localSetting.reEngagementNotificationEnabled } returns false
localSetting.reEngagementNotificationShown = false
assertFalse(localSetting.shouldShowReEngagementNotification())
every { localSetting.reEngagementNotificationEnabled } returns true
localSetting.reEngagementNotificationShown = false
assert(localSetting.shouldShowReEngagementNotification())
every { localSetting.reEngagementNotificationEnabled } returns false
localSetting.reEngagementNotificationShown = true
assertFalse(localSetting.shouldShowReEngagementNotification())
every { localSetting.reEngagementNotificationEnabled } returns true
localSetting.reEngagementNotificationShown = true
assertFalse(localSetting.shouldShowReEngagementNotification())
every { localSetting.isDefaultBrowserBlocking() } returns true
every { localSetting.reEngagementNotificationEnabled } returns false
localSetting.reEngagementNotificationShown = false
assertFalse(localSetting.shouldShowReEngagementNotification())
every { localSetting.reEngagementNotificationEnabled } returns true
localSetting.reEngagementNotificationShown = false
assertFalse(localSetting.shouldShowReEngagementNotification())
every { localSetting.reEngagementNotificationEnabled } returns false
localSetting.reEngagementNotificationShown = true
assertFalse(localSetting.shouldShowReEngagementNotification())
every { localSetting.reEngagementNotificationEnabled } returns true
localSetting.reEngagementNotificationShown = true
assertFalse(localSetting.shouldShowReEngagementNotification())
}
@Test
fun inactiveTabsAreEnabled() {
// When just created

View File

@ -257,6 +257,14 @@ features:
value:
enabled: true
re-engagement-notification:
description: A feature that shows the re-enagement notification if the user is inactive.
variables:
enabled:
description: If true, the re-engagement notification is shown to the inactive user.
type: Boolean
default: true
types:
objects:
MessageData: