Search code examples
androidandroid-intentandroid-contentprovider

Attaching file to email using ContentProvider isn't working


I want to attach a file I generate on runtime.

Runtime flow:

  1. Create intent
  2. Create a file under cacheDir
  3. Add file as content:// extra to the intent
  4. Start chooser and select Gmail
  5. expect openFile() to be called and allow access
  6. Gmail opens with no attachment

However, openFile() isn't called.

Here is the relevant code:

Create an intent and add extras:

public static void contact(Activity activity, String message) {
    Intent emailIntent = new Intent(Intent.ACTION_SEND);
    emailIntent.setType("text/plain");

    String version = Version.appVersion(activity);
    emailIntent.putExtra(Intent.EXTRA_EMAIL, new String[]{activity.getString(R.string.contact_email)});
    emailIntent.putExtra(Intent.EXTRA_SUBJECT, activity.getString(R.string.app_name)+" ("+version+")");
    emailIntent.putExtra(Intent.EXTRA_TEXT, message);

    addInfoAttachment(emailIntent, activity);

    String title = activity.getString(R.string.string_select_email_app);
    activity.startActivity(Intent.createChooser(emailIntent, title));
}

Create the cached file:

public static void createCachedFile(Context context, String fileName,
                                    String content) throws IOException 
{
    File cacheFile = new File(context.getCacheDir() + File.separator
            + fileName);
    cacheFile.delete();
    cacheFile.createNewFile();

    FileOutputStream fos = new FileOutputStream(cacheFile);
    OutputStreamWriter osw = new OutputStreamWriter(fos, "UTF8");
    PrintWriter pw = new PrintWriter(osw);
    pw.println(content);
    pw.flush();
    pw.close();
}

Add the file to the intent:

emailIntent.putExtra(Intent.EXTRA_STREAM, Uri.parse("content://" + AUTHORITY + File.separator + REPORT_INFO_FILE_NAME));

Overriding ContentProvider: (not called)

public class LogFileProvider extends ContentProvider {

    private static final String TAG = "LogFileProvider";

    public static final String AUTHORITY = "com.domain.appName.LogFileProvider";

    private UriMatcher uriMatcher;

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

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

        Log.v(TAG, "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:
                String fileLocation = getContext().getCacheDir() + File.separator
                        + uri.getLastPathSegment();
                return ParcelFileDescriptor.open(new File(
                        fileLocation), ParcelFileDescriptor.MODE_READ_ONLY);

            default:
                Log.v(TAG, "Unsupported uri: '" + uri + "'.");
                throw new FileNotFoundException("Unsupported uri: "
                        + uri.toString());
        }
    }

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

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

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

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

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

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

In manifest:

<provider
    android:name=".LogFileProvider"
    android:authorities="com.domain.appName.LogFileProvider"
    android:enabled="true"
    android:exported="false"
    android:grantUriPermissions="true"/>

Solution

  • You need to add FLAG_GRANT_READ_URI_PERMISSION to the Intent. As it stands, the recipient has no rights to access that content.

    You might consider using FileProvider rather than bothering with your own. Some clients will expect the provider to do more than what you have (e.g., respond to query() for the OpenableColumns, respond to getType() with the actual MIME type).