Skip to content

Commit 0cdbbaf

Browse files
Merge branch 'release/5.249.0'
2 parents 76907a2 + 56113d0 commit 0cdbbaf

File tree

202 files changed

+6762
-2325
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

202 files changed

+6762
-2325
lines changed

.maestro/input_screen/input_screen_search_mode.yaml

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,13 @@ tags:
2323
id: "omnibarTextInput"
2424
- assertVisible:
2525
id: "inputModeWidget"
26-
- inputText: "redd"
26+
- inputText: "reddit"
2727
- assertVisible:
2828
id: "autoCompleteSuggestionsList"
2929
- assertVisible:
30-
text: "reddit"
30+
text: "reddit.com"
31+
childOf:
32+
id: "autoCompleteSuggestionsList"
3133

3234
# verify that submitting a search query opens SERP
3335
- tapOn:
@@ -38,6 +40,8 @@ tags:
3840
id: "browserLayout"
3941
- assertVisible:
4042
text: "reddit"
43+
childOf:
44+
id: "browserLayout"
4145

4246
# add the page to favorites (for future assertions)
4347
- runFlow: ../shared/browser_screen/click_on_menu_button.yaml
@@ -59,11 +63,13 @@ tags:
5963
- assertVisible:
6064
id: "inputModeWidget"
6165
- assertVisible:
62-
text: "redd"
66+
text: "reddit"
6367
- assertVisible:
6468
id: "autoCompleteSuggestionsList"
6569
- assertVisible:
6670
text: "reddit.com"
71+
childOf:
72+
id: "autoCompleteSuggestionsList"
6773

6874
# verify that clearing the Input Screen text and submitting a URL opens the web page
6975
- tapOn:
@@ -86,7 +92,7 @@ tags:
8692
- assertNotVisible:
8793
id: "autoCompleteSuggestionsList"
8894
- assertVisible:
89-
text: "redd at DuckDuckGo"
95+
text: "reddit at DuckDuckGo"
9096
childOf:
9197
id: "focusedFavourites"
9298

@@ -121,8 +127,10 @@ tags:
121127
- assertVisible:
122128
id: "browserLayout"
123129
- assertVisible:
124-
text: "redd"
130+
text: "reddit"
125131
childOf:
126132
id: "singleOmnibar"
127133
- assertVisible:
128134
text: "reddit"
135+
childOf:
136+
id: "browserLayout"

app-tracking-protection/vpn-impl/src/main/java/com/duckduckgo/mobile/android/vpn/service/TrackerBlockingVpnService.kt

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package com.duckduckgo.mobile.android.vpn.service
1818

1919
import android.annotation.SuppressLint
2020
import android.app.ActivityManager
21+
import android.app.ForegroundServiceStartNotAllowedException
2122
import android.app.Service
2223
import android.content.ComponentName
2324
import android.content.Context
@@ -815,7 +816,7 @@ class TrackerBlockingVpnService : VpnService(), CoroutineScope by MainScope(), V
815816
if (!isServiceRunning(appContext)) return
816817

817818
snoozeIntent(appContext, triggerAtMillis).run {
818-
ContextCompat.startForegroundService(appContext, this)
819+
appContext.startForegroundServiceWithFallback(this)
819820
}
820821
}
821822

@@ -842,7 +843,7 @@ class TrackerBlockingVpnService : VpnService(), CoroutineScope by MainScope(), V
842843
if (isServiceRunning(applicationContext)) return
843844

844845
startIntent(applicationContext).run {
845-
ContextCompat.startForegroundService(applicationContext, this)
846+
applicationContext.startForegroundServiceWithFallback(this)
846847
}
847848
}
848849

@@ -852,15 +853,15 @@ class TrackerBlockingVpnService : VpnService(), CoroutineScope by MainScope(), V
852853
if (!isServiceRunning(applicationContext)) return
853854

854855
stopIntent(applicationContext).run {
855-
ContextCompat.startForegroundService(applicationContext, this)
856+
applicationContext.startForegroundServiceWithFallback(this)
856857
}
857858
}
858859

