diff --git a/app/src/main/java/org/mozilla/fenix/cfr/SearchWidgetCFR.kt b/app/src/main/java/org/mozilla/fenix/cfr/SearchWidgetCFR.kt
new file mode 100644
index 0000000000..884e7c11fa
--- /dev/null
+++ b/app/src/main/java/org/mozilla/fenix/cfr/SearchWidgetCFR.kt
@@ -0,0 +1,90 @@
+/* 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.cfr
+
+import android.app.Dialog
+import android.content.Context
+import android.graphics.Color
+import android.graphics.drawable.ColorDrawable
+import android.view.Gravity
+import android.view.LayoutInflater
+import android.view.View
+import androidx.core.view.isGone
+import androidx.core.view.isVisible
+import androidx.core.view.marginTop
+import kotlinx.android.synthetic.main.search_widget_cfr.view.*
+import kotlinx.android.synthetic.main.tracking_protection_onboarding_popup.view.drop_down_triangle
+import kotlinx.android.synthetic.main.tracking_protection_onboarding_popup.view.pop_up_triangle
+import org.mozilla.fenix.R
+import org.mozilla.fenix.components.SearchWidgetCreator
+import org.mozilla.fenix.ext.settings
+import org.mozilla.fenix.utils.Settings
+
+/**
+ * Displays a CFR above the HomeFragment toolbar that recommends usage / installation of the search widget.
+ */
+class SearchWidgetCFR(
+ private val context: Context,
+ private val getToolbar: () -> View
+) {
+
+ // TODO: Based on pref && is in the bucket...?
+ fun displayIfNecessary() {
+ if (!context.settings().shouldDisplaySearchWidgetCFR()) { return }
+ showSearchWidgetCFR()
+ }
+
+ @Suppress("MagicNumber", "InflateParams")
+ private fun showSearchWidgetCFR() {
+ context.settings().incrementSearchWidgetCFRDisplayed()
+
+ val searchWidgetCFRDialog = Dialog(context)
+ val layout = LayoutInflater.from(context)
+ .inflate(R.layout.search_widget_cfr, null)
+ val isBottomToolbar = Settings.getInstance(context).shouldUseBottomToolbar
+
+ layout.drop_down_triangle.isGone = isBottomToolbar
+ layout.pop_up_triangle.isVisible = isBottomToolbar
+
+ val toolbar = getToolbar()
+
+ val gravity = if (isBottomToolbar) {
+ Gravity.CENTER_HORIZONTAL or Gravity.BOTTOM
+ } else {
+ Gravity.CENTER_HORIZONTAL or Gravity.TOP
+ }
+
+ layout.cfr_neg_button.setOnClickListener {
+ searchWidgetCFRDialog.dismiss()
+ context.settings().manuallyDismissSearchWidgetCFR()
+ }
+
+ layout.cfr_pos_button.setOnClickListener {
+ //context.components.analytics.metrics.track(Event.)
+ SearchWidgetCreator.createSearchWidget(context)
+ searchWidgetCFRDialog.dismiss()
+ //context.settings().manuallyDismissSearchWidgetCFR()
+ }
+
+ searchWidgetCFRDialog.apply {
+ setContentView(layout)
+ }
+
+ searchWidgetCFRDialog.window?.let {
+ it.setGravity(gravity)
+ val attr = it.attributes
+ attr.y =
+ (toolbar.y + toolbar.height - toolbar.marginTop - toolbar.paddingTop).toInt()
+ it.attributes = attr
+ it.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
+ }
+
+ searchWidgetCFRDialog.setOnDismissListener {
+ context.settings().incrementSearchWidgetCFRDismissed()
+ }
+
+ searchWidgetCFRDialog.show()
+ }
+}
diff --git a/app/src/main/java/org/mozilla/fenix/components/SearchWidgetCreator.kt b/app/src/main/java/org/mozilla/fenix/components/SearchWidgetCreator.kt
new file mode 100644
index 0000000000..f16cc4eef2
--- /dev/null
+++ b/app/src/main/java/org/mozilla/fenix/components/SearchWidgetCreator.kt
@@ -0,0 +1,33 @@
+/* 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.components
+
+import android.annotation.TargetApi
+import android.appwidget.AppWidgetManager
+import android.content.ComponentName
+import android.content.Context
+import android.os.Build
+import org.mozilla.gecko.search.SearchWidgetProvider
+
+/**
+ * Handles the creation of search widget.
+ */
+object SearchWidgetCreator {
+
+ /**
+ * Attempts to display a prompt requesting the user pin the search widget
+ * Returns true if the prompt is displayed successfully, and false otherwise.
+ */
+ @TargetApi(Build.VERSION_CODES.O)
+ fun createSearchWidget(context: Context): Boolean {
+ val appWidgetManager: AppWidgetManager = context.getSystemService(AppWidgetManager::class.java)
+ if (!appWidgetManager.isRequestPinAppWidgetSupported) { return false }
+
+ val myProvider = ComponentName(context, SearchWidgetProvider::class.java)
+ appWidgetManager.requestPinAppWidget(myProvider, null, null)
+
+ return true
+ }
+}
diff --git a/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt b/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt
index 8d3f9461c0..ccaf1c4b4d 100644
--- a/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt
+++ b/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt
@@ -26,6 +26,7 @@ import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
import androidx.constraintlayout.widget.ConstraintSet.TOP
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.content.ContextCompat
+import androidx.core.view.doOnLayout
import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams
import androidx.fragment.app.Fragment
@@ -78,6 +79,7 @@ import org.mozilla.fenix.addons.runIfFragmentIsAttached
import org.mozilla.fenix.browser.BrowserAnimator.Companion.getToolbarNavOptions
import org.mozilla.fenix.browser.browsingmode.BrowsingMode
import org.mozilla.fenix.collections.SaveCollectionStep
+import org.mozilla.fenix.cfr.SearchWidgetCFR
import org.mozilla.fenix.components.FenixSnackbar
import org.mozilla.fenix.components.PrivateShortcutCreateManager
import org.mozilla.fenix.components.StoreProvider
@@ -380,6 +382,14 @@ class HomeFragment : Fragment() {
)
}
}
+
+ // We call this onLayout so that the bottom bar width is correctly set for us to center
+ // the CFR in.
+ view.toolbar_wrapper.doOnLayout {
+ if (!browsingModeManager.mode.isPrivate) {
+ SearchWidgetCFR(view.context) { view.toolbar_wrapper }.displayIfNecessary()
+ }
+ }
}
override fun onDestroyView() {
diff --git a/app/src/main/java/org/mozilla/fenix/search/SearchController.kt b/app/src/main/java/org/mozilla/fenix/search/SearchController.kt
index c69515c53a..cbe131f1c8 100644
--- a/app/src/main/java/org/mozilla/fenix/search/SearchController.kt
+++ b/app/src/main/java/org/mozilla/fenix/search/SearchController.kt
@@ -81,6 +81,8 @@ class DefaultSearchController(
val event = if (url.isUrl()) {
Event.EnteredUrl(false)
} else {
+ context.settings().incrementActiveSearchCount()
+
val searchAccessPoint = when (store.state.searchAccessPoint) {
NONE -> ACTION
else -> store.state.searchAccessPoint
@@ -142,6 +144,8 @@ class DefaultSearchController(
}
override fun handleSearchTermsTapped(searchTerms: String) {
+ context.settings().incrementActiveSearchCount()
+
activity.openToBrowserAndLoad(
searchTermOrURL = searchTerms,
newTab = store.state.session == null,
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 f63b874c07..342e15e5eb 100644
--- a/app/src/main/java/org/mozilla/fenix/utils/Settings.kt
+++ b/app/src/main/java/org/mozilla/fenix/utils/Settings.kt
@@ -149,6 +149,63 @@ class Settings private constructor(
preferences.getBoolean(appContext.getString(R.string.pref_key_migrating_from_firefox_nightly_tip), true) &&
preferences.getBoolean(appContext.getString(R.string.pref_key_migrating_from_fenix_tip), true)
+ private val activeSearchCount by intPreference(
+ appContext.getPreferenceKey(R.string.pref_key_search_count),
+ default = 0
+ )
+
+ fun incrementActiveSearchCount() {
+ preferences.edit().putInt(
+ appContext.getPreferenceKey(R.string.pref_key_search_count),
+ activeSearchCount + 1
+ ).apply()
+ }
+
+ private val isActiveSearcher: Boolean
+ get() = activeSearchCount > 2
+
+ fun shouldDisplaySearchWidgetCFR(): Boolean =
+ isActiveSearcher &&
+ searchWidgetCFRDismissCount < 3 &&
+ !searchWidgetInstalled &&
+ !searchWidgetCFRManuallyDismissed
+
+ private val searchWidgetCFRDisplayCount by intPreference(
+ appContext.getPreferenceKey(R.string.pref_key_search_widget_cfr_display_count),
+ default = 0
+ )
+
+ fun incrementSearchWidgetCFRDisplayed() {
+ preferences.edit().putInt(
+ appContext.getPreferenceKey(R.string.pref_key_search_widget_cfr_display_count),
+ searchWidgetCFRDisplayCount + 1
+ ).apply()
+ }
+
+ private val searchWidgetCFRManuallyDismissed by booleanPreference(
+ appContext.getPreferenceKey(R.string.pref_key_search_widget_cfr_manually_dismissed),
+ default = false
+ )
+
+ fun manuallyDismissSearchWidgetCFR() {
+ preferences.edit().putBoolean(
+ appContext.getPreferenceKey(R.string.pref_key_search_widget_cfr_manually_dismissed),
+ true
+ ).apply()
+ }
+
+ private val searchWidgetCFRDismissCount by intPreference(
+ appContext.getPreferenceKey(R.string.pref_key_search_widget_cfr_dismiss_count),
+ default = 0
+ )
+
+ fun incrementSearchWidgetCFRDismissed() {
+ preferences.edit().putInt(
+ appContext.getPreferenceKey(R.string.pref_key_search_widget_cfr_dismiss_count),
+ searchWidgetCFRDismissCount + 1
+ ).apply()
+ }
+
var defaultSearchEngineName by stringPreference(
appContext.getPreferenceKey(R.string.pref_key_search_engine),
default = ""
diff --git a/app/src/main/res/drawable-xhdpi/search_widget_illustration.png b/app/src/main/res/drawable-xhdpi/search_widget_illustration.png
new file mode 100644
index 0000000000..dc6539813c
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/search_widget_illustration.png differ
diff --git a/app/src/main/res/layout/search_widget_cfr.xml b/app/src/main/res/layout/search_widget_cfr.xml
new file mode 100644
index 0000000000..e5f6be05b6
--- /dev/null
+++ b/app/src/main/res/layout/search_widget_cfr.xml
@@ -0,0 +1,100 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/values/preference_keys.xml b/app/src/main/res/values/preference_keys.xml
index 36aeb3f6bc..07b77edcfb 100644
--- a/app/src/main/res/values/preference_keys.xml
+++ b/app/src/main/res/values/preference_keys.xml
@@ -171,4 +171,10 @@
pref_key_new_tab_tray
pref_key_debug_settings
+
+ pref_key_search_count
+ pref_key_search_widget_cfr_display_count
+ pref_key_search_widget_cfr_dismiss_count
+ pref_key_search_widget_cfr_manually_dismissed
+ pref_key_show_search_widget_cfr
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index ee4f0a8bd7..a946856736 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -36,7 +36,7 @@
Delete session
-
+
Add a shortcut to open private tabs from your Home screen.
@@ -44,6 +44,14 @@
No thanks
+
+
+ Get to Firefox faster. Add a widget to your Home screen.
+
+ Add widget
+
+ Not now
+
New tab