Thursday , November 7 2024

Email crash report using ACRA

It would be great to get a detailed report every time your app crashes.
And that’s exactly what we’re gonna do in this post.

I’l be using ACRA, a wonderfully simple crash reporting library.Its also well documented.




A brief intro into ACRA :
1. It is capable of creating detailed reports on every app crash.
2. To obtain the report, ACRA can be configured in the following ways:

  • Send it to various supported backends.
  • Send it to your custom backend.
  • Send it via email.
  • Implement your own report handler.

Well, it already has an option to email the report !! Then why this post ?
Because for emailing, ACRA prompts the user to manually choose the emailing app (The intent chooser basically). This is simply an overhead.

So here’s what we’re gonna do.
We implement our own custom report handler.
This handler will capture the report and email it silently without the user knowing about it.
This method must be employed only during the testing phase of the app. Emailing silently from a production app will attract a lot of angry users, plus legal issues.

Let’s do some code :).

A brief overview of the classes used:
1. ACRAReportSender – Our custom report handler.
2. MyApplication – Our application class (to initialize ACRA).
3. MainActivity – Our launcher activity (to test the report sender).
4. GMailSender , JSSEProvider – Helper classes for ACRAReportSender.

You’ll need the following jars :



Here are the classes :
The application class – To configure ACRA.

import org.acra.ACRA;
import org.acra.annotation.ReportsCrashes;
 
import android.app.Application;
 
@ReportsCrashes(formKey = "qwertysvvasdfghjkl1235547657891234")
public class MyApplication extends Application {
 
 @Override
 public void onCreate() {
  super.onCreate();
   
  ACRA.init(this);
   
  // instantiate the report sender with the email credentials.
  // these will be used to send the crash report
  ACRAReportSender reportSender = new ACRAReportSender("yourEmail@gmail.com", "yourPassword");
   
  // register it with ACRA.
  ACRA.getErrorReporter().setReportSender(reportSender);
   
 }
}

Our custom report handler.

import org.acra.ReportField;
import org.acra.collector.CrashReportData;
import org.acra.sender.ReportSender;
import org.acra.sender.ReportSenderException;
 
import android.content.Context;
import android.util.Log;
 
public class ACRAReportSender implements ReportSender {
 
 private String emailUsername ;
 private String emailPassword ;
  
  
  
 public ACRAReportSender(String emailUsername, String emailPassword) {
  super();
  this.emailUsername = emailUsername;
  this.emailPassword = emailPassword;
 }
 
 
 
 @Override
 public void send(Context context, CrashReportData report)
   throws ReportSenderException {
   
  // Extract the required data out of the crash report.
  String reportBody = createCrashReport(report);
   
  // instantiate the email sender
  GMailSender gMailSender = new GMailSender(emailUsername, emailPassword);
   
  try {
   // specify your recipients and send the email
   gMailSender.sendMail("CRASH REPORT", reportBody, emailUsername, "recipient1@gmail.com, recipient2@gmail.com");
  } catch (Exception e) {
   Log.d("Error Sending email", e.toString());
  }
 }
 
 
 /** Extract the required data out of the crash report.*/
 private String createCrashReport(CrashReportData report) {
   
  // I've extracted only basic information.
  // U can add loads more data using the enum ReportField. See below.
  StringBuilder body = new StringBuilder();
  body
  .append("Device : " + report.getProperty(ReportField.BRAND) + "-" + report.getProperty(ReportField.PHONE_MODEL))
  .append("\n")
  .append("Android Version :" + report.getProperty(ReportField.ANDROID_VERSION))
  .append("\n")
  .append("App Version : " + report.getProperty(ReportField.APP_VERSION_CODE))
  .append("\n")
  .append("STACK TRACE : \n" + report.getProperty(ReportField.STACK_TRACE));
   
   
  return body.toString();
 }
}

I’ve extracted only the basic data required. For a complete list of available data, refer this.

Helper for class for sending the email.

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.Security;
import java.util.Properties;
 
import javax.activation.DataHandler;
import javax.activation.DataSource;
import javax.mail.Message;
import javax.mail.PasswordAuthentication;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
 
import android.util.Log;
 
public class GMailSender extends javax.mail.Authenticator {   
    private String mailhost = "smtp.gmail.com";   
    private String user;   
    private String password;   
    private Session session;   
 
    static {   
        Security.addProvider(new JSSEProvider());   
    }  
 
