Android: Leer correos de Gmail
- Introducción
- Captura de pantalla de la aplicación a construir
- Código fuente del ejemplo
- 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(ArrayListemails){ 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
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
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.
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 🙂