I am trying to share image file in cache directory, i have the complete path, but not able to send the file in attachments, the code is
File shareImage=Utils.getBitmapFile();
Log.d("Activity", "get final path in result"+shareImage.getAbsolutePath());
/*MimeTypeMap mime = MimeTypeMap.getSingleton();
String ext=shareImage.getName().substring(shareImage.getName().lastIndexOf(".")+1);
String type = mime.getMimeTypeFromExtension(ext);
shareIntent.setType(type);
Intent shareIntent = new Intent(Intent.ACTION_SEND);
shareIntent.setType("image/*");
Uri shareImageUri = Uri.fromFile(shareImage);
Uri shareImageUri = Uri.fromParts("content", shareImage.getAbsolutePath(), null);//("content://"+shareImage.getAbsolutePath());
*/
Uri shareImageUri = Uri.fromFile(shareImage);
Log.d("Result ","uri is "+shareImageUri.toString());
shareIntent.putExtra(Intent.EXTRA_STREAM, shareImageUri);
startActivity(Intent.createChooser(shareIntent, "Share Results"));
the above commented code is not working
the send mail shows attachment,but not receiving end there is not attachment,
facebook sharing also shows no image in post
what the reason for this??
I have already seen the following SO Links how-to-use-share-image-using-sharing-intent-to-share-images-in-android and many others, none of them are able to resolve the issue
P.S.;
1.The aim is to take screenshot of screen save it in cache directory and share it online from there
2.Yes i do have file, I can pull it via DDMS from device and see on system.
I followed #CommonsWare's advice and used a FileProvider. Assuming your image is already in the internal cache directory as cache/images/image.png, then you can use the following steps. These are mostly a consolidation of the documentation.
Set up the FileProvider in the Manifest
<manifest>
...
<application>
...
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.example.myapp.fileprovider"
android:grantUriPermissions="true"
android:exported="false">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/filepaths" />
</provider>
...
</application>
</manifest>
Replace com.example.myapp with your app package name.
Create res/xml/filepaths.xml
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<cache-path name="shared_images" path="images/"/>
</paths>
This tells the FileProvider where to get the files to share (using the cache directory in this case).
Share the image
File imagePath = new File(context.getCacheDir(), "images");
File newFile = new File(imagePath, "image.png");
Uri contentUri = FileProvider.getUriForFile(context, "com.example.app.fileprovider", newFile);
if (contentUri != null) {
Intent shareIntent = new Intent();
shareIntent.setAction(Intent.ACTION_SEND);
shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); // temp permission for receiving app to read this file
shareIntent.setDataAndType(contentUri, getContentResolver().getType(contentUri));
shareIntent.putExtra(Intent.EXTRA_STREAM, contentUri);
startActivity(Intent.createChooser(shareIntent, "Choose an app"));
}
Documentation
FileProvider
Storage Options - Internal Storage
Sharing Files
Saving Files
what the reason for this?
As noted, other apps do not have access to your app's internal storage.
none of them are able to resolve the issue
Feel free to open a fresh StackOverflow question, where you explain, completely and precisely what specific solutions you have tried and what specific problems you have encountered.
but that does not seems to be working as per SO post!!!
Feel free to open a fresh StackOverflow question, where you explain, completely and precisely what "that does not seems to be working" means.
Or, use FileProvider, which offers this capability with no code required beyond an entry for it in your manifest.
Or, store your image on external storage, such as getExternalCacheDir().
I share cached image by followed steps .
1.Copy your cached image to target path.
public static File copyImage(String sourcePath, String targetPath){
try {
InputStream in = new FileInputStream(sourcePath);
OutputStream out = new FileOutputStream(targetPath);
byte[] buf = new byte[1024];
int len;
while ((len = in.read(buf)) > 0) {
out.write(buf, 0, len);
}
in.close();
out.close();
return new File(targetPath);
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
2.Get the Uri of copy file.
Uri uri = Uri.fromFile(target);
3.Share image by intent
File dir = new File(Constant.INKPIC_PATH);//your custom path,such as /mnt/sdcard/Pictures
if(!dir.exists()){
dir.mkdirs();
}
File f = new File(dir,"temporary_file.jpg");
File target = copyImage(url,f.getAbsolutePath());
Uri uri = Uri.fromFile(target);
Intent intent = new Intent();
intent.setAction(Intent.ACTION_SEND);
intent.setType("image/*");
intent.putExtra(Intent.EXTRA_STREAM,uri );
context.startActivity(intent);
Related
I'm using FileProvider API for sharing content actually storing in internal storage.
Following is my xml configuration that linked with Provider configured in Manifiest file.
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<cache-path name="xs_audio" path="/audio/records"/>
</paths>
and code that I'm using to share is following:
private final static String FILE_PROVIDER = BuildConfig.APPLICATION_ID + ".fileprovider";
private String testPackageAppHaveAccess = "com.whats";
public static void shareDocument(Activity activity, CallRecordData data) {
Intent intent = new Intent();
intent.setAction(Intent.ACTION_SEND_MULTIPLE);
intent.putExtra(Intent.EXTRA_SUBJECT, "Record File");
intent.setType("audio/*");
ArrayList<Uri> files = new ArrayList<>();
//for (AudioModelObj image : data.getDocuments()) {
files.add(getImageUri(activity, data.getFile()));
//}
activity.grantUriPermission(testPackageAppHaveAccess, files.get(0), Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, files);
activity.startActivity(Intent.createChooser(intent, "Share Audio File."));
}
private static Uri getImageUri(Activity activity, String audioURI) {
if (Build.VERSION.SDK_INT < 24) {
return Uri.parse(audioURI);
} else {
URI uri = URI.create(audioURI);
File file = new File(uri);
return FileProvider.getUriForFile(activity, FILE_PROVIDER, file);
}
}
}
but while launching with app it's not attaching anything. In case of gmail it say "can't attach empty file". File is confirmedly available as I'm displaying list of file and playing.
For reference: Uri generating from getImageUri(..) is
/data/user/0/com.xl.cl.debug/cache/audio/records/17-10-17_170728_abc_.wav
Any suggestion what I'm doing wrong ?
<files-path> already points to what on some devices will be /data/user/0/com.xl.cl.debug
<cache-path> is what you should be using, replacing path with just the subdirectory of interest (audio/records), eliminating the /data/user/0/com.xl.cl.debug/cache bit
You are calling grantUriPermission(), where the first parameter is not a package
You are calling grantUriPermission(), where the first parameter is not a package identifying the app to which you are trying to grant permission
You are not adding FLAG_GRANT_READ_URI_PERMISSION to the Intent, which is the typical way of saying "the Uri in this Intent should be readable by the recipient of this Intent" (though it is possible that ACTION_SEND_MULTIPLE requires more work here, as I haven't played with that much)
No filesystem path in the human history has begun with content:/, so get rid of that
Calling new File() and supplying a value that is not a filesystem path is not going to work well
There may be more problems than those, but that should get you started
I am trying to share image file in cache directory, i have the complete path, but not able to send the file in attachments, the code is
File shareImage=Utils.getBitmapFile();
Log.d("Activity", "get final path in result"+shareImage.getAbsolutePath());
/*MimeTypeMap mime = MimeTypeMap.getSingleton();
String ext=shareImage.getName().substring(shareImage.getName().lastIndexOf(".")+1);
String type = mime.getMimeTypeFromExtension(ext);
shareIntent.setType(type);
Intent shareIntent = new Intent(Intent.ACTION_SEND);
shareIntent.setType("image/*");
Uri shareImageUri = Uri.fromFile(shareImage);
Uri shareImageUri = Uri.fromParts("content", shareImage.getAbsolutePath(), null);//("content://"+shareImage.getAbsolutePath());
*/
Uri shareImageUri = Uri.fromFile(shareImage);
Log.d("Result ","uri is "+shareImageUri.toString());
shareIntent.putExtra(Intent.EXTRA_STREAM, shareImageUri);
startActivity(Intent.createChooser(shareIntent, "Share Results"));
the above commented code is not working
the send mail shows attachment,but not receiving end there is not attachment,
facebook sharing also shows no image in post
what the reason for this??
I have already seen the following SO Links how-to-use-share-image-using-sharing-intent-to-share-images-in-android and many others, none of them are able to resolve the issue
P.S.;
1.The aim is to take screenshot of screen save it in cache directory and share it online from there
2.Yes i do have file, I can pull it via DDMS from device and see on system.
I followed #CommonsWare's advice and used a FileProvider. Assuming your image is already in the internal cache directory as cache/images/image.png, then you can use the following steps. These are mostly a consolidation of the documentation.
Set up the FileProvider in the Manifest
<manifest>
...
<application>
...
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.example.myapp.fileprovider"
android:grantUriPermissions="true"
android:exported="false">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/filepaths" />
</provider>
...
</application>
</manifest>
Replace com.example.myapp with your app package name.
Create res/xml/filepaths.xml
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<cache-path name="shared_images" path="images/"/>
</paths>
This tells the FileProvider where to get the files to share (using the cache directory in this case).
Share the image
File imagePath = new File(context.getCacheDir(), "images");
File newFile = new File(imagePath, "image.png");
Uri contentUri = FileProvider.getUriForFile(context, "com.example.app.fileprovider", newFile);
if (contentUri != null) {
Intent shareIntent = new Intent();
shareIntent.setAction(Intent.ACTION_SEND);
shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); // temp permission for receiving app to read this file
shareIntent.setDataAndType(contentUri, getContentResolver().getType(contentUri));
shareIntent.putExtra(Intent.EXTRA_STREAM, contentUri);
startActivity(Intent.createChooser(shareIntent, "Choose an app"));
}
Documentation
FileProvider
Storage Options - Internal Storage
Sharing Files
Saving Files
what the reason for this?
As noted, other apps do not have access to your app's internal storage.
none of them are able to resolve the issue
Feel free to open a fresh StackOverflow question, where you explain, completely and precisely what specific solutions you have tried and what specific problems you have encountered.
but that does not seems to be working as per SO post!!!
Feel free to open a fresh StackOverflow question, where you explain, completely and precisely what "that does not seems to be working" means.
Or, use FileProvider, which offers this capability with no code required beyond an entry for it in your manifest.
Or, store your image on external storage, such as getExternalCacheDir().
I share cached image by followed steps .
1.Copy your cached image to target path.
public static File copyImage(String sourcePath, String targetPath){
try {
InputStream in = new FileInputStream(sourcePath);
OutputStream out = new FileOutputStream(targetPath);
byte[] buf = new byte[1024];
int len;
while ((len = in.read(buf)) > 0) {
out.write(buf, 0, len);
}
in.close();
out.close();
return new File(targetPath);
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
2.Get the Uri of copy file.
Uri uri = Uri.fromFile(target);
3.Share image by intent
File dir = new File(Constant.INKPIC_PATH);//your custom path,such as /mnt/sdcard/Pictures
if(!dir.exists()){
dir.mkdirs();
}
File f = new File(dir,"temporary_file.jpg");
File target = copyImage(url,f.getAbsolutePath());
Uri uri = Uri.fromFile(target);
Intent intent = new Intent();
intent.setAction(Intent.ACTION_SEND);
intent.setType("image/*");
intent.putExtra(Intent.EXTRA_STREAM,uri );
context.startActivity(intent);
My goal is to create a XML file on internal storage and then send it through the share Intent.
I'm able to create a XML file using this code
FileOutputStream outputStream = context.openFileOutput(fileName, Context.MODE_WORLD_READABLE);
PrintStream printStream = new PrintStream(outputStream);
String xml = this.writeXml(); // get XML here
printStream.println(xml);
printStream.close();
I'm stuck trying to retrieve a Uri to the output file in order to share it. I first tried to access the file by converting the file to a Uri
File outFile = context.getFileStreamPath(fileName);
return Uri.fromFile(outFile);
This returns file:///data/data/com.my.package/files/myfile.xml but I cannot appear to attach this to an email, upload, etc.
If I manually check the file length, it's proper and shows there is a reasonable file size.
Next I created a content provider and tried to reference the file and it isn't a valid handle to the file. The ContentProvider doesn't ever seem to be called a any point.
Uri uri = Uri.parse("content://" + CachedFileProvider.AUTHORITY + "/" + fileName);
return uri;
This returns content://com.my.package.provider/myfile.xml but I check the file and it's zero length.
How do I access files properly? Do I need to create the file with the content provider? If so, how?
Update
Here is the code I'm using to share. If I select Gmail, it does show as an attachment but when I send it gives an error Couldn't show attachment and the email that arrives has no attachment.
public void onClick(View view) {
Log.d(TAG, "onClick " + view.getId());
switch (view.getId()) {
case R.id.share_cancel:
setResult(RESULT_CANCELED, getIntent());
finish();
break;
case R.id.share_share:
MyXml xml = new MyXml();
Uri uri;
try {
uri = xml.writeXmlToFile(getApplicationContext(), "myfile.xml");
//uri is "file:///data/data/com.my.package/files/myfile.xml"
Log.d(TAG, "Share URI: " + uri.toString() + " path: " + uri.getPath());
File f = new File(uri.getPath());
Log.d(TAG, "File length: " + f.length());
// shows a valid file size
Intent shareIntent = new Intent();
shareIntent.setAction(Intent.ACTION_SEND);
shareIntent.putExtra(Intent.EXTRA_STREAM, uri);
shareIntent.setType("text/plain");
startActivity(Intent.createChooser(shareIntent, "Share"));
} catch (FileNotFoundException e) {
e.printStackTrace();
}
break;
}
}
I noticed that there is an Exception thrown here from inside createChooser(...), but I can't figure out why it's thrown.
E/ActivityThread(572): Activity
com.android.internal.app.ChooserActivity has leaked IntentReceiver
com.android.internal.app.ResolverActivity$1#4148d658 that was
originally registered here. Are you missing a call to
unregisterReceiver()?
I've researched this error and can't find anything obvious. Both of these links suggest that I need to unregister a receiver.
ChooserActivity has leaked IntentReceiver
Why does Intent.createChooser() need a BroadcastReceiver and how to implement?
I have a receiver setup, but it's for an AlarmManager that is set elsewhere and doesn't require the app to register / unregister.
Code for openFile(...)
In case it's needed, here is the content provider I've created.
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
String fileLocation = getContext().getCacheDir() + "/" + uri.getLastPathSegment();
ParcelFileDescriptor pfd = ParcelFileDescriptor.open(new File(fileLocation), ParcelFileDescriptor.MODE_READ_ONLY);
return pfd;
}
It is possible to expose a file stored in your apps private directory via a ContentProvider. Here is some example code I made showing how to create a content provider that can do this.
Manifest
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.providertest"
android:versionCode="1"
android:versionName="1.0">
<uses-sdk android:minSdkVersion="11" android:targetSdkVersion="15" />
<application android:label="#string/app_name"
android:icon="#drawable/ic_launcher"
android:theme="#style/AppTheme">
<activity
android:name=".MainActivity"
android:label="#string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<provider
android:name="MyProvider"
android:authorities="com.example.prov"
android:exported="true"
/>
</application>
</manifest>
In your ContentProvider override openFile to return the ParcelFileDescriptor
#Override
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
File cacheDir = getContext().getCacheDir();
File privateFile = new File(cacheDir, "file.xml");
return ParcelFileDescriptor.open(privateFile, ParcelFileDescriptor.MODE_READ_ONLY);
}
Make sure you have copied your xml file to the cache directory
private void copyFileToInternal() {
try {
InputStream is = getAssets().open("file.xml");
File cacheDir = getCacheDir();
File outFile = new File(cacheDir, "file.xml");
OutputStream os = new FileOutputStream(outFile.getAbsolutePath());
byte[] buff = new byte[1024];
int len;
while ((len = is.read(buff)) > 0) {
os.write(buff, 0, len);
}
os.flush();
os.close();
is.close();
} catch (IOException e) {
e.printStackTrace(); // TODO: should close streams properly here
}
}
Now any other apps should be able to get an InputStream for your private file by using the content uri (content://com.example.prov/myfile.xml)
For a simple test, call the content provider from a seperate app similar to the following
private class MyTask extends AsyncTask<String, Integer, String> {
#Override
protected String doInBackground(String... params) {
Uri uri = Uri.parse("content://com.example.prov/myfile.xml");
InputStream is = null;
StringBuilder result = new StringBuilder();
try {
is = getApplicationContext().getContentResolver().openInputStream(uri);
BufferedReader r = new BufferedReader(new InputStreamReader(is));
String line;
while ((line = r.readLine()) != null) {
result.append(line);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try { if (is != null) is.close(); } catch (IOException e) { }
}
return result.toString();
}
#Override
protected void onPostExecute(String result) {
Toast.makeText(CallerActivity.this, result, Toast.LENGTH_LONG).show();
super.onPostExecute(result);
}
}
So Rob's answer is correct I assume but I did it a bit differently. As far as I understand, with the setting in in provider:
android:exported="true"
you are giving public access to all your files?! Anyway, a way to give only access to some files is to define file path permissions in the following way:
<provider
android:authorities="com.your.app.package"
android:name="android.support.v4.content.FileProvider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/file_paths" />
</provider>
and then in your XML directory you define file_paths.xml file as follows:
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<files-path path="/" name="allfiles" />
<files-path path="tmp/" name="tmp" />
</paths>
now, the "allfiles" gives the same kind of public permission I guess as the option android:exported="true" but you don't really want that I guess so to define a subdirectory is the next line. Then all you have to do is store the files you want to share, there in that dir.
Next what you have to do is, as also Rob says, obtain a URI for this file. This is how I did it:
Uri contentUri = FileProvider.getUriForFile(context, "com.your.app.package", sharedFile);
Then, when I have this URI, I had to attach to it permissions for other app to use it. I was using or sending this file URI to camera app. Anyway this is the way how I got the other app package info and granted permissions to the URI:
PackageManager packageManager = getPackageManager();
List<ResolveInfo> list = packageManager.queryIntentActivities(cameraIntent, PackageManager.MATCH_DEFAULT_ONLY);
if (list.size() < 1) {
return;
}
String packageName = list.get(0).activityInfo.packageName;
grantUriPermission(packageName, sharedFileUri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
ClipData clipData = ClipData.newRawUri("CAMFILE", sharedFileUri);
cameraIntent.setClipData(clipData);
cameraIntent.setFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
cameraIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivityForResult(cameraIntent, GET_FROM_CAMERA);
I left the code for camera as I did not want to take some other example I did not work on. But this way you see that you can attach permissions to URI itself.
The camera's thing is that I can set it via ClipData and then additionally set permissions. I guess in your case you only need FLAG_GRANT_READ_URI_PERMISSION as you are attaching a file to an email.
Here is the link to help on FileProvider as I based all of my post on the info I found there. Had some trouble finding a package info for camera app though.
Hope it helps.
None of the above answers helped. My problem was the point of passing intent extras but I'll walk you through all the steps to share a file.
Step 1: Create a Content Provider
This will make the file accessible to whichever app you want to share with.
Paste the following in the Manifest.xml file inside the <application></applicatio> tags
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="{YOUR_PACKAGE_NAME}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/provider_paths" />
</provider>
Step 2: Define paths accessible by the content provider
Do this by creating a file called provider_paths.xml (or a name of your choice) under res/xml. Put the following code in the file
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path
name="external"
path="." />
<external-files-path
name="external_files"
path="." />
<cache-path
name="cache"
path="." />
<external-cache-path
name="external_cache"
path="." />
<files-path
name="files"
path="." />
</paths>
Step 3: Create the Intent to share the file
Intent intentShareFile = new Intent(Intent.ACTION_SEND);
Uri uri = FileProvider.getUriForFile(getApplicationContext(), getPackageName() + ".fileprovider", fileToShare);
intentShareFile.setDataAndType(uri, URLConnection.guessContentTypeFromName(fileToShare.getName()));
//Allow sharing apps to read the file Uri
intentShareFile.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
//Pass the file Uri instead of the path
intentShareFile.putExtra(Intent.EXTRA_STREAM,
uri);
startActivity(Intent.createChooser(intentShareFile, "Share File"));
If you need to permission other apps to see your app's private files (for Share, or otherwise) you might be able to save some time and just use v4 compat library's FileProvider
This is what i'm using:
I combined some answers and used the current AndroidX Doku:
Sharing files Android Development
Basic Process: You change the manifest to make it possible for other apps to access your local files. the filepath's that are allowed to be accessed from outside are found in the res/xml/filepaths.xml. When sharing you create an intent to share and set a Flag that temporarily allowed the other app to access your local files. Android documentation claims this is the secure way to share files
Step1: Add FileProvider to Manifest
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.YOUR.APP.PACKAGE.fileprovider"
android:grantUriPermissions="true"
android:exported="false">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/filepaths" />
</provider>
Step2: Add filepaths.xml to res/xml (if XML folder does not exists just create it yourself)
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<files-path path="share/" name="share" />
</paths>
Step3: Use a function like this to start a file share. this function moves the file to the predefined share folder and creates a Url to it. the ShareDir is the File pointing to the files/share/ directory. the copy_File function copies the given file to the share directory in order to be accessible from the outside.
The function also makes it possible to Send the File as email with given header and body. if not needed just set it to empty strings
public void ShareFiles(Activity activity, List<File> files, String header, String body) {
ArrayList<Uri> uriList = new ArrayList<>();
if(files != null) {
for (File f : files) {
if (f.exists()) {
File file_in_share = copy_File(f, ShareDir);
if(file_in_share == null)
continue;
// Use the FileProvider to get a content URI
try {
Uri fileUri = FileProvider.getUriForFile(
activity,
"com.YOUR.APP.PACKAGE.fileprovider",
file_in_share);
uriList.add(fileUri);
} catch (IllegalArgumentException e) {
Log.e("File Selector",
"The selected file can't be shared: " + f.toString());
}
}
}
}
if(uriList.size() == 0)
{
Log.w("StorageModule", "ShareFiles: no files to share");
return;
}
Intent intent = new Intent(Intent.ACTION_SEND_MULTIPLE);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.setType("text/html");
intent.putExtra(Intent.EXTRA_SUBJECT, header);
intent.putExtra(Intent.EXTRA_TEXT, body);
intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uriList);
activity.startActivity(Intent.createChooser(intent, "Share Files"));
}
i am calling an intent to share an image. this works with most providers, BUT with Google+. Google+ opens the post activity without the image and displays the toast "You can only post photos stored on your device." at the same time.
File f = storeImage(image); // f = /data/data/com.myapp/files/1333070776978.jpg
Uri uri = Uri.fromFile(f);
Intent share = new Intent(Intent.ACTION_SEND);
share.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
share.setType("image/jpeg");
share.putExtra(Intent.EXTRA_STREAM, uri);
share.putExtra(Intent.EXTRA_TITLE,"Share that title");
share.putExtra(Intent.EXTRA_SUBJECT,"Share that subject");
share.putExtra(Intent.EXTRA_TEXT,"Check that out...");
share.putExtra("sms_body", "sms body");
startActivity(Intent.createChooser(share, "Share Image"));
i save the image with
Context.openFileOutput(fileName, Context.MODE_WORLD_READABLE);
my understanding was that by setting FLAG_GRANT_READ_URI_PERMISSION, i give Google+ specific access to this file.
it works when i store the image into the MediaStore, but i actually don't wanna clutter the users image gallery.
ContentValues values = new ContentValues(2);
values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
values.put(MediaStore.Images.Media.DATA, f.getAbsolutePath());
Uri uri = getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
any advice is appreciated
Simon
Google+ cannot access file:// Uris from the private folder of another application. And the FLAG_GRANT_READ_URI_PERMISSION doesn't work in this case because it's only for Uris in the "data" part of an Intent, but not for extras.
One common workaround is to add it to the gallery (via the MediaStore) but it's not necessary to do so. The correct solution in this case is to use a FileProvider:
FileProvider is a special subclass of ContentProvider that facilitates
secure sharing of files associated with an app by creating a
content:// Uri for a file instead of a file:/// Uri.
First place the following in your AndroidManifest.xml
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.myapp.testshare.fileprovider"
android:grantUriPermissions="true"
android:exported="false">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/filepaths" />
</provider>
Then this file in res\xml\filepaths.xml:
<?xml version="1.0" encoding="utf-8"?>
<paths>
<files-path name="shared_images" path="shared_images/" />
</paths>
Then, to share an image file, make sure it's in the path specified above (i.e. shared_images under getFilesDir()) and build the intent as follows:
File file = getImageFileToShare();
Uri fileUri = FileProvider.getUriForFile(this, "com.myapp.testshare.fileprovider", file);
Intent intent = new Intent(Intent.ACTION_SEND);
intent.putExtra(Intent.EXTRA_STREAM, fileUri);
intent.setType("image/png");
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(intent);
(Make sure the authority specified in the getUriForFile() method matches the one in the manifest).
This will produce a content:// Uri (like content://com.myapp.testshare.fileprovider/shared_images/img1.png that the Google+ app will be able to access, and thus include in the post).
Simon,
Seems as if it works to set the permission after you set the uri.
share.putExtra(Intent.EXTRA_STREAM, uri);
share.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
try this one, I'm also having problems with this as its showing up in Gallery if I do an insert..
//out here is a File instance
String uriString = MediaStore.Images.Media.insertImage(getContentResolver(),
out.getAbsolutePath(),null,null);
intent.putExtra(Intent.EXTRA_STREAM,Uri.parse(uriString));
https://github.com/google-plus/google-plus-office-hours/blob/master/2012_08_14-curiosity-android-app/src/com/example/CuriosityActivity.java
If you are using a custom ContentProvider you must check that the MIME type of the images is correct. The correct MIME for a jpeg is "image/jpeg". If you return "image/jpg" in the overridden getType(Uri uri); method then Google+ fails to attach them. Most other one work, e.g. messaging and email. "image/jpg" is NOT a valid IANA MIME type:
http://www.iana.org/assignments/media-types/image
The specification of getType is clear about this, but it is very easy to miss (I did). Having solved this, I wanted to ensure others spot this because it is very unobbious.
You can use this :
Drawable drawable;
File file;
if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.LOLLIPOP) {
drawable = getResources().getDrawable(imgResId);
} else {
drawable = getResources().getDrawable(imgResId, null);
}
if (drawable != null) {
Bitmap bm = ((BitmapDrawable) drawable).getBitmap();
String path = Environment.getExternalStorageDirectory()
.toString();
OutputStream fOut = null;
file = new File(path, "pic.jpg"); // the File to save to
try {
file.createNewFile();
fOut = new FileOutputStream(file);
// obtaining the Bitmap
bm.compress(Bitmap.CompressFormat.JPEG, 100, fOut);
fOut.flush();
fOut.close();
return file;
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
I try to export a bitmap from my app using share intent without saving a file for a temporal location. All the examples I found are two-step
1) save to SD Card and create Uri for that file
2) start the intent with this Uri
Is it possible to make it without requiring WRITE_EXTERNAL_STORAGE permission, saving the file [and removing it afterwards]? How to address devices without ExternalStorage?
I had this same problem. I didn't want to have to ask for the read and write external storage permissions. Also, sometimes there are problems when phones don't have SD cards or the cards get unmounted.
The following method uses a ContentProvider called FileProvider. Technically, you are still saving the bitmap (in internal storage) prior to sharing, but you don't need to request any permissions. Also, every time you share the bitmap the image file gets overwritten. And since it is in the internal cache, it will be deleted when the user uninstalls the app. So in my opinion, it is just as good as not saving the image. This method is also more secure than saving it to external storage.
The documentation is pretty good (see the Further Reading below), but some parts are a little tricky. Here is a summary that worked for me.
Set up the FileProvider in the Manifest
<manifest>
...
<application>
...
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.example.myapp.fileprovider"
android:grantUriPermissions="true"
android:exported="false">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/filepaths" />
</provider>
...
</application>
</manifest>
Replace com.example.myapp with your app package name.
Create res/xml/filepaths.xml
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<cache-path name="shared_images" path="images/"/>
</paths>
This tells the FileProvider where to get the files to share (using the cache directory in this case).
Save the image to internal storage
// save bitmap to cache directory
try {
File cachePath = new File(context.getCacheDir(), "images");
cachePath.mkdirs(); // don't forget to make the directory
FileOutputStream stream = new FileOutputStream(cachePath + "/image.png"); // overwrites this image every time
bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream);
stream.close();
} catch (IOException e) {
e.printStackTrace();
}
Share the image
File imagePath = new File(context.getCacheDir(), "images");
File newFile = new File(imagePath, "image.png");
Uri contentUri = FileProvider.getUriForFile(context, "com.example.myapp.fileprovider", newFile);
if (contentUri != null) {
Intent shareIntent = new Intent();
shareIntent.setAction(Intent.ACTION_SEND);
shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); // temp permission for receiving app to read this file
shareIntent.setDataAndType(contentUri, getContentResolver().getType(contentUri));
shareIntent.putExtra(Intent.EXTRA_STREAM, contentUri);
startActivity(Intent.createChooser(shareIntent, "Choose an app"));
}
Further reading
FileProvider
Storage Options - Internal Storage
Sharing Files
Saving Files
I try to export a bitmap from my app using share intent without saving a file for a temporal location.
In theory, this is possible. In practice, it is probably not possible.
In theory, all you need to share is a Uri that will resolve to the bitmap. The simplest approach is if that is a file that is directly accessible by the other application, such as on external storage.
To not write it to flash at all, you would need to implement your own ContentProvider, figure out how to implement openFile() to return your in-memory bitmap, and then pass a Uri representing that bitmap in the ACTION_SEND Intent. Since openFile() needs to return a ParcelFileDescriptor, I don't know how you would do that without an on-disk representation, but I have not spent much time searching.
Is it possible to make it without requiring WRITE_EXTERNAL_STORAGE permission, saving the file [and removing it afterwards]?
If you simply do not want it on external storage, you can go the ContentProvider route, using a file on internal storage. This sample project demonstrates a ContentProvider that serves up a PDF file via ACTION_VIEW to a PDF viewer on a device; the same approach could be used for ACTION_SEND.
If anyone still looking for easy and short solution without any storage permission (Supports nougat 7.0 as well). Here it is.
Add this in Manifest
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/provider_paths" />
</provider>
Now create provider_paths.xml
<paths>
<external-path name="external_files" path="."/>
</paths>
Finally Add this method to your activity/fragment (rootView is the view you want share)
private void ShareIt(View rootView){
if (rootView != null && context != null && !context.isFinishing()) {
rootView.setDrawingCacheEnabled(true);
Bitmap bitmap = Bitmap.createBitmap(rootView.getDrawingCache());
if (bitmap != null ) {
//Save the image inside the APPLICTION folder
File mediaStorageDir = new File(AppContext.getInstance().getExternalCacheDir() + "Image.png");
try {
FileOutputStream outputStream = new FileOutputStream(String.valueOf(mediaStorageDir));
bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream);
outputStream.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
if (ObjectUtils.isNotNull(mediaStorageDir)) {
Uri imageUri = FileProvider.getUriForFile(getActivity(), getActivity().getApplicationContext().getPackageName() + ".provider", mediaStorageDir);
if (ObjectUtils.isNotNull(imageUri)) {
Intent waIntent = new Intent(Intent.ACTION_SEND);
waIntent.setType("image/*");
waIntent.putExtra(Intent.EXTRA_STREAM, imageUri);
startActivity(Intent.createChooser(waIntent, "Share with"));
}
}
}
}
}
Update:
As #Kathir mentioned in comments,
DrawingCache is deprecated from API 28+. Use below code to use Canvas instead.
Bitmap bitmap = Bitmap.createBitmap(rootView.getWidth(), rootView.getHeight(), quality);
Canvas canvas = new Canvas(bitmap);
Drawable backgroundDrawable = view.getBackground();
if (backgroundDrawable != null) {
backgroundDrawable.draw(canvas);
} else {
canvas.drawColor(Color.WHITE);
}
view.draw(canvas);
return bitmap;
This for sharing CardView as an Image then saving it in the cache subdirectory of the app's internal storage area.
hope it will be helpful.
#Override
public void onClick(View view) {
CardView.setDrawingCacheEnabled(true);
CardView.buildDrawingCache();
Bitmap bitmap = CardView.getDrawingCache();
try{
File file = new File(getContext().getCacheDir()+"/Image.png");
bitmap.compress(Bitmap.CompressFormat.PNG,100,new FileOutputStream(file));
Uri uri = FileProvider.getUriForFile(getContext(),"com.mydomain.app", file);
Intent shareIntent = new Intent();
shareIntent.setAction(Intent.ACTION_SEND);
shareIntent.putExtra(Intent.EXTRA_STREAM, uri);
shareIntent.setType("image/jpeg");
getContext().startActivity(Intent.createChooser(shareIntent, "Share"));
}catch (FileNotFoundException e) {e.printStackTrace();}
}
});
Here is working method to make a screenshot of own app and share it as image via any messanger or email client.
To fix the bitmap not updating problem I improved Suragch's answer, using Gurupad Mamadapur's comment and added own modifications.
Here is code in Kotlin language:
private lateinit var myRootView:View // root view of activity
#SuppressLint("SimpleDateFormat")
private fun shareScreenshot() {
// We need date and time to be added to image name to make it unique every time, otherwise bitmap will not update
val sdf = SimpleDateFormat("yyyyMMdd_HHmmss")
val currentDateandTime = sdf.format(Date())
val imageName = "/image_$currentDateandTime.jpg"
// CREATE
myRootView = window.decorView.rootView
myRootView.isDrawingCacheEnabled = true
myRootView.buildDrawingCache(true) // maybe You dont need this
val bitmap = Bitmap.createBitmap(myRootView.drawingCache)
myRootView.isDrawingCacheEnabled = false
// SAVE
try {
File(this.cacheDir, "images").deleteRecursively() // delete old images
val cachePath = File(this.cacheDir, "images")
cachePath.mkdirs() // don't forget to make the directory
val stream = FileOutputStream("$cachePath$imageName")
bitmap.compress(Bitmap.CompressFormat.JPEG, 90, stream) // can be png and any quality level
stream.close()
} catch (ex: Exception) {
Toast.makeText(this, ex.javaClass.canonicalName, Toast.LENGTH_LONG).show() // You can replace this with Log.e(...)
}
// SHARE
val imagePath = File(this.cacheDir, "images")
val newFile = File(imagePath, imageName)
val contentUri = FileProvider.getUriForFile(this, "com.example.myapp.fileprovider", newFile)
if (contentUri != null) {
val shareIntent = Intent()
shareIntent.action = Intent.ACTION_SEND
shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) // temp permission for receiving app to read this file
shareIntent.type = "image/jpeg" // just assign type. we don't need to set data, otherwise intent will not work properly
shareIntent.putExtra(Intent.EXTRA_STREAM, contentUri)
startActivity(Intent.createChooser(shareIntent, "Choose app"))
}
}