Skip to content

Commit ec19fd4

Browse files
committed
Update DuckChat.isEnabled to check config flag and user pref
1 parent 1fdedeb commit ec19fd4

File tree

7 files changed

+71
-28
lines changed

7 files changed

+71
-28
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ class SpecialUrlDetectorImpl(
105105

106106
val uri = uriString.toUri()
107107

108-
if (duckChat.isEnabled() && duckChat.isDuckChatUrl(uri)) {
108+
if (duckChat.isDuckChatUrl(uri)) {
109109
return UrlType.ShouldLaunchDuckChatLink
110110
}
111111

app/src/test/java/com/duckduckgo/app/browser/SpecialUrlDetectorImplTest.kt

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -485,10 +485,9 @@ class SpecialUrlDetectorImplTest {
485485
}
486486

487487
@Test
488-
fun whenDuckChatIsEnabledAndIsDuckChatUrlThenReturnShouldLaunchDuckChatLink() = runTest {
489-
whenever(mockDuckChat.isEnabled()).thenReturn(true)
488+
fun whenIsDuckChatUrlThenReturnShouldLaunchDuckChatLink() = runTest {
490489
whenever(mockDuckChat.isDuckChatUrl(any())).thenReturn(true)
491-
val type = testee.determineType("https://example.com")
490+
val type = testee.determineType("https://duck.ai")
492491
whenever(mockPackageManager.resolveActivity(any(), eq(PackageManager.MATCH_DEFAULT_ONLY))).thenReturn(null)
493492
whenever(mockPackageManager.queryIntentActivities(any(), anyInt())).thenReturn(
494493
listOf(
@@ -501,9 +500,8 @@ class SpecialUrlDetectorImplTest {
501500
}
502501

503502
@Test
504-
fun whenDuckChatIsDisabledAndIsDuckChatUrlThenDoNotReturnShouldLaunchDuckChatLink() = runTest {
505-
whenever(mockDuckChat.isEnabled()).thenReturn(false)
506-
whenever(mockDuckChat.isDuckChatUrl(any())).thenReturn(true)
503+
fun whenIsNotDuckChatUrlThenDoNotReturnShouldLaunchDuckChatLink() = runTest {
504+
whenever(mockDuckChat.isDuckChatUrl(any())).thenReturn(false)
507505
val type = testee.determineType("https://example.com")
508506
whenever(mockPackageManager.resolveActivity(any(), eq(PackageManager.MATCH_DEFAULT_ONLY))).thenReturn(null)
509507
whenever(mockPackageManager.queryIntentActivities(any(), anyInt())).thenReturn(

duckchat/duckchat-api/src/main/java/com/duckduckgo/duckchat/api/DuckChat.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import android.net.Uri
2323
*/
2424
interface DuckChat {
2525
/**
26-
* Checks whether DuckChat is enabled based on remote config flag.
26+
* Checks whether DuckChat is enabled based on remote config flag and user preference.
2727
* Uses a cached value - does not perform disk I/O.
2828
*
2929
* @return true if DuckChat is enabled, false otherwise.

duckchat/duckchat-impl/src/main/java/com/duckduckgo/duckchat/impl/RealDuckChat.kt

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,11 @@ interface DuckChatInternal : DuckChat {
159159
* Returns whether dedicated Duck.ai input screen feature is available (its feature flag is enabled).
160160
*/
161161
fun isInputScreenFeatureAvailable(): Boolean
162+
163+
/**
164+
* Checks whether DuckChat is enabled based on remote config flag.
165+
*/
166+
fun isDuckChatFeatureEnabled(): Boolean
162167
}
163168

164169
enum class ChatState(val value: String) {
@@ -234,7 +239,7 @@ class RealDuckChat @Inject constructor(
234239
moshi.adapter(DuckChatSettingJson::class.java)
235240
}
236241

237-
private var isDuckChatEnabled = false
242+
private var isDuckChatFeatureEnabled = false
238243
private var isDuckAiInBrowserEnabled = false
239244
private var duckAiInputScreen = false
240245
private var isDuckChatUserEnabled = false
@@ -281,13 +286,17 @@ class RealDuckChat @Inject constructor(
281286
}
282287

283288
override fun isEnabled(): Boolean {
284-
return isDuckChatEnabled
289+
return isDuckChatFeatureEnabled && isDuckChatUserEnabled
285290
}
286291

287292
override fun isInputScreenFeatureAvailable(): Boolean {
288293
return duckAiInputScreen
289294
}
290295

296+
override fun isDuckChatFeatureEnabled(): Boolean {
297+
return isDuckChatFeatureEnabled
298+
}
299+
291300
override fun observeEnableDuckChatUserSetting(): Flow<Boolean> {
292301
return duckChatFeatureRepository.observeDuckChatUserEnabled()
293302
}
@@ -491,6 +500,8 @@ class RealDuckChat @Inject constructor(
491500
}
492501

493502
override fun isDuckChatUrl(uri: Uri): Boolean {
503+
if (!isDuckChatFeatureEnabled) return false
504+
494505
if (isDuckChatBang(uri)) return true
495506

496507
if (uri.host != DUCKDUCKGO_HOST) {
@@ -524,7 +535,7 @@ class RealDuckChat @Inject constructor(
524535
private fun cacheConfig() {
525536
appCoroutineScope.launch(dispatchers.io()) {
526537
val featureEnabled = duckChatFeature.self().isEnabled()
527-
isDuckChatEnabled = featureEnabled
538+
isDuckChatFeatureEnabled = featureEnabled
528539
_showSettings.value = featureEnabled
529540
isDuckAiInBrowserEnabled = duckChatFeature.duckAiButtonInBrowser().isEnabled()
530541
duckAiInputScreen = duckChatFeature.duckAiInputScreen().isEnabled()
@@ -553,16 +564,16 @@ class RealDuckChat @Inject constructor(
553564
private suspend fun cacheUserSettings() = withContext(dispatchers.io()) {
554565
isDuckChatUserEnabled = duckChatFeatureRepository.isDuckChatUserEnabled()
555566

556-
val showInputScreen = isInputScreenFeatureAvailable() && isDuckChatEnabled && isDuckChatUserEnabled &&
567+
val showInputScreen = isInputScreenFeatureAvailable() && isDuckChatFeatureEnabled && isDuckChatUserEnabled &&
557568
experimentalThemingDataStore.isSingleOmnibarEnabled.value && duckChatFeatureRepository.isInputScreenUserSettingEnabled()
558569
_showInputScreen.emit(showInputScreen)
559570

560571
val showInBrowserMenu = duckChatFeatureRepository.shouldShowInBrowserMenu() &&
561-
isDuckChatEnabled && isDuckChatUserEnabled
572+
isDuckChatFeatureEnabled && isDuckChatUserEnabled
562573
_showInBrowserMenu.emit(showInBrowserMenu)
563574

564575
val showInAddressBar = duckChatFeatureRepository.shouldShowInAddressBar() &&
565-
isDuckChatEnabled && isDuckChatUserEnabled && isAddressBarEntryPointEnabled
576+
isDuckChatFeatureEnabled && isDuckChatUserEnabled && isAddressBarEntryPointEnabled
566577
_showInAddressBar.emit(showInAddressBar)
567578

568579
val showOmnibarShortcutInAllStates = showInAddressBar && isDuckAiInBrowserEnabled

duckchat/duckchat-impl/src/main/java/com/duckduckgo/duckchat/impl/helper/DuckChatJSHelper.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ class RealDuckChatJSHelper @Inject constructor(
111111
): JsCallbackData {
112112
val jsonPayload = JSONObject().apply {
113113
put(PLATFORM, ANDROID)
114-
put(IS_HANDOFF_ENABLED, duckChat.isEnabled())
114+
put(IS_HANDOFF_ENABLED, duckChat.isDuckChatFeatureEnabled())
115115
put(PAYLOAD, runBlocking { dataStore.fetchAndClearUserPreferences() })
116116
}
117117
return JsCallbackData(jsonPayload, featureName, method, id)
@@ -124,7 +124,7 @@ class RealDuckChatJSHelper @Inject constructor(
124124
): JsCallbackData {
125125
val jsonPayload = JSONObject().apply {
126126
put(PLATFORM, ANDROID)
127-
put(IS_HANDOFF_ENABLED, duckChat.isEnabled())
127+
put(IS_HANDOFF_ENABLED, duckChat.isDuckChatFeatureEnabled())
128128
put(SUPPORTS_CLOSING_AI_CHAT, true)
129129
put(SUPPORTS_OPENING_SETTINGS, true)
130130
put(SUPPORTS_NATIVE_CHAT_INPUT, false)

duckchat/duckchat-impl/src/test/kotlin/com/duckduckgo/duckchat/impl/RealDuckChatTest.kt

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,13 +133,39 @@ class RealDuckChatTest {
133133
}
134134

135135
@Test
136-
fun whenDuckChatIsEnabledThenReturnTrue() = runTest {
136+
fun whenDuckChatFeatureEnabledAndUserEnabledThenIsEnabledReturnsTrue() = runTest {
137+
duckChatFeature.self().setRawStoredState(State(true))
138+
whenever(mockDuckChatFeatureRepository.isDuckChatUserEnabled()).thenReturn(true)
139+
140+
testee.onPrivacyConfigDownloaded()
141+
137142
assertTrue(testee.isEnabled())
138143
}
139144

140145
@Test
141-
fun whenDuckChatIsDisabledThenReturnFalse() = runTest {
146+
fun whenDuckChatFeatureDisabledAndUserEnabledThenIsEnabledReturnFalse() = runTest {
142147
duckChatFeature.self().setRawStoredState(State(false))
148+
whenever(mockDuckChatFeatureRepository.isDuckChatUserEnabled()).thenReturn(true)
149+
150+
testee.onPrivacyConfigDownloaded()
151+
152+
assertFalse(testee.isEnabled())
153+
}
154+
155+
@Test
156+
fun whenDuckChatFeatureEnabledAndUserDisabledThenIsEnabledReturnsFalse() = runTest {
157+
duckChatFeature.self().setRawStoredState(State(true))
158+
whenever(mockDuckChatFeatureRepository.isDuckChatUserEnabled()).thenReturn(false)
159+
160+
testee.onPrivacyConfigDownloaded()
161+
162+
assertFalse(testee.isEnabled())
163+
}
164+
165+
@Test
166+
fun whenDuckChatFeatureDisabledAndUserDisabledThenIsEnabledReturnsFalse() = runTest {
167+
duckChatFeature.self().setRawStoredState(State(false))
168+
whenever(mockDuckChatFeatureRepository.isDuckChatUserEnabled()).thenReturn(false)
143169

144170
testee.onPrivacyConfigDownloaded()
145171

@@ -377,6 +403,14 @@ class RealDuckChatTest {
377403
assertFalse(testee.isDuckChatUrl("https://example.com/?ia=chat".toUri()))
378404
}
379405

406+
@Test
407+
fun whenDuckChatFeatureDisabledThenIsDuckChatUrlReturnsFalse() {
408+
duckChatFeature.self().setRawStoredState(State(enable = false))
409+
testee.onPrivacyConfigDownloaded()
410+
411+
assertFalse(testee.isDuckChatUrl("https://duckduckgo.com/?ia=chat".toUri()))
412+
}
413+
380414
@Test
381415
fun whenWasOpenedBeforeQueriedThenRepoStateIsReturned() = runTest {
382416
whenever(mockDuckChatFeatureRepository.wasOpenedBefore()).thenReturn(true)

duckchat/duckchat-impl/src/test/kotlin/com/duckduckgo/duckchat/impl/helper/RealDuckChatJSHelperTest.kt

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -74,12 +74,12 @@ class RealDuckChatJSHelperTest {
7474
}
7575

7676
@Test
77-
fun whenGetAIChatNativeHandoffDataAndDuckChatEnabledThenReturnJsCallbackDataWithDuckChatEnabled() = runTest {
77+
fun whenGetAIChatNativeHandoffDataAndDuckChatFeatureEnabledThenReturnJsCallbackDataWithDuckChatEnabled() = runTest {
7878
val featureName = "aiChat"
7979
val method = "getAIChatNativeHandoffData"
8080
val id = "123"
8181

82-
whenever(mockDuckChat.isEnabled()).thenReturn(true)
82+
whenever(mockDuckChat.isDuckChatFeatureEnabled()).thenReturn(true)
8383
whenever(mockDataStore.fetchAndClearUserPreferences()).thenReturn("preferences")
8484

8585
val result = testee.processJsCallbackMessage(featureName, method, id, null)
@@ -99,12 +99,12 @@ class RealDuckChatJSHelperTest {
9999
}
100100

101101
@Test
102-
fun whenGetAIChatNativeHandoffDataAndDuckChatDisabledThenReturnJsCallbackDataWithDuckChatDisabled() = runTest {
102+
fun whenGetAIChatNativeHandoffDataAndDuckChatFeatureDisabledThenReturnJsCallbackDataWithDuckChatDisabled() = runTest {
103103
val featureName = "aiChat"
104104
val method = "getAIChatNativeHandoffData"
105105
val id = "123"
106106

107-
whenever(mockDuckChat.isEnabled()).thenReturn(false)
107+
whenever(mockDuckChat.isDuckChatFeatureEnabled()).thenReturn(false)
108108
whenever(mockDataStore.fetchAndClearUserPreferences()).thenReturn("preferences")
109109

110110
val result = testee.processJsCallbackMessage(featureName, method, id, null)
@@ -129,7 +129,7 @@ class RealDuckChatJSHelperTest {
129129
val method = "getAIChatNativeHandoffData"
130130
val id = "123"
131131

132-
whenever(mockDuckChat.isEnabled()).thenReturn(true)
132+
whenever(mockDuckChat.isDuckChatFeatureEnabled()).thenReturn(true)
133133
whenever(mockDataStore.fetchAndClearUserPreferences()).thenReturn(null)
134134

135135
val result = testee.processJsCallbackMessage(featureName, method, id, null)
@@ -159,12 +159,12 @@ class RealDuckChatJSHelperTest {
159159
}
160160

161161
@Test
162-
fun whenGetAIChatNativeConfigValuesAndDuckChatEnabledThenReturnJsCallbackDataWithDuckChatEnabled() = runTest {
162+
fun whenGetAIChatNativeConfigValuesAndDuckChatFeatureEnabledThenReturnJsCallbackDataWithDuckChatEnabled() = runTest {
163163
val featureName = "aiChat"
164164
val method = "getAIChatNativeConfigValues"
165165
val id = "123"
166166

167-
whenever(mockDuckChat.isEnabled()).thenReturn(true)
167+
whenever(mockDuckChat.isDuckChatFeatureEnabled()).thenReturn(true)
168168

169169
val result = testee.processJsCallbackMessage(featureName, method, id, null)
170170

@@ -186,12 +186,12 @@ class RealDuckChatJSHelperTest {
186186
}
187187

188188
@Test
189-
fun whenGetAIChatNativeConfigValuesAndDuckChatDisabledThenReturnJsCallbackDataWithDuckChatDisabled() = runTest {
189+
fun whenGetAIChatNativeConfigValuesAndDuckChatFeatureDisabledThenReturnJsCallbackDataWithDuckChatDisabled() = runTest {
190190
val featureName = "aiChat"
191191
val method = "getAIChatNativeConfigValues"
192192
val id = "123"
193193

194-
whenever(mockDuckChat.isEnabled()).thenReturn(false)
194+
whenever(mockDuckChat.isDuckChatFeatureEnabled()).thenReturn(false)
195195

196196
val result = testee.processJsCallbackMessage(featureName, method, id, null)
197197

@@ -350,7 +350,7 @@ class RealDuckChatJSHelperTest {
350350
val method = "getAIChatNativeConfigValues"
351351
val id = "123"
352352

353-
whenever(mockDuckChat.isEnabled()).thenReturn(true)
353+
whenever(mockDuckChat.isDuckChatFeatureEnabled()).thenReturn(true)
354354
whenever(mockDuckChat.isImageUploadEnabled()).thenReturn(true)
355355

356356
val result = testee.processJsCallbackMessage(featureName, method, id, null)

0 commit comments

Comments
 (0)