[pull] develop from duckduckgo:develop#350
Open
pull[bot] wants to merge 5681 commits intoRachelmorrell:developfrom
Open
[pull] develop from duckduckgo:develop#350pull[bot] wants to merge 5681 commits intoRachelmorrell:developfrom
pull[bot] wants to merge 5681 commits intoRachelmorrell:developfrom
Conversation
3bc8c57 to
ceb6fa4
Compare
Task/Issue URL: https://app.asana.com/1/137249556945/task/1213074060364697 ### Description This PR ensures that when attaching context in the FE side we are properly showing the favicon ### Steps to test this PR Enable contextualMode _Feature_ - [x] Open app and open a contextual chat - [x] Send a prompt, but don’t attach the context - [x] When in Duck.ai, tap on Attach Page Content - [x] Verify context is added, and favicon too. ### UI changes | Before | After | | ------ | ----- | <img width="1080" height="2400" alt="Screenshot_20260127_202941" src="https://github.com/user-attachments/assets/18b6f145-68cf-4996-bd75-aedaa28ce090" />|<img width="1080" height="2424" alt="Screenshot_20260209_224902" src="https://github.com/user-attachments/assets/8903ddd2-7179-4a6e-bbca-7503982f6194" />| <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Touches JS-bridge payload generation and adds bitmap-to-base64 encoding plus disk favicon lookup, which could affect performance/memory and the shape of data sent to the webview. > > **Overview** > Duck.ai contextual mode now passes the current `tabId` through JS callback handling and, when responding to `getAIChatPageContext`, looks up the tab/url favicon on disk and injects it into the returned `pageContext` as a `favicon` array containing a `data:image/png;base64,...` icon. > > This updates the contextual sheet to store/forward `sheetTabId`, extends `DuckChatJSHelper.processJsCallbackMessage` with a `tabId` parameter, and adjusts unit/instrumentation tests to cover the new argument and favicon enrichment behavior. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 76bd669. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> --- - To see the specific tasks where the Asana app for GitHub is being used, see below: - https://app.asana.com/0/0/1213074060364697
…in state (#7722) Task/Issue URL: https://app.asana.com/1/137249556945/task/1213228272275032 ### Description Updates SetUpSyncHandler validation so the "sync already on" error is only returned for sendToSetupSync; sendToSyncSettings now proceeds to open Sync settings regardless of sign-in state. ### Steps to test this PR - QA optional - [ ] Enable internal duck ai chat state: [Internal Testing Chat Sync Integration with Native Apps](https://app.asana.com/1/137249556945/task/1212472824864986?focus=true) - [ ] Enable sync - [ ] Visit duck ai chat settings and click on the `Manage` button under `Sync & Backup`; verify sync settings activity is launched <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Small, well-tested change to JS message validation/gating that only affects whether the Sync settings activity is launched. > > **Overview** > Updates `SetUpSyncHandler` validation so the **"sync already on"** error is only returned for `sendToSetupSync`; `sendToSyncSettings` now proceeds to open Sync settings regardless of sign-in state. > > Refactors method names into constants and expands tests to cover the new behavior (error when feature disabled for both methods, and activity launch for `sendToSyncSettings` when already signed in). > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit cafd87e. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> Co-authored-by: Craig Russell <1336281+CDRussell@users.noreply.github.com>
Task/Issue URL: https://app.asana.com/1/137249556945/task/1213226424542893 ### Description Update incorrect string in PIR initial scan started notification ### Steps to test this PR QA_optional <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > String-only UI copy change with no functional or data-handling impact; risk is limited to messaging/consistency. > > **Overview** > Updates the PIR foreground scan notification copy by changing `pirNotificationMessageInProgress` from “A scan is currently in progress.” to “Your scan is in progress…”, improving feedback when an initial scan starts. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 29c1d78. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
Task/Issue URL: https://app.asana.com/1/137249556945/task/1213213117376953 ### Description This PR adds a new destructive secondary button type. ### Steps to test this PR - [ ] Go to Settings -> Android Design System Preview - [ ] Tap on the Buttons tap and scroll down - [ ] Verify the new secondary destructive button type is visible and looks as expected ### UI changes | Light | Dark | | ------ | ----- | || --- - To see the specific tasks where the Asana app for GitHub is being used, see below: - https://app.asana.com/0/0/1213213117376953 - https://app.asana.com/0/0/1213213535164007 <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Low risk, mostly additive UI/theming resources and a new `DaxButton` subclass plus wiring in previews. Main risk is minor visual regressions or lint rule false positives/negatives due to the updated `DaxButtonStylingDetector` class list. > > **Overview** > Adds a new **destructive secondary** button variant to the design system: a `DaxButtonDestructiveSecondary` view, a new `ButtonType.DESTRUCTIVE_SECONDARY`, and theme/attribute wiring (`daxButtonDestructiveSecondary`) with dedicated stroke/text/ripple selectors and widget style. > > Updates the design-system preview layout to showcase the new button in small/large, icon, and disabled states, and extends the custom lint rule (`DaxButtonStylingDetector`) plus its tests to recognize the new button class (and updated `com.duckduckgo.common.ui.view.button.*` class names). > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 1371e45. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
Task/Issue URL: https://app.asana.com/1/137249556945/task/1213210377163129 ### Description * Don't try to open context menu until bookmark added botomSheet has disappeared ### Steps to test this PR _Feature 1_ - [ ] [Release tests](https://app.maestro.dev/project/proj_01htg54rdtfwx8rgbzv03cxkpf/maestro-test/app/app_01hkqhj1thevwtn9ym8a2ctn2r/upload/mupload_01kh6eembtfm6vpmdhhfptas7c?sort=name) are passing on Maestro ### UI changes n/a <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Test-only timing change; no production code or data flows are modified, with minimal risk beyond potentially masking a real UI hang if the prompt never dismisses. > > **Overview** > Stabilizes the Maestro release tests for adding/removing favorites from bookmarks by inserting an `extendedWaitUntil` step after tapping **add bookmark**. > > Both `favorites_bookmarks_add.yaml` and `favorites_bookmarks_delete.yaml` now wait up to 5s for the "Bookmark added" bottom-sheet/prompt to disappear before reopening the menu and continuing, reducing flakiness from UI timing/race conditions. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 45dd0d9. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
Task/Issue URL: https://app.asana.com/1/137249556945/task/1212358379355435 ### Description This PR adds pixels to all agreed entry points ### Steps to test this PR See task <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Primarily adds telemetry events and minor method renames; risk is limited to potentially incorrect/duplicated pixel firing or slightly altered context-validation behavior. > > **Overview** > Adds **new Duck.ai Contextual-mode telemetry** definitions and wiring, introducing count + daily pixels for contextual sheet open/dismiss/expand, session restoration, new-chat, summarize quick action, page-context placeholder shown/tapped, page-context attach/remove (native + frontend), prompt submission with/without context, and invalid/empty context collection. > > Hooks these pixels into the contextual UI flow (`DuckChatContextualViewModel`/`Fragment`) and JS bridge (`RealDuckChatJSHelper` adds `togglePageContextTelemetry`), and extends daily reporting to include the automatic page-context setting state plus firing enable/disable pixels when the setting is toggled. Tests are updated/added to verify the new pixel emissions and the expanded allowed JS method list. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit c32b5b8. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
Task/Issue URL: https://app.asana.com/1/137249556945/task/1213218785316169 ### Description This PR fixes the issue where sending a pixel shortly after enabling VPN may fail. ### Steps to test this PR - [x] Apply patch on https://app.asana.com/1/137249556945/task/1210448620621729 - [x] Fresh install - [x] Purchase a test subscription (Free Trial) - [x] Before it expires activate VPN - [x] Check in logcat that `subscription_free_trial_vpn_activation` pixel is fired with the new `platform` parameter ### No UI changes <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Small, localized change to pixel delivery behavior; risk is limited to analytics event timing/delivery for pixels using the new `enqueue` flag. > > **Overview** > Adds an `enqueue` flag to `SubscriptionPixel` and updates `SubscriptionPixelSenderImpl.fire` to *conditionally* send pixels via `pixelSender.enqueueFire` instead of immediate `fire`. > > Marks `FREE_TRIAL_VPN_ACTIVATION` to use the queued send path, reducing the chance the pixel is lost when VPN is enabled and network conditions are transient. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit daeb405. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
Task/Issue URL: https://app.asana.com/1/137249556945/task/1213074215455849 ### Description This PR adds a `singeTabFireDialog` feature flag and a site-specific data clearing capability check. It also adds a new `FireDialogOrigin` parameter to be able to customize the UI based on the origin. ### Steps to test this PR QA-optional <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Touches data-clearing UI entry points and feature-flag-driven dialog selection logic; incorrect flag priority or event handling could lead to showing the wrong dialog or inconsistent UI state during clearing. > > **Overview** > Adds a new remote-config feature flag, `singleTabFireDialog`, and wires it into `FireDialogProvider` so this path takes priority over `granularFireDialog`/`improvedDataClearingOptions` when choosing which fire dialog variant to show. > > Updates all fire-dialog entry points (browser, tab switcher, and settings screens) to pass a new `FireDialogOrigin` argument, and introduces a new `FireDialog` result event (`EVENT_ON_SINGLE_TAB_CLEAR_STARTED`) that `BrowserActivity` handles to update tab UI state. > > Extends `WebViewCapabilityChecker` with a `DeleteBrowsingData` capability and implements the check in `RealWebViewCapabilityChecker` using `WebViewFeature.DELETE_BROWSING_DATA`. Adds unit tests covering the new feature-flag priority logic in `FireDialogProviderImpl`. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 9ce1ed3. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
Task/Issue URL: https://app.asana.com/1/137249556945/task/1213217874770243 ### Description Implement showPillIcon method to set visibility programatically in SettingsListItem ### Steps to test this PR - [x] Install from branch - [x] Go to Settings > ADS Preview - [x] Tap on List items - [x] Check pill icons on Settings items look correct ### No UI changes <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Small UI-component API addition and preview wiring change; risk is limited to pill visibility/attribute behavior in settings list items. > > **Overview** > `SettingsListItem` now exposes `showPillIcon(Boolean)` to control the yellow pill visibility programmatically, and initialization was refactored to always set `pillText` while delegating visibility to the new method. > > The internal design-system preview for the settings list item was updated to default `pillIcon` to false in XML and then explicitly enable it in `SettingsListItemComponentViewHolder.bind` to demonstrate runtime toggling. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 1884783. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
Task/Issue URL: https://app.asana.com/1/137249556945/task/1213237094766900 ### Description Fixes flakey maestro test ### Steps to test this PR - qa optional <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Test-only changes that mainly swap a brittle text assertion for a stable view-id check and add an extra tap step; no production logic is affected. > > **Overview** > Stabilizes the `input_screen_search_mode_flow.yaml` Maestro test by adjusting the “add to favorites” sequence to explicitly re-tap the browser menu button after selecting “add bookmark”. > > Replaces a text-based assertion for the favorite (“reddit at DuckDuckGo”) with an ID-based assertion (`quickAccessFavicon` under `focusedFavourites`) to reduce flakiness from variable copy/UI content. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 0f7fc6b. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> --- - To see the specific tasks where the Asana app for GitHub is being used, see below: - https://app.asana.com/0/0/1213237094766900 Co-authored-by: Craig Russell <1336281+CDRussell@users.noreply.github.com>
Task/Issue URL: https://app.asana.com/1/137249556945/task/1211916932461025 ### Description This PR adds a new single tab burning dialog. ### Steps to test this PR - [x] Enable the `singleTabFireDialog` flag - [x] Tap on the Fire button - [x] Verify the new single tab burn dialog is shown - [x] Tap on Delete All button - [x] Verify that all tabs are cleared as usual - [x] Tap on the Fire button - [x] Tap on the Delete This Tab button - [x] Verify that the fire animation is played and dialog is dismissed (no data cleared yet) _Site data warning subtitle_ - [x] Open any website, tap the Fire button - [x] Verify the subtitle "Deleting site data can sign you out of accounts." is shown - [x] Dismiss the dialog, tap the Fire button again - [x] Verify the site data subtitle is still shown (2nd showing) - [x] Dismiss the dialog, tap the Fire button a 3rd time - [x] Verify the site data subtitle is no longer shown _Downloads warning subtitle_ - [x] Start a file download (e.g. a large file) - [x] While the download is in progress, tap the Fire button - [x] Verify the subtitle "This will cancel downloads in progress." is shown - [x] Wait for the download to finish, tap the Fire button again - [x] Verify the downloads subtitle is no longer shown _Duck AI subtitle_ - [x] Make sure the `imrovedDataClearing` feature flag is enabled - [x] Navigate to duck.ai in a tab - [x] Ensure "Duck AI Chats" is not selected in Fire button settings - [x] Tap the Fire button - [x] Verify the Duck AI subtitle is shown - [x] Now enable "Duck AI Chats" in Fire button settings - [x] Tap the Fire button again on the duck.ai tab - [x] Verify the Duck AI subtitle is not shown (clearing chats is already selected) _Combined subtitles:_ - [x] On a fresh install (shown count < 2), start a download, navigate to duck.ai - [x] Tap the Fire button - [x] Verify all three subtitle lines are shown, each on its own line ### UI changes | Light | Dark | | ------ | ----- | <img width="1080" height="2400" alt="image" src="https://github.com/user-attachments/assets/948c687b-2c8f-446b-91b2-c694955b48b9" />|<img width="1080" height="2400" alt="image" src="https://github.com/user-attachments/assets/e4c131d6-54fb-4528-9a7c-7d14264d8474" />| <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Adds a new Fire dialog variant and adjusts data-clearing event signaling (restart vs no-restart), which could affect clearing/restart UX paths. Single-tab clearing is introduced as a new API but is currently a no-op, reducing data-loss risk while still requiring careful end-to-end testing of dialog flows. > > **Overview** > Adds a new bottom-sheet `SingleTabFireDialog` (feature-flagged via `singleTabFireDialog`) with `Delete All` and `Delete This Tab` actions, dynamic subtitle messaging (site-data warning, downloads-in-progress warning, Duck.ai chat note), and animation/pixel tracking via `SingleTabFireDialogViewModel`. > > Updates fire-dialog event wiring to distinguish *clear with restart* vs *clear without restart* (`EVENT_CLEAR_WITHOUT_RESTART_STARTED`) and adds a completion event for single-tab clears; `BrowserActivity` now reacts by hiding the dialog appropriately and showing a “1 tab deleted” snackbar on completion. > > Extends `ManualDataClearing` with `clearSingleTabData(tabId)` (implemented as a logged no-op in `DataClearing` for now), adds a new `DataClearingWideEvent.EntryPoint.SINGLE_TAB_FIRE_DIALOG`, persists `singleTabFireDialogShownCount` in `SettingsDataStore`, and hardens `setAndPropagateUpFitsSystemWindows` to only traverse `View` parents. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit f915f32. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
…ectric (#7729) Task/Issue URL: https://app.asana.com/1/137249556945/task/1213242498673303 ### Description ### Steps to test this PR QA-optional ### No UI changes <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Test-only migration with lint-baseline churn; minimal production impact, but there’s some risk of behavior drift due to Robolectric vs device/runtime differences. > > **Overview** > Migrates `BrowserTabViewModelTest` and `BrowserWebViewClientTest` from instrumentation (`androidTest`) to Robolectric/JVM (`test`) by switching to `AndroidJUnit4` + `@Config(sdk = [34])`, replacing `InstrumentationRegistry` usage with `RuntimeEnvironment`, and dropping `@UiThreadTest`/`@SdkSuppress` gating. > > Adds a reusable `ValueCaptorObserver` test helper, and adjusts test setup to better match Robolectric behavior (e.g., explicitly populating `MimeTypeMap` mappings and making `TestWebView` report `progress=100`). The lint baseline is updated to reflect moved test file paths and to remove prior denylisted-API entries tied to the old instrumentation tests. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit cdfc264. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
- Automated content scope scripts dependency update This PR updates the content scope scripts dependency to the latest available version and copies the necessary files. Tests will only run if something has changed in the `node_modules/@duckduckgo/content-scope-scripts` folder. If only the package version has changed, there is no need to run the tests. If tests have failed, see https://app.asana.com/0/1202561462274611/1203986899650836/f for further information on what to do next. _`content-scope-scripts` folder update_ - [ ] All tests must pass - [ ] Privacy tests must pass _Only `content-scope-scripts` package update_ - [x] All tests must pass - [x] Privacy tests do not need to run <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Dependency bundle update adds a new AI chat history feature that reads browser `localStorage` and expands platform feature gating; behavior changes are mostly confined to content-scope scripts but can affect on-page injection and messaging. > > **Overview** > Updates the vendored `@duckduckgo/content-scope-scripts` Android builds to version `13.5.0`. > > This brings in new/expanded feature gating: adds `print` as a platform-specific feature (including Apple support lists) and introduces a new `android-ai-history` injection that loads a new `duckAiChatHistory` bundle which surfaces saved DuckAI chat metadata from `localStorage` via the messaging bridge. > > Also adjusts profile import parsing by allowing `stringToList` separators to be provided as regex strings (e.g., `"/…/"`) in the `autofillImport` and `brokerProtection` builds. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit f4eff2a. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> Co-authored-by: daxmobile <daxmobile@users.noreply.github.com>
…prevent the BrowserActivity's bundle size from increasing too much (#7668) Task/Issue URL: https://app.asana.com/1/137249556945/project/1211724162604201/task/1212878274586093?focus=true ### Description Set `saveEnabled=false` to various UI elements across multiple layout files to prevent unnecessary state saving inside the BrowserActivity to potential crashes from the bundle size increasing too much. All the changes are behing a new feature flag: `reduceBrowserTabBundleSize` ### Steps to test this PR _Verify UI elements function correctly_ - [x] Navigate through different screens and verify UI elements display correctly (onboarding, error screens - SSL warning, malicious site warning) to ensure they function properly ### UI changes No visual changes, this is a performance optimization that doesn't affect appearance <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Touches core browsing UI lifecycle/state restoration by disabling view state saving across many views; risk is mainly regressions in state restore or subtle UI behavior when the new flag is enabled. > > **Overview** > Adds a new internal remote-config toggle `reduceBrowserTabBundleSize` and, when enabled, disables Android view state saving (`isSaveEnabled = false`) across many `BrowserTabFragment` UI elements to reduce saved-instance bundle growth. > > This introduces helper methods like `disableViewStateSaving`/`disableStateSaving` on components (`BrowserNavigationBarView`, `Omnibar`/`OmnibarLayout`/`OmnibarView`, SSL and malicious-site warning layouts, `MessageCta`, and `NewTabPageView`) and wires them into fragment/view attach flows. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 9f86e74. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
Task/Issue URL: https://app.asana.com/1/137249556945/project/488551667048375/task/1213099842128498?focus=true ### Description Adds a utility class which will be used to load `sqlcipher` library in autofill (see stacked PRs) now and if it goes well could be extended outside of `autofill` (e.g., `PIR`) ### Steps to test this PR - QA optional since this is just introducing the capabilities but they aren't used yet. - Full testing will happen in a higher PR; wanted to break the large PR down a bit to make reviews easier <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Introduces new concurrency and timeout-based native library loading behavior (feature-flagged) which can affect secure storage initialization when adopted. Risk is moderated by defaulting to current behavior via a fallback path and adding unit tests for success/failure/timeout and concurrent callers. > > **Overview** > Adds a new `AutofillFeature.sqlCipherAsyncLoading()` toggle (default **enabled**) to control whether SqlCipher native library loading runs asynchronously or uses a synchronous fallback. > > Introduces `SqlCipherLibraryLoader`, a lazily-initialized singleton that loads `sqlcipher` via `LibraryLoader` with mutex-guarded single-init, supports multiple concurrent waiters, and returns explicit `Success`/`Timeout`/`Failure` results; includes Robolectric tests using a `LibraryLoader` shadow to cover async vs sync paths, failures, timeouts, and idempotent initialization. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit b6bbfdf. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
…ization (#7691) Task/Issue URL: https://app.asana.com/1/137249556945/task/1213119837878194 ### Description Updates autofill database initialization to utilize the new `SqlCipherLibraryLoader` - Logcat filter: `message~:"SqlCipher-Init: |Autofill-DB-Init: "` - Patches for testing the various error cases: [Testing patches](https://app.asana.com/1/137249556945/task/1213119837878196?focus=true) ### Steps to test this PR #### Update from previous version path - [x] Fresh install from **develop** - [x] Launch the app and save a password or two - [x] Install this branch over the top - [x] Launch the app and confirm the passwords are still accessible #### Fresh install path - [x] Fresh install from this branch - [x] Confirm in the logs that `Database created successfully` #### Test timeout scenario - [x] Apply the **timeout** patch - [x] Launch the app and confirm the app isn't blocked (UI is still responsive etc...) - [x] Wait for more than 10s until you see `SqlCipher library load failure - cannot create database: kotlinx.coroutines.TimeoutCancellationException` - [x] Try to access passwords and verify you get the device not supported messaging - [x] Kill and restart the app; this time try to access passwords within 10s. Confirm the passwords screen stays blank until it updates to say device not supported. #### Test slow loading scenario - [x] Remove current patch, and apply the **slow loading** patch - [x] Launch the app and confirm the app isn't blocked (UI is still responsive etc...) - [x] Wait for ~8s and verify you see `Database created successfully` and passwords then works #### Test failure scenario - [x] Remove the current patch, and apply the **failure** patch - [x] Launch the app and verify in logs you see `cannot create database:` - [x] Confirm passwords not being available is gracefully handled #### Feature flag disabled - [x] Remove the current patch and install the app with no local changes - [x] Update `autofill/sqlCipherAsyncLoading` to **disabled** - [x] Kill and restart the app - [x] Verify in logs you see `Starting synchronous library load on thread main` and `Database created successfully` <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Changes the initialization path for the encrypted autofill database and introduces new failure/timeout behavior that can return `null`, which could affect password availability if the loader or timing behaves unexpectedly. > > **Overview** > Updates autofill secure storage DB initialization to depend on the new `SqlCipherLibraryLoader` instead of loading `sqlcipher` in the factory `init`, and gates DB creation on `waitForLibraryLoad()` (returning `null` on timeout/failure) with more detailed `Autofill-DB-Init` logging. > > Adds a new `RealSecureStorageDatabaseFactoryTest` covering successful creation, library load timeout/failure, keystore-inaccessible behavior, instance caching, and concurrent callers only triggering a single initialization. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 9783923. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
…7694) Task/Issue URL: https://app.asana.com/1/137249556945/task/1213116557045964 ### Description Adds error pixels if there's a problem with the new async `sqlcipher` native library loading. Logcat filter: message~:`"SqlCipher-Init: |Autofill-DB-Init: |library_load_"` Patches for testing the various error cases: [Testing patches](https://app.asana.com/1/137249556945/task/1213119837878196?focus=true) ### Steps to test this PR #### Timeouts - [x] Apply the timeout patch and launch the app - [x] Wait 10s for timeout, then verify `Pixel sent: library_load_timeout_sqlcipher` in logs #### Timeouts - [x] Remove previous patch, and apply the error patch and launch the app - [x] Wait 10s for timeout, then verify `Pixel sent: library_load_failure_sqlcipher` in logs <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Adds telemetry-only behavior gated to async loading; no changes to the actual library loading flow beyond emitting pixels and updating tests. > > **Overview** > Adds two new *daily* error pixels (`library_load_timeout_sqlcipher`, `library_load_failure_sqlcipher`) to track sqlcipher native library async loading issues, including wiring them into `AutofillPixelNames` and data-cleaning rules. > > Updates `SqlCipherLibraryLoader.waitForLibraryLoad` to fire the appropriate pixel on timeout or exception **only when async loading is enabled**, and extends unit tests to verify pixels fire (or not) across timeout, failure, success, and sync-loading scenarios. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 60314cd. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
Task/Issue URL: https://app.asana.com/1/137249556945/task/1212922585263994 ### Description See attached task description ### Steps to test this PR https://app.asana.com/1/137249556945/task/1213230593027484 <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Adds a new `isBeta` field to an existing JS messaging response and a small UI label; main risk is backwards-compatibility for any consumers expecting the old `GetFeatureConfigResponse` shape. > > **Overview** > Adds a **beta indicator** for PIR in both the web dashboard config and the Android settings UI. > > The `GET_FEATURE_CONFIG` JS message response now includes a new `isBeta` boolean (currently always `true`), with tests updated to assert the new field. The PIR settings row also gets a "Beta" pill (`pillText`) and only shows the pill when PIR is in the dashboard-enabled state. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 5c56e2a. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
Task/Issue URL: https://app.asana.com/1/137249556945/project/1198194956794324/task/1213651965660200?focus=true ### Description Updates `url-predictor-android` from `0.3.13` to `0.3.15`. This version includes a fix for embedded newlines and tab characters in omnibar input being misclassified as a Navigate decision (triggering navigation) instead of a Search decision (triggering a search query). ### Steps to test this PR _Test_ - [x] In toggle input screen, select duck.ai input - [x] Input `https://example.com` + newline + `test` into the omnibar. It should open a DDG search, **not** navigate to example.com - [x] Paste text with an embedded newline where the URL comes second, e.g. `hello world` on line 1 and `https://example.com` on line 2 — should open a DDG search, not navigate - [ ] Paste text with a tab character followed by something URL-like, e.g. a tab then `example.com` — should open a DDG search, not navigate _Regression — normal navigation still works_ - [x] Type a plain URL in the omnibar (e.g. `duckduckgo.com`) and confirm it navigates to the site - [x] Type a full URL with scheme (e.g. `https://wikipedia.org`) and confirm it navigates directly - [x] Type a search query (e.g. `how do penguins sleep`) and confirm it opens a DDG search results page ### UI changes N/A — no UI changes.
Task/Issue URL: https://app.asana.com/1/137249556945/project/1211654189969294/task/1213651299034811 ### Description Adds a mic button on the duck.ai tab of the input screen that opens duck.ai directly in voice mode (`duck.ai/?mode=voice-mode`), giving users 1-click access to voice chat. - New `duckAiVoiceEntryPoint` sub-feature flag (`DefaultFeatureValue.INTERNAL` — off in production, on in internal builds) - New `openVoiceDuckChat()` on the `DuckChat` API → implemented in `RealDuckChat` by appending `mode=voice-mode` and forcing a new session - `InputScreenViewModel`: extended the voice button visibility `combine` block to be tab-aware; duck.ai tab + flag on → button follows `voiceInputAllowed` (text presence) rather than `VoiceSearchAvailability` - `InputScreenFragment`: both voice click handlers route to `viewModel.onVoiceEntryTapped()` on the duck.ai tab when flag is on - Pixel: `m_aichat_voice_entry_tapped` fired on tap - 7 new unit tests in `InputScreenViewModelTest` ### Steps to test this PR _Enable flag_ - [x] In internal build, enable `duckAiVoiceEntryPoint` under duck.ai feature flags _Duck.ai tab — empty field_ - [x] Open the input screen and switch to the duck.ai tab - [x] Mic button is visible (even if private voice search is disabled in settings) - [x] Tap the mic button → duck.ai opens with `?mode=voice-mode` in the URL, fresh session _Duck.ai tab — with text_ - [x] Type something in the input field → mic button disappears, send button appears - [x] Clear the field → mic button reappears _Search tab (unchanged)_ - [x] Switch to the search tab → mic button follows voice search availability as before - [x] Tap mic → system voice recognition launches (not duck.ai voice mode) _Flag off_ - [x] Disable `duckAiVoiceEntryPoint` → duck.ai tab mic button behaves as before (follows voice search availability, launches system voice recognition) ### UI changes | Before | After | | ------ | ----- | |(Upload before screenshot)|(Upload after screenshot)| --- > [!NOTE] > **Medium Risk** > Changes input-screen voice button behavior behind a new feature flag and adds a new DuckChat navigation path that forces a fresh session; risk is moderate due to UI/flow branching and URL/session handling. > > **Overview** > Adds a new, flag-gated *Duck.ai voice entry point* on the input screen: when on the Duck.ai tab and `duckAiVoiceEntryPoint` is enabled, the mic button opens Duck.ai with `mode=voice-mode` (forcing a new session) instead of launching system voice search. > > Introduces `DuckChat.openVoiceDuckChat()` (implemented in `RealDuckChat` via `mode=voice-mode` query param), updates voice button visibility logic in `InputScreenViewModel` to be tab-aware, and wires both voice click handlers in `InputScreenFragment` to the new behavior. Adds new pixels (`m_aichat_voice_entry_tapped_*`) plus a shared `fireCountAndDaily` helper, and expands unit test coverage for the new routing/visibility/session semantics. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 0ed7e9a. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup>
Task/Issue URL: https://app.asana.com/1/137249556945/project/1208671518894266/task/1213642299611483?focus=true ### Description Instead of skipping the workflow at the trigger level, the workflow now always runs but gates all jobs behind a check_changes job that inspects the diff. - If only `.md` or `.github/` files changed → all jobs are skipped (GitHub treats skipped as passing for branch protection) - If `ci.yml` itself changed → all jobs run normally, so regressions are caught before merging - If any code file changed → all jobs run normally ### Steps to test this PR QA optional: - Open a PR that only modifies an `.md` file → check_changes should pass, all other jobs should be skipped, PR should be mergeable - Open a PR that only modifies a `.github` workflow file (not `ci.yml`) → check_changes should pass, all other jobs should be skipped, PR should be mergeable - Open a PR that modifies `.github/workflows/ci.yml` → all jobs should run - Open a PR that modifies any source file → all jobs should run I tested all of these via #7962. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Modifies the CI workflow execution logic to conditionally skip most jobs based on diff contents, which could unintentionally reduce coverage if the change-detection logic is wrong. > > **Overview** > The CI workflow no longer uses trigger-level `paths-ignore`; instead it always starts and runs a new `check_changes` job that diffs the PR (or always allows `push`/`workflow_dispatch`) to decide whether checks should run. > > All existing jobs (`code_formatting`, `unit_tests`, `lint`, `android_tests`) now `need` `check_changes` and are gated by `if: needs.check_changes.outputs.should_run == 'true'`, so docs-only or non-`ci.yml` `.github/` changes skip the expensive checks while changes to `ci.yml` or any code still run the full suite. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 9ff5ad8. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
#7955) Task/Issue URL: https://app.asana.com/1/137249556945/project/488551667048375/task/1213635828556477?focus=true ### Description Pins the version of the gradler-profiler to v0.23.0 to stabilize our metrics. Refs gradle/gradle-profiler#739. ### Steps to test this PR No QA needed. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Low risk CI workflow change that only affects the nightly benchmark job by making the `gradleprofiler` install deterministic. > > **Overview** > Pins the GitHub Actions nightly build benchmark workflow to install `gradleprofiler` version `0.23.0` via SDKMAN instead of the latest version, to stabilize benchmark results. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit df240fc. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
Task/Issue URL: https://app.asana.com/1/137249556945/project/488551667048375/task/1213690753964717?focus=true ### Description Don't show subscription dialog for subscribers ### Steps to test this PR _Pre steps_ - [x] Apply patch on https://app.asana.com/1/137249556945/project/1209991789468715/task/1210448620621729?focus=true _Returning users who skip onboarding_ - [x] Fresh install - [x] Skip onboarding as a returning user (Skip onboarding as a returning user (I've been here before > Start Browsing) - [x] Go to Feature Flag Inventory and enable `privacyProCtaSkippedOnboarding` - [x] Purchase a test subscription - [x] Close the app - [x] Change the date in your device for >=7 days after today - [x] Open the app and check subscription onboarding dialog doesn't appear in a new tab page _Regular onboarding_ - [x] Set the device time back to normal - [x] Fresh install - [x] Don't skip onboarding and go to browser - [x] Purchase a test subscription - [x] Go through onboarding and check Subscription onboarding dialog doesn't appear after 'end onboarding dialog' in new tab page. ### No UI changes <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Changes onboarding CTA eligibility logic to depend on subscription status, which can affect when onboarding is considered complete and whether promo dialogs appear. Risk is moderate due to potential edge cases if `SubscriptionStatus` is incorrect or delayed. > > **Overview** > Prevents the Privacy Pro onboarding CTA/dialog from showing to users who already have an active subscription by additionally gating CTA availability on `subscriptions.getSubscriptionStatus()` (only show when status is `UNKNOWN`). > > Updates onboarding completion/required CTA logic and adjusts/adds unit tests to cover subscribed vs unsubscribed scenarios and to stub the new subscription-status dependency. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 9e0b7dd. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
…t from the privacy-config module to the new one (#7967) Task/Issue URL: https://app.asana.com/1/137249556945/project/1211724162604201/task/1213642872484684?focus=true ### Description Moved the `RequestBlocklist` interface and implementation from the `privacy-config` module to a new dedicated `request-interception` module. Updated the `RequestBlocklist.containedInBlocklist()` method signature to accept `Uri` parameters instead of `String` parameters, improving type safety and eliminating the need for string-based domain extraction. ### Steps to test this PR _Request Blocking Functionality_ - [x] Verify that request blocking still works correctly in the browser: https://privacy-test-pages.site/privacy-protections/request-blocklist/ - [x] CI passes - [x] com.duckduckgo.espresso.RequestBlocklistTest passes ### UI changes No UI changes - internal refactoring only <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Medium risk because it relocates request-blocking logic into a new module and changes `RequestBlocklist` to use `Uri` parameters, which could alter matching behavior or wiring if any call sites weren’t updated. > > **Overview** > Moves `RequestBlocklist` out of `privacy-config` into a new `request-interception` API/impl module, and wires the app to depend on it. > > Updates the `containedInBlocklist` API (and call sites/tests) to accept `Uri` instead of `String`, and adjusts `WebViewRequestInterceptor` and the blocklist implementation to use host-based matching (`baseHost`) rather than string domain extraction. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 680d0fe. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
Task/Issue URL: https://app.asana.com/1/137249556945/project/1200204095367872/task/1213720960821640?focus=true ### Description - Catches `IllegalArgumentException` when `dismiss` is called on a destroyed view (after a delay). ### Steps to test this PR - [ ] Visit a site - [ ] Tap the overflow menu and add a bookmark - [ ] Verify that the confirmation dialog is dismissed after a delay <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Low risk UI-only change that adds defensive error handling around dialog dismissal to avoid a crash when the window is already gone. > > **Overview** > Prevents a crash in `BookmarkAddedConfirmationDialog` when the auto-dismiss timer fires after the dialog window has already been removed. > > The auto-dismiss `dismiss()` call is now wrapped in a `try/catch` for `IllegalArgumentException` and logs a message instead of throwing. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit a14cdad. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
Task/Issue URL: https://app.asana.com/1/137249556945/project/1202552961248957/task/1213449998545088?focus=true ### Description Removes the temporary Block Store de-risking capability observer now that sufficient production data has been collected. - Deleted `SyncAutoRecoveryCapabilityObserver` and its tests - Removed 9 Block Store daily pixels from `SyncPixels.kt` and `SyncPixelParamRemovalPlugin.kt` - Removed `syncAutoRecoveryCapabilityDetectionWrite` and `syncAutoRecoveryCapabilityDetectionRead` feature flags from `SyncFeature.kt` ### Steps to test this PR **QA optional** - [ ] [Optional] Build and verify app launches without crashes - [ ] [Optional] Confirm no Block Store capability pixels are fired <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Low risk cleanup that removes de-risking telemetry and its feature flags/tests; behavior change is limited to no longer performing Block Store capability checks or emitting related daily pixels. > > **Overview** > Removes the temporary Block Store de-risking path by deleting `SyncAutoRecoveryCapabilityObserver` (and its test) that ran capability read/write checks after privacy config downloads. > > Cleans up associated telemetry by dropping the Block Store daily pixel definitions and their parameter-removal entries, and removes the two remote feature toggles (`syncAutoRecoveryCapabilityDetectionWrite`/`Read`) used to gate this logic. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 0b5823f. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> Co-authored-by: Craig Russell <1336281+CDRussell@users.noreply.github.com>
Task/Issue URL: https://app.asana.com/1/137249556945/project/1211724162604201/task/1213684338936124?focus=true ### Description Added a new lint rule that prevents the use of `postValue()` on `SingleLiveEvent` instances. The rule enforces the use of `setValue()` instead to avoid silently dropping commands when multiple `postValue()` calls occur before the main thread processes them. The detector identifies calls to `postValue()` on `SingleLiveEvent` or its subclasses and reports an error with guidance to use `setValue()` on the main thread or wrap background thread calls with `withContext(dispatchers.main())`. ### Steps to test this PR _Lint Rule Validation_ - [ ] Create a class with a `SingleLiveEvent` property and call `postValue()` on it - verify lint error appears - [ ] Change the same call to use `setValue()` or `.value = ...` - verify lint error disappears - [ ] Call `postValue()` on a regular `MutableLiveData` instance - verify no lint error occurs ### UI changes No UI changes. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Medium risk because it changes how several UI `command` emissions are dispatched (switching to main-thread `setValue`), which can affect timing/order of one-shot navigation/dialog events if any callers relied on background posting. > > **Overview** > Adds a new lint rule (`NoPostValueOnSingleLiveEventDetector`) registered in the project’s lint registry (with unit tests) to **error** on `SingleLiveEvent.postValue()` usage, guiding developers to use main-thread `setValue`/`.value = ...` to avoid dropped commands. > > Updates multiple ViewModels (notably `BrowserTabViewModel`, plus `FeedbackViewModel` and `BookmarksViewModel`) to replace `postValue` with direct `.value` assignments, and wraps previously background-thread emissions in `dispatchers.main()` launches where needed. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit bc1b973. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
Task/Issue URL: https://app.asana.com/1/137249556945/project/72649045549333/task/1211766481496464?focus=true ### Description Adds in the headless sync setup using the persisted sync recovery key. This is still gated by the `syncAutoRestore` feature flag which **remains disabled** for now (you'll need to update this manually for some of the tests) ### Steps to test this PR **Notes** - Prerequisites: Internal build installed on a device with Google Play Services, logged into Google account. - Navigate to Settings > Sync Dev Settings to access Block Store controls. - ℹ️ Clearing data and relaunching the app does not restore Block Store data; it has to be an uninstall/reinstall. - Suggested logcat filter: `Sync-Recovery|Sync-AutoRestore` **Scenario 1: Feature flag OFF (default) — no restore offered regardless of stored key** - [x] Fresh install - [x] Launch app and verify the "Restore" dialog is not shown, normal onboarding flow proceeds - [x] go to Sync Dev Settings and write any string to Block Store - [x] Uninstall and reinstall (do not clear app data) - [x] Launch app and verify the "Restore" dialog is still not shown (because flag is off) **Scenario 2: Feature flag ON, no recovery key — no restore offered** - [x] Apply the patch defined below to hardcode `syncAutoRestore` to be enabled, and install - [x] Clear app data to ensure Block Store has no data - [x] Launch app — verify the "Restore" dialog is not shown, normal onboarding proceeds **Scenario 3: Feature flag ON, recovery key present — user accepts restore** - [x] Keep the hardcoded FF enabled changes from before - [x] Save a password or two - [x] Set up Sync on the device, using Sync & Backup -> Sync & Back Up This Device and copy the recovery key when it's available using the `Copy code` button - [x] Paste the recovery key into Block Store via Sync Dev Settings and use the `Write` button to persist it - [x] Uninstall and reinstall (do not clear app data) - [x] Go through onboarding — verify the "Restore" dialog is shown - [x] Tap "Restore My Stuff" — verify onboarding continues normally (e.g., comparison chart shown next) - [x] Complete onboarding, then go to Settings > Sync — verify sync account is re-established - [x] Verify previous password is available **Scenario 4: Feature flag ON, recovery key present — user skips restore** - [x] Repeat setup from Scenario 3 (to get a valid recovery code in Block Store, uninstall and then reinstall) - [x] Launch app — verify the "Restore" dialog is shown - [x] Tap "Skip" button from that dialog — verify you see the `Got it! I'll skip other tips` dialog - [x] Complete onboarding, then go to Settings > Sync — verify sync is not set up **Scenario 5: Invalid code persisted** - [x] Use `Sync Dev Settings` to write an invalid recovery code (e.g., a few random characters) - [x] Uninstall and reinstall (do not clear app data) - [x] Launch app — verify the "Restore" dialog is shown - [x] Tap `Restore My Stuff`. Verify the UX continues normally and in logs you see `Sync-Recovery: restore failed` ## Patch to enable FF for `syncAutoRestore` ``` Index: sync/sync-impl/src/main/java/com/duckduckgo/sync/impl/SyncFeature.kt IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/sync/sync-impl/src/main/java/com/duckduckgo/sync/impl/SyncFeature.kt b/sync/sync-impl/src/main/java/com/duckduckgo/sync/impl/SyncFeature.kt --- a/sync/sync-impl/src/main/java/com/duckduckgo/sync/impl/SyncFeature.kt (revision Staged) +++ b/sync/sync-impl/src/main/java/com/duckduckgo/sync/impl/SyncFeature.kt (date 1773231063963) @@ -78,6 +78,6 @@ @Toggle.DefaultValue(DefaultFeatureValue.TRUE) fun syncAutoRecoveryCapabilityDetectionRead(): Toggle - @Toggle.DefaultValue(DefaultFeatureValue.FALSE) + @Toggle.DefaultValue(DefaultFeatureValue.INTERNAL) fun syncAutoRestore(): Toggle } ```
Task/Issue URL: https://app.asana.com/1/137249556945/project/72649045549333/task/1213646602671393?focus=true ### Description When auto-restoring a sync account from Block Store on reinstall, the previous implementation called `login()` without providing the original device ID, causing the sync backend to assign a new device ID. This left the old device entry as an orphan on the account — visible to other connected devices as a ghost entry. This PR fixes the orphaned device problem by storing the device ID alongside the recovery code in Block Store as a JSON payload, then reusing that device ID during auto-restore login. This matches the approach iOS already uses. No user-facing prod changes as it is still guarded by `syncAutoRestore` FF which remains `DISABLED` ### Steps to test this PR Logcat filter `message~:"Sync-Recovery|Sync-AutoRestore"` **Pre-requsites** 1. Device/emulator with Google Play Services 2. Be signed in to the Google account on your device, and have device-level backups enabled 3. Have device-level auth set (PIN/Pattern/Password) ### Feature flag disabled (default) - [x] Fresh install `internalDebug`, launch app - [x] Verify in logs, `Sync-AutoRestore: canRestore=false` ### Feature flag enabled ❗ **hardcode the feature flag to enabled for the following tests** **Testing recovery code cleared when logging out of sync** - [x] Apply patch below - [x] Install `internalDebug` and launch - [x] Verify `canRestore=false` in logs - [x] Verify you do **not** see "Restore my stuff" dialog - [x] Set up sync `Sync and Back Up This Device` then disable sync again - [x] Verify in logs, `sync disabled, clearing recovery code from Block Store` **Ensuring device not orphaned on restore** - [x] Set up sync again, and this time copy the recovery code using the `Copy Code` button - [x] Visit `Sync Dev Settings`, and paste into the `Recovery code` edit text (in the `Persistent storage` section) - [x] Scroll down to the `Account Settings` section, and tap on `Device id`'s value - [x] Paste into the `Device ID` edit text - [x] Tap the `Write` button and verify you see the `Stored successfully` toast and JSON containing both recovery and device ID is shown - [x] Uninstall and reinstall (do not use clear app data). Launch app - [x] Verify you see in logs, `canRestore=true` - [x] Verify you are offered to `Restore My Stuff`. Tap that button. - [x] Skip rest of onboarding, and visit `Settings -> Sync & Backup` - [x] Verify sync is set up, and that there is only one device showing ### Patch to enabled `syncAutoRestore` feature flag ``` Index: sync/sync-impl/src/main/java/com/duckduckgo/sync/impl/SyncFeature.kt IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/sync/sync-impl/src/main/java/com/duckduckgo/sync/impl/SyncFeature.kt b/sync/sync-impl/src/main/java/com/duckduckgo/sync/impl/SyncFeature.kt --- a/sync/sync-impl/src/main/java/com/duckduckgo/sync/impl/SyncFeature.kt (revision Staged) +++ b/sync/sync-impl/src/main/java/com/duckduckgo/sync/impl/SyncFeature.kt (date 1773659914685) @@ -78,6 +78,6 @@ @Toggle.DefaultValue(DefaultFeatureValue.TRUE) fun syncAutoRecoveryCapabilityDetectionRead(): Toggle - @Toggle.DefaultValue(DefaultFeatureValue.FALSE) + @Toggle.DefaultValue(DefaultFeatureValue.INTERNAL) fun syncAutoRestore(): Toggle } ``` <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Changes sync auto-restore login behavior to reuse a stored device ID and adds new persistence/cleanup logic; mistakes could cause failed restores or unexpected sign-in/device registration behavior. > > **Overview** > Fixes sync auto-restore creating *orphaned/ghost devices* by allowing `processCode`/recovery `login` to accept an `existingDeviceId` and using it during restore instead of always generating a new one. > > Introduces `SyncAutoRestoreManager` to persist a JSON payload (`recovery_code` + optional `device_id`) in Block Store, updates `RealSyncAutoRestore` to read that payload (and to hard-skip when the `syncAutoRestore` flag is off), and adds a lifecycle observer that clears the stored recovery payload when the user signs out while auto-restore is enabled. > > Updates internal sync settings UI to write/read the new payload format (separate recovery-code and device-id inputs, plus tap-to-copy fields) and adjusts/extends unit tests to cover the new manager, observer, and updated auto-restore flow. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit fcb4307. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
Task/Issue URL: https://app.asana.com/1/137249556945/project/488551667048375/task/1213614217074373?focus=true ### Description Enables Develocity PTS for JVM unit tests for local builds and PR checks. Keeps it disabled for post-merge checks and nightly flows since we want to run the full suite. This runs with the [Standard profile](https://docs.gradle.com/develocity/current/using-develocity/predictive-test-selection/#selection-profiles) that balances speed and selecting relevant tests. Alternatively, we can go with Conservative to select more tests but reduces time savings. ### Steps to test this PR _Run tests locally_ - [x] Run `./gradlew :pir-impl:testDebugUnitTest -Dpts.enabled=false` (replace `pir-impl` with any module you are familiar with the unit tests) to get a baseline time of how long it takes. It should run all tests in that module - [x] Run `./gradlew :pir-impl:testDebugUnitTest` twice in a row. The second time it should finish in less than a second and run no tests. - [ ] Now change some code in that module in a way that would break the test. For example in `pir-impl`, edit `PirAuthInterceptor:65` and change `bearer` to `Bearer`. This change will break one of the tests that checks for correct header value. - [ ] Run `./gradlew :pir-impl:testDebugUnitTest` again. You should see something like `Predictive Test Selection: 4 of 74 test classes selected with profile 'Standard' (saving 54.371s serial time)` and the tests that were run should fail. ### UI changes No UI change <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Changes the JVM unit-test execution model in CI by enabling Develocity Predictive Test Selection by default, which can unintentionally skip relevant tests if misconfigured. Adds JUnit Platform/Vintage dependencies across modules, which may change how tests are discovered and run. > > **Overview** > **Enables Develocity Predictive Test Selection (PTS) for JVM unit tests** by configuring all Gradle `Test` tasks to `useJUnitPlatform()` and wiring `develocity.predictiveTestSelection.enabled` to a `-Dpts.enabled` system property (defaulting to `true`). > > CI workflows now **disable PTS for post-merge/nightly and external reference test runs** by passing `-Dpts.enabled=false`, while PR checks keep PTS enabled by default. This also adds `org.junit.vintage:junit-vintage-engine` (and its version pin) to test dependencies so existing JUnit 4 tests continue to run under the JUnit Platform. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit e0dfd38. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
Task/Issue URL: https://app.asana.com/1/137249556945/project/488551667048375/task/1213634152051160?focus=true ### Description See attached description ### Steps to test this PR https://app.asana.com/1/137249556945/task/1213721083788440?focus=true <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Adds a large set of new bundled broker configuration JSONs that directly drive PIR scan/opt-out automation; incorrect selectors/flows or `removedAt` flags could cause broken runs or unintended broker enablement/disablement. > > **Overview** > Adds a batch of new broker JSON assets under `pir-impl/src/main/assets/brokers/`, expanding the set of bundled broker definitions used for PIR scanning and opt-out flows. > > These configs include new `scan`/`optOut` step recipes (including captcha handling and email confirmations), parent/mirror-site relationships, scheduling parameters, and per-broker activation state via `removedAt` (some brokers ship pre-removed). > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 5b9dc42. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
Task/Issue URL: https://app.asana.com/1/137249556945/project/1200204095367872/task/1213433888294716?focus=true ### Description - Adds the native input to the Duck.ai contextual sheet ### Steps to test this PR - [ ] Enable the native input - [ ] Open contextual Duck.ai and send prompt - [ ] Verify that the native input is visible - [ ] Send a prompt - [ ] Verify that the prompt is submitted - [ ] Change to search - [ ] Submit a query - [ ] Very that contextual is closed and the search is submitted ### UI changes | Before | After | | ------ | ----- | <img width="1280" height="2856" alt="Screenshot_20260317_004850" src="https://github.com/user-attachments/assets/e20c0ffc-de95-4ea8-98e3-36b65285c59c" />|<img width="1280" height="2856" alt="Screenshot_20260317_010044" src="https://github.com/user-attachments/assets/2682325c-72b6-4617-8668-bbe9a6e0ad44" /> <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Introduces a new native input overlay that sends JS subscription events into the contextual WebView and changes sheet-mode rendering/visibility, so regressions could impact prompt submission or UI state toggling. > > **Overview** > Adds an optional **native input overlay** to the Duck.ai contextual sheet when in WebView mode, driven by a new `ContextualNativeInputManager` that wires up `NativeInputModeWidget` and toggles visibility based on the user setting. > > Native chat prompts are now submitted via `JsMessaging` subscription events (`submitAIChatNativePrompt` / `submitPromptInterruption`), while search submissions close the contextual sheet and open the query in a new browser tab. The layout is updated to wrap the `WebView` in a container and overlay the new input card at the bottom. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 31a9ea3. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
Task/Issue URL: https://app.asana.com/1/137249556945/project/488551667048375/task/1213310129933666?focus=true ### Description - Adds a Duck.ai dev setting to override the Duck.ai URL - Centralizes all uses of the “duck.ai” domain - Changes the default `DUCK_CHAT_WEB_LINK` to "https://duck.ai/chat?duckai=5” (the same URL that’s used in the config) ### Steps to test this PR - [ ] Go to Settings > Developer Settings > Custom Duck.ai URL - [ ] Enter a custom URL - [ ] Tap “Save" - [ ] Verify that the app is restarted - [ ] Go to Duck.ai - [ ] Verify that the custom URL is used ### UI changes | Developer Settings | Custom Duck.ai URL | | ------ | ----- | <img width="1080" height="2340" alt="Screenshot_20260312_212642" src="https://github.com/user-attachments/assets/fff528da-6a7d-40ef-8fbc-639c71283547" />|<img width="1080" height="2340" alt="Screenshot_20260312_213453" src="https://github.com/user-attachments/assets/32ff1142-a813-4544-9db3-41d287f31730" /> <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Touches multiple privacy/cookie-clearing and JS messaging allowlist paths by replacing hardcoded `duck.ai` checks with an injected host provider, so a misconfiguration could break Duck.ai navigation, data clearing, or messaging on that domain. > > **Overview** > Adds a `DuckAiHostProvider` abstraction and rewires Duck.ai-related logic to use it instead of hardcoded `duck.ai` (cookies/third‑party cookie exceptions, IndexedDB/site data clearing exclusions, site permissions microphone recovery allowlist, and multiple JS messaging `allowedDomains` lists). > > Introduces an *internal/dev-only* `duckchat-internal` module and Developer Settings UI to override the Duck.ai URL, persisting it in a small data store and replacing the default provider via DI; the DuckChat entry URL is also updated to the direct `https://duck.ai/chat?duckai=5` form. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 4a3a624. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
Task/Issue URL: https://app.asana.com/1/137249556945/project/1174433894299346/task/1213630582405252 ### Description Adds a `FirstScreenHandlerImpl` that decides the first screen shown when the app is opened, with two modes controlled by the `showNTPAfterIdleReturn` feature flag: **When `showNTPAfterIdleReturn` is enabled** (new behavior): - On every app open (fresh launch or return from background), checks if the idle timeout has elapsed since the app was last backgrounded. - If the timeout has passed (or no previous timestamp exists), delegates to `ShowOnAppLaunchOptionHandler` to show the configured launch screen. - If the timeout hasn't passed, does nothing (user returns to their previous state). - Timeout is configurable via remote settings JSON (`timeoutMinutes` field), defaults to 30 minutes. **When `showNTPAfterIdleReturn` is disabled** (legacy behavior): - Only on fresh launches: delegates to `ShowOnAppLaunchOptionHandler` if `showOnAppLaunchFeature` is enabled. - Non-fresh launches (return from background): does nothing. Key changes: - New `FirstScreenHandlerImpl` registered as a `BrowserLifecycleObserver` via `@ContributesMultibinding` - `showNTPAfterIdleReturn` is checked first and takes precedence over `showOnAppLaunchFeature` - Records background timestamp on `onClose()` using `System.currentTimeMillis()` (survives reboots) - `BrowserViewModel` no longer owns show-on-app-launch logic — fully decoupled - New `lastSessionBackgroundTimestamp` in `SettingsDataStore` (separate from `AutomaticDataClearer`'s timestamp) - `showNTPAfterIdleReturn` feature flag defaults to `FALSE`, enabled on internal builds via `@InternalAlwaysEnabled` ### Steps to test this PR _Idle return enabled — timeout exceeded (cold start)_ - [x] Enable `showNTPAfterIdleReturn` feature flag (auto-enabled on internal builds) - [x] Open the app and navigate to a website - [x] Background the app and wait longer than the configured timeout (default 1 min on internal) - [x] Reopen the app — the configured ShowOnAppLaunch option should be applied _Idle return enabled — timeout exceeded (fresh launch)_ - [x] Enable `showNTPAfterIdleReturn` feature flag - [x] Open the app and navigate to a website - [x] Force-stop the app and wait longer than the configured timeout - [x] Reopen the app — the configured ShowOnAppLaunch option should be applied _Idle return enabled — timeout not exceeded_ - [x] Enable `showNTPAfterIdleReturn` feature flag - [x] Open the app and navigate to a website - [x] Background the app and reopen within the timeout window - [x] The previous tab should still be visible (no action taken) _Idle return enabled — first ever launch_ - [ ] Enable `showNTPAfterIdleReturn` feature flag - [ ] Clear app data or fresh install - [ ] Open the app — the configured ShowOnAppLaunch option should be applied (no previous timestamp) _Idle return disabled — fresh launch (legacy behavior)_ - [x] Disable `showNTPAfterIdleReturn` feature flag - [x] Enable `showOnAppLaunchFeature` and set the option to "New Tab Page" - [x] Force-stop the app and reopen — a new tab page should be shown - [x] Set the option to "Specific Page" with a URL, force-stop and reopen — the specific page should load - [x] Set the option to "Last Opened Tab", force-stop and reopen — the last opened tab should be shown _Idle return disabled — non-fresh launch (legacy behavior)_ - [x] Disable `showNTPAfterIdleReturn` feature flag - [x] Open the app, navigate to a website, background it for any duration - [x] Reopen the app — the previous tab should still be visible (no action taken) _Idle return disabled — ShowOnAppLaunch also disabled_ - [x] Disable both `showNTPAfterIdleReturn` and `showOnAppLaunchFeature` - [x] Force-stop and reopen the app — default behavior, no delegation ### UI changes No UI changes — this is a behavioral change only. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Changes app-open/return behavior by moving “show on app launch” decisions into a new lifecycle observer and gating it behind remote config + persisted timestamps, which could affect what users see when resuming the app. > > **Overview** > Adds `FirstScreenHandlerImpl` as a `BrowserLifecycleObserver` to centralize first-screen selection on app open. When remote flag `androidBrowserConfig.showNTPAfterIdleReturn` is enabled, it conditionally applies `ShowOnAppLaunchOptionHandler` based on elapsed time since the app was last backgrounded (remote-configurable `timeoutMinutes`, default 30m); otherwise it preserves the legacy *fresh-launch only* behavior. > > Removes the previous “show on app launch” handling from `BrowserActivity`/`BrowserViewModel`, introduces persisted `SettingsDataStore.lastSessionBackgroundTimestamp`, adds the new remote sub-toggle to `AndroidBrowserConfigFeature`, and updates/adds tests accordingly. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 2d06fa7. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Task/Issue URL: https://app.asana.com/0/488551667048375/1213729660473597/f ----- - Automated content scope scripts dependency update This PR updates the content scope scripts dependency to the latest available version and copies the necessary files. Tests will only run if something has changed in the `node_modules/@duckduckgo/content-scope-scripts` folder. If only the package version has changed, there is no need to run the tests. If tests have failed, see https://app.asana.com/0/1202561462274611/1203986899650836/f for further information on what to do next. _`content-scope-scripts` folder update_ - [x] All tests must pass - [x] Privacy tests must pass _Only `content-scope-scripts` package update_ - [ ] All tests must pass - [ ] Privacy tests do not need to run <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Updates bundled content-scope scripts that run on web pages, including YouTube interference detection behavior and new event firing, which could affect detection accuracy and page performance. Most changes are vendor-built/minified, making regressions harder to spot in review. > > **Overview** > Bumps `@duckduckgo/content-scope-scripts` to `13.33.0` and refreshes the bundled Android content-scope artifacts. > > The update extends YouTube ad/interference detection to optionally **fire `webEvents` detection events**, adds safer start/stop lifecycle cleanup for retry timers, and gates running the detector to YouTube/*test* hostnames. > > Includes a handful of robustness fixes in the bundled scripts (safer window/property and text extraction checks, improved performance-metrics error handling/LCP observer cleanup, favicon extraction type-guarding, and minor runtime safety in the DuckPlayer bundle). > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 001ee9b. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> Co-authored-by: daxmobile <daxmobile@users.noreply.github.com>
Task/Issue URL: https://app.asana.com/1/137249556945/task/1213720291861205?focus=true ### Description See attached task description ### Steps to test this PR Run https://app.asana.com/1/137249556945/task/1213721083788440?focus=true and ensure that: - [x] Test 1, no pixel emitted for bundled json - [x] Test 2, no pixel emitted for bundled json - [x] Test 3, pixel emitted for bundled json - [x] Test 4, pixel emitted for bundled json - [x] Test 5, no pixel emitted for bundled json - [x] Test 5, no pixel emitted for bundled json
Task/Issue URL: https://app.asana.com/1/137249556945/task/1213726950062648?focus=true ### Description Adds killswitch for broker bundle json usage ### Steps to test this PR Disable useBundledBrokerJsons and run https://app.asana.com/1/137249556945/task/1213721083788440?focus=true, verify that PIR-update: Bundled broker jsons disabled. Not loading bundled data. is shown for cases where buundle usage was expected. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Adds a new remote toggle that can disable the bundled broker-JSON fallback when network seeding fails, which could leave PIR without broker data in offline/BE-failure scenarios if misconfigured. > > **Overview** > Adds a new PIR remote feature flag, `useBundledBrokerJsons` (default *enabled*), to control whether bundled broker JSON assets may be used as a fallback. > > Updates `RealBrokerJsonUpdater.update()` so that when the network update fails and **no broker data is stored**, it will *skip loading bundled data* and return `false` if the toggle is disabled (with a new log message). Tests are updated to inject `PirRemoteFeatures` and to cover both toggle-enabled and toggle-disabled fallback behavior. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 9ccf794. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
Task/Issue URL: https://app.asana.com/1/137249556945/project/1210594645151737/task/1213720450436951?focus=true ### Description Adds a `PirProcessLifecycleObserver` similar to existing `VpnProcessLifecycleObserver` ### Steps to test this PR Nothing to test ### UI changes No UI changes <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Low risk: adds a new lifecycle observer interface and dispatch hook for the `pir` secondary process, mirroring existing VPN behavior without changing core app flows or data handling. > > **Overview** > Adds support for a new `pir` secondary process lifecycle callback. > > `DuckDuckGoApplication` now recognizes the `pir` process name and dispatches `onPirProcessCreated()` to a new `PluginPoint<PirProcessLifecycleObserver>`, alongside the existing VPN process handling. This PR also introduces the `PirProcessLifecycleObserver` interface and an Anvil `ContributesPluginPoint` (`PirProcessLifecycleObserverPluginPoint`) so features can register PIR process startup observers via DI. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 63fd802. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
Task/Issue URL: https://app.asana.com/1/137249556945/task/1213732305810290 Autoconsent Release: https://github.com/duckduckgo/autoconsent/releases/tag/v14.61.0 ## Description Updates Autoconsent to version [v14.61.0](https://github.com/duckduckgo/autoconsent/releases/tag/v14.61.0). ### Autoconsent v14.61.0 release notes See release notes [here](https://github.com/duckduckgo/autoconsent/blob/v14.61.0/CHANGELOG.md) ## Steps to test This release has been tested during Autoconsent development. You can check the release notes for more information. 1. Make sure that there's no unexpected failures in CI checks 2. (optional) smoke test some of the sites mentioned in the release notes 3. If there are problems, reach out to a CPM DRI <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Updates a large, minified third-party consent automation bundle and its rules/heuristics, which can change consent handling behavior across many sites. Main risk is regressions in CMP detection/opt-out flows due to updated selectors and new rule-step capabilities. > > **Overview** > Updates `@duckduckgo/autoconsent` from `14.54.0` to `14.61.0` (and lockfile), and refreshes the shipped `autoconsent-bundle.js` to the matching release build. > > The new bundle expands rule-step support (e.g., new DOM mutations like `removeClass`/`setStyle`/`addStyle`, compact rule format v2 compatibility) and adjusts several CMP handlers/selectors (notably TrustArc, Cookiebot, OneTrust, and Sourcepoint) to improve detection and opt-out reliability. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 23e8e58. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> Co-authored-by: muodov <2726132+muodov@users.noreply.github.com>
Task/Issue URL: https://app.asana.com/1/137249556945/project/1200204095367872/task/1213689666003865?focus=true ### Description - Overrides `onRequestChildRectangleOnScreen` to prevent the omnibar from hiding on focus change - Focuses the input in the omnibar when going back from Duck.ai ### Steps to test this PR - [x] Open a new tab - [x] Type something and tap the Duck.ai omnibar icon - [x] Swipe back - [x] Verify that the input is focussed - [x] Tap on the overflow menu - [x] Verify that the omnibar does not hide ### UI changes Before https://github.com/user-attachments/assets/da125548-5df1-4cc1-b7c1-473b601ee231 After https://github.com/user-attachments/assets/c5e01290-3f8e-43db-bbdd-7a716ad15fcf <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Touches omnibar focus/command behavior and `CoordinatorLayout` scrolling behavior, which can affect navigation/scrolling UI across many screens. Risk is moderate due to potential regressions in focus handling and child scroll requests. > > **Overview** > Fixes returning from `ViewMode.DuckAI` by explicitly re-focusing the omnibar input (`Command.FocusInputField`) when switching back to non-DuckAI modes. > > Prevents the webview container from triggering automatic scroll-to-rectangle behavior by overriding `TopOmnibarBrowserContainerLayoutBehavior.onRequestChildRectangleOnScreen` to always return `false`, avoiding unintended layout/toolbar shifts. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 72652f5. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
Task/Issue URL: https://app.asana.com/1/137249556945/project/488551667048375/task/1213721049904377?focus=true ### Description Introduces a dedicated `sqlcipher-loader` module that centralises SQLCipher native library loading and fixes a JNI deadlock causing a 7x spike in `LIBRARY_LOAD_TIMEOUT_SQLCIPHER` (350 → 2,500/day). **Root cause:** removing PIR's eager `System.loadLibrary("sqlcipher")` call (f6c879e) inadvertently removed the pre-warm that autofill depended on. Autofill's `SqlCipherLibraryLoader` then performed the first full load on a background thread, and when PIR's lazy load raced concurrently, a JNI loading deadlock could occur — triggering the 10s timeout. **Fix:** - New `sqlcipher-loader-api` module exposes a `SqlCipherLoader` interface with a single `waitForLibraryLoad(): Result<Unit>` API. - New `sqlcipher-loader-impl` provides `RealSqlCipherLoader`, which: - Implements `MainProcessLifecycleObserver` to eagerly pre-warm SQLCipher on the IO dispatcher at app startup, before autofill or PIR need it. - Uses a `CompletableDeferred<Unit>` (initialised at construction) so all callers share the same load — concurrent `complete()` calls are no-ops, making the race structurally impossible. - Fires `LIBRARY_LOAD_FAILURE_SQLCIPHER` (daily pixel) if the load throws. - PIR and autofill both now inject `SqlCipherLoader` and call `waitForLibraryLoad()` instead of loading independently. - `SqlCipherLibraryLoader` (autofill-local) and its test deleted. - `sqlCipherAsyncLoading` feature flag and `LIBRARY_LOAD_TIMEOUT_SQLCIPHER` pixel removed — no timeout needed when the load is predictable and early. ### Steps to test this PR _SQLCipher loads correctly_ - [x] Install the app and open a page with a password field — autofill suggestion should appear normally - [x] Open the PIR screen — it should load without errors - [x] Check logcat for `SqlCipher: native library loaded successfully` appearing once at startup (not twice, not on demand) _No regression on autofill_ - [x] Save a login and verify it autofills on the target site - [x] Confirm no `LIBRARY_LOAD_TIMEOUT_SQLCIPHER` or `LIBRARY_LOAD_FAILURE_SQLCIPHER` pixels fire under normal conditions _Verify build_ - [x] `./gradlew :sqlcipher-loader-impl:testDebugUnitTest` - [x] `./gradlew :autofill-impl:testDebugUnitTest` - [x] `./gradlew :pir-impl:testDebugUnitTest` _SQLCipher loads correctly on PIR process_ - [x] Obtain subscription - [x] Start a PIR scan via the PIR dashboard - [x] Logcat should show "SqlCipher: Attempting to load native library loaded on the PIR process” - [x] Logcat should show " SqlCipher-Init: Library load wait completed successfully” - [x] Logcat should show "PIR-DB: sqlcipher native library loaded ok" <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Moderate risk because it changes when/how the SQLCipher native library is loaded and gates creation of encrypted databases for both Autofill and PIR, which can impact data access and startup behavior. > > **Overview** > Centralizes SQLCipher native library loading into a new `sqlcipher-loader` module (`SqlCipherLoader` API + `RealSqlCipherLoader` impl) that eagerly starts async loading via process lifecycle observers and provides a shared `waitForLibraryLoad` for all callers (with timeout/failure pixels). > > Migrates Autofill and PIR secure DB factories to inject `SqlCipherLoader` instead of doing their own loads, deleting Autofill’s local `SqlCipherLibraryLoader` and its feature flag (`sqlCipherAsyncLoading`) and moving the SQLCipher load pixels out of `autofill.json5` into `sqlcipher_loader.json5`. The app wiring is updated to depend on the new modules and to strip ATB params for the new SQLCipher pixels. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 33c8a5f. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Karl Dimla <klmbdimla@gmail.com>
…ion (#8027) Task/Issue URL: https://app.asana.com/1/137249556945/project/1200204095367872/task/1213736584422131?focus=true ### Description - Returns early if the URL is non-hierarchical on the subscriptions WebView (allowing the back nav) ### Steps to test this PR - [x] Go to the subscriptions WebView - [x] Verify that you can go back <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Low risk, localized change to back-navigation gating that adds a safety check to prevent an `UnsupportedOperationException` when the WebView URL is non-hierarchical. > > **Overview** > Fixes a crash in `SubscriptionsWebViewActivity.canGoBack()` by checking `uri.isHierarchical` before reading query parameters. > > Non-hierarchical URLs now bypass the `preventBackNavigation` query check, preventing `UnsupportedOperationException` during back navigation decisions. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 72e615e. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
Task/Issue URL: https://app.asana.com/1/137249556945/task/1213726950062653?focus=true ### Description See attached task description ### Steps to test this PR _Feature 1_ - [x] Install debug build - [x] Verify m_dbp_user-eligible_d is not emitted - [x] Obtain a susbcription - [x] Verify that m_dbp_user-eligible_d is emitted - [x] Reopen app and verify suucceeding pixels for m_dbp_user-eligible_d are dropped <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Low risk: adds a new analytics pixel and a single call site when `canRunPir()` becomes true, with minimal impact on PIR behavior beyond an extra enqueue. > > **Overview** > Adds a new Android PIR daily analytics pixel, `m_dbp_user-eligible_d`, to report when a user is eligible to run PIR. > > Wires the pixel through `PirPixel`/`PirPixelSender` and emits it from `PirDataUpdateObserver` when `canRunPir()` transitions to enabled; updates unit tests to inject and mock the new `PirPixelSender` dependency. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 9635c94. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
Task/Issue URL: https://app.asana.com/1/137249556945/project/1211850753229323/task/1213541507583168?focus=true ### Description A docked header is added in top of the browser sheet menu to display the current page loaded in the tab. ### Steps to test this PR - [x] Go to Appearance settings and active the new browser menu - [x] Open a valid website - [x] Open the browser menu - [x] The header should display the favicon, the title and the short URL of the website loaded (https://www.figma.com/design/yYErQGaw8Jkd699ldp3tdW/%F0%9F%97%82-New-Menu?node-id=2087-42802&m=dev) - [x] Scroll in the browser menu and check the header is docked at the top - [x] Close the menu with the cross icon at the right of the header - [x] Open a new tab - [x] Open the browser menu - [x] Check there is no header in the menu (https://www.figma.com/design/yYErQGaw8Jkd699ldp3tdW/%F0%9F%97%82-New-Menu?node-id=2100-29778&m=dev) - [x] Open a website that doesn't exist (yeti page) - [x] Open the browser menu - [x] Check the header is in failing state mode (https://www.figma.com/design/yYErQGaw8Jkd699ldp3tdW/%F0%9F%97%82-New-Menu?node-id=2088-44924&m=dev) - [x] Open Duck.ai tab - [x] Open the browser menu - [x] Check the header is in Duck.ai state mode (https://www.figma.com/design/yYErQGaw8Jkd699ldp3tdW/%F0%9F%97%82-New-Menu?node-id=2088-45138&m=dev) - [x] Open custom tab screen from settings - [x] Open the browser menu - [x] Check the header is the same than website mode (or duck.ai mode if you opened duck.ai website) - [x] Check the browser menu is compatible in landscape (https://www.figma.com/design/yYErQGaw8Jkd699ldp3tdW/%F0%9F%97%82-New-Menu?node-id=2278-25256&m=dev) - [x] Check the browser menu is compatible with tablet devices in portrait and landscape (https://www.figma.com/design/yYErQGaw8Jkd699ldp3tdW/%F0%9F%97%82-New-Menu?node-id=2278-25877&m=dev) ### UI changes | Before | After | | ------ | ----- | <img width="252" height="561" alt="image" src="https://github.com/user-attachments/assets/8bbdae2a-4d3c-4180-9f2c-8da9df545734" /> | <img width="252" height="561" alt="image" src="https://github.com/user-attachments/assets/f50cfc35-0b05-4cd1-b813-d0c0d238d36b" /> <img width="252" height="561" alt="image" src="https://github.com/user-attachments/assets/b64e0dd1-35a8-48a7-8d32-ae6ac57e4738" /> | <img width="252" height="561" alt="image" src="https://github.com/user-attachments/assets/45ecd350-e79f-4d29-89b8-fad5795c0593" /> <img width="252" height="561" alt="image" src="https://github.com/user-attachments/assets/2c567469-f2ab-4c6a-94ea-1dc71894e9cb" /> | <img width="252" height="561" alt="image" src="https://github.com/user-attachments/assets/352967bb-626f-4254-a9dd-9e435b2b4283" /> <img width="561" height="252" alt="image" src="https://github.com/user-attachments/assets/b2443a39-4ad5-4868-aef8-8ae70099fd69" /> | <img width="561" height="252" alt="image" src="https://github.com/user-attachments/assets/fffa3383-9c83-4a63-a780-5410e4b7f1dc" /> <img width="640" height="400" alt="image" src="https://github.com/user-attachments/assets/d47bb076-61a7-401b-b4ce-16e373006d24" /> | <img width="640" height="400" alt="image" src="https://github.com/user-attachments/assets/82ce20d8-581d-4054-a040-5c169ec8c3c3" />
Task/Issue URL: https://app.asana.com/0/488551667048375/1213740429007458/f ----- - Automated content scope scripts dependency update This PR updates the content scope scripts dependency to the latest available version and copies the necessary files. Tests will only run if something has changed in the `node_modules/@duckduckgo/content-scope-scripts` folder. If only the package version has changed, there is no need to run the tests. If tests have failed, see https://app.asana.com/0/1202561462274611/1203986899650836/f for further information on what to do next. _`content-scope-scripts` folder update_ - [ ] All tests must pass - [ ] Privacy tests must pass _Only `content-scope-scripts` package update_ - [x] All tests must pass - [x] Privacy tests do not need to run <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Updates a core privacy/content script dependency, which can subtly change runtime behavior even though the diff is only dependency/lockfile changes. Risk is limited to integration/regression issues from the new upstream versions. > > **Overview** > Bumps `@duckduckgo/content-scope-scripts` from `13.33.0` to `13.34.0` in `package.json`, updating `package-lock.json` to the new git commit. > > The lockfile refresh also pulls `@duckduckgo/autoconsent` to `14.62.0` (via the existing semver range), updating resolved tarball/integrity metadata. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 708d1bf. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> Co-authored-by: daxmobile <daxmobile@users.noreply.github.com>
Task/Issue URL: https://app.asana.com/1/137249556945/project/1212810093780571/task/1213484200004108?focus=true ### Description Adds the ability to store tab content which will be used later for attaching tabs to ai chat prompts. It will be gated by a new feature flag `storePageContext` and currently only hooked to the existing PAGE_CONTEXT_FEATURE_NAME handler. So at this point it will only be triggered by opening the contextual ai chat. This will allow us to test the storage without impact on existing logic. - Add TabPageContextRepository API for caching page content extracted by the JS PageContext layer, stored per tab in Room - Create tab_page_context table with FK to tabs (DB migration 60→61) - Wire caching into BrowserTabViewModel's existing PAGE_CONTEXT_FEATURE_NAME handler ### Steps to test this PR (See video below) 1. Enable the `storePageContext` feature flag (under androidBrowserConfig) 2. Open a tab and visit any website 3. Click on the duck.ai icon in the address bar to bring up the contextual ai sheet 4. Validate in Android Studio's App Inspection (View -> Tool Windown -> App inspection) that the database is storing the tab context correctly 5. Open more tabs and store more page context. Validate all of them are stored properly 6. Navigate to another URL in an existing tab -> Validate that the database storage replaced the tab context (and not added a new one). This is tied to the Tab ID 7. Close tabs, and validate that the context is deleted properly 8. Use Fire button and validate that all tab context were deleted Notes: - When you close a tab, the tab is soft deleted (allow undo) the page context is not deleted at that point. It's only deleted once the Tab entity is gone. - I decided to gate it behind a new feature flag as this is a browser level feature and don’t want to tie it directly to the chat attachment project. ### Database Demo Video https://github.com/user-attachments/assets/ce7f8f6f-cec9-40cf-aaa9-5d50f363ec60 <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Introduces a new Room table and migration (60→61) plus new write-path in `BrowserTabViewModel`, so there’s some risk of migration issues and additional on-device storage of page content when the flag is enabled. > > **Overview** > Adds a new `androidBrowserConfig.storePageContext` toggle to optionally persist the JS `PAGE_CONTEXT_FEATURE_NAME` payload for a tab. > > Bumps Room DB to v61 and introduces `tab_page_context` (FK to `tabs`, cascade delete) with a new `TabPageContextRepository`/DAO/entity implementation, wired into `BrowserTabViewModel` with failure logging and updated DI + tests. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 889ab10. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
Task/Issue URL: https://app.asana.com/1/137249556945/project/1200204095367872/task/1213736584422142?focus=true ### Description - Catches `FileAlreadyExistsException` if `copyTo` throws ### Steps to test this PR - [ ] Verify that favicons work correctly <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Low risk: wraps a single file copy operation to avoid crashing when copy fails (e.g., existing destination/race), with no behavioral changes elsewhere. > > **Overview** > Prevents crashes when persisting favicons by wrapping `FileBasedFaviconPersister.copyToDirectory`’s `file.copyTo(...)` call in `runCatching`, effectively swallowing `FileAlreadyExistsException`/IO failures during the copy. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 2789592. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
Task/Issue URL: https://app.asana.com/1/137249556945/project/1201462763415876/task/1213737428120588?focus=true ### Description Add back removed test for SqlCipher loader ### Steps to test this PR QA-optional <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Low risk: only unit test and Gradle test configuration changes, no production logic modifications. Main risk is potential test flakiness or slower test execution due to added Robolectric resources. > > **Overview** > Adds a Robolectric-based test suite for `RealSqlCipherLoader.waitForLibraryLoad`, covering success, load failure, timeout handling, cancellation propagation, single-initialization behavior, concurrent callers, and early-load triggers via `onCreate`/`onPirProcessCreated`. > > Updates `sqlcipher-loader-impl` test setup to support these cases by adding Robolectric/AndroidX test dependencies, enabling `includeAndroidResources`, and introducing a `ShadowLibraryLoader` to simulate native library load callbacks without loading real binaries. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 143c446. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
See Commits and Changes for more details.
Created by
pull[bot]
Can you help keep this open source service alive? 💖 Please sponsor : )