Exporting image through ContentProvider - android

I´m trying to export an image from my application to other applications like twitter, facebook or other apps accepting it images.
I'm doing the following:
private void exportImage()
{
storeBitmapOnDisk(this.bitmap);
Intent i = new Intent(Intent.ACTION_SEND);
i.addCategory(Intent.CATEGORY_LAUNCHER);
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
File imgFile = context.getFileStreamPath("export.png");
Uri uri = Uri.fromFile(imgFile);
i.putExtra(Intent.EXTRA_STREAM, uri);
i.setType("image/png");
i.setComponent(componentName);
context.startActivity(i);
}
private void storeBitmapOnDisk(Bitmap bitmap)
{
try
{
FileOutputStream outStream = context.openFileOutput("export.png",
Context.MODE_WORLD_READABLE);
bitmap.compress(Bitmap.CompressFormat.PNG, 50, outStream);
outStream.close();
}
catch (IOException e)
{
Log.d(TAG, e.getMessage());
}
}
This is not working because I'm writing internal storage with is not accessible by other applications. As I don't want to use external storage (SD card) I think what I need is a ContentProvider but all examples I saw is about custom ContentPovider are using a SQlite database to store the data. I can't figure out how I could store a bitmap in internal storage and make it available through a ContentProvider to other applications without storing my bitmap in a database. MatrixCursor seems not to be adapted too...
SOLUTION I create a custom Content Provider and set the path of the file in the the content provider as the Uri EXTRA_STREAM extra parameter of the intent:
First my provider class where I only needed to override openFile which is not clear in the doc...
public class ExportContentProvider extends ContentProvider
{
public static Uri CONTENT_URI = Uri
.parse("content://com.path.to.my.provider");
#Override
public int delete(Uri uri, String selection, String[] selectionArgs)
{
return 0;
}
#Override
public String getType(Uri uri)
{
return null;
}
#Override
public Uri insert(Uri uri, ContentValues values)
{
return null;
}
#Override
public boolean onCreate()
{
return false;
}
#Override
public ParcelFileDescriptor openFile(Uri uri, String mode)
{
int imode = 0;
if (mode.contains("w"))
imode |= ParcelFileDescriptor.MODE_WRITE_ONLY;
if (mode.contains("r"))
imode |= ParcelFileDescriptor.MODE_READ_ONLY;
if (mode.contains("+"))
imode |= ParcelFileDescriptor.MODE_APPEND;
try
{
return ParcelFileDescriptor.open(new File(uri.getEncodedPath()), imode);
}
catch (FileNotFoundException e)
{
e.printStackTrace();
}
return null;
}
#Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder)
{
return null;
}
#Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs)
{
return 0;
}
}
And then my export function:
private void exportImage()
{
storeBitmapOnDisk();
Intent i = new Intent(Intent.ACTION_SEND);
i.addCategory(Intent.CATEGORY_LAUNCHER);
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
i.putExtra(Intent.EXTRA_STREAM, Uri.withAppendedPath(
ExportContentProvider.CONTENT_URI, context.getFileStreamPath(
"export.png").getAbsolutePath()));
i.setType("image/png");
i.setComponent(componentName);
if (exportFileObserver == null)
{
this.bitmapPath = context.getFileStreamPath("export.png")
.getAbsolutePath();
exportFileObserver = new ExportFileObserver(this.bitmapPath);
exportFileObserver.startWatching();
}
listener.launchExportActivity(Dms.ACT_IMAGE_EXPORT, i);
}
Nevertheless there are 2 problems remaining:
- When trying to delete my temporary export.png file on my internal memory using result of the activity I received the onResult when the activity is launch...
- It s not working for mail app in my 2.3 emulator. I'm getting "File to large" message...
Somenone tested that ?

Related

Issue when opening a DOCX file through a third party app using IOCipher and ContentProvider

