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 -->