Skip to content

Commit 3bb7c45

Browse files
malmsteinYoussefKeyrouz
authored andcommitted
Add pixel instrumentation for NTP after idle feature
1 parent 2ab5fc9 commit 3bb7c45

13 files changed

Lines changed: 489 additions & 8 deletions

File tree

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
{
2+
// NTP shown pixels
3+
"m_ntp_after_idle_ntp_shown_after_idle": {
4+
"description": "NTP was shown because the idle threshold was met (after inactivity feature)",
5+
"owners": ["malmstein"],
6+
"triggers": ["other"],
7+
"suffixes": ["form_factor"],
8+
"parameters": ["appVersion"]
9+
},
10+
"m_ntp_after_idle_ntp_shown_after_idle_daily": {
11+
"description": "(Daily Pixel) NTP was shown because the idle threshold was met (after inactivity feature)",
12+
"owners": ["malmstein"],
13+
"triggers": ["other"],
14+
"suffixes": ["form_factor"],
15+
"parameters": ["appVersion"]
16+
},
17+
"m_ntp_after_idle_ntp_shown_user_initiated": {
18+
"description": "NTP was shown because the user opened it (not via idle)",
19+
"owners": ["malmstein"],
20+
"triggers": ["other"],
21+
"suffixes": ["form_factor"],
22+
"parameters": ["appVersion"]
23+
},
24+
"m_ntp_after_idle_ntp_shown_user_initiated_daily": {
25+
"description": "(Daily Pixel) NTP was shown because the user opened it (not via idle)",
26+
"owners": ["malmstein"],
27+
"triggers": ["other"],
28+
"suffixes": ["form_factor"],
29+
"parameters": ["appVersion"]
30+
},
31+
32+
// Return to page (hatch) tapped pixels
33+
"m_ntp_after_idle_return_to_page_tapped_after_idle": {
34+
"description": "User tapped the return-to-page hatch card when NTP was shown after idle",
35+
"owners": ["malmstein"],
36+
"triggers": ["other"],
37+
"suffixes": ["form_factor"],
38+
"parameters": ["appVersion"]
39+
},
40+
"m_ntp_after_idle_return_to_page_tapped_after_idle_daily": {
41+
"description": "(Daily Pixel) User tapped the return-to-page hatch card when NTP was shown after idle",
42+
"owners": ["malmstein"],
43+
"triggers": ["other"],
44+
"suffixes": ["form_factor"],
45+
"parameters": ["appVersion"]
46+
},
47+
"m_ntp_after_idle_return_to_page_tapped_user_initiated": {
48+
"description": "User tapped the return-to-page hatch card when NTP was opened by the user",
49+
"owners": ["malmstein"],
50+
"triggers": ["other"],
51+
"suffixes": ["form_factor"],
52+
"parameters": ["appVersion"]
53+
},
54+
"m_ntp_after_idle_return_to_page_tapped_user_initiated_daily": {
55+
"description": "(Daily Pixel) User tapped the return-to-page hatch card when NTP was opened by the user",
56+
"owners": ["malmstein"],
57+
"triggers": ["other"],
58+
"suffixes": ["form_factor"],
59+
"parameters": ["appVersion"]
60+
},
61+
62+
// Bar (search) used from NTP pixels
63+
"m_ntp_after_idle_bar_used_from_ntp_after_idle": {
64+
"description": "User submitted a search from the NTP when NTP was shown after idle",
65+
"owners": ["malmstein"],
66+
"triggers": ["other"],
67+
"suffixes": ["form_factor"],
68+
"parameters": ["appVersion"]
69+
},
70+
"m_ntp_after_idle_bar_used_from_ntp_after_idle_daily": {
71+
"description": "(Daily Pixel) User submitted a search from the NTP when NTP was shown after idle",
72+
"owners": ["malmstein"],
73+
"triggers": ["other"],
74+
"suffixes": ["form_factor"],
75+
"parameters": ["appVersion"]
76+
},
77+
"m_ntp_after_idle_bar_used_from_ntp_user_initiated": {
78+
"description": "User submitted a search from the NTP when NTP was opened by the user",
79+
"owners": ["malmstein"],
80+
"triggers": ["other"],
81+
"suffixes": ["form_factor"],
82+
"parameters": ["appVersion"]
83+
},
84+
"m_ntp_after_idle_bar_used_from_ntp_user_initiated_daily": {
85+
"description": "(Daily Pixel) User submitted a search from the NTP when NTP was opened by the user",
86+
"owners": ["malmstein"],
87+
"triggers": ["other"],
88+
"suffixes": ["form_factor"],
89+
"parameters": ["appVersion"]
90+
},
91+
92+
// Timeout selected pixels (one per supported value)
93+
"m_ntp_after_idle_timeout_selected_always": {
94+
"description": "User selected Always as the idle timeout threshold",
95+
"owners": ["malmstein"],
96+
"triggers": ["other"],
97+
"suffixes": ["form_factor"],
98+
"parameters": ["appVersion"]
99+
},
100+
"m_ntp_after_idle_timeout_selected_daily_always": {
101+
"description": "(Daily Pixel) User has Always selected as the idle timeout threshold",
102+
"owners": ["malmstein"],
103+
"triggers": ["other"],
104+
"suffixes": ["form_factor"],
105+
"parameters": ["appVersion"]
106+
},
107+
"m_ntp_after_idle_timeout_selected_60": {
108+
"description": "User selected 60 seconds (1 minute) as the idle timeout threshold",
109+
"owners": ["malmstein"],
110+
"triggers": ["other"],
111+
"suffixes": ["form_factor"],
112+
"parameters": ["appVersion"]
113+
},
114+
"m_ntp_after_idle_timeout_selected_daily_60": {
115+
"description": "(Daily Pixel) User has 60 seconds (1 minute) selected as the idle timeout threshold",
116+
"owners": ["malmstein"],
117+
"triggers": ["other"],
118+
"suffixes": ["form_factor"],
119+
"parameters": ["appVersion"]
120+
},
121+
"m_ntp_after_idle_timeout_selected_300": {
122+
"description": "User selected 300 seconds (5 minutes) as the idle timeout threshold",
123+
"owners": ["malmstein"],
124+
"triggers": ["other"],
125+
"suffixes": ["form_factor"],
126+
"parameters": ["appVersion"]
127+
},
128+
"m_ntp_after_idle_timeout_selected_daily_300": {
129+
"description": "(Daily Pixel) User has 300 seconds (5 minutes) selected as the idle timeout threshold",
130+
"owners": ["malmstein"],
131+
"triggers": ["other"],
132+
"suffixes": ["form_factor"],
133+
"parameters": ["appVersion"]
134+
},
135+
"m_ntp_after_idle_timeout_selected_600": {
136+
"description": "User selected 600 seconds (10 minutes) as the idle timeout threshold",
137+
"owners": ["malmstein"],
138+
"triggers": ["other"],
139+
"suffixes": ["form_factor"],
140+
"parameters": ["appVersion"]
141+
},
142+
"m_ntp_after_idle_timeout_selected_daily_600": {
143+
"description": "(Daily Pixel) User has 600 seconds (10 minutes) selected as the idle timeout threshold",
144+
"owners": ["malmstein"],
145+
"triggers": ["other"],
146+
"suffixes": ["form_factor"],
147+
"parameters": ["appVersion"]
148+
},
149+
"m_ntp_after_idle_timeout_selected_1800": {
150+
"description": "User selected 1800 seconds (30 minutes) as the idle timeout threshold",
151+
"owners": ["malmstein"],
152+
"triggers": ["other"],
153+
"suffixes": ["form_factor"],
154+
"parameters": ["appVersion"]
155+
},
156+
"m_ntp_after_idle_timeout_selected_daily_1800": {
157+
"description": "(Daily Pixel) User has 1800 seconds (30 minutes) selected as the idle timeout threshold",
158+
"owners": ["malmstein"],
159+
"triggers": ["other"],
160+
"suffixes": ["form_factor"],
161+
"parameters": ["appVersion"]
162+
},
163+
"m_ntp_after_idle_timeout_selected_3600": {
164+
"description": "User selected 3600 seconds (1 hour) as the idle timeout threshold",
165+
"owners": ["malmstein"],
166+
"triggers": ["other"],
167+
"suffixes": ["form_factor"],
168+
"parameters": ["appVersion"]
169+
},
170+
"m_ntp_after_idle_timeout_selected_daily_3600": {
171+
"description": "(Daily Pixel) User has 3600 seconds (1 hour) selected as the idle timeout threshold",
172+
"owners": ["malmstein"],
173+
"triggers": ["other"],
174+
"suffixes": ["form_factor"],
175+
"parameters": ["appVersion"]
176+
},
177+
"m_ntp_after_idle_timeout_selected_43200": {
178+
"description": "User selected 43200 seconds (12 hours) as the idle timeout threshold",
179+
"owners": ["malmstein"],
180+
"triggers": ["other"],
181+
"suffixes": ["form_factor"],
182+
"parameters": ["appVersion"]
183+
},
184+
"m_ntp_after_idle_timeout_selected_daily_43200": {
185+
"description": "(Daily Pixel) User has 43200 seconds (12 hours) selected as the idle timeout threshold",
186+
"owners": ["malmstein"],
187+
"triggers": ["other"],
188+
"suffixes": ["form_factor"],
189+
"parameters": ["appVersion"]
190+
},
191+
"m_ntp_after_idle_timeout_selected_86400": {
192+
"description": "User selected 86400 seconds (24 hours) as the idle timeout threshold",
193+
"owners": ["malmstein"],
194+
"triggers": ["other"],
195+
"suffixes": ["form_factor"],
196+
"parameters": ["appVersion"]
197+
},
198+
"m_ntp_after_idle_timeout_selected_daily_86400": {
199+
"description": "(Daily Pixel) User has 86400 seconds (24 hours) selected as the idle timeout threshold",
200+
"owners": ["malmstein"],
201+
"triggers": ["other"],
202+
"suffixes": ["form_factor"],
203+
"parameters": ["appVersion"]
204+
}
205+
}

