Android: Leer correos de Gmail

3
12819

Android: Leer correos de Gmail

  1. Introducción
  2. Captura de pantalla de la aplicación a construir
  3. Código fuente del ejemplo
  4. Otros métodos y consideraciones

Introducción

Este tutorial os voy a proporcionar un ejemplo de como acceder a los correos electrónicos de Gmail desde vuestras aplicaciones Android sin tener que solicitar al usuario la contraseña de su cuenta de correo.

Para empezar te diré, que no necesita acceder a Internet, es decir, que los correos están ya descargados por el cliente de correo que tiene preinstalado Android y configurado.

Este tutorial surge como apoyo al lector después de realizar muchos esfuerzos e investigaciones para crear mi aplicación Haaala!

Captura de pantalla de la aplicación a construir

Es una aplicación sencilla (didáctica), un botón que al hacer clic en el se ejecuta una tarea que accede al correo electrónico y muestra el asunto de los correos recibidos en desde una determinada fecha.

Código fuente del ejemplo

Puedes descargarte el código fuente haciendo clic aquí

Manos a la obra..

Ventana principal de la aplicación:

package es.carlosgarcia;

import java.util.ArrayList;
import java.util.Date;

import android.app.Activity;
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;

/**
 * Ventana de test del ContentProvider
 * @author Carlos García. 
 * @see http://www.carlos-garcia.es
 */
public class GmailContentProviderActivity extends Activity implements OnClickListener, IBackgroudTaskResultListener {
   
	private Button   btnReadGmail;
	private EditText txtGmailContent;
	
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        this.configureUI();
    }
    
    private void configureUI(){
        this.setContentView(R.layout.main);
        this.txtGmailContent = (EditText) this.findViewById(R.id.txtEmails);
        this.btnReadGmail = (Button) this.findViewById(R.id.cmdReadMails);
        this.btnReadGmail.setOnClickListener(this);    	
    }
    
	@Override
	public void onClick(View v) {
		txtGmailContent.setText("");
		
		if (btnReadGmail == v){
			String account  = "cgpcosmad@gmail.com";
			Date   fromDate = new Date(2011, 7, 1);
			
			GmailReadTask task = new GmailReadTask(this.getContentResolver(), this, account, fromDate); 
			task.execute();
		}
	}
	
	
	@Override
	public boolean onKeyDown(int keyCode, KeyEvent event) {
		if (keyCode == KeyEvent.KEYCODE_BACK) {
			this.finish();
			return true;
		}
		return super.onKeyDown(keyCode, event);
	}

	private void paintMessages(ArrayList emails){
		if (emails != null){
			for (Mail mail : emails){
				txtGmailContent.append(mail.getSubject());
				txtGmailContent.append("\n---\n");
			}
		} else {
			txtGmailContent.append("No hay emails");
		}
	}

	@SuppressWarnings("unchecked")
	@Override
	public void backgroundTaskFinished(String code, Object result) {
		if ("ok".equals(code)){
			this.paintMessages((ArrayList) result);
		}
	}
}

Contrato de comunicación entre actividad y tarea en background (tampoco haría falta hacerlo así, hay otras muchas formas de comunicación..)

package es.carlosgarcia;

/**
 * Interfaz a implementar por las clases que deseen ser notificadas de las tareas en background 
 * @author Carlos García. 
 * @see http://www.carlos-garcia.es
 */
public interface IBackgroudTaskResultListener {
	/**
	 * @param code 		Código de la operación que ha finalizado
	 * @param result	Resultado de la operación
	 */
	public void backgroundTaskFinished(String code, Object result);
}

Tarea que accede a través de consultas a un Content Provider a los datos de Gmail:

Si quieres más información sobre posibles parámetros de todo, busca en Google información sobre Gmail.java ContentProvider

package es.carlosgarcia;

import java.util.ArrayList;
import java.util.Date;

import android.content.ContentResolver;
import android.database.Cursor;
import android.net.Uri;
import android.os.AsyncTask;
import android.util.Log;

