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 <noreply@anthropic.com>
This commit is contained in:
r-zakarya
2026-08-15 18:51:57 +01:00
parent 773e47330f
commit 301a999596
+45 -4
View File
@@ -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;