Skip to content

LGC-2026-07-15T045820

@landomen landomen tagged this 14 Jul 15:51
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

- [x] Check-out this branch and apply the patch below
- [x] Run a scan that will find profiles - wait until it completes all
opt-outs are submitted
- [x] 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
- [x] 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)
- [x] 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

<!-- CURSOR_SUMMARY -->
---

> [!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.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
35fe63de02080f2fed0caed12bf75e4fc51d95b1. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
Assets 2
Loading