Skip to content

PIR: Handle PIR profile reappearance on maintenance scans#9149

Open
landomen wants to merge 1 commit into
developfrom
fix/landomen/pir-profile-reappearance
Open

PIR: Handle PIR profile reappearance on maintenance scans#9149
landomen wants to merge 1 commit into
developfrom
fix/landomen/pir-profile-reappearance

Conversation

@landomen

@landomen landomen commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Task/Issue URL: https://app.asana.com/1/137249556945/project/72649045549333/task/1216387860516593?focus=true
Tech Design URL (if applicable): N/A

Description

Implements the missing profile reappearance handling on maintenance scans that exists on other platforms.

Steps to test this PR

  • Check-out this branch and apply the patch below
  • Run a scan that will find profiles - wait until it completes all opt-outs are submitted
  • Go to PIR Dev Settings -> Scan -> long press the Debug scan button which runs the code below that marks all profiles as removed
  • (Optional) Verify in the DB all opt out records have status REMOVED
  • Without clearing any data run a debug scan on one of the brokers for which the extracted profile was found (alternatively you can also trigger a full scan via the dashboard by editing the profile but this is simpler)
  • Verify you see m_dbp_optoutjob_reappeared in log cat - that means that the profile was marked as re-appeared
  • (Optional) Verify in the DB all opt out records have status REQUESTED again
Index: pir/pir-internal/src/main/java/com/duckduckgo/pir/internal/settings/PirDevScanActivity.kt
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/pir/pir-internal/src/main/java/com/duckduckgo/pir/internal/settings/PirDevScanActivity.kt b/pir/pir-internal/src/main/java/com/duckduckgo/pir/internal/settings/PirDevScanActivity.kt
--- a/pir/pir-internal/src/main/java/com/duckduckgo/pir/internal/settings/PirDevScanActivity.kt	(revision 35fe63de02080f2fed0caed12bf75e4fc51d95b1)
+++ b/pir/pir-internal/src/main/java/com/duckduckgo/pir/internal/settings/PirDevScanActivity.kt	(date 1783610673576)
@@ -40,6 +40,7 @@
 import com.duckduckgo.pir.impl.models.Address
 import com.duckduckgo.pir.impl.models.ExtractedProfile
 import com.duckduckgo.pir.impl.models.ProfileQuery
+import com.duckduckgo.pir.impl.models.scheduling.JobRecord.OptOutJobRecord.OptOutJobStatus
 import com.duckduckgo.pir.impl.notifications.PirNotificationManager
 import com.duckduckgo.pir.impl.scan.PirForegroundScanService
 import com.duckduckgo.pir.impl.scan.PirRemoteWorkerService
@@ -54,6 +55,7 @@
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
 import logcat.logcat
 import javax.inject.Inject
 
@@ -197,6 +199,38 @@
             }
         }
 
+        // Long-press "Run scan" to simulate a maintenance scan that failed to re-find the currently
+        // stored profiles: every valid opt-out job record is forced to REMOVED (with a removed date).
+        // Then tap "Run scan" normally on a broker that still lists the profile to exercise the
+        // reappearance path in RealPirRunStateHandler.handleBrokerScanActionSucceeded:
+        //   - opt-out status reverts REMOVED -> REQUESTED and optOutRemovedDateInMillis is cleared
+        //   - an m_dbp_optoutjob_reappeared pixel is fired per reverted record
+        // Observe: `adb logcat | grep -E "PIR-JOB-RECORD|reappeared"`
+        binding.debugScan.setOnLongClickListener {
+            lifecycleScope.launch {
+                val forced = withContext(dispatcherProvider.io()) {
+                    val records = pirSchedulingRepository.getAllValidOptOutJobRecords()
+                    records.forEach { record ->
+                        pirSchedulingRepository.saveOptOutJobRecord(
+                            record.copy(
+                                status = OptOutJobStatus.REMOVED,
+                                optOutRemovedDateInMillis = currentTimeProvider.currentTimeMillis(),
+                            ),
+                        )
+                        logcat { "PIR-TEST: forced opt-out ${record.extractedProfileId} -> REMOVED" }
+                    }
+                    records
+                }
+                Toast.makeText(
+                    this@PirDevScanActivity,
+                    "Forced ${forced.size} opt-out record(s) to REMOVED. Now tap Run scan to re-find them.",
+                    Toast.LENGTH_LONG,
+                ).show()
+            }
+            true
+        }
+
         binding.debugForceKill.setOnClickListener {
             killRunningWork()
         }

UI changes

No UI changes


Note

Medium Risk
Changes opt-out job state during scheduled/maintenance scans; incorrect matching could wrongly re-queue opt-outs or miss reappearances, though guards limit reverts to scan-confirmed REMOVED records on valid job records.

Overview
Adds profile reappearance handling on successful broker scan extract results, aligning Android with other PIR platforms.

When maintenance scans return profiles that were already stored but whose opt-out job was REMOVED (no longer seen on a prior scan), markReappearedOptOutJobRecords detects the overlap, reverts those records to REQUESTED, clears optOutRemovedDateInMillis, and only touches valid non-deprecated records still in REMOVED status. RealPirRunStateHandler invokes this after markRemovedOptOutJobRecords and before persisting new matches, firing m_dbp_optoutjob_reappeared once per reverted record via reportBrokerOptOutProfileReappeared.

Pixel wiring includes the new definition, PIR_BROKER_CUSTOM_STATS_OPTOUT_PROFILE_REAPPEARED, and unit tests for the updater and handler paths.

Reviewed by Cursor Bugbot for commit 35fe63d. Bugbot is set up for automated code reviews on this repo. Configure here.

@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

2 participants