83

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?

Piotr
  • 1,597
  • 3
  • 18
  • 25

5 Answers5

292

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

Community
  • 1
  • 1
Suragch
  • 484,302
  • 314
  • 1,365
  • 1,393
  • 1
    Great solution! Any reason why you are using _getCacheDir()_ instead of _getFilesDir()_, or even _getExternalCacheDir()_? – TechAurelian Aug 22 '16 at 17:01
  • Also, this would require an AsyncTask or IntentService? What do you think works best for this case? Do you have (or know of) any full code that wraps up your solution in an AsyncTask or IntentService? – TechAurelian Aug 22 '16 at 17:03
  • 3
    @Jamrelian, since the purpose is just to share the image and not to save it, I selected the cache directory. Things in the cache directory are not long lasting. I imagine, though, that it would work fine to use another directory. – Suragch Aug 23 '16 at 04:11
  • cache memory size is increasing every time once i share the screen shot but as you said it will replace the previous image so it should not increase the cache memory size. Please explain this. – GvSharma Aug 25 '16 at 09:41
  • @GvSharma. I don't know how to explain that. It sounds like a good candidate for a new question. If you get an answer, link back to it here. – Suragch Aug 26 '16 at 01:53
  • 7
    Great answer! *For future readers* - Most apps save a thumbnail cache relative to the image file name. The cache isn't updated if the filename remains same. I solved this by deleting the contents of the directory and setting the filename to the current date in milliseconds. – Gurupad Mamadapur Apr 12 '17 at 17:00
  • 1
    @GurupadMamadapur You are right. You need delete the cache and save with other file name... – Hoang Nguyen Aug 08 '17 at 07:37
  • Is there any risk of using cacheDir instead of externalFilesDir? For example, if I'm saving an image, am I at a higher risk of running out of space when saving to the cache? – JHowzer Feb 01 '19 at 00:46
  • @JHowzer, The system automatically deletes data in the cache directory when it needs to (though you should do it yourself if you don't need the file anymore). Read [this](https://developer.android.com/training/data-storage/files) for more information. – Suragch Feb 01 '19 at 15:30
  • What is minimalSDK requirement for this code? (as I know 21) What is alternate if minimalSDK requirement is 15 (4.0.3)? – vugar_saleh Feb 16 '19 at 17:55
  • @vugar_saleh, I don't know what the minimum is but I think I have used it with API 14, the minimum for the current support library. Which part is telling you the min is 21? Are you using the support library? – Suragch Feb 16 '19 at 23:42
  • Anybody knows how to create the filpaths.xml file in Xamarin? – Daniel Reyhanian Apr 24 '19 at 15:13
  • @UsmanRana, I haven't done much of that but see [this](https://developer.android.com/training/sharing/receive) and [this](https://developer.android.com/training/secure-file-sharing) – Suragch Jul 09 '19 at 15:02
  • The Uri shared by i.e. whatsapp is of private directory and file is unable to be retrieved – Usman Rana Jul 10 '19 at 05:37
11

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.

CommonsWare
  • 986,068
  • 189
  • 2,389
  • 2,491
  • Couldn't one just use `getCacheDir()` as is suggested in [this SO answer](http://stackoverflow.com/a/16973223/3681880)? – Suragch May 11 '15 at 04:33
  • 1
    @Suragch: Creating world-readable files on your app's internal storage is not a good idea. – CommonsWare May 11 '15 at 10:56
  • 1
    That makes sense. After making that comment I read [another one of your answers](http://stackoverflow.com/a/19096855/3681880) where you recommend using [FileProvider](http://developer.android.com/reference/android/support/v4/content/FileProvider.html) so that is what I am working toward now. – Suragch May 11 '15 at 14:07
  • cache memory size is increasing every time once i share the screen shot but as from the other answer, it will replace the previous image so it should not increase the cache memory size. Please explain this. – GvSharma Aug 26 '16 at 07:42
8

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;
Fajar Khan
  • 952
  • 1
  • 12
  • 30
  • 2
    `DrawingCache` is deprecated from API 28+. Use [Canvas](https://developer.android.com/guide/topics/graphics/drawables) or [PixelCopy](https://developer.android.com/reference/android/view/PixelCopy) to accomplish this. [Example for Canvas usage](https://stackoverflow.com/a/30360394/7551190). And, do not forget to set the background colour (either to your `view` or `Bitmap`), else you will get a black background – Kathir Dec 06 '18 at 07:28
3

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();}

        }
    });
1

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"))
    } 
}
Zakir Shikhli
  • 397
  • 4
  • 14
  • 1
    Zakir, please explain your code to help other people to use it. Btw, it's good to say that Kotlin and Java codes can be used in the same Android Studio project. Some people might not consider your solution because they don't know that. Thanks! – Will Dec 19 '19 at 23:59
  • Hi zakir can you please provide java code. I need to capture scrollview data. Successfully capturing image and able to store in /storage but while sharing not getting file path. Will you please help me out – Tara Apr 14 '20 at 15:12