I'm building a secure android application that runs with IOCipher and SQLCipher. My app is storing PDF, DOC, DOCX, XLS, XLSX files that are intended to be openned by a third party application. Currently I can open all these type of files but DOCX. When I open a docx file that is stored in IOCipher using this method:
File file = new File(path);
Uri contentUri = Uri.parse(VFSContentProvider.FILES_URI + file.getName());
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setData(contentUri);
Microsoft Word prompt me with that error message :
Can't open file "myFile.docx.docx".
This happen For all my docx file, it seems to append the mime type twice at the end of the file when it's returned by the content provider...
Here is my IOCipher Content Provider:
public class VFSContentProvider extends ContentProvider {
public static final String TAG = "VFSContentProvider";
public static final Uri FILES_URI = Uri
.parse("content://com.plante.android.cobalt.VFSContentProvider/");
private MimeTypeMap mimeTypeMap;
#Override
public boolean onCreate() {
mimeTypeMap = MimeTypeMap.getSingleton();
return true;
}
#Override
public String getType(Uri uri) {
String fileExtension = MimeTypeMap.getFileExtensionFromUrl(uri.toString());
return mimeTypeMap.getMimeTypeFromExtension(fileExtension);
}
#Override
public ParcelFileDescriptor openFile(Uri uri, String mode)
throws FileNotFoundException {
ParcelFileDescriptor[] pipe = null;
InputStream in = null;
try {
pipe = ParcelFileDescriptor.createPipe();
String path = uri.getPath();
Log.i(TAG, "streaming " + path);
// BufferedInputStream could help, AutoCloseOutputStream conflicts
in = new FileInputStream(new File(path));
new PipeFeederThread(in, new AutoCloseOutputStream(pipe[1])).start();
} catch (IOException e) {
Log.e(TAG, "Error opening pipe", e);
throw new FileNotFoundException("Could not open pipe for: "
+ uri.toString());
}
return (pipe[0]);
}
#Override
public Cursor query(Uri url, String[] projection, String selection,
String[] selectionArgs, String sort) {
// throw new RuntimeException("Operation not supported");
return null;
}
#Override
public Uri insert(Uri uri, ContentValues initialValues) {
throw new RuntimeException("Operation not supported");
}
#Override
public int update(Uri uri, ContentValues values, String where,
String[] whereArgs) {
throw new RuntimeException("Operation not supported");
}
#Override
public int delete(Uri uri, String where, String[] whereArgs) {
throw new RuntimeException("Operation not supported");
}
static class PipeFeederThread extends Thread {
InputStream in;
OutputStream out;
PipeFeederThread(InputStream in, OutputStream out) {
this.in = in;
this.out = out;
}
#Override
public void run() {
byte[] buf = new byte[8192];
int len;
try {
while ((len = in.read(buf)) > 0) {
out.write(buf, 0, len);
}
in.close();
out.flush();
out.close();
} catch (IOException e) {
Log.e(TAG, "File transfer failed:", e);
}
}
}
Here is the log file:
03-16 13:38:20.714 800-1325/? I/ActivityManager: START u0 {act=android.intent.action.VIEW dat=content://com.plante.android.cobalt.VFSContentProvider/0c8dbc0e1671e127ed9fcb2786b218d379f12888Finance_Corporate_BCP_Checklist_2014_MH20150115.docx flg=0x13000003 cmp=com.microsoft.office.word/com.microsoft.office.apphost.LaunchActivity} from uid 10182 on display 0

Not able to attach File to an email app in android from asset folder

I tried attaching a image from asset folder to email. But I haven't got considerable success in that while I have been able to tweet an image from asset with help of ContentProvider
public class AssetProvider extends ContentProvider {
#Override
public AssetFileDescriptor openAssetFile( Uri uri, String mode ) throws FileNotFoundException
{
Log.v("HERREEE", "AssetsGetter: Open asset file");
AssetManager am = getContext( ).getAssets( );
String file_name = uri.getLastPathSegment( );
if( file_name == null )
throw new FileNotFoundException( );
AssetFileDescriptor afd = null;
try
{
afd = am.openFd( file_name );
}
catch(IOException e)
{
e.printStackTrace( );
}
return afd;//super.openAssetFile(uri, mode);
}
#Override
public String getType( Uri p1 )
{
// TODO: Implement this method
return null;
}
#Override
public int delete( Uri p1, String p2, String[] p3 )
{
// TODO: Implement this method
return 0;
}
#Override
public Cursor query( Uri p1, String[] p2, String p3, String[] p4, String p5 )
{
// TODO: Implement this method
return null;
}
#Override
public Cursor query( Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder, CancellationSignal cancellationSignal )
{
// TODO: Implement this method
return super.query( uri, projection, selection, selectionArgs, sortOrder, cancellationSignal );
}
#Override
public Uri insert( Uri p1, ContentValues p2 )
{
// TODO: Implement this method
return null;
}
#Override
public boolean onCreate( )
{
return false;
}
#Override
public int update( Uri p1, ContentValues p2, String p3, String[] p4 )
{
// TODO: Implement this method
return 0;
}
}
Code for posting image from asset folder to twitter :
Intent intent = new Intent(Intent.ACTION_SEND);
intent.putExtra(Intent.EXTRA_TEXT,"Test tweet");
intent.setType("*/*");
final PackageManager pm = mContext.getPackageManager();
final List<?> activityList = pm.queryIntentActivities(intent, 0);
int len = activityList.size();
boolean isFound = false;
for (int i = 0; i < len; i++) {
final ResolveInfo app = (ResolveInfo) activityList.get(i);
if(app.activityInfo.packageName.contains("com.twitter.android")){
isFound = true;
final ActivityInfo activity=app.activityInfo;
final ComponentName name=new ComponentName(activity.applicationInfo.packageName, activity.name);
intent.addCategory(Intent.CATEGORY_LAUNCHER);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
intent.setComponent(name);
Uri uri = Uri.parse("content://com.authority/file:///android_asset/image.png");
intent.putExtra(android.content.Intent.EXTRA_STREAM, uri);
mContext.startActivity(intent);
break;
}
}
But while doing same for email it gives a message "Can't attach an empty file"
Email:
Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("*/*");
intent.putExtra(Intent.EXTRA_SUBJECT, "Checkout MY Application");
intent.putExtra(Intent.EXTRA_TEXT, "Test mail");
Uri uri = Uri.parse("content://com.authority/file:///android_asset/image.png"));
intent.putExtra(android.content.Intent.EXTRA_STREAM,uri);
mContext.startActivity(intent);
Please suggest.
Thanks in advance.
The Latest Facebook App versions doesn't allow you to share text using intents. You have to use the Facebook SDK to share/publish a post or you can share only images or url.
Please refer this post, it says Facebook Design Team has intentionally closed pre-filling a message as it is against their policy.
For more information you can refer these Stack Overflow Questions :
Share Text on Facebook from Android App via ACTION_SEND
Android: How to share image with text on facebook via intent?
Edit 1 : There is a work around refer this SO Answer.

Sharing an image from internal storage

I've been trying and trying to get this to work. I've gone through many examples with no luck. The problem is that the image is not attached when I try to share it, for example, using an email client. I managed to get this to work when using external storage but internal storage suits my needs better.
I click a button and then the image is saved to internal storage and right after that it is shared but there's no image.
This is from the Activity class:
int width = size.x;
int height = size.y;
Bitmap shareBmp = Bitmap.createBitmap(screenBmp, 0, 0, width, height);
ContextWrapper wrapper = new ContextWrapper(getApplicationContext());
File directory = wrapper.getDir("images", Context.MODE_PRIVATE);
File filePath = new File(directory, "share.png");
FileOutputStream fos;
try
{
fos = new FileOutputStream(filePath);
shareBmp.compress(Bitmap.CompressFormat.PNG, 90, fos);
fos.close();
}
catch (Exception aE){}
Uri uri = Uri.parse("content://com.example.Test/share.png");
Intent intent = new Intent();
intent.setAction(Intent.ACTION_SEND);
intent.putExtra(Intent.EXTRA_STREAM, uri);
intent.setType("image/png");
startActivity(Intent.createChooser(intent, "Share Image"));
Here's the ImageProvider class:
public class ImageProvider extends ContentProvider
{
#Override
public ParcelFileDescriptor openFile(Uri aUri, String aMode) throwsFileNotFoundException
{
File file = new File(getContext().getFilesDir(), aUri.getPath());
if (file.exists())
{
return (ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY));
}
throw new FileNotFoundException(aUri.getPath());
}
#Override
public boolean onCreate()
{
return false;
}
#Override
public int delete(Uri aUri, String aSelection, String[] aSelectionArgs)
{
return 0;
}
#Override
public String getType(Uri aUri)
{
return null;
}
#Override
public Uri insert(Uri aUri, ContentValues aValues)
{
return null;
}
#Override
public Cursor query(Uri aUri, String[] aProjection, String aSelection, String[] aSelectionArgs, String aSortOrder)
{
return null;
}
#Override
public int update(Uri aUri, ContentValues aValues, String aSelection, String[] aSelectionArgs)
{
return 0;
}
}
This is from the manifest:
<provider
android:name=".ImageProvider"
android:authorities="com.example.Test"
android:exported="true"/>
Only your app can see/read/write files in it's app specific -private- storage. So you cannot ask other app to share from it as they cannot even 'see' those files there.

