I create images in my app and want to share these social networks (facebook), mail apps (gmail), and other apps that can "receive" images.
The origin of the problem (I think) is that I don't want to use the external storage as a base for my images. I want to either use my data folder or my cache folder since neither of these require any permission to access.
The code which I use to write my image to file (and I specify the MODE_WORLD_READABLE so that other apps can read them):
FileOutputStream fos = null;
try {
fos = context.openFileOutput("image.jpg", Context.MODE_WORLD_READABLE);
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos);
} finally {
if (fos != null)
fos.close();
}
And this is the code where I share the image:
File internalFile = context.getFileStreamPath("image.jpg");
Intent intent = new Intent();
intent.setAction(Intent.ACTION_SEND);
intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(internalFile));
intent.setType("image/jpeg");
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
context.startActivity(Intent.createChooser(intent, "share"));
This solution is very easy and works fine for apps like facebook but not for example gmail which failes with:
file:// attachment paths must point to file:///mnt/sdcard
There are a number of "hacks" (see below) to get it to work with gmail but I leaves me asking myself if there is an even better way to share images that works without hacks, something I overlooked. So, to the questions:
What is the best way to share images? (external storage?)
Is there any more apps that (mis-)behave just like gmail? (I have seen some trouble with google+)
If there is no other way: Can I write special intents for sharing to specific apps. I have a default way of sharing and override it when the user selects an app on my watch list?
Hacks
Using a path-hack by simply pointing the Uri to:
file:///mnt/sdcard/../../my/package/name/...
This solution doesn't feel right.
Using a ContentProvider as described here. But quoted from the link:
Warning: the method described in the post works well for Gmail, but apparently has some issues with other ACTION_SEND handlers (e.g. the MMS composer).
(Issue: It crashes the MMS composer)
Did you try ParecelableFileDescriptor?
http://developer.android.com/reference/android/os/ParcelFileDescriptor.html
Create with
static ParcelFileDescriptor open(File file, int mode, Handler handler, ParcelFileDescriptor.OnCloseListener listener)
Create a new ParcelFileDescriptor accessing a given file.
static ParcelFileDescriptor open(File file, int mode)
Create a new ParcelFileDescriptor accessing a given file.
Receiver side like this:
Returning an Input Stream from Parcel File Descriptor using Androids DownloadManager
You should to make 3 steps.
Take picture.
public Bitmap takeScreenshot() {
View rootView = findViewById(android.R.id.content).getRootView();
rootView.setDrawingCacheEnabled(true);
return rootView.getDrawingCache();
}
Save picture.
public String saveBitmap(Bitmap bitmap) {
File imagePath = new File(Environment.getExternalStorageDirectory() + “/screenshot.png”);
FileOutputStream fos;
try {
fos = new FileOutputStream(imagePath);
bitmap.compress(CompressFormat.PNG, 100, fos);
fos.flush();
fos.close();
} catch (FileNotFoundException e) {
Log.e(“GREC”, e.getMessage(), e);
} catch (IOException e) {
Log.e(“GREC”, e.getMessage(), e);
}
return imagePath.getAbsolutePath();
}
Share to social network.
Related
I'm building an app that allows the user to save the bitmap or share it without saving it. The 2nd functionality doesn't quite work. I understand that the app needs to save the file to the device before sharing it on a social media app so my idea was, immediately after the file was successfully shared, to automatically delete the file from the device. I've build a delete method trying 2 different approaches and neither have worked:
First approach:
public void deleteFile(String path){
File file = new File(path);
try {
file.getCanonicalFile().delete();
} catch (IOException e) {
e.printStackTrace();
}
}
Second approach:
public void deleteFile(String path){
File file = new File(path);
boolean deleted = file.delete();
}
And I'm calling deleteFile(String) from the sharing method:
public void shareMeme(Bitmap bitmap) {
String path = MediaStore.Images.Media.insertImage(Objects.requireNonNull(getContext()).getContentResolver(), bitmap, "Meme", null);
Uri uri = Uri.parse(path);
Intent share = new Intent(Intent.ACTION_SEND);
share.setType("image/*");
share.putExtra(Intent.EXTRA_STREAM, uri);
share.putExtra(Intent.EXTRA_TEXT, "This is my Meme");
getContext().startActivity(Intent.createChooser(share, "Share Your Meme!"));
deleteFile(path);
}
With respect to your stated problem, insertImage() returns a string representation of a Uri. That Uri is not a file. Calling getPath() on it is pointless, and you cannot delete anything based on that path.
More broadly, if your intention is to delete the content right away:
Do not put it in the MediaStore
Do not share it, as you will be deleting it before the other app has a chance to do anything with it
If you want to share it, but then delete it:
Do not put it in the MediaStore
Delete it the next day, or in a few hours, or something, as you have no good way of knowing when the other app is done with the content
To share an image with another app without using the MediaStore:
Save the image to a file in getCacheDir() (call that on a Context, such as an Activity or Service)
Use FileProvider to make that file available to other apps
Beyond that:
Do not use wildcard MIME types in ACTION_SEND. You are the one who is supplying the content to send. You know the actual MIME type. Use it.
Note that there is no requirement for an ACTION_SEND activity to honor both EXTRA_TEXT and EXTRA_STREAM. Most seem to do so, but that behavior is outside of the ACTION_SEND specification.
Note that insertImage() is deprecated on Android Q.
First, you need to check if your file exists, (maybe you set the wrong path?). Then delete the file
File file = new File(path);
if (file.exists()){
if (file.delete()) {
Toast.makeText(this, "file Deleted :" + path, Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this, "file not Deleted :" + path, Toast.LENGTH_SHORT).show();
}
}
I am having an issue, I have never had problem opening files via ACTION_VIEW the next way:
File file = new File(getActivity().getFilesDir(), TEMP_FILE_NAME);
String dataType = "image/*";
if (file.exists()) {
Intent fileIntent = new Intent(Intent.ACTION_VIEW);
fileIntent.setDataAndType(Uri.fromFile(file), dataType);
fileIntent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
Intent intent = Intent.createChooser(fileIntent, "Open file");
try {
startActivity(intent);
} catch (ActivityNotFoundException e) {
e.printStackTrace();
Log.e(TAG, "There is a problem when opening the file");
}
} else {
Toast.makeText(getContext(), "Invalido", Toast.LENGTH_LONG).show();
}
The problem I am having right now is that even though the file exists when I choose the app to open the file it immediately closes and tells me Not found. I have put the image I am loading in an image view and there is no problem, so the file is valid but for some reason it has conflicts when I am opening it via intent.
I am aware that it may have something to do with the way I am creating the file, I am retrieving it from Google drive so I am writing the file using the Apache Commons library the next way:
DriveContents contents = result.getDriveContents();
InputStream inputStream = contents.getInputStream();
File file = new File(getActivity().getFilesDir(), TEMP_FILE_NAME);
try {
OutputStream outputStream = new FileOutputStream(file);
IOUtils.copy(inputStream, outputStream);
IOUtils.closeQuietly(inputStream);
IOUtils.closeQuietly(outputStream);
} catch (IOException e) {
e.printStackTrace();
}
What is it I am doing wrong? I am not totally sure if the problem has to do with the copy method executing asynchronously or something like that.
Thanks in advance.
I have never had problem opening files via ACTION_VIEW the next way
That code will never work, as third-party apps have no rights to work with files on getFilesDir() of your app.
What is it I am doing wrong?
You are attempting to serve an inaccessible file to third-party programs. Use FileProvider to serve the file, using FileProvider.getUriForFile() to get the Uri to use in your ACTION_VIEW Intent.
Developing Android email plugin for Unity. I have a screenshot in the files/ folder of the app, I want to attach to mail. As it turned out, I cannot attach from there directly. I implemented a FileProvider, but it turned out that it exist only above 4.0.
So I implemented the suggested workaround, to save it to external storage, then attach from there. Saving seems work, even reading seems work, but still, Gmail says "Can't attach empty file". Also When launching email intent, I have an error message, like:
E/HwEmailTag( 7327): AttachmentUtilities->inferMimeTypeForUri->Unable to determine MIME type for uri=/storage/emulated/0/com.eppz.plugins_screenshot.jpg
I tried application/image, image/jpg as intent.setType(), still the same, while Gmail says the file is empty.
Is this something with emulated external storage /storage/emulated/0/? The device has no SD card, but I've read that getExternalStorage() returns a shared / public place for files in such cases either.
It should work. Should I remove dots from filename? Hope not. Here's the corresponding code:
String saveImageAtPathToExternalStorage(String imagePath)
{
Log.i(TAG, "saveImageAtPathToExternalStorage(...)");
// Create bitmap.
File imageFile = new File(imagePath);
Bitmap bitmap = BitmapFactory.decodeFile(imageFile.getAbsolutePath(), new BitmapFactory.Options());
// Output.
String outputFileName = _unityPlayerActivity.getPackageName()+"_screenshot.jpg";
String externalStorageDirectory = Environment.getExternalStorageDirectory().toString();
File outputImageFile = new File(externalStorageDirectory, outputFileName);
String outputImagePath = outputImageFile.getAbsolutePath();
if (outputImageFile.exists()) outputImageFile.delete(); // Delete if existed
try
{
// Write JPG.
FileOutputStream outputStream = new FileOutputStream(outputImageFile);
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream);
outputStream.flush();
outputStream.close();
Log.i(TAG, "Image written to `"+outputImagePath+"`");
}
catch (Exception e)
{ e.printStackTrace(); }
// Return with output path.
return outputImagePath;
}
public void openMailComposer(String to, String subject, String body, int isHTML, String attachmentImagePath)
{
Log.i(TAG, "openMailComposer(...)");
// Attachment image.
File attachmentImageFile = new File(attachmentImagePath);
if (attachmentImageFile.exists() == false)
{
Log.i(TAG, IMAGE_NOT_FOUND);
SendUnityMessage(OPEN_MAIL_COMPOSER_CALLBACK_METHOD_NAME, IMAGE_NOT_FOUND);
return;
}
// Save to external first.
String externalImagePath = saveImageAtPathToExternalStorage(attachmentImagePath);
final Uri externalImageUri = Uri.parse(externalImagePath);
Log.i(TAG, "externalImageUri `"+externalImageUri+"`");
// Intent.
Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("image/jpeg");
intent.putExtra(Intent.EXTRA_EMAIL, to);
intent.putExtra(Intent.EXTRA_SUBJECT, subject);
intent.putExtra(Intent.EXTRA_TEXT, body);
if (isHTML == 1) intent.putExtra(Intent.EXTRA_HTML_TEXT, body);
// Attach.
intent.putExtra(Intent.EXTRA_STREAM, externalImageUri);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivityForResult(Intent.createChooser(intent, "Send email"), OPEN_MAIL_COMPOSER_REQUEST_CODE);
}
I'm relatively new to Android development, and really want to believe, but having all this hassle compared to having a single line for this in iOS is quiet distressing.
I'm running this on a Huawei MediaPad (TT1 7.0), Android 4.4.2, and I want it to run about Android 2.3+ basically (why I refused using FileProvider earlier).
I've been searching for many topics about android file writing, yet most of them wanted to write files to android internal storage. Others who wanted to write files on external SD card didn't success at all. My case is quite similar but I think that writing files to external USB is a totally different case.
I am using Samsung galaxy Note II running stock TouchWiz 4.4.2 [not rooted]. My phone supports micro-USB-OTG and I can mount my USB as rwxrwx--x without rooting. The complete path of my USB is /storage/UsbDriveA.
I've tried to use Environment.getExternalStorageDirectory() to get the path or use the path (mentioned above) directly but neither of them succeed. The first one returns internal storage path and the second one returns an error with "permission denied". I have already put the
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
in Android Manifest so I wondered why my code didn't work.
Moreover, I can write anything to my USB using Root Browser (use it without root) and Simple Browser thus I believe that there's a way to do that.
Here's my code:
File file = new File(path.getAbsolutePath(), "test.txt");
// File file = new File("/storage/extSdCard","test.txt");
err = false;
try {
FileOutputStream f = new FileOutputStream(file);
PrintWriter pw = new PrintWriter(f);
pw.print(get);
pw.flush();
pw.close();
f.close();
}catch (Exception e) {
e.printStackTrace();
Toast.makeText(MainActivity.this, "writing error",Toast.LENGTH_LONG).show();
err = true;
}
Log.i("File Path:", file.getPath());
From android 4.4, you can use Storage Access Framework to access to removable media (see https://commonsware.com/blog/2014/04/09/storage-situation-removable-storage.html).
For example, I tried with success to copy a pdf file from local memory to removable memory connected by OTG adapter. The only limitation: the user has to choose a destination folder.
1) call Intent.ACTION_CREATE_DOCUMENT:
Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
intent.setType("application/pdf");
intent.putExtra(Intent.EXTRA_TITLE, file.getName());
startActivityForResult(intent, REQUEST_CODE);
2) intercept the return intent
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data){
if(requestCode == REQUEST_CODE) {
if (resultCode != RESULT_OK) return;
copyFile(fileToCopy, data.getData());
}
}
3) use the ContentResolver to open the outputStream and use it to copy the file
private void copyFile(File src, Uri destUri) {
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
try {
bis = new BufferedInputStream(new FileInputStream(src));
bos = new BufferedOutputStream(getContentResolver().openOutputStream(destUri));
byte[] buf = new byte[1024];
bis.read(buf);
do {
bos.write(buf);
} while(bis.read(buf) != -1);
} catch (NullPointerException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (bis != null) bis.close();
if (bos != null) bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
From https://source.android.com/devices/storage/
Starting in Android 4.4, ...
The WRITE_EXTERNAL_STORAGE permission must only grant write access to
the primary external storage on a device. Apps must not be allowed to
write to secondary external storage devices, except in their
package-specific directories as allowed by synthesized permissions.
Restricting writes in this way ensures the system can clean up files
when applications are uninstalled.
So, starting from Android 4.4 in devices with multiple external storages you will be able to write only on the primary external storage. Take into account that External Storage does not mean only "real external" devices. It is defined as follows (from the External Storage reference)
External storage can be provided by physical media (such as an SD
card), or by exposing a portion of internal storage through an
emulation layer.
Anyway there is a workaround to write to secondary external storage using the media content provider. Take a look at http://forum.xda-developers.com/showthread.php?t=2634840
I have used it on a project of mine, but as the author says, it's far from the ideal solution, and it is not guaranteed to work on coming Android versions, so you must not let all your app to rely on this workaround.
I'm programming an app that receives all kind of documents as base64 strings. I've been searching all morning, and I didn't find a way to display the documents properly without storing them. As an alternative I wrote this:
private void createReadableFile(DocumentBinary document) {
try {
byte[] bytes = Base64.decode(document.getDocument(), 0);
FileOutputStream os = openFileOutput(document.getSuggestedFileName(), MODE_PRIVATE);
os.write(bytes);
os.flush();
os.close();
openFile(document);
} catch (Exception e) {
Ln.e(e, "Error while parsing document");
}
}
And I do this with the created file:
private void openFile(File file, String mimeType) {
Intent viewIntent = new Intent();
viewIntent.setAction(Intent.ACTION_VIEW);
viewIntent.setDataAndType(Uri.fromFile(file), mimeType);
viewIntent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
startActivity(viewIntent);
}
But it doesn't start anything. It calls onPause and onResume but nothing happens. I know that if I change MODE_PRIVATE to MODE_WORLD_READABLE it would work, but MODE_WORLD_READABLE is deprecated. Do you know a better way to do it or what to use instead of MODE_PRIVATE?
Save the file to external storage, or
Use FileProvider to create a ContentProvider that serves the file from internal storage, or
Create your own ContentProvider that implements openFile() and serves up a stream of content from some other source