diff --git a/app/src/androidTest/java/com/duckduckgo/app/browser/BrowserTabViewModelTest.kt b/app/src/androidTest/java/com/duckduckgo/app/browser/BrowserTabViewModelTest.kt index cc3fa158c5ca..d196cb0add12 100644 --- a/app/src/androidTest/java/com/duckduckgo/app/browser/BrowserTabViewModelTest.kt +++ b/app/src/androidTest/java/com/duckduckgo/app/browser/BrowserTabViewModelTest.kt @@ -420,6 +420,9 @@ class BrowserTabViewModelTest { @Mock private lateinit var mockAppBuildConfig: AppBuildConfig + @Mock + private lateinit var mockDuckDuckGoUrlDetector: DuckDuckGoUrlDetector + private lateinit var remoteMessagingModel: RemoteMessagingModel private val lazyFaviconManager = Lazy { mockFaviconManager } @@ -537,6 +540,7 @@ class BrowserTabViewModelTest { whenever(mockSSLCertificatesFeature.allowBypass()).thenReturn(mockEnabledToggle) whenever(mockExtendedOnboardingFeatureToggles.aestheticUpdates()).thenReturn(mockEnabledToggle) whenever(subscriptions.shouldLaunchPrivacyProForUrl(any())).thenReturn(false) + whenever(mockDuckDuckGoUrlDetector.isDuckDuckGoUrl(any())).thenReturn(false) remoteMessagingModel = givenRemoteMessagingModel(mockRemoteMessagingRepository, mockPixel, coroutineRule.testDispatcherProvider) @@ -649,7 +653,7 @@ class BrowserTabViewModelTest { newTabPixels = { mockNewTabPixels }, httpErrorPixels = { mockHttpErrorPixels }, duckPlayer = mockDuckPlayer, - duckPlayerJSHelper = DuckPlayerJSHelper(mockDuckPlayer, mockAppBuildConfig, mockPixel), + duckPlayerJSHelper = DuckPlayerJSHelper(mockDuckPlayer, mockAppBuildConfig, mockPixel, mockDuckDuckGoUrlDetector), ) testee.loadData("abc", null, false, false) @@ -4851,7 +4855,13 @@ class BrowserTabViewModelTest { fun whenProcessJsCallbackMessageWebShareSendCommand() = runTest { val url = "someUrl" loadUrl(url) - testee.processJsCallbackMessage("myFeature", "webShare", "myId", JSONObject("""{ "my":"object"}""")) + testee.processJsCallbackMessage( + "myFeature", + "webShare", + "myId", + JSONObject("""{ "my":"object"}"""), + "someUrl", + ) assertCommandIssued { assertEquals("object", this.data.params.getString("my")) assertEquals("myFeature", this.data.featureName) @@ -4865,7 +4875,13 @@ class BrowserTabViewModelTest { val url = "someUrl" loadUrl(url) whenever(mockSitePermissionsManager.getPermissionsQueryResponse(eq(url), any(), any())).thenReturn(SitePermissionQueryResponse.Granted) - testee.processJsCallbackMessage("myFeature", "permissionsQuery", "myId", JSONObject("""{ "name":"somePermission"}""")) + testee.processJsCallbackMessage( + "myFeature", + "permissionsQuery", + "myId", + JSONObject("""{ "name":"somePermission"}"""), + "someUrl", + ) assertCommandIssued { assertEquals("granted", this.data.params.getString("state")) assertEquals("myFeature", this.data.featureName) @@ -4877,14 +4893,26 @@ class BrowserTabViewModelTest { @Test fun whenProcessJsCallbackMessageScreenLockNotEnabledDoNotSendCommand() = runTest { whenever(mockEnabledToggle.isEnabled()).thenReturn(false) - testee.processJsCallbackMessage("myFeature", "screenLock", "myId", JSONObject("""{ "my":"object"}""")) + testee.processJsCallbackMessage( + "myFeature", + "screenLock", + "myId", + JSONObject("""{ "my":"object"}"""), + "someUrl", + ) assertCommandNotIssued() } @Test fun whenProcessJsCallbackMessageScreenLockEnabledSendCommand() = runTest { whenever(mockEnabledToggle.isEnabled()).thenReturn(true) - testee.processJsCallbackMessage("myFeature", "screenLock", "myId", JSONObject("""{ "my":"object"}""")) + testee.processJsCallbackMessage( + "myFeature", + "screenLock", + "myId", + JSONObject("""{ "my":"object"}"""), + "someUrl", + ) assertCommandIssued { assertEquals("object", this.data.params.getString("my")) assertEquals("myFeature", this.data.featureName) @@ -4896,14 +4924,26 @@ class BrowserTabViewModelTest { @Test fun whenProcessJsCallbackMessageScreenUnlockNotEnabledDoNotSendCommand() = runTest { whenever(mockEnabledToggle.isEnabled()).thenReturn(false) - testee.processJsCallbackMessage("myFeature", "screenUnlock", "myId", JSONObject("""{ "my":"object"}""")) + testee.processJsCallbackMessage( + "myFeature", + "screenUnlock", + "myId", + JSONObject("""{ "my":"object"}"""), + "someUrl", + ) assertCommandNotIssued() } @Test fun whenProcessJsCallbackMessageScreenUnlockEnabledSendCommand() = runTest { whenever(mockEnabledToggle.isEnabled()).thenReturn(true) - testee.processJsCallbackMessage("myFeature", "screenUnlock", "myId", JSONObject("""{ "my":"object"}""")) + testee.processJsCallbackMessage( + "myFeature", + "screenUnlock", + "myId", + JSONObject("""{ "my":"object"}"""), + "someUrl", + ) assertCommandIssued() } @@ -4911,7 +4951,13 @@ class BrowserTabViewModelTest { fun whenProcessJsCallbackMessageGetUserPreferencesFromOverlayThenSendCommand() = runTest { whenever(mockEnabledToggle.isEnabled()).thenReturn(true) whenever(mockDuckPlayer.getUserPreferences()).thenReturn(UserPreferences(overlayInteracted = true, privatePlayerMode = AlwaysAsk)) - testee.processJsCallbackMessage(DUCK_PLAYER_FEATURE_NAME, "getUserValues", "id", data = null) + testee.processJsCallbackMessage( + DUCK_PLAYER_FEATURE_NAME, + "getUserValues", + "id", + data = null, + "someUrl", + ) assertCommandIssued() } @@ -4924,6 +4970,7 @@ class BrowserTabViewModelTest { "setUserValues", "id", JSONObject("""{ overlayInteracted: "true", privatePlayerMode: {disabled: {} }}"""), + "someUrl", ) assertCommandIssued() verify(mockDuckPlayer).setUserPreferences(any(), any()) @@ -4939,6 +4986,7 @@ class BrowserTabViewModelTest { "setUserValues", "id", JSONObject("""{ overlayInteracted: "true", privatePlayerMode: {enabled: {} }}"""), + "someUrl", ) assertCommandIssued() verify(mockDuckPlayer).setUserPreferences(any(), any()) @@ -4954,6 +5002,7 @@ class BrowserTabViewModelTest { "setUserValues", "id", JSONObject("""{ overlayInteracted: "true", privatePlayerMode: {enabled: {} }}"""), + "someUrl", ) assertCommandIssued() verify(mockDuckPlayer).setUserPreferences(true, "enabled") @@ -4964,7 +5013,13 @@ class BrowserTabViewModelTest { fun whenProcessJsCallbackMessageSendDuckPlayerPixelThenSendPixel() = runTest { whenever(mockEnabledToggle.isEnabled()).thenReturn(true) whenever(mockDuckPlayer.getUserPreferences()).thenReturn(UserPreferences(overlayInteracted = true, privatePlayerMode = AlwaysAsk)) - testee.processJsCallbackMessage(DUCK_PLAYER_FEATURE_NAME, "sendDuckPlayerPixel", "id", JSONObject("""{ pixelName: "pixel", params: {}}""")) + testee.processJsCallbackMessage( + DUCK_PLAYER_FEATURE_NAME, + "sendDuckPlayerPixel", + "id", + JSONObject("""{ pixelName: "pixel", params: {}}"""), + "someUrl", + ) verify(mockDuckPlayer).sendDuckPlayerPixel("pixel", mapOf()) } @@ -4972,7 +5027,13 @@ class BrowserTabViewModelTest { fun whenProcessJsCallbackMessageOpenDuckPlayerWithUrlThenNavigate() = runTest { whenever(mockEnabledToggle.isEnabled()).thenReturn(true) whenever(mockDuckPlayer.getUserPreferences()).thenReturn(UserPreferences(overlayInteracted = true, privatePlayerMode = AlwaysAsk)) - testee.processJsCallbackMessage(DUCK_PLAYER_FEATURE_NAME, "openDuckPlayer", "id", JSONObject("""{ href: "duck://player/1234" }""")) + testee.processJsCallbackMessage( + DUCK_PLAYER_FEATURE_NAME, + "openDuckPlayer", + "id", + JSONObject("""{ href: "duck://player/1234" }"""), + "someUrl", + ) assertCommandIssued() } @@ -4980,7 +5041,13 @@ class BrowserTabViewModelTest { fun whenProcessJsCallbackMessageOpenDuckPlayerWithoutUrlThenDoNotNavigate() = runTest { whenever(mockEnabledToggle.isEnabled()).thenReturn(true) whenever(mockDuckPlayer.getUserPreferences()).thenReturn(UserPreferences(overlayInteracted = true, privatePlayerMode = AlwaysAsk)) - testee.processJsCallbackMessage(DUCK_PLAYER_FEATURE_NAME, "openDuckPlayer", "id", null) + testee.processJsCallbackMessage( + DUCK_PLAYER_FEATURE_NAME, + "openDuckPlayer", + "id", + null, + "someUrl", + ) assertCommandNotIssued() } @@ -4988,7 +5055,13 @@ class BrowserTabViewModelTest { fun whenJsCallbackMessageInitialSetupFromOverlayThenSendResponseToJs() = runTest { whenever(mockEnabledToggle.isEnabled()).thenReturn(true) whenever(mockDuckPlayer.getUserPreferences()).thenReturn(UserPreferences(overlayInteracted = true, privatePlayerMode = AlwaysAsk)) - testee.processJsCallbackMessage(DUCK_PLAYER_FEATURE_NAME, "initialSetup", "id", null) + testee.processJsCallbackMessage( + DUCK_PLAYER_FEATURE_NAME, + "initialSetup", + "id", + null, + "someUrl", + ) assertCommandIssued() } @@ -4996,21 +5069,39 @@ class BrowserTabViewModelTest { fun whenJsCallbackMessageInitialSetupFromDuckPlayerPageThenSendResponseToDuckPlayer() = runTest { whenever(mockEnabledToggle.isEnabled()).thenReturn(true) whenever(mockDuckPlayer.getUserPreferences()).thenReturn(UserPreferences(overlayInteracted = true, privatePlayerMode = AlwaysAsk)) - testee.processJsCallbackMessage(DUCK_PLAYER_PAGE_FEATURE_NAME, "initialSetup", "id", null) + testee.processJsCallbackMessage( + DUCK_PLAYER_PAGE_FEATURE_NAME, + "initialSetup", + "id", + null, + "someUrl", + ) assertCommandIssued() } @Test fun whenJsCallbackMessageOpenSettingsThenOpenSettings() = runTest { whenever(mockEnabledToggle.isEnabled()).thenReturn(true) - testee.processJsCallbackMessage(DUCK_PLAYER_PAGE_FEATURE_NAME, "openSettings", "id", null) + testee.processJsCallbackMessage( + DUCK_PLAYER_PAGE_FEATURE_NAME, + "openSettings", + "id", + null, + "someUrl", + ) assertCommandIssued() } @Test fun whenJsCallbackMessageOpenInfoThenOpenInfo() = runTest { whenever(mockEnabledToggle.isEnabled()).thenReturn(true) - testee.processJsCallbackMessage(DUCK_PLAYER_PAGE_FEATURE_NAME, "openInfo", "id", null) + testee.processJsCallbackMessage( + DUCK_PLAYER_PAGE_FEATURE_NAME, + "openInfo", + "id", + null, + "someUrl", + ) assertCommandIssued() } diff --git a/app/src/main/java/com/duckduckgo/app/browser/BrowserTabFragment.kt b/app/src/main/java/com/duckduckgo/app/browser/BrowserTabFragment.kt index 73dcb60a060e..29251ae40823 100644 --- a/app/src/main/java/com/duckduckgo/app/browser/BrowserTabFragment.kt +++ b/app/src/main/java/com/duckduckgo/app/browser/BrowserTabFragment.kt @@ -182,7 +182,6 @@ import com.duckduckgo.app.browser.webshare.WebShareChooser import com.duckduckgo.app.browser.webview.WebContentDebugging import com.duckduckgo.app.browser.webview.WebViewBlobDownloadFeature import com.duckduckgo.app.browser.webview.safewebview.SafeWebViewFeature -import com.duckduckgo.app.cta.ui.* import com.duckduckgo.app.cta.ui.Cta import com.duckduckgo.app.cta.ui.CtaViewModel import com.duckduckgo.app.cta.ui.DaxBubbleCta @@ -2421,7 +2420,9 @@ class BrowserTabFragment : id: String?, data: JSONObject?, ) { - viewModel.processJsCallbackMessage(featureName, method, id, data) + appCoroutineScope.launch(dispatchers.main()) { + viewModel.processJsCallbackMessage(featureName, method, id, data, it.url) + } } }, ) @@ -2434,7 +2435,9 @@ class BrowserTabFragment : id: String?, data: JSONObject?, ) { - viewModel.processJsCallbackMessage(featureName, method, id, data) + appCoroutineScope.launch(dispatchers.main()) { + viewModel.processJsCallbackMessage(featureName, method, id, data, it.url) + } } }, ) diff --git a/app/src/main/java/com/duckduckgo/app/browser/BrowserTabViewModel.kt b/app/src/main/java/com/duckduckgo/app/browser/BrowserTabViewModel.kt index 6d362c3cb8e9..6b8f1b91053c 100644 --- a/app/src/main/java/com/duckduckgo/app/browser/BrowserTabViewModel.kt +++ b/app/src/main/java/com/duckduckgo/app/browser/BrowserTabViewModel.kt @@ -3153,11 +3153,12 @@ class BrowserTabViewModel @Inject constructor( ) } - fun processJsCallbackMessage( + suspend fun processJsCallbackMessage( featureName: String, method: String, id: String?, data: JSONObject?, + url: String?, ) { when (method) { "webShare" -> if (id != null && data != null) { @@ -3185,8 +3186,8 @@ class BrowserTabViewModel @Inject constructor( when (featureName) { DUCK_PLAYER_FEATURE_NAME, DUCK_PLAYER_PAGE_FEATURE_NAME -> { - viewModelScope.launch { - val response = duckPlayerJSHelper.processJsCallbackMessage(featureName, method, id, data) + withContext(dispatchers.io()) { + val response = duckPlayerJSHelper.processJsCallbackMessage(featureName, method, id, data, url) withContext(dispatchers.main()) { response?.let { command.value = it diff --git a/app/src/main/java/com/duckduckgo/app/browser/duckplayer/DuckPlayerJSHelper.kt b/app/src/main/java/com/duckduckgo/app/browser/duckplayer/DuckPlayerJSHelper.kt index 22897399016a..bfcc2e1e49d9 100644 --- a/app/src/main/java/com/duckduckgo/app/browser/duckplayer/DuckPlayerJSHelper.kt +++ b/app/src/main/java/com/duckduckgo/app/browser/duckplayer/DuckPlayerJSHelper.kt @@ -17,6 +17,7 @@ package com.duckduckgo.app.browser.duckplayer import androidx.core.net.toUri +import com.duckduckgo.app.browser.DuckDuckGoUrlDetector import com.duckduckgo.app.browser.commands.Command import com.duckduckgo.app.browser.commands.Command.OpenDuckPlayerInfo import com.duckduckgo.app.browser.commands.Command.OpenDuckPlayerSettings @@ -25,7 +26,9 @@ 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_ALWAYS_SERP import com.duckduckgo.app.pixels.AppPixelName.DUCK_PLAYER_SETTING_NEVER_OVERLAY_YOUTUBE +import com.duckduckgo.app.pixels.AppPixelName.DUCK_PLAYER_SETTING_NEVER_SERP import com.duckduckgo.app.statistics.pixels.Pixel import com.duckduckgo.appbuildconfig.api.AppBuildConfig import com.duckduckgo.duckplayer.api.DuckPlayer @@ -43,6 +46,7 @@ class DuckPlayerJSHelper @Inject constructor( private val duckPlayer: DuckPlayer, private val appBuildConfig: AppBuildConfig, private val pixel: Pixel, + private val duckDuckGoUrlDetector: DuckDuckGoUrlDetector, ) { private suspend fun getUserPreferences(featureName: String, method: String, id: String): JsCallbackData { val userValues = duckPlayer.getUserPreferences() @@ -128,6 +132,7 @@ class DuckPlayerJSHelper @Inject constructor( method: String, id: String?, data: JSONObject?, + url: String?, ): Command? { when (method) { "getUserValues" -> if (id != null) { @@ -140,9 +145,17 @@ class DuckPlayerJSHelper @Inject constructor( DUCK_PLAYER_FEATURE_NAME -> { SendResponseToJs(getUserPreferences(featureName, method, id)).also { if (data.getJSONObject(PRIVATE_PLAYER_MODE).keys().next() == "enabled") { - pixel.fire(DUCK_PLAYER_SETTING_ALWAYS_OVERLAY_YOUTUBE) + if (url != null && duckDuckGoUrlDetector.isDuckDuckGoUrl(url)) { + pixel.fire(DUCK_PLAYER_SETTING_ALWAYS_SERP) + } else { + 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) + if (url != null && duckDuckGoUrlDetector.isDuckDuckGoUrl(url)) { + pixel.fire(DUCK_PLAYER_SETTING_NEVER_SERP) + } else { + pixel.fire(DUCK_PLAYER_SETTING_NEVER_OVERLAY_YOUTUBE) + } } } } diff --git a/app/src/main/java/com/duckduckgo/app/pixels/AppPixelName.kt b/app/src/main/java/com/duckduckgo/app/pixels/AppPixelName.kt index ecc9962e6e2a..9805019d5ba7 100644 --- a/app/src/main/java/com/duckduckgo/app/pixels/AppPixelName.kt +++ b/app/src/main/java/com/duckduckgo/app/pixels/AppPixelName.kt @@ -352,6 +352,8 @@ enum class AppPixelName(override val pixelName: String) : Pixel.PixelName { TAB_MANAGER_VIEW_MODE_TOGGLED_DAILY("m_tab_manager_view_mode_toggled_daily"), DUCK_PLAYER_SETTING_ALWAYS_OVERLAY_YOUTUBE("duck-player_setting_always_overlay_youtube"), + DUCK_PLAYER_SETTING_ALWAYS_SERP("duck-player_setting_always_overlay_serp"), + DUCK_PLAYER_SETTING_NEVER_SERP("duck-player_setting_never_overlay_serp"), DUCK_PLAYER_SETTING_NEVER_OVERLAY_YOUTUBE("duck-player_setting_never_overlay_youtube"), DUCK_PLAYER_SETTING_ALWAYS_DUCK_PLAYER("duck-player_setting_always_duck-player"),