Skip to content

Commit

Permalink
Add pixels
Browse files Browse the repository at this point in the history
  • Loading branch information
CrisBarreiro committed Aug 19, 2024
1 parent c612fce commit dc3c086
Show file tree
Hide file tree
Showing 10 changed files with 107 additions and 41 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -627,7 +627,7 @@ class BrowserTabViewModelTest {
history = mockNavigationHistory,
newTabPixels = { mockNewTabPixels },
duckPlayer = mockDuckPlayer,
duckPlayerJSHelper = DuckPlayerJSHelper(mockDuckPlayer, mockAppBuildConfig),
duckPlayerJSHelper = DuckPlayerJSHelper(mockDuckPlayer, mockAppBuildConfig, mockPixel),
)

testee.loadData("abc", null, false)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,17 @@

package com.duckduckgo.app.browser.duckplayer

import androidx.core.net.toUri
import com.duckduckgo.app.browser.commands.Command
import com.duckduckgo.app.browser.commands.Command.OpenDuckPlayerInfo
import com.duckduckgo.app.browser.commands.Command.OpenDuckPlayerSettings
import com.duckduckgo.app.browser.commands.Command.SendResponseToDuckPlayer
import com.duckduckgo.app.browser.commands.Command.SendResponseToJs
import com.duckduckgo.app.browser.commands.NavigationCommand.Navigate
import com.duckduckgo.app.pixels.AppPixelName.DUCK_PLAYER_SETTING_ALWAYS_DUCK_PLAYER
import com.duckduckgo.app.pixels.AppPixelName.DUCK_PLAYER_SETTING_ALWAYS_OVERLAY_YOUTUBE
import com.duckduckgo.app.pixels.AppPixelName.DUCK_PLAYER_SETTING_NEVER_OVERLAY_YOUTUBE
import com.duckduckgo.app.statistics.pixels.Pixel
import com.duckduckgo.appbuildconfig.api.AppBuildConfig
import com.duckduckgo.duckplayer.api.DuckPlayer
import com.duckduckgo.js.messaging.api.JsCallbackData
Expand All @@ -37,6 +42,7 @@ private const val PRIVATE_PLAYER_MODE = "privatePlayerMode"
class DuckPlayerJSHelper @Inject constructor(
private val duckPlayer: DuckPlayer,
private val appBuildConfig: AppBuildConfig,
private val pixel: Pixel,
) {
private suspend fun getUserPreferences(featureName: String, method: String, id: String): JsCallbackData {
val userValues = duckPlayer.getUserPreferences()
Expand Down Expand Up @@ -126,10 +132,20 @@ class DuckPlayerJSHelper @Inject constructor(
setUserPreferences(data)
return when (featureName) {
DUCK_PLAYER_FEATURE_NAME -> {
SendResponseToJs(getUserPreferences(featureName, method, id))
SendResponseToJs(getUserPreferences(featureName, method, id)).also {
if (data.getJSONObject(PRIVATE_PLAYER_MODE).keys().next() == "enabled") {
pixel.fire(DUCK_PLAYER_SETTING_ALWAYS_OVERLAY_YOUTUBE)
} else if (data.getJSONObject(PRIVATE_PLAYER_MODE).keys().next() == "disabled") {
pixel.fire(DUCK_PLAYER_SETTING_NEVER_OVERLAY_YOUTUBE)
}
}
}
DUCK_PLAYER_PAGE_FEATURE_NAME -> {
SendResponseToDuckPlayer(getUserPreferences(featureName, method, id))
SendResponseToDuckPlayer(getUserPreferences(featureName, method, id)).also {
if (data.getJSONObject(PRIVATE_PLAYER_MODE).keys().next() == "enabled") {
pixel.fire(DUCK_PLAYER_SETTING_ALWAYS_DUCK_PLAYER)
}
}
} else -> {
null
}
Expand All @@ -142,7 +158,7 @@ class DuckPlayerJSHelper @Inject constructor(
}
"openDuckPlayer" -> {
return data?.getString("href")?.let {
Navigate(it, mapOf())
Navigate(it.toUri().buildUpon().appendQueryParameter("origin", "overlay").build().toString(), mapOf())
}
}
"initialSetup" -> {
Expand Down
4 changes: 4 additions & 0 deletions app/src/main/java/com/duckduckgo/app/pixels/AppPixelName.kt
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,10 @@ enum class AppPixelName(override val pixelName: String) : Pixel.PixelName {
TAB_MANAGER_LIST_VIEW_BUTTON_CLICKED("m_tab_manager_list_view_button_clicked"),
TAB_MANAGER_VIEW_MODE_TOGGLED_DAILY("m_tab_manager_view_mode_toggled_daily"),

DUCK_PLAYER_SETTING_ALWAYS_OVERLAY_YOUTUBE("m_duck-player_setting_always_overlay_youtube"),
DUCK_PLAYER_SETTING_NEVER_OVERLAY_YOUTUBE("m_duck-player_setting_never_overlay_youtube"),
DUCK_PLAYER_SETTING_ALWAYS_DUCK_PLAYER("m_duck-player_setting_always_duck-player"),

ADD_BOOKMARK_CONFIRM_EDITED("m_add_bookmark_confirm_edit"),

REFERRAL_INSTALL_UTM_CAMPAIGN("m_android_install"),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.duckduckgo.duckplayer.impl

import app.cash.turbine.test
import com.duckduckgo.app.statistics.pixels.Pixel
import com.duckduckgo.common.test.CoroutineTestRule
import com.duckduckgo.duckplayer.api.DuckPlayer
import com.duckduckgo.duckplayer.api.DuckPlayer.UserPreferences
Expand All @@ -27,13 +28,14 @@ class DuckPlayerSettingsViewModelTest {

private val duckPlayer: DuckPlayer = mock()
private val duckPlayerFeatureRepository: DuckPlayerFeatureRepository = mock()
private val pixel: Pixel = mock()
private lateinit var viewModel: DuckPlayerSettingsViewModel

@Before
fun setUp() {
runTest {
whenever(duckPlayer.getUserPreferences()).thenReturn(UserPreferences(overlayInteracted = true, privatePlayerMode = AlwaysAsk))
viewModel = DuckPlayerSettingsViewModel(duckPlayer, duckPlayerFeatureRepository)
viewModel = DuckPlayerSettingsViewModel(duckPlayer, duckPlayerFeatureRepository, pixel)
}
}

Expand All @@ -56,7 +58,7 @@ class DuckPlayerSettingsViewModelTest {
@Test
fun whenViewModelIsCreatedAndPrivatePlayerModeIsDisabledThenEmitDisabled() = runTest {
whenever(duckPlayer.observeUserPreferences()).thenReturn(flowOf(UserPreferences(overlayInteracted = true, privatePlayerMode = Disabled)))
viewModel = DuckPlayerSettingsViewModel(duckPlayer, duckPlayerFeatureRepository)
viewModel = DuckPlayerSettingsViewModel(duckPlayer, duckPlayerFeatureRepository, pixel)

viewModel.viewState.test {
assertEquals(Disabled, awaitItem().privatePlayerMode)
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright (c) 2024 DuckDuckGo
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.duckduckgo.duckplayer.impl

import com.duckduckgo.app.statistics.pixels.Pixel

enum class DuckPlayerPixelNames(override val pixelName: String) : Pixel.PixelName {
DUCK_PLAYER_OVERLAY_YOUTUBE_IMPRESSIONS("m_duck-player_overlay_youtube_impressions"),
DUCK_PLAYER_VIEW_FROM_YOUTUBE_MAIN_OVERLAY("m_duck-player_view-from_youtube_main-overlay"),
DUCK_PLAYER_OVERLAY_YOUTUBE_WATCH_HERE("m_duck-player_overlay_youtube_watch_here"),
DUCK_PLAYER_WATCH_ON_YOUTUBE("m_duck-player_watch_on_youtube"),
DUCK_PLAYER_DAILY_UNIQUE_VIEW("m_duck-player_daily-unique-view"),
DUCK_PLAYER_VIEW_FROM_YOUTUBE_AUTOMATIC("m_duck-player_view-from_youtube_automatic"),
DUCK_PLAYER_VIEW_FROM_OTHER("m_duck-player_view-from_other"),
DUCK_PLAYER_SETTINGS_ALWAYS_SETTINGS("m_duck-player_setting_always_settings"),
DUCK_PLAYER_SETTINGS_BACK_TO_DEAULT("m_duck-player_setting_back-to-default"),
DUCK_PLAYER_SETTINGS_NEVER_SETTINGS("m_duck-player_setting_never_settings"),
DUCK_PLAYER_SETTINGS_PRESSED("ms_duck_player_setting_pressed"),
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import com.duckduckgo.app.statistics.pixels.Pixel
import com.duckduckgo.common.ui.view.listitem.OneLineListItem
import com.duckduckgo.di.scopes.ActivityScope
import com.duckduckgo.duckplayer.api.DuckPlayerSettingsNoParams
import com.duckduckgo.duckplayer.impl.DuckPlayerPixelName.SETTINGS_DUCK_PLAYER_PRESSED
import com.duckduckgo.duckplayer.impl.DuckPlayerPixelNames.DUCK_PLAYER_SETTINGS_PRESSED
import com.duckduckgo.navigation.api.GlobalActivityStarter
import com.duckduckgo.settings.api.DuckPlayerSettingsPlugin
import com.squareup.anvil.annotations.ContributesMultibinding
Expand All @@ -39,7 +39,7 @@ class DuckPlayerSettingsTitle @Inject constructor(
return OneLineListItem(context).apply {
setPrimaryText(context.getString(R.string.duck_player_setting_title))
setOnClickListener {
pixel.fire(SETTINGS_DUCK_PLAYER_PRESSED)
pixel.fire(DUCK_PLAYER_SETTINGS_PRESSED)
globalActivityStarter.start(this.context, DuckPlayerSettingsNoParams, null)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,16 @@ package com.duckduckgo.duckplayer.impl
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.duckduckgo.anvil.annotations.ContributesViewModel
import com.duckduckgo.app.statistics.pixels.Pixel
import com.duckduckgo.di.scopes.ActivityScope
import com.duckduckgo.duckplayer.api.DuckPlayer
import com.duckduckgo.duckplayer.api.DuckPlayer.DuckPlayerState.DISABLED_WIH_HELP_LINK
import com.duckduckgo.duckplayer.api.PrivatePlayerMode
import com.duckduckgo.duckplayer.api.PrivatePlayerMode.AlwaysAsk
import com.duckduckgo.duckplayer.api.PrivatePlayerMode.Disabled
import com.duckduckgo.duckplayer.impl.DuckPlayerPixelNames.DUCK_PLAYER_SETTINGS_ALWAYS_SETTINGS
import com.duckduckgo.duckplayer.impl.DuckPlayerPixelNames.DUCK_PLAYER_SETTINGS_BACK_TO_DEAULT
import com.duckduckgo.duckplayer.impl.DuckPlayerPixelNames.DUCK_PLAYER_SETTINGS_NEVER_SETTINGS
import com.duckduckgo.duckplayer.impl.DuckPlayerSettingsViewModel.Command.OpenLearnMore
import com.duckduckgo.duckplayer.impl.DuckPlayerSettingsViewModel.Command.OpenPlayerModeSelector
import com.duckduckgo.duckplayer.impl.DuckPlayerSettingsViewModel.ViewState.DisabledWithHelpLink
Expand All @@ -43,6 +48,7 @@ import kotlinx.coroutines.runBlocking
class DuckPlayerSettingsViewModel @Inject constructor(
private val duckPlayer: DuckPlayer,
private val duckPlayerFeatureRepository: DuckPlayerFeatureRepository,
private val pixel: Pixel,
) : ViewModel() {

private val commandChannel = Channel<Command>(capacity = 1, onBufferOverflow = DROP_OLDEST)
Expand Down Expand Up @@ -81,7 +87,14 @@ class DuckPlayerSettingsViewModel @Inject constructor(

fun onPlayerModeSelected(selectedPlayerMode: PrivatePlayerMode) {
viewModelScope.launch {
duckPlayer.setUserPreferences(overlayInteracted = false, privatePlayerMode = selectedPlayerMode.value)
duckPlayer.setUserPreferences(overlayInteracted = false, privatePlayerMode = selectedPlayerMode.value).also {
val pixelName = when (selectedPlayerMode) {
is PrivatePlayerMode.Enabled -> { DUCK_PLAYER_SETTINGS_ALWAYS_SETTINGS }
is AlwaysAsk -> { DUCK_PLAYER_SETTINGS_BACK_TO_DEAULT }
is Disabled -> { DUCK_PLAYER_SETTINGS_NEVER_SETTINGS }
}
pixel.fire(pixelName)
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import android.webkit.WebView
import androidx.core.net.toUri
import androidx.fragment.app.FragmentManager
import com.duckduckgo.app.statistics.pixels.Pixel
import com.duckduckgo.app.statistics.pixels.Pixel.PixelType.DAILY
import com.duckduckgo.common.utils.DispatcherProvider
import com.duckduckgo.common.utils.UrlScheme.Companion.duck
import com.duckduckgo.common.utils.UrlScheme.Companion.https
Expand All @@ -38,6 +39,13 @@ import com.duckduckgo.duckplayer.api.DuckPlayer.UserPreferences
import com.duckduckgo.duckplayer.api.PrivatePlayerMode.AlwaysAsk
import com.duckduckgo.duckplayer.api.PrivatePlayerMode.Disabled
import com.duckduckgo.duckplayer.api.PrivatePlayerMode.Enabled
import com.duckduckgo.duckplayer.impl.DuckPlayerPixelNames.DUCK_PLAYER_DAILY_UNIQUE_VIEW
import com.duckduckgo.duckplayer.impl.DuckPlayerPixelNames.DUCK_PLAYER_OVERLAY_YOUTUBE_IMPRESSIONS
import com.duckduckgo.duckplayer.impl.DuckPlayerPixelNames.DUCK_PLAYER_OVERLAY_YOUTUBE_WATCH_HERE
import com.duckduckgo.duckplayer.impl.DuckPlayerPixelNames.DUCK_PLAYER_VIEW_FROM_OTHER
import com.duckduckgo.duckplayer.impl.DuckPlayerPixelNames.DUCK_PLAYER_VIEW_FROM_YOUTUBE_AUTOMATIC
import com.duckduckgo.duckplayer.impl.DuckPlayerPixelNames.DUCK_PLAYER_VIEW_FROM_YOUTUBE_MAIN_OVERLAY
import com.duckduckgo.duckplayer.impl.DuckPlayerPixelNames.DUCK_PLAYER_WATCH_ON_YOUTUBE
import com.duckduckgo.duckplayer.impl.ui.DuckPlayerPrimeBottomSheet
import com.duckduckgo.duckplayer.impl.ui.DuckPlayerPrimeDialogFragment
import com.squareup.anvil.annotations.ContributesBinding
Expand Down Expand Up @@ -128,8 +136,12 @@ class RealDuckPlayer @Inject constructor(
pixelName: String,
pixelData: Map<String, String>,
) {
val androidPixelName = "m_${pixelName.replace('.', '_')}"
pixel.fire(androidPixelName, pixelData)
when (pixelName) {
"overlay" -> pixel.fire(DUCK_PLAYER_OVERLAY_YOUTUBE_IMPRESSIONS, parameters = pixelData)
"play.use" -> pixel.fire(DUCK_PLAYER_VIEW_FROM_YOUTUBE_MAIN_OVERLAY, parameters = pixelData)
"play.do_not_use" -> pixel.fire(DUCK_PLAYER_OVERLAY_YOUTUBE_WATCH_HERE, parameters = pixelData)
else -> {}
}
}

private suspend fun createYoutubeNoCookieFromDuckPlayer(uri: Uri): String? {
Expand Down Expand Up @@ -157,6 +169,7 @@ class RealDuckPlayer @Inject constructor(
if (getUserPreferences().privatePlayerMode == AlwaysAsk) {
shouldHideOverlay = true
}
pixel.fire(DUCK_PLAYER_WATCH_ON_YOUTUBE)
}
private fun isDuckPlayerUri(uri: Uri): Boolean {
if (uri.normalizeScheme()?.scheme != duck) return false
Expand Down Expand Up @@ -207,7 +220,7 @@ class RealDuckPlayer @Inject constructor(

private suspend fun createDuckPlayerUriFromYoutube(uri: Uri): String {
val videoIdQueryParam = duckPlayerFeatureRepository.getVideoIDQueryParam()
return "$DUCK_PLAYER_URL_BASE${uri.getQueryParameter(videoIdQueryParam)}"
return "$DUCK_PLAYER_URL_BASE${uri.getQueryParameter(videoIdQueryParam)}?origin=auto"
}

override suspend fun intercept(
Expand Down Expand Up @@ -242,7 +255,9 @@ class RealDuckPlayer @Inject constructor(
}
} else {
val inputStream: InputStream = webView.context.assets.open(DUCK_PLAYER_ASSETS_INDEX_PATH)
return WebResourceResponse("text/html", "UTF-8", inputStream)
return WebResourceResponse("text/html", "UTF-8", inputStream).also {
pixel.fire(DUCK_PLAYER_DAILY_UNIQUE_VIEW, type = DAILY)
}
}
}

Expand Down Expand Up @@ -271,6 +286,7 @@ class RealDuckPlayer @Inject constructor(
withContext(dispatchers.main()) {
webView.loadUrl(createDuckPlayerUriFromYoutube(url))
}
pixel.fire(DUCK_PLAYER_VIEW_FROM_YOUTUBE_AUTOMATIC)
return WebResourceResponse(null, null, null)
}
return null
Expand All @@ -292,6 +308,9 @@ class RealDuckPlayer @Inject constructor(
withContext(dispatchers.main()) {
webView.loadUrl(youtubeUrl)
}
if (url.getQueryParameter("origin") != "overlay" && url.getQueryParameter("origin") != "auto") {
pixel.fire(DUCK_PLAYER_VIEW_FROM_OTHER)
}
}
}
return WebResourceResponse(null, null, null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import android.webkit.WebView
import androidx.core.net.toUri
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.duckduckgo.app.statistics.pixels.Pixel
import com.duckduckgo.app.statistics.pixels.Pixel.PixelType.COUNT
import com.duckduckgo.common.utils.UrlScheme.Companion.duck
import com.duckduckgo.common.utils.UrlScheme.Companion.https
import com.duckduckgo.duckplayer.api.DuckPlayer.DuckPlayerState.DISABLED
Expand All @@ -34,6 +35,7 @@ import com.duckduckgo.duckplayer.api.DuckPlayer.UserPreferences
import com.duckduckgo.duckplayer.api.PrivatePlayerMode.AlwaysAsk
import com.duckduckgo.duckplayer.api.PrivatePlayerMode.Disabled
import com.duckduckgo.duckplayer.api.PrivatePlayerMode.Enabled
import com.duckduckgo.duckplayer.impl.DuckPlayerPixelNames.DUCK_PLAYER_OVERLAY_YOUTUBE_IMPRESSIONS
import com.duckduckgo.feature.toggles.api.Toggle
import com.duckduckgo.feature.toggles.api.Toggle.State
import kotlinx.coroutines.flow.first
Expand Down Expand Up @@ -224,21 +226,21 @@ class RealDuckPlayerTest {

@Test
fun sendDuckPlayerPixel_firesPixelWithCorrectNameAndData() = runTest {
val pixelName = "pixelName"
val pixelName = "overlay"
val pixelData = mapOf("key" to "value")

testee.sendDuckPlayerPixel(pixelName, pixelData)

verify(mockPixel).fire("m_pixelName", pixelData)
verify(mockPixel).fire(DUCK_PLAYER_OVERLAY_YOUTUBE_IMPRESSIONS, pixelData, emptyMap(), COUNT)
}

@Test
fun sendDuckPlayerPixel_firesPixelWithEmptyDataWhenNoDataProvided() = runTest {
val pixelName = "pixelName"
val pixelName = "overlay"

testee.sendDuckPlayerPixel(pixelName, emptyMap())

verify(mockPixel).fire("m_pixelName", emptyMap())
verify(mockPixel).fire(DUCK_PLAYER_OVERLAY_YOUTUBE_IMPRESSIONS, emptyMap(), emptyMap(), COUNT)
}

// endregion
Expand Down Expand Up @@ -551,7 +553,7 @@ class RealDuckPlayerTest {

val result = testee.intercept(request, url, webView)

verify(webView).loadUrl("duck://player/12345")
verify(webView).loadUrl("duck://player/12345?origin=auto")
assertNotNull(result)
}

Expand Down

0 comments on commit dc3c086

Please sign in to comment.