Search code examples
androidpermissionsscreenshot

How can I take and share a screenshot without using the external storage?


I would like to programatically take a screenshot and share it using a ShareActionProvider without asking for the "android.permission.WRITE_EXTERNAL_STORAGE" permission. I'm trying to do this because I want to avoid asking for permissions if it's not absolutely necessary and to be able to deal with devices without external storage.

I managed to successfully send the screenshot to some apps, but there is at least one (Gmail) that won't attach my screenshot. Here's my code:

...
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
    ...
    MenuItem shareMenuItem = menu.findItem(R.id.action_share);
    ShareActionProvider shareActionProvider = (ShareActionProvider) MenuItemCompat.getActionProvider(shareMenuItem);
    shareActionProvider.setShareIntent(createShareIntent());
    shareActionProvider.setOnShareTargetSelectedListener(new ShareActionProvider.OnShareTargetSelectedListener()
    {
        @Override
        public boolean onShareTargetSelected(ShareActionProvider source, Intent intent)
        {
            saveScreenshot();
            // The return result is ignored. Return false for consistency.
            return false;
        }
    });
}

private Intent createShareIntent() {
    // Get the path of the screenshot inside the directory holding application files.
    File screenshotFile = new File(getActivity().getApplicationContext().getFilesDir(), SCREENSHOT_NAME);

    Intent shareIntent = new Intent(Intent.ACTION_SEND);
    shareIntent.setType("image/jpeg");
    shareIntent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(screenshotFile));
    return shareIntent;
}

private void saveScreenshot() {
    view.setDrawingCacheEnabled(true);
    Bitmap bitmap = Bitmap.createBitmap(view.getDrawingCache());
    view.setDrawingCacheEnabled(false);

    // Save on Internal Storage to avoid asking for WRITE_EXTERNAL_STORAGE permission.
    FileOutputStream outputStream = null;
    try {
        // Open a file associated with this Context's application package for writing.
        // Make file readable by other apps otherwise it can't be shared.
        outputStream = getActivity().getApplicationContext()
                .openFileOutput(SCREENSHOT_NAME, Context.MODE_WORLD_READABLE);
        bitmap.compress(Bitmap.CompressFormat.JPEG, COMPRESS_QUALITY, outputStream);
        outputStream.close();
    } catch (IOException e) {
        Log.e(LOG_TAG, "Can't save screenshot!", e);
    } finally {
        if (outputStream != null) {
            try {
                outputStream.close();
            } catch (IOException e) {
                Log.e(LOG_TAG, "Can't close the output stream!", e);
            }
        }
    }
}
...

This code works if I want to share with Twitter, Hangouts, Keep, Evernote, Inbox from Google and other apps I tested, but it doesn't work with Gmail. For example on a Nexus 6, Gmail gives me a Toast message saying "Permission denied" and on a Nexus 4 it just doesn't attach the screenshot without any error message. There is nothing relevant in the logs. Using something like

screenshotFile.setReadable(true, false);

or

try {
    Runtime.getRuntime().exec("chmod 777 " + screenshotFile.getAbsolutePath());
} catch (IOException e) {
    Log.e(LOG_TAG, "Can't give permissions to screenshot file!", e);
}

does not make a difference.

If I save my screenshot on the external storage using Environment.getExternalStorageDirectory() and ask for the "android.permission.WRITE_EXTERNAL_STORAGE" permission all apps I tested work fine.

How can I do this without using the external storage and have all apps (including Gmail) working? Is it possible that this is a problem with the Gmail app? Or would you advise me to just ask for the permission to write on the external storage and accept the fact that some of my users might complain and some devices don't have a SD card?

Thanks!


Solution

  • How can I do this without using the external storage and have all apps (including Gmail) working?

    You can try FileProvider, possibly in conjunction with my LegacyCompatCursorWrapper.

    Is it possible that this is a problem with the Gmail app?

    I would consider it to be a feature, not a bug. MODE_WORLD_READABLE has been deprecated for some time, with good reason. As it stands, your code is making your screenshot available to all apps, which is poor from a security standpoint. Proper use of a ContentProvider can limit access to one app (not all), for one request (this particular ACTION_SEND operation), for a limited amount of time. Perhaps the Gmail developers are just trying to give others a nudge in the right direction.

    some devices don't have a SD card?

    External storage is not removable storage on the vast majority of Android devices, including pretty much all of them that shipped with Android 3.0 or higher. If you are aware of a device model that shipped with something newer, for which external storage is a removable card, please let me know.