3.4 KiB
Patch plan – bounded EPG retention
Target
/system/priv-app/channels/channels.apk, DEX class
org.droidtv.channels.epg.bcepg.EpgProvider$deleteStaleDataRunnable plus a
small helper in EpgDatabaseHelper. Original APK SHA-256:
3A44BC648624F0CC9CD9FEB4CFC3FD33F41323597718E4FCCFC25FE35504D34F.
Policy
- Keep the clock API explicitly in epoch milliseconds, then convert once to
epoch seconds before touching the database. The physical
starttimeandendtimevalues in both the old and freshly reacquired databases are epoch seconds. UseTimeUnit.MILLISECONDS.toSeconds(nowMillis)andTimeUnit.DAYS.toSeconds(7); never compare a millisecond cutoff to the DB. - Retain seven past days for backward-guide/recording metadata compatibility; retain all future events. Make the value OEM-configurable in 1–30 days.
- Now/Next may be deleted after
endtime < now-2h; FVP rows must also respect their expiry semantics.
Algorithm
Ensure scheduletable(endtime) remains indexed. Once per day, initially 5–15
minutes after provider startup and only while no acquisition transaction is
active, delete at most 2.000 rows per transaction:
long nowMillis = clock.millis();
long cutoffSeconds = TimeUnit.MILLISECONDS.toSeconds(nowMillis)
- TimeUnit.DAYS.toSeconds(retentionDays);
do {
int n = db.deleteByIdBatch(
"scheduletable", "endtime < ?", cutoffSeconds, "endtime", 2000);
metrics.addDeleted(n);
if (n == 2000) SystemClock.sleep(100);
} while (n == 2000 && powerAndLoadPolicy.permitsMaintenance());
deleteByIdBatch must run one transaction containing an index-ordered ID
selection with LIMIT ? and a delete by those IDs. Do not build a 2.000-
placeholder SQL string; use a temp-ID table or
DELETE ... WHERE _id IN (SELECT _id ... ORDER BY endtime LIMIT 2000) with
bound cutoff. Commit each batch so rollback journal/lock duration is bounded.
The fixed Runnable should call this helper and log cutoff, unit, rows, duration,
DB bytes and remaining old-row count—not the misleading word “Deleting” before
any mutation. Schedule a unique daily WorkManager job with KEEP, not a chain.
Phase 6 deliberately limits the first production change to scheduletable.
Now/Next are tiny and FVP expiry semantics are not sufficiently proven for a
shared delete rule. They can be added only after table-specific retention tests.
Auto-vacuum / power / locks
The DB is auto_vacuum=FULL; small daily batches prevent the massive page-move
storm seen after years of accumulation. Use normal SQLite transactions and
busy_timeout/bounded retry with jitter; never delete while acquisition owns a
write transaction. A power loss can roll back one batch only. Do not VACUUM
during Live TV. For a one-time legacy migration, offer the already validated
compact-rebuild workflow with backup and atomic rename.
Smali plan
Replace the Runnable body after cutoff computation with a typed call to the new
helper; do not inline a shell or raw concatenated SQL payload. If adding DEX
methods is impractical, redirect to the already present
EpgProgramPump.deleteScheduleProgramsByTime(long) only after correcting its
unit and adding batching; its current unbounded delete is unsuitable for a
3-million-row legacy DB.
Acceptance
See patches/REGRESSION-TEST-PLAN.md. Key gate: after a 30-day synthetic soak,
oldest row >= cutoff (within one batch), DB growth plateaus, current/future EPG,
favorites and recording/reminder tables remain correct.