0

I have the following code that correctly attaches the image to the email and sends:

Intent sharingIntent = new Intent(Intent.ACTION_SEND);

sharingIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
// Set tht type to image/* and add the extra text field for the message to send
sharingIntent.setType(Application2.instance().getResString(R.string.share_intent_type_text_image));
sharingIntent.putExtra(Intent.EXTRA_TEXT, String.format(Application2.instance().getResString(R.string.share_intent_body_question), question.question));

if (destFile != null)
{
    Uri uri = Uri.fromFile(destFile);
    sharingIntent.putExtra(Intent.EXTRA_STREAM, uri);

    ((ActivityMain) getActivity()).startActivity(Intent.createChooser(sharingIntent, "Share via"));
}

R.string.share_intent_type_text_image is defined as "image/png"

destFile is an image grabbed from the external cache directory of the app, (((ActivityMain) getActivity()).getExternalCacheDir()

However, when I attempt to open the file in Gmail, a dialog appears that says: Info - No app can open this attachment for viewing. I've downloaded the file via my PC and the extension comes up as .File. I can open it with paint and other image viewers.

Anyone experience this before?

  • What is the value of `destFile`? – CommonsWare Aug 27 '14 at 19:39
  • I suggest you take a look at https://developer.android.com/reference/android/support/v4/content/FileProvider.html – Simon Aug 27 '14 at 19:47
  • @Simon I'll test it out and post the results. Looks like this is exactly what I need! Just a note, the images work fine for many other apps. Also, added a description of `destFile`. – Arian Sardari Aug 28 '14 at 20:38

2 Answers2

1

Considering the FileProvider problems, and also because I wanted to implement a max cache size for collected temp files, I went with a ContentProvider solution and it works a treat. Basically, you're allowed to use your internal cache without any problem but still provide third party apps with a URI they can use to reference your temporary files you want to share with them. Because you use your internal cache, there will be no unnecessary WRITE_EXTERNAL_STORAGE permission to ask for.

The added max cache size limit (that you can remove from the class by simply deleting everything from checkSize() to the end of the class, for instance, if you can make sure you delete all files directly after sharing, so they won't remain on the device) works by checking the cumulated max size upon each call and clearing up half the cache (deleting the oldest files) if necessary.

public class TemporaryFile extends ContentProvider {
  private static final long MAX_SIZE = 512 * 1024;
  // commented out on purpose so that you don't forget to rewrite it...
  // public static final String AUTHORITY = "com.example.tempfile";

  private UriMatcher uriMatcher;

  @Override
  public boolean onCreate() {
    uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
    uriMatcher.addURI(AUTHORITY, "*", 1);
    return true;
  }

  @Override
  public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
    if (uriMatcher.match(uri) == 1) {
      final String file = getContext().getCacheDir() + File.separator + uri.getLastPathSegment();
      return ParcelFileDescriptor.open(new File(file), ParcelFileDescriptor.MODE_READ_ONLY);
    }
    else
      throw new FileNotFoundException(uri.toString());
  }

  @Override
  public int update (Uri uri, ContentValues values, String selection, String[] selectionArgs) {
    return 0;
  }

  @Override
  public int delete (Uri uri, String selection, String[] selectionArgs) {
    return 0;
  }

  @Override
  public Uri insert(Uri uri, ContentValues values) {
    return null;
  }

  @Override
  public String getType(Uri uri) {
    return null;
  }

  @Override
  public Cursor query (Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
    return null;
  }

  public static File getFile(Context context, String prefix, String extension) throws IOException {
    checkSize(context);
    File file = File.createTempFile(prefix, extension, context.getCacheDir());
    file.setReadable(true);
    file.deleteOnExit();
    return file;
  }

  public static Uri getPublicUri(File file) {
    return Uri.withAppendedPath(Uri.parse("content://" + AUTHORITY), file.getName());
  }

  public static void checkSize(Context context) throws IOException {
    File dir = context.getCacheDir();
    if (getDirSize(dir) > MAX_SIZE)
      cleanDir(dir, MAX_SIZE / 2);
  }

  private static long getDirSize(File dir) {
    long size = 0;
    for (File file : dir.listFiles())
      if (file.isFile())
        size += file.length();
    return size;
  }

  private static void cleanDir(File dir, long atLeast) {
    long deleted = 0;

    File[] files = dir.listFiles();
    Arrays.sort(files, new Comparator<File>() {
      public int compare(File f1, File f2) {
        return Long.valueOf(f1.lastModified()).compareTo(f2.lastModified());
      }
    });

    for (File file : files) {
      deleted += file.length();
      file.delete();
      if (deleted >= atLeast)
        break;
    }
  }
}

Using it couldn't be simpler, just call

File file = TemporaryFile.getFile(this, "prefix", ".extension");

whenever you want to create a new file and

TemporaryFile.getPublicUri(file)

whenever you want to get a public Uri to the file, eg. to pass it to an intent as data or Intent.EXTRA_STREAM.

Being a provider, don't forget to add the necessary manifest entry, either:

<provider
    android:name=".TemporaryFile"
    android:authorities="com.example.tempfile"
    android:exported="true"
    tools:ignore="ExportedContentProvider" >
</provider>
Gábor
  • 9,466
  • 3
  • 65
  • 79
0

This works but requires external storage and the relating permissions. When downloading an app, a dialog will show that the app is requesting to be able to read/write data which may turn users away. Use the FileProvider as Simon suggested in my initial post if that's a concern.

Useful links:
https://developer.android.com/reference/android/support/v4/content/FileProvider.html

I attempted to use the File Provider as Simon suggested in my initial post to no avail. I received a NullPointerException on the following line:

final ProviderInfo info = context.getPackageManager()
            .resolveContentProvider(authority, PackageManager.GET_META_DATA);

I was unable to track the problem after following the guide at: https://developer.android.com/reference/android/support/v4/content/FileProvider.html
as well as the other thread at:
How to use support FileProvider for sharing content to other apps?

At this point I realized there is no file type set for the images being used. I simply added .png to the files and the attachments work correctly in Gmail as well as the previous apps that already worked.

I provided the following code if anyone was curious how I shared an internal file. It's not complete and does not handle errors completely but it may be useful for someone as a start.

// Copy image file to external memory and send with the intent
File srcFile = getImage();
File destDir = new File(((ActivityMain) getActivity()).getExternalCacheDir(),
        Application2.instance().getResString(R.string.temporary_external_image_path));
if(!destDir.exists())
{
    destDir.mkdirs();
}

if(destDir != null && srcFile != null)
{
    File destFile = new File(destDir, srcFile.getName());

    if (!destFile.exists())
    {
        try
        {
            Application2.instance().copy(srcFile, destFile);
        }
        catch (IOException e)
        {
            if (BuildConfig.DEBUG) Log.e("Failed to copy file '" + srcFile.getName() + "'");
        }
    }

    if (destFile != null)
    {
        Uri uri = Uri.fromFile(destFile);
        sharingIntent.putExtra(Intent.EXTRA_STREAM, uri);

        ((ActivityMain) getActivity()).startActivity(Intent.createChooser(sharingIntent, "Share via"));
    }
}
Community
  • 1
  • 1