Share an image with a content provider in Android app

I'm developing an Android app that is a gallery of images in which the images are downloaded from internet for display on the screen of smathphone. Images are displayed one at a time and the application has a button to share the image that is displayed.
Following the directions I've found in some StackOverflow post which indicated that the right way to share an image was using a ContentProvider I have implemented the following code that works to share the images of certain applications (eg Twitter, Gmail ...) but does not work for others (Facebook, Yahoo, MMS ...).
Then I show the code used hoping you can help me get the correct implementation to share images in all applications.
Initially I capture the button press to share:
#Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.menu_share) {
// I get the image being displayed on the screen
View root = getView();
ImageView imageView = (ImageView) root.findViewById(R.id.image);
Drawable imageToShareDrawable = imageView.getDrawable();
if (imageToShareDrawable instanceof BitmapDrawable) {
// I convert the image to Bitmap
Bitmap imageToShare = ((BitmapDrawable) imageToShareDrawable).getBitmap();
// Name of de image extracted from a bean property
String fileName = quote.getImage();
// I keep the image in the folder "files" of internal storage application
TempInternalStorage.createCachedFile(fileName, imageToShare, getActivity().getApplicationContext());
// I start the Activity to select the application to share the image after the intent Built with the method "getDefaultShareIntent"
startActivity(getDefaultShareIntent(fileName));
} else {
Toast.makeText(getActivity().getApplicationContext(), "Please wait, the quote is being downloaded", Toast.LENGTH_SHORT).show();
}
}
return true;
}
The method for saving the image to the internal storage of the application is as follows:
public static void createCachedFile(String fileName, Bitmap image, Context context) {
try {
File file = new File(context.getFilesDir(), fileName);
if (!file.exists()) {
FileOutputStream fos = new FileOutputStream(file);
image.compress(Bitmap.CompressFormat.JPEG, 100, fos);
fos.flush();
fos.close();
}
} catch (Exception e) {
Log.e("saveTempFile()", "**** Error");
}
}
The method that constructs the Intent to share it:
private Intent getDefaultShareIntent(String fileName) {
final Intent shareIntent = new Intent();
shareIntent.setAction(Intent.ACTION_SEND);
shareIntent.setType("image/jpeg");
shareIntent.putExtra(Intent.EXTRA_TEXT, "Test text");
shareIntent.putExtra(Intent.EXTRA_STREAM, Uri.parse("content://" + CachedFileProvider.AUTHORITY + File.separator + "img" + File.separator + fileName));
shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
return shareIntent;
}
Finally ContentProvider code is as follows:
public class CachedFileProvider extends ContentProvider {
private static final String CLASS_NAME = "CachedFileProvider";
public static final String AUTHORITY = "com.example.appname.cachefileprovider";
private UriMatcher uriMatcher;
#Override
public boolean onCreate() {
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI(AUTHORITY, "img/*", 1);
return true;
}
#Override
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
String LOG_TAG = CLASS_NAME + " - openFile";
Log.i(LOG_TAG, "Called with uri: '" + uri + "'." + uri.getLastPathSegment());
switch (uriMatcher.match(uri)) {
case 1:
String fileLocation = getContext().getFilesDir() + File.separator + uri.getLastPathSegment();
ParcelFileDescriptor image = ParcelFileDescriptor.open(new File(fileLocation), ParcelFileDescriptor.MODE_READ_ONLY);
return image;
default:
Log.i(LOG_TAG, "Unsupported uri: '" + uri + "'.");
throw new FileNotFoundException("Unsupported uri: " + uri.toString());
}
}
#Override
public int update(Uri uri, ContentValues contentvalues, String s, String[] as) {
return 0;
}
#Override
public int delete(Uri uri, String s, String[] as) {
return 0;
}
#Override
public Uri insert(Uri uri, ContentValues contentvalues) {
return null;
}
#Override
public String getType(Uri uri) {
return null;
}
#Override
public Cursor query(Uri uri, String[] projection, String s, String[] as1, String s1) {
MatrixCursor c = null;
Log.i(">>>> projection", java.util.Arrays.toString(projection));
String fileLocation = getContext().getFilesDir() + File.separator + uri.getLastPathSegment();
File file = new File(fileLocation);
long time = System.currentTimeMillis();
c = new MatrixCursor(new String[] { "_id", "_data", "orientation", "mime_type", "datetaken", "_display_name" });
c.addRow(new Object[] { 0, file, 0, "image/jpeg", time, uri.getLastPathSegment() });
return c;
}
#Override
public String[] getStreamTypes(Uri uri, String mimeTypeFilter) {
return null;
}
}
I have found that when the image is sharing some applications only call the method "query" (these are where the code does not work and I can not share the image) while there are others that also call the method "query" also call the method "openFile" and these do work and I can share the image.
I hope you can help me, thank you very much in advance.
As #Sun Ning-s comment noted some "share target apps" can handle URI-s starting with "content://.." which you have implemented.
Other apps handle file uri-s starting with "file://...." so you have to implement a 2nd share menue "share as file"
private Intent getFileShareIntent(String fullPathTofile) {
final Intent shareIntent = new Intent();
shareIntent.setAction(Intent.ACTION_SEND);
shareIntent.setType("image/jpeg");
shareIntent.putExtra(Intent.EXTRA_STREAM, Uri.parse("file://" + fullPathTofile));
shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
return shareIntent;
}
You can use the android app intentintercept to find out what other "share source apps" provide.