/**
 * Lectura de emails
 * @author Carlos García. 
 * @see http://www.carlos-garcia.es
 */
public class GmailReadTask extends AsyncTask> {  //  
	private static final String   URI_PREFIX              = "content://gmail-ls/conversations/";
	private static final String[] CONVERSATION_PROYECTION = {"_id", "date"};
	private static final String[] MESSAGE_PROJECTION	  = {"_id", "subject", "fromAddress", "body", "dateReceivedMs"};
	 
	private ContentResolver resolver;
	private IBackgroudTaskResultListener listener;
	private String email;
	private Date   fromTime;
    
	public GmailReadTask(ContentResolver resolver, IBackgroudTaskResultListener listener, String email, Date fromTime){
		this.resolver = resolver;
		this.listener = listener;
		this.email	  = email;
		this.fromTime = fromTime;
	}
	
	protected ArrayList doInBackground(Void... params) {
		ArrayList mails = new ArrayList(32);
        boolean finish = false;
        Cursor	cCursor;
        
		try {
	        // Conversaciones ordenadas por fecha (de más reciente a más antigua)
	        cCursor	= resolver.query(Uri.parse(URI_PREFIX + email), CONVERSATION_PROYECTION, "label:^i" , null, null);	
		    
	        while (cCursor.moveToNext() && (! finish)) {
        		finish = fromTime.before(new Date(cCursor.getLong(cCursor.getColumnIndex("date"))));

            	if (! finish){
            		// Obtenemos un cursor para esa conversación.
            		String conv_id  = cCursor.getString(cCursor.getColumnIndex("_id"));
            		Uri	   uri	    = Uri.parse(URI_PREFIX + email + "/" + conv_id + "/messages");
                    Cursor mCursor	= resolver.query(uri, MESSAGE_PROJECTION, null, null, null);	                
                    
                    try {
                       while (mCursor.moveToNext() &&  (! finish)){
                    	   long mtime = mCursor.getLong(mCursor.getColumnIndex("dateReceivedMs"));
                    	   finish = fromTime.before(new Date(mtime));
                    	   
                    	   if (! finish){
                    	   	   // TODO: Cachear el resultado de getColumnIndex para sólo invocarlo una vez.
                              String id      = mCursor.getString(mCursor.getColumnIndex("_id"));
                              String from	   = mCursor.getString(mCursor.getColumnIndex("fromAddress"));
                              String subject = mCursor.getString(mCursor.getColumnIndex("subject"));
                              String body    = mCursor.getString(mCursor.getColumnIndex("body"));
                              
                              mails.add(new Mail(id, from, subject, body, mtime));    
                    	   }
                       }
                    } finally {
                    	closeQuietly(mCursor); 
                    }                    
            	}
	    	}
		} catch (Exception ex){	// TODO: Nota, capturar la excepción padre de todas las posibles 
			Log.e("GmailReadApp", ex.toString());
			mails.add(new Mail(null, null, ex.toString(), ex.toString(), 0));
		} finally {
			closeQuietly(cCursor);
		}
		
		return mails;
	}
	
	private void closeQuietly(Cursor cursor) {
		try {
			cursor.close();
		} catch (Exception ex){
			// Nada
		}	
	}
	
	@Override
	protected void onPostExecute(ArrayList result) {
		super.onPostExecute(result);
		this.listener.backgroundTaskFinished("ok", result);
	}
}

Objeto de negocio que nos construye la tarea desde el contenido de un correo electrónico

package es.carlosgarcia;

/**
 * Correo electrónico
 * @author Carlos García. 
 * @see http://www.carlos-garcia.es
 */
public class Mail implements java.io.Serializable {
	private static final long serialVersionUID = 6806007514699011076L;
	
	private String subject;
	private String id;
	private String from;
	private String body;
	private long   ctime;
	
	public Mail(String id, String from, String subject, String body, long ctime) {
		super();
		this.id = id;		
		this.from = from;
		this.subject = subject;
		this.body = body;
		this.ctime = ctime;
	}

