Android: Attaching files from internal cache to Gmail

Warning: the method described in the post works well for Gmail, but apparently has some issues with other ACTION_SEND handlers (e.g. the MMS composer). A workaround is described in the comments.

I’ve just spent the day modifying one of my Android applications so that it no longer requires the use of the SD card; making it use the internal cache for what little storage is required. Everything went pretty smoothly, until I got to a part of the app that tries to share some data by sending an email with an attachment via Gmail (using an ACTION_SEND intent). Then things started behaving very oddly.

What I saw was that the Compose activity would launch correctly and my file would be shown as attached (as an example, I’ve attached a text file called Test.txt here); however when I sent the message, the attachment was not on the received email:

Or even shown in my Gmail sent folder.

A quick browse through the log showed the reason:
02-28 21:01:28.434: E/Gmail(19673): file:// attachment paths must point to file:///mnt/sdcard. Ignoring attachment file:///data/data/com.stephendnicholas.gmailattach/cache/Test.txt

So, it looks like this is something Gmail has explicitly ruled out; though I’m not sure why. At this point I could easily get all moany and ranty, but today was a glass half full day and so it was time for a workaround.

After a few false starts and a lot of searching, what I actually ended up doing was using a ContentProvider to provide access to the file from my application’s internal cache; as apparently Gmail can happily resolve attachments this way.

Unfortunately there wasn’t much example code to help me on my way, which is why I’ve decided to include a simple example here. There’s four main parts to this:

  1. The ContentProvider that provides access to the files from the application’s internal cache. Complete code, with comments, is shown below:
package com.stephendnicholas.gmailattach;


import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.os.ParcelFileDescriptor;
import android.util.Log;

public class CachedFileProvider extends ContentProvider {

	private static final String CLASS_NAME = "CachedFileProvider";

	// The authority is the symbolic name for the provider class
	public static final String AUTHORITY = "com.stephendnicholas.gmailattach.provider";

	// UriMatcher used to match against incoming requests
	private UriMatcher uriMatcher;

	public boolean onCreate() {
		uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);

		// Add a URI to the matcher which will match against the form
		// 'content://com.stephendnicholas.gmailattach.provider/*'
		// and return 1 in the case that the incoming Uri matches this pattern
		uriMatcher.addURI(AUTHORITY, "*", 1);

		return true;

	public ParcelFileDescriptor openFile(Uri uri, String mode)
			throws FileNotFoundException {

		String LOG_TAG = CLASS_NAME + " - openFile";

				"Called with uri: '" + uri + "'." + uri.getLastPathSegment());

		// Check incoming Uri against the matcher
		switch (uriMatcher.match(uri)) {

		// If it returns 1 - then it matches the Uri defined in onCreate
		case 1:

			// The desired file name is specified by the last segment of the
			// path
			// E.g.
			// 'content://com.stephendnicholas.gmailattach.provider/Test.txt'
			// Take this and build the path to the file
			String fileLocation = getContext().getCacheDir() + File.separator
					+ uri.getLastPathSegment();

			// Create & return a ParcelFileDescriptor pointing to the file
			// Note: I don't care what mode they ask for - they're only getting
			// read only
			ParcelFileDescriptor pfd = File(
					fileLocation), ParcelFileDescriptor.MODE_READ_ONLY);
			return pfd;

			// Otherwise unrecognised Uri
			Log.v(LOG_TAG, "Unsupported uri: '" + uri + "'.");
			throw new FileNotFoundException("Unsupported uri: "
					+ uri.toString());

	// //////////////////////////////////////////////////////////////
	// Not supported / used / required for this example
	// //////////////////////////////////////////////////////////////

