/* * Copyright 2012 Kevin Gaudin * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package kidozen.client.crash; import android.content.Context; import android.util.Log; import java.io.File; import java.io.IOException; import java.util.Arrays; import java.util.List; import static kidozen.client.crash.CrashReporter.LOG_TAG; /** * Checks and send reports on a separate Thread. * * @author Kevin Gaudin */ final class SendWorker extends Thread { private final Context context; private final boolean sendOnlySilentReports; private final boolean approvePendingReports; private final CrashReportFileNameParser fileNameParser = new CrashReportFileNameParser(); private final List<ReportSender> reportSenders; /** * Creates a new {@link SendWorker} to try sending pending reports. * * @param context * ApplicationContext in which the reports are being sent. * @param reportSenders * List of ReportSender to use to send the crash reports. * @param sendOnlySilentReports * If set to true, will send only reports which have been * explicitly declared as silent by the application developer. * @param approvePendingReports * if this endWorker should approve pending reports before * sending any reports. */ public SendWorker(Context context, List<ReportSender> reportSenders, boolean sendOnlySilentReports, boolean approvePendingReports) { this.context = context; this.reportSenders = reportSenders; this.sendOnlySilentReports = sendOnlySilentReports; this.approvePendingReports = approvePendingReports; } /* * (non-Javadoc) * * @see java.lang.Thread#run() */ @Override public void run() { if (approvePendingReports) { approvePendingReports(); } checkAndSendReports(context, sendOnlySilentReports); } /** * Flag all pending reports as "approved" by the user. These reports can be * sent. */ private void approvePendingReports() { Log.d(LOG_TAG, "Mark all pending reports as approved."); final CrashReportFinder reportFinder = new CrashReportFinder(context); final String[] reportFileNames = reportFinder.getCrashReportFiles(); for (String reportFileName : reportFileNames) { if (!fileNameParser.isApproved(reportFileName)) { final File reportFile = new File(context.getFilesDir(), reportFileName); // TODO look into how this could cause a file to go from // -approved.stacktrace to -approved-approved.stacktrace final String newName = reportFileName.replace(CrashConstants.REPORTFILE_EXTENSION, CrashConstants.APPROVED_SUFFIX + CrashConstants.REPORTFILE_EXTENSION); // TODO Look into whether rename is atomic. Is there a better // option? final File newFile = new File(context.getFilesDir(), newName); if (!reportFile.renameTo(newFile)) { Log.e(LOG_TAG, "Could not rename approved report from " + reportFile + " to " + newFile); } } } } /** * Send pending reports. * * @param context * The application context. * @param sendOnlySilentReports * Send only reports explicitly declared as SILENT by the * developer (sent via * {@link ErrorReporter#handleSilentException(Throwable)}. */ private void checkAndSendReports(Context context, boolean sendOnlySilentReports) { Log.d(LOG_TAG, "#checkAndSendReports - start"); final CrashReportFinder reportFinder = new CrashReportFinder(context); final String[] reportFiles = reportFinder.getCrashReportFiles(); Arrays.sort(reportFiles); int reportsSentCount = 0; for (String curFileName : reportFiles) { if (sendOnlySilentReports && !fileNameParser.isSilent(curFileName)) { continue; } if (reportsSentCount >= CrashConstants.MAX_SEND_REPORTS) { break; // send only a few reports to avoid overloading the // network } Log.i(LOG_TAG, "Sending file " + curFileName); try { final CrashReportPersister persister = new CrashReportPersister(context); final CrashReportData previousCrashReport = persister.load(curFileName); sendCrashReport(previousCrashReport); deleteFile(context, curFileName); } catch (RuntimeException e) { Log.e(LOG_TAG, "Failed to send crash reports for " + curFileName, e); deleteFile(context, curFileName); break; // Something really unexpected happened. Don't try to // send any more reports now. } catch (IOException e) { Log.e(LOG_TAG, "Failed to load crash report for " + curFileName, e); deleteFile(context, curFileName); break; // Something unexpected happened when reading the crash // report. Don't try to send any more reports now. } catch (ReportSenderException e) { Log.e(LOG_TAG, "Failed to send crash report for " + curFileName, e); // An issue occurred while sending this report but we can still try to // send other reports. Report sending is limited by CrashConstants.MAX_SEND_REPORTS // so there's not much to fear about overloading a failing server. } reportsSentCount++; } Log.d(LOG_TAG, "#checkAndSendReports - finish"); } /** * Sends the report with all configured ReportSenders. If at least one * sender completed its job, the report is considered as sent and will not * be sent again for failing senders. * * @param errorContent * Crash data. * @throws ReportSenderException * if unable to send the crash report. */ private void sendCrashReport(CrashReportData errorContent) throws ReportSenderException { Log.d(LOG_TAG, String.format( "CrashReporter.isDebuggable() : %s", String.valueOf(CrashReporter.isDebuggable()))); Log.d(LOG_TAG, String.format( "CrashReporter.getConfig().sendReportsInDevMode() : %s", String.valueOf(CrashReporter.getConfig().sendReportsInDevMode()) )); Log.d(LOG_TAG, String.format( "reportSenders size : %s", String.valueOf(reportSenders.size()) )); if (!CrashReporter.isDebuggable() || CrashReporter.getConfig().sendReportsInDevMode()) { boolean sentAtLeastOnce = false; for (ReportSender sender : reportSenders) { try { Log.d(LOG_TAG, String.format("Sender class : %s, ErrorContent: %s" , sender.getClass().toString() , errorContent )); sender.send(errorContent); // If at least one sender worked, don't re-send the report // later. sentAtLeastOnce = true; } catch (ReportSenderException e) { if (!sentAtLeastOnce) { throw e; // Don't log here because we aren't dealing // with the Exception here. } else { Log.w(LOG_TAG, "ReportSender of class " + sender.getClass().getName() + " failed but other senders completed their task. ACRA will not send this report again."); } } } } } private void deleteFile(Context context, String fileName) { final boolean deleted = context.deleteFile(fileName); if (!deleted) { Log.w(LOG_TAG, "Could not delete error report : " + fileName); } } }