feat(android): mark the clipboard sensitive, and lock after 3 min idle
Since Android 13 the system shows a preview of whatever was just copied, at the bottom of the screen. For a password that is precisely what we are trying to avoid: it appears in clear, outside the app, for several seconds. The flag that suppresses it is a ClipDescription extra, which IFMXClipboardService does not expose - hence going straight to ClipboardManager. Note Jcontent_ClipboardManager, not JClipboardManager: the latter is the deprecated android.text one, which knows only bare text and has no ClipData to carry an extra. Backgrounding already locks; the idle timer covers the phone left face-up on a table with the app open. It samples timestamps every 15 s rather than counting seconds - a wake-up per second would cost battery for no precision that matters. Attachments dropped from the plan: this vault has none. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+54
-15
@@ -24,7 +24,7 @@ interface
|
||||
|
||||
uses
|
||||
System.SysUtils, System.Types, System.UITypes, System.Classes,
|
||||
System.Diagnostics, System.Rtti, System.NetEncoding,
|
||||
System.Diagnostics, System.Rtti, System.NetEncoding, System.DateUtils,
|
||||
System.Generics.Collections,
|
||||
FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.Platform,
|
||||
FMX.Controls.Presentation, FMX.StdCtrls, FMX.Memo, FMX.Memo.Types,
|
||||
@@ -51,6 +51,11 @@ const
|
||||
// mot de passe traine dans l'historique du presse-papiers.
|
||||
CLIPBOARD_CLEAR_SECONDS = 45;
|
||||
|
||||
// Verrouillage sur inactivite au premier plan. Le passage en arriere-plan
|
||||
// verrouille deja ; ceci couvre le telephone pose sur une table, ecran
|
||||
// allume, application ouverte.
|
||||
AUTOLOCK_MINUTES = 3;
|
||||
|
||||
// TEST_PWD vient d'un fichier LOCAL, hors depot : il ouvre le coffre entier
|
||||
// une fois le conteneur telecharge. Absent apres un clone -> recopier
|
||||
// PMA.LocalConfig.template.inc.
|
||||
@@ -121,6 +126,12 @@ type
|
||||
FClipTimer: TTimer;
|
||||
FCopied: string;
|
||||
|
||||
// --- verrouillage sur inactivite
|
||||
FIdleTimer: TTimer;
|
||||
FLastActivity: TDateTime;
|
||||
procedure Touch;
|
||||
procedure OnIdleTimer(Sender: TObject);
|
||||
|
||||
procedure BuildUnlock;
|
||||
procedure BuildList;
|
||||
procedure BuildDetail;
|
||||
@@ -176,7 +187,7 @@ implementation
|
||||
|
||||
uses
|
||||
PMA.Crypto, PMA.SyncClient, PMA.Store, PMA.Biometric, PMA.Totp,
|
||||
PMA.ScreenGuard;
|
||||
PMA.ScreenGuard, PMA.Clipboard;
|
||||
|
||||
{$R *.fmx}
|
||||
|
||||
@@ -215,6 +226,13 @@ begin
|
||||
FTotpTimer.Interval := 1000;
|
||||
FTotpTimer.OnTimer := OnTotpTick;
|
||||
|
||||
// Tick grossier : on compare des horodatages, il ne sert qu'a echantillonner.
|
||||
// Une seconde de precision n'apporterait rien et reveillerait l'appareil.
|
||||
FIdleTimer := TTimer.Create(Self);
|
||||
FIdleTimer.Enabled := False;
|
||||
FIdleTimer.Interval := 15000;
|
||||
FIdleTimer.OnTimer := OnIdleTimer;
|
||||
|
||||
// Le bouton retour materiel arrive comme une touche : sans ce gestionnaire
|
||||
// il ferme l'application depuis le detail, au lieu de revenir a la liste.
|
||||
OnKeyUp := OnHardwareBack;
|
||||
@@ -532,6 +550,7 @@ var
|
||||
end;
|
||||
|
||||
begin
|
||||
Touch;
|
||||
ClearDetailRows;
|
||||
FCurrent := FVault.Entry(AVaultIndex);
|
||||
FDetTitle.Text := FCurrent.Display;
|
||||
@@ -673,6 +692,7 @@ begin
|
||||
// afficher - et sur un poste sans cache local (hors Android), plus aucun
|
||||
// moyen de rouvrir le coffre sans relancer l'application.
|
||||
FETag := '';
|
||||
FIdleTimer.Enabled := False;
|
||||
FVault.Clear;
|
||||
FList.Clear;
|
||||
FIcons.Clear;
|
||||
@@ -690,6 +710,8 @@ begin
|
||||
FVault.TrashCount.ToString + ' en corbeille');
|
||||
RebuildIcons;
|
||||
PopulateFolders;
|
||||
Touch;
|
||||
FIdleTimer.Enabled := True;
|
||||
// Deja dans l'application : reconstruire la liste SANS y ramener
|
||||
// l'utilisateur. Obligatoire meme s'il est dans le detail - les lignes de
|
||||
// la liste portent des indices vers l'ancien coffre, et un appui ouvrirait
|
||||
@@ -1060,6 +1082,7 @@ begin
|
||||
// ponytail: refonte complete de la liste a chaque frappe. Sur un coffre de
|
||||
// quelques centaines d'entrees c'est imperceptible ; passer a un modele
|
||||
// virtuel (TListView) le jour ou ca rame.
|
||||
Touch;
|
||||
LFolder := '';
|
||||
if FFolderBox.ItemIndex > 0 then // 0 = tous les dossiers
|
||||
LFolder := FFolderBox.Items[FFolderBox.ItemIndex];
|
||||
@@ -1105,6 +1128,7 @@ procedure TForm8.OnRevealTap(Sender: TObject);
|
||||
var
|
||||
I: Integer;
|
||||
begin
|
||||
Touch;
|
||||
if not (Sender is TButton) then Exit;
|
||||
I := TButton(Sender).Tag;
|
||||
if (I < 0) or (I > High(FRows)) then Exit;
|
||||
@@ -1126,6 +1150,7 @@ procedure TForm8.OnCopyTap(Sender: TObject);
|
||||
var
|
||||
I: Integer;
|
||||
begin
|
||||
Touch;
|
||||
if not (Sender is TButton) then Exit;
|
||||
I := TButton(Sender).Tag;
|
||||
// -1 : la ligne du TOTP, qui n'est pas dans FRows - sa valeur change chaque
|
||||
@@ -1137,13 +1162,12 @@ begin
|
||||
end;
|
||||
|
||||
procedure TForm8.CopyToClipboard(const AText, ALabel: string);
|
||||
var
|
||||
LClip: IFMXClipboardService;
|
||||
begin
|
||||
if AText = '' then Exit;
|
||||
if not TPlatformServices.Current.SupportsPlatformService(
|
||||
IFMXClipboardService, LClip) then Exit;
|
||||
LClip.SetClipboard(AText);
|
||||
// Marque le contenu sensible : sans cela Android 13+ affiche un apercu de ce
|
||||
// qui vient d'etre copie, mot de passe compris, en clair et hors de
|
||||
// l'application.
|
||||
if not CopySensitive(AText) then Exit;
|
||||
FCopied := AText;
|
||||
// Reprogramme depuis zero : deux copies successives donnent au second
|
||||
// contenu son delai complet, pas le reste de celui du premier.
|
||||
@@ -1157,17 +1181,12 @@ begin
|
||||
end;
|
||||
|
||||
procedure TForm8.OnClipTimer(Sender: TObject);
|
||||
var
|
||||
LClip: IFMXClipboardService;
|
||||
begin
|
||||
FClipTimer.Enabled := False;
|
||||
if FCopied = '' then Exit;
|
||||
if TPlatformServices.Current.SupportsPlatformService(
|
||||
IFMXClipboardService, LClip) then
|
||||
// Seulement si c'est TOUJOURS notre texte : l'utilisateur a pu copier
|
||||
// autre chose entre-temps, et le lui effacer serait une surprise.
|
||||
if LClip.GetClipboard.ToString = FCopied then
|
||||
LClip.SetClipboard('');
|
||||
// Seulement si c'est TOUJOURS notre texte : l'utilisateur a pu copier autre
|
||||
// chose entre-temps, et le lui effacer serait une surprise.
|
||||
if CurrentClipboard = FCopied then ClearClipboard;
|
||||
FCopied := '';
|
||||
end;
|
||||
|
||||
@@ -1211,6 +1230,26 @@ begin
|
||||
end);
|
||||
end;
|
||||
|
||||
procedure TForm8.Touch;
|
||||
begin
|
||||
FLastActivity := Now;
|
||||
end;
|
||||
|
||||
procedure TForm8.OnIdleTimer(Sender: TObject);
|
||||
begin
|
||||
// Rien d'ouvert : rien a verrouiller.
|
||||
if FVault.Count = 0 then
|
||||
begin
|
||||
FIdleTimer.Enabled := False;
|
||||
Exit;
|
||||
end;
|
||||
if MinutesBetween(Now, FLastActivity) >= AUTOLOCK_MINUTES then
|
||||
begin
|
||||
Log('Verrouille apres ' + AUTOLOCK_MINUTES.ToString + ' min d''inactivite');
|
||||
LockVault;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TForm8.Status(const AMsg: string);
|
||||
begin
|
||||
FStatus.Text := AMsg;
|
||||
|
||||
Reference in New Issue
Block a user