From 301a99959604ad64b74a3ce82ff416634b3a782d Mon Sep 17 00:00:00 2001 From: r-zakarya <82443831+r-zakarya@users.noreply.github.com> Date: Sat, 15 Aug 2026 18:51:57 +0100 Subject: [PATCH] feat(android): a short grace period before backgrounding locks the vault Locking the instant the app went behind made the commonest gesture painful: copy a password, switch across to paste it, come back - and authenticate again, with the 2FA code once more behind a prompt. The screen is still hidden immediately and unconditionally; that is the part that protects, and it still runs before the recents snapshot. Only the plaintext now survives, for thirty seconds. Long enough for a round trip, short enough that a phone left down does not stay open. Returning in time restores the screen you left, detail or list. Also drops the last ClearVault, in the manual open path. Nothing in the app destroys the cache any more - only a successful write replaces it. An unreadable file costs a few hundred kilobytes; three times today a wrong verdict cost the cache instead. Co-Authored-By: Claude Opus 5 --- mainApk.pas | 49 +++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 45 insertions(+), 4 deletions(-) diff --git a/mainApk.pas b/mainApk.pas index b0631f1..b802f89 100644 --- a/mainApk.pas +++ b/mainApk.pas @@ -56,6 +56,11 @@ const // allume, application ouverte. AUTOLOCK_MINUTES = 3; + // Sursis avant de jeter le clair quand l'application passe derriere. Assez + // pour aller coller un mot de passe ailleurs et revenir ; trop court pour + // qu'un telephone repose ouvert. + BACKGROUND_GRACE_SECONDS = 30; + // FIRSTRUN_TOKEN et TEST_PWD viennent d'un fichier LOCAL, hors depot : le // jeton authentifie l'appareil, le mot de passe ouvre le coffre entier. Ni // l'un ni l'autre n'a sa place dans un depot, meme sans remote. Absents apres @@ -136,6 +141,10 @@ type // --- verrouillage sur inactivite FIdleTimer: TTimer; FLastActivity: TDateTime; + /// Quand l'application est passee derriere, et quel ecran elle a quitte. + /// 0 = elle est au premier plan. + FBackgroundAt: TDateTime; + FHiddenOnDetail: Boolean; procedure Touch; procedure OnIdleTimer(Sender: TObject); @@ -930,10 +939,16 @@ begin end else begin - Log('No protection left on this device - cache cleared'); + // ON N'EFFACE PLUS, MEME ICI. C'etait le dernier endroit ou + // l'application detruisait le cache, sur la foi d'un verdict qui + // s'est deja revele fragile aujourd'hui. Le garder ne coute que + // quelques centaines de kilo-octets, et un telechargement le + // remplace de toute facon. Desormais RIEN ne detruit le cache : + // seule une ecriture reussie le remplace. + Log('No usable protection on this device - vault kept but unreadable'); Log(' ' + LastBiometricError); - ClearVault; ShowUnlock; + Status('Screen lock missing - download again to replace the cache'); end; else // boFailed : la couche Java a leve quelque chose. Cela ne dit rien de @@ -1462,16 +1477,42 @@ begin case AAppEvent of // WillBecomeInactive arrive AVANT la capture d'ecran des applications // recentes : masquer ici, c'est ne pas y laisser la liste des comptes. + // Le masquage est IMMEDIAT et sans condition - c'est lui qui protege. TApplicationEvent.WillBecomeInactive: if FLayList.Visible or FLayDetail.Visible then begin + FHiddenOnDetail := FLayDetail.Visible; FLayDetail.Visible := False; FLayList.Visible := False; FLayUnlock.Visible := True; end; - // Et le clair est reellement jete quand l'application passe derriere. + + // Le clair, lui, survit a un bref passage en arriere-plan. + // + // Verrouiller instantanement rendait le geste le plus courant penible : + // copier un mot de passe, basculer pour le coller, revenir - et devoir se + // reauthentifier, avec le code 2FA de nouveau derriere une invite. L'ecran + // etant deja masque, garder le clair quelques secondes de plus n'expose + // rien de visible ; c'est le temps d'un aller-retour, pas d'un oubli. TApplicationEvent.EnteredBackground: - if FVault.Count > 0 then LockVault; + FBackgroundAt := Now; + + TApplicationEvent.BecameActive: + if FVault.Count > 0 then + begin + if (FBackgroundAt > 0) and + (SecondsBetween(Now, FBackgroundAt) >= BACKGROUND_GRACE_SECONDS) then + LockVault + else + begin + // Retour a temps : on rend l'ecran quitte, pas un autre. + FLayUnlock.Visible := False; + FLayDetail.Visible := FHiddenOnDetail; + FLayList.Visible := not FHiddenOnDetail; + Touch; + end; + FBackgroundAt := 0; + end; end; end;