Skip to content

Commit afefeea

Browse files
authored
Android: Add pixels to understand navigation and search entry points (#4733)
Task/Issue URL: https://app.asana.com/0/488551667048375/1206975542817143/f ### Description Added pixels to understand navigation and search entry points. ### Steps to test this PR See https://app.asana.com/0/488551667048375/1206975817853363/f ### NO UI changes
1 parent 52073e3 commit afefeea

File tree

23 files changed

+783
-24
lines changed

23 files changed

+783
-24
lines changed

app/src/androidTest/java/com/duckduckgo/app/browser/BrowserTabViewModelTest.kt

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import android.net.http.SslError
2525
import android.os.Build
2626
import android.print.PrintAttributes
2727
import android.view.MenuItem
28+
import android.view.MotionEvent
2829
import android.view.View
2930
import android.webkit.GeolocationPermissions
3031
import android.webkit.HttpAuthHandler
@@ -189,6 +190,7 @@ import com.duckduckgo.remote.messaging.api.RemoteMessagingRepository
189190
import com.duckduckgo.savedsites.api.SavedSitesRepository
190191
import com.duckduckgo.savedsites.api.models.SavedSite.Bookmark
191192
import com.duckduckgo.savedsites.api.models.SavedSite.Favorite
193+
import com.duckduckgo.savedsites.impl.SavedSitesPixelName
192194
import com.duckduckgo.site.permissions.api.SitePermissionsManager
193195
import com.duckduckgo.site.permissions.api.SitePermissionsManager.SitePermissionQueryResponse
194196
import com.duckduckgo.site.permissions.api.SitePermissionsManager.SitePermissions
@@ -2292,6 +2294,7 @@ class BrowserTabViewModelTest {
22922294
fun whenUserRequestedToOpenNewTabThenGenerateWebViewPreviewImage() {
22932295
testee.userRequestedOpeningNewTab()
22942296
assertCommandIssued<Command.GenerateWebViewPreviewImage>()
2297+
verify(mockPixel, never()).fire(AppPixelName.TAB_MANAGER_NEW_TAB_LONG_PRESSED)
22952298
}
22962299

22972300
@Test
@@ -2300,6 +2303,14 @@ class BrowserTabViewModelTest {
23002303
verify(mockCommandObserver, atLeastOnce()).onChanged(commandCaptor.capture())
23012304
val command = commandCaptor.lastValue
23022305
assertTrue(command is Command.LaunchNewTab)
2306+
verify(mockPixel, never()).fire(AppPixelName.TAB_MANAGER_NEW_TAB_LONG_PRESSED)
2307+
}
2308+
2309+
@Test
2310+
fun whenUserRequestedToOpenNewTabByLongPressThenPixelFired() {
2311+
testee.userRequestedOpeningNewTab(longPress = true)
2312+
2313+
verify(mockPixel).fire(AppPixelName.TAB_MANAGER_NEW_TAB_LONG_PRESSED)
23032314
}
23042315

23052316
@Test
@@ -5268,6 +5279,146 @@ class BrowserTabViewModelTest {
52685279
assertFalse(testee.isPrinting())
52695280
}
52705281

5282+
@Test
5283+
fun whenOnFavoriteAddedThePixelFired() {
5284+
testee.onFavoriteAdded()
5285+
5286+
verify(mockPixel).fire(SavedSitesPixelName.EDIT_BOOKMARK_ADD_FAVORITE_TOGGLED)
5287+
}
5288+
5289+
@Test
5290+
fun whenOnFavoriteRemovedThePixelFired() {
5291+
testee.onFavoriteRemoved()
5292+
5293+
verify(mockPixel).fire(SavedSitesPixelName.EDIT_BOOKMARK_REMOVE_FAVORITE_TOGGLED)
5294+
}
5295+
5296+
@Test
5297+
fun whenOnSavedSiteDeleteCancelledThenPixelFired() {
5298+
testee.onSavedSiteDeleteCancelled()
5299+
5300+
verify(mockPixel).fire(SavedSitesPixelName.EDIT_BOOKMARK_DELETE_BOOKMARK_CANCELLED)
5301+
}
5302+
5303+
@Test
5304+
fun whenOnSavedSiteDeleteRequestedThenPixelFired() {
5305+
testee.onSavedSiteDeleteRequested()
5306+
5307+
verify(mockPixel).fire(SavedSitesPixelName.EDIT_BOOKMARK_DELETE_BOOKMARK_CLICKED)
5308+
}
5309+
5310+
@Test
5311+
fun whenUserLaunchingTabSwitcherThenLaunchTabSwitcherCommandSentAndPixelFired() {
5312+
testee.userLaunchingTabSwitcher()
5313+
5314+
assertCommandIssued<Command.LaunchTabSwitcher>()
5315+
verify(mockPixel).fire(AppPixelName.TAB_MANAGER_CLICKED)
5316+
}
5317+
5318+
@Test
5319+
fun whenOnUserTouchedOmnibarTextInputWithEmptyTextAndActionUpThenPixelFired() {
5320+
testee.onUserTouchedOmnibarTextInput(MotionEvent.ACTION_UP)
5321+
5322+
verify(mockPixel).fire(AppPixelName.ADDRESS_BAR_NEW_TAB_PAGE_CLICKED)
5323+
}
5324+
5325+
@Test
5326+
fun whenOnUserTouchedOmnibarTextInputWithUrlAndActionUpThenPixelFired() {
5327+
loadUrl("https://example.com")
5328+
testee.onUserTouchedOmnibarTextInput(MotionEvent.ACTION_UP)
5329+
5330+
verify(mockPixel).fire(AppPixelName.ADDRESS_BAR_WEBSITE_CLICKED)
5331+
}
5332+
5333+
@Test
5334+
fun whenOnUserTouchedOmnibarTextInputWithQueryAndActionUpThenPixelFired() {
5335+
loadUrl("https://duckduckgo.com/?q=example")
5336+
testee.onUserTouchedOmnibarTextInput(MotionEvent.ACTION_UP)
5337+
5338+
verify(mockPixel).fire(AppPixelName.ADDRESS_BAR_SERP_CLICKED)
5339+
}
5340+
5341+
@Test
5342+
fun whenOnUserTouchedOmnibarTextInputWithAnyTextAndOtherActionThenPixelNotFired() {
5343+
loadUrl("https://duckduckgo.com/?q=example")
5344+
testee.onUserTouchedOmnibarTextInput(MotionEvent.ACTION_DOWN)
5345+
5346+
verify(mockPixel, never()).fire(AppPixelName.ADDRESS_BAR_NEW_TAB_PAGE_CLICKED)
5347+
verify(mockPixel, never()).fire(AppPixelName.ADDRESS_BAR_WEBSITE_CLICKED)
5348+
verify(mockPixel, never()).fire(AppPixelName.ADDRESS_BAR_SERP_CLICKED)
5349+
}
5350+
5351+
@Test
5352+
fun whenOnClearOmnibarTextInputWithEmptyTextThenPixelFired() {
5353+
testee.onClearOmnibarTextInput()
5354+
5355+
verify(mockPixel).fire(AppPixelName.ADDRESS_BAR_NEW_TAB_PAGE_ENTRY_CLEARED)
5356+
}
5357+
5358+
@Test
5359+
fun whenOnClearOmnibarTextInputWithUrlThenPixelFired() {
5360+
loadUrl("https://example.com")
5361+
testee.onClearOmnibarTextInput()
5362+
5363+
verify(mockPixel).fire(AppPixelName.ADDRESS_BAR_WEBSITE_ENTRY_CLEARED)
5364+
}
5365+
5366+
@Test
5367+
fun whenOnClearOmnibarTextInputWithQueryUrlThenPixelFired() {
5368+
loadUrl("https://duckduckgo.com/?q=example")
5369+
testee.onClearOmnibarTextInput()
5370+
5371+
verify(mockPixel).fire(AppPixelName.ADDRESS_BAR_SERP_ENTRY_CLEARED)
5372+
}
5373+
5374+
@Test
5375+
fun whenSendPixelsOnBackKeyPressedWithEmptyTextThenPixelFired() {
5376+
testee.sendPixelsOnBackKeyPressed()
5377+
5378+
verify(mockPixel).fire(AppPixelName.ADDRESS_BAR_NEW_TAB_PAGE_CANCELLED)
5379+
}
5380+
5381+
@Test
5382+
fun whenSendPixelsOnBackKeyPressedWithUrlThenPixelFired() {
5383+
loadUrl("https://example.com")
5384+
testee.sendPixelsOnBackKeyPressed()
5385+
5386+
verify(mockPixel).fire(AppPixelName.ADDRESS_BAR_WEBSITE_CANCELLED)
5387+
}
5388+
5389+
@Test
5390+
fun whenSendPixelsOnBackKeyPressedWithQueryUrlThenPixelFired() {
5391+
loadUrl("https://duckduckgo.com/?q=example")
5392+
5393+
testee.sendPixelsOnBackKeyPressed()
5394+
5395+
verify(mockPixel).fire(AppPixelName.ADDRESS_BAR_SERP_CANCELLED)
5396+
}
5397+
5398+
@Test
5399+
fun whenSendPixelsOnEnterKeyPressedWithEmptyTextThenPixelFired() {
5400+
testee.sendPixelsOnEnterKeyPressed()
5401+
5402+
verify(mockPixel).fire(AppPixelName.KEYBOARD_GO_NEW_TAB_CLICKED)
5403+
}
5404+
5405+
@Test
5406+
fun whenSendPixelsOnEnterKeyPressedWithUrlThenPixelFired() {
5407+
loadUrl("https://example.com")
5408+
testee.sendPixelsOnEnterKeyPressed()
5409+
5410+
verify(mockPixel).fire(AppPixelName.KEYBOARD_GO_WEBSITE_CLICKED)
5411+
}
5412+
5413+
@Test
5414+
fun whenSendPixelsOnEnterKeyPressedWithQueryUrlThenPixelFired() {
5415+
loadUrl("https://duckduckgo.com/?q=example")
5416+
5417+
testee.sendPixelsOnEnterKeyPressed()
5418+
5419+
verify(mockPixel).fire(AppPixelName.KEYBOARD_GO_SERP_CLICKED)
5420+
}
5421+
52715422
private fun aCredential(): LoginCredentials {
52725423
return LoginCredentials(domain = null, username = null, password = null)
52735424
}

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

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2181,6 +2181,7 @@ class BrowserTabFragment :
21812181

21822182
omnibar.omnibarTextInput.onBackKeyListener = object : KeyboardAwareEditText.OnBackKeyListener {
21832183
override fun onBackKey(): Boolean {
2184+
viewModel.sendPixelsOnBackKeyPressed()
21842185
omnibar.omnibarTextInput.hideKeyboard()
21852186
binding.focusDummy.requestFocus()
21862187
// Allow the event to be handled by the next receiver.
@@ -2191,14 +2192,23 @@ class BrowserTabFragment :
21912192
omnibar.omnibarTextInput.setOnEditorActionListener(
21922193
TextView.OnEditorActionListener { _, actionId, keyEvent ->
21932194
if (actionId == EditorInfo.IME_ACTION_GO || keyEvent?.keyCode == KeyEvent.KEYCODE_ENTER) {
2195+
viewModel.sendPixelsOnEnterKeyPressed()
21942196
userEnteredQuery(omnibar.omnibarTextInput.text.toString())
21952197
return@OnEditorActionListener true
21962198
}
21972199
false
21982200
},
21992201
)
22002202

2201-
omnibar.clearTextButton.setOnClickListener { omnibar.omnibarTextInput.setText("") }
2203+
omnibar.omnibarTextInput.setOnTouchListener { _, event ->
2204+
viewModel.onUserTouchedOmnibarTextInput(event.action)
2205+
false
2206+
}
2207+
2208+
omnibar.clearTextButton.setOnClickListener {
2209+
viewModel.onClearOmnibarTextInput()
2210+
omnibar.omnibarTextInput.setText("")
2211+
}
22022212
}
22032213

22042214
private fun userEnteredQuery(query: String) {
@@ -2696,6 +2706,7 @@ class BrowserTabFragment :
26962706

26972707
override fun onSecondaryItemClicked() {
26982708
if (savedSiteChangedViewState.savedSite is Bookmark) {
2709+
pixel.fire(AppPixelName.ADD_BOOKMARK_CONFIRM_EDITED)
26992710
editSavedSite(
27002711
savedSiteChangedViewState.copy(
27012712
savedSite = savedSiteChangedViewState.savedSite.copy(
@@ -3493,7 +3504,7 @@ class BrowserTabFragment :
34933504

34943505
private fun configureLongClickOpensNewTabListener() {
34953506
tabsButton?.setOnLongClickListener {
3496-
launch { viewModel.userRequestedOpeningNewTab() }
3507+
launch { viewModel.userRequestedOpeningNewTab(longPress = true) }
34973508
return@setOnLongClickListener true
34983509
}
34993510
}

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

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,13 @@ import android.provider.MediaStore
2626
import android.util.Patterns
2727
import android.view.ContextMenu
2828
import android.view.MenuItem
29+
import android.view.MotionEvent.ACTION_UP
2930
import android.view.View
3031
import android.webkit.GeolocationPermissions
3132
import android.webkit.MimeTypeMap
3233
import android.webkit.PermissionRequest
3334
import android.webkit.SslErrorHandler
35+
import android.webkit.URLUtil
3436
import android.webkit.ValueCallback
3537
import android.webkit.WebChromeClient.FileChooserParams
3638
import android.webkit.WebView
@@ -181,6 +183,7 @@ import com.duckduckgo.savedsites.api.models.BookmarkFolder
181183
import com.duckduckgo.savedsites.api.models.SavedSite
182184
import com.duckduckgo.savedsites.api.models.SavedSite.Bookmark
183185
import com.duckduckgo.savedsites.api.models.SavedSite.Favorite
186+
import com.duckduckgo.savedsites.impl.SavedSitesPixelName
184187
import com.duckduckgo.savedsites.impl.dialogs.EditSavedSiteDialogFragment.DeleteBookmarkListener
185188
import com.duckduckgo.savedsites.impl.dialogs.EditSavedSiteDialogFragment.EditSavedSiteListener
186189
import com.duckduckgo.site.permissions.api.SitePermissionsManager
@@ -2075,10 +2078,26 @@ class BrowserTabViewModel @Inject constructor(
20752078
}
20762079
}
20772080

2081+
override fun onFavoriteAdded() {
2082+
pixel.fire(SavedSitesPixelName.EDIT_BOOKMARK_ADD_FAVORITE_TOGGLED)
2083+
}
2084+
2085+
override fun onFavoriteRemoved() {
2086+
pixel.fire(SavedSitesPixelName.EDIT_BOOKMARK_REMOVE_FAVORITE_TOGGLED)
2087+
}
2088+
20782089
override fun onSavedSiteDeleted(savedSite: SavedSite) {
20792090
onDeleteSavedSiteRequested(savedSite)
20802091
}
20812092

2093+
override fun onSavedSiteDeleteCancelled() {
2094+
pixel.fire(SavedSitesPixelName.EDIT_BOOKMARK_DELETE_BOOKMARK_CANCELLED)
2095+
}
2096+
2097+
override fun onSavedSiteDeleteRequested() {
2098+
pixel.fire(SavedSitesPixelName.EDIT_BOOKMARK_DELETE_BOOKMARK_CLICKED)
2099+
}
2100+
20822101
fun onEditSavedSiteRequested(savedSite: SavedSite) {
20832102
viewModelScope.launch(dispatchers.io()) {
20842103
val bookmarkFolder =
@@ -2398,9 +2417,12 @@ class BrowserTabViewModel @Inject constructor(
23982417
}
23992418
}
24002419

2401-
fun userRequestedOpeningNewTab() {
2420+
fun userRequestedOpeningNewTab(longPress: Boolean = false) {
24022421
command.value = GenerateWebViewPreviewImage
24032422
command.value = LaunchNewTab
2423+
if (longPress) {
2424+
pixel.fire(AppPixelName.TAB_MANAGER_NEW_TAB_LONG_PRESSED)
2425+
}
24042426
}
24052427

24062428
fun onCtaShown() {
@@ -2590,6 +2612,7 @@ class BrowserTabViewModel @Inject constructor(
25902612

25912613
fun userLaunchingTabSwitcher() {
25922614
command.value = LaunchTabSwitcher
2615+
pixel.fire(AppPixelName.TAB_MANAGER_CLICKED)
25932616
}
25942617

25952618
private fun isFireproofWebsite(domain: String? = site?.domain): Boolean {
@@ -3260,6 +3283,55 @@ class BrowserTabViewModel @Inject constructor(
32603283
return currentBrowserViewState().isPrinting
32613284
}
32623285

3286+
fun onUserTouchedOmnibarTextInput(touchAction: Int) {
3287+
if (touchAction == ACTION_UP) {
3288+
firePixelBasedOnCurrentUrl(
3289+
AppPixelName.ADDRESS_BAR_NEW_TAB_PAGE_CLICKED,
3290+
AppPixelName.ADDRESS_BAR_SERP_CLICKED,
3291+
AppPixelName.ADDRESS_BAR_WEBSITE_CLICKED,
3292+
)
3293+
}
3294+
}
3295+
3296+
fun onClearOmnibarTextInput() {
3297+
firePixelBasedOnCurrentUrl(
3298+
AppPixelName.ADDRESS_BAR_NEW_TAB_PAGE_ENTRY_CLEARED,
3299+
AppPixelName.ADDRESS_BAR_SERP_ENTRY_CLEARED,
3300+
AppPixelName.ADDRESS_BAR_WEBSITE_ENTRY_CLEARED,
3301+
)
3302+
}
3303+
3304+
fun sendPixelsOnBackKeyPressed() {
3305+
firePixelBasedOnCurrentUrl(
3306+
AppPixelName.ADDRESS_BAR_NEW_TAB_PAGE_CANCELLED,
3307+
AppPixelName.ADDRESS_BAR_SERP_CANCELLED,
3308+
AppPixelName.ADDRESS_BAR_WEBSITE_CANCELLED,
3309+
)
3310+
}
3311+
3312+
fun sendPixelsOnEnterKeyPressed() {
3313+
firePixelBasedOnCurrentUrl(
3314+
AppPixelName.KEYBOARD_GO_NEW_TAB_CLICKED,
3315+
AppPixelName.KEYBOARD_GO_SERP_CLICKED,
3316+
AppPixelName.KEYBOARD_GO_WEBSITE_CLICKED,
3317+
)
3318+
}
3319+
3320+
private fun firePixelBasedOnCurrentUrl(emptyUrlPixel: AppPixelName, duckDuckGoQueryUrlPixel: AppPixelName, websiteUrlPixel: AppPixelName) {
3321+
val text = url.orEmpty()
3322+
if (text.isEmpty()) {
3323+
pixel.fire(emptyUrlPixel)
3324+
} else if (duckDuckGoUrlDetector.isDuckDuckGoQueryUrl(text)) {
3325+
pixel.fire(duckDuckGoQueryUrlPixel)
3326+
} else if (isUrl(text)) {
3327+
pixel.fire(websiteUrlPixel)
3328+
}
3329+
}
3330+
3331+
private fun isUrl(text: String): Boolean {
3332+
return URLUtil.isNetworkUrl(text) || URLUtil.isAssetUrl(text) || URLUtil.isFileUrl(text) || URLUtil.isContentUrl(text)
3333+
}
3334+
32633335
companion object {
32643336
private const val FIXED_PROGRESS = 50
32653337

0 commit comments

Comments
 (0)