Skip to content
This repository has been archived by the owner on Feb 20, 2023. It is now read-only.

For #9488: Add Search widget CFR & telemetry #10958

Merged
merged 2 commits into from
May 28, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 61 additions & 2 deletions app/metrics.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -480,7 +480,6 @@ metrics:
notification_emails:
- fenix-core@mozilla.com
expires: "2020-09-01"

toolbar_position:
type: string
lifetime: application
Expand All @@ -495,7 +494,20 @@ metrics:
notification_emails:
- fenix-core@mozilla.com
expires: "2020-09-01"

search_widget_installed:
type: boolean
lifetime: application
description: |
Whether or not the search widget is installed
send_in_pings:
- metrics
bugs:
- https://github.com/mozilla-mobile/fenix/issues/9488
data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/10958
notification_emails:
- fenix-core@mozilla.com
expires: "2020-09-01"

search.default_engine:
code:
Expand Down Expand Up @@ -1356,6 +1368,53 @@ search_widget:
- fenix-core@mozilla.com
expires: "2020-09-01"

search_widget_cfr:
displayed:
type: event
description: |
The search widget cfr was displayed.
bugs:
- https://github.com/mozilla-mobile/fenix/issues/9488
data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/10958
notification_emails:
- fenix-core@mozilla.com
expires: "2020-09-01"
add_widget_pressed:
type: event
description: |
The user pressed the "add widget" button.
bugs:
- https://github.com/mozilla-mobile/fenix/issues/9488
data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/10958
notification_emails:
- fenix-core@mozilla.com
expires: "2020-09-01"
not_now_pressed:
type: event
description: |
The user pressed the "not now" button.
bugs:
- https://github.com/mozilla-mobile/fenix/issues/9488
data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/10958
notification_emails:
- fenix-core@mozilla.com
expires: "2020-09-01"
canceled:
type: event
description: |
The user dismissed the search widget cfr by
tapping outside of the prompt
bugs:
- https://github.com/mozilla-mobile/fenix/issues/9488
data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/10958
notification_emails:
- fenix-core@mozilla.com
expires: "2020-09-01"

private_browsing_mode:
garbage_icon:
type: event
Expand Down
6 changes: 6 additions & 0 deletions app/src/main/java/org/mozilla/fenix/FeatureFlags.kt
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,10 @@ object FeatureFlags {
* Enables new voice search feature
*/
val voiceSearch = Config.channel.isNightlyOrDebug

/**
* Allows search widget CFR to be displayed.
* This is a placeholder for the experimentation framework determining cohorts.
*/
val searchWidgetCFR = Config.channel.isDebug
}
98 changes: 98 additions & 0 deletions app/src/main/java/org/mozilla/fenix/cfr/SearchWidgetCFR.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/* 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.FeatureFlags
import org.mozilla.fenix.R
import org.mozilla.fenix.components.SearchWidgetCreator
import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.ext.components
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
) {

fun displayIfNecessary() {
if (!context.settings().shouldDisplaySearchWidgetCFR() || !FeatureFlags.searchWidgetCFR) { 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 {
context.components.analytics.metrics.track(Event.SearchWidgetCFRNotNowPressed)
searchWidgetCFRDialog.dismiss()
context.settings().manuallyDismissSearchWidgetCFR()
}

layout.cfr_pos_button.setOnClickListener {
context.components.analytics.metrics.track(Event.SearchWidgetCFRAddWidgetPressed)
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.setOnCancelListener {
context.components.analytics.metrics.track(Event.SearchWidgetCFRCanceled)
}

searchWidgetCFRDialog.setOnDismissListener {
context.settings().incrementSearchWidgetCFRDismissed()
}

searchWidgetCFRDialog.show()
context.components.analytics.metrics.track(Event.SearchWidgetCFRDisplayed)
}
}
Original file line number Diff line number Diff line change
@@ -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 the pinning search widget dialog.
*/
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)
sblatz marked this conversation as resolved.
Show resolved Hide resolved
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)
sblatz marked this conversation as resolved.
Show resolved Hide resolved