	public int update(Uri uri, ContentValues contentvalues, String s,
			String[] as) {
		return 0;

	public int delete(Uri uri, String s, String[] as) {
		return 0;

	public Uri insert(Uri uri, ContentValues contentvalues) {
		return null;

	public String getType(Uri uri) {
		return null;

	public Cursor query(Uri uri, String[] projection, String s, String[] as1,
			String s1) {
		return null;

As you can see, all you really need to do is to overwrite the openFile(...) method. Although you can also override the query(...) method to provide more information to the application that calls you (it would make this example unnecessarily complicated, but I’m happy to provide code on request).

To make this provider available to use, you need to add a line to your AndroidManifest.xml defining the class and the authority (symbolic name) used to reference it:

<provider android:name="CachedFileProvider" android:authorities="com.stephendnicholas.gmailattach.provider"></provider>

Note: this needs to go inside the <application>...</application> definition in your AndroidManifest.xml.

  1. The utility method that creates a file in the internal cache:
public static void createCachedFile(Context context, String fileName,
			String content) throws IOException {

	File cacheFile = new File(context.getCacheDir() + File.separator
				+ fileName);

	FileOutputStream fos = new FileOutputStream(cacheFile);
	OutputStreamWriter osw = new OutputStreamWriter(fos, "UTF8");
	PrintWriter pw = new PrintWriter(osw);


  1. The utility method that creates the intent to send the content via Gmail (explicitly using Gmail, rather than using a chooser):
public static Intent getSendEmailIntent(Context context, String email,
			String subject, String body, String fileName) {

	final Intent emailIntent = new Intent(

	//Explicitly only use Gmail to send


	//Add the recipients
				new String[] { email });

	emailIntent.putExtra(android.content.Intent.EXTRA_SUBJECT, subject);

	emailIntent.putExtra(android.content.Intent.EXTRA_TEXT, body);

	//Add the attachment by specifying a reference to our custom ContentProvider
	//and the specific file of interest
				Uri.parse("content://" + CachedFileProvider.AUTHORITY + "/"
						+ fileName));

	return emailIntent;
  1. The code that calls 2 & 3 to do something on a button press. Triggered in my dummy app by a button click:
Button button = (Button) findViewById(;
button.setOnClickListener(new OnClickListener() {

	public void onClick(View v) {
		try {
							"Test.txt", "This is a test");

							"<YOUR_EMAIL_HERE>@<YOUR_DOMAIN>.com", "Test",
							"See attached", "Test.txt"));
		} catch (IOException e) {
		} catch (ActivityNotFoundException e) {
				"Gmail is not available on this device.",

Hopefully that’s all pretty straight forward, however feel free to ask any questions in the comments and I’ll help where I can.

The full source is now available as a complete (albeit very simple) Android app on github: Gmail Attacher.

  • Ricardo

    Hello, i need to do the same but with a photo recently taken, but i don’t know how to addapt your part :( can you help me?? please!! here is the code

    Code Removed

  • Martin

    Thank you very much!
    Your solution also works for Google Docs.

    Why Gmail limits attachments to the external memory is quite strange since it does not even validate relative URLs (file:///mnt/sdcard/../../data/xyz also works).


  • Whatzit

    Thanks a lot!!!

    I ran into the same issue and this is the example that I needed! This tutorial was long overdue by someone and I’m glad I found your page!

    I wish the Android documentation would be better. What’s so hard about saying, hey, use a content provider to share a private file and this is how it’s done…?

    Thanks again!!!

  • Whatzit

    Your code worked like a charm and can also be used when storing private files via FileOutputStream!

    The one thing I’d like to add, as a reminder is that the provider declaration in the manifest must be done within the application (e.g. <application>
    <provider android:name=”MyProviderClassHere” android:authorities=””/provider>/application>).

    Thank you!

  • Shelly

    You should cover deleting the cache files, lest they build up unnecessarily and hog space on the storage.

    • Stephen Nicholas

      Hi Shelly,

      The files can be deleted using the standard Java mechanisms. For example:

      For a single cache file:

      File cacheFile = new File(context.getCacheDir() + File.separator + fileName);
      Log.i("SDN", "Cache file '" + fileName + "' successfully deleted: " + cacheFile.delete());

      For all files in the cache directory:

      File[] cacheFiles = context.getCacheDir().listFiles();

      for (File file : cacheFiles){
      Log.e("SDN", "Cache file '" + file + "' deleted: " + file.delete());

      You could also use a FilenameFilter if the files of interest match a particular pattern.

      Unfortunately there’s not an easy way to tell when the cache file has been shared (startActivityForResult(...) doesn’t work with ACTION_SEND) so there is some question as to when you would do the clean up.

      You could put something in the onResume(...) of your activity and this shouldn’t get called until the share intent returns; however I guess there’s no guarantee that the file has been finished with.

  • rany

    I am a newbie to android. Wanna know from where this Utils word came from in onclick method.

    • Stephen Nicholas

      Hi Rany,

      The Utils is simply the name of my custom class that contains the createCachedFile(...) & getSendEmailIntent(...) methods. I’ve put those methods into a separate class to the Activity that has the onclick, so that they can be easily re-used. As those methods are static I invoke them by using the class name, rather than a reference to an object of that class. Hopefully that makes sense :)

  • tos

    Thanks for this piece of code. I was able to use it successfuly.

    However, in my case this method disables attachments for other apps, such as a regular mail client, a twitter client. The MMS composer even crashes.

    Is your code also sending attachments with these apps ? If so I may have another problem on my end..

    Thank you for reading.

    • Stephen Nicholas

      Hi tos,

      Thanks for that, you’re correct – there does appear to be an issue using this method with some of the ACTION_SEND handlers.

      It seems to be very variable though, e.g. Evernote and Gmail work fine, Twitter doesn’t handle text files (and does so gracefully) and the MMS composer does seem to have serious issues.

      I’ve changed the post so that the example code explicitly only calls the Gmail composer.

      Also, a possible workaround is to use Martin’s approach, simply using relative paths to specify the file.
      E.g. ‘file:///mnt/sdcard/../../data/xyz/file.txt’
      I.e. "file:///mnt/sdcard/../../" + context.getCacheDir() + File.separator + fileName;

  • Marcelo Barros

    Hello Stephen !

    You made my day, great code and pretty good idea.
    I have tried the workaround using relative path but it not worked for me. I do not have a SD card, though.

    One question: what is the license ? Public domain ? BSD ? Beer license ? :D

    Thanks again

    • Stephen Nicholas

      Thanks Marcelo, glad to hear it’s useful. I’ve not really considered the license issue before. Basically I’m happy for anyone to use it to do anything, with no need to pay, attribute, etc. However, at the same time, I don’t want anything unpleasant to come my way. So I think the MIT license is probably the best fit. I’ve added a page to this site explicitly stating that’s the case for any code on here, but let me know if you need it more formal than that.

  • Pingback: How to bundle the data captured from customized dialog(edittext,datepicker,spinner e.t.c) in .csv and attach to email in android? | PHP Developer Resource()

  • ReCo

    Thanks for the interesting article.
    Strange, but I have it worked out well for both Gmail and for Bluetooth:

    Intent shareIntent = new Intent(Intent.ACTION_SEND);

    File shareFile = new File(getCacheDir() + File.separator + zip_filename);
    shareFile.setReadable(true, false);
    shareIntent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(shareFile));

    startActivity(Intent.createChooser(shareIntent, getString(R.string.text_share)));

    • PeteS

      Indeed, simply calling “shareFile.setReadable(true, false);” is all it took for the simple approach to work. Seems the provider method is not needed. Thanks though!

      • Joates

        Same for me. Looks like if the instance of the file object that’s used to create the URI is readable, the email client will be able to access it. Interesting to note that setting setReadable(true, false) on the File object that creates the file in the first place does not seem to affect the permissions on the file system.

    • dan

      Awsome this really works!!!

  • sunu

    i am trying to implement this, but i get a crash saying activity not found are you sure declared in manifest ? i am testing it in simulator, what should i do ?

    in my case i am able to send attachment from my simulator using action_send, but in device it does not work, hence i am looking for a solution

    if my package name is different, where should i change ?


    • Stephen Nicholas

      Hi Sunu,

      I believe the problem is that that Gmail is not present on the emulator; meaning that the exception is legitimate. I have updated the code above so that it handles this better and now displays a Toast, instead of allowing an exception to occur.

      I think you can get Gmail installed on the emulator, but I don’t believe it is an easy process. There’s a few tutorials online, but it takes a bit of work.

      Are you experiencing the same problem on a real device?

  • Dheeraj

    I’d been struggling with this problem all day. Thanks for sharing the code!

  • hemanth

    I have the same problem as sunu.

  • hemanth

    can i get an email with working source code zip?
    my email id is:

  • hemanth

    i get a crash saying activity not found are you sure declared in manifest ?
    I have declared it in manifest this way:

    but still get activity not found exception Please suggest a solution.

    • Stephen Nicholas

      Hi Hemanth,

      As I’ve replied to Sunu above (and assuming that you are using the emulator as well), I believe the problem is that that Gmail is not present on the emulator; meaning that the exception is legitimate (that activity is not present on the device). I have updated the code above so that it handles this better and now displays a Toast, instead of allowing an exception to occur.

      I’ve put the complete source of the demo app up on Github, so you should be able to get hold of it there:

  • Abdur Rahman


    New to android and really need this functionality in my app. Can you provide a complete file so that i can see its proper usage.

    Thanks for the great article.

    • Stephen Nicholas

      Hi Abdur,

      I’ve put the complete source of the demo app up on Github, so you should be able to get hold of it there:

      It’s a rather simple app, but it shows how to use this approach.

      • Rajesh

        Hi Stephen,
        I am trying your code it’s working fine for me, But I am facing problem with 3.1 . to Gmail file not attaching. Please give me any solution for this problem.
        Thanks .

  • amy

    from what part of the code is the app taking the file?and from where? i dont gt where is the test file stored and how does my app know where it is

    • Stephen Nicholas

      Hi Amy,

      The app is storing and retrieving the file from the application’s internal cache.

      In my demo app, the file is created by the createCachedFile(...) function:
      File cacheFile = new File(context.getCacheDir() + File.separator
                      + fileName);

      The call to context.getCacheDir() returns the path to the cache folder for the application to use.

      The CachedFileProvider does the same context.getCacheDir() call (line 56) to find out where to read the files from.

      Hopefully that makes sense :)

  • Blundell

    What version of Android did you test this on? Doesn’t seem to be working with my N1 android 2.3.3

    • Blundell

      Ah right if you use the folder: getFilesDir() it won’t work, but does work with getCacheDir()

      • Stephen Nicholas

        Hi Blundell,

        Yes, that’s correct. In this case I’ve only focused on getCacheDir().

        If you wanted, you could easily write another ContentProvider for getFilesDir() (you’d just need to change line 56 in CachedFileProvider). Or you could modify the existing one to either use a slightly different URI structure, which specified which to fetch the file from. Or do a simple preference order (i.e. check cache first, if not found, then look in files, if not found, then error).

  • Faison

    Superb… :)

  • Heath

    I was able to share images with all apps using the MediaStore as follows:

    // Step 1: Save the image
    FileOutputStream fos = openFileOutput(filename + “.jpg”, MODE_WORLD_READABLE);

    // Step 2: Get the path as a string
    File jpg = getFileStreamPath(filename + “.jpg”);

    // Step 3: Add the image to the MediaStore
    String path = MediaStore.Images.Media.insertImage(getApplicationContext().getContentResolver(), jpg.getAbsolutePath(), jpg.getName(), filename);

    // Step 4: Share using the context:// URI from the MediaStore
    share.putExtra(Intent.EXTRA_STREAM, Uri.parse(path));

    While it worked, I did have a number of complaints with this method. There was a conflict with Dropbox. I have Dropbox setup to upload every picture I take with my phone’s camera. Well it appears that Dropbox implemented this functionality by uploading every image added to the MediaStore. So that means if I share an image from my app, it also goes to Dropbox. Not exactly a great user experience. Also, I couldn’t figure out how to add .png files to the MediaStore. Lastly, the MediaStore renames your file, so the attachment comes out as 12914014980.jpg.

    Martin’s method worked for me on everything except Google+

  • Danielle

    Is it possible sending Bitmaps using this method?

  • markus

    Thanks for this, I’ve been looking all over for an explanation to this idiotic dilemma. Cheers!

  • John

    Can you show the code to attach more than one file to the mail?
    I believe ACTION_SEND_MULTIPLE should be used and also putParcelableArrayListExtra,

  • Pingback: Default mail client - Error attached file - How-To Video()

  • Lucas Daniel

    obrigado Nicolas, muito bom seu post!! ;)

  • Roberto

    Heya Nicholas,

    Would you mind sharing the solution to build the query(…) cursor necessary to provide metadata about the item?

  • Mariusz

    Perfect. Thank you.

  • k

    Gosh, thanks – appreciate you posting this.

  • Harry

    Thanks Stephen for providing this tutorial. Actually it pretty cumbersome to send attachments using mail… Any idea what is missing for me:

    Implemented a similar content provider, stored files to the cached dir, declared the provider for my app, built an ACTION_SENT_MULTIPLE intent (that’s a difference) and starting it through a chooser presenting the standard email and the Gmail client. I’m using a Nexus 4 Android 4.2.2.

    Problem: when selecting the standard email client, the mail appears – but without the (single) attachment added. Adding some logs shows the mail client is contacting my content provider calling query () twice, then getType () once, but never calls openFile ()

    I had a permission missing message in LogCat first, but have been able to get rid of it adding android:exported=”true” to the provider declaration.

    Any idea what is missing? LogCat shows no other messages then the query / query / getType sequence, the attachment is simply missing.

    I didn’t test it with Gmail so far as this beast is syncing my account forever after having it setup.

    The whole code kind of worked not using a content provider but instead adding the Uris using the file scheme. I ran into the usual problem however the attachments were shown in the composer but not send as the mail app is not allowed to access files in my app. SD card is not an option as the Nexus has none…

    Thanks for any hint.


  • Georgi Pachov

    Thank you for this tutorial! Though I will not use it or any of the code, it helped me gain a great insight!

  • Paul Dingemans

    @Stephen: thank for your tutorial which was a very good starting point.

    Like many others I had problems with the default email app of android. But I have found why the native email app doesn’t work with the provider and have a solution as well. Now the content provider works well both with gmail and the native android app.

    The source for the native email app can be found at File src\com\android\email\activity\ contains procedure loadAttachmentInfo(Uri uri) which calls the query method of the content provider to determine the size of the file. As this method, and method getType, are not implemented the native email app can not include attachment specified with a content-uri.

    Change following:

    public String getType(Uri uri) {
    switch (uriMatcher.match(uri)) {

    // If it returns 1 - then it matches the Uri defined in onCreate
    case 1:
    return "text/plain"; // Use an appropriate mime type here
    return null;

    public Cursor query(Uri uri, String[] arg1, String arg2, String[] arg3,
    String arg4) {
    switch (uriMatcher.match(uri)) {

    // If it returns 1 - then it matches the Uri defined in onCreate
    case 1:
    MatrixCursor cursor = null;

    File file = new File( getContext().getCacheDir() + File.separator
    + uri.getLastPathSegment());
    if (file.exists()) {
    cursor = new MatrixCursor(new String[] {
    OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE });
    cursor.addRow(new Object[] { uri.getLastPathSegment(),
    file.length() });

    return cursor;
    return null;

    • Roberto

      Thanks Paul. That worked perfectly for me here.

    • Jeff

      Paul…THANKS! I had tracked it far enough to know that Mail was calling query but I wasn’t able to figure out what to return. Your code works perfectly.

  • Eric Smith

    I found the need for setting the exported attribute to true in the manifest the hard way, not having noticed that Harry’s reply above mentions it. It might be a good idea to edit the notes about the manifest in the article to show that.

  • Zach

    Hi Stephen Nicholas

    was wondering if you could go through how to create a cashed PDF from extiting PDF File within the device SDCard?
    many thanks

  • Mahesh

    Hi Stephen,

    Your Code has some problem. It is Showing as Attachment. But it is not going to the Sender. Once check this.

    And my requirement is in my Application i am having 4 PDF files. But i will select only two. I need to do the selected items as a attachment in GMAIL by a button Click. Can you provide me any Source Code for that.

    Thanks in Advance


  • Ross

    Very easy to understand and follow. Thanks

  • dan

    Hi, I am trying to use this for sharing a video file to facebook and whatsapp without success.
    It is just not attaching the attachment…

  • Yaniv Blumenfeld

    thanks a lot, I have been searching for exactly this.