Close #27949: Add engagement notification for inactive users
This commit is contained in:
parent
9b920a472c
commit
11efaff96c
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -261,6 +261,7 @@ class BrowserFragment : BaseBrowserFragment(), UserInteractionHandler {
|
|||
}
|
||||
|
||||
subscribeToTabCollections()
|
||||
updateLastBrowseActivity()
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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 -->
|
||||
|
|
|
@ -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 }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
Loading…
Reference in New Issue