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; var LMgr: Jcontent_ClipboardManager; begin // clearPrimaryClip QUAND IL EXISTE, et un contenu vide seulement en dessous // de l'API 28. // // Le resultat n'est PAS le meme, contrairement a ce qui etait ecrit ici : // ecrire un contenu vide reste une COPIE aux yeux du systeme, et Android // affiche donc sa vignette "copie" - quarante-cinq secondes apres le geste // de l'utilisateur, sans que rien ne l'explique. Vider n'est pas copier, et // l'API le distingue depuis 2018. // // Elle ne se montre pas a chaque fois : le minuteur ne vide que si le // presse-papiers contient TOUJOURS notre texte, donc copier autre chose // entre-temps la supprime. D'ou une apparition sur deux. try LMgr := Manager; if LMgr = nil then Exit; if TJBuild_VERSION.JavaClass.SDK_INT >= 28 then LMgr.clearPrimaryClip else CopySensitive(''); except // Un presse-papiers qu'on n'a pas pu vider vaut mieux qu'une exception : // le contenu partira au prochain copier de l'utilisateur. end; 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.