/* * 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 org.acra.sender; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.support.annotation.NonNull; import org.acra.ACRA; import org.acra.collector.CrashReportData; import org.acra.config.ACRAConfiguration; import org.acra.config.DefaultRetryPolicy; import org.acra.config.RetryPolicy; import org.acra.file.CrashReportPersister; import org.acra.util.IOUtils; import org.acra.util.InstanceCreator; import org.json.JSONException; import java.io.File; import java.io.IOException; import java.util.LinkedList; import java.util.List; import static org.acra.ACRA.LOG_TAG; /** * Distributes reports to all Senders. * * @author William Ferguson * @since 4.8.0 */ final class ReportDistributor { private final Context context; private final ACRAConfiguration config; private final List<ReportSender> reportSenders; /** * Creates a new {@link ReportDistributor} to try sending pending reports. * * @param context ApplicationContext in which the reports are being sent. * @param config Configuration to use while sending. * @param reportSenders List of ReportSender to use to send the crash reports. */ ReportDistributor(@NonNull Context context, @NonNull ACRAConfiguration config, @NonNull List<ReportSender> reportSenders) { this.context = context; this.config = config; this.reportSenders = reportSenders; } /** * Send report via all senders. * * @param reportFile Report to send. */ public void distribute(@NonNull File reportFile) { ACRA.log.i(LOG_TAG, "Sending report " + reportFile ); try { final CrashReportPersister persister = new CrashReportPersister(); final CrashReportData previousCrashReport = persister.load(reportFile); sendCrashReport(previousCrashReport); IOUtils.deleteReport(reportFile); } catch (RuntimeException e) { ACRA.log.e(LOG_TAG, "Failed to send crash reports for " + reportFile, e); IOUtils.deleteReport(reportFile); } catch (IOException e) { ACRA.log.e(LOG_TAG, "Failed to load crash report for " + reportFile, e); IOUtils.deleteReport(reportFile); } catch (JSONException e) { ACRA.log.e(LOG_TAG, "Failed to load crash report for " + reportFile, e); IOUtils.deleteReport(reportFile); }catch (ReportSenderException e) { ACRA.log.e(LOG_TAG, "Failed to send crash report for " + reportFile, e); // An issue occurred while sending this report but we can still try to // send other reports. Report sending is limited by ACRAConstants.MAX_SEND_REPORTS // so there's not much to fear about overloading a failing server. } } /** * 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(@NonNull CrashReportData errorContent) throws ReportSenderException { if (!isDebuggable() || config.sendReportsInDevMode()) { final List<RetryPolicy.FailedSender> failedSenders = new LinkedList<RetryPolicy.FailedSender>(); for (ReportSender sender : reportSenders) { try { if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "Sending report using " + sender.getClass().getName()); sender.send(context, errorContent); if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "Sent report using " + sender.getClass().getName()); } catch (ReportSenderException e) { failedSenders.add(new RetryPolicy.FailedSender(sender, e)); } } final InstanceCreator instanceCreator = new InstanceCreator(); if (failedSenders.isEmpty()) { if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "Report was sent by all senders"); } else if (instanceCreator.create(config.retryPolicyClass(), new DefaultRetryPolicy()).shouldRetrySend(reportSenders, failedSenders)) { final Throwable firstFailure = failedSenders.get(0).getException(); throw new ReportSenderException("Policy marked this task as incomplete. ACRA will try to send this report again.", firstFailure); } else { final StringBuilder builder = new StringBuilder("ReportSenders of classes ["); for (final RetryPolicy.FailedSender failedSender : failedSenders) { builder.append(failedSender.getSender().getClass().getName()); builder.append(", "); } builder.append("] failed, but Policy marked this task as complete. ACRA will not send this report again."); ACRA.log.w(LOG_TAG, builder.toString()); } } } /** * Returns true if the application is debuggable. * * @return true if the application is debuggable. */ private boolean isDebuggable() { final PackageManager pm = context.getPackageManager(); try { return (pm.getApplicationInfo(context.getPackageName(), 0).flags & ApplicationInfo.FLAG_DEBUGGABLE) > 0; } catch (PackageManager.NameNotFoundException e) { return false; } } }