Skip to content

Update DuckChat.isEnabled to check config flag and user pref #6605

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ class SpecialUrlDetectorImpl(

val uri = uriString.toUri()

if (duckChat.isEnabled() && duckChat.isDuckChatUrl(uri)) {
if (duckChat.isDuckChatUrl(uri)) {
return UrlType.ShouldLaunchDuckChatLink
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -485,10 +485,9 @@ class SpecialUrlDetectorImplTest {
}

@Test
fun whenDuckChatIsEnabledAndIsDuckChatUrlThenReturnShouldLaunchDuckChatLink() = runTest {
whenever(mockDuckChat.isEnabled()).thenReturn(true)
fun whenIsDuckChatUrlThenReturnShouldLaunchDuckChatLink() = runTest {
whenever(mockDuckChat.isDuckChatUrl(any())).thenReturn(true)
val type = testee.determineType("https://example.com")
val type = testee.determineType("https://duck.ai")
whenever(mockPackageManager.resolveActivity(any(), eq(PackageManager.MATCH_DEFAULT_ONLY))).thenReturn(null)
whenever(mockPackageManager.queryIntentActivities(any(), anyInt())).thenReturn(
listOf(
Expand All @@ -501,9 +500,8 @@ class SpecialUrlDetectorImplTest {
}

@Test
fun whenDuckChatIsDisabledAndIsDuckChatUrlThenDoNotReturnShouldLaunchDuckChatLink() = runTest {
whenever(mockDuckChat.isEnabled()).thenReturn(false)
whenever(mockDuckChat.isDuckChatUrl(any())).thenReturn(true)
fun whenIsNotDuckChatUrlThenDoNotReturnShouldLaunchDuckChatLink() = runTest {
whenever(mockDuckChat.isDuckChatUrl(any())).thenReturn(false)
val type = testee.determineType("https://example.com")
whenever(mockPackageManager.resolveActivity(any(), eq(PackageManager.MATCH_DEFAULT_ONLY))).thenReturn(null)
whenever(mockPackageManager.queryIntentActivities(any(), anyInt())).thenReturn(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import android.net.Uri
*/
interface DuckChat {
/**
* Checks whether DuckChat is enabled based on remote config flag.
* Checks whether DuckChat is enabled based on remote config flag and user preference.
* Uses a cached value - does not perform disk I/O.
*
* @return true if DuckChat is enabled, false otherwise.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,11 @@ interface DuckChatInternal : DuckChat {
* Returns whether dedicated Duck.ai input screen feature is available (its feature flag is enabled).
*/
fun isInputScreenFeatureAvailable(): Boolean

/**
* Checks whether DuckChat is enabled based on remote config flag.
*/
fun isDuckChatFeatureEnabled(): Boolean
}

enum class ChatState(val value: String) {
Expand Down Expand Up @@ -234,7 +239,7 @@ class RealDuckChat @Inject constructor(
moshi.adapter(DuckChatSettingJson::class.java)
}

private var isDuckChatEnabled = false
private var isDuckChatFeatureEnabled = false
private var isDuckAiInBrowserEnabled = false
private var duckAiInputScreen = false
private var isDuckChatUserEnabled = false
Expand Down Expand Up @@ -281,13 +286,17 @@ class RealDuckChat @Inject constructor(
}

override fun isEnabled(): Boolean {
return isDuckChatEnabled
return isDuckChatFeatureEnabled && isDuckChatUserEnabled
}

override fun isInputScreenFeatureAvailable(): Boolean {
return duckAiInputScreen
}

override fun isDuckChatFeatureEnabled(): Boolean {
return isDuckChatFeatureEnabled
}

override fun observeEnableDuckChatUserSetting(): Flow<Boolean> {
return duckChatFeatureRepository.observeDuckChatUserEnabled()
}
Expand Down Expand Up @@ -491,6 +500,8 @@ class RealDuckChat @Inject constructor(
}

override fun isDuckChatUrl(uri: Uri): Boolean {
if (!isDuckChatFeatureEnabled) return false

if (isDuckChatBang(uri)) return true

if (uri.host != DUCKDUCKGO_HOST) {
Expand Down Expand Up @@ -524,7 +535,7 @@ class RealDuckChat @Inject constructor(
private fun cacheConfig() {
appCoroutineScope.launch(dispatchers.io()) {
val featureEnabled = duckChatFeature.self().isEnabled()
isDuckChatEnabled = featureEnabled
isDuckChatFeatureEnabled = featureEnabled
_showSettings.value = featureEnabled
isDuckAiInBrowserEnabled = duckChatFeature.duckAiButtonInBrowser().isEnabled()
duckAiInputScreen = duckChatFeature.duckAiInputScreen().isEnabled()
Expand Down Expand Up @@ -553,16 +564,16 @@ class RealDuckChat @Inject constructor(
private suspend fun cacheUserSettings() = withContext(dispatchers.io()) {
isDuckChatUserEnabled = duckChatFeatureRepository.isDuckChatUserEnabled()

val showInputScreen = isInputScreenFeatureAvailable() && isDuckChatEnabled && isDuckChatUserEnabled &&
val showInputScreen = isInputScreenFeatureAvailable() && isDuckChatFeatureEnabled && isDuckChatUserEnabled &&
experimentalThemingDataStore.isSingleOmnibarEnabled.value && duckChatFeatureRepository.isInputScreenUserSettingEnabled()
_showInputScreen.emit(showInputScreen)

val showInBrowserMenu = duckChatFeatureRepository.shouldShowInBrowserMenu() &&
isDuckChatEnabled && isDuckChatUserEnabled
isDuckChatFeatureEnabled && isDuckChatUserEnabled
_showInBrowserMenu.emit(showInBrowserMenu)

val showInAddressBar = duckChatFeatureRepository.shouldShowInAddressBar() &&
isDuckChatEnabled && isDuckChatUserEnabled && isAddressBarEntryPointEnabled
isDuckChatFeatureEnabled && isDuckChatUserEnabled && isAddressBarEntryPointEnabled
_showInAddressBar.emit(showInAddressBar)

val showOmnibarShortcutInAllStates = showInAddressBar && isDuckAiInBrowserEnabled
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ class RealDuckChatJSHelper @Inject constructor(
): JsCallbackData {
val jsonPayload = JSONObject().apply {
put(PLATFORM, ANDROID)
put(IS_HANDOFF_ENABLED, duckChat.isEnabled())
put(IS_HANDOFF_ENABLED, duckChat.isDuckChatFeatureEnabled())
put(PAYLOAD, runBlocking { dataStore.fetchAndClearUserPreferences() })
}
return JsCallbackData(jsonPayload, featureName, method, id)
Expand All @@ -124,7 +124,7 @@ class RealDuckChatJSHelper @Inject constructor(
): JsCallbackData {
val jsonPayload = JSONObject().apply {
put(PLATFORM, ANDROID)
put(IS_HANDOFF_ENABLED, duckChat.isEnabled())
put(IS_HANDOFF_ENABLED, duckChat.isDuckChatFeatureEnabled())
put(SUPPORTS_CLOSING_AI_CHAT, true)
put(SUPPORTS_OPENING_SETTINGS, true)
put(SUPPORTS_NATIVE_CHAT_INPUT, false)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,13 +133,39 @@ class RealDuckChatTest {
}

@Test
fun whenDuckChatIsEnabledThenReturnTrue() = runTest {
fun whenDuckChatFeatureEnabledAndUserEnabledThenIsEnabledReturnsTrue() = runTest {
duckChatFeature.self().setRawStoredState(State(true))
whenever(mockDuckChatFeatureRepository.isDuckChatUserEnabled()).thenReturn(true)

testee.onPrivacyConfigDownloaded()

assertTrue(testee.isEnabled())
}

@Test
fun whenDuckChatIsDisabledThenReturnFalse() = runTest {
fun whenDuckChatFeatureDisabledAndUserEnabledThenIsEnabledReturnsFalse() = runTest {
duckChatFeature.self().setRawStoredState(State(false))
whenever(mockDuckChatFeatureRepository.isDuckChatUserEnabled()).thenReturn(true)

testee.onPrivacyConfigDownloaded()

assertFalse(testee.isEnabled())
}

@Test
fun whenDuckChatFeatureEnabledAndUserDisabledThenIsEnabledReturnsFalse() = runTest {
duckChatFeature.self().setRawStoredState(State(true))
whenever(mockDuckChatFeatureRepository.isDuckChatUserEnabled()).thenReturn(false)

testee.onPrivacyConfigDownloaded()

assertFalse(testee.isEnabled())
}

@Test
fun whenDuckChatFeatureDisabledAndUserDisabledThenIsEnabledReturnsFalse() = runTest {
duckChatFeature.self().setRawStoredState(State(false))
whenever(mockDuckChatFeatureRepository.isDuckChatUserEnabled()).thenReturn(false)

testee.onPrivacyConfigDownloaded()

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

@Test
fun whenDuckChatFeatureDisabledThenIsDuckChatUrlReturnsFalse() {
duckChatFeature.self().setRawStoredState(State(enable = false))
testee.onPrivacyConfigDownloaded()

assertFalse(testee.isDuckChatUrl("https://duckduckgo.com/?ia=chat".toUri()))
}

@Test
fun whenWasOpenedBeforeQueriedThenRepoStateIsReturned() = runTest {
whenever(mockDuckChatFeatureRepository.wasOpenedBefore()).thenReturn(true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,12 +74,12 @@ class RealDuckChatJSHelperTest {
}

@Test
fun whenGetAIChatNativeHandoffDataAndDuckChatEnabledThenReturnJsCallbackDataWithDuckChatEnabled() = runTest {
fun whenGetAIChatNativeHandoffDataAndDuckChatFeatureEnabledThenReturnJsCallbackDataWithDuckChatEnabled() = runTest {
val featureName = "aiChat"
val method = "getAIChatNativeHandoffData"
val id = "123"

whenever(mockDuckChat.isEnabled()).thenReturn(true)
whenever(mockDuckChat.isDuckChatFeatureEnabled()).thenReturn(true)
whenever(mockDataStore.fetchAndClearUserPreferences()).thenReturn("preferences")

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

@Test
fun whenGetAIChatNativeHandoffDataAndDuckChatDisabledThenReturnJsCallbackDataWithDuckChatDisabled() = runTest {
fun whenGetAIChatNativeHandoffDataAndDuckChatFeatureDisabledThenReturnJsCallbackDataWithDuckChatDisabled() = runTest {
val featureName = "aiChat"
val method = "getAIChatNativeHandoffData"
val id = "123"

whenever(mockDuckChat.isEnabled()).thenReturn(false)
whenever(mockDuckChat.isDuckChatFeatureEnabled()).thenReturn(false)
whenever(mockDataStore.fetchAndClearUserPreferences()).thenReturn("preferences")

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

whenever(mockDuckChat.isEnabled()).thenReturn(true)
whenever(mockDuckChat.isDuckChatFeatureEnabled()).thenReturn(true)
whenever(mockDataStore.fetchAndClearUserPreferences()).thenReturn(null)

val result = testee.processJsCallbackMessage(featureName, method, id, null)
Expand Down Expand Up @@ -159,12 +159,12 @@ class RealDuckChatJSHelperTest {
}

@Test
fun whenGetAIChatNativeConfigValuesAndDuckChatEnabledThenReturnJsCallbackDataWithDuckChatEnabled() = runTest {
fun whenGetAIChatNativeConfigValuesAndDuckChatFeatureEnabledThenReturnJsCallbackDataWithDuckChatEnabled() = runTest {
val featureName = "aiChat"
val method = "getAIChatNativeConfigValues"
val id = "123"

whenever(mockDuckChat.isEnabled()).thenReturn(true)
whenever(mockDuckChat.isDuckChatFeatureEnabled()).thenReturn(true)

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

Expand All @@ -186,12 +186,12 @@ class RealDuckChatJSHelperTest {
}

@Test
fun whenGetAIChatNativeConfigValuesAndDuckChatDisabledThenReturnJsCallbackDataWithDuckChatDisabled() = runTest {
fun whenGetAIChatNativeConfigValuesAndDuckChatFeatureDisabledThenReturnJsCallbackDataWithDuckChatDisabled() = runTest {
val featureName = "aiChat"
val method = "getAIChatNativeConfigValues"
val id = "123"

whenever(mockDuckChat.isEnabled()).thenReturn(false)
whenever(mockDuckChat.isDuckChatFeatureEnabled()).thenReturn(false)

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

Expand Down Expand Up @@ -350,7 +350,7 @@ class RealDuckChatJSHelperTest {
val method = "getAIChatNativeConfigValues"
val id = "123"

whenever(mockDuckChat.isEnabled()).thenReturn(true)
whenever(mockDuckChat.isDuckChatFeatureEnabled()).thenReturn(true)
whenever(mockDuckChat.isImageUploadEnabled()).thenReturn(true)

val result = testee.processJsCallbackMessage(featureName, method, id, null)
Expand Down
Loading