22

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?

zmarties
  • 4,809
  • 22
  • 39
Buda Gavril
  • 21,409
  • 40
  • 127
  • 196

7 Answers7

17

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;
    }
smith324
  • 13,020
  • 9
  • 37
  • 58
15

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

intrepidis
  • 2,870
  • 1
  • 34
  • 36
Augusto Icaro
  • 553
  • 5
  • 15
  • can someone please help me i am getting images from assets---->folder---->image1.png and i am failed to get images from assets subfolder pls suggest me code editing of this line String file_name = uri.getLastPathSegment( ); – user3233280 Jun 06 '14 at 18:10
  • 2
    Sorry for my late. How you send the file to intent? I think the code is alright. You need call image by addres like this: file:///android_asset/folder/image1.png If you need share this, you need to use this code: String file = "folder/image1.png"; Uri theUri = Uri.parse( "content://com.example.yourproject/" + file ); Intent theIntent = new Intent( Intent.ACTION_SEND ); theIntent.setType( "image/*" ); theIntent.putExtra( Intent.EXTRA_STREAM, theUri ); startActivity( Intent.createChooser( theIntent, "Send to:" ); – Augusto Icaro Aug 04 '14 at 16:23
  • 2
    @Kikuto actually, if you have subfolders in your assets, then you should change AssetsProvider openAssetFile method file_name to String file_name = uri.getPath().substring(1, uri.getPath().length()); – Ragaisis Mar 19 '15 at 08:19
13

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);
intrepidis
  • 2,870
  • 1
  • 34
  • 36
4

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;
    }
}
Stanislav Agapov
  • 947
  • 8
  • 15
4

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.

2

AFAIK, there's no way to share an image from the assets folder. But it's possible to share resources from the res folder.

Michael
  • 53,859
  • 22
  • 133
  • 139
  • can you give me a example how to share images from drawable folder from resources? – Buda Gavril May 04 '11 at 19:30
  • 3
    The format is: `android.resource://[package]/[type]/[id]`. So, an URI example is: `"android.resource://com.your.app/drawable/" + Integer.toStrng(R.drawable.some_resource)`. – Michael May 04 '11 at 19:37
  • I've tried using share.putExtra(Intent.EXTRA_STREAM, Uri.parse("android.resource://com.packake.myapp/drawable/" + Integer.toString(R.drawable.myimage))); it works sharing it with gmail but when trying to share it with yahoo mail I get the message "Read access denied. Resource cannot be read". do you have any ideea why? – Buda Gavril May 04 '11 at 19:49
  • I think it depends on how an app handles this kind of URIs. – Michael May 04 '11 at 19:56
  • @Pixie It is possible, please see my answer. – smith324 Aug 24 '11 at 14:25
  • @smith324 Writing your own `ContentProvider` must be a working solution but is sharing an image worth doing it? I think it's much easier to put this image to `res/drawable-nodpi` folder. – Michael Aug 24 '11 at 15:13
  • @Pixie Depends on the application, you may need to share from the assests with more than just an image – smith324 Aug 24 '11 at 15:18
  • 1
    @smith324 Maybe. But as for me it's not a practical problem. However it doesn't make your solution bad. +1 – Michael Aug 24 '11 at 16:20
1

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).

Jérémy Reynaud
  • 3,020
  • 1
  • 24
  • 36