android share images from assets folder

I'm trying to share an image from my assets folder. My code is:
Intent share = new Intent(Intent.ACTION_SEND);
share.setType("image/jpg");
share.putExtra(Intent.EXTRA_STREAM, Uri.parse("file:///assets/myImage.jpg"));
startActivity(Intent.createChooser(share, "Share This Image"));
but it doesn't work. Do you have any ideas?
It is possible to share files (images including) from the assets folder through a custom ContentProvider
You need to extend ContentProvider, register it in your manifest and implement the openAssetFile method. You can then assess the assets via Uris
#Override
public AssetFileDescriptor openAssetFile(Uri uri, String mode) throws FileNotFoundException {
AssetManager am = getContext().getAssets();
String file_name = uri.getLastPathSegment();
if(file_name == null)
throw new FileNotFoundException();
AssetFileDescriptor afd = null;
try {
afd = am.openFd(file_name);
} catch (IOException e) {
e.printStackTrace();
}
return afd;
}
Complementing what #intrepidis answered:
You will need override methods like example class above:
package com.android.example;
import android.content.ContentProvider;
import android.net.Uri;
import android.content.res.AssetFileDescriptor;
import android.content.res.AssetManager;
import java.io.FileNotFoundException;
import android.content.ContentValues;
import android.database.Cursor;
import java.io.IOException;
import android.os.CancellationSignal;
public class AssetsProvider extends ContentProvider
{
#Override
public AssetFileDescriptor openAssetFile( Uri uri, String mode ) throws FileNotFoundException
{
Log.v( TAG, "AssetsGetter: Open asset file" );
AssetManager am = getContext( ).getAssets( );
String file_name = uri.getLastPathSegment( );
if( file_name == null )
throw new FileNotFoundException( );
AssetFileDescriptor afd = null;
try
{
afd = am.openFd( file_name );
}
catch(IOException e)
{
e.printStackTrace( );
}
return afd;//super.openAssetFile(uri, mode);
}
#Override
public String getType( Uri p1 )
{
// TODO: Implement this method
return null;
}
#Override
public int delete( Uri p1, String p2, String[] p3 )
{
// TODO: Implement this method
return 0;
}
#Override
public Cursor query( Uri p1, String[] p2, String p3, String[] p4, String p5 )
{
// TODO: Implement this method
return null;
}
#Override
public Cursor query( Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder, CancellationSignal cancellationSignal )
{
// TODO: Implement this method
return super.query( uri, projection, selection, selectionArgs, sortOrder, cancellationSignal );
}
#Override
public Uri insert( Uri p1, ContentValues p2 )
{
// TODO: Implement this method
return null;
}
#Override
public boolean onCreate( )
{
// TODO: Implement this method
return false;
}
#Override
public int update( Uri p1, ContentValues p2, String p3, String[] p4 )
{
// TODO: Implement this method
return 0;
}
}
I needed to override two times the query method.
And add these lines above tag in your androidmanifest.xml:
<provider
android:name="com.android.example.AssetsProvider"
android:authorities="com.android.example"
android:grantUriPermissions="true"
android:exported="true" />
And with this, all work like a charm :D
This blog explains it all:
http://nowherenearithaca.blogspot.co.uk/2012/03/too-easy-using-contentprovider-to-send.html
Basically, this goes in the manifest:
<provider android:name="yourclass.that.extendsContentProvider" android:authorities="com.yourdomain.whatever"/>
The content provider class has this:
#Override
public AssetFileDescriptor openAssetFile(Uri uri, String mode) throws FileNotFoundException {
AssetManager am = getContext().getAssets();
String file_name = uri.getLastPathSegment();
if(file_name == null)
throw new FileNotFoundException();
AssetFileDescriptor afd = null;
try {
afd = am.openFd(file_name);
} catch (IOException e) {
e.printStackTrace();
}
return afd;//super.openAssetFile(uri, mode);
}
And the calling code does this:
Uri theUri = Uri.parse("content://com.yourdomain.whatever/someFileInAssetsFolder");
Intent theIntent = new Intent(Intent.ACTION_SEND);
theIntent.setType("image/*");
theIntent.putExtra(Intent.EXTRA_STREAM,theUri);
theIntent.putExtra(android.content.Intent.EXTRA_SUBJECT,"Subject for message");
theIntent.putExtra(android.content.Intent.EXTRA_TEXT, "Body for message");
startActivity(theIntent);
Many apps require you to provide name and size of the image. So here is an improved code (using Google's FileProvider code as an example):
public class AssetsProvider extends ContentProvider {
private final static String LOG_TAG = AssetsProvider.class.getName();
private static final String[] COLUMNS = {
OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE };
#Override
public boolean onCreate() {
return true;
}
#Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
/**
* Source: {#link FileProvider#query(Uri, String[], String, String[], String)} .
*/
if (projection == null) {
projection = COLUMNS;
}
final AssetManager am = getContext().getAssets();
final String path = getRelativePath(uri);
long fileSize = 0;
try {
final AssetFileDescriptor afd = am.openFd(path);
fileSize = afd.getLength();
afd.close();
} catch(IOException e) {
Log.e(LOG_TAG, "Can't open asset file", e);
}
final String[] cols = new String[projection.length];
final Object[] values = new Object[projection.length];
int i = 0;
for (String col : projection) {
if (OpenableColumns.DISPLAY_NAME.equals(col)) {
cols[i] = OpenableColumns.DISPLAY_NAME;
values[i++] = uri.getLastPathSegment();
} else if (OpenableColumns.SIZE.equals(col)) {
cols[i] = OpenableColumns.SIZE;
values[i++] = fileSize;
}
}
final MatrixCursor cursor = new MatrixCursor(cols, 1);
cursor.addRow(values);
return cursor;
}
#Override
public String getType(Uri uri) {
/**
* Source: {#link FileProvider#getType(Uri)} .
*/
final String file_name = uri.getLastPathSegment();
final int lastDot = file_name.lastIndexOf('.');
if (lastDot >= 0) {
final String extension = file_name.substring(lastDot + 1);
final String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
if (mime != null) {
return mime;
}
}
return "application/octet-stream";
}
#Override
public Uri insert(Uri uri, ContentValues values) {
return null;
}
#Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
return 0;
}
#Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
return 0;
}
#Override
public AssetFileDescriptor openAssetFile(Uri uri, String mode) throws FileNotFoundException {
final AssetManager am = getContext().getAssets();
final String path = getRelativePath(uri);
if(path == null) {
throw new FileNotFoundException();
}
AssetFileDescriptor afd = null;
try {
afd = am.openFd(path);
} catch(IOException e) {
Log.e(LOG_TAG, "Can't open asset file", e);
}
return afd;
}
private String getRelativePath(Uri uri) {
String path = uri.getPath();
if (path.charAt(0) == '/') {
path = path.substring(1);
}
return path;
}
}
Since none of the other answers here worked for me (in 2019) I made a workaround by copying the asset to the app's internal file directory and then sharing this file.
In my case, I needed to share a pdf file from the assets folder.
In the AndroidManifest.xml add a file provider (no need to use a custom one):
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/filepaths" />
</provider>
Create a filepaths.xml file in res/xml/
<?xml version="1.0" encoding="utf-8"?>
<paths>
<files-path
name="root"
path="/" />
</paths>
Of course you should use a subdirectory here if you manage other files in your app directory.
Now in the class where you want to trigger the share intent.
1. Create an empty file in the files directory
private fun createFileInFilesDir(filename: String): File {
val file = File(filesDir.path + "/" + filename)
if (file.exists()) {
if (!file.delete()) {
throw IOException()
}
}
if (!file.createNewFile()) {
throw IOException()
}
return file
}
2. Copy the content of the asset to the file
private fun copyAssetToFile(assetName: String, file: File) {
val buffer = ByteArray(1024)
val inputStream = assets.open(assetName)
val outputStream: OutputStream = FileOutputStream(file)
while (inputStream.read(buffer) > 0) {
outputStream.write(buffer)
}
}
3. Create a share intent for the file
private fun createIntentForFile(file: File, intentAction: String): Intent {
val uri = FileProvider.getUriForFile(this, applicationContext.packageName + ".provider", file)
val intent = Intent(intentAction)
intent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
intent.setDataAndType(uri, "application/pdf")
return intent
}
4. Execute 1-3 and fire the intent
private fun sharePdfAsset(assetName: String, intentAction: String) {
try {
val file = createFileInFilesDir(assetName)
copyAssetToFile(assetName, file)
val intent = createIntentForFile(file, intentAction)
startActivity(Intent.createChooser(intent, null))
} catch (e: IOException) {
e.printStackTrace()
AlertDialog.Builder(this)
.setTitle(R.string.error)
.setMessage(R.string.share_error)
.show()
}
}
5. Call the function
sharePdfAsset("your_pdf_asset.pdf", Intent.ACTION_SEND)
If you want to delete the file after sharing it, you probably could use startActivityForResult() and delete it afterwards. By changing the intentAction you can also use this process for an "open with..." action by using Intent.ACTION_VIEW.
For assets, filesDir, ... you need to be in an Activity or have a Context of course.
AFAIK, there's no way to share an image from the assets folder. But it's possible to share resources from the res folder.
To share from assets folder I can only recommend the cwac-provider library (StreamProvider).
Among avoiding to develop your own content provider, it adds some support for capricious legacy apps (check USE_LEGACY_CURSOR_WRAPPER).

Categories

Resources