Skip to content
Permalink

Comparing changes

This is a direct comparison between two commits made in this repository or its related repositories. View the default comparison for this range or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: duckduckgo/Android
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: adbca096ad6d35b00775f2220e696dcbc359327f
Choose a base ref
..
head repository: duckduckgo/Android
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: c1eeb48248a75841f890319d8a105ae9c7efd0e2
Choose a head ref
Showing with 18,072 additions and 23,638 deletions.
  1. +1 −0 app/src/androidTest/java/com/duckduckgo/app/browser/BrowserTabViewModelTest.kt
  2. +4 −0 app/src/androidTest/java/com/duckduckgo/app/cta/ui/CtaViewModelTest.kt
  3. +1 −0 app/src/main/java/com/duckduckgo/app/browser/webview/MaliciousSiteBlockerWebViewIntegration.kt
  4. +16 −0 app/src/main/java/com/duckduckgo/app/cta/ui/CtaViewModel.kt
  5. +2 −0 app/src/main/java/com/duckduckgo/app/onboarding/store/OnboardingStore.kt
  6. +9 −0 app/src/main/java/com/duckduckgo/app/onboarding/store/OnboardingStoreImpl.kt
  7. +1 −0 app/src/main/java/com/duckduckgo/app/pixels/AppPixelName.kt
  8. +7 −8 app/src/main/java/com/duckduckgo/app/tabs/ui/TabSwitcherActivity.kt
  9. +8 −10 app/src/main/java/com/duckduckgo/app/tabs/ui/TabSwitcherAdapter.kt
  10. +18 −9 app/src/main/java/com/duckduckgo/app/tabs/ui/TabSwitcherViewModel.kt
  11. +59 −0 app/src/main/res/layout/item_tab_switcher_animation_info_panel.xml
  12. +0 −27 app/src/main/res/layout/item_tracker_animation_info_panel.xml
  13. +3 −0 app/src/test/java/com/duckduckgo/app/cta/ui/OnboardingDaxDialogTests.kt
  14. +7 −6 app/src/test/java/com/duckduckgo/app/tabs/ui/TabSwitcherViewModelTest.kt
  15. +1 −1 app/version/version.properties
  16. +2,661 −3,838 node_modules/@duckduckgo/content-scope-scripts/build/android/autofillPasswordImport.js
  17. +6,463 −9,532 node_modules/@duckduckgo/content-scope-scripts/build/android/brokerProtection.js
  18. +7,391 −10,179 node_modules/@duckduckgo/content-scope-scripts/build/android/contentScope.js
  19. +2 −2 package-lock.json
  20. +1 −1 package.json
  21. +1 −0 pir/pir-internal/build.gradle
  22. +516 −0 pir/pir-internal/schemas/com.duckduckgo.pir.internal.store.PirDatabase/3.json
  23. +3 −0 pir/pir-internal/src/main/AndroidManifest.xml
  24. +94 −12 pir/pir-internal/src/main/java/com/duckduckgo/pir/internal/component/PirActionsRunner.kt
  25. +10 −1 pir/pir-internal/src/main/java/com/duckduckgo/pir/internal/component/PirActionsRunnerFactory.kt
  26. +9 −0 pir/pir-internal/src/main/java/com/duckduckgo/pir/internal/di/PirModule.kt
  27. +89 −0 pir/pir-internal/src/main/java/com/duckduckgo/pir/internal/pixels/PirPixel.kt
  28. +76 −0 pir/pir-internal/src/main/java/com/duckduckgo/pir/internal/pixels/PirPixelInterceptor.kt
  29. +204 −0 pir/pir-internal/src/main/java/com/duckduckgo/pir/internal/pixels/PirPixelSender.kt
  30. +82 −3 pir/pir-internal/src/main/java/com/duckduckgo/pir/internal/scan/PirScan.kt
  31. +2 −1 pir/pir-internal/src/main/java/com/duckduckgo/pir/internal/service/PirForegroundScanService.kt
  32. +2 −1 pir/pir-internal/src/main/java/com/duckduckgo/pir/internal/service/PirScheduledScanWorker.kt
  33. +26 −0 pir/pir-internal/src/main/java/com/duckduckgo/pir/internal/settings/PirDevSettingsActivity.kt
  34. +82 −0 pir/pir-internal/src/main/java/com/duckduckgo/pir/internal/settings/PirScanResultsActivity.kt
  35. +7 −1 pir/pir-internal/src/main/java/com/duckduckgo/pir/internal/store/PirDatabase.kt
  36. +56 −3 pir/pir-internal/src/main/java/com/duckduckgo/pir/internal/store/PirRepository.kt
  37. +44 −0 pir/pir-internal/src/main/java/com/duckduckgo/pir/internal/store/db/ScanLogDao.kt
  38. +47 −0 pir/pir-internal/src/main/java/com/duckduckgo/pir/internal/store/db/ScanLogEntities.kt
  39. +12 −3 pir/pir-internal/src/main/java/com/duckduckgo/pir/internal/store/db/ScanResultsDao.kt
  40. +46 −0 pir/pir-internal/src/main/res/layout/activity_pir_internal_scan_results.xml
  41. +7 −0 pir/pir-internal/src/main/res/layout/activity_pir_internal_settings.xml
  42. +2 −0 pir/pir-internal/src/main/res/values/donottranslate.xml
