Files
Password-Manager/android-client/PMA.Clipboard.pas
T
r-zakarya bc89ec0010 i18n(android): the interface speaks English, like the desktop app
It was in French because the conversation is, and I never asked. The desktop
app the client mirrors says Add field, Attach file, Show previous versions -
someone moving between the two would notice the seam.

Every visible string moved: buttons, statuses, detail row captions, log lines
and the biometric prompt, plus the messages the modules surface through
LastStoreError and LastBiometricError.

Code comments stay in French. They address the author, not the user, and that
is already the language of CLAUDE.md and much of the repo.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-15 17:59:31 +01:00

137 lines
4.2 KiB
ObjectPascal

unit PMA.Clipboard;
{
Copie dans le presse-papiers, en marquant le contenu SENSIBLE.
POURQUOI PAS IFMXClipboardService : depuis Android 13, le systeme affiche un
apercu de ce qui vient d'etre copie, en bas de l'ecran. Pour un mot de passe,
c'est exactement ce qu'on cherche a eviter - il s'affiche en clair, hors de
l'application, et reste visible quelques secondes.
Le drapeau qui supprime cet apercu est un extra du ClipDescription, que le
service FMX n'expose pas. D'ou ce passage direct par ClipboardManager.
Cela ne remplace pas l'effacement differe : le drapeau cache l'apercu, il ne
vide pas le presse-papiers.
}
interface
/// Copie ATexte. Retourne False si la plateforme n'a rien fourni - l'appelant
/// peut alors retomber sur le service FMX.
function CopySensitive(const AText: string): Boolean;
/// Contenu courant, ou chaine vide. Sert a ne vider le presse-papiers que
/// s'il contient toujours ce qu'on y a mis.
function CurrentClipboard: string;
/// Vide le presse-papiers.
procedure ClearClipboard;
implementation
{$IF DEFINED(ANDROID)}
uses
System.SysUtils, Androidapi.Helpers, Androidapi.JNI.JavaTypes,
Androidapi.JNI.GraphicsContentViewText, Androidapi.JNI.Os, Androidapi.JNIBridge;
const
// android.content.ClipDescription.EXTRA_IS_SENSITIVE, ecrit en dur : la
// constante n'existe qu'a partir de l'API 33 dans les en-tetes, alors que la
// valeur est reconnue par le systeme des qu'il sait la lire, et ignoree
// ailleurs. Un extra inconnu ne derange personne.
EXTRA_IS_SENSITIVE = 'android.content.extra.IS_SENSITIVE';
/// Jcontent_ClipboardManager et NON JClipboardManager : ce dernier est
/// l'android.text.ClipboardManager deprecie, qui ne connait que du texte nu et
/// aucun ClipData - donc aucun extra ou poser le drapeau.
function Manager: Jcontent_ClipboardManager;
var
LObj: JObject;
begin
LObj := TAndroidHelper.Context.getSystemService(
TJContext.JavaClass.CLIPBOARD_SERVICE);
if LObj = nil then Exit(nil);
Result := TJcontent_ClipboardManager.Wrap((LObj as ILocalObject).GetObjectID);
end;
function CopySensitive(const AText: string): Boolean;
var
LMgr: Jcontent_ClipboardManager;
LClip: JClipData;
LExtras: JPersistableBundle;
begin
Result := False;
try
LMgr := Manager;
if LMgr = nil then Exit;
// L'etiquette est visible du systeme : elle ne doit rien decrire du
// contenu, seulement son origine.
// Les DEUX arguments sont des CharSequence : passer un JString au premier
// compile ailleurs mais pas ici.
LClip := TJClipData.JavaClass.newPlainText(StrToJCharSequence('vault'),
StrToJCharSequence(AText));
LExtras := TJPersistableBundle.JavaClass.init;
LExtras.putBoolean(StringToJString(EXTRA_IS_SENSITIVE), True);
LClip.getDescription.setExtras(LExtras);
LMgr.setPrimaryClip(LClip);
Result := True;
except
Result := False; // l'appelant retombera sur le service FMX
end;
end;
function CurrentClipboard: string;
var
LMgr: Jcontent_ClipboardManager;
LClip: JClipData;
begin
Result := '';
try
LMgr := Manager;
if (LMgr = nil) or not LMgr.hasPrimaryClip then Exit;
LClip := LMgr.getPrimaryClip;
if (LClip = nil) or (LClip.getItemCount < 1) then Exit;
Result := JCharSequenceToStr(LClip.getItemAt(0).getText);
except
Result := '';
end;
end;
procedure ClearClipboard;
begin
// Un contenu vide plutot qu'un appel a clearPrimaryClip, absent avant l'API
// 28 : le resultat visible est le meme.
CopySensitive('');
end;
{$ELSE}
uses
System.SysUtils, System.Rtti, FMX.Platform;
function CopySensitive(const AText: string): Boolean;
var
LClip: IFMXClipboardService;
begin
// Hors Android il n'y a pas d'apercu systeme a supprimer : le service
// standard suffit.
Result := TPlatformServices.Current.SupportsPlatformService(
IFMXClipboardService, LClip);
if Result then LClip.SetClipboard(AText);
end;
function CurrentClipboard: string;
var
LClip: IFMXClipboardService;
begin
Result := '';
if TPlatformServices.Current.SupportsPlatformService(IFMXClipboardService, LClip) then
Result := LClip.GetClipboard.ToString;
end;
procedure ClearClipboard;
begin
CopySensitive('');
end;
{$ENDIF}
end.