859860
private fun restartService(context: Context) {
860861
val applicationContext = context.applicationContext
861862

862863
restartIntent(applicationContext).run {
863-
ContextCompat.startForegroundService(applicationContext, this)
864+
applicationContext.startForegroundServiceWithFallback(this)
864865
}
865866
}
866867

@@ -883,6 +884,23 @@ class TrackerBlockingVpnService : VpnService(), CoroutineScope by MainScope(), V
883884
}
884885
}
885886

887+
@SuppressLint("DenyListedApi") // static private method
888+
private fun Context.startForegroundServiceWithFallback(intent: Intent) {
889+
try {
890+
ContextCompat.startForegroundService(this, intent)
891+
} catch (ex: ForegroundServiceStartNotAllowedException) {
892+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
893+
try {
894+
this.startService(intent)
895+
} catch (_: Throwable) {
896+
// no-op
897+
}
898+
} else {
899+
throw ex
900+
}
901+
}
902+
}
903+
886904
private const val ACTION_START_VPN = "ACTION_START_VPN"
887905
private const val ACTION_STOP_VPN = "ACTION_STOP_VPN"
888906
private const val ACTION_RESTART_VPN = "ACTION_RESTART_VPN"

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

Lines changed: 117 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,6 @@ import com.duckduckgo.app.browser.commands.NavigationCommand
9191
import com.duckduckgo.app.browser.commands.NavigationCommand.Navigate
9292
import com.duckduckgo.app.browser.customtabs.CustomTabPixelNames
9393
import com.duckduckgo.app.browser.defaultbrowsing.prompts.AdditionalDefaultBrowserPrompts
94-
import com.duckduckgo.app.browser.defaultbrowsing.prompts.ui.experiment.OnboardingHomeScreenWidgetExperiment
9594
import com.duckduckgo.app.browser.duckplayer.DUCK_PLAYER_FEATURE_NAME
9695
import com.duckduckgo.app.browser.duckplayer.DUCK_PLAYER_PAGE_FEATURE_NAME
9796
import com.duckduckgo.app.browser.duckplayer.DuckPlayerJSHelper
@@ -122,6 +121,7 @@ import com.duckduckgo.app.browser.santize.NonHttpAppLinkChecker
122121
import com.duckduckgo.app.browser.session.WebViewSessionStorage
123122
import com.duckduckgo.app.browser.tabs.TabManager
124123
import com.duckduckgo.app.browser.trafficquality.AndroidFeaturesHeaderPlugin.Companion.X_DUCKDUCKGO_ANDROID_HEADER
124+
import com.duckduckgo.app.browser.ui.dialogs.widgetprompt.OnboardingHomeScreenWidgetToggles
125125
import com.duckduckgo.app.browser.viewstate.BrowserViewState
126126
import com.duckduckgo.app.browser.viewstate.CtaViewState
127127
import com.duckduckgo.app.browser.viewstate.FindInPageViewState
@@ -149,6 +149,7 @@ import com.duckduckgo.app.cta.ui.OnboardingDaxDialogCta
149149
import com.duckduckgo.app.cta.ui.OnboardingDaxDialogCta.DaxMainNetworkCta
150150
import com.duckduckgo.app.cta.ui.OnboardingDaxDialogCta.DaxSerpCta
151151
import com.duckduckgo.app.cta.ui.OnboardingDaxDialogCta.DaxTrackersBlockedCta
152+
import com.duckduckgo.app.dispatchers.ExternalIntentProcessingState
152153
import com.duckduckgo.app.fire.fireproofwebsite.data.FireproofWebsiteDao
153154
import com.duckduckgo.app.fire.fireproofwebsite.data.FireproofWebsiteEntity
154155
import com.duckduckgo.app.fire.fireproofwebsite.data.FireproofWebsiteRepositoryImpl
@@ -447,6 +448,12 @@ class BrowserTabViewModelTest {
447448

448449
private val mockDuckAiFeatureStateInputScreenOpenAutomaticallyFlow = MutableStateFlow(false)
449450

451+
private val mockExternalIntentProcessingState: ExternalIntentProcessingState = mock()
452+
453+
private val mockHasPendingTabLaunchFlow = MutableStateFlow(false)
454+
455+
private val mockHasPendingDuckAiOpenFlow = MutableStateFlow(false)
456+
450457
private val mockAppBuildConfig: AppBuildConfig = mock()
451458

452459
private val mockDuckDuckGoUrlDetector: DuckDuckGoUrlDetector = mock()
@@ -561,7 +568,7 @@ class BrowserTabViewModelTest {
561568
private val mockSiteHttpErrorHandler: HttpCodeSiteErrorHandler = mock()
562569
private val mockSubscriptionsJSHelper: SubscriptionsJSHelper = mock()
563570
private val mockReactivateUsersExperiment: ReactivateUsersExperiment = mock()
564-
private val mockOnboardingHomeScreenWidgetExperiment: OnboardingHomeScreenWidgetExperiment = mock()
571+
private val mockOnboardingHomeScreenWidgetToggles: OnboardingHomeScreenWidgetToggles = mock()
565572
private val mockRebrandingFeatureToggle: SubscriptionRebrandingFeatureToggle = mock()
566573
private val tabManager: TabManager = mock()
567574

@@ -651,6 +658,8 @@ class BrowserTabViewModelTest {
651658
whenever(mockDuckAiFeatureState.showPopupMenuShortcut).thenReturn(MutableStateFlow(false))
652659
whenever(mockDuckAiFeatureState.showInputScreen).thenReturn(mockDuckAiFeatureStateInputScreenFlow)
653660
whenever(mockDuckAiFeatureState.showInputScreenAutomaticallyOnNewTab).thenReturn(mockDuckAiFeatureStateInputScreenOpenAutomaticallyFlow)
661+
whenever(mockExternalIntentProcessingState.hasPendingTabLaunch).thenReturn(mockHasPendingTabLaunchFlow)
662+
whenever(mockExternalIntentProcessingState.hasPendingDuckAiOpen).thenReturn(mockHasPendingDuckAiOpenFlow)
654663
whenever(mockOnboardingDesignExperimentManager.isModifiedControlEnrolledAndEnabled()).thenReturn(false)
655664
whenever(mockOnboardingDesignExperimentManager.isBuckEnrolledAndEnabled()).thenReturn(false)
656665
whenever(mockOnboardingDesignExperimentManager.isBbEnrolledAndEnabled()).thenReturn(false)
@@ -674,7 +683,7 @@ class BrowserTabViewModelTest {
674683
subscriptions = subscriptions,
675684
duckPlayer = mockDuckPlayer,
676685
brokenSitePrompt = mockBrokenSitePrompt,
677-
onboardingHomeScreenWidgetExperiment = mockOnboardingHomeScreenWidgetExperiment,
686+
onboardingHomeScreenWidgetToggles = mockOnboardingHomeScreenWidgetToggles,
678687
onboardingDesignExperimentManager = mockOnboardingDesignExperimentManager,
679688
rebrandingFeatureToggle = mockRebrandingFeatureToggle,
680689
)
@@ -800,6 +809,7 @@ class BrowserTabViewModelTest {
800809
autoCompleteSettings = mockAutoCompleteSettings,
801810
serpEasterEggLogosToggles = mockSerpEasterEggLogoToggles,
802811
nonHttpAppLinkChecker = nonHttpAppLinkChecker,
812+
externalIntentProcessingState = mockExternalIntentProcessingState,
803813
)
804814

805815
testee.loadData("abc", null, false, false)
@@ -6936,6 +6946,110 @@ class BrowserTabViewModelTest {
69366946
)
69376947
}
69386948

6949+
@Test
6950+
fun whenInputScreenEnabledAndExternalIntentProcessingThenLaunchInputScreenCommandSuppressed() = runTest {
6951+
val initialTabId = "initial-tab"
6952+
val initialTab = TabEntity(tabId = initialTabId, url = "https://example.com", title = "EX", skipHome = false, viewed = true, position = 0)
6953+
val ntpTabId = "ntp-tab"
6954+
val ntpTab = TabEntity(tabId = ntpTabId, url = null, title = "", skipHome = false, viewed = true, position = 0)
6955+
whenever(mockTabRepository.getTab(initialTabId)).thenReturn(initialTab)
6956+
whenever(mockTabRepository.getTab(ntpTabId)).thenReturn(ntpTab)
6957+
flowSelectedTab.emit(initialTab)
6958+
6959+
testee.loadData(tabId = ntpTabId, initialUrl = null, skipHome = false, isExternal = false)
6960+
mockDuckAiFeatureStateInputScreenOpenAutomaticallyFlow.emit(true)
6961+
mockHasPendingTabLaunchFlow.emit(true)
6962+
6963+
flowSelectedTab.emit(ntpTab)
6964+
6965+
verify(mockCommandObserver, atLeastOnce()).onChanged(commandCaptor.capture())
6966+
val commands = commandCaptor.allValues
6967+
assertFalse(
6968+
"LaunchInputScreen command should be suppressed when external intent processing is active",
6969+
commands.any { it is Command.LaunchInputScreen },
6970+
)
6971+
}
6972+
6973+
@Test
6974+
fun whenInputScreenEnabledAndExternalIntentProcessingCompletedThenLaunchInputScreenCommandTriggered() = runTest {
6975+
val initialTabId = "initial-tab"
6976+
val initialTab = TabEntity(tabId = initialTabId, url = "https://example.com", title = "EX", skipHome = false, viewed = true, position = 0)
6977+
val ntpTabId = "ntp-tab"
6978+
val ntpTab = TabEntity(tabId = ntpTabId, url = null, title = "", skipHome = false, viewed = true, position = 0)
6979+
whenever(mockTabRepository.getTab(initialTabId)).thenReturn(initialTab)
6980+
whenever(mockTabRepository.getTab(ntpTabId)).thenReturn(ntpTab)
6981+
flowSelectedTab.emit(initialTab)
6982+
6983+
testee.loadData(tabId = ntpTabId, initialUrl = null, skipHome = false, isExternal = false)
6984+
mockDuckAiFeatureStateInputScreenOpenAutomaticallyFlow.emit(true)
6985+
mockHasPendingTabLaunchFlow.emit(true)
6986+
6987+
// Switch to a new tab with no URL
6988+
flowSelectedTab.emit(ntpTab)
6989+
6990+
// Complete external intent processing
6991+
mockHasPendingTabLaunchFlow.emit(false)
6992+
6993+
verify(mockCommandObserver, atLeastOnce()).onChanged(commandCaptor.capture())
6994+
val commands = commandCaptor.allValues
6995+
assertTrue(
6996+
"LaunchInputScreen command should be triggered when external intent processing completes",
6997+
commands.any { it is Command.LaunchInputScreen },
6998+
)
6999+
}
7000+
7001+
@Test
7002+
fun whenInputScreenEnabledAndDuckAiOpenThenLaunchInputScreenCommandSuppressed() = runTest {
7003+
val initialTabId = "initial-tab"
7004+
val initialTab = TabEntity(tabId = initialTabId, url = "https://example.com", title = "EX", skipHome = false, viewed = true, position = 0)
7005+
val ntpTabId = "ntp-tab"
7006+
val ntpTab = TabEntity(tabId = ntpTabId, url = null, title = "", skipHome = false, viewed = true, position = 0)
7007+
whenever(mockTabRepository.getTab(initialTabId)).thenReturn(initialTab)
7008+
whenever(mockTabRepository.getTab(ntpTabId)).thenReturn(ntpTab)
7009+
flowSelectedTab.emit(initialTab)
7010+
7011+
testee.loadData(tabId = ntpTabId, initialUrl = null, skipHome = false, isExternal = false)
7012+
mockDuckAiFeatureStateInputScreenOpenAutomaticallyFlow.emit(true)
7013+
mockHasPendingDuckAiOpenFlow.emit(true)
7014+
7015+
flowSelectedTab.emit(ntpTab)
7016+
7017+
verify(mockCommandObserver, atLeastOnce()).onChanged(commandCaptor.capture())
7018+
val commands = commandCaptor.allValues
7019+
assertFalse(
7020+
"LaunchInputScreen command should be suppressed when Duck.ai is opened",
7021+
commands.any { it is Command.LaunchInputScreen },
7022+
)
7023+
}
7024+
7025+
@Test
7026+
fun whenInputScreenEnabledAndDuckAiClosedThenLaunchInputScreenCommandTriggered() = runTest {
7027+
val initialTabId = "initial-tab"
7028+
val initialTab = TabEntity(tabId = initialTabId, url = "https://example.com", title = "EX", skipHome = false, viewed = true, position = 0)
7029+
val ntpTabId = "ntp-tab"
7030+
val ntpTab = TabEntity(tabId = ntpTabId, url = null, title = "", skipHome = false, viewed = true, position = 0)
7031+
whenever(mockTabRepository.getTab(initialTabId)).thenReturn(initialTab)
7032+
whenever(mockTabRepository.getTab(ntpTabId)).thenReturn(ntpTab)
7033+
flowSelectedTab.emit(initialTab)
7034+
7035+
testee.loadData(tabId = ntpTabId, initialUrl = null, skipHome = false, isExternal = false)
7036+
mockDuckAiFeatureStateInputScreenOpenAutomaticallyFlow.emit(true)
7037+
mockHasPendingDuckAiOpenFlow.emit(true)
7038+
7039+
// Switch to a new tab with no URL
7040+
flowSelectedTab.emit(ntpTab)
7041+
7042+
// Close Duck.ai
7043+
mockHasPendingDuckAiOpenFlow.emit(false)
7044+
7045+
verify(mockCommandObserver, atLeastOnce()).onChanged(commandCaptor.capture())
7046+
val commands = commandCaptor.allValues
7047+
assertTrue(
7048+
"LaunchInputScreen command should be triggered when Duck.ai is closed",
7049+
commands.any { it is Command.LaunchInputScreen },
7050+
)
7051+
}
7052+
69397053
@Test
69407054
fun whenEvaluateSerpLogoStateCalledWithDuckDuckGoUrlAndFeatureEnabledThenExtractSerpLogoCommandIssued() {
69417055
whenever(mockSerpEasterEggLogoToggles.feature()).thenReturn(mockEnabledToggle)

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -387,7 +387,7 @@ class BrowserWebViewClientTest {
387387

388388
assertFalse(fakePostMessageWrapperPlugins.plugin.postMessageCalled)
389389

390-
testee.postContentScopeMessage(data)
390+
testee.postContentScopeMessage(data, webView)
391391

392392
assertTrue(fakePostMessageWrapperPlugins.plugin.postMessageCalled)
393393
}
@@ -1383,7 +1383,7 @@ class BrowserWebViewClientTest {
13831383
var postMessageCalled = false
13841384
private set
13851385

1386-
override fun postMessage(message: SubscriptionEventData) {
1386+
override fun postMessage(message: SubscriptionEventData, webView: WebView) {
13871387
postMessageCalled = true
13881388
}
13891389

app/src/androidTest/java/com/duckduckgo/app/browser/certificates/rootstore/TrustedCertificateStoreTest.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import androidx.test.platform.app.InstrumentationRegistry
2020
import com.duckduckgo.app.browser.certificates.LetsEncryptCertificateProvider
2121
import com.duckduckgo.app.browser.certificates.LetsEncryptCertificateProviderImpl
2222
import org.junit.Assert.assertEquals
23+
import org.junit.Ignore
2324
import org.junit.Test
2425

2526
class TrustedCertificateStoreTest {
@@ -117,6 +118,7 @@ class TrustedCertificateStoreTest {
117118
)
118119
}
119120

121+
@Ignore("Skipping until https://app.asana.com/1/137249556945/project/1202552961248957/task/1211370790060438?focus=true")
120122
@Test
121123
fun whenValidateSslCertificateChainWithR3TrustedChainThenSuccess() {
122124
val letsEncryptCertificateProvider: LetsEncryptCertificateProvider = LetsEncryptCertificateProviderImpl(
@@ -143,6 +145,7 @@ class TrustedCertificateStoreTest {
143145
)
144146
}
145147

148+
@Ignore("Skipping until https://app.asana.com/1/137249556945/project/1202552961248957/task/1211370790060438?focus=true")
146149
@Test
147150
fun whenValidateSslCertificateChainWithMissingRootThenError() {
148151
val letsEncryptCertificateProvider: LetsEncryptCertificateProvider = LetsEncryptCertificateProviderImpl(

0 commit comments

Comments
 (0)