    public GMailSender(String user, String password) {   
        this.user = user;   
        this.password = password;   
 
        Properties props = new Properties();   
        props.setProperty("mail.transport.protocol", "smtp");   
        props.setProperty("mail.host", mailhost);   
        props.put("mail.smtp.auth", "true");   
        props.put("mail.smtp.port", "465");   
        props.put("mail.smtp.socketFactory.port", "465");   
        props.put("mail.smtp.socketFactory.class",   
                "javax.net.ssl.SSLSocketFactory");   
        props.put("mail.smtp.socketFactory.fallback", "false");   
        props.setProperty("mail.smtp.quitwait", "false");   
 
        session = Session.getDefaultInstance(props, this);   
    }   
 
    protected PasswordAuthentication getPasswordAuthentication() {   
        return new PasswordAuthentication(user, password);   
    }   
 
    public synchronized void sendMail(String subject, String body, String sender, String recipients) throws Exception {   
        try{
        MimeMessage message = new MimeMessage(session);   
        DataHandler handler = new DataHandler(new ByteArrayDataSource(body.getBytes(), "text/plain"));   
        message.setSender(new InternetAddress(sender));   
        message.setSubject(subject);   
        message.setDataHandler(handler);   
        if (recipients.indexOf(',') > 0)   
            message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(recipients));   
        else 
            message.setRecipient(Message.RecipientType.TO, new InternetAddress(recipients));   
        Transport.send(message);   
        }catch(Exception e){
         Log.d("Error Sendin Email", e.toString());
        }
    }   
 
    public class ByteArrayDataSource implements DataSource {   
        private byte[] data;   
        private String type;   
 
        public ByteArrayDataSource(byte[] data, String type) {   
            super();   
            this.data = data;   
            this.type = type;   
        }   
 
        public ByteArrayDataSource(byte[] data) {   
            super();   
            this.data = data;   
        }   
 
        public void setType(String type) {   
            this.type = type;   
        }   
 
        public String getContentType() {   
            if (type == null)   
                return "application/octet-stream";   
            else 
                return type;   
        }   
 
        public InputStream getInputStream() throws IOException {   
            return new ByteArrayInputStream(data);   
        }   
 
        public String getName() {   
            return "ByteArrayDataSource";   
        }   
 
        public OutputStream getOutputStream() throws IOException {   
            throw new IOException("Not Supported");   
        }   
    }   
}

Another helper class

import java.security.AccessController;
import java.security.Provider;
 
@SuppressWarnings("serial")
public final class JSSEProvider extends Provider {
 
    public JSSEProvider() {
        super("HarmonyJSSE", 1.0, "Harmony JSSE Provider");
        AccessController.doPrivileged(new java.security.PrivilegedAction<Void>() {
            public Void run() {
                put("SSLContext.TLS",
                        "org.apache.harmony.xnet.provider.jsse.SSLContextImpl");
                put("Alg.Alias.SSLContext.TLSv1", "TLS");
                put("KeyManagerFactory.X509",
                        "org.apache.harmony.xnet.provider.jsse.KeyManagerFactoryImpl");
                put("TrustManagerFactory.X509",
                        "org.apache.harmony.xnet.provider.jsse.TrustManagerFactoryImpl");
                return null;
            }
        });
    }
}

Finally , The launcher activity to test this out.

import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
 
public class MainActivity extends ActionBarActivity {
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
 
  throw new RuntimeException("DummyException");
 
 }
 
}

And yes, the AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.w2class.acra"
    android:versionCode="1"
    android:versionName="1.0" >
 
    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="21" />
 
    <!-- IMPORTANT : Required by ACRA to send the email !!  -->
    <uses-permission android:name="android.permission.INTERNET"/>
     
    <!-- IMPORTANT : The application class MUST be MyApplication.  -->
    <application
        android:name="MyApplication"
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/Theme.AppCompat"
        >
        <activity
            android:name=".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>
    </application>
 
</manifest>

Important :

  •  <uses-permission android:name=”android.permission.INTERNET”/>MUST BE DECLARED in the manifest. This required by ACRA to send the email.
  •  android:name=”MyApplication” MUST BE DECLARED in the application tag. Otherwise ACRA will not work.

A Few Notes :

  • In this line :                                                                                                                                                              ACRAReportSender reportSender = new ACRAReportSender(“yourEmail@gmail.com”, “yourPassword”);
  • Your are exposing your email’s username and password. This is a security threat and hence must be used only when the app is in the testing phase,

  • I’m afraid the class GMailSender has been configured for gmail only. You may have to change it to suit your email provider.
  • If you have any kind of trouble setting up ACRA, refer ACRA Setup. Its super simple.




About admin

Check Also

Binding JavaScript and Android Code – Example

When developing a web application that’s designed specifically for the WebView in your Android application, …

Leave a Reply