[TUT] HTML in an Email Intent

Recently I’ve had to write an Android Application that can send some of its data via email. It’s easy to send the data but I wanted to style the email using CSS. After trying every possible scenario here’s my results.

You can add < b >, < i > and other small styling tags but you can’t use things like < style > or < img >.

This list of tags is written up here and here:
https://commonsware.com/blog/Android/2010/05/26/html-tags-supported-by-textview.html
https://support.google.com/mail/bin/answer.py?hl=en&answer=8260

You can style a html file and use it as an attachment!

This is the work around I’m going to demonstrate to you now.
What we are going to do:

  1. Take some user input
  2. Create a HTML file
  3. Store the HTML file on the phone
  4. Start an Email Intent sending the HTML





As a heads up, we are going to avoid the use of the SD card. This keeps our life a little bit easier in that we don’t have to worry if the SD card is mounted or not. To do this we create a temporary file in our own local storage (this is the HTML file), we then create a content provider that will give the gmail application access to this file.



Note – The code is heavily commented rather than bore you with paragraphs and shock you with my illiteracy.

Ok so first we have the activity that takes some input from the user and they hit send. I’ve put some simple verification of the user input. The task to create the email is in an AsyncTask so we need a dialog to show up while this is loading.

MainActivity.class

package com.blundell.tutorial.ui.phone;

import com.blundell.tutorial.R;
import com.blundell.tutorial.domain.HtmlFile;
import com.blundell.tutorial.service.task.CreateHtmlTask;
import com.blundell.tutorial.service.task.CreateHtmlTask.OnTaskFinishedListener;
import com.blundell.tutorial.ui.fragment.LoadingDialogFragment;

import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.text.Html;
import android.view.View;
import android.widget.EditText;
import android.widget.Toast;

public class MainActivity extends FragmentActivity implements OnTaskFinishedListener {

    private LoadingDialogFragment loadingDialog;
	private EditText nameEditText;

	@Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        nameEditText = (EditText) findViewById(R.id.main_edit_text_enter_name);
    }

	// The XML onClick for out button
    public void onSendEmailClick(View button){
    	// Verify the input
    	String input = nameEditText.getText().toString();
		if(verifyNameInput(input)){
    		// We are about to do a task on another thread - so inform the user we are waiting
			showLoadingDialog();
			// Start our ASync Task to do the long running process of creating the HTML
			// Passing in the internal storage folder for our app - this is where the file will be created
			new CreateHtmlTask(getCacheDir(), this).execute(input);
    	} else {
    		// If the verification failed - simple feedback
    		Toast.makeText(this, "Please enter your name", Toast.LENGTH_SHORT).show();
    	}
    }

    /**
     * Do some simple verification on the input
     * @param input
     * @return
     */
	private static boolean verifyNameInput(String input) {
		// Check we havent been passed null
		// Remove the whitespace from the beginning and end
		// Check it's not empty
		if(input != null && !"".equals(input = input.trim())){
			// Check for smart people
			if(input.equalsIgnoreCase("no")){
				return false;
			}
			// **Assumption** know ones name is less than 3 chars (Li?)
			if(input.length() < 3){
				return false;
			}
			// If we've passed all verification return true
			return true;
		}
		return false;
	}

	@Override
	public void onHtmlCreated(HtmlFile htmlFile) {
		// Get rid of the dialog - we have finished
		dismissLoadingDialog();
		// Check we were sent a valid file
		if(htmlFile.isValid()){
			// Deal with the file we were sent
			startSendEmailIntent(htmlFile.getFilePath());
		} else {
			// Error checking - in a real app you might want to be more informative
			Toast.makeText(this, "Something went wrong!", Toast.LENGTH_SHORT).show();
		}
	}

	private void startSendEmailIntent(Uri attachmentUri) {
		// Create a new intent - we are 'sending' data
		Intent intent = new Intent(Intent.ACTION_SEND);
		// Mime type of html - so we can add some funky html tags in the email <b> etc </b>
		intent.setType("text/html");
		// The subject of your email
		intent.putExtra(Intent.EXTRA_SUBJECT, "Subject");
		// The uri to the attachment that is the real guts of our email
		intent.putExtra(Intent.EXTRA_STREAM, attachmentUri);
		// The email message
		intent.putExtra(Intent.EXTRA_TEXT, Html.fromHtml("Message body. <b>Funky!</b> <i>not</i>"));
		// Let the user select which application to send the email with, we have added a title
		// to give a hint that they should pick an email client
		Intent chooser = Intent.createChooser(intent, "Send Email");
		startActivity(chooser);
	}

	private void showLoadingDialog() {
		// Use our loading fragment to show progress
		this.loadingDialog = LoadingDialogFragment.newInstance().show(getSupportFragmentManager());
	}

	private void dismissLoadingDialog() {
		// Safety check
		if(this.loadingDialog != null){
			this.loadingDialog.dismiss();
		}
	}
}