app/src/main/java/com/duckduckgo/app/browser/BrowserTabFragment.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,7 @@ import com.duckduckgo.navigation.api.GlobalActivityStarter
320320
import com.duckduckgo.navigation.api.GlobalActivityStarter.DeeplinkActivityParams
321321
import com.duckduckgo.networkprotection.api.NetworkProtectionScreens.NetworkProtectionManagementScreenNoParams
322322
import com.duckduckgo.newtabpage.api.NewTabPageProvider
323+
import com.duckduckgo.newtabpage.api.NtpAfterIdleManager
323324
import com.duckduckgo.privacy.dashboard.api.ui.DashboardOpener
324325
import com.duckduckgo.privacy.dashboard.api.ui.PrivacyDashboardHybridScreenParams.BrokenSiteForm
325326
import com.duckduckgo.privacy.dashboard.api.ui.PrivacyDashboardHybridScreenParams.BrokenSiteForm.BrokenSiteFormReportFlow
@@ -425,6 +426,9 @@ class BrowserTabFragment :
425426
@Inject
426427
lateinit var pixel: Pixel
427428

429+
@Inject
430+
lateinit var ntpAfterIdleManager: NtpAfterIdleManager
431+
428432
@Inject
429433
lateinit var vpnMenuStore: VpnMenuStore
430434

