diff --git a/app/src/main/java/org/mozilla/fenix/HomeActivity.kt b/app/src/main/java/org/mozilla/fenix/HomeActivity.kt
index d43ca5c4af..14de0b6676 100644
--- a/app/src/main/java/org/mozilla/fenix/HomeActivity.kt
+++ b/app/src/main/java/org/mozilla/fenix/HomeActivity.kt
@@ -86,6 +86,7 @@ import org.mozilla.fenix.ext.setNavigationIcon
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.home.HomeFragmentDirections
import org.mozilla.fenix.home.intent.CrashReporterIntentProcessor
+import org.mozilla.fenix.home.intent.DefaultBrowserIntentProcessor
import org.mozilla.fenix.home.intent.OpenBrowserIntentProcessor
import org.mozilla.fenix.home.intent.OpenSpecificTabIntentProcessor
import org.mozilla.fenix.home.intent.SpeechProcessingIntentProcessor
@@ -94,6 +95,7 @@ import org.mozilla.fenix.library.bookmarks.BookmarkFragmentDirections
import org.mozilla.fenix.library.bookmarks.DesktopFolders
import org.mozilla.fenix.library.history.HistoryFragmentDirections
import org.mozilla.fenix.library.recentlyclosed.RecentlyClosedFragmentDirections
+import org.mozilla.fenix.onboarding.DefaultBrowserNotificationWorker
import org.mozilla.fenix.perf.Performance
import org.mozilla.fenix.perf.PerformanceInflater
import org.mozilla.fenix.perf.ProfilerMarkers
@@ -158,7 +160,8 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
SpeechProcessingIntentProcessor(this, components.core.store, components.analytics.metrics),
StartSearchIntentProcessor(components.analytics.metrics),
OpenBrowserIntentProcessor(this, ::getIntentSessionId),
- OpenSpecificTabIntentProcessor(this)
+ OpenSpecificTabIntentProcessor(this),
+ DefaultBrowserIntentProcessor(this)
)
}
@@ -337,6 +340,8 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
}
settings().wasDefaultBrowserOnLastResume = settings().isDefaultBrowser()
+
+ DefaultBrowserNotificationWorker.setDefaultBrowserNotificationIfNeeded(applicationContext)
}
}
diff --git a/app/src/main/java/org/mozilla/fenix/home/intent/DefaultBrowserIntentProcessor.kt b/app/src/main/java/org/mozilla/fenix/home/intent/DefaultBrowserIntentProcessor.kt
new file mode 100644
index 0000000000..8479bd169b
--- /dev/null
+++ b/app/src/main/java/org/mozilla/fenix/home/intent/DefaultBrowserIntentProcessor.kt
@@ -0,0 +1,34 @@
+/* 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.home.intent
+
+import android.content.Intent
+import androidx.navigation.NavController
+import org.mozilla.fenix.HomeActivity
+import org.mozilla.fenix.ext.openSetDefaultBrowserOption
+import org.mozilla.fenix.ext.settings
+import org.mozilla.fenix.onboarding.DefaultBrowserNotificationWorker.Companion.isDefaultBrowserNotificationIntent
+
+/**
+ * When the default browser notification is tapped we need to launch [openSetDefaultBrowserOption]
+ *
+ * This should only happens once in a user's lifetime since once the user taps on the default browser
+ * notification, [settings.shouldShowDefaultBrowserNotification] will return false
+ */
+class DefaultBrowserIntentProcessor(
+ private val activity: HomeActivity
+) : HomeIntentProcessor {
+
+ override fun process(intent: Intent, navController: NavController, out: Intent): Boolean {
+ return if (isDefaultBrowserNotificationIntent(intent)) {
+ activity.openSetDefaultBrowserOption()
+ activity.settings().defaultBrowserNotificationDisplayed = true
+
+ true
+ } else {
+ false
+ }
+ }
+}
diff --git a/app/src/main/java/org/mozilla/fenix/onboarding/DefaultBrowserNotificationWorker.kt b/app/src/main/java/org/mozilla/fenix/onboarding/DefaultBrowserNotificationWorker.kt
new file mode 100644
index 0000000000..eabed8de38
--- /dev/null
+++ b/app/src/main/java/org/mozilla/fenix/onboarding/DefaultBrowserNotificationWorker.kt
@@ -0,0 +1,126 @@
+/* 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.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
+import androidx.work.ExistingWorkPolicy
+import androidx.work.OneTimeWorkRequest
+import androidx.work.WorkManager
+import androidx.work.Worker
+import androidx.work.WorkerParameters
+import java.util.concurrent.TimeUnit
+import mozilla.components.support.base.ids.SharedIdsHelper
+import org.mozilla.fenix.HomeActivity
+import org.mozilla.fenix.R
+import org.mozilla.fenix.ext.settings
+import org.mozilla.fenix.utils.Settings
+
+class DefaultBrowserNotificationWorker(
+ context: Context,
+ workerParameters: WorkerParameters
+) : Worker(context, workerParameters) {
+
+ override fun doWork(): Result {
+ ensureChannelExists()
+ NotificationManagerCompat.from(applicationContext)
+ .notify(NOTIFICATION_TAG, NOTIFICATION_ID, buildNotification())
+ return Result.success()
+ }
+
+ /**
+ * Build the default browser notification.
+ */
+ private fun buildNotification(): Notification {
+ val channelId = ensureChannelExists()
+ val intent = Intent(applicationContext, HomeActivity::class.java)
+ intent.putExtra(INTENT_DEFAULT_BROWSER_NOTIFICATION, true)
+
+ val pendingIntent = PendingIntent.getActivity(
+ applicationContext,
+ SharedIdsHelper.getNextIdForTag(applicationContext, NOTIFICATION_PENDING_INTENT_TAG),
+ intent,
+ 0
+ )
+
+ 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_default_browser_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()
+ }
+ }
+
+ /**
+ * Make sure a notification channel for default browser notification exists.
+ *
+ * Returns the channel id to be used for notifications.
+ */
+ private fun ensureChannelExists(): String {
+ 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)
+ }
+
+ 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"
+ private const val NOTIFICATION_WORK_NAME = "org.mozilla.fenix.default.browser.work"
+ private const val NOTIFICATION_DELAY = Settings.ONE_DAY_MS
+
+ fun isDefaultBrowserNotificationIntent(intent: Intent) =
+ intent.extras?.containsKey(INTENT_DEFAULT_BROWSER_NOTIFICATION) ?: false
+
+ fun setDefaultBrowserNotificationIfNeeded(context: Context) {
+ val instanceWorkManager = WorkManager.getInstance(context)
+
+ if (!context.settings().shouldShowDefaultBrowserNotification()) {
+ // cancel notification work if already default browser
+ instanceWorkManager.cancelUniqueWork(NOTIFICATION_WORK_NAME)
+ return
+ }
+
+ val notificationWork = OneTimeWorkRequest.Builder(DefaultBrowserNotificationWorker::class.java)
+ .setInitialDelay(NOTIFICATION_DELAY, TimeUnit.MILLISECONDS)
+ .build()
+
+ instanceWorkManager.beginUniqueWork(
+ NOTIFICATION_WORK_NAME,
+ ExistingWorkPolicy.REPLACE,
+ notificationWork
+ ).enqueue()
+ }
+ }
+}
diff --git a/app/src/main/java/org/mozilla/fenix/utils/Settings.kt b/app/src/main/java/org/mozilla/fenix/utils/Settings.kt
index 7c27923227..0e8a09c355 100644
--- a/app/src/main/java/org/mozilla/fenix/utils/Settings.kt
+++ b/app/src/main/java/org/mozilla/fenix/utils/Settings.kt
@@ -484,6 +484,15 @@ class Settings(private val appContext: Context) : PreferencesHolder {
return browsers.isDefaultBrowser
}
+ var defaultBrowserNotificationDisplayed by booleanPreference(
+ appContext.getPreferenceKey(R.string.pref_key_should_show_default_browser_notification),
+ default = false
+ )
+
+ fun shouldShowDefaultBrowserNotification(): Boolean {
+ return !defaultBrowserNotificationDisplayed && !isDefaultBrowser()
+ }
+
val shouldUseAutoBatteryTheme by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_auto_battery_theme),
default = false
diff --git a/app/src/main/res/values/preference_keys.xml b/app/src/main/res/values/preference_keys.xml
index 2861fe18cf..e45acd9302 100644
--- a/app/src/main/res/values/preference_keys.xml
+++ b/app/src/main/res/values/preference_keys.xml
@@ -67,6 +67,7 @@
pref_key_last_review_prompt_shown_time
pref_key_last_browse_activity_time
pref_key_last_cfr_shown_time
+ pref_key_should_show_default_browser_notification
pref_key_telemetry
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index eea4bc6332..07ff420cef 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1022,6 +1022,12 @@
Delete and Open
Powered By
+
+ Marketing
+
+ Make %1$s your default browser
+
+
Collection deleted
diff --git a/app/src/test/java/org/mozilla/fenix/home/intent/DefaultBrowserIntentProcessorTest.kt b/app/src/test/java/org/mozilla/fenix/home/intent/DefaultBrowserIntentProcessorTest.kt
new file mode 100644
index 0000000000..ae63c0d634
--- /dev/null
+++ b/app/src/test/java/org/mozilla/fenix/home/intent/DefaultBrowserIntentProcessorTest.kt
@@ -0,0 +1,52 @@
+/* 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.home.intent
+
+import android.content.Intent
+import androidx.navigation.NavController
+import io.mockk.Called
+import io.mockk.every
+import io.mockk.mockk
+import io.mockk.verify
+import mozilla.components.support.test.robolectric.testContext
+import org.junit.Assert.assertFalse
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mozilla.fenix.HomeActivity
+import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
+
+@RunWith(FenixRobolectricTestRunner::class)
+class DefaultBrowserIntentProcessorTest {
+
+ @Test
+ fun `do not process blank intents`() {
+ val navController: NavController = mockk()
+ val out: Intent = mockk()
+ val result = DefaultBrowserIntentProcessor(mockk()).process(Intent(), navController, out)
+
+ assertFalse(result)
+ verify { navController wasNot Called }
+ verify { out wasNot Called }
+ }
+
+ @Test
+ fun `process default browser notification intents`() {
+ val navController: NavController = mockk(relaxed = true)
+ val out: Intent = mockk()
+ val activity: HomeActivity = mockk()
+
+ val intent = Intent().apply {
+ putExtra("org.mozilla.fenix.default.browser.intent", true)
+ }
+ every { activity.startActivity(any()) } returns Unit
+ every { activity.applicationContext } returns testContext
+
+ val result = DefaultBrowserIntentProcessor(activity).process(intent, navController, out)
+
+ assert(result)
+ verify { navController wasNot Called }
+ verify { out wasNot Called }
+ }
+}
diff --git a/app/src/test/java/org/mozilla/fenix/utils/SettingsTest.kt b/app/src/test/java/org/mozilla/fenix/utils/SettingsTest.kt
index 83aaaba85f..968e787077 100644
--- a/app/src/test/java/org/mozilla/fenix/utils/SettingsTest.kt
+++ b/app/src/test/java/org/mozilla/fenix/utils/SettingsTest.kt
@@ -745,4 +745,24 @@ class SettingsTest {
assertFalse(localSetting.shouldStartOnHome())
}
+
+ @Test
+ fun `GIVEN shownDefaultBrowserNotification and isDefaultBrowser WHEN calling shouldShowDefaultBrowserNotification THEN return correct value`() {
+ val localSetting = spyk(settings)
+ every { localSetting.isDefaultBrowser() } returns false
+
+ localSetting.defaultBrowserNotificationDisplayed = false
+ assert(localSetting.shouldShowDefaultBrowserNotification())
+
+ localSetting.defaultBrowserNotificationDisplayed = true
+ assertFalse(localSetting.shouldShowDefaultBrowserNotification())
+
+ every { localSetting.isDefaultBrowser() } returns true
+
+ localSetting.defaultBrowserNotificationDisplayed = false
+ assertFalse(localSetting.shouldShowDefaultBrowserNotification())
+
+ localSetting.defaultBrowserNotificationDisplayed = true
+ assertFalse(localSetting.shouldShowDefaultBrowserNotification())
+ }
}