Our loading dialog is a fragment that is shown on the screen whilst we do some long running task.
LoadingDialogFragment.java

package com.blundell.tutorial.ui.fragment;

import com.blundell.tutorial.R;
import com.blundell.tutorial.util.Log;

import android.app.Dialog;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.FragmentManager;

/**
 * This isn't the subject of this Tutorial, if you want it explaining please ask
 *
 * @author paul.blundell
 */
public class LoadingDialogFragment extends DialogFragment {
	private static final String ID = "loadingDialog";

    public static LoadingDialogFragment newInstance() {
		LoadingDialogFragment f = new LoadingDialogFragment();

        return f;
    }

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
    	Dialog d = new Dialog(getActivity());
    	d.setTitle("Loading");
    	d.setContentView(R.layout.fragment_loading);
    	return d;
    }

    public LoadingDialogFragment show(FragmentManager manager){
    	show(manager, ID);
    	return this;
    }

    @Override
    public void dismiss() {
	    try{
	    	super.dismiss();
	    } catch (Exception e) {
	    	// Null because it's not attached or some bs, why can't it just die quietly
			Log.w("Dialog tried to dismiss and failed. Are you bothered?");
		}
    }
}

The task to create the HTML is an ASyncTask meaning it runs in it’s own thread. In this example is it pretty simple we just add some bold text and an image. You could create whatever complex layout you want here. It also has an interface to listen for when the task finished. That way your activity will know when we have finished.

CreateHtmlTask.java

package com.blundell.tutorial.service.task;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;

import com.blundell.tutorial.domain.HtmlFile;
import com.blundell.tutorial.util.Log;

import android.os.AsyncTask;

/**
 * This class creates your HTML its input is an array of Strings that in this scenario are used to show the persons name
 * Once the HTML is created it is saved to the file system
 * We then wrap this file in our own domain object type and send it back to the calling class
 *
 * @author paul.blundell
 */
public class CreateHtmlTask extends AsyncTask<String, Integer, HtmlFile>{

	// This is an interface so whoever started this task can be informed when it is finished
	public interface OnTaskFinishedListener {
		void onHtmlCreated(HtmlFile html);
	}

	// The finished listener
	private final OnTaskFinishedListener taskFinishedListener;
	private final File folder;

	// Let the listener be set in the constructor (making it obvious to anyone using this class they can be informed when it is finished)
	// Note they can still pass null to not listen
	public CreateHtmlTask(File storageFolder, OnTaskFinishedListener taskFinishedListener) {
		this.folder = storageFolder;
		this.taskFinishedListener = taskFinishedListener;
	}

	@Override
	protected HtmlFile doInBackground(String... params) {
		// We are wrapping the File in our own domain object so we can add some convenience methods to it
		HtmlFile htmlFile;
		try {
			// Create whatever HTML you want here - don't forget to escape strings
			String name = params[0];
			StringBuilder builder = new StringBuilder();
			builder.append("<html>");
			builder.append("<head>");
			builder.append("</head>");
			builder.append("<body>");
			builder.append("<p>");
			builder.append("Hello ");
			builder.append(name);
			builder.append("<img src=\"https://developer.android.com/images/jb-android-4.1.png\"/>");
			builder.append("</body>");
			builder.append("</html>");
			String content = builder.toString();
			// Store the file
			File file = createTempFile(folder, "temp_file.html", content);
			// Create our domain object wrapping the file
			htmlFile = new HtmlFile(file);
		} catch (IOException e) {
			Log.e("IOException - creating safe HtmlFile", e);
			// Create a 'NullSafe' HtmlFile object if an error occurs
			htmlFile = new HtmlFile(null);
		}
		return htmlFile;
	}

	/**
	 * Creates a file - doesn't do any clean up
	 * @param folder - the folder to save the file in
	 * @param filename - the file name
	 * @param fileContent - the content to put in the file
	 * @return the created File
	 * @throws IOException - if anything goes wrong :-(
	 */
	private static File createTempFile(File folder, String filename, String fileContent) throws IOException {
		File f = new File(folder, filename);
		f.createNewFile();
		BufferedWriter buf = new BufferedWriter(new FileWriter(f));
		buf.append(fileContent);
		buf.close();
		return f;
    }

	@Override
	protected void onPostExecute(HtmlFile result) {
		super.onPostExecute(result);
		// Inform the class listening we have finished - sending back the completed Html File
		if(this.taskFinishedListener != null)
			this.taskFinishedListener.onHtmlCreated(result);
	}
}