@@ -3521,6 +3525,7 @@ class BrowserTabFragment :
35213525
newTabReturnHatchView.setHatchListener(
35223526
object : NewTabReturnHatchView.HatchListener {
35233527
override fun onHatchPressed() {
3528+
ntpAfterIdleManager.fireReturnToPageTapped()
35243529
browserActivity?.openExistingTab(newTabReturnHatchView.tabId)
35253530
}
35263531

app/src/main/java/com/duckduckgo/app/browser/omnibar/OmnibarLayoutViewModel.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ import com.duckduckgo.duckchat.api.DuckAiFeatureState
6767
import com.duckduckgo.duckchat.api.DuckChat
6868
import com.duckduckgo.duckchat.impl.pixel.DuckChatPixelName
6969
import com.duckduckgo.duckplayer.api.DuckPlayer.DuckPlayerState.ENABLED
70+
import com.duckduckgo.newtabpage.api.NtpAfterIdleManager
7071
import com.duckduckgo.privacy.dashboard.impl.pixels.PrivacyDashboardPixels
7172
import com.duckduckgo.serp.logos.api.SerpEasterEggLogosToggles
7273
import com.duckduckgo.serp.logos.api.SerpLogo
@@ -113,6 +114,7 @@ class OmnibarLayoutViewModel @Inject constructor(
113114
private val addressBarTrackersAnimationManager: AddressBarTrackersAnimationManager,
114115
private val standardizedLeadingIconToggle: StandardizedLeadingIconFeatureToggle,
115116
private val progressBarUpgradeFeature: ProgressBarUpgradeFeature,
117+
private val ntpAfterIdleRepository: NtpAfterIdleManager,
116118
) : ViewModel() {
117119

118120
private val isSplitOmnibarEnabled = settingsDataStore.omnibarType == OmnibarType.SPLIT
@@ -963,6 +965,9 @@ class OmnibarLayoutViewModel @Inject constructor(
963965
AppPixelName.KEYBOARD_GO_SERP_CLICKED,
964966
AppPixelName.KEYBOARD_GO_WEBSITE_CLICKED,
965967
)
968+
if (_viewState.value.url.isEmpty()) {
969+
ntpAfterIdleRepository.fireBarUsedFromNtp()
970+
}
966971
}
967972

968973
fun onAnimationStarted(decoration: Decoration) {

app/src/main/java/com/duckduckgo/app/generalsettings/showonapplaunch/FirstScreenHandler.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import com.duckduckgo.app.settings.db.SettingsDataStore
2222
import com.duckduckgo.browser.api.BrowserLifecycleObserver
2323
import com.duckduckgo.common.utils.DispatcherProvider
2424
import com.duckduckgo.di.scopes.AppScope
25+
import com.duckduckgo.newtabpage.api.NtpAfterIdleManager
2526
import com.squareup.anvil.annotations.ContributesMultibinding
2627
import dagger.SingleInstanceIn
2728
import kotlinx.coroutines.CoroutineScope
@@ -40,6 +41,7 @@ class FirstScreenHandlerImpl @Inject constructor(
4041
private val settingsDataStore: SettingsDataStore,
4142
private val showOnAppLaunchOptionHandler: ShowOnAppLaunchOptionHandler,
4243
private val dispatcherProvider: DispatcherProvider,
44+
private val ntpAfterIdleManager: NtpAfterIdleManager,
4345
@AppCoroutineScope private val appCoroutineScope: CoroutineScope,
4446
) : BrowserLifecycleObserver {
4547

@@ -55,10 +57,12 @@ class FirstScreenHandlerImpl @Inject constructor(
5557
val lastBackgrounded = settingsDataStore.lastSessionBackgroundTimestamp
5658
val elapsed = System.currentTimeMillis() - lastBackgrounded
5759
if (lastBackgrounded == 0L || elapsed >= timeoutMs) {
60+
ntpAfterIdleManager.onNtpShownAfterIdle()
5861
showOnAppLaunchOptionHandler.handleAfterInactivityOption()
5962
return
6063
}
6164
} else if (isFreshLaunch && showOnAppLaunchFeature.self().isEnabled()) {
65+
ntpAfterIdleManager.onNtpShownUserInitiated()
6266
showOnAppLaunchOptionHandler.handleAppLaunchOption()
6367
}
6468
}

app/src/main/java/com/duckduckgo/app/generalsettings/showonapplaunch/ShowOnAppLaunchViewModel.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import com.duckduckgo.app.settings.db.SettingsDataStore
2727
import com.duckduckgo.app.statistics.pixels.Pixel
2828
import com.duckduckgo.common.utils.DispatcherProvider
2929
import com.duckduckgo.di.scopes.ActivityScope
30+
import com.duckduckgo.newtabpage.api.NtpAfterIdleManager
3031
import kotlinx.coroutines.channels.Channel
3132
import kotlinx.coroutines.flow.MutableStateFlow
3233
import kotlinx.coroutines.flow.asStateFlow
@@ -46,6 +47,7 @@ class ShowOnAppLaunchViewModel @Inject constructor(
4647
private val androidBrowserConfigFeature: AndroidBrowserConfigFeature,
4748
private val settingsDataStore: SettingsDataStore,
4849
private val pixel: Pixel,
50+
private val ntpAfterIdleManager: NtpAfterIdleManager,
4951
) : ViewModel() {
5052

5153
data class ViewState(
@@ -119,6 +121,7 @@ class ShowOnAppLaunchViewModel @Inject constructor(
119121
settingsDataStore.userSelectedIdleThresholdSeconds = seconds
120122
userSelectedThreshold.value = seconds
121123
pixel.fire(SETTINGS_AFTER_INACTIVITY_TIMEOUT_CHANGED, mapOf("selectedSeconds" to seconds.toString()))
124+
ntpAfterIdleManager.fireTimeoutSelected(seconds)
122125
}
123126
}
124127
}

app/src/test/java/com/duckduckgo/app/browser/omnibar/OmnibarLayoutViewModelTest.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ import com.duckduckgo.duckplayer.api.DuckPlayer
4343
import com.duckduckgo.feature.toggles.api.FakeFeatureToggleFactory
4444
import com.duckduckgo.feature.toggles.api.FakeToggleStore
4545
import com.duckduckgo.feature.toggles.api.FeatureToggles
46+
import com.duckduckgo.newtabpage.api.NtpAfterIdleManager
4647
import com.duckduckgo.privacy.dashboard.impl.pixels.PrivacyDashboardPixels
4748
import com.duckduckgo.serp.logos.api.SerpEasterEggLogosToggles
4849
import com.duckduckgo.serp.logos.api.SerpLogo
@@ -93,6 +94,7 @@ class OmnibarLayoutViewModelTest {
9394
private val duckAiShowInputScreenFlow = MutableStateFlow(false)
9495
private val nativeInputFieldSettingFlow = MutableStateFlow(false)
9596
private val isFullUrlEnabledFlow = MutableStateFlow(true)
97+
private val ntpAfterIdleManager: NtpAfterIdleManager = mock()
9698
private val settingsDataStore: SettingsDataStore = mock()
9799
private val urlDisplayRepository: UrlDisplayRepository = mock()
98100
private val mockAddressDisplayFormatter: AddressDisplayFormatter by lazy {
@@ -187,6 +189,7 @@ class OmnibarLayoutViewModelTest {
187189
addressBarTrackersAnimationManager = addressBarTrackersAnimationManager,
188190
standardizedLeadingIconToggle = fakeStandardizedLeadingIconToggle,
189191
progressBarUpgradeFeature = fakeProgressBarUpgradeFeature,
192+
ntpAfterIdleRepository = ntpAfterIdleManager,
190193
)
191194
}
192195

app/src/test/java/com/duckduckgo/app/generalsettings/showonapplaunch/FirstScreenHandlerImplTest.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import com.duckduckgo.app.pixels.remoteconfig.AndroidBrowserConfigFeature
2020
import com.duckduckgo.app.settings.db.SettingsDataStore
2121
import com.duckduckgo.common.test.CoroutineTestRule
2222
import com.duckduckgo.feature.toggles.api.Toggle
23+
import com.duckduckgo.newtabpage.api.NtpAfterIdleManager
2324
import kotlinx.coroutines.test.TestScope
2425
import kotlinx.coroutines.test.runTest
2526
import org.junit.Before
@@ -40,6 +41,7 @@ class FirstScreenHandlerImplTest {
4041
private val showOnAppLaunchFeature: ShowOnAppLaunchFeature = mock()
4142
private val settingsDataStore: SettingsDataStore = mock()
4243
private val showOnAppLaunchOptionHandler: ShowOnAppLaunchOptionHandler = mock()
44+
private val ntpAfterIdleManager: NtpAfterIdleManager = mock()
4345
private val idleReturnToggle: Toggle = mock()
4446
private val showOnAppLaunchToggle: Toggle = mock()
4547
private val testScope = TestScope()
@@ -58,6 +60,7 @@ class FirstScreenHandlerImplTest {
5860
settingsDataStore = settingsDataStore,
5961
showOnAppLaunchOptionHandler = showOnAppLaunchOptionHandler,
6062
dispatcherProvider = coroutineTestRule.testDispatcherProvider,
63+
ntpAfterIdleManager = ntpAfterIdleManager,
6164
appCoroutineScope = testScope,
6265
)
6366
}

0 commit comments

Comments
 (0)