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('coffre'), 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.