diff --git a/acra-advanced-scheduler/src/main/java/org/acra/annotation/AcraScheduler.java b/acra-advanced-scheduler/src/main/java/org/acra/annotation/AcraScheduler.java index 626f900598..41c97e4138 100644 --- a/acra-advanced-scheduler/src/main/java/org/acra/annotation/AcraScheduler.java +++ b/acra-advanced-scheduler/src/main/java/org/acra/annotation/AcraScheduler.java @@ -31,6 +31,7 @@ @Inherited @Configuration public @interface AcraScheduler { + String EXTRA_APP_RESTARTED = "acra.restarted"; /** * Network constraint for report sending * @@ -62,4 +63,14 @@ * @since 5.2.0 */ boolean requiresBatteryNotLow() default false; + + /** + * Restarts the last activity immediately after a crash. + * If an activity is restarted, the {@link org.acra.annotation.AcraScheduler#EXTRA_APP_RESTARTED} extra will contain a boolean true. + * Note that this might interact badly with the crash dialog. + * + * @return if acra should attempt to restart the app after a crash + * @since 5.2.0 + */ + boolean restartAfterCrash() default false; } diff --git a/acra-advanced-scheduler/src/main/java/org/acra/scheduler/AcraJobCreator.java b/acra-advanced-scheduler/src/main/java/org/acra/scheduler/AcraJobCreator.java new file mode 100644 index 0000000000..c86259e3c0 --- /dev/null +++ b/acra-advanced-scheduler/src/main/java/org/acra/scheduler/AcraJobCreator.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2018 + * + * 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.scheduler; + +import android.app.Activity; +import android.content.Intent; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import com.evernote.android.job.Job; +import com.evernote.android.job.JobCreator; +import org.acra.ACRA; +import org.acra.config.CoreConfiguration; +import org.acra.sender.SenderService; + +/** + * @author F43nd1r + * @since 07.05.18 + */ +class AcraJobCreator implements JobCreator { + static final String REPORT_TAG = "org.acra.report.Job"; + static final String RESTART_TAG = "org.acra.restart.Job"; + private final CoreConfiguration config; + + public AcraJobCreator(CoreConfiguration config) { + this.config = config; + } + + @Nullable + @Override + public Job create(@NonNull String tag) { + switch (tag) { + case REPORT_TAG: + return new Job() { + @NonNull + @Override + protected Result onRunJob(@NonNull Params params) { + boolean sendOnlySilentReports = params.getExtras().getBoolean(SenderService.EXTRA_ONLY_SEND_SILENT_REPORTS, false); + new DefaultSenderScheduler(getContext(), config).scheduleReportSending(sendOnlySilentReports); + return Result.SUCCESS; + } + }; + case RESTART_TAG: + return new Job() { + @NonNull + @Override + protected Result onRunJob(@NonNull Params params) { + String className = params.getExtras().getString(RestartingAdministrator.EXTRA_LAST_ACTIVITY, null); + if (className != null) { + try { + //noinspection unchecked + Class activityClass = (Class) Class.forName(className); + final Intent intent = new Intent(getContext(), activityClass); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + getContext().startActivity(intent); + } catch (ClassNotFoundException e) { + ACRA.log.w(ACRA.LOG_TAG, "Unable to find activity class" + className); + } + } + return Result.SUCCESS; + } + }; + default: + return null; + } + } +} diff --git a/acra-advanced-scheduler/src/main/java/org/acra/scheduler/AdvancedSenderScheduler.java b/acra-advanced-scheduler/src/main/java/org/acra/scheduler/AdvancedSenderScheduler.java index 04506ab0dc..a4ef83cc16 100644 --- a/acra-advanced-scheduler/src/main/java/org/acra/scheduler/AdvancedSenderScheduler.java +++ b/acra-advanced-scheduler/src/main/java/org/acra/scheduler/AdvancedSenderScheduler.java @@ -18,7 +18,6 @@ import android.content.Context; import android.support.annotation.NonNull; -import com.evernote.android.job.Job; import com.evernote.android.job.JobManager; import com.evernote.android.job.JobRequest; import com.evernote.android.job.util.support.PersistableBundleCompat; @@ -39,7 +38,6 @@ * @since 18.04.18 */ public class AdvancedSenderScheduler implements SenderScheduler { - static final String TAG = "org.acra.report.Job"; private final Context context; private final CoreConfiguration config; @@ -56,7 +54,7 @@ public void scheduleReportSending(boolean onlySendSilentReports) { SchedulerConfiguration schedulerConfiguration = ConfigUtils.getPluginConfiguration(config, SchedulerConfiguration.class); PersistableBundleCompat extras = new PersistableBundleCompat(); extras.putBoolean(SenderService.EXTRA_ONLY_SEND_SILENT_REPORTS, onlySendSilentReports); - new JobRequest.Builder(TAG) + new JobRequest.Builder(AcraJobCreator.REPORT_TAG) .setExecutionWindow(1, TimeUnit.MINUTES.toMillis(1)) .setExtras(extras) .setRequirementsEnforced(true) @@ -79,16 +77,10 @@ public Factory() { @NonNull @Override public SenderScheduler create(@NonNull Context context, @NonNull CoreConfiguration config) { - JobManager.create(context).addJobCreator(tag -> TAG.equals(tag) ? new Job() { - @NonNull - @Override - protected Result onRunJob(@NonNull Params params) { - boolean sendOnlySilentReports = params.getExtras().getBoolean(SenderService.EXTRA_ONLY_SEND_SILENT_REPORTS, false); - new DefaultSenderScheduler(getContext(), config).scheduleReportSending(sendOnlySilentReports); - return Result.SUCCESS; - } - } : null); + JobManager.create(context).addJobCreator(new AcraJobCreator(config)); return new AdvancedSenderScheduler(context, config); } + } + } diff --git a/acra-advanced-scheduler/src/main/java/org/acra/scheduler/RestartingAdministrator.java b/acra-advanced-scheduler/src/main/java/org/acra/scheduler/RestartingAdministrator.java new file mode 100644 index 0000000000..3dae3eeaf9 --- /dev/null +++ b/acra-advanced-scheduler/src/main/java/org/acra/scheduler/RestartingAdministrator.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2018 + * + * 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.scheduler; + +import android.content.Context; +import android.support.annotation.NonNull; +import com.evernote.android.job.JobRequest; +import com.evernote.android.job.util.support.PersistableBundleCompat; +import com.google.auto.service.AutoService; +import org.acra.builder.LastActivityManager; +import org.acra.config.ConfigUtils; +import org.acra.config.CoreConfiguration; +import org.acra.config.ReportingAdministrator; +import org.acra.config.SchedulerConfiguration; +import org.acra.plugins.ConfigBasedAllowsDisablePlugin; + +import java.util.concurrent.TimeUnit; + +/** + * @author F43nd1r + * @since 07.05.18 + */ +@AutoService(ReportingAdministrator.class) +public class RestartingAdministrator extends ConfigBasedAllowsDisablePlugin implements ReportingAdministrator { + public static final String EXTRA_LAST_ACTIVITY = "lastActivity"; + + public RestartingAdministrator() { + super(SchedulerConfiguration.class); + } + + @Override + public boolean shouldFinishActivity(@NonNull Context context, @NonNull CoreConfiguration config, LastActivityManager lastActivityManager) { + if (ConfigUtils.getPluginConfiguration(config, SchedulerConfiguration.class).restartAfterCrash() && lastActivityManager.getLastActivity() != null) { + PersistableBundleCompat extras = new PersistableBundleCompat(); + extras.putString(EXTRA_LAST_ACTIVITY, lastActivityManager.getLastActivity().getClass().getName()); + new JobRequest.Builder(AcraJobCreator.RESTART_TAG) + .setExact(TimeUnit.SECONDS.toMillis(1)) + //.setExecutionWindow(TimeUnit.SECONDS.toMillis(10), TimeUnit.MINUTES.toMillis(1)) + .setExtras(extras) + .setUpdateCurrent(true) + .build() + .schedule(); + } + return true; + } +} diff --git a/acra-core/src/main/java/org/acra/builder/ReportExecutor.java b/acra-core/src/main/java/org/acra/builder/ReportExecutor.java index 68b8952741..5310a68728 100644 --- a/acra-core/src/main/java/org/acra/builder/ReportExecutor.java +++ b/acra-core/src/main/java/org/acra/builder/ReportExecutor.java @@ -55,6 +55,7 @@ public class ReportExecutor { private final CrashReportDataFactory crashReportDataFactory; private final List reportingAdministrators; private final SchedulerStarter schedulerStarter; + private final LastActivityManager lastActivityManager; // A reference to the system's previous default UncaughtExceptionHandler // kept in order to execute the default exception handling after sending the report. @@ -74,7 +75,8 @@ public class ReportExecutor { * @param processFinisher used to end process after reporting */ public ReportExecutor(@NonNull Context context, @NonNull CoreConfiguration config, @NonNull CrashReportDataFactory crashReportDataFactory, - @Nullable Thread.UncaughtExceptionHandler defaultExceptionHandler, @NonNull ProcessFinisher processFinisher, @NonNull SchedulerStarter schedulerStarter) { + @Nullable Thread.UncaughtExceptionHandler defaultExceptionHandler, @NonNull ProcessFinisher processFinisher, @NonNull SchedulerStarter schedulerStarter, + @NonNull LastActivityManager lastActivityManager) { this.context = context; this.config = config; this.crashReportDataFactory = crashReportDataFactory; @@ -82,6 +84,7 @@ public ReportExecutor(@NonNull Context context, @NonNull CoreConfiguration confi this.processFinisher = processFinisher; reportingAdministrators = new PluginLoader(config).load(ReportingAdministrator.class); this.schedulerStarter = schedulerStarter; + this.lastActivityManager = lastActivityManager; } /** @@ -149,8 +152,20 @@ public final void execute(@NonNull final ReportBuilder reportBuilder) { if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "Not collecting crash report because of ReportingAdministrator " + blockingAdministrator.getClass().getName()); } if (reportBuilder.isEndApplication()) { - // Finish the last activity early to prevent restarts on android 7+ - processFinisher.finishLastActivity(reportBuilder.getUncaughtExceptionThread()); + boolean finishActivity = true; + for (ReportingAdministrator administrator : reportingAdministrators) { + try { + if (!administrator.shouldFinishActivity(context, config, lastActivityManager)) { + finishActivity = false; + } + } catch (Throwable t) { + ACRA.log.w(LOG_TAG, "ReportingAdministrator " + administrator.getClass().getName() + " threw exception", t); + } + } + if (finishActivity) { + // Finish the last activity early to prevent restarts on android 7+ + processFinisher.finishLastActivity(reportBuilder.getUncaughtExceptionThread()); + } } if (blockingAdministrator == null) { StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites(); diff --git a/acra-core/src/main/java/org/acra/config/ReportingAdministrator.java b/acra-core/src/main/java/org/acra/config/ReportingAdministrator.java index 8cc0fa4e6f..8f09851aab 100644 --- a/acra-core/src/main/java/org/acra/config/ReportingAdministrator.java +++ b/acra-core/src/main/java/org/acra/config/ReportingAdministrator.java @@ -19,6 +19,7 @@ import android.content.Context; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import org.acra.builder.LastActivityManager; import org.acra.builder.ReportBuilder; import org.acra.data.CrashReportData; import org.acra.plugins.Plugin; @@ -63,6 +64,10 @@ default boolean shouldSendReport(@NonNull Context context, @NonNull CoreConfigur default void notifyReportDropped(@NonNull Context context, @NonNull CoreConfiguration config) { } + default boolean shouldFinishActivity(@NonNull Context context, @NonNull CoreConfiguration config, LastActivityManager lastActivityManager) { + return true; + } + /** * Determines if the application should be killed * @@ -72,7 +77,8 @@ default void notifyReportDropped(@NonNull Context context, @NonNull CoreConfigur * @param crashReportData the collected report * @return if the application should be killed */ - default boolean shouldKillApplication(@NonNull Context context, @NonNull CoreConfiguration config, @NonNull ReportBuilder reportBuilder, @Nullable CrashReportData crashReportData) { + default boolean shouldKillApplication(@NonNull Context context, @NonNull CoreConfiguration config, @NonNull ReportBuilder reportBuilder, + @Nullable CrashReportData crashReportData) { return true; } } diff --git a/acra-core/src/main/java/org/acra/reporter/ErrorReporterImpl.java b/acra-core/src/main/java/org/acra/reporter/ErrorReporterImpl.java index 30612780da..2e2fa653f5 100644 --- a/acra-core/src/main/java/org/acra/reporter/ErrorReporterImpl.java +++ b/acra-core/src/main/java/org/acra/reporter/ErrorReporterImpl.java @@ -86,7 +86,7 @@ public ErrorReporterImpl(@NonNull Application context, @NonNull CoreConfiguratio schedulerStarter = new SchedulerStarter(context, config); - reportExecutor = new ReportExecutor(context, config, crashReportDataFactory, defaultExceptionHandler, processFinisher, schedulerStarter); + reportExecutor = new ReportExecutor(context, config, crashReportDataFactory, defaultExceptionHandler, processFinisher, schedulerStarter, lastActivityManager); reportExecutor.setEnabled(enabled); // Check for approved reports and send them (if enabled).