	public String getId() {
		return id;
	}
	
	public String getFrom() {
		return from;
	}

	public String getBody() {
		return body;
	}
	
	public long getCtime() {
		return ctime;
	}

	public String getSubject() {
		return subject;
	}
}

El archivo de configuración de la aplicación: AndroidManifest.xml



    
        
            
                
                
            
        
    

	
	

	
	
	

La ventana principal de la aplicación



	

Otros métodos y consideraciones:

Los usuarios son muy reticentes a proporcionar sus contraseñas de acceso a servicios tan importantes como Gmail, Facebook, etc…
un método que cada vez está más extendido es el de usar el protocolo OAuth (o en caso de Google, además los protocolos propietarios: AuthSub y ClientLogin) para conceder a las aplicaciones
determinados privilegios de acceso a los datos.

En el caso de Google, actualmente hay un rico API sobre muchos de sus Servicios (Google Maps, Latitude, Contacts, Picasa, etc.) los cuales encapsulan (wrapper) los datos que nos proporcionan las invocaciones a los servicios
exportados en la nueva arquitectura que está migrando Google, de hecho existe un API en fase Beta.

El servicio de Gmail es un tanto especial, pues no hay un API del mismo tipo que para los otros servicios, pero si que hay distintas formas de acceso. He investigado bastante y conseguido acceso por todos los métodos y he visto en todas ellas las siguientes limitaciones:

  • ¿A través de OAuth (v1 o v2)? » Sólo podremos acceder a un flujo de datos ATOM en donde podemos obtener el número de mensajes no leídos, los asuntos, un fragmento del cuerpo, la fecha y el emisor.
  • API para acceder a IMAP y SMTP via Oauth, potente pero hay que hacer muchos pasos en donde se necesitan conocimientos técnicos, por lo que no lo veo
    viable para aplicaciones de uso general.
  • ¿El AccountManager de Android? » El AccesToken sólo nos vale para conseguir el flujo ATOM.. es decir limitado para Gmail.
  • ¿Pedirle los datos de acceso al usuario? Pues entonces sin problemas, tienes aceso por JavaMail a todo (envio, recepción, etc.), claro otra cosa es que el usuario quiera ponerla en la aplicación :-).

Bueno, espero que os sea de utilidad.

Un saludo. Carlos García

3 COMENTARIOS

  1. Dependiendo de vuestra versión de Android, Google a cancelado los permisos de lectura de GMail, produciendose la siguiente excepción:

    Permission Denial: opening provider com.google.android.gm.provider.MailProvider…

    requires. com.google.android.gm.permission.READ_GMAIL or com.google.android.gm.permission.WRITE_GMAIL.

    Está explicado en:
    http://groups.google.com/a/googleproductforums.com/forum/#!category-topic/gmail/reading-and-receiving-messages/XD0C4sw9K7U

  2. Hola Carlos,tengo una tablet Pacal 2,quiero leer el Gmail(tiene Android 4)normal de Google y no puedo,me sale el Gmail de Android,te agradecería ,me echases una mano,gracias y enhorabuena.

  3. Buen día!
    Intenté implementar su código ya que necesito algo parecido para una función dentro de mi aplicación de proyecto de titulación. Sin embargo, al ejecutarla, presiono el botón «GMAIL MENSAJES» y me aparece esto: «java.lang.SecurityException:Permission Denial:opening provider….» tal cual lo escribes en un comentario superior; ya intenté cambiar los permisos, pero me aparece el mismo.
    Me serviría muchísimo cualquier ayuda o explicación del programa, de preferencia lo más pronto posible.
    De antemano, muchas gracias 🙂

DEJA UNA RESPUESTA

Por favor ingrese su comentario!

He leído y acepto la política de privacidad

Por favor ingrese su nombre aquí

Información básica acerca de la protección de datos

  • Responsable:
  • Finalidad:
  • Legitimación:
  • Destinatarios:
  • Derechos:
  • Más información: Puedes ampliar información acerca de la protección de datos en el siguiente enlace:política de privacidad