Skip to content

Commit

Permalink
Merge pull request #667 from F43nd1r/issue_660
Browse files Browse the repository at this point in the history
Add option to restart last activity after crash
  • Loading branch information
F43nd1r authored May 13, 2018
2 parents 2c77a12 + 7089762 commit fcf3dc2
Show file tree
Hide file tree
Showing 7 changed files with 181 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
@Inherited
@Configuration
public @interface AcraScheduler {
String EXTRA_APP_RESTARTED = "acra.restarted";
/**
* Network constraint for report sending
*
Expand Down Expand Up @@ -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;
}
Original file line number Diff line number Diff line change
@@ -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<? extends Activity> activityClass = (Class<? extends Activity>) 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;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

Expand All @@ -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)
Expand All @@ -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);
}

}

}
Original file line number Diff line number Diff line change
@@ -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;
}
}
21 changes: 18 additions & 3 deletions acra-core/src/main/java/org/acra/builder/ReportExecutor.java
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ public class ReportExecutor {
private final CrashReportDataFactory crashReportDataFactory;
private final List<ReportingAdministrator> 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.
Expand All @@ -74,14 +75,16 @@ 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;
this.defaultExceptionHandler = defaultExceptionHandler;
this.processFinisher = processFinisher;
reportingAdministrators = new PluginLoader(config).load(ReportingAdministrator.class);
this.schedulerStarter = schedulerStarter;
this.lastActivityManager = lastActivityManager;
}

/**
Expand Down Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
*
Expand All @@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down

0 comments on commit fcf3dc2

Please sign in to comment.