I am trying to send base64 image through the Intent. I've read that if the image is big, I need to put it to the internal storage first and then retrieve it from its path to send it by Intent. The problem is that android is giving me toast like this:
The upload was unsuccessful because it did not contain data.
I need to say that everything is done in my custom WebView where I passed the context. Maybe that's the issue? My API is 21. I put the code here:
this.webView.setDownloadListener((new DownloadListener() {
#Override
public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimetype, long contentLength) {
try {
if(url != null) {
FileOutputStream fos;
fos = context.openFileOutput("image.png", Context.MODE_PRIVATE);
byte[] decodedStr = Base64.decode(url, Base64.DEFAULT);
fos.write(decodedStr);
Intent sendIntent = new Intent();
sendIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(new File("file://" + context.getFilesDir() + "/chart.png")));
sendIntent.setType("image/png");
getContext().startActivity(sendIntent);
fos.flush();
fos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}));
My base64 looks like this:
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAArgAAAIACAYAAABpWR83AAAgAElEQVR4Xux9B3xU15X+ESBUEEIVgbpAgCq9V9tg3B1jO3FLcUnsuCa2s7v/TeJksym7yaa6pttOcY2Nu8
It is much longer. I just put here a cut version to make my question more readable.
Thanks for your time!
#EDIT
I have the feeling that it is not clear what he wants to achieve. I'm making use of this tutorial: https://developer.android.com/training/sharing/send.html
But the thing which is different is base64 image
**#EDIT 2 **
Changed method to putExtra() but now other apps can't read the image
You have to keep in mind that there is a transaction limit to the 1 mb when you sending data by intent. Send to the activity only uri, and onCreate method, new started activity load image.
There are many bugs in this code:
You are writing the image to getFilesDir() via openFileOutput(), but third-party apps have no rights to files in that directory
You are using a file: Uri, which will not work on Android 7.0+
You are putting the Uri in the Intent via setData(), which is not how ACTION_SEND works (use EXTRA_STREAM)
You are not closing the file before calling startActivity() (while this should work in practice, it's the sort of thing that you will get yelled at in code reviews)
- You claim the MIME type is image/png, when the content of your file is not a PNG file
Even if you fix those, I question your premise. There are ~7 billion people on this planet. IMHO, few if any of them will want to put a base64-encoded file into some other app, as those apps will not be able to use the file. If you want to allow the user to share an image, share the image, not a base64-encoded version of the image.
Related
In the application I receive a PDF from an API call so it is inside a byte array and I would like to display it inside the application without having to save it in the user's phone. I have tried a WebView but it has not worked. Seems like a WebView will display a PDF from an url but will not render it if you give it the PDF as a string.
I was wondering if there was a way to display a PDF inside an android application without having to save it in the user's phone?
I am doing this by writing the received bytes of the pdf file to a temporary cache DIR and then open it with an intent. So for example in an async task I download the file in the doInBackground method and do this in the onPostExecute.
#Override
protected void onPostExecute(byte[] result) {
final File reportFile = new File(context.getExternalCacheDir(), "pdf-file.pdf");
final BufferedOutputStream output = new BufferedOutputStream(new FileOutputStream(reportFile));
output.write(result);
output.close();
Uri path = FileProvider.getUriForFile(getActivity(), BuildConfig.APPLICATION_ID + ".fileprovider", reportFile);
final Intent intent = new Intent(Intent.ACTION_VIEW);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
intent.setDataAndType(path, "application/pdf");
startActivity(intent);
}
This is working quite well. The only thing you have to do is to check if the user has a PDF reader installed that is working.
The advantage I see with this solution is that you provide the user the opportunity to further do with the pdf what he or she wants (e.g. print, store, share, ...). If you just display it within a frame in your app you would quite limit the interaction possibilities (or would have to implement them all by your self).
My app creates mails with attachments, and uses an intent with Intent.ACTION_SEND to launch a mail app.
It works with all the mail apps I tested with, except for the new Gmail 5.0 (it works with Gmail 4.9), where the mail opens without attachment, showing the error: "Permission denied for the attachment".
There are no useful messages from Gmail on logcat. I only tested Gmail 5.0 on Android KitKat, but on multiple devices.
I create the file for the attachment like this:
String fileName = "file-name_something_like_this";
FileOutputStream output = context.openFileOutput(
fileName, Context.MODE_WORLD_READABLE);
// Write data to output...
output.close();
File fileToSend = new File(context.getFilesDir(), fileName);
I'm aware of the security concerns with MODE_WORLD_READABLE.
I send the intent like this:
public static void compose(
Context context,
String address,
String subject,
String body,
File attachment) {
Intent emailIntent = new Intent(Intent.ACTION_SEND);
emailIntent.setType("message/rfc822");
emailIntent.putExtra(
Intent.EXTRA_EMAIL, new String[] { address });
emailIntent.putExtra(Intent.EXTRA_SUBJECT, subject);
emailIntent.putExtra(Intent.EXTRA_TEXT, body);
emailIntent.putExtra(
Intent.EXTRA_STREAM,
Uri.fromFile(attachment));
Intent chooser = Intent.createChooser(
emailIntent,
context.getString(R.string.send_mail_chooser));
context.startActivity(chooser);
}
Is there anything I do wrong when creating the file or sending the intent? Is there a better way to start a mail app with attachment? Alternatively - has someone encountered this problem and found a workaround for it?
Thanks!
I was able to pass a screenshot .jpeg file from my app to GMail 5.0 through an Intent. The key was in this answer.
Everything I have from #natasky 's code is nearly identical but instead, I have the file's directory as
context.getExternalCacheDir();
Which "represents the external storage directory where you should save cache files" (documentation)
GMail 5.0 added some security checks to attachments it receives from an Intent. These are unrelated to unix permissions, so the fact that the file is readable doesn't matter.
When the attachment Uri is a file://, it'll only accept files from external storage, the private directory of gmail itself, or world-readable files from the private data directory of the calling app.
The problem with this security check is that it relies on gmail being able to find the caller app, which is only reliable when the caller has asked for result. In your code above, you do not ask for result and therefore gmail does not know who the caller is, and rejects your file.
Since it worked for you in 4.9 but not in 5.0, you know it's not a unix permission problem, so the reason must be the new checks.
TL;DR answer:
replace startActivity with startActivityForResult.
Or better yet, use a content provider.
Use getExternalCacheDir() with File.createTempFile.
Use the following to create a temporary file in the external cache directory:
File tempFile = File.createTempFile("fileName", ".txt", context.getExternalCacheDir());
Then copy your original file's content to tempFile,
FileWriter fw = new FileWriter(tempFile);
FileReader fr = new FileReader(Data.ERR_BAK_FILE);
int c = fr.read();
while (c != -1) {
fw.write(c);
c = fr.read();
}
fr.close();
fw.flush();
fw.close();
now put your file to intent,
emailIntent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(tempFile));
You should implement a FileProvider, which can create Uris for your app's internal files. Other apps are granted permission to read these Uris. Then, simply instead of calling Uri.fromFile(attachment), you instantiate your FileProvider and use:
fileProvider.getUriForFile(attachment);
Google have an answer for that issue:
Store the data in your own ContentProvider, making sure that other apps have the correct permission to access your provider. The preferred mechanism for providing access is to use per-URI permissions which are temporary and only grant access to the receiving application. An easy way to create a ContentProvider like this is to use the FileProvider helper class.
Use the system MediaStore. The MediaStore is primarily aimed at video, audio and image MIME types, however beginning with Android 3.0 (API level 11) it can also store non-media types (see MediaStore.Files for more info). Files can be inserted into the MediaStore using scanFile() after which a content:// style Uri suitable for sharing is passed to the provided onScanCompleted() callback. Note that once added to the system MediaStore the content is accessible to any app on the device.
Also you can try set permissions for your file:
emailIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
And finally you can copy/store your files in external storage - permissions not needed there.
I tested it and I found out that it was definitely private storage access problem.
When you attach some file to Gmail (over 5.0) do not use the file from private storage such as /data/data/package/. Try to use /storage/sdcard.
You can successfully attach your file.
Not sure why GMail 5.0 doesn't like certain file paths (which I've confirmed it does have read access to), but an apparently better solution is to implement your own ContentProvider class to serve the file. It's actually somewhat simple, and I found a decent example here: http://stephendnicholas.com/archives/974
Be sure to add the tag to your app manifest, and include a "android:grantUriPermissions="true"" within that. You'll also want to implement getType() and return the appropriate MIME type for the file URI, otherwise some apps wont work with this... There's an example of that in the comment section on the link.
I was having this problem and finally found an easy way to send email with attachment. Here is the code
public void SendEmail(){
try {
//saving image
String randomNameOfPic = Calendar.DAY_OF_YEAR+DateFormat.getTimeInstance().toString();
File file = new File(ActivityRecharge.this.getCacheDir(), "slip"+ randomNameOfPic+ ".jpg");
FileOutputStream fOut = new FileOutputStream(file);
myPic.compress(Bitmap.CompressFormat.JPEG, 100, fOut);
fOut.flush();
fOut.close();
file.setReadable(true, false);
//sending email
Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("text/plain");
intent.putExtra(Intent.EXTRA_EMAIL, new String[]{"zohabali5#gmail.com"});
intent.putExtra(Intent.EXTRA_SUBJECT, "Recharge Account");
intent.putExtra(Intent.EXTRA_TEXT, "body text");
//Uri uri = Uri.parse("file://" + fileAbsolutePath);
intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(file));
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivityForResult(Intent.createChooser(intent, "Send email..."),12);
}catch (Exception e){
Toast.makeText(ActivityRecharge.this,"Unable to open Email intent",Toast.LENGTH_LONG).show();
}
}
In this code "myPic" is bitmap which was returned by camera intent
Step 1: Add authority in your attached URI
Uri uri = FileProvider.getUriForFile(context, ""com.yourpackage", file);
Same as your manifest file provide name
android:authorities="com.yourpackage"
Step 2`; Add flag for allow to read
myIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
I am experimenting with Android development. I am making an app that will allow the user to browse files in a web service and view them. These files could be anything: text, pdf, pictures, etc.
Previously, I would download the file to external storage and then call Intent.SetDataAndType() and pass it the URL to the file. That would cause the Android device to bring up an app picker and let the user choose the appropriate method to look at the file.
But since I do not want the user to edit the file, only to look at it, it seemed silly to download a file to storage; a file that I didn't want to hang around. Since the file can be obtained by a URL, why don't I pass that as a parameter to the Intent.SetDataAndType()?
I tried that. The first problem was that the file name was assumed to be the name of the web service call, and that seemed to be more important than the mime-type. I changed the web service to be the same name as whatever file was attempting to be downloaded. That solved that issue.
So now, the file is being opened. But it is always being opened in a web browser. I get to choose the web browser, but I would rather have another app open it.
My code looks like this:
Intent i = new Intent(Intent.ActionView);
i.SetDataAndType(Android.Net.Uri.Parse(GetUrlToFile(fileref, fileName)), mimeType);
i.SetFlags(ActivityFlags.GrantReadUriPermission);
i.SetFlags(ActivityFlags.NewTask);
i.SetFlags(ActivityFlags.ClearWhenTaskReset); // so if the app is relaunched, we don't show the display application.
StartActivity(i);
The code is in C# because I'm using Xamarin, but I don't believe that should make a difference.
I tried using StartActivity(Intent.CreateChooser(i, "Open me")); but that didn't give me any more options for choosing.
Does anyone have any ideas as to how to do this?
I have not found a way to do this yet, so I have gone through a workaround.
Instead of using a URL, I changed my app to be a Content Provider as well. Now, when I want the file opened, I create a URI that refers to the file within my app and pass that off to an Intent. When my app is contacted by this Intent, I download the file locally to my cache directory and return that.
My code has changed to this:
Intent i = new Intent(Intent.ActionView);
i.SetDataAndType(Android.Net.Uri.Parse("content://com.sample.erik.provider/files/" + id), mimeType);
i.SetFlags(ActivityFlags.GrantReadUriPermission);
i.SetFlags(ActivityFlags.NewTask);
i.SetFlags(ActivityFlags.ClearWhenTaskReset); // so if the app is relaunched, we don't show the display application.
StartActivity(i);
Then, I have my own content provider which does most of the work in OpenFile()
public override ParcelFileDescriptor OpenFile(Android.Net.Uri uri, string mode)
{
switch (sUriMatcher.Match(uri))
{
case FILE_ID:
if (mode != "r")
throw new Java.Lang.UnsupportedOperationException("Do not support write access: " + uri);
String id = uri.LastPathSegment;
Java.IO.File file = new Java.IO.File(Application.Context.CacheDir, id);
DownloadToFile(file, id);
return ParcelFileDescriptor.Open(file, ParcelFileMode.ReadOnly);
default:
throw new Java.Lang.IllegalArgumentException("Unknown Uri: " + uri);
}
}
It is not my original plan, but this way seems to work quite well and meets my needs.
This question contained several sub-questions. I am forking these, starting by this question. I'll eventually clean up by deleting this question.
The following program will in theory share a hello-world text file. The code runs, but sharing to either Dropbox or to Gmail (by way of just two concrete examples) fails.
public class MainActivity extends Activity {
#Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
String filename = "hellow.txt";
String fileContents = "Hello, World!\n";
byte[] bytes = fileContents.getBytes();
FileOutputStream fos = null;
try {
fos = this.openFileOutput(filename, MODE_PRIVATE);
fos.write(bytes);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
File file = new File(filename);
Intent shareIntent = new Intent();
shareIntent.setAction(Intent.ACTION_SEND);
shareIntent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(file));
shareIntent.setType("application/txt");
startActivity(Intent.createChooser(shareIntent, getResources().getText(R.string.send_to)));
file.delete();
}
}
Aside from adding a value for send_to in res/values/strings.xml, the only other pair of changes I did to the generic Hello, World that Eclipse creates is adding the following <provider> tag in AndroidManifest.xml:
<application
android:allowBackup="true"
android:icon="#drawable/ic_launcher"
android:label="#string/app_name"
android:theme="#style/AppTheme" >
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.mycorp.helloworldtxtfileprovider.MainActivity"
android:exported="false"
android:grantUriPermissions="true" >
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/my_paths" />
</provider>
<activity
android:name="com.mycorp.helloworldtxtfileprovider.MainActivity"
...
... and adding the following in res/xml/my_paths.xml
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<files-path name="files" path="." />
</paths>
My main question is the first, but while you're at this topic, a discussion of questions 2-4 would also be interesting.
Why does the program above fail?
Is it indeed the case that if one needs a custom ContentProvider, then one needs to extend that class, but if one just needs a FileProvider, then one can use that class without derivation?
In this code, I needed to use filename twice—once with openFileOutput and another with new File(). Is there a way to avoid this duplication (that would guarantee that the same file is being referenced)?
Is it safe to delete the file right after startActivity(..) is called, or is it necessary to devise a callback to wait learning that the file has been uploaded/shared. (The real file may take some time to share/upload.)
Edit
The code runs fine and shows a list of apps to send to.
If I select Dropbox, I can select the location just fine. Dropbox sends the notifications "Uploading to Dropbox" followed by "Upload failed: my_file.txt".
If I select Gmail, I can fill the recipient and the file appears to be attached, but after "sending message.." I get "Couldn't send attachment".
1.
Use FileProvider.getUriForFile(...) to construct the URI. This will direct the started activity to your FileProvider (which can then serve the file from your app's private files directory). Uri.fromFile(...) does not work because the started activity will try to directly access the private directory.
Set FLAG_GRANT_READ_URI_PERMISSION so that the started activity is granted read permission for the URI constructed by the FileProvider.
Finally, "text/plain" might work better than "application/txt" as MIME type.
I've had some problems getting this to work consistently across devices. This is my best bet so far (will edit if I ever refine it):
final String auth = "org.volley.sndcard_android.fileprovider";
final Uri uri = FileProvider.getUriForFile(activity, auth, file);
final String type = activity.getContentResolver().getType(uriForFile);
final Intent shareIntent = new Intent(Intent.ACTION_SEND);
shareIntent.setDataAndType(uri, type);
shareIntent.putExtra(Intent.EXTRA_STREAM, uriForFile);
shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
final Intent chooser = Intent.createChooser(shareIntent, "<title>");
activity.startActivity(chooser);
Setting only the type works on my Nexus 5 but not on my Samsung tablet. It seems the Samsung tablet needs the URI as data in order to grant the permission. Also note that intent.getData() cancels any previous calls to intent.setType() and vice versa, so you have to use the combined method, as done above.
Gmail seems to interpret the additional data as a default To-address. Highly annoying! If anyone has a better solution, please share it (pun intended, I'm from Gothenburg).
2.Yes, it is indeed. You see that ContentProvider is an abstract class, so to use custom content provider one must have to extend it. As FileProvider is a subclass of ContentProvider (which is not abstract), programmers can use FileProvider without subclassing it.
3.For ensuring same file you can follow the sample code below -
String fileName = "my_file.txt";
String fileData = "sample file content";
// Get the file
File file = new File(this.getFilesDir(), fileName);
if (!file.exists()) {
file.createNewFile();
}
// Write data to file
FileWriter fileWriter = new FileWriter(file);
fileWriter.write(fileData);
fileWriter.close();
// Get the uri
Uri uri = Uri.fromFile(file);
// Share the file with corresponding uri
Intent shareIntent = new Intent();
shareIntent.setAction(Intent.ACTION_SEND);
shareIntent.putExtra(Intent.EXTRA_STREAM, uri);
shareIntent.setType("application/txt");
startActivity(Intent.createChooser(shareIntent, "Send To"));
4.No, it's not safe to delete the file right after you call startActivity(). Because startActivity() is non-blocking function call and it'll return immediately. You have to wait for the time the file is being shared. You can do that by using startActivityForResult() . See if it serves the purpose.
I have a programmatically generated image that I want to send as an attachment via the ACTION_SEND and EXTRA_STREAM method.
But how do i do this?
My first attempt (writing to my context.getCacheDir() based file path) appeared to work in the Gmail preview (no image preview, but attached file name and icon was visible), but the attachment never arrived on the recipient side. I guess this has something to do with permissions on the generated file, but how to avoid this? Do I need to set more permissive settings on these generated files (so that the Gmail activity can access)? Is that even possible for the app's cache folder?
Is there another file location that would be more suitable to write my files to? I considered the downloads folder, but think it would be an awkward location for something that only needs to exist until it has been emailed.
I have even tried encoding my image purely in a data:image/png;base64,ABCD... style URI. This, too, showed up in Gmail preview (attachment icon, but no file name), but did not result in a recipient-side attachment.
Has anyone been able to attach a one-shot generated image to an email intent by any means? What options may I have overlooked?
My problem really consisted of two parts:
context.getCacheDir() is private to your app. You can't put something there and expect another app to be able to access it.
I misunderstood what MIME type I should have been using. Even though I was sending email text, I really needed to specify image/png for the sake of my attachment.
Additionally, research indicated that putting (potentially large) images on the primary memory was not a good idea, even if you were going to immediately clean it up.
Once I did these things and wrote my generated images to a public location on the SD Card, it worked just fine.
So, in overview:
Request SD Card Access in your manifest
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
Make sure SD Card is available
if (!Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()))
{
//Bail gracefully
}
Create a directory on the SD Card
File pngDir = new File(
Environment.getExternalStorageDirectory(),
//Loose convention inferred from app examples
"Android/data/com.somedomain.someapp/flotsam");
if (!pngDir.exists())
pngDir.mkdirs();
Write your file to that directory and capture the Uri
File pngFile = new File(pngDir, "jetsam.png");
//Save file encoded as PNG
Uri pngUri = Uri.fromFile(pngFile);
Build an ACTION_SEND intent
Intent intent = new Intent(android.content.Intent.ACTION_SEND);
intent.setType("image/png"); //
intent.putExtra(android.content.Intent.EXTRA_EMAIL, new String[] { "someone#somewhere.com" });
intent.putExtra(android.content.Intent.EXTRA_SUBJECT, "Portable Network Graphics");
intent.putExtra(android.content.Intent.EXTRA_CC, new String[] { "carbon#somewhere.com" });
intent.putExtra(Intent.EXTRA_TEXT, "Something textual");
intent.putExtra(Intent.EXTRA_STREAM, pngUri);
And then start the activity
context.startActivity(Intent.createChooser(intent, "Something Pithy"));
And then make sure you clean everything up...
Caveat 1
There appears to be more support coming for app-specific SD Card directories, but alas, not in my required SDK version.
Caveat 2
This is an overview of the solution that eventually worked for me. It is not necessarily a "best practice" approach.
Caveat 3
This does mean that the application has to have an SD Card mounted in order to have the image attachments feature available, but this was totally acceptable for my use case. Your mileage may vary. If the SD Card is not available, I append a friendly note to the email explaining why the images could not be attached and how to rectify the situation.
I've just run into exactly the same issue (wanting to attach a text file in my case). If you look in the Android log, the reason for it is:
02-28 21:01:28.434: E/Gmail(19673): file:// attachment paths must point to file:///mnt/sdcard. Ignoring attachment file:///data/data/com.stephendnicholas.gmailattach/cache/Test.txt
As a workaround (as mentioned by HRJ), you can use a ContentProvider to provide access to files in your application's internal cache so that Gmail can attach them. I've just written up a blog post on how to do it.
Hopefully that's of some help :)
tableLayout.buildDrawingCache();
Bitmap test = Bitmap.createBitmap(tableLayout.getDrawingCache());
tableLayout.destroyDrawingCache();
Log.d("Image", test.toString());
String path = Environment.getExternalStorageDirectory().toString();
Log.d("Path", path);
File file = new File(path,"mail_image.png");
Uri pngUri = Uri.fromFile(file);
Log.d("Real Image Path", pngUri.toString());
Intent emailIntent = new Intent(android.content.Intent.ACTION_SEND);
emailIntent.setType("image/png");
emailIntent.putExtra(android.content.Intent.EXTRA_EMAIL, "email to");
emailIntent.putExtra(android.content.Intent.EXTRA_SUBJECT,"Subject");
emailIntent.putExtra(android.content.Intent.EXTRA_TEXT, "From My App");
emailIntent.putExtra(android.content.Intent.EXTRA_STREAM, pngUri );
startActivity(Intent.createChooser(emailIntent, "Send mail..."));