Francisco 1 Posted September 30, 2019 (edited) When targeting newer Android SDK for uploading an app to Google's store I obtain an error when sending a file as an attachment: android.os.FileUriExposedException: file:///storage/emulated/0/Documents/filename.jpg exposed beyond app through ClipData.Item.getUri() The code was working fine in Tokyo. procedure CreateEmail(const Recipient, Subject, Content, Attachment: string); var JRecipient: TJavaObjectArray<JString>; Intent: JIntent; Uri: Jnet_Uri; AttachmentFile: JFile; begin JRecipient := TJavaObjectArray<JString>.Create(1); JRecipient.Items[0] := StringToJString(Recipient); Intent := TJIntent.Create; Intent.setAction(TJIntent.JavaClass.ACTION_SEND); Intent.setFlags(TJIntent.JavaClass.FLAG_ACTIVITY_NEW_TASK); Intent.putExtra(TJIntent.JavaClass.EXTRA_EMAIL, JRecipient); Intent.putExtra(TJIntent.JavaClass.EXTRA_SUBJECT, StringToJString(Subject)); Intent.putExtra(TJIntent.JavaClass.EXTRA_TEXT, StringToJString(Content)); if Attachment <> '' then begin AttachmentFile := TJFile.JavaClass.init(StringToJString(Attachment)); Uri := TJnet_Uri.JavaClass.fromFile(AttachmentFile); Intent.putExtra(TJIntent.JavaClass.EXTRA_STREAM, TJParcelable.Wrap((Uri as ILocalObject).GetObjectID)); end; Intent.setType(StringToJString('vnd.android.cursor.dir/email')); TandroidHelper.Activity.startActivity(Intent); end; I have tried different codes I found over there. No one works. I have activated "Secure File Sharing" in Project Options. I have tried a code using DW.Androidapi.JNI.FileProvider.pas But I cannot get one working. Thanks in advance. Edited September 30, 2019 by Francisco Share this post Link to post
Remy Lebeau 1394 Posted September 30, 2019 (edited) You are simply not allowed to use `file://` URLs to share files via Intents anymore. Which means your call to Uri := TJnet_Uri.JavaClass.fromFile(AttachmentFile); Will still produce a valid 'file://' URL, you just can't use it in your Intent anymore. You need to add a FileProvider to your Android app and manifest, and then you can use 'content://' URLs serviced by that provider for any files you want to share access to. See Sharing files and Sharing files through Intents: are you ready for Nougat? for more details. Edited October 1, 2019 by Remy Lebeau Share this post Link to post
Francisco 1 Posted October 1, 2019 (edited) Thanks Remy. This seems tricky. The first thing I have done is including in the manifest file the following code: <provider android:name="android.support.v4.content.FileProvider" android:authorities="com.FJAG.StatSuite.fileprovider" android:grantUriPermissions="true" android:exported="false"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/filepaths" /> </provider> Then I create a file with name filepaths.xml with the following content: <?xml version="1.0" encoding="utf-8"?> <paths> <external-path name="external_files" path="." /> </paths> In RAD Studio, deployment, I add this file with remote path: res\xml\ My procedure to send the email with an attachmet is: procedure CreateEmail(const Recipient, Subject, Content, Attachment: string); var JRecipient: TJavaObjectArray<JString>; Intent: JIntent; Uri, data: Jnet_Uri; AttachmentFile: JFile; begin JRecipient := TJavaObjectArray<JString>.Create(1); JRecipient.Items[0] := StringToJString(Recipient); Intent := TJIntent.Create; Intent.setAction(TJIntent.JavaClass.ACTION_SEND); Intent.setFlags(TJIntent.JavaClass.FLAG_ACTIVITY_NEW_TASK); Intent.putExtra(TJIntent.JavaClass.EXTRA_EMAIL, JRecipient); Intent.putExtra(TJIntent.JavaClass.EXTRA_SUBJECT, StringToJString(Subject)); Intent.putExtra(TJIntent.JavaClass.EXTRA_TEXT, StringToJString(Content)); if TJBuild_VERSION.JavaClass.SDK_INT >= TJBuild_VERSION_CODES.JavaClass.N then begin AttachmentFile := TJFile.JavaClass.init(StringToJString(Attachment)); Intent.setFlags(TJIntent.JavaClass.FLAG_GRANT_READ_URI_PERMISSION); Data := TJFileProvider.JavaClass.getUriForFile(TAndroidHelper.Context, StringToJString('com.FJAG.StatSuite.fileprovider'), AttachmentFile); end else Data := TJnet_Uri.JavaClass.parse(StringToJString('file://' + Attachment)); Intent.putExtra(TJIntent.JavaClass.EXTRA_STREAM, TJParcelable.Wrap((Data as ILocalObject).GetObjectID)); TAndroidHelper.Activity.startActivity(Intent); end; The error when calling this procedure is: android.content.ActivityNotFoundException: No activity found to handle Intent {act=android.intent.action.SEND flg=0x1 launchParam=MultiScreenLaunchParams {mDisplayld=0 mFlags=0} clip={null T:A StatSuite file is attached} (has extras)} "A StatSuite file is attached" is the subject of the email Please, a couple of questions: 1.- Must I activate "Secure File Sharing" in the Entitlement List? (it is activated now) 2.- What am I doing wrong? Thanks in advance Edited October 1, 2019 by Francisco Share this post Link to post
Remy Lebeau 1394 Posted October 1, 2019 6 hours ago, Francisco said: Intent.setFlags(TJIntent.JavaClass.FLAG_GRANT_READ_URI_PERMISSION); That should be using addFlags() instead of setFlags(). By using setFlags(), you are overwriting your FLAG_ACTIVITY_NEW_TASK flag. 6 hours ago, Francisco said: Data := TJnet_Uri.JavaClass.parse(StringToJString('file://' + Attachment)); You can still use TJnet_Uri.JavaClass.fromFile() in earlier Android versions. You didn't need to change that part of the code to use TJnet_Uri.JavaClass.parse() instead. Share this post Link to post
Francisco 1 Posted October 1, 2019 Remy, I modified that line to: Intent.addFlags(TJIntent.JavaClass.FLAG_GRANT_READ_URI_PERMISSION); And I am still obtaining the same error: android.content.ActivityNotFoundException: No activity found to handle Intent {act=android.intent.action.SEND flg=0x1 launchParam=MultiScreenLaunchParams {mDisplayld=0 mFlags=0} clip={null T:A StatSuite file is attached} (has extras)} Share this post Link to post
Dave Nottage 557 Posted October 1, 2019 17 minutes ago, Francisco said: android.content.ActivityNotFoundException: No activity found to handle Intent {act=android.intent.action.SEND You need to call Intent.setType with an appropriate type for email, e.g. plain/text message/rfc822 application/octet-stream If that still does not work, then there's probably no email client installed. Share this post Link to post
Francisco 1 Posted October 1, 2019 It works now!!! Tested in Android 7 and 9 I added: Intent.setType(StringToJString('vnd.android.cursor.dir/email')); Therefore, I would like to share a procedure that permits to send an email with attachment (any type of file) in Android. Do not forget to modify the AndroidManifest file and to create the xml file, as commented before. procedure CreateEmail(const Recipient, Subject, Content, Attachment: string); var JRecipient: TJavaObjectArray<JString>; Intent: JIntent; Uri, data: Jnet_Uri; AttachmentFile: JFile; begin JRecipient := TJavaObjectArray<JString>.Create(1); JRecipient.Items[0] := StringToJString(Recipient); Intent := TJIntent.Create; Intent.setAction(TJIntent.JavaClass.ACTION_SEND); Intent.setFlags(TJIntent.JavaClass.FLAG_ACTIVITY_NEW_TASK); Intent.putExtra(TJIntent.JavaClass.EXTRA_EMAIL, JRecipient); Intent.putExtra(TJIntent.JavaClass.EXTRA_SUBJECT, StringToJString(Subject)); Intent.putExtra(TJIntent.JavaClass.EXTRA_TEXT, StringToJString(Content)); if TJBuild_VERSION.JavaClass.SDK_INT >= TJBuild_VERSION_CODES.JavaClass.N then begin AttachmentFile := TJFile.JavaClass.init(StringToJString(Attachment)); Intent.addFlags(TJIntent.JavaClass.FLAG_GRANT_READ_URI_PERMISSION); Data := TJFileProvider.JavaClass.getUriForFile(TAndroidHelper.Context, StringToJString('com.FJAG.StatSuite.fileprovider'), AttachmentFile); end else Data := TJnet_Uri.JavaClass.parse(StringToJString('file://' + Attachment)); Intent.putExtra(TJIntent.JavaClass.EXTRA_STREAM, TJParcelable.Wrap((Data as ILocalObject).GetObjectID)); Intent.setType(StringToJString('vnd.android.cursor.dir/email')); TAndroidHelper.Activity.startActivity(Intent); end; Thanks a lot to Remy and Dave!! Share this post Link to post
MichaMD 0 Posted October 25, 2021 Hey, i'm trying to follow your example.=) But i get a problem. It does not work.... XD i get a popup: java.lang.nullpointerException:attempt to invoke virtual method 'android.content.res.xmlResourceParser android.content.pm.ProviderInfo.loadXmlMetaData(android.content.pm.PackageManager,Java.lang.String)' on a null object reference. Do you have any idea, what the problem could be? I already tried it with files-path vs. external-path in the xml, with or without <paths xmlns:android="http://schemas.android.com/apk/res/android">, activated "save exchange" and wrote and deleted the provider in different parts of the manifest.=/ Share this post Link to post
Dave Nottage 557 Posted October 25, 2021 2 hours ago, MichaMD said: i get a popup: java.lang.nullpointerException:attempt to invoke virtual method 'android.content.res.xmlResourceParser android.content.pm.ProviderInfo.loadXmlMetaData(android.content.pm.PackageManager,Java.lang.String)' on a null object reference. This is usually because the Secure File Sharing checkbox in the Entitlements List in the Project Options is unchecked, and it needs to be checked Share this post Link to post
MichaMD 0 Posted October 25, 2021 5 hours ago, Dave Nottage said: This is usually because the Secure File Sharing checkbox in the Entitlements List in the Project Options is unchecked, and it needs to be checked That was what i meant with "activated "save exchange" ", got lost in translation. Something i only found here.^^ My Code example: unit Main; interface uses System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants, FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.StdCtrls, FMX.Controls.Presentation, System.IOUtils, System.Messaging, System.StrUtils, FMX.Edit, FMX.Layouts, Androidapi.JNI.Net, Androidapi.JNI.App, Androidapi.JNI.JavaTypes, Androidapi.JNI.Os, Androidapi.JNI.GraphicsContentViewText, Androidapi.Helpers, Androidapi.JNIBridge, Androidapi.JNI.Media, Androidapi.JNI.Support; type TForm1 = class(TForm) GridPanelLayout1: TGridPanelLayout; Button1: TButton; EmailAddy: TEdit; Betreff: TEdit; Text: TEdit; procedure Button1Click(Sender: TObject); private { Private-Deklarationen } procedure CreateCSVForAttachment(Path:string); Procedure SendMail(const Recipient, Subject, MailBody, Attachment: string); public { Public-Deklarationen } end; var Form1: TForm1; implementation {$R *.fmx} procedure TForm1.Button1Click(Sender: TObject); var FileToWrite: string; TestRecipient: string; TestSubject: string; TestMailBody: string; begin FileToWrite:='Example.csv'; TestRecipient:='Dummy.Dummy@Dummy.de'; TestSubject:='Emailtest'; TestMailBody:='blabla'; CreateCSVForAttachment(System.IOUtils.TPath.GetDocumentsPath + System.SysUtils.PathDelim + FileToWrite); //crate file to try sending SendMail(TestRecipient,TestSubject,TestMailBody,System.IOUtils.TPath.GetDocumentsPath + System.SysUtils.PathDelim + FileToWrite); end; Procedure TForm1.CreateCSVForAttachment(Path:string); var strList: TStringList; a:string; begin StrList := TStringList.Create; a:='Lorem ipsum dolor sit amet, consetetur sadipscing elitr,'; StrList.Add(a); a:='Lorem ipsum dolor sit amet: consetetur sadipscing elitr:'; StrList.Add(a); a:='Lorem ipsum dolor sit amet; consetetur sadipscing elitr;'; StrList.Add(a); try begin StrList.SaveToFile(Path); showmessage(Path); end; finally StrList.Free; end; end; Procedure TForm1.SendMail(const Recipient, Subject, MailBody, Attachment: string); var Intent: JIntent; listRecipients: TJavaObjectArray<JString>; Data:Jnet_Uri; AttachmentFile: JFile; begin listRecipients:=TJavaObjectArray<JString>.Create(1); listRecipients.Items[0]:= StringToJString(Recipient); Intent:= TJIntent.create; Intent.setAction(TJIntent.javaClass.ACTION_SEND); Intent.setFlags(TJIntent.JavaClass.FLAG_ACTIVITY_NEW_TASK); //Found many Codeexamples without this part, necessary? Intent.putExtra(TJIntent.javaClass.EXTRA_EMAIL,listRecipients); //Intent.putExtra(TJIntent.javaClass.EXTRA_CC,listRecipients); //Intent.putExtra(TJIntent.javaClass.EXTRA_Bcc,listRecipients); Intent.putExtra(TJIntent.javaClass.EXTRA_SUBJECT,StringToJString(Subject)); Intent.putExtra(TJIntent.javaClass.EXTRA_TEXT,STringToJString(MailBody)); if Attachment<>'' then begin if TJBuild_Version.JavaClass.SDK_INT>=TJBuild_VERSION_CODES.JavaClass.N then begin AttachmentFile := TJFile.JavaClass.init(StringToJString(Attachment)); //add 'internal_files/'+ ? Intent.addFlags(TJIntent.JavaClass.FLAG_GRANT_READ_URI_PERMISSION); Data:=TJFileProvider.JavaClass.getUriForFile(TAndroidHelper.Context, StringToJString('com.embarcadero.EMail.fileprovider'),AttachmentFile);// Found many Codeexamples with .provider in contrast . fileprovider, //com.embarcadero.projectname.fileprovider ? end else begin Data:=TJnet_Uri.JavaClass.parse(STringToJString('file://'+Attachment)); end; Intent.putExtra(TJIntent.JavaClass.EXTRA_STREAM, TJParcelable.Wrap((Data as ILocalObject).GetObjectID)); //found often TJnet_Uri.JavaClass.fromFile(AttachmentFile); ? end; Intent.setType(StringToJString('vnd.android.cursor.dir/mail')); {tryed "text/plain" or "*/*" or message/rfc822 or vnd.android.cursor.dir/mail or vnd.android.cursor.dir/*} TAndroidHelper.Activity.startActivity(Intent); end; end. Changed AndroidManifest.template.xml: from: <application android:persistent="%persistent%" android:restoreAnyVersion="%restoreAnyVersion%" android:label="%label%" android:debuggable="%debuggable%" android:largeHeap="%largeHeap%" android:icon="%icon%" android:theme="%theme%" android:hardwareAccelerated="%hardwareAccelerated%" android:resizeableActivity="false"> <%provider%> <%application-meta-data%> <%uses-libraries%> to: <application android:persistent="%persistent%" android:restoreAnyVersion="%restoreAnyVersion%" android:label="%label%" android:debuggable="%debuggable%" android:largeHeap="%largeHeap%" android:icon="%icon%" android:theme="%theme%" android:hardwareAccelerated="%hardwareAccelerated%" android:resizeableActivity="false"> <provider android:name="android.support.v4.content.FileProvider" android:authorities="com.embarcadero.Email.fileprovider" android:grantUriPermissions="true" android:exported="false"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/filepaths" /> </provider> <%application-meta-data%> <%uses-libraries%> <%services%> And added Android/Debug/Email/res +xml/filepaths.xml with: <?xml version="1.0" encoding="utf-8"?> <paths> <external-path name="external_files" path="." /> </paths> Share this post Link to post
aetsmith 0 Posted November 5 On 10/25/2021 at 1:13 PM, MichaMD said: That was what i meant with "activated "save exchange" ", got lost in translation. Something i only found here.^^ My Code example: unit Main; interface uses System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants, FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.StdCtrls, FMX.Controls.Presentation, System.IOUtils, System.Messaging, System.StrUtils, FMX.Edit, FMX.Layouts, Androidapi.JNI.Net, Androidapi.JNI.App, Androidapi.JNI.JavaTypes, Androidapi.JNI.Os, Androidapi.JNI.GraphicsContentViewText, Androidapi.Helpers, Androidapi.JNIBridge, Androidapi.JNI.Media, Androidapi.JNI.Support; type TForm1 = class(TForm) GridPanelLayout1: TGridPanelLayout; Button1: TButton; EmailAddy: TEdit; Betreff: TEdit; Text: TEdit; procedure Button1Click(Sender: TObject); private { Private-Deklarationen } procedure CreateCSVForAttachment(Path:string); Procedure SendMail(const Recipient, Subject, MailBody, Attachment: string); public { Public-Deklarationen } end; var Form1: TForm1; implementation {$R *.fmx} procedure TForm1.Button1Click(Sender: TObject); var FileToWrite: string; TestRecipient: string; TestSubject: string; TestMailBody: string; begin FileToWrite:='Example.csv'; TestRecipient:='Dummy.Dummy@Dummy.de'; TestSubject:='Emailtest'; TestMailBody:='blabla'; CreateCSVForAttachment(System.IOUtils.TPath.GetDocumentsPath + System.SysUtils.PathDelim + FileToWrite); //crate file to try sending SendMail(TestRecipient,TestSubject,TestMailBody,System.IOUtils.TPath.GetDocumentsPath + System.SysUtils.PathDelim + FileToWrite); end; Procedure TForm1.CreateCSVForAttachment(Path:string); var strList: TStringList; a:string; begin StrList := TStringList.Create; a:='Lorem ipsum dolor sit amet, consetetur sadipscing elitr,'; StrList.Add(a); a:='Lorem ipsum dolor sit amet: consetetur sadipscing elitr:'; StrList.Add(a); a:='Lorem ipsum dolor sit amet; consetetur sadipscing elitr;'; StrList.Add(a); try begin StrList.SaveToFile(Path); showmessage(Path); end; finally StrList.Free; end; end; Procedure TForm1.SendMail(const Recipient, Subject, MailBody, Attachment: string); var Intent: JIntent; listRecipients: TJavaObjectArray<JString>; Data:Jnet_Uri; AttachmentFile: JFile; begin listRecipients:=TJavaObjectArray<JString>.Create(1); listRecipients.Items[0]:= StringToJString(Recipient); Intent:= TJIntent.create; Intent.setAction(TJIntent.javaClass.ACTION_SEND); Intent.setFlags(TJIntent.JavaClass.FLAG_ACTIVITY_NEW_TASK); //Found many Codeexamples without this part, necessary? Intent.putExtra(TJIntent.javaClass.EXTRA_EMAIL,listRecipients); //Intent.putExtra(TJIntent.javaClass.EXTRA_CC,listRecipients); //Intent.putExtra(TJIntent.javaClass.EXTRA_Bcc,listRecipients); Intent.putExtra(TJIntent.javaClass.EXTRA_SUBJECT,StringToJString(Subject)); Intent.putExtra(TJIntent.javaClass.EXTRA_TEXT,STringToJString(MailBody)); if Attachment<>'' then begin if TJBuild_Version.JavaClass.SDK_INT>=TJBuild_VERSION_CODES.JavaClass.N then begin AttachmentFile := TJFile.JavaClass.init(StringToJString(Attachment)); //add 'internal_files/'+ ? Intent.addFlags(TJIntent.JavaClass.FLAG_GRANT_READ_URI_PERMISSION); Data:=TJFileProvider.JavaClass.getUriForFile(TAndroidHelper.Context, StringToJString('com.embarcadero.EMail.fileprovider'),AttachmentFile);// Found many Codeexamples with .provider in contrast . fileprovider, //com.embarcadero.projectname.fileprovider ? end else begin Data:=TJnet_Uri.JavaClass.parse(STringToJString('file://'+Attachment)); end; Intent.putExtra(TJIntent.JavaClass.EXTRA_STREAM, TJParcelable.Wrap((Data as ILocalObject).GetObjectID)); //found often TJnet_Uri.JavaClass.fromFile(AttachmentFile); ? end; Intent.setType(StringToJString('vnd.android.cursor.dir/mail')); {tryed "text/plain" or "*/*" or message/rfc822 or vnd.android.cursor.dir/mail or vnd.android.cursor.dir/*} TAndroidHelper.Activity.startActivity(Intent); end; end. Changed AndroidManifest.template.xml: from: <application android:persistent="%persistent%" android:restoreAnyVersion="%restoreAnyVersion%" android:label="%label%" android:debuggable="%debuggable%" android:largeHeap="%largeHeap%" android:icon="%icon%" android:theme="%theme%" android:hardwareAccelerated="%hardwareAccelerated%" android:resizeableActivity="false"> <%provider%> <%application-meta-data%> <%uses-libraries%> to: <application android:persistent="%persistent%" android:restoreAnyVersion="%restoreAnyVersion%" android:label="%label%" android:debuggable="%debuggable%" android:largeHeap="%largeHeap%" android:icon="%icon%" android:theme="%theme%" android:hardwareAccelerated="%hardwareAccelerated%" android:resizeableActivity="false"> <provider android:name="android.support.v4.content.FileProvider" android:authorities="com.embarcadero.Email.fileprovider" android:grantUriPermissions="true" android:exported="false"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/filepaths" /> </provider> <%application-meta-data%> <%uses-libraries%> <%services%> And added Android/Debug/Email/res +xml/filepaths.xml with: <?xml version="1.0" encoding="utf-8"?> <paths> <external-path name="external_files" path="." /> </paths> Hello all. I have been battling for weeks to send an email attachment in my documents directory via my app. I have tried so many variations but none has ever worked. Your full example here does not compile on Delphi 11 as TJFileProvider is unknown. Where do I get that? Please help. Share this post Link to post
Remy Lebeau 1394 Posted November 5 7 hours ago, aetsmith said: Your full example here does not compile on Delphi 11 as TJFileProvider is unknown. Where do I get that? Please help. If it is not already in one of the RTL's 'Androidapi.*.pas' units (did you try grepping for it?), then you'll have to import it manually using Java2Op or similar tool, or use a 3rd party unit that imports it (like this one). Also, this statement: StringToJString('com.embarcadero.EMail.fileprovider') should be this instead: StringToJString(JStringToString(TAndroidHelper.Context.getApplicationContext.getPackageName) + '.fileprovider') Share this post Link to post
Dave Nottage 557 Posted November 6 On 11/5/2024 at 8:41 PM, aetsmith said: Your full example here does not compile on Delphi 11 as TJFileProvider is unknown It's: TJcontent_FileProvider instead. Share this post Link to post