return true
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import org.mozilla.fenix.GleanMetrics.SearchDefaultEngine
import org.mozilla.fenix.GleanMetrics.SearchShortcuts
import org.mozilla.fenix.GleanMetrics.SearchSuggestions
import org.mozilla.fenix.GleanMetrics.SearchWidget
import org.mozilla.fenix.GleanMetrics.SearchWidgetCfr
import org.mozilla.fenix.GleanMetrics.SyncAccount
import org.mozilla.fenix.GleanMetrics.SyncAuth
import org.mozilla.fenix.GleanMetrics.Tab
Expand Down Expand Up @@ -529,6 +530,19 @@ private val Event.wrapper: EventWrapper<*>?
is Event.VoiceSearchTapped -> EventWrapper<NoExtraKeys>(
{ VoiceSearch.tapped.record(it) }
)
is Event.SearchWidgetCFRDisplayed -> EventWrapper<NoExtraKeys>(
{ SearchWidgetCfr.displayed.record(it) }
)
is Event.SearchWidgetCFRCanceled -> EventWrapper<NoExtraKeys>(
{ SearchWidgetCfr.canceled.record(it) }
)
is Event.SearchWidgetCFRNotNowPressed -> EventWrapper<NoExtraKeys>(
{ SearchWidgetCfr.notNowPressed.record(it) }
)
is Event.SearchWidgetCFRAddWidgetPressed -> EventWrapper<NoExtraKeys>(
{ SearchWidgetCfr.addWidgetPressed.record(it) }
)

// Don't record other events in Glean:
is Event.AddBookmark -> null
is Event.OpenedBookmark -> null
Expand Down Expand Up @@ -584,6 +598,9 @@ class GleanMetricsService(private val context: Context) : MetricsService {
adjustAdGroup.set(context.settings().adjustAdGroup)
adjustCreative.set(context.settings().adjustCreative)
adjustNetwork.set(context.settings().adjustNetwork)

searchWidgetInstalled.set(context.settings().searchWidgetInstalled)

val topSitesSize = context.settings().topSitesSize
hasTopSites.set(topSitesSize > 0)
if (topSitesSize > 0) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,10 @@ sealed class Event {
object AddonsOpenInSettings : Event()
object AddonsOpenInToolbarMenu : Event()
object VoiceSearchTapped : Event()
object SearchWidgetCFRDisplayed : Event()
object SearchWidgetCFRCanceled : Event()
object SearchWidgetCFRNotNowPressed : Event()
object SearchWidgetCFRAddWidgetPressed : Event()

// Interaction events with extras

Expand Down
10 changes: 10 additions & 0 deletions app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ class DefaultSearchController(
val event = if (url.isUrl()) {
Event.EnteredUrl(false)
} else {
activity.settings().incrementActiveSearchCount()

val searchAccessPoint = when (store.state.searchAccessPoint) {
NONE -> ACTION
else -> store.state.searchAccessPoint
Expand Down Expand Up @@ -142,6 +144,8 @@ class DefaultSearchController(
}

override fun handleSearchTermsTapped(searchTerms: String) {
activity.settings().incrementActiveSearchCount()

activity.openToBrowserAndLoad(
searchTermOrURL = searchTerms,
newTab = store.state.session == null,
Expand Down
58 changes: 58 additions & 0 deletions app/src/main/java/org/mozilla/fenix/utils/Settings.kt
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ class Settings private constructor(
const val trackingProtectionOnboardingMaximumCount = 1
const val FENIX_PREFERENCES = "fenix_preferences"

private const val showSearchWidgetCFRMaxCount = 3
private const val BLOCKED_INT = 0
private const val ASK_TO_ALLOW_INT = 1
private const val ALLOWED_INT = 2
Expand Down Expand Up @@ -149,6 +150,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 < showSearchWidgetCFRMaxCount &&
!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 = ""
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading