Home > Back-end >  Share intent only works with some apps
Share intent only works with some apps

Time:10-11

I am trying to create a share button that will share an Image with some extra caption text to social media. The code I'm using now works fine with some apps, but doesn't work in other apps. Here's the code:

  String message = "hello world";

            Uri uri = Uri.parse("android.resource://"   getActivity().getPackageName()   "/"   R.raw.loremipsum2);
            Intent intent = new Intent();
            intent.setAction(Intent.ACTION_SEND);
            intent.putExtra(Intent.EXTRA_TEXT, message);
            intent.putExtra(Intent.EXTRA_STREAM, uri);
            intent.setType("image/jpeg");
            intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
            startActivity(intent);

I've been searching for hours what the problem may be, but none of the solutions have worked for me so far.

Apps where it does work:

-Telegram

-Drive

-Reddit

Apps where it doesn't work:

-WhatsApp

-Gmail

Error when sharing to Gmail:

Gmail:Can't find android resource
    java.io.FileNotFoundException: No package found for authority: android.resource://com.test.testApp/2131820547

Error when sharing to whatsapp

Access denied finding property "ro.vendor.scroll.preobtain.enable"

I'm going crazy onto this one, I need it to work and can obviously be achieved, as many apps can share images to whatsapp. Any ideas on how to solve it? you're welcome! :)

CodePudding user response:

EXTRA_STREAM is documented to take a content: Uri. That is not what you are using. Few developers know of the android.resource scheme. Fewer still will have code that handles it when an app uses one unexpectedly in EXTRA_STREAM.

For better compatibility, use a ContentProvider and a content: Uri that points to it. For example, you could share a file with FileProvider.

CodePudding user response:

Okay, so finally, after researching a lot, trust me, a lot, I've finally found an answer to my question, so I'm posting it here as it seems like a pretty basic feature to have in an app.

The solution is, as CommonsWare said, using a fileProvider. I also used a class I found on github to manage cache usage , it is linked here, as FileProvider may not use resource URIs

The idea is, store the image in cache, then call Cache class to provide a valid URI to the image in cache, and then use that URI for the EXTRA_STREAM

Follow this guide to solve the issue: (it works in all apps I've tried so far. It should also work for other File types with some little modifications.)

SOLUTION

1-Create the class that handles cache. Here's code:

public class Cache {

    public static final String TAG = Cache.class.getSimpleName();

    private static final String CHILD_DIR = "images";
    private static final String TEMP_FILE_NAME = "img";
    private static final String FILE_EXTENSION = ".png";
    private Context context;

    private static final int COMPRESS_QUALITY = 100;


    public Cache(Context context){
        this.context = context;
    }

    /**
     * Save image to the App cache
     * @param bitmap to save to the cache
     * @param name file name in the cache.
     * If name is null file will be named by default {@link #TEMP_FILE_NAME}
     * @return file dir when file was saved
     */
    public File saveImgToCache(Bitmap bitmap, @Nullable String name) {
        File cachePath = null;
        String fileName = TEMP_FILE_NAME;
        if (!TextUtils.isEmpty(name)) {
            fileName = name;
        }
        try {
            cachePath = new File(context.getCacheDir(), CHILD_DIR);
            cachePath.mkdirs();

            FileOutputStream stream = new FileOutputStream(cachePath   "/"   fileName   FILE_EXTENSION);
            bitmap.compress(Bitmap.CompressFormat.PNG, COMPRESS_QUALITY, stream);
            stream.close();
        } catch (IOException e) {
            Log.e(TAG, "saveImgToCache error: "   bitmap, e);
        }
        return cachePath;
    }

    /**
     * Save an image to the App cache dir and return it {@link Uri}
     * @param bitmap to save to the cache
     */
    public Uri saveToCacheAndGetUri(Bitmap bitmap) {
        return saveToCacheAndGetUri(bitmap, null);
    }

    /**
     * Save an image to the App cache dir and return it {@link Uri}
     * @param bitmap to save to the cache
     * @param name file name in the cache.
     * If name is null file will be named by default {@link #TEMP_FILE_NAME}
     */
    public Uri saveToCacheAndGetUri(Bitmap bitmap, @Nullable String name) {
        File file = saveImgToCache(bitmap, name);
        return getImageUri(file, name);
    }

    /**
     * Get a file {@link Uri}
     * @param name of the file
     * @return file Uri in the App cache or null if file wasn't found
     */
    @Nullable public Uri getUriByFileName(String name) {
        Context context = this.context;
        String fileName;
        if (!TextUtils.isEmpty(name)) {
            fileName = name;
        } else {
            return null;
        }

        File imagePath = new File(context.getCacheDir(), CHILD_DIR);
        File newFile = new File(imagePath, fileName   FILE_EXTENSION);
        return FileProvider.getUriForFile(context, context.getPackageName()   ".provider", newFile);
    }

    // Get an image Uri by name without extension from a file dir
    private Uri getImageUri(File fileDir, @Nullable String name) {
        Context context = this.context;
        String fileName = TEMP_FILE_NAME;
        if (!TextUtils.isEmpty(name)) {
            fileName = name;
        }
        File newFile = new File(fileDir, fileName   FILE_EXTENSION);
        return FileProvider.getUriForFile(context, context.getPackageName()   ".provider", newFile);
    }

    /**
     * Get Uri type by {@link Uri}
     */
    public String getContentType(Uri uri) {
        return this.context.getContentResolver().getType(uri);
    }

}

I've done little to none modification to this class, the author is Ivan V on 12.05.2019. (Github)

This class uses a FileProvider internally. FileProvider will allow other apps to access our app files. To configure file providers, we have to do some modification in AndroidManifest.xml

2 - Inside the <application> tag, place the following FileProvider config:

<provider
                android:name="androidx.core.content.FileProvider"
                android:authorities="${applicationId}.provider"
                android:grantUriPermissions="true"
                android:exported="false"
                tools:replace="android:authorities">
            <meta-data
                    android:name="android.support.FILE_PROVIDER_PATHS"
                    android:resource="@xml/file_paths" />
        </provider>

And create file_paths.xml in /res/xml (if the xml folder doesn't exist, create it to) with the following code:

<?xml version="1.0" encoding="utf-8"?>
<paths>
    <files-path name="file_path" path="."/>
    <external-files-path name="external_path" path="/" />
</paths> 

3 - Create the sharing method in your activity

public void shareImage(){
Cache cache = new Cache(getActivity());
                cache.saveImgToCache(BitmapFactory.decodeResource(getResources(),R.raw.loremipsum),"testImg");
                Uri sendUri =  cache.getUriByFileName("testImg");

                Intent intent = new Intent();
                intent.setAction(Intent.ACTION_SEND);
                intent.putExtra(Intent.EXTRA_TEXT, "whatsAppMessage");
                intent.putExtra(Intent.EXTRA_STREAM, sendUri);
                intent.setType("image/*");
                intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
                startActivity(intent);
}

Just change all image names etc and you are ready to go!! I hope i was helpful :)

  • Related