The CreateHtmlTask creates us our file that is full of HTML, to allow the activity to check if this file has been created we wrap the java.io.File in our own HtmlFile object this gives us a convenient place to write helper methods on the File. For instance to check if it is null but wrap that in a doWeHaveTheFile() method. This is more human readable.

HtmlFile.java

package com.blundell.tutorial.domain;

import java.io.File;

import com.blundell.tutorial.service.provider.CacheFileProvider;

import android.net.Uri;

/**
 * This is a wrapper class for our 'File'
 * It lets us add methods and test the file without having to be locked to using java.io.File
 * @author paul.blundell
 */
public class HtmlFile {

	private final File file;

	public HtmlFile(File file) {
		this.file = file;
	}

	/**
	 * A convenience method to check that we are in a happy state
	 * @return
	 */
	public boolean isValid(){
		return this.file != null;
	}

	private String getFileName(){
		return file.getName();
	}

	/**
	 * @return a uri that is a pointer to the html file we have created - this can be used by content providers
	 */
	public Uri getFilePath(){
		return Uri.parse("content://"+ CacheFileProvider.AUTHORITY +"/"+ getFileName());
	}
}

Finally because we store our file on internal storage we need to have a content provider that will allow other applications to view the file. This is done using a Provider and a declaration in the AndroidManifest.

CacheFileProvider.java:

package com.blundell.tutorial.service.provider;

import java.io.File;
import java.io.FileNotFoundException;

import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.net.Uri;
import android.os.ParcelFileDescriptor;

/**
 * Thanks to Stephen Nicholas for the information to allow Gmail to view internal app data
 * https://stephendnicholas.com/archives/974
 *
 * @author paul.blundell
 */
public class CacheFileProvider extends ContentProvider {

	private static final int A_MATCH = 1;

	public static final String AUTHORITY = "com.blundell.tutorial.cacheFileProvider";

	private UriMatcher uriMatcher;

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

		uriMatcher.addURI(AUTHORITY, "*", A_MATCH);

		return true;
	}

	@Override
	public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
		if(uriMatcher.match(uri) == A_MATCH){
			String fileLocation = getContext().getCacheDir() + File.separator + uri.getLastPathSegment();
			File externallyVisibleFile = new File(fileLocation);
			ParcelFileDescriptor pfd = ParcelFileDescriptor.open(externallyVisibleFile, ParcelFileDescriptor.MODE_READ_ONLY);

			return pfd;
		}
		return super.openFile(uri, mode);
	}

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

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

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

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

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

AndroidManifest.xml

<manifest xmlns:android="https://schemas.android.com/apk/res/android"
    package="com.blundell.tutorial"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="15" />

    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name=".ui.phone.MainActivity"
            android:label="@string/title_activity_main" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <!-- This is declaring we provide some data to other apps (i.e. our html email attachment) -->
        <provider
            android:name=".service.provider.CacheFileProvider"
            android:authorities="com.blundell.tutorial.cacheFileProvider"
            android:exported="true" />
    </application>

</manifest>

Thats it, just click the button and the Intent does the rest! Email with attached Html File.

Here is the eclipse source:

Send Html Attachment Eclipse Source

Here is a GitHub mirror:

Coming soon

Some of the references I used in this research:

https://developer.android.com/reference/android/text/Html.html#toHtml(android.text.Spanned)

https://code.google.com/p/android/issues/detail?id=8640

https://stackoverflow.com/questions/6445014/action-send-text-html-is-not-sending-images-using-html-image-tag

https://stackoverflow.com/questions/1555171/why-gmail-blocked-the-css

7 thoughts on “[TUT] HTML in an Email Intent

  1. hii.. i am designing an app in which i have to send email with css and i found your blog post helpful. Thanks for your efforts. I have one query though… in MainActivity.class i have used button click in given fashion:
    public class Discount extends FragmentActivity implements CreateHtmlTask.OnTaskFinishedListener {

    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_discount);

    public void onClick(View v) {

    showLoadingDialog();
    // no authentication needed
    // Start our ASync Task to do the long running process of creating the HTML
    // Passing in the internal storage folder for our app – this is where the file will be created
    new CreateHtmlTask(getCacheDir(), v);

    }

    I am getting following error:
    CreateHtmlTask (File, package.CreateHtmlTask.onTaskFinishedListener) in CreateHtmlTask cannot be applied to (File, android.view.View).
    Can you explain this?

    1. You need to pass a listener as the second parameter, not a raw view. new CreateHtmlTask(getCacheDir(), this); or new CreateHtmlTask(getCacheDir(), new CreateHtmlTask.onTaskFinishedListener() { });

Comments are closed.