Original file line number Diff line number Diff line change
@@ -603,6 +603,7 @@ class BrowserTabViewModelTest {
subscriptions = mock(),
duckPlayer = mockDuckPlayer,
brokenSitePrompt = mockBrokenSitePrompt,
userBrowserProperties = mockUserBrowserProperties,
)

val siteFactory = SiteFactoryImpl(
Original file line number Diff line number Diff line change
@@ -49,6 +49,7 @@ import com.duckduckgo.app.trackerdetection.model.TrackerType
import com.duckduckgo.app.trackerdetection.model.TrackingEvent
import com.duckduckgo.app.widget.ui.WidgetCapabilities
import com.duckduckgo.brokensite.api.BrokenSitePrompt
import com.duckduckgo.browser.api.UserBrowserProperties
import com.duckduckgo.common.test.CoroutineTestRule
import com.duckduckgo.common.test.InstantSchedulersRule
import com.duckduckgo.duckplayer.api.DuckPlayer
@@ -114,6 +115,8 @@ class CtaViewModelTest {

private val mockBrokenSitePrompt: BrokenSitePrompt = mock()

private val mockUserBrowserProperties: UserBrowserProperties = mock()

private val requiredDaxOnboardingCtas: List<CtaId> = listOf(
CtaId.DAX_INTRO,
CtaId.DAX_DIALOG_SERP,
@@ -166,6 +169,7 @@ class CtaViewModelTest {
subscriptions = mockSubscriptions,
duckPlayer = mockDuckPlayer,
brokenSitePrompt = mockBrokenSitePrompt,
userBrowserProperties = mockUserBrowserProperties,
)
}

Original file line number Diff line number Diff line change
@@ -285,6 +285,7 @@ class RealMaliciousSiteBlockerWebViewIntegration @Inject constructor(
}

override fun onPageLoadStarted(url: String) {
if (!isEnabled()) return
/* onPageLoadStarted is often called after shouldOverride/shouldIntercept, therefore, if the URL
* is already stored, we don't clear the processedUrls map to avoid re-checking the URL for the same
* page load.
16 changes: 16 additions & 0 deletions app/src/main/java/com/duckduckgo/app/cta/ui/CtaViewModel.kt
Original file line number Diff line number Diff line change
@@ -35,12 +35,14 @@ import com.duckduckgo.app.onboarding.store.OnboardingStore
import com.duckduckgo.app.onboarding.store.UserStageStore
import com.duckduckgo.app.onboarding.store.daxOnboardingActive
import com.duckduckgo.app.onboarding.ui.page.extendedonboarding.ExtendedOnboardingFeatureToggles
import com.duckduckgo.app.pixels.AppPixelName
import com.duckduckgo.app.privacy.db.UserAllowListRepository
import com.duckduckgo.app.settings.db.SettingsDataStore
import com.duckduckgo.app.statistics.pixels.Pixel
import com.duckduckgo.app.tabs.model.TabRepository
import com.duckduckgo.app.widget.ui.WidgetCapabilities
import com.duckduckgo.brokensite.api.BrokenSitePrompt
import com.duckduckgo.browser.api.UserBrowserProperties
import com.duckduckgo.common.utils.DispatcherProvider
import com.duckduckgo.di.scopes.AppScope
import com.duckduckgo.duckplayer.api.DuckPlayer
@@ -80,6 +82,7 @@ class CtaViewModel @Inject constructor(
private val subscriptions: Subscriptions,
private val duckPlayer: DuckPlayer,
private val brokenSitePrompt: BrokenSitePrompt,
private val userBrowserProperties: UserBrowserProperties,
) {
@ExperimentalCoroutinesApi
@VisibleForTesting
@@ -142,6 +145,18 @@ class CtaViewModel @Inject constructor(
if (cta is BrokenSitePromptDialogCta) {
brokenSitePrompt.ctaShown()
}

// Temporary pixel
val isVisitSiteSuggestionsCta = cta is DaxBubbleCta.DaxIntroVisitSiteOptionsCta || cta is OnboardingDaxDialogCta.DaxSiteSuggestionsCta
if (isVisitSiteSuggestionsCta) {
if (userBrowserProperties.daysSinceInstalled() <= MIN_DAYS_TO_COUNT_ONBOARDING_CTA_SHOWN) {
val count = onboardingStore.visitSiteCtaDisplayCount ?: 0
pixel.fire(AppPixelName.ONBOARDING_VISIT_SITE_CTA_SHOWN, mapOf("count" to count.toString()))
onboardingStore.visitSiteCtaDisplayCount = count + 1
} else {
onboardingStore.clearVisitSiteCtaDisplayCount()
}
}
}
}

@@ -436,5 +451,6 @@ class CtaViewModel @Inject constructor(

companion object {
private const val MAX_TABS_OPEN_FIRE_EDUCATION = 2
private const val MIN_DAYS_TO_COUNT_ONBOARDING_CTA_SHOWN = 3
}
}
Original file line number Diff line number Diff line change
@@ -20,9 +20,11 @@ import com.duckduckgo.app.cta.ui.DaxBubbleCta.DaxDialogIntroOption

interface OnboardingStore {
var onboardingDialogJourney: String?
var visitSiteCtaDisplayCount: Int

@Deprecated(message = "Parameter used for a temporary pixel")
fun getSearchOptions(): List<DaxDialogIntroOption>
fun getSitesOptions(): List<DaxDialogIntroOption>
fun getExperimentSearchOptions(): List<DaxDialogIntroOption>
fun clearVisitSiteCtaDisplayCount()
}
Original file line number Diff line number Diff line change
@@ -35,6 +35,10 @@ class OnboardingStoreImpl @Inject constructor(
get() = preferences.getString(ONBOARDING_JOURNEY, null)
set(dialogJourney) = preferences.edit { putString(ONBOARDING_JOURNEY, dialogJourney) }

override var visitSiteCtaDisplayCount: Int
get() = preferences.getInt(VISIT_SITE_CTA_DISPLAY_COUNT, 0)
set(count) = preferences.edit { putInt(VISIT_SITE_CTA_DISPLAY_COUNT, count) }

override fun getSearchOptions(): List<DaxDialogIntroOption> {
val country = Locale.getDefault().country
val language = Locale.getDefault().language
@@ -207,8 +211,13 @@ class OnboardingStoreImpl @Inject constructor(
)
}

override fun clearVisitSiteCtaDisplayCount() {
preferences.edit { remove(VISIT_SITE_CTA_DISPLAY_COUNT) }
}

companion object {
const val FILENAME = "com.duckduckgo.app.onboarding.settings"
const val ONBOARDING_JOURNEY = "onboardingJourney"
const val VISIT_SITE_CTA_DISPLAY_COUNT = "visitSiteCtaDisplayCount"
}
}
Original file line number Diff line number Diff line change
@@ -51,6 +51,7 @@ enum class AppPixelName(override val pixelName: String) : Pixel.PixelName {
ONBOARDING_DAX_ALL_CTA_HIDDEN("m_odc_h"),
ONBOARDING_DAX_CTA_OK_BUTTON("m_odc_ok"),
ONBOARDING_DAX_CTA_CANCEL_BUTTON("m_onboarding_dax_cta_cancel"),
ONBOARDING_VISIT_SITE_CTA_SHOWN("onboarding_visit_site_cta_shown"),

BROWSER_MENU_ALLOWLIST_ADD("mb_wla"),
BROWSER_MENU_ALLOWLIST_REMOVE("mb_wlr"),
Original file line number Diff line number Diff line change
@@ -225,6 +225,11 @@ class TabSwitcherActivity : DuckDuckGoActivity(), TabSwitcherListener, Coroutine
if (noTabSelected && tabSwitcherItems.isNotEmpty()) {
updateTabGridItemDecorator(tabSwitcherItems.last().id)
}

if (firstTimeLoadingTabsList) {
firstTimeLoadingTabsList = false
scrollToActiveTab()
}
}
viewModel.activeTab.observe(this) { tab ->
if (tab != null && tab.tabId != tabItemDecorator.tabSwitcherItemId && !tab.deletable) {
@@ -271,13 +276,7 @@ class TabSwitcherActivity : DuckDuckGoActivity(), TabSwitcherListener, Coroutine
tabsAdapter.onLayoutTypeChanged(layoutType)
tabTouchHelper.onLayoutTypeChanged(layoutType)

if (firstTimeLoadingTabsList) {
firstTimeLoadingTabsList = false

scrollToShowCurrentTab()
} else {
scrollToPreviousCenterOffset(centerOffsetPercent)
}
scrollToPreviousCenterOffset(centerOffsetPercent)

tabsRecycler.show()
}
@@ -336,7 +335,7 @@ class TabSwitcherActivity : DuckDuckGoActivity(), TabSwitcherListener, Coroutine
tabsAdapter.updateData(tabs)
}

private fun scrollToShowCurrentTab() {
private fun scrollToActiveTab() {
val index = tabsAdapter.adapterPositionForTab(selectedTabId)
if (index != -1) {
scrollToPosition(index)
18 changes: 8 additions & 10 deletions app/src/main/java/com/duckduckgo/app/tabs/ui/TabSwitcherAdapter.kt
Original file line number Diff line number Diff line change
@@ -37,7 +37,7 @@ import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
import com.duckduckgo.app.browser.R
import com.duckduckgo.app.browser.databinding.ItemTabGridBinding
import com.duckduckgo.app.browser.databinding.ItemTabListBinding
import com.duckduckgo.app.browser.databinding.ItemTrackerAnimationInfoPanelBinding
import com.duckduckgo.app.browser.databinding.ItemTabSwitcherAnimationInfoPanelBinding
import com.duckduckgo.app.browser.favicon.FaviconManager
import com.duckduckgo.app.browser.tabpreview.WebViewPreviewPersister
import com.duckduckgo.app.browser.tabs.adapter.TabSwitcherItemDiffCallback
@@ -91,15 +91,15 @@ class TabSwitcherAdapter(
return when (viewType) {
GRID_TAB -> {
val binding = ItemTabGridBinding.inflate(inflater, parent, false)
return TabSwitcherViewHolder.GridTabViewHolder(binding)
TabSwitcherViewHolder.GridTabViewHolder(binding)
}
LIST_TAB -> {
val binding = ItemTabListBinding.inflate(inflater, parent, false)
return TabSwitcherViewHolder.ListTabViewHolder(binding)
TabSwitcherViewHolder.ListTabViewHolder(binding)
}
TRACKER_ANIMATION_TILE_INFO_PANEL -> {
val binding = ItemTrackerAnimationInfoPanelBinding.inflate(inflater, parent, false)
return TabSwitcherViewHolder.TrackerAnimationInfoPanelViewHolder(binding)
val binding = ItemTabSwitcherAnimationInfoPanelBinding.inflate(inflater, parent, false)
TabSwitcherViewHolder.TrackerAnimationInfoPanelViewHolder(binding)
}
else -> throw IllegalArgumentException("Unknown viewType: $viewType")
}
@@ -131,15 +131,13 @@ class TabSwitcherAdapter(
is TabSwitcherViewHolder.TrackerAnimationInfoPanelViewHolder -> {
val trackerAnimationInfoPanel = list[position] as TabSwitcherItem.TrackerAnimationInfoPanel

holder.binding.trackerCountInfoPanel.infoPanelText.setTextColor(holder.itemView.context.getColor(CommonR.color.black84))

trackerCountAnimator.animateTrackersBlockedCountView(
context = holder.binding.root.context,
stringRes = R.string.trackersBlockedInTheLast7days,
totalTrackerCount = trackerAnimationInfoPanel.trackerCount,
trackerTextView = holder.binding.trackerCountInfoPanel.infoPanelText,
trackerTextView = holder.binding.infoPanelText,
)
holder.binding.trackerCountInfoPanel.setOnClickListener {
holder.binding.root.setOnClickListener {
onAnimationTileCloseClickListener?.invoke()
}
}
@@ -408,7 +406,7 @@ class TabSwitcherAdapter(
) : TabSwitcherViewHolder(binding.root), TabViewHolder

data class TrackerAnimationInfoPanelViewHolder(
val binding: ItemTrackerAnimationInfoPanelBinding,
val binding: ItemTabSwitcherAnimationInfoPanelBinding,
) : TabSwitcherViewHolder(binding.root)
}
}
Original file line number Diff line number Diff line change
@@ -48,12 +48,17 @@ import com.duckduckgo.di.scopes.ActivityScope
import com.duckduckgo.duckchat.api.DuckChat
import com.duckduckgo.duckchat.impl.DuckChatPixelName
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch

@OptIn(FlowPreview::class)
@ContributesViewModel(ActivityScope::class)
class TabSwitcherViewModel @Inject constructor(
private val tabRepository: TabRepository,
@@ -68,16 +73,20 @@ class TabSwitcherViewModel @Inject constructor(
private val tabSwitcherDataStore: TabSwitcherDataStore,
) : ViewModel() {

val tabSwitcherItems: LiveData<List<TabSwitcherItem>> = tabRepository.liveTabs.switchMap { tabEntities ->
// TODO use test framework to determine whether to show tracker animation tile
liveData {
if (tabSwitcherAnimationFeature.self().isEnabled()) {
collectTabItemsWithOptionalAnimationTile(tabEntities)
} else {
val tabItems = tabEntities.map { Tab(it) }
emit(tabItems)
val tabSwitcherItems: LiveData<List<TabSwitcherItem>> = tabRepository.flowTabs
.debounce(100.milliseconds)
.conflate()
.asLiveData()
.switchMap { tabEntities ->
// TODO use test framework to determine whether to show tracker animation tile
liveData {
if (tabSwitcherAnimationFeature.self().isEnabled()) {
collectTabItemsWithOptionalAnimationTile(tabEntities)
} else {
val tabItems = tabEntities.map { Tab(it) }
emit(tabItems)
}
}
}
}

val activeTab = tabRepository.liveSelectedTab
59 changes: 59 additions & 0 deletions app/src/main/res/layout/item_tab_switcher_animation_info_panel.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright (c) 2025 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.
-->

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/item_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="@dimen/keyline_2"
android:layout_marginTop="@dimen/keyline_2"
android:layout_marginBottom="@dimen/keyline_4"
android:background="@drawable/background_animated_tile">

<com.airbnb.lottie.LottieAnimationView
android:id="@+id/infoPanelAnimatedImage"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginStart="@dimen/keyline_3"
android:layout_marginTop="@dimen/keyline_2"
android:layout_marginBottom="@dimen/keyline_2"
android:importantForAccessibility="no"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:lottie_autoPlay="true"
app:lottie_rawRes="@raw/shield_tabswitcher"
tools:srcCompat="@drawable/ic_info_panel_info" />

<com.duckduckgo.common.ui.view.text.DaxTextView
android:id="@+id/infoPanelText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/keyline_2"
android:layout_marginTop="@dimen/keyline_4"
android:layout_marginEnd="@dimen/keyline_4"
android:layout_marginBottom="@dimen/keyline_4"
android:textColor="@color/black84"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/infoPanelAnimatedImage"
app:layout_constraintTop_toTopOf="parent"
app:typography="body2"
tools:text="396 trackers blocked in the last 7 days" />

</androidx.constraintlayout.widget.ConstraintLayout>
27 changes: 0 additions & 27 deletions app/src/main/res/layout/item_tracker_animation_info_panel.xml

This file was deleted.

Original file line number Diff line number Diff line change
@@ -38,6 +38,7 @@ import com.duckduckgo.app.statistics.pixels.Pixel
import com.duckduckgo.app.tabs.model.TabRepository
import com.duckduckgo.app.widget.ui.WidgetCapabilities
import com.duckduckgo.brokensite.api.BrokenSitePrompt
import com.duckduckgo.browser.api.UserBrowserProperties
import com.duckduckgo.common.test.CoroutineTestRule
import com.duckduckgo.duckplayer.api.DuckPlayer
import com.duckduckgo.feature.toggles.api.Toggle
@@ -76,6 +77,7 @@ class OnboardingDaxDialogTests {
private val extendedOnboardingFeatureToggles: ExtendedOnboardingFeatureToggles = mock()
private val mockDuckPlayer: DuckPlayer = mock()
private val mockBrokenSitePrompt: BrokenSitePrompt = mock()
private val mockUserBrowserProperties: UserBrowserProperties = mock()

val mockEnabledToggle: Toggle = org.mockito.kotlin.mock { on { it.isEnabled() } doReturn true }
val mockDisabledToggle: Toggle = org.mockito.kotlin.mock { on { it.isEnabled() } doReturn false }
@@ -100,6 +102,7 @@ class OnboardingDaxDialogTests {
subscriptions = mock(),
mockDuckPlayer,
mockBrokenSitePrompt,
mockUserBrowserProperties,
)
}

Loading