From 8dc0840e0321308586f9e84d2a975dc09da49134 Mon Sep 17 00:00:00 2001 From: r-zakarya <82443831+r-zakarya@users.noreply.github.com> Date: Sat, 15 Aug 2026 14:11:20 +0100 Subject: [PATCH] fix(android): declare FMX's biometric activity, and stop LoadVault from deleting the cache The prompt raised ActivityNotFoundException: FMX does not show BiometricPrompt from our NativeActivity, it launches its own BiometricFragmentActivity, which lives in fmx.dex.jar but has to be declared in the manifest. Given a translucent framework theme - it hosts the system dialog and draws nothing itself, and the generated resources contain no AppCompat. Then the read failed and the cache vanished with no trace. LoadVault sorted 'key lost' from 'not authenticated' on the exception message and deleted the file when it guessed the former - so a wrong guess destroyed the cache and the only record of why. It now reports through LastStoreError and deletes nothing; the caller has the log and the context. OpenLocalNow shows the reason, after ShowUnlock rather than before, which was overwriting it. Co-Authored-By: Claude Opus 5 --- AndroidManifest.template.xml | 15 +++++++++++++++ android-client/PMA.Store.pas | 37 +++++++++++++++++++++++------------- mainApk.pas | 5 ++++- 3 files changed, 43 insertions(+), 14 deletions(-) diff --git a/AndroidManifest.template.xml b/AndroidManifest.template.xml index 61006f1..6be2ed1 100644 --- a/AndroidManifest.template.xml +++ b/AndroidManifest.template.xml @@ -77,6 +77,21 @@ + + <%activity%> <%receivers%> diff --git a/android-client/PMA.Store.pas b/android-client/PMA.Store.pas index 1348cb4..9073b83 100644 --- a/android-client/PMA.Store.pas +++ b/android-client/PMA.Store.pas @@ -92,7 +92,16 @@ function VaultNeedsAuth: Boolean; /// range : l'appelant redemande alors, et le prochain enregistrement le pose. function LoadVault(out APlainJson, AETag, ASyncPwd: string): Boolean; -/// Efface le coffre local. A appeler au moindre doute : il se retelecharge. +/// Pourquoi la derniere lecture a echoue. Vide apres un succes. +/// +/// LoadVault N'EFFACE RIEN de lui-meme. Un cache detruit par une routine de +/// lecture, c'est un diagnostic errone qui coute un retelechargement et +/// 4,5 s de derivation - et qui emporte la cause avec lui. Elle rapporte, +/// l'appelant decide. +function LastStoreError: string; + +/// Efface le coffre local. Seul un geste explicite de l'utilisateur, ou une +/// certitude, devrait l'appeler : il se retelecharge, mais pas gratuitement. procedure ClearVault; /// Chemin du fichier, pour le journal et les tests. @@ -129,6 +138,14 @@ const VER_WRAP = 3; // AES aleatoire enveloppee par la cle publique RSA RSA_TRANSFORM = 'RSA/ECB/OAEPWithSHA-256AndMGF1Padding'; +var + FLastStoreError: string = ''; + +function LastStoreError: string; +begin + Result := FLastStoreError; +end; + function VaultPath: string; begin // GetHomePath sur Android = /data/data//files, prive a l'app. @@ -678,6 +695,7 @@ begin APlainJson := ''; AETag := ''; ASyncPwd := ''; + FLastStoreError := ''; if not TFile.Exists(VaultPath) then Exit; try @@ -743,18 +761,11 @@ begin on E: Exception do begin Result := False; - // NE PAS effacer sur un simple defaut d'authentification : le coffre est - // intact, c'est l'utilisateur qui n'a pas presente son schema ou qui a - // laisse expirer le delai. L'effacer imposerait un retelechargement et - // 4,5 secondes de derivation pour une empreinte annulee. - // - // On efface en revanche quand la cle est REELLEMENT perdue - // (reinstallation, Keystore vide, nouvelle empreinte enrolee qui - // invalide la cle) ou quand le fichier est altere : dans ces cas il ne - // redeviendra jamais lisible. - if (Pos('UserNotAuthenticated', E.Message) = 0) and - (Pos('NotAuthenticated', E.ClassName) = 0) then - ClearVault; + // ON N'EFFACE PAS. Le tri « cle perdue / pas authentifie » se faisait + // ici sur le texte du message, et une erreur de tri detruisait le cache + // en emportant la seule trace de sa cause. L'appelant a le journal et le + // contexte - lui seul sait si l'utilisateur a demande quelque chose. + FLastStoreError := E.ClassName + ': ' + E.Message; end; end; end; diff --git a/mainApk.pas b/mainApk.pas index 949f2f7..80e801d 100644 --- a/mainApk.pas +++ b/mainApk.pas @@ -727,8 +727,11 @@ begin end else begin - Status('Coffre local illisible - retelechargement necessaire'); + // ShowUnlock AVANT le message : il repositionne le statut selon ce que + // l'appareil detient, et ecraserait donc l'explication. ShowUnlock; + Log('Coffre local illisible : ' + LastStoreError); + Status('Coffre local illisible - voir le journal'); end; end;