From d072cf0b1df7e54dd21dd1cef82f386444a76ac7 Mon Sep 17 00:00:00 2001 From: f43nd1r Date: Fri, 16 Oct 2020 22:31:39 +0200 Subject: [PATCH 01/16] deprecate annotations and improve kotlin dsl configuration --- .../org/acra/annotation/AcraScheduler.java | 1 + .../scheduler/AdvancedSenderScheduler.java | 16 +- .../scheduler/RestartingAdministrator.java | 2 +- acra-core-ktx/src/main/AndroidManifest.xml | 18 -- .../java/org/acra/annotation/AcraCore.java | 8 +- .../attachment/DefaultAttachmentProvider.java | 2 +- .../java/org/acra/builder/ReportExecutor.java | 4 +- .../collector/BaseReportFieldCollector.java | 2 +- .../collector/ConfigurationCollector.java | 2 +- .../org/acra/collector/DropBoxCollector.java | 6 +- .../org/acra/collector/LogCatCollector.java | 7 +- .../org/acra/collector/LogFileCollector.java | 4 +- .../acra/collector/ReflectionCollector.java | 2 +- .../org/acra/collector/SettingsCollector.java | 2 +- .../collector/SharedPreferencesCollector.java | 4 +- .../org/acra/collector/TimeCollector.java | 2 +- .../config/BaseCoreConfigurationBuilder.java | 4 +- .../java/org/acra/config/ConfigUtils.java | 4 +- .../org/acra/config/KtCoreConfiguration.kt | 284 ++++++++++++++++++ .../java/org/acra/data/CrashReportData.java | 3 +- .../org/acra/data/CrashReportDataFactory.java | 4 +- .../main/java/org/acra/data/StringFormat.java | 7 +- .../ReportInteractionExecutor.java | 2 +- .../src/main/java/org/acra/ktx}/Extensions.kt | 0 .../acra/prefs/SharedPreferencesFactory.java | 4 +- .../org/acra/scheduler/SchedulerStarter.java | 2 +- .../org/acra/sender/ReportDistributor.java | 4 +- .../org/acra/sender/SendingConductor.java | 25 +- .../startup/StartupProcessorExecutor.java | 2 +- .../startup/UnapprovedStartupProcessor.java | 11 +- .../util/ApplicationStartupProcessor.java | 2 +- .../java/org/acra/util/ProcessFinisher.java | 2 +- .../java/org/acra/annotation/AcraDialog.java | 1 + .../org/acra/dialog/CrashReportDialog.java | 24 +- .../acra/interaction/DialogInteraction.java | 2 +- .../org/acra/annotation/AcraHttpSender.java | 5 +- .../java/org/acra/http/BaseHttpRequest.java | 9 +- .../org/acra/security/KeyStoreHelper.java | 8 +- .../main/java/org/acra/sender/HttpSender.java | 22 +- .../java/org/acra/annotation/AcraLimiter.java | 1 + .../config/LimitingReportAdministrator.java | 14 +- .../acra/startup/LimiterStartupProcessor.java | 6 +- .../org/acra/annotation/AcraMailSender.java | 3 +- .../org/acra/sender/EmailIntentSender.java | 22 +- .../org/acra/annotation/AcraNotification.java | 1 + .../interaction/NotificationInteraction.java | 45 +-- .../java/org/acra/annotation/AcraToast.java | 1 + .../acra/interaction/ToastInteraction.java | 4 +- annotationprocessor/build.gradle.kts | 3 +- .../processor/AcraAnnotationProcessor.java | 75 ----- .../acra/processor/AcraAnnotationProcessor.kt | 59 ++++ .../processor/creator/BuildMethodCreator.java | 126 -------- .../processor/creator/BuildMethodCreator.kt | 81 +++++ .../acra/processor/creator/ClassCreator.java | 156 ---------- .../acra/processor/creator/ClassCreator.kt | 128 ++++++++ .../acra/processor/creator/ModelBuilder.java | 126 -------- .../acra/processor/creator/ModelBuilder.kt | 118 ++++++++ .../processor/element/AbstractElement.java | 56 ---- .../{Element.java => AbstractElement.kt} | 19 +- .../processor/element/AnnotationField.java | 181 ----------- .../acra/processor/element/AnnotationField.kt | 132 ++++++++ .../processor/element/BuilderElement.java | 153 ---------- .../acra/processor/element/BuilderElement.kt | 107 +++++++ .../acra/processor/element/ConfigElement.java | 56 ---- .../acra/processor/element/ConfigElement.kt | 35 +++ .../processor/element/DelegateMethod.java | 93 ------ .../acra/processor/element/DelegateMethod.kt | 77 +++++ .../org/acra/processor/element/Element.kt | 24 +- .../processor/element/ElementFactory.java | 106 ------- .../acra/processor/element/ElementFactory.kt | 102 +++++++ ...{PreBuildMethod.java => PreBuildMethod.kt} | 24 +- .../processor/element/TransformedField.java | 89 ------ .../processor/element/TransformedField.kt | 53 ++++ ...idatedElement.java => ValidatedElement.kt} | 13 +- .../util/IsValidResourceVisitor.java | 15 - .../processor/util/IsValidResourceVisitor.kt | 13 + .../java/org/acra/processor/util/Strings.java | 77 ----- .../java/org/acra/processor/util/Strings.kt | 68 +++++ .../processor/util/ToCodeBlockVisitor.java | 78 ----- .../acra/processor/util/ToCodeBlockVisitor.kt | 50 +++ .../java/org/acra/processor/util/Types.java | 115 ------- .../java/org/acra/processor/util/Types.kt | 104 +++++++ .../java/org/acra/processor/util/utils.kt | 67 +++++ .../org/acra/annotation/Instantiatable.java | 2 +- buildSrc/build.gradle.kts | 2 + .../kotlin/acra-android-library.gradle.kts | 12 +- .../main/kotlin/acra-application.gradle.kts | 6 +- buildSrc/src/main/kotlin/acra-base.gradle.kts | 2 +- .../main/kotlin/acra-java-library.gradle.kts | 2 + gradle.properties | 6 +- settings.gradle.kts | 1 - 91 files changed, 1683 insertions(+), 1739 deletions(-) delete mode 100644 acra-core-ktx/src/main/AndroidManifest.xml create mode 100644 acra-core/src/main/java/org/acra/config/KtCoreConfiguration.kt rename {acra-core-ktx/src/main/java/org.acra.ktx => acra-core/src/main/java/org/acra/ktx}/Extensions.kt (100%) delete mode 100644 annotationprocessor/src/main/java/org/acra/processor/AcraAnnotationProcessor.java create mode 100644 annotationprocessor/src/main/java/org/acra/processor/AcraAnnotationProcessor.kt delete mode 100644 annotationprocessor/src/main/java/org/acra/processor/creator/BuildMethodCreator.java create mode 100644 annotationprocessor/src/main/java/org/acra/processor/creator/BuildMethodCreator.kt delete mode 100644 annotationprocessor/src/main/java/org/acra/processor/creator/ClassCreator.java create mode 100644 annotationprocessor/src/main/java/org/acra/processor/creator/ClassCreator.kt delete mode 100644 annotationprocessor/src/main/java/org/acra/processor/creator/ModelBuilder.java create mode 100644 annotationprocessor/src/main/java/org/acra/processor/creator/ModelBuilder.kt delete mode 100644 annotationprocessor/src/main/java/org/acra/processor/element/AbstractElement.java rename annotationprocessor/src/main/java/org/acra/processor/element/{Element.java => AbstractElement.kt} (70%) delete mode 100644 annotationprocessor/src/main/java/org/acra/processor/element/AnnotationField.java create mode 100644 annotationprocessor/src/main/java/org/acra/processor/element/AnnotationField.kt delete mode 100644 annotationprocessor/src/main/java/org/acra/processor/element/BuilderElement.java create mode 100644 annotationprocessor/src/main/java/org/acra/processor/element/BuilderElement.kt delete mode 100644 annotationprocessor/src/main/java/org/acra/processor/element/ConfigElement.java create mode 100644 annotationprocessor/src/main/java/org/acra/processor/element/ConfigElement.kt delete mode 100644 annotationprocessor/src/main/java/org/acra/processor/element/DelegateMethod.java create mode 100644 annotationprocessor/src/main/java/org/acra/processor/element/DelegateMethod.kt rename acra-core-ktx/build.gradle.kts => annotationprocessor/src/main/java/org/acra/processor/element/Element.kt (64%) delete mode 100644 annotationprocessor/src/main/java/org/acra/processor/element/ElementFactory.java create mode 100644 annotationprocessor/src/main/java/org/acra/processor/element/ElementFactory.kt rename annotationprocessor/src/main/java/org/acra/processor/element/{PreBuildMethod.java => PreBuildMethod.kt} (58%) delete mode 100644 annotationprocessor/src/main/java/org/acra/processor/element/TransformedField.java create mode 100644 annotationprocessor/src/main/java/org/acra/processor/element/TransformedField.kt rename annotationprocessor/src/main/java/org/acra/processor/element/{ValidatedElement.java => ValidatedElement.kt} (72%) delete mode 100644 annotationprocessor/src/main/java/org/acra/processor/util/IsValidResourceVisitor.java create mode 100644 annotationprocessor/src/main/java/org/acra/processor/util/IsValidResourceVisitor.kt delete mode 100644 annotationprocessor/src/main/java/org/acra/processor/util/Strings.java create mode 100644 annotationprocessor/src/main/java/org/acra/processor/util/Strings.kt delete mode 100644 annotationprocessor/src/main/java/org/acra/processor/util/ToCodeBlockVisitor.java create mode 100644 annotationprocessor/src/main/java/org/acra/processor/util/ToCodeBlockVisitor.kt delete mode 100644 annotationprocessor/src/main/java/org/acra/processor/util/Types.java create mode 100644 annotationprocessor/src/main/java/org/acra/processor/util/Types.kt create mode 100644 annotationprocessor/src/main/java/org/acra/processor/util/utils.kt 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 8692bf347d..efb443c984 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 @@ -24,6 +24,7 @@ * @author F43nd1r * @since 18.04.18 */ +@Deprecated @Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) 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 2817f527a9..bbedc5e553 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 @@ -42,15 +42,15 @@ private AdvancedSenderScheduler(@NonNull Context context, @NonNull CoreConfigura @Override protected void configureJob(@NonNull JobInfo.Builder job) { - job.setRequiredNetworkType(schedulerConfiguration.requiresNetworkType()); - job.setRequiresCharging(schedulerConfiguration.requiresCharging()); - job.setRequiresDeviceIdle(schedulerConfiguration.requiresDeviceIdle()); - boolean constrained = schedulerConfiguration.requiresNetworkType() != JobInfo.NETWORK_TYPE_NONE || - schedulerConfiguration.requiresCharging() || - schedulerConfiguration.requiresDeviceIdle(); + job.setRequiredNetworkType(schedulerConfiguration.getRequiresNetworkType()); + job.setRequiresCharging(schedulerConfiguration.getRequiresCharging()); + job.setRequiresDeviceIdle(schedulerConfiguration.getRequiresDeviceIdle()); + boolean constrained = schedulerConfiguration.getRequiresNetworkType() != JobInfo.NETWORK_TYPE_NONE || + schedulerConfiguration.getRequiresCharging() || + schedulerConfiguration.getRequiresDeviceIdle(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - job.setRequiresBatteryNotLow(schedulerConfiguration.requiresBatteryNotLow()); - constrained |= schedulerConfiguration.requiresBatteryNotLow(); + job.setRequiresBatteryNotLow(schedulerConfiguration.getRequiresBatteryNotLow()); + constrained |= schedulerConfiguration.getRequiresBatteryNotLow(); } if (!constrained) { job.setOverrideDeadline(0); 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 index 979f991f75..380abfd592 100644 --- a/acra-advanced-scheduler/src/main/java/org/acra/scheduler/RestartingAdministrator.java +++ b/acra-advanced-scheduler/src/main/java/org/acra/scheduler/RestartingAdministrator.java @@ -48,7 +48,7 @@ public RestartingAdministrator() { @Override public boolean shouldFinishActivity(@NonNull Context context, @NonNull CoreConfiguration config, LastActivityManager lastActivityManager) { if (ACRA.DEV_LOGGING) ACRA.log.d(ACRA.LOG_TAG, "RestartingAdministrator entry"); - if (ConfigUtils.getPluginConfiguration(config, SchedulerConfiguration.class).restartAfterCrash()) { + if (ConfigUtils.getPluginConfiguration(config, SchedulerConfiguration.class).getRestartAfterCrash()) { Activity activity = lastActivityManager.getLastActivity(); if (activity != null) { if (ACRA.DEV_LOGGING) ACRA.log.d(ACRA.LOG_TAG, "Try to schedule last activity (" + activity.getClass().getName() + ") for restart"); diff --git a/acra-core-ktx/src/main/AndroidManifest.xml b/acra-core-ktx/src/main/AndroidManifest.xml deleted file mode 100644 index c1d91b86a7..0000000000 --- a/acra-core-ktx/src/main/AndroidManifest.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/annotation/AcraCore.java b/acra-core/src/main/java/org/acra/annotation/AcraCore.java index 731f824692..5a0f657463 100644 --- a/acra-core/src/main/java/org/acra/annotation/AcraCore.java +++ b/acra-core/src/main/java/org/acra/annotation/AcraCore.java @@ -37,6 +37,7 @@ * @author F43nd1r * @since 01.06.2017 */ +@Deprecated @Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @@ -227,13 +228,6 @@ */ @NonNull Class buildConfigClass() default Object.class; - /** - * @return {@link org.acra.sender.ReportSenderFactory}s with which to construct the {@link org.acra.sender.ReportSender}s that will send the crash reports. - * @deprecated register with plugin loading instead - */ - @Deprecated - @Instantiatable @NonNull Class[] reportSenderFactoryClasses() default {}; - /** * To use in combination with {@link ReportField#APPLICATION_LOG} to set the path/name of your application log file. * diff --git a/acra-core/src/main/java/org/acra/attachment/DefaultAttachmentProvider.java b/acra-core/src/main/java/org/acra/attachment/DefaultAttachmentProvider.java index e4db2d473d..755c440be2 100644 --- a/acra-core/src/main/java/org/acra/attachment/DefaultAttachmentProvider.java +++ b/acra-core/src/main/java/org/acra/attachment/DefaultAttachmentProvider.java @@ -44,7 +44,7 @@ public class DefaultAttachmentProvider implements AttachmentUriProvider { @Override public List getAttachments(@NonNull Context context, @NonNull CoreConfiguration configuration) { final ArrayList result = new ArrayList<>(); - for (String s : configuration.attachmentUris()) { + for (String s : configuration.getAttachmentUris()) { try { result.add(Uri.parse(s)); } catch (Exception e) { 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 9a9120813e..9d7eca6cdf 100644 --- a/acra-core/src/main/java/org/acra/builder/ReportExecutor.java +++ b/acra-core/src/main/java/org/acra/builder/ReportExecutor.java @@ -84,7 +84,7 @@ public ReportExecutor(@NonNull Context context, @NonNull CoreConfiguration confi this.crashReportDataFactory = crashReportDataFactory; this.defaultExceptionHandler = defaultExceptionHandler; this.processFinisher = processFinisher; - reportingAdministrators = config.pluginLoader().loadEnabled(config, ReportingAdministrator.class); + reportingAdministrators = config.getPluginLoader().loadEnabled(config, ReportingAdministrator.class); this.schedulerStarter = schedulerStarter; this.lastActivityManager = lastActivityManager; } @@ -226,7 +226,7 @@ public final void execute(@NonNull final ReportBuilder reportBuilder) { * End the application. */ private void endApplication(@Nullable Thread uncaughtExceptionThread, Throwable th) { - final boolean letDefaultHandlerEndApplication = config.alsoReportToAndroidFramework(); + final boolean letDefaultHandlerEndApplication = config.getAlsoReportToAndroidFramework(); final boolean handlingUncaughtException = uncaughtExceptionThread != null; if (handlingUncaughtException && letDefaultHandlerEndApplication && defaultExceptionHandler != null) { diff --git a/acra-core/src/main/java/org/acra/collector/BaseReportFieldCollector.java b/acra-core/src/main/java/org/acra/collector/BaseReportFieldCollector.java index 25cc444202..fa0ed1bd27 100644 --- a/acra-core/src/main/java/org/acra/collector/BaseReportFieldCollector.java +++ b/acra-core/src/main/java/org/acra/collector/BaseReportFieldCollector.java @@ -58,7 +58,7 @@ abstract class BaseReportFieldCollector implements Collector { * @return if this field should be collected now */ boolean shouldCollect(@NonNull Context context, @NonNull CoreConfiguration config, @NonNull ReportField collect, @NonNull ReportBuilder reportBuilder) { - return config.reportContent().contains(collect); + return config.getReportContent().contains(collect); } /** diff --git a/acra-core/src/main/java/org/acra/collector/ConfigurationCollector.java b/acra-core/src/main/java/org/acra/collector/ConfigurationCollector.java index b490d2eb30..6fb61dc28f 100644 --- a/acra-core/src/main/java/org/acra/collector/ConfigurationCollector.java +++ b/acra-core/src/main/java/org/acra/collector/ConfigurationCollector.java @@ -89,7 +89,7 @@ void collect(@NonNull ReportField reportField, @NonNull Context context, @NonNul */ @Override public void collectApplicationStartUp(@NonNull Context context, @NonNull CoreConfiguration config) { - if(config.reportContent().contains(ReportField.INITIAL_CONFIGURATION)) { + if(config.getReportContent().contains(ReportField.INITIAL_CONFIGURATION)) { initialConfiguration = collectConfiguration(context); } } diff --git a/acra-core/src/main/java/org/acra/collector/DropBoxCollector.java b/acra-core/src/main/java/org/acra/collector/DropBoxCollector.java index b6b723c2d4..6d6c7e30f9 100644 --- a/acra-core/src/main/java/org/acra/collector/DropBoxCollector.java +++ b/acra-core/src/main/java/org/acra/collector/DropBoxCollector.java @@ -75,15 +75,15 @@ void collect(@NonNull ReportField reportField, @NonNull Context context, @NonNul final DropBoxManager dropbox = SystemServices.getDropBoxManager(context); final Calendar calendar = Calendar.getInstance(); - calendar.roll(Calendar.MINUTE, -config.dropboxCollectionMinutes()); + calendar.roll(Calendar.MINUTE, -config.getDropboxCollectionMinutes()); final long time = calendar.getTimeInMillis(); dateFormat.format(calendar.getTime()); final List tags = new ArrayList<>(); - if (config.includeDropBoxSystemTags()) { + if (config.getIncludeDropBoxSystemTags()) { tags.addAll(Arrays.asList(SYSTEM_TAGS)); } - final List additionalTags = config.additionalDropBoxTags(); + final List additionalTags = Arrays.asList(config.getAdditionalDropBoxTags()); if (!additionalTags.isEmpty()) { tags.addAll(additionalTags); } diff --git a/acra-core/src/main/java/org/acra/collector/LogCatCollector.java b/acra-core/src/main/java/org/acra/collector/LogCatCollector.java index a680bad814..d7af7184a5 100644 --- a/acra-core/src/main/java/org/acra/collector/LogCatCollector.java +++ b/acra-core/src/main/java/org/acra/collector/LogCatCollector.java @@ -34,6 +34,7 @@ import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import static org.acra.ACRA.LOG_TAG; @@ -69,7 +70,7 @@ public Order getOrder() { private String collectLogCat(@NonNull CoreConfiguration config, @Nullable String bufferName) throws IOException { final int myPid = android.os.Process.myPid(); // no need to filter on jellybean onwards, android does that anyway - final String myPidStr = Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN && config.logcatFilterByPid() && myPid > 0 ? Integer.toString(myPid) + "):" : null; + final String myPidStr = Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN && config.getLogcatFilterByPid() && myPid > 0 ? myPid + "):" : null; final List commandLine = new ArrayList<>(); commandLine.add("logcat"); @@ -79,7 +80,7 @@ private String collectLogCat(@NonNull CoreConfiguration config, @Nullable String } final int tailCount; - final List logcatArgumentsList = config.logcatArguments(); + final List logcatArgumentsList = Arrays.asList(config.getLogcatArguments()); final int tailIndex = logcatArgumentsList.indexOf("-t"); if (tailIndex > -1 && tailIndex < logcatArgumentsList.size()) { @@ -135,7 +136,7 @@ void collect(@NonNull ReportField reportField, @NonNull Context context, @NonNul @NonNull private String streamToString(@NonNull CoreConfiguration config, @NonNull InputStream input, @Nullable Predicate filter, int limit) throws IOException { final StreamReader reader = new StreamReader(input).setFilter(filter).setLimit(limit); - if (config.logcatReadNonBlocking()) { + if (config.getLogcatReadNonBlocking()) { reader.setTimeout(READ_TIMEOUT); } return reader.read(); diff --git a/acra-core/src/main/java/org/acra/collector/LogFileCollector.java b/acra-core/src/main/java/org/acra/collector/LogFileCollector.java index 09c28072ff..04f6dea85d 100644 --- a/acra-core/src/main/java/org/acra/collector/LogFileCollector.java +++ b/acra-core/src/main/java/org/acra/collector/LogFileCollector.java @@ -49,7 +49,7 @@ public Order getOrder() { @Override void collect(@NonNull ReportField reportField, @NonNull Context context, @NonNull CoreConfiguration config, @NonNull ReportBuilder reportBuilder, @NonNull CrashReportData target) throws IOException { - target.put(ReportField.APPLICATION_LOG, new StreamReader(config.applicationLogFileDir().getFile(context, config.applicationLogFile())) - .setLimit(config.applicationLogFileLines()).read()); + target.put(ReportField.APPLICATION_LOG, new StreamReader(config.getApplicationLogFileDir().getFile(context, config.getApplicationLogFile())) + .setLimit(config.getApplicationLogFileLines()).read()); } } diff --git a/acra-core/src/main/java/org/acra/collector/ReflectionCollector.java b/acra-core/src/main/java/org/acra/collector/ReflectionCollector.java index 394ffd4f80..c1018b794f 100644 --- a/acra-core/src/main/java/org/acra/collector/ReflectionCollector.java +++ b/acra-core/src/main/java/org/acra/collector/ReflectionCollector.java @@ -131,7 +131,7 @@ private void collectStaticGettersResults(@NonNull Class someClass, @NonNull J */ @NonNull private Class getBuildConfigClass(@NonNull Context context, @NonNull CoreConfiguration config) throws ClassNotFoundException { - final Class configuredBuildConfig = config.buildConfigClass(); + final Class configuredBuildConfig = config.getBuildConfigClass(); if (!configuredBuildConfig.equals(Object.class)) { // If set via annotations or programmatically then it will have a real value, // otherwise it will be Object.class (default). diff --git a/acra-core/src/main/java/org/acra/collector/SettingsCollector.java b/acra-core/src/main/java/org/acra/collector/SettingsCollector.java index 0db4c5768f..3c7b52e5a3 100644 --- a/acra-core/src/main/java/org/acra/collector/SettingsCollector.java +++ b/acra-core/src/main/java/org/acra/collector/SettingsCollector.java @@ -96,7 +96,7 @@ private boolean isAuthorized(@NonNull CoreConfiguration config, @Nullable Field if (key == null || key.getName().startsWith("WIFI_AP")) { return false; } - for (String regex : config.excludeMatchingSettingsKeys()) { + for (String regex : config.getExcludeMatchingSettingsKeys()) { if (key.getName().matches(regex)) { return false; } diff --git a/acra-core/src/main/java/org/acra/collector/SharedPreferencesCollector.java b/acra-core/src/main/java/org/acra/collector/SharedPreferencesCollector.java index 3c2fc41310..414f5b3c81 100644 --- a/acra-core/src/main/java/org/acra/collector/SharedPreferencesCollector.java +++ b/acra-core/src/main/java/org/acra/collector/SharedPreferencesCollector.java @@ -80,7 +80,7 @@ private JSONObject collect(@NonNull Context context, @NonNull CoreConfiguration sharedPrefs.put("default", PreferenceManager.getDefaultSharedPreferences(context)); // Add in any additional SharedPreferences - for (final String sharedPrefId : config.additionalSharedPreferences()) { + for (final String sharedPrefId : config.getAdditionalSharedPreferences()) { sharedPrefs.put(sharedPrefId, context.getSharedPreferences(sharedPrefId, Context.MODE_PRIVATE)); } @@ -115,7 +115,7 @@ private JSONObject collect(@NonNull Context context, @NonNull CoreConfiguration * @return true if the key has to be excluded from reports. */ private boolean filteredKey(@NonNull CoreConfiguration config, @NonNull String key) { - for (String regex : config.excludeMatchingSharedPreferencesKeys()) { + for (String regex : config.getExcludeMatchingSharedPreferencesKeys()) { if (key.matches(regex)) { return true; } diff --git a/acra-core/src/main/java/org/acra/collector/TimeCollector.java b/acra-core/src/main/java/org/acra/collector/TimeCollector.java index da2f89edf2..582b8a59fb 100644 --- a/acra-core/src/main/java/org/acra/collector/TimeCollector.java +++ b/acra-core/src/main/java/org/acra/collector/TimeCollector.java @@ -66,7 +66,7 @@ void collect(@NonNull ReportField reportField, @NonNull Context context, @NonNul @Override public void collectApplicationStartUp(@NonNull Context context, @NonNull CoreConfiguration config) { - if (config.reportContent().contains(ReportField.USER_APP_START_DATE)) { + if (config.getReportContent().contains(ReportField.USER_APP_START_DATE)) { appStartDate = new GregorianCalendar(); } } diff --git a/acra-core/src/main/java/org/acra/config/BaseCoreConfigurationBuilder.java b/acra-core/src/main/java/org/acra/config/BaseCoreConfigurationBuilder.java index 51e9950788..95f500b233 100644 --- a/acra-core/src/main/java/org/acra/config/BaseCoreConfigurationBuilder.java +++ b/acra-core/src/main/java/org/acra/config/BaseCoreConfigurationBuilder.java @@ -93,8 +93,8 @@ void preBuild() throws ACRAConfigurationException { @NonNull @Transform(methodName = "reportContent") - Set transformReportContent(@NonNull ReportField[] reportFields) { - final Set reportContent = new LinkedHashSet<>(); + List transformReportContent(@NonNull ReportField[] reportFields) { + final List reportContent = new ArrayList<>(); if (reportFields.length != 0) { if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "Using custom Report Fields"); reportContent.addAll(Arrays.asList(reportFields)); diff --git a/acra-core/src/main/java/org/acra/config/ConfigUtils.java b/acra-core/src/main/java/org/acra/config/ConfigUtils.java index 7180017735..4d4589e1a2 100644 --- a/acra-core/src/main/java/org/acra/config/ConfigUtils.java +++ b/acra-core/src/main/java/org/acra/config/ConfigUtils.java @@ -29,8 +29,8 @@ public final class ConfigUtils { @NonNull public static T getPluginConfiguration(@NonNull CoreConfiguration config, @NonNull Class c) { - if (ACRA.DEV_LOGGING) ACRA.log.d(ACRA.LOG_TAG, "Checking plugin Configurations : " + config.pluginConfigurations() + " for class : " + c); - for (Configuration configuration : config.pluginConfigurations()) { + if (ACRA.DEV_LOGGING) ACRA.log.d(ACRA.LOG_TAG, "Checking plugin Configurations : " + config.getPluginConfigurations() + " for class : " + c); + for (Configuration configuration : config.getPluginConfigurations()) { if (ACRA.DEV_LOGGING) ACRA.log.d(ACRA.LOG_TAG, "Checking plugin Configuration : " + configuration + " against plugin class : " + c); if (c.isAssignableFrom(configuration.getClass())) { //noinspection unchecked diff --git a/acra-core/src/main/java/org/acra/config/KtCoreConfiguration.kt b/acra-core/src/main/java/org/acra/config/KtCoreConfiguration.kt new file mode 100644 index 0000000000..995c79b17c --- /dev/null +++ b/acra-core/src/main/java/org/acra/config/KtCoreConfiguration.kt @@ -0,0 +1,284 @@ +package org.acra.config + +import androidx.annotation.StringRes +import org.acra.ACRAConstants +import org.acra.ReportField +import org.acra.annotation.Instantiatable +import org.acra.attachment.AttachmentUriProvider +import org.acra.attachment.DefaultAttachmentProvider +import org.acra.data.StringFormat +import org.acra.file.Directory + +data class KtCoreConfiguration( + + /** + * Name of the SharedPreferences that will host ACRA settings which you can make accessible to your users through a preferences screen: + *
    + *
  • {@link org.acra.ACRA#PREF_DISABLE_ACRA} or {@link org.acra.ACRA#PREF_ENABLE_ACRA}
  • + *
  • {@link org.acra.ACRA#PREF_ALWAYS_ACCEPT}
  • + *
  • {@link org.acra.ACRA#PREF_ENABLE_DEVICE_ID}
  • + *
  • {@link org.acra.ACRA#PREF_ENABLE_SYSTEM_LOGS}
  • + *
+ * Default is to use the application default SharedPreferences, as retrieved with {@link android.preference.PreferenceManager#getDefaultSharedPreferences(android.content.Context)} + * + * @return SharedPreferences name. + */ + val sharedPreferencesName: String = ACRAConstants.DEFAULT_STRING_VALUE, + + /** + * If enabled, DropBox events collection will include system tags: + *
    + *
  • system_app_anr
  • + *
  • system_app_wtf
  • + *
  • system_app_crash
  • + *
  • system_server_anr
  • + *
  • system_server_wtf
  • + *
  • system_server_crash
  • + *
  • BATTERY_DISCHARGE_INFO
  • + *
  • SYSTEM_RECOVERY_LOG
  • + *
  • SYSTEM_BOOT
  • + *
  • SYSTEM_LAST_KMSG
  • + *
  • APANIC_CONSOLE
  • + *
  • APANIC_THREADS
  • + *
  • SYSTEM_RESTART
  • + *
  • SYSTEM_TOMBSTONE
  • + *
  • data_app_strictmode
  • + *
+ * + * @return if system tags are to be included as part of DropBox events. + */ + val includeDropBoxSystemTags: Boolean = false, + + /** + * Custom tags to be included in DropBox event collection + * + * @return tags that you want to be fetched when collecting DropBox events. + */ + val additionalDropBoxTags: List, + + /** + * DropBox event collection will look back this many minutes + * + * @return Number of minutes to look back. + */ + val dropboxCollectionMinutes: Int = 5, + + /** + *

+ * Arguments to be passed to the logcat command line. Default is { "-t", "100", "-v", "time" } for: + *

+ *
logcat -t 100 -v time
+ *

+ * Do not include -b arguments for buffer selection, include {@link ReportField#EVENTSLOG} and {@link ReportField#RADIOLOG} in {@link #reportContent()} to activate alternative logcat buffers reporting. + * They will use the same other arguments as those provided here. + *

+ *

+ * See Listing of logcat Command Options. + *

+ * + * @return arguments to supply if retrieving the log as part of the report. + */ + val logcatArguments: List = listOf("-t", "" + ACRAConstants.DEFAULT_LOG_LINES, "-v", "time"), + + /** + *

+ * Redefines the list of {@link ReportField}s collected and sent in your reports. + *

+ *

+ * You can also use this property to modify fields order in your reports. + *

+ *

+ * The default list is {@link org.acra.ACRAConstants#DEFAULT_REPORT_FIELDS} + * + * @return fields to be included in the report. + */ + val reportContent: List = emptyList(), + + /** + * Controls whether unapproved reports are deleted on application start or not. + *

+ * Silent and Toast reports are automatically approved. + * Dialog and Notification reports require explicit approval by the user before they are sent. + *

+ *

+ * On application restart the user is prompted with approval for one unsent report. + * So you generally don't want to accumulate unapproved reports, otherwise you will prompt them multiple times. + *

+ *

+ * If this is set to true then all unapproved reports bar one will be deleted on application start. + * The last report is always retained because that is the report that probably just happened. + *

+ * + * @return if ACRA should delete unapproved reports on application start. + */ + val deleteUnapprovedReportsOnApplicationStart: Boolean = true, + + /** + * Set this to true if you prefer displaying the native force close dialog after ACRA is done. + * Recommended: Keep this set to false if using interactions with user input. + * + * @return if the native force close dialog should be displayed. + */ + val alsoReportToAndroidFramework: Boolean = false, + + /** + * Add here your {@link android.content.SharedPreferences} identifier Strings if you use others than your application's default. They will be added to the {@link ReportField#SHARED_PREFERENCES} field. + * + * @return names of additional preferences. + */ + val additionalSharedPreferences: List = emptyList(), + + /** + * Set this to true if you want to include only logcat lines related to your Application process. Note that this is always done by android starting with API 16 (Jellybean) + * + * @return true if you want to filter logcat with your process id. + */ + val logcatFilterByPid: Boolean = true, + + /** + * Set this to true if you want to read logcat lines in a non blocking way for your thread. It has a default timeout of 3 seconds. + * + * @return if reading of logcat lines should not block the current thread. + */ + val logcatReadNonBlocking: Boolean = false, + + /** + * Set this to false if you want to disable sending reports in development mode. Only signed application packages will send reports. + * + * @return if reports should only be sent from signed packages. + */ + val sendReportsInDevMode: Boolean = true, + + /** + * Provide here regex patterns to be evaluated on each {@link android.content.SharedPreferences} key to exclude KV pairs from the collected SharedPreferences. + * This allows you to exclude sensitive user data like passwords from being collected. + * + * If you only want to include some keys, you may use regular expressions to do so: + * + * + * + *
only keys foo and bar
"^(?!foo|bar).*$"
only keys containing foo and bar
"^((?!foo|bar).)*$"
+ * + * @return regex patterns, every matching key is not collected. + */ + val excludeMatchingSharedPreferencesKeys: List = emptyList(), + + /** + * Provide here regex patterns to be evaluated on each {@link android.provider.Settings.System}, {@link android.provider.Settings.Secure} and {@link android.provider.Settings.Global} key to exclude KV pairs from being collected. + * This allows you to exclude sensitive data from being collected. + * + * If you only want to include some keys, you may use regular expressions to do so: + * + * + * + *
only keys foo and bar
"^(?!foo|bar).*$"
only keys containing foo and bar
"^((?!foo|bar).)*$"
+ * + * @return regex patterns, every matching key is not collected. + */ + val excludeMatchingSettingsKeys: List = emptyList(), + + /** + * The default value will be a BuildConfig class residing in the same package as the Application class. + * You only have to set this option if your BuildConfig class is obfuscated. + * + * @return BuildConfig class from which to read any BuildConfig attributes. + */ + val buildConfigClass: Class<*> = Object::class.java, + + /** + * To use in combination with {@link ReportField#APPLICATION_LOG} to set the path/name of your application log file. + * + * @return path/name of your application log file. + */ + val applicationLogFile: String = ACRAConstants.DEFAULT_STRING_VALUE, + + /** + * To use in combination with {@link ReportField#APPLICATION_LOG} to set the number of latest lines of your application log file to be collected. + * Default value is 100. + * + * @return number of lines to collect. + */ + val applicationLogFileLines: Int = ACRAConstants.DEFAULT_LOG_LINES, + + /** + * To use in combination with {@link ReportField#APPLICATION_LOG} to set the root for the path provided in {@link #applicationLogFile()} + * + * @return the directory of the application log file + */ + val applicationLogFileDir: Directory = Directory.FILES_LEGACY, + + /** + * Implement a custom {@link RetryPolicy} to decide if a failed report should be resent or not. + * + * @return a class that decides if a report should be resent (usually if one or more senders failed). + * @since 4.9.1 + */ + @Instantiatable val retryPolicyClass: Class = DefaultRetryPolicy::class.java, + + /** + * If you have services which might crash on startup android will try to restart them indefinitely. Set this to true to prevent that. + * + * @return if all services running in a process should be stopped before it is killed. + * @since 4.9.2 + */ + val stopServicesOnCrash: Boolean = false, + + /** + * Allows to attach files to crash reports. + *

+ * ACRA contains a file provider under the following Uri: + * content://[applicationId].acra/[Directory]/[Path] + * where [applicationId] is your application package name, [Directory] is one of the enum constants in {@link Directory} in lower case and [Path] is the relative path to the file in that directory + * e.g. content://org.acra.test.acra/files/thisIsATest.txt + *

+ * Side effects: + *
    + *
  • POST mode: requests will be sent with content-type multipart/form-data
  • + *
  • PUT mode: There will be additional requests with the attachments. Naming scheme: [report-id]-[filename]
  • + *
  • EMAIL mode: Some email clients do not support attachments, so some email may lack these attachments. Note that attachments might be readable to email clients when they are sent.
  • + *
+ * + * @return uris to be attached to crash reports. + * @since 4.9.3 + */ + val attachmentUris: List = emptyList(), + + /** + * Allows {@link #attachmentUris()} configuration at runtime instead of compile time. + * + * @return a class that decides which uris should be attached to reports + * @since 4.9.3 + */ + @Instantiatable val attachmentUriProvider: Class = DefaultAttachmentProvider::class.java, + + /** + * Toast shown when a report is sent successfully + * + * @return Resource id for the Toast text triggered when a report was sent successfully. + * @since 5.0.0 + */ + @StringRes val resReportSendSuccessToast: Int = ACRAConstants.DEFAULT_RES_VALUE, + + /** + * Toast shown when report sending fails + * + * @return Resource id for the Toast text triggered when no report was sent successfully. + * @since 5.0.0 + */ + @StringRes val resReportSendFailureToast: Int = ACRAConstants.DEFAULT_RES_VALUE, + + /** + * Format in which the report should be sent + * + * @return report format + * @since 5.0.0 + */ + val reportFormat: StringFormat = StringFormat.JSON, + + /** + * Allow parallel collection. Increases performance but might pollute e.g. logcat output + * @return if parallel collection should be active + * @since 5.0.1 + */ + val parallel: Boolean = true, +) \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/data/CrashReportData.java b/acra-core/src/main/java/org/acra/data/CrashReportData.java index 52c364807c..c933ea3def 100644 --- a/acra-core/src/main/java/org/acra/data/CrashReportData.java +++ b/acra-core/src/main/java/org/acra/data/CrashReportData.java @@ -27,6 +27,7 @@ import org.json.JSONException; import org.json.JSONObject; +import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.Map; @@ -173,7 +174,7 @@ public boolean containsKey(@NonNull ReportField key) { @NonNull public String toJSON() throws JSONException { try { - return StringFormat.JSON.toFormattedString(this, ImmutableSet.empty(), "", "", false); + return StringFormat.JSON.toFormattedString(this, Collections.emptyList(), "", "", false); } catch (JSONException e) { throw e; } catch (Exception e) { diff --git a/acra-core/src/main/java/org/acra/data/CrashReportDataFactory.java b/acra-core/src/main/java/org/acra/data/CrashReportDataFactory.java index 25ce168dd6..8ae092862c 100644 --- a/acra-core/src/main/java/org/acra/data/CrashReportDataFactory.java +++ b/acra-core/src/main/java/org/acra/data/CrashReportDataFactory.java @@ -50,7 +50,7 @@ public final class CrashReportDataFactory { public CrashReportDataFactory(@NonNull Context context, @NonNull CoreConfiguration config) { this.context = context; this.config = config; - collectors = config.pluginLoader().loadEnabled(config, Collector.class); + collectors = config.getPluginLoader().loadEnabled(config, Collector.class); //noinspection Java8ListSort Collections.sort(collectors, (c1, c2) -> { Collector.Order o1; @@ -77,7 +77,7 @@ public CrashReportDataFactory(@NonNull Context context, @NonNull CoreConfigurati */ @NonNull public CrashReportData createCrashData(@NonNull final ReportBuilder builder) { - final ExecutorService executorService = config.parallel() ? Executors.newCachedThreadPool() : Executors.newSingleThreadExecutor(); + final ExecutorService executorService = config.getParallel() ? Executors.newCachedThreadPool() : Executors.newSingleThreadExecutor(); final CrashReportData crashReportData = new CrashReportData(); final List> futures = new ArrayList<>(); for (final Collector collector : collectors) { diff --git a/acra-core/src/main/java/org/acra/data/StringFormat.java b/acra-core/src/main/java/org/acra/data/StringFormat.java index 0100c710bd..ef6c538cde 100644 --- a/acra-core/src/main/java/org/acra/data/StringFormat.java +++ b/acra-core/src/main/java/org/acra/data/StringFormat.java @@ -34,6 +34,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Set; /** * Represents possible report formats @@ -45,7 +46,7 @@ public enum StringFormat { JSON("application/json") { @NonNull @Override - public String toFormattedString(@NonNull CrashReportData data, @NonNull ImmutableSet order, @NonNull String mainJoiner, @NonNull String subJoiner, boolean urlEncode) throws JSONException { + public String toFormattedString(@NonNull CrashReportData data, @NonNull List order, @NonNull String mainJoiner, @NonNull String subJoiner, boolean urlEncode) throws JSONException { final Map map = data.toMap(); final JSONStringer stringer = new JSONStringer().object(); for (ReportField field : order) { @@ -60,7 +61,7 @@ public String toFormattedString(@NonNull CrashReportData data, @NonNull Immutabl KEY_VALUE_LIST("application/x-www-form-urlencoded") { @NonNull @Override - public String toFormattedString(@NonNull CrashReportData data, @NonNull ImmutableSet order, @NonNull String mainJoiner, @NonNull String subJoiner, boolean urlEncode) throws UnsupportedEncodingException { + public String toFormattedString(@NonNull CrashReportData data, @NonNull List order, @NonNull String mainJoiner, @NonNull String subJoiner, boolean urlEncode) throws UnsupportedEncodingException { final Map map = toStringMap(data.toMap(), subJoiner); final StringBuilder builder = new StringBuilder(); for (ReportField field : order) { @@ -130,7 +131,7 @@ private List flatten(@NonNull JSONObject json) { } @NonNull - public abstract String toFormattedString(@NonNull CrashReportData data, @NonNull ImmutableSet order, @NonNull String mainJoiner, @NonNull String subJoiner, boolean urlEncode) throws Exception; + public abstract String toFormattedString(@NonNull CrashReportData data, @NonNull List order, @NonNull String mainJoiner, @NonNull String subJoiner, boolean urlEncode) throws Exception; @NonNull public String getMatchingHttpContentType() { diff --git a/acra-core/src/main/java/org/acra/interaction/ReportInteractionExecutor.java b/acra-core/src/main/java/org/acra/interaction/ReportInteractionExecutor.java index 8c21986335..887aeee835 100644 --- a/acra-core/src/main/java/org/acra/interaction/ReportInteractionExecutor.java +++ b/acra-core/src/main/java/org/acra/interaction/ReportInteractionExecutor.java @@ -44,7 +44,7 @@ public class ReportInteractionExecutor { public ReportInteractionExecutor(@NonNull final Context context, @NonNull final CoreConfiguration config) { this.context = context; this.config = config; - reportInteractions = config.pluginLoader().loadEnabled(config, ReportInteraction.class); + reportInteractions = config.getPluginLoader().loadEnabled(config, ReportInteraction.class); } public boolean hasInteractions() { diff --git a/acra-core-ktx/src/main/java/org.acra.ktx/Extensions.kt b/acra-core/src/main/java/org/acra/ktx/Extensions.kt similarity index 100% rename from acra-core-ktx/src/main/java/org.acra.ktx/Extensions.kt rename to acra-core/src/main/java/org/acra/ktx/Extensions.kt diff --git a/acra-core/src/main/java/org/acra/prefs/SharedPreferencesFactory.java b/acra-core/src/main/java/org/acra/prefs/SharedPreferencesFactory.java index 39d5cbaaf0..49e668b311 100644 --- a/acra-core/src/main/java/org/acra/prefs/SharedPreferencesFactory.java +++ b/acra-core/src/main/java/org/acra/prefs/SharedPreferencesFactory.java @@ -70,8 +70,8 @@ public SharedPreferences create() { //noinspection ConstantConditions if (context == null) { throw new IllegalStateException("Cannot call ACRA.getACRASharedPreferences() before ACRA.init()."); - } else if (!ACRAConstants.DEFAULT_STRING_VALUE.equals(config.sharedPreferencesName())) { - return context.getSharedPreferences(config.sharedPreferencesName(), Context.MODE_PRIVATE); + } else if (!ACRAConstants.DEFAULT_STRING_VALUE.equals(config.getSharedPreferencesName())) { + return context.getSharedPreferences(config.getSharedPreferencesName(), Context.MODE_PRIVATE); } else { return PreferenceManager.getDefaultSharedPreferences(context); } diff --git a/acra-core/src/main/java/org/acra/scheduler/SchedulerStarter.java b/acra-core/src/main/java/org/acra/scheduler/SchedulerStarter.java index b1802a84ef..ff89a55a19 100644 --- a/acra-core/src/main/java/org/acra/scheduler/SchedulerStarter.java +++ b/acra-core/src/main/java/org/acra/scheduler/SchedulerStarter.java @@ -39,7 +39,7 @@ public class SchedulerStarter { public SchedulerStarter(@NonNull Context context, @NonNull CoreConfiguration config) { locator = new ReportLocator(context); - List schedulerFactories = config.pluginLoader().loadEnabled(config, SenderSchedulerFactory.class); + List schedulerFactories = config.getPluginLoader().loadEnabled(config, SenderSchedulerFactory.class); if (schedulerFactories.isEmpty()) { senderScheduler = new DefaultSenderScheduler(context, config); } else { diff --git a/acra-core/src/main/java/org/acra/sender/ReportDistributor.java b/acra-core/src/main/java/org/acra/sender/ReportDistributor.java index 3917410f6b..f1f02ac4ca 100644 --- a/acra-core/src/main/java/org/acra/sender/ReportDistributor.java +++ b/acra-core/src/main/java/org/acra/sender/ReportDistributor.java @@ -107,7 +107,7 @@ public boolean distribute(@NonNull File reportFile) { * @throws ReportSenderException if unable to send the crash report. */ private void sendCrashReport(@NonNull CrashReportData errorContent) throws ReportSenderException { - if (!isDebuggable() || config.sendReportsInDevMode()) { + if (!isDebuggable() || config.getSendReportsInDevMode()) { final List failedSenders = new LinkedList<>(); for (ReportSender sender : reportSenders) { try { @@ -122,7 +122,7 @@ private void sendCrashReport(@NonNull CrashReportData errorContent) throws Repor 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(), DefaultRetryPolicy::new).shouldRetrySend(reportSenders, failedSenders)) { + } else if (instanceCreator.create(config.getRetryPolicyClass(), DefaultRetryPolicy::new).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 { diff --git a/acra-core/src/main/java/org/acra/sender/SendingConductor.java b/acra-core/src/main/java/org/acra/sender/SendingConductor.java index 8a5ceadb5b..d5a74f5f10 100644 --- a/acra-core/src/main/java/org/acra/sender/SendingConductor.java +++ b/acra-core/src/main/java/org/acra/sender/SendingConductor.java @@ -1,11 +1,12 @@ package org.acra.sender; import android.content.Context; -import android.os.Bundle; import android.os.Handler; import android.os.Looper; -import androidx.annotation.NonNull; import android.widget.Toast; + +import androidx.annotation.NonNull; + import org.acra.ACRA; import org.acra.ACRAConstants; import org.acra.config.CoreConfiguration; @@ -13,7 +14,6 @@ import org.acra.file.ReportLocator; import org.acra.plugins.PluginLoader; import org.acra.util.BundleWrapper; -import org.acra.util.InstanceCreator; import org.acra.util.ToastSender; import java.io.File; @@ -72,7 +72,7 @@ public void sendReports(boolean foreground, BundleWrapper extras) { } } final String toast; - if (anyNonSilent && (toast = reportsSentCount > 0 ? config.reportSendSuccessToast() : config.reportSendFailureToast()) != null) { + if (anyNonSilent && !(toast = reportsSentCount > 0 ? config.getReportSendSuccessToast() : config.getReportSendFailureToast()).isEmpty()) { if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "About to show " + (reportsSentCount > 0 ? "success" : "failure") + " toast"); new Handler(Looper.getMainLooper()).post(() -> ToastSender.sendToast(context, toast, Toast.LENGTH_LONG)); } @@ -85,19 +85,10 @@ public void sendReports(boolean foreground, BundleWrapper extras) { @NonNull public List getSenderInstances(boolean foreground) { - List> factoryClasses = config.reportSenderFactoryClasses(); - - if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "config#reportSenderFactoryClasses : " + factoryClasses); - final List factories; - if (factoryClasses.isEmpty()) { - if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "Using PluginLoader to find ReportSender factories"); - final PluginLoader loader = config.pluginLoader(); - factories = loader.loadEnabled(config, ReportSenderFactory.class); - } else { - if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "Creating reportSenderFactories for reportSenderFactory config"); - factories = new InstanceCreator().create(factoryClasses); - } + if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "Using PluginLoader to find ReportSender factories"); + final PluginLoader loader = config.getPluginLoader(); + factories = loader.loadEnabled(config, ReportSenderFactory.class); if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "reportSenderFactories : " + factories); @@ -105,7 +96,7 @@ public List getSenderInstances(boolean foreground) { for (ReportSenderFactory factory : factories) { final ReportSender sender = factory.create(context, config); if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "Adding reportSender : " + sender); - if(foreground == sender.requiresForeground()) { + if (foreground == sender.requiresForeground()) { reportSenders.add(sender); } } diff --git a/acra-core/src/main/java/org/acra/startup/StartupProcessorExecutor.java b/acra-core/src/main/java/org/acra/startup/StartupProcessorExecutor.java index 06a9f6676a..df0ec773d7 100644 --- a/acra-core/src/main/java/org/acra/startup/StartupProcessorExecutor.java +++ b/acra-core/src/main/java/org/acra/startup/StartupProcessorExecutor.java @@ -63,7 +63,7 @@ public void processReports(boolean isAcraEnabled) { for (File r : reportLocator.getApprovedReports()) { reports.add(new Report(r, true)); } - final List startupProcessors = config.pluginLoader().loadEnabled(config, StartupProcessor.class); + final List startupProcessors = config.getPluginLoader().loadEnabled(config, StartupProcessor.class); for (StartupProcessor processor : startupProcessors) { processor.processReports(context, config, reports); } diff --git a/acra-core/src/main/java/org/acra/startup/UnapprovedStartupProcessor.java b/acra-core/src/main/java/org/acra/startup/UnapprovedStartupProcessor.java index 42fa968677..05c6c872c0 100644 --- a/acra-core/src/main/java/org/acra/startup/UnapprovedStartupProcessor.java +++ b/acra-core/src/main/java/org/acra/startup/UnapprovedStartupProcessor.java @@ -17,8 +17,11 @@ package org.acra.startup; import android.content.Context; + import androidx.annotation.NonNull; + import com.google.auto.service.AutoService; + import org.acra.config.CoreConfiguration; import org.acra.file.LastModifiedComparator; @@ -34,7 +37,7 @@ public class UnapprovedStartupProcessor implements StartupProcessor { @Override public void processReports(@NonNull Context context, @NonNull CoreConfiguration config, List reports) { - if (config.deleteUnapprovedReportsOnApplicationStart()) { + if (config.getDeleteUnapprovedReportsOnApplicationStart()) { final List sort = new ArrayList<>(); for (Report report : reports) { if (!report.isApproved()) { @@ -44,10 +47,8 @@ public void processReports(@NonNull Context context, @NonNull CoreConfiguration if (!sort.isEmpty()) { final LastModifiedComparator comparator = new LastModifiedComparator(); Collections.sort(sort, (r1, r2) -> comparator.compare(r1.getFile(), r2.getFile())); - if(config.deleteUnapprovedReportsOnApplicationStart()) { - for (int i = 0; i < sort.size() - 1; i++) { - sort.get(i).delete(); - } + for (int i = 0; i < sort.size() - 1; i++) { + sort.get(i).delete(); } sort.get(sort.size() - 1).approve(); } diff --git a/acra-core/src/main/java/org/acra/util/ApplicationStartupProcessor.java b/acra-core/src/main/java/org/acra/util/ApplicationStartupProcessor.java index 920b4e1e4a..d4cf123961 100644 --- a/acra-core/src/main/java/org/acra/util/ApplicationStartupProcessor.java +++ b/acra-core/src/main/java/org/acra/util/ApplicationStartupProcessor.java @@ -44,7 +44,7 @@ public ApplicationStartupProcessor(@NonNull Context context, @NonNull CoreConfig public void checkReports() { //run it on a background thread because we're doing disk I/O new Thread(() -> { - if (config.deleteOldUnsentReportsOnApplicationStart()) { + if (config.getDeleteOldUnsentReportsOnApplicationStart()) { deleteUnsentReportsFromOldAppVersion(); } }).start(); diff --git a/acra-core/src/main/java/org/acra/util/ProcessFinisher.java b/acra-core/src/main/java/org/acra/util/ProcessFinisher.java index 528ac92ae6..0d6bac6931 100644 --- a/acra-core/src/main/java/org/acra/util/ProcessFinisher.java +++ b/acra-core/src/main/java/org/acra/util/ProcessFinisher.java @@ -81,7 +81,7 @@ public void finishLastActivity(@Nullable Thread uncaughtExceptionThread) { } private void stopServices() { - if (config.stopServicesOnCrash()) { + if (config.getStopServicesOnCrash()) { try { final ActivityManager activityManager = SystemServices.getActivityManager(context); final List runningServices = activityManager.getRunningServices(Integer.MAX_VALUE); diff --git a/acra-dialog/src/main/java/org/acra/annotation/AcraDialog.java b/acra-dialog/src/main/java/org/acra/annotation/AcraDialog.java index d54337589e..c93515f8f8 100644 --- a/acra-dialog/src/main/java/org/acra/annotation/AcraDialog.java +++ b/acra-dialog/src/main/java/org/acra/annotation/AcraDialog.java @@ -33,6 +33,7 @@ * @author F43nd1r * @since 01.06.2017 */ +@Deprecated @Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) diff --git a/acra-dialog/src/main/java/org/acra/dialog/CrashReportDialog.java b/acra-dialog/src/main/java/org/acra/dialog/CrashReportDialog.java index 32dbdc3c65..7e3e57ce0f 100644 --- a/acra-dialog/src/main/java/org/acra/dialog/CrashReportDialog.java +++ b/acra-dialog/src/main/java/org/acra/dialog/CrashReportDialog.java @@ -70,7 +70,7 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { scrollable.setOrientation(LinearLayout.VERTICAL); sharedPreferencesFactory = new SharedPreferencesFactory(getApplicationContext(), helper.getConfig()); dialogConfiguration = ConfigUtils.getPluginConfiguration(helper.getConfig(), DialogConfiguration.class); - final int themeResourceId = dialogConfiguration.resTheme(); + final int themeResourceId = dialogConfiguration.getResTheme(); if (themeResourceId != ACRAConstants.DEFAULT_RES_VALUE) setTheme(themeResourceId); padding = loadPaddingFromTheme(); @@ -87,17 +87,17 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { */ protected void buildAndShowDialog(@Nullable Bundle savedInstanceState) { final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this); - final String title = dialogConfiguration.title(); - if (title != null) { + final String title = dialogConfiguration.getTitle(); + if (!title.isEmpty()) { dialogBuilder.setTitle(title); } - final int iconResourceId = dialogConfiguration.resIcon(); + final int iconResourceId = dialogConfiguration.getResIcon(); if (iconResourceId != ACRAConstants.DEFAULT_RES_VALUE) { dialogBuilder.setIcon(iconResourceId); } dialogBuilder.setView(buildCustomView(savedInstanceState)) - .setPositiveButton(dialogConfiguration.positiveButtonText(), this) - .setNegativeButton(dialogConfiguration.negativeButtonText(), this); + .setPositiveButton(dialogConfiguration.getPositiveButtonText(), this) + .setNegativeButton(dialogConfiguration.getNegativeButtonText(), this); mDialog = dialogBuilder.create(); mDialog.setCanceledOnTouchOutside(false); @@ -160,8 +160,8 @@ protected final void addViewToDialog(@NonNull View v) { @NonNull protected View getMainView() { final TextView text = new TextView(this); - final String dialogText = dialogConfiguration.text(); - if (dialogText != null) { + final String dialogText = dialogConfiguration.getText(); + if (!dialogText.isEmpty()) { text.setText(dialogText); } return text; @@ -174,8 +174,8 @@ protected View getMainView() { */ @Nullable protected View getCommentLabel() { - final String commentPrompt = dialogConfiguration.commentPrompt(); - if (commentPrompt != null) { + final String commentPrompt = dialogConfiguration.getCommentPrompt(); + if (!commentPrompt.isEmpty()) { final TextView labelView = new TextView(this); labelView.setText(commentPrompt); return labelView; @@ -206,8 +206,8 @@ protected EditText getCommentPrompt(@Nullable CharSequence savedComment) { */ @Nullable protected View getEmailLabel() { - final String emailPrompt = dialogConfiguration.emailPrompt(); - if (emailPrompt != null) { + final String emailPrompt = dialogConfiguration.getEmailPrompt(); + if (!emailPrompt.isEmpty()) { final TextView labelView = new TextView(this); labelView.setText(emailPrompt); return labelView; diff --git a/acra-dialog/src/main/java/org/acra/interaction/DialogInteraction.java b/acra-dialog/src/main/java/org/acra/interaction/DialogInteraction.java index d207ae20e8..09f57856e4 100644 --- a/acra-dialog/src/main/java/org/acra/interaction/DialogInteraction.java +++ b/acra-dialog/src/main/java/org/acra/interaction/DialogInteraction.java @@ -81,7 +81,7 @@ public boolean performInteraction(@NonNull Context context, @NonNull CoreConfigu @NonNull private Intent createCrashReportDialogIntent(@NonNull Context context, @NonNull CoreConfiguration config, @NonNull File reportFile) { if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "Creating DialogIntent for " + reportFile); - final Intent dialogIntent = new Intent(context, ConfigUtils.getPluginConfiguration(config, DialogConfiguration.class).reportDialogClass()); + final Intent dialogIntent = new Intent(context, ConfigUtils.getPluginConfiguration(config, DialogConfiguration.class).getReportDialogClass()); dialogIntent.putExtra(EXTRA_REPORT_FILE, reportFile); dialogIntent.putExtra(EXTRA_REPORT_CONFIG, config); return dialogIntent; diff --git a/acra-http/src/main/java/org/acra/annotation/AcraHttpSender.java b/acra-http/src/main/java/org/acra/annotation/AcraHttpSender.java index dba5e6156d..6f6c0131e1 100644 --- a/acra-http/src/main/java/org/acra/annotation/AcraHttpSender.java +++ b/acra-http/src/main/java/org/acra/annotation/AcraHttpSender.java @@ -33,6 +33,7 @@ * @author F43nd1r * @since 01.06.2017 */ +@Deprecated @Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @@ -45,7 +46,7 @@ * @return URI of a server to which to send reports. * @since 5.0.0 */ - @NonNull String uri(); + @NonNull @AnyNonDefault String uri() default ACRAConstants.NULL_VALUE; /** * you can set here and in {@link org.acra.annotation.AcraHttpSender#basicAuthPassword()} the credentials for a BASIC HTTP authentication. @@ -71,7 +72,7 @@ * @return HTTP method used when posting reports. * @since 5.0.0 */ - @NonNull HttpSender.Method httpMethod(); + @NonNull HttpSender.Method httpMethod() default HttpSender.Method.POST; /** * timeout for server connection diff --git a/acra-http/src/main/java/org/acra/http/BaseHttpRequest.java b/acra-http/src/main/java/org/acra/http/BaseHttpRequest.java index 142f6131d9..6e9e4d40d7 100644 --- a/acra-http/src/main/java/org/acra/http/BaseHttpRequest.java +++ b/acra-http/src/main/java/org/acra/http/BaseHttpRequest.java @@ -42,6 +42,7 @@ import java.net.URL; import java.security.GeneralSecurityException; import java.security.KeyStore; +import java.util.Arrays; import java.util.Map; import java.util.zip.GZIPOutputStream; @@ -108,7 +109,7 @@ public void send(@NonNull URL url, @NonNull T content) throws IOException { handleResponse(urlConnection.getResponseCode(), urlConnection.getResponseMessage()); urlConnection.disconnect(); } catch (SocketTimeoutException e) { - if (senderConfiguration.dropReportsOnTimeout()) { + if (senderConfiguration.getDropReportsOnTimeout()) { Log.w(ACRA.LOG_TAG, "Dropped report due to timeout"); } else { throw e; @@ -134,7 +135,7 @@ protected void configureHttps(@NonNull HttpsURLConnection connection) throws Gen final SSLContext sslContext = SSLContext.getInstance("TLS"); sslContext.init(null, tmf.getTrustManagers(), null); - connection.setSSLSocketFactory(new ProtocolSocketFactoryWrapper(sslContext.getSocketFactory(), senderConfiguration.tlsProtocols())); + connection.setSSLSocketFactory(new ProtocolSocketFactoryWrapper(sslContext.getSocketFactory(), Arrays.asList(senderConfiguration.getTlsProtocols()))); } @SuppressWarnings("WeakerAccess") @@ -159,7 +160,7 @@ protected void configureHeaders(@NonNull HttpURLConnection connection, @Nullable connection.setRequestProperty("Authorization", "Basic " + encoded); } - if (senderConfiguration.compress()) { + if (senderConfiguration.getCompress()) { connection.setRequestProperty("Content-Encoding", "gzip"); } @@ -184,7 +185,7 @@ protected void writeContent(@NonNull HttpURLConnection connection, @NonNull Meth connection.connect(); - final OutputStream outputStream = senderConfiguration.compress() ? new GZIPOutputStream(connection.getOutputStream(), ACRAConstants.DEFAULT_BUFFER_SIZE_IN_BYTES) + final OutputStream outputStream = senderConfiguration.getCompress() ? new GZIPOutputStream(connection.getOutputStream(), ACRAConstants.DEFAULT_BUFFER_SIZE_IN_BYTES) : new BufferedOutputStream(connection.getOutputStream()); try { write(outputStream, content); diff --git a/acra-http/src/main/java/org/acra/security/KeyStoreHelper.java b/acra-http/src/main/java/org/acra/security/KeyStoreHelper.java index d1f7a65d44..f024c1d751 100644 --- a/acra-http/src/main/java/org/acra/security/KeyStoreHelper.java +++ b/acra-http/src/main/java/org/acra/security/KeyStoreHelper.java @@ -49,12 +49,12 @@ private KeyStoreHelper() { public static KeyStore getKeyStore(@NonNull Context context, @NonNull CoreConfiguration config) { final HttpSenderConfiguration senderConfiguration = ConfigUtils.getPluginConfiguration(config, HttpSenderConfiguration.class); final InstanceCreator instanceCreator = new InstanceCreator(); - KeyStore keyStore = instanceCreator.create(senderConfiguration.keyStoreFactoryClass(), NoKeyStoreFactory::new).create(context); + KeyStore keyStore = instanceCreator.create(senderConfiguration.getKeyStoreFactoryClass(), NoKeyStoreFactory::new).create(context); if(keyStore == null) { //either users factory did not create a keystore, or the configuration is default {@link NoKeyStoreFactory} - final int certificateRes = senderConfiguration.resCertificate(); - final String certificatePath = senderConfiguration.certificatePath(); - final String certificateType = senderConfiguration.certificateType(); + final int certificateRes = senderConfiguration.getResCertificate(); + final String certificatePath = senderConfiguration.getCertificatePath(); + final String certificateType = senderConfiguration.getCertificateType(); if(certificateRes != ACRAConstants.DEFAULT_RES_VALUE){ keyStore = new ResourceKeyStoreFactory(certificateType, certificateRes).create(context); }else if(!certificatePath.equals(ACRAConstants.DEFAULT_STRING_VALUE)){ diff --git a/acra-http/src/main/java/org/acra/sender/HttpSender.java b/acra-http/src/main/java/org/acra/sender/HttpSender.java index dce72aab23..395a971a44 100644 --- a/acra-http/src/main/java/org/acra/sender/HttpSender.java +++ b/acra-http/src/main/java/org/acra/sender/HttpSender.java @@ -90,9 +90,9 @@ public HttpSender(@NonNull CoreConfiguration config, @Nullable Method method, @N public HttpSender(@NonNull CoreConfiguration config, @Nullable Method method, @Nullable StringFormat type, @Nullable String formUri) { this.config = config; this.httpConfig = ConfigUtils.getPluginConfiguration(config, HttpSenderConfiguration.class); - mMethod = (method == null) ? httpConfig.httpMethod() : method; - mFormUri = Uri.parse((formUri == null) ? httpConfig.uri() : formUri); - mType = (type == null) ? config.reportFormat() : type; + mMethod = (method == null) ? httpConfig.getHttpMethod() : method; + mFormUri = Uri.parse((formUri == null) ? httpConfig.getUri() : formUri); + mType = (type == null) ? config.getReportFormat() : type; mUsername = null; mPassword = null; } @@ -117,11 +117,11 @@ public void send(@NonNull Context context, @NonNull CrashReportData report) thro final String baseUrl = mFormUri.toString(); if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "Connect to " + baseUrl); - final String login = mUsername != null ? mUsername : isNull(httpConfig.basicAuthLogin()) ? null : httpConfig.basicAuthLogin(); - final String password = mPassword != null ? mPassword : isNull(httpConfig.basicAuthPassword()) ? null : httpConfig.basicAuthPassword(); + final String login = mUsername != null ? mUsername : isNull(httpConfig.getBasicAuthLogin()) ? null : httpConfig.getBasicAuthLogin(); + final String password = mPassword != null ? mPassword : isNull(httpConfig.getBasicAuthPassword()) ? null : httpConfig.getBasicAuthPassword(); final InstanceCreator instanceCreator = new InstanceCreator(); - final List uris = instanceCreator.create(config.attachmentUriProvider(), DefaultAttachmentProvider::new).getAttachments(context, config); + final List uris = instanceCreator.create(config.getAttachmentUriProvider(), DefaultAttachmentProvider::new).getAttachments(context, config); // Generate report body depending on requested type final String reportAsString = convertToString(report, mType); @@ -129,11 +129,11 @@ public void send(@NonNull Context context, @NonNull CrashReportData report) thro // Adjust URL depending on method final URL reportUrl = mMethod.createURL(baseUrl, report); - sendHttpRequests(config, context, mMethod, mType.getMatchingHttpContentType(), login, password, httpConfig.connectionTimeout(), - httpConfig.socketTimeout(), httpConfig.httpHeaders(), reportAsString, reportUrl, uris); + sendHttpRequests(config, context, mMethod, mType.getMatchingHttpContentType(), login, password, httpConfig.getConnectionTimeout(), + httpConfig.getSocketTimeout(), httpConfig.getHttpHeaders(), reportAsString, reportUrl, uris); } catch (@NonNull Exception e) { - throw new ReportSenderException("Error while sending " + config.reportFormat() + throw new ReportSenderException("Error while sending " + config.getReportFormat() + " report via Http " + mMethod.name(), e); } } @@ -196,11 +196,11 @@ protected void putAttachment(@NonNull CoreConfiguration configuration, @NonNull @NonNull @SuppressWarnings("WeakerAccess") protected String convertToString(CrashReportData report, @NonNull StringFormat format) throws Exception { - return format.toFormattedString(report, config.reportContent(), "&", "\n", true); + return format.toFormattedString(report, config.getReportContent(), "&", "\n", true); } private boolean isNull(@Nullable String aString) { - return aString == null || ACRAConstants.NULL_VALUE.equals(aString); + return aString == null || aString.length() == 0 || ACRAConstants.NULL_VALUE.equals(aString); } /** diff --git a/acra-limiter/src/main/java/org/acra/annotation/AcraLimiter.java b/acra-limiter/src/main/java/org/acra/annotation/AcraLimiter.java index 248dd88d5d..74a1022c48 100644 --- a/acra-limiter/src/main/java/org/acra/annotation/AcraLimiter.java +++ b/acra-limiter/src/main/java/org/acra/annotation/AcraLimiter.java @@ -35,6 +35,7 @@ * @author F43nd1r * @since 26.10.2017 */ +@Deprecated @Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) diff --git a/acra-limiter/src/main/java/org/acra/config/LimitingReportAdministrator.java b/acra-limiter/src/main/java/org/acra/config/LimitingReportAdministrator.java index 16961ae741..61dd8a079d 100644 --- a/acra-limiter/src/main/java/org/acra/config/LimitingReportAdministrator.java +++ b/acra-limiter/src/main/java/org/acra/config/LimitingReportAdministrator.java @@ -56,12 +56,12 @@ public boolean shouldStartCollecting(@NonNull Context context, @NonNull CoreConf try { final LimiterConfiguration limiterConfiguration = ConfigUtils.getPluginConfiguration(config, LimiterConfiguration.class); final ReportLocator reportLocator = new ReportLocator(context); - if (reportLocator.getApprovedReports().length + reportLocator.getUnapprovedReports().length >= limiterConfiguration.failedReportLimit()) { + if (reportLocator.getApprovedReports().length + reportLocator.getUnapprovedReports().length >= limiterConfiguration.getFailedReportLimit()) { if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "Reached failedReportLimit, not collecting"); return false; } final List reportMetadata = loadLimiterData(context, limiterConfiguration).getReportMetadata(); - if (reportMetadata.size() >= limiterConfiguration.overallLimit()) { + if (reportMetadata.size() >= limiterConfiguration.getOverallLimit()) { if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "Reached overallLimit, not collecting"); return false; } @@ -87,11 +87,11 @@ public boolean shouldSendReport(@NonNull Context context, @NonNull CoreConfigura sameClass++; } } - if (sameTrace >= limiterConfiguration.stacktraceLimit()) { + if (sameTrace >= limiterConfiguration.getStacktraceLimit()) { if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "Reached stacktraceLimit, not sending"); return false; } - if (sameClass >= limiterConfiguration.exceptionClassLimit()) { + if (sameClass >= limiterConfiguration.getExceptionClassLimit()) { if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "Reached exceptionClassLimit, not sending"); return false; } @@ -106,10 +106,10 @@ public boolean shouldSendReport(@NonNull Context context, @NonNull CoreConfigura @Override public void notifyReportDropped(@NonNull final Context context, @NonNull final CoreConfiguration config) { final LimiterConfiguration limiterConfiguration = ConfigUtils.getPluginConfiguration(config, LimiterConfiguration.class); - if (limiterConfiguration.ignoredCrashToast() != null) { + if (!limiterConfiguration.getIgnoredCrashToast().isEmpty()) { final Future future = Executors.newSingleThreadExecutor().submit(() -> { Looper.prepare(); - ToastSender.sendToast(context, limiterConfiguration.ignoredCrashToast(), Toast.LENGTH_LONG); + ToastSender.sendToast(context, limiterConfiguration.getIgnoredCrashToast(), Toast.LENGTH_LONG); final Looper looper = Looper.myLooper(); if (looper != null) { new Handler(looper).postDelayed(() -> { @@ -138,7 +138,7 @@ public void notifyReportDropped(@NonNull final Context context, @NonNull final C private LimiterData loadLimiterData(@NonNull Context context, @NonNull LimiterConfiguration limiterConfiguration) throws IOException { final LimiterData limiterData = LimiterData.load(context); final Calendar keepAfter = Calendar.getInstance(); - keepAfter.add(Calendar.MINUTE, (int) -limiterConfiguration.periodUnit().toMinutes(limiterConfiguration.period())); + keepAfter.add(Calendar.MINUTE, (int) -limiterConfiguration.getPeriodUnit().toMinutes(limiterConfiguration.getPeriod())); if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "purging reports older than " + keepAfter.getTime().toString()); limiterData.purgeOldData(keepAfter); limiterData.store(context); diff --git a/acra-limiter/src/main/java/org/acra/startup/LimiterStartupProcessor.java b/acra-limiter/src/main/java/org/acra/startup/LimiterStartupProcessor.java index 0b0ab90bb7..57b3f3c653 100644 --- a/acra-limiter/src/main/java/org/acra/startup/LimiterStartupProcessor.java +++ b/acra-limiter/src/main/java/org/acra/startup/LimiterStartupProcessor.java @@ -48,19 +48,19 @@ public LimiterStartupProcessor() { @Override public void processReports(@NonNull Context context, @NonNull CoreConfiguration config, List reports) { final LimiterConfiguration limiterConfiguration = ConfigUtils.getPluginConfiguration(config, LimiterConfiguration.class); - if(limiterConfiguration.deleteReportsOnAppUpdate() || limiterConfiguration.resetLimitsOnAppUpdate()) { + if(limiterConfiguration.getDeleteReportsOnAppUpdate() || limiterConfiguration.getResetLimitsOnAppUpdate()) { final SharedPreferences prefs = new SharedPreferencesFactory(context, config).create(); final long lastVersionNr = prefs.getInt(ACRA.PREF_LAST_VERSION_NR, 0); final int appVersion = getAppVersion(context); if (appVersion > lastVersionNr) { - if(limiterConfiguration.deleteReportsOnAppUpdate()) { + if(limiterConfiguration.getDeleteReportsOnAppUpdate()) { prefs.edit().putInt(ACRA.PREF_LAST_VERSION_NR, appVersion).apply(); for (Report report : reports) { report.delete(); } } - if(limiterConfiguration.resetLimitsOnAppUpdate()) { + if(limiterConfiguration.getResetLimitsOnAppUpdate()) { try { new LimiterData().store(context); } catch (IOException e) { diff --git a/acra-mail/src/main/java/org/acra/annotation/AcraMailSender.java b/acra-mail/src/main/java/org/acra/annotation/AcraMailSender.java index 32bdf3590f..dde5a91602 100644 --- a/acra-mail/src/main/java/org/acra/annotation/AcraMailSender.java +++ b/acra-mail/src/main/java/org/acra/annotation/AcraMailSender.java @@ -33,6 +33,7 @@ * @author F43nd1r * @since 01.06.2017 */ +@Deprecated @Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @@ -46,7 +47,7 @@ * @return email address to which to send reports. * @since 5.0.0 */ - @NonNull String mailTo(); + @NonNull @AnyNonDefault String mailTo() default ACRAConstants.NULL_VALUE; /** * Sending the report as an attachment prevents issues with report size and the user from modifying it diff --git a/acra-mail/src/main/java/org/acra/sender/EmailIntentSender.java b/acra-mail/src/main/java/org/acra/sender/EmailIntentSender.java index 9ffc483b7f..b1b871fa6c 100644 --- a/acra-mail/src/main/java/org/acra/sender/EmailIntentSender.java +++ b/acra-mail/src/main/java/org/acra/sender/EmailIntentSender.java @@ -68,12 +68,12 @@ public void send(@NonNull Context context, @NonNull CrashReportData errorContent final String subject = buildSubject(context); final String reportText; try { - reportText = config.reportFormat().toFormattedString(errorContent, config.reportContent(), "\n", "\n\t", false); + reportText = config.getReportFormat().toFormattedString(errorContent, config.getReportContent(), "\n", "\n\t", false); } catch (Exception e) { throw new ReportSenderException("Failed to convert Report to text", e); } - final String bodyPrefix = mailConfig.body(); - final String body = bodyPrefix != null ? bodyPrefix + "\n" + reportText : reportText; + final String bodyPrefix = mailConfig.getBody(); + final String body = !bodyPrefix.isEmpty() ? bodyPrefix + "\n" + reportText : reportText; final ArrayList attachments = new ArrayList<>(); final boolean contentAttached = fillAttachmentList(context, reportText, attachments); @@ -147,7 +147,7 @@ private String getPackageName(@NonNull ComponentName resolveActivity, @NonNull L @NonNull protected Intent buildAttachmentIntent(@NonNull String subject, @Nullable String body, @NonNull ArrayList attachments) { final Intent intent = new Intent(Intent.ACTION_SEND_MULTIPLE); - intent.putExtra(Intent.EXTRA_EMAIL, new String[]{mailConfig.mailTo()}); + intent.putExtra(Intent.EXTRA_EMAIL, new String[]{mailConfig.getMailTo()}); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.putExtra(Intent.EXTRA_SUBJECT, subject); intent.setType("message/rfc822"); @@ -166,7 +166,7 @@ protected Intent buildAttachmentIntent(@NonNull String subject, @Nullable String @NonNull protected Intent buildResolveIntent(@NonNull String subject, @NonNull String body) { final Intent intent = new Intent(Intent.ACTION_SENDTO); - intent.setData(Uri.fromParts("mailto", mailConfig.mailTo(), null)); + intent.setData(Uri.fromParts("mailto", mailConfig.getMailTo(), null)); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.putExtra(Intent.EXTRA_SUBJECT, subject); intent.putExtra(Intent.EXTRA_TEXT, body); @@ -190,7 +190,7 @@ private List buildInitialIntents(@NonNull PackageManager pm, @NonNull In private void showChooser(@NonNull Context context, @NonNull List initialIntents) { final Intent chooser = new Intent(Intent.ACTION_CHOOSER); chooser.putExtra(Intent.EXTRA_INTENT, initialIntents.remove(0)); - chooser.putExtra(Intent.EXTRA_INITIAL_INTENTS, initialIntents.toArray(new Intent[initialIntents.size()])); + chooser.putExtra(Intent.EXTRA_INITIAL_INTENTS, initialIntents.toArray(new Intent[0])); chooser.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(chooser); } @@ -214,8 +214,8 @@ private void grantPermission(@NonNull Context context, @NonNull Intent intent, S */ @NonNull protected String buildSubject(@NonNull Context context) { - final String subject = mailConfig.subject(); - if (subject != null) { + final String subject = mailConfig.getSubject(); + if (!subject.isEmpty()) { return subject; } return context.getPackageName() + " Crash Report"; @@ -231,9 +231,9 @@ protected String buildSubject(@NonNull Context context) { */ protected boolean fillAttachmentList(@NonNull Context context, @NonNull String reportText, @NonNull List attachments) { final InstanceCreator instanceCreator = new InstanceCreator(); - attachments.addAll(instanceCreator.create(config.attachmentUriProvider(), DefaultAttachmentProvider::new).getAttachments(context, config)); - if (mailConfig.reportAsFile()) { - final Uri report = createAttachmentFromString(context, mailConfig.reportFileName(), reportText); + attachments.addAll(instanceCreator.create(config.getAttachmentUriProvider(), DefaultAttachmentProvider::new).getAttachments(context, config)); + if (mailConfig.getReportAsFile()) { + final Uri report = createAttachmentFromString(context, mailConfig.getReportFileName(), reportText); if (report != null) { attachments.add(report); return true; diff --git a/acra-notification/src/main/java/org/acra/annotation/AcraNotification.java b/acra-notification/src/main/java/org/acra/annotation/AcraNotification.java index dbb957db44..ed5bd6de9d 100644 --- a/acra-notification/src/main/java/org/acra/annotation/AcraNotification.java +++ b/acra-notification/src/main/java/org/acra/annotation/AcraNotification.java @@ -33,6 +33,7 @@ * @author F43nd1r * @since 15.09.2017 */ +@Deprecated @Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) diff --git a/acra-notification/src/main/java/org/acra/interaction/NotificationInteraction.java b/acra-notification/src/main/java/org/acra/interaction/NotificationInteraction.java index 273fdd06bc..0ace032e79 100644 --- a/acra-notification/src/main/java/org/acra/interaction/NotificationInteraction.java +++ b/acra-notification/src/main/java/org/acra/interaction/NotificationInteraction.java @@ -24,10 +24,13 @@ import android.content.SharedPreferences; import android.os.Build; import android.widget.RemoteViews; + import androidx.annotation.NonNull; import androidx.core.app.NotificationCompat; import androidx.core.app.RemoteInput; + import com.google.auto.service.AutoService; + import org.acra.ACRA; import org.acra.config.ConfigUtils; import org.acra.config.CoreConfiguration; @@ -74,45 +77,45 @@ public boolean performInteraction(@NonNull Context context, @NonNull CoreConfigu final NotificationConfiguration notificationConfig = ConfigUtils.getPluginConfiguration(config, NotificationConfiguration.class); //We have to create a channel on Oreo+, because notifications without one aren't allowed if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - final NotificationChannel channel = new NotificationChannel(CHANNEL, notificationConfig.channelName(), notificationConfig.resChannelImportance()); + final NotificationChannel channel = new NotificationChannel(CHANNEL, notificationConfig.getChannelName(), notificationConfig.getResChannelImportance()); channel.setSound(null, null); - if (notificationConfig.channelDescription() != null) { - channel.setDescription(notificationConfig.channelDescription()); + if (!notificationConfig.getChannelDescription().isEmpty()) { + channel.setDescription(notificationConfig.getChannelDescription()); } notificationManager.createNotificationChannel(channel); } //configure base notification final NotificationCompat.Builder notification = new NotificationCompat.Builder(context, CHANNEL) .setWhen(System.currentTimeMillis()) - .setContentTitle(notificationConfig.title()) - .setContentText(notificationConfig.text()) - .setSmallIcon(notificationConfig.resIcon()) + .setContentTitle(notificationConfig.getTitle()) + .setContentText(notificationConfig.getText()) + .setSmallIcon(notificationConfig.getResIcon()) .setPriority(NotificationCompat.PRIORITY_HIGH); //add ticker if set - if (notificationConfig.tickerText() != null) { - notification.setTicker(notificationConfig.tickerText()); + if (!notificationConfig.getTickerText().isEmpty()) { + notification.setTicker(notificationConfig.getTickerText()); } final PendingIntent sendIntent = getSendIntent(context, config, reportFile); final PendingIntent discardIntent = getDiscardIntent(context); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && notificationConfig.sendWithCommentButtonText() != null) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && !notificationConfig.getSendWithCommentButtonText().isEmpty()) { final RemoteInput.Builder remoteInput = new RemoteInput.Builder(KEY_COMMENT); - if (notificationConfig.commentPrompt() != null) { - remoteInput.setLabel(notificationConfig.commentPrompt()); + if (!notificationConfig.getCommentPrompt().isEmpty()) { + remoteInput.setLabel(notificationConfig.getCommentPrompt()); } - notification.addAction(new NotificationCompat.Action.Builder(notificationConfig.resSendWithCommentButtonIcon(), notificationConfig.sendWithCommentButtonText(), sendIntent) + notification.addAction(new NotificationCompat.Action.Builder(notificationConfig.getResSendWithCommentButtonIcon(), notificationConfig.getSendWithCommentButtonText(), sendIntent) .addRemoteInput(remoteInput.build()).build()); } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { final RemoteViews bigView = getBigView(context, notificationConfig); - notification.addAction(notificationConfig.resSendButtonIcon(), notificationConfig.sendButtonText(), sendIntent) - .addAction(notificationConfig.resDiscardButtonIcon(), notificationConfig.discardButtonText(), discardIntent) + notification.addAction(notificationConfig.getResSendButtonIcon(), notificationConfig.getSendButtonText(), sendIntent) + .addAction(notificationConfig.getResDiscardButtonIcon(), notificationConfig.getDiscardButtonText(), discardIntent) .setCustomContentView(getSmallView(context, notificationConfig, sendIntent, discardIntent)) .setCustomBigContentView(bigView) .setCustomHeadsUpContentView(bigView) .setStyle(new NotificationCompat.DecoratedCustomViewStyle()); } //On old devices we have no notification buttons, so we have to set the intent to the only possible interaction: click - if (notificationConfig.sendOnClick() || Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { + if (notificationConfig.getSendOnClick() || Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { notification.setContentIntent(sendIntent); } notification.setDeleteIntent(discardIntent); @@ -137,10 +140,10 @@ private PendingIntent getDiscardIntent(@NonNull Context context) { @NonNull private RemoteViews getSmallView(@NonNull Context context, @NonNull NotificationConfiguration notificationConfig, @NonNull PendingIntent sendIntent, @NonNull PendingIntent discardIntent) { final RemoteViews view = new RemoteViews(context.getPackageName(), R.layout.notification_small); - view.setTextViewText(R.id.text, notificationConfig.text()); - view.setTextViewText(R.id.title, notificationConfig.title()); - view.setImageViewResource(R.id.button_send, notificationConfig.resSendButtonIcon()); - view.setImageViewResource(R.id.button_discard, notificationConfig.resDiscardButtonIcon()); + view.setTextViewText(R.id.text, notificationConfig.getText()); + view.setTextViewText(R.id.title, notificationConfig.getTitle()); + view.setImageViewResource(R.id.button_send, notificationConfig.getResSendButtonIcon()); + view.setImageViewResource(R.id.button_discard, notificationConfig.getResDiscardButtonIcon()); view.setOnClickPendingIntent(R.id.button_send, sendIntent); view.setOnClickPendingIntent(R.id.button_discard, discardIntent); return view; @@ -149,8 +152,8 @@ private RemoteViews getSmallView(@NonNull Context context, @NonNull Notification @NonNull private RemoteViews getBigView(@NonNull Context context, @NonNull NotificationConfiguration notificationConfig) { final RemoteViews view = new RemoteViews(context.getPackageName(), R.layout.notification_big); - view.setTextViewText(R.id.text, notificationConfig.text()); - view.setTextViewText(R.id.title, notificationConfig.title()); + view.setTextViewText(R.id.text, notificationConfig.getText()); + view.setTextViewText(R.id.title, notificationConfig.getTitle()); return view; } } diff --git a/acra-toast/src/main/java/org/acra/annotation/AcraToast.java b/acra-toast/src/main/java/org/acra/annotation/AcraToast.java index 762ca946f8..fe64d37a7d 100644 --- a/acra-toast/src/main/java/org/acra/annotation/AcraToast.java +++ b/acra-toast/src/main/java/org/acra/annotation/AcraToast.java @@ -32,6 +32,7 @@ * @author F43nd1r * @since 02.06.2017 */ +@Deprecated @Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) diff --git a/acra-toast/src/main/java/org/acra/interaction/ToastInteraction.java b/acra-toast/src/main/java/org/acra/interaction/ToastInteraction.java index 2bbb16ec50..7d83a6d165 100644 --- a/acra-toast/src/main/java/org/acra/interaction/ToastInteraction.java +++ b/acra-toast/src/main/java/org/acra/interaction/ToastInteraction.java @@ -46,7 +46,7 @@ public ToastInteraction() { public boolean performInteraction(@NonNull Context context, @NonNull CoreConfiguration config, @NonNull File reportFile) { Looper.prepare(); ToastConfiguration pluginConfig = ConfigUtils.getPluginConfiguration(config, ToastConfiguration.class); - ToastSender.sendToast(context, pluginConfig.text(), pluginConfig.length()); + ToastSender.sendToast(context, pluginConfig.getText(), pluginConfig.getLength()); final Looper looper = Looper.myLooper(); if (looper != null) { new Handler(looper).postDelayed(() -> { @@ -55,7 +55,7 @@ public boolean performInteraction(@NonNull Context context, @NonNull CoreConfigu } else { looper.quit(); } - }, getLengthInMs(pluginConfig.length()) + 100); + }, getLengthInMs(pluginConfig.getLength()) + 100); Looper.loop(); } return true; diff --git a/annotationprocessor/build.gradle.kts b/annotationprocessor/build.gradle.kts index a8d5af5f43..beb47a8ab5 100644 --- a/annotationprocessor/build.gradle.kts +++ b/annotationprocessor/build.gradle.kts @@ -19,7 +19,8 @@ plugins { dependencies { implementation("com.google.auto:auto-common:0.10") - implementation("com.squareup:javapoet:1.11.1") + implementation("com.squareup:kotlinpoet:1.7.1") + implementation(kotlin("reflect")) implementation("org.apache.commons:commons-text:1.6") implementation(project(":annotations")) implementation(project(":acra-javacore")) diff --git a/annotationprocessor/src/main/java/org/acra/processor/AcraAnnotationProcessor.java b/annotationprocessor/src/main/java/org/acra/processor/AcraAnnotationProcessor.java deleted file mode 100644 index 4a7061d80a..0000000000 --- a/annotationprocessor/src/main/java/org/acra/processor/AcraAnnotationProcessor.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (c) 2018 the ACRA team - * - * 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.processor; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import com.google.auto.common.MoreElements; -import com.google.auto.service.AutoService; -import com.squareup.javapoet.ClassName; -import org.acra.annotation.Configuration; -import org.acra.processor.creator.ClassCreator; -import org.acra.processor.util.Types; - -import javax.annotation.processing.AbstractProcessor; -import javax.annotation.processing.Processor; -import javax.annotation.processing.RoundEnvironment; -import javax.annotation.processing.SupportedSourceVersion; -import javax.lang.model.SourceVersion; -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.TypeElement; -import javax.tools.Diagnostic; -import java.util.ArrayList; -import java.util.Set; -import java.util.stream.Collectors; - -/** - * @author F43nd1r - * @since 18.03.2017 - */ -@AutoService(Processor.class) -@SupportedSourceVersion(SourceVersion.RELEASE_8) -public class AcraAnnotationProcessor extends AbstractProcessor { - - @Override - public Set getSupportedAnnotationTypes() { - return Types.MARKER_ANNOTATIONS.stream().map(ClassName::reflectionName).collect(Collectors.toSet()); - } - - @Override - public boolean process(@Nullable Set annotations, @NonNull RoundEnvironment roundEnv) { - try { - final ArrayList annotatedElements = new ArrayList<>(roundEnv.getElementsAnnotatedWith(Configuration.class)); - if (!annotatedElements.isEmpty()) { - for (final Element e : annotatedElements) { - if (e.getKind() == ElementKind.ANNOTATION_TYPE) { - new ClassCreator(MoreElements.asType(e), e.getAnnotation(Configuration.class), processingEnv).createClasses(); - } else { - processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, String.format("%s is only supported on %s", - Configuration.class.getName(), ElementKind.ANNOTATION_TYPE.name()), e); - } - } - } - } catch (Exception e) { - e.printStackTrace(); - processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Failed to generate acra classes"); - } - return true; - } - -} diff --git a/annotationprocessor/src/main/java/org/acra/processor/AcraAnnotationProcessor.kt b/annotationprocessor/src/main/java/org/acra/processor/AcraAnnotationProcessor.kt new file mode 100644 index 0000000000..5e438ebfd1 --- /dev/null +++ b/annotationprocessor/src/main/java/org/acra/processor/AcraAnnotationProcessor.kt @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2018 the ACRA team + * + * 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.processor + +import com.google.auto.common.MoreElements +import com.google.auto.service.AutoService +import org.acra.annotation.Configuration +import org.acra.processor.creator.ClassCreator +import org.acra.processor.util.Types +import javax.annotation.processing.AbstractProcessor +import javax.annotation.processing.Processor +import javax.annotation.processing.RoundEnvironment +import javax.annotation.processing.SupportedSourceVersion +import javax.lang.model.SourceVersion +import javax.lang.model.element.ElementKind +import javax.lang.model.element.TypeElement +import javax.tools.Diagnostic + +/** + * @author F43nd1r + * @since 18.03.2017 + */ +@AutoService(Processor::class) +@SupportedSourceVersion(SourceVersion.RELEASE_8) +class AcraAnnotationProcessor : AbstractProcessor() { + override fun getSupportedAnnotationTypes(): Set { + return Types.MARKER_ANNOTATIONS.map { it.reflectionName() }.toSet() + } + + override fun process(annotations: Set?, roundEnv: RoundEnvironment): Boolean { + try { + for (e in roundEnv.getElementsAnnotatedWith(Configuration::class.java)) { + if (e.kind == ElementKind.ANNOTATION_TYPE) { + ClassCreator(MoreElements.asType(e), e.getAnnotation(Configuration::class.java), processingEnv).createClasses() + } else { + processingEnv.messager.printMessage(Diagnostic.Kind.ERROR, String.format("%s is only supported on %s", + Configuration::class.java.name, ElementKind.ANNOTATION_TYPE.name), e) + } + } + } catch (e: Throwable) { + e.printStackTrace() + processingEnv.messager.printMessage(Diagnostic.Kind.ERROR, "Failed to generate acra classes") + } + return true + } +} \ No newline at end of file diff --git a/annotationprocessor/src/main/java/org/acra/processor/creator/BuildMethodCreator.java b/annotationprocessor/src/main/java/org/acra/processor/creator/BuildMethodCreator.java deleted file mode 100644 index 1723fc580c..0000000000 --- a/annotationprocessor/src/main/java/org/acra/processor/creator/BuildMethodCreator.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright (c) 2018 the ACRA team - * - * 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.processor.creator; - -import androidx.annotation.NonNull; - -import com.squareup.javapoet.ClassName; -import com.squareup.javapoet.CodeBlock; -import com.squareup.javapoet.MethodSpec; -import com.squareup.javapoet.TypeName; - -import org.acra.config.ACRAConfigurationException; -import org.acra.config.ClassValidator; -import org.acra.processor.util.Strings; -import org.acra.processor.util.Types; - -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -import javax.lang.model.element.ExecutableElement; - -/** - * @author F43nd1r - * @since 12.06.2017 - */ - -public class BuildMethodCreator { - private final MethodSpec.Builder methodBuilder; - private final Map anyNonDefault; - private final ClassName config; - private final List statements; - - BuildMethodCreator(@NonNull ExecutableElement override, @NonNull ClassName config) { - this.config = config; - methodBuilder = Types.overriding(override) - .addAnnotation(Types.NON_NULL) - .returns(config) - .beginControlFlow("if ($L)", Strings.FIELD_ENABLED); - anyNonDefault = new LinkedHashMap<>(); - statements = new ArrayList<>(); - } - - public void addNotUnset(@NonNull String name, @NonNull TypeName type) { - methodBuilder.beginControlFlow("if ($L == $L)", name, getDefault(type)) - .addStatement("throw new $T(\"$L has to be set\")", ACRAConfigurationException.class, name) - .endControlFlow(); - } - - public void addNotEmpty(@NonNull String name) { - methodBuilder.beginControlFlow("if ($L.length == 0)", name) - .addStatement("throw new $T(\"$L cannot be empty\")", ACRAConfigurationException.class, name) - .endControlFlow(); - } - - public void addInstantiatable(@NonNull String name) { - methodBuilder.addStatement("$T.check($L)", ClassValidator.class, name); - } - - public void addAnyNonDefault(@NonNull String name, @NonNull CodeBlock defaultValue) { - anyNonDefault.put(name, defaultValue); - } - - @NonNull - private String getDefault(@NonNull TypeName type) { - if (type.isPrimitive()) { - if (type.equals(TypeName.BOOLEAN)) { - return "false"; - } else if (type.equals(TypeName.BYTE)) { - return "0"; - } else if (type.equals(TypeName.SHORT)) { - return "0"; - } else if (type.equals(TypeName.INT)) { - return "0"; - } else if (type.equals(TypeName.LONG)) { - return "0L"; - } else if (type.equals(TypeName.CHAR)) { - return "\u0000"; - } else if (type.equals(TypeName.FLOAT)) { - return "0.0f"; - } else if (type.equals(TypeName.DOUBLE)) { - return "0.0d"; - } - } - return "null"; - } - - public void addDelegateCall(@NonNull String methodName) { - statements.add(CodeBlock.builder().addStatement("$L.$L()", Strings.FIELD_DELEGATE, methodName).build()); - } - - @NonNull - MethodSpec build() { - if (anyNonDefault.size() > 0) { - methodBuilder.beginControlFlow("if ($L)", anyNonDefault.entrySet().stream().map(field -> CodeBlock.builder().add(field.getKey()).add(" == ").add(field.getValue()).build()) - .reduce((c1, c2) -> CodeBlock.builder().add(c1).add(" && ").add(c2).build()).orElseGet(() -> CodeBlock.of("true"))) - .addStatement("throw new $T(\"One of $L must not be default\")", ACRAConfigurationException.class, - anyNonDefault.keySet().stream().collect(Collectors.joining(", "))) - .endControlFlow(); - } - methodBuilder.endControlFlow(); - for (CodeBlock s : statements) { - methodBuilder.addCode(s); - } - methodBuilder.addStatement("return new $T(this)", config); - return methodBuilder.build(); - } - - -} diff --git a/annotationprocessor/src/main/java/org/acra/processor/creator/BuildMethodCreator.kt b/annotationprocessor/src/main/java/org/acra/processor/creator/BuildMethodCreator.kt new file mode 100644 index 0000000000..16c6b4d548 --- /dev/null +++ b/annotationprocessor/src/main/java/org/acra/processor/creator/BuildMethodCreator.kt @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2018 the ACRA team + * + * 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.processor.creator + +import com.squareup.kotlinpoet.AnnotationSpec +import com.squareup.kotlinpoet.ClassName +import com.squareup.kotlinpoet.CodeBlock +import com.squareup.kotlinpoet.FunSpec +import com.squareup.kotlinpoet.asTypeName +import org.acra.config.ACRAConfigurationException +import org.acra.config.ClassValidator +import org.acra.processor.util.Strings +import org.acra.processor.util.Types +import java.util.stream.Collectors +import javax.lang.model.element.ExecutableElement +import kotlin.jvm.Throws + +/** + * @author F43nd1r + * @since 12.06.2017 + */ +class BuildMethodCreator(override: ExecutableElement, private val config: ClassName) { + private val methodBuilder: FunSpec.Builder = Types.overriding(override) + .returns(config) + .addAnnotation(AnnotationSpec.builder(Throws::class).addMember("exceptionClasses = [%L::class]", ACRAConfigurationException::class.asTypeName()).build()) + .beginControlFlow("if (%L)", Strings.FIELD_ENABLED) + private val anyNonDefault: MutableMap = mutableMapOf() + private val statements: MutableList = mutableListOf() + + fun addNotUnset(name: String) { + methodBuilder.beginControlFlow("if (%L == null)", name) + .addStatement("throw %T(\"%L has to be set\")", ACRAConfigurationException::class.java, name) + .endControlFlow() + } + + fun addNotEmpty(name: String) { + methodBuilder.beginControlFlow("if (%L.isEmpty())", name) + .addStatement("throw %T(\"%L cannot be empty\")", ACRAConfigurationException::class.java, name) + .endControlFlow() + } + + fun addInstantiatable(name: String) { + methodBuilder.addStatement("%T.check(%L)", ClassValidator::class.java, name) + } + + fun addAnyNonDefault(name: String, default: CodeBlock) { + anyNonDefault[name] = default + } + + fun addDelegateCall(methodName: String) { + statements.add(CodeBlock.builder().addStatement("%L.%L()", Strings.FIELD_DELEGATE, methodName).build()) + } + + fun build(): FunSpec { + if (anyNonDefault.isNotEmpty()) { + methodBuilder.beginControlFlow("if (%L)", anyNonDefault.map { CodeBlock.of("%L == %L", it.key, it.value) } + .reduceOrNull { c1: CodeBlock, c2: CodeBlock -> CodeBlock.builder().add(c1).add(" && ").add(c2).build() } ?: CodeBlock.of("true")) + .addStatement("throw %T(\"One·of·%L·must·not·be·default\")", ACRAConfigurationException::class.java, anyNonDefault.keys.joinToString(",·")) + .endControlFlow() + } + methodBuilder.endControlFlow() + for (s in statements) { + methodBuilder.addCode(s) + } + methodBuilder.addStatement("return %T(this)", config) + return methodBuilder.build() + } +} \ No newline at end of file diff --git a/annotationprocessor/src/main/java/org/acra/processor/creator/ClassCreator.java b/annotationprocessor/src/main/java/org/acra/processor/creator/ClassCreator.java deleted file mode 100644 index b3418b85f4..0000000000 --- a/annotationprocessor/src/main/java/org/acra/processor/creator/ClassCreator.java +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright (c) 2018 the ACRA team - * - * 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.processor.creator; - -import androidx.annotation.NonNull; -import com.google.auto.common.MoreTypes; -import com.google.auto.service.AutoService; -import com.squareup.javapoet.*; -import org.acra.annotation.Configuration; -import org.acra.config.ConfigurationBuilder; -import org.acra.processor.element.*; -import org.acra.processor.util.Strings; -import org.acra.processor.util.Types; - -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.Modifier; -import javax.lang.model.element.TypeElement; -import javax.lang.model.type.MirroredTypeException; -import java.io.IOException; -import java.io.Serializable; -import java.util.List; - -import static org.acra.processor.util.Strings.*; - -/** - * @author F43nd1r - * @since 04.06.2017 - */ - -public class ClassCreator { - private final TypeElement baseAnnotation; - private final Configuration configuration; - private final ProcessingEnvironment processingEnv; - private final String factoryName; - private final String configName; - private final String builderName; - private final String builderVisibleName; - - public ClassCreator(@NonNull TypeElement baseAnnotation, Configuration configuration, @NonNull ProcessingEnvironment processingEnv) { - this.baseAnnotation = baseAnnotation; - this.configuration = configuration; - this.processingEnv = processingEnv; - configName = baseAnnotation.getSimpleName().toString().replace("Acra", "") + "Configuration"; - builderVisibleName = configName + "Builder"; - builderName = configuration.isPlugin() ? builderVisibleName + "Impl" : builderVisibleName; - factoryName = builderVisibleName + "Factory"; - - } - - public void createClasses() throws IOException { - TypeElement baseBuilder; - try { - baseBuilder = processingEnv.getElementUtils().getTypeElement(configuration.baseBuilderClass().getName()); - } catch (MirroredTypeException e) { - baseBuilder = MoreTypes.asTypeElement(e.getTypeMirror()); - } - final List elements = new ModelBuilder(baseAnnotation, new ElementFactory(processingEnv.getElementUtils()), baseBuilder, processingEnv.getMessager()).build(); - createBuilderClass(elements); - createConfigClass(elements); - if (configuration.isPlugin()) { - createBuilderInterface(elements); - createFactoryClass(); - } - } - - private void createBuilderInterface(@NonNull List elements) throws IOException { - final TypeSpec.Builder interfaceBuilder = TypeSpec.interfaceBuilder(builderVisibleName) - .addOriginatingElement(baseAnnotation) - .addModifiers(Modifier.PUBLIC) - .addSuperinterface(ConfigurationBuilder.class); - final TypeName baseAnnotation = TypeName.get(this.baseAnnotation.asType()); - Strings.addClassJavadoc(interfaceBuilder, baseAnnotation); - ClassName builder = ClassName.get(PACKAGE, builderVisibleName); - elements.stream().filter(BuilderElement.Interface.class::isInstance).map(BuilderElement.Interface.class::cast) - .forEach(element -> element.addToBuilderInterface(interfaceBuilder, builder)); - Strings.writeClass(processingEnv.getFiler(), interfaceBuilder.build()); - } - - private void createBuilderClass(@NonNull List elements) throws IOException { - final TypeSpec.Builder classBuilder = TypeSpec.classBuilder(builderName) - .addOriginatingElement(baseAnnotation) - .addModifiers(Modifier.FINAL); - final TypeName baseAnnotation = TypeName.get(this.baseAnnotation.asType()); - Strings.addClassJavadoc(classBuilder, baseAnnotation); - final MethodSpec.Builder constructor = MethodSpec.constructorBuilder() - .addParameter(ParameterSpec.builder(Types.CONTEXT, PARAM_0).addAnnotation(Types.NON_NULL).build()) - .addJavadoc("@param $L object annotated with {@link $T}\n", PARAM_0, baseAnnotation) - .addStatement("final $1T $2L = $3L.getClass().getAnnotation($1T.class)", baseAnnotation, VAR_ANNOTATION, PARAM_0); - if (!configuration.isPlugin()) { - classBuilder.addModifiers(Modifier.PUBLIC) - .addSuperinterface(ConfigurationBuilder.class); - constructor.addModifiers(Modifier.PUBLIC); - } else { - classBuilder.addSuperinterface(ClassName.get(PACKAGE, builderVisibleName)); - } - final CodeBlock.Builder always = CodeBlock.builder(); - final CodeBlock.Builder whenAnnotationPresent = CodeBlock.builder(); - final CodeBlock.Builder whenAnnotationMissing = CodeBlock.builder(); - ClassName builder = ClassName.get(PACKAGE, builderName); - elements.stream().filter(BuilderElement.class::isInstance).map(BuilderElement.class::cast).forEach(m -> m.addToBuilder(classBuilder, builder, always, whenAnnotationPresent, whenAnnotationMissing)); - constructor.addCode(always.build()) - .beginControlFlow("if ($L)", Strings.FIELD_ENABLED) - .addCode(whenAnnotationPresent.build()) - .nextControlFlow("else") - .addCode(whenAnnotationMissing.build()) - .endControlFlow(); - classBuilder.addMethod(constructor.build()); - final BuildMethodCreator build = new BuildMethodCreator(Types.getOnlyMethod(processingEnv, ConfigurationBuilder.class.getName()), ClassName.get(PACKAGE, configName)); - elements.stream().filter(ValidatedElement.class::isInstance).map(ValidatedElement.class::cast).forEach(element -> element.addToBuildMethod(build)); - classBuilder.addMethod(build.build()); - Strings.writeClass(processingEnv.getFiler(), classBuilder.build()); - } - - - private void createConfigClass(@NonNull List elements) throws IOException { - final TypeSpec.Builder classBuilder = TypeSpec.classBuilder(configName) - .addOriginatingElement(baseAnnotation) - .addSuperinterface(Serializable.class) - .addSuperinterface(org.acra.config.Configuration.class) - .addModifiers(Modifier.PUBLIC, Modifier.FINAL); - Strings.addClassJavadoc(classBuilder, ClassName.get(baseAnnotation.asType())); - final MethodSpec.Builder constructor = MethodSpec.constructorBuilder().addModifiers(Modifier.PUBLIC) - .addParameter(ParameterSpec.builder(ClassName.get(PACKAGE, builderName), PARAM_0).addAnnotation(Types.NON_NULL).build()); - elements.stream().filter(ConfigElement.class::isInstance).map(ConfigElement.class::cast).forEach(element -> element.addToConfig(classBuilder, constructor)); - classBuilder.addMethod(constructor.build()); - Strings.writeClass(processingEnv.getFiler(), classBuilder.build()); - } - - private void createFactoryClass() throws IOException { - final TypeName configurationBuilderFactory = Types.CONFIGURATION_BUILDER_FACTORY; - Strings.writeClass(processingEnv.getFiler(), TypeSpec.classBuilder(factoryName) - .addOriginatingElement(baseAnnotation) - .addModifiers(Modifier.PUBLIC) - .addSuperinterface(configurationBuilderFactory) - .addAnnotation(AnnotationSpec.builder(AutoService.class).addMember("value", "$T.class", configurationBuilderFactory).build()) - .addMethod(Types.overriding(Types.getOnlyMethod(processingEnv, Strings.CONFIGURATION_BUILDER_FACTORY)) - .addAnnotation(Types.NON_NULL) - .addStatement("return new $T($L)", ClassName.get(PACKAGE, builderName), PARAM_0) - .build()) - .build()); - } -} diff --git a/annotationprocessor/src/main/java/org/acra/processor/creator/ClassCreator.kt b/annotationprocessor/src/main/java/org/acra/processor/creator/ClassCreator.kt new file mode 100644 index 0000000000..4023d7fe25 --- /dev/null +++ b/annotationprocessor/src/main/java/org/acra/processor/creator/ClassCreator.kt @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2018 the ACRA team + * + * 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.processor.creator + +import com.google.auto.common.MoreTypes +import com.google.auto.service.AutoService +import com.squareup.kotlinpoet.AnnotationSpec +import com.squareup.kotlinpoet.ClassName +import com.squareup.kotlinpoet.CodeBlock +import com.squareup.kotlinpoet.FileSpec +import com.squareup.kotlinpoet.FunSpec +import com.squareup.kotlinpoet.KModifier +import com.squareup.kotlinpoet.LambdaTypeName +import com.squareup.kotlinpoet.ParameterSpec +import com.squareup.kotlinpoet.TypeSpec +import com.squareup.kotlinpoet.asTypeName +import org.acra.annotation.Configuration +import org.acra.config.ConfigurationBuilder +import org.acra.processor.element.BuilderElement +import org.acra.processor.element.ConfigElement +import org.acra.processor.element.Element +import org.acra.processor.element.ElementFactory +import org.acra.processor.element.ValidatedElement +import org.acra.processor.util.Strings +import org.acra.processor.util.Types +import org.acra.processor.util.writeTo +import org.apache.commons.text.WordUtils +import java.io.Serializable +import javax.annotation.processing.ProcessingEnvironment +import javax.lang.model.element.TypeElement +import javax.lang.model.type.MirroredTypeException + +/** + * @author F43nd1r + * @since 04.06.2017 + */ +class ClassCreator(private val baseAnnotation: TypeElement, private val configuration: Configuration, private val processingEnv: ProcessingEnvironment) { + private val baseName = baseAnnotation.simpleName.toString().replace("Acra", "") + private val configName: String = baseName + "Configuration" + private val builderName: String = configName + "Builder" + private val factoryName: String = builderName + "Factory" + + fun createClasses() { + val baseBuilder: TypeElement? + baseBuilder = try { + processingEnv.elementUtils.getTypeElement(configuration.baseBuilderClass.qualifiedName) + } catch (e: MirroredTypeException) { + MoreTypes.asTypeElement(e.typeMirror) + } + val elements = ModelBuilder(baseAnnotation, ElementFactory(processingEnv.elementUtils), baseBuilder!!, processingEnv.messager).build() + createBuilderClass(elements) + createConfigClass(elements) + if (configuration.isPlugin) { + createFactoryClass() + createExtensions() + } + } + + private fun createBuilderClass(elements: List) { + val classBuilder = TypeSpec.classBuilder(builderName) + .addOriginatingElement(baseAnnotation) + val baseAnnotation = baseAnnotation.asType().asTypeName() + Strings.addClassKdoc(classBuilder, baseAnnotation) + val constructor = FunSpec.constructorBuilder() + .addParameter(ParameterSpec.builder(Strings.PARAM_0, Types.CONTEXT).build()) + .addKdoc("@param %L object annotated with {@link %T}\n", Strings.PARAM_0, baseAnnotation) + .addStatement("val %2L : %1T? = %3L.javaClass.getAnnotation(%1T::class.java)", baseAnnotation, Strings.VAR_ANNOTATION, Strings.PARAM_0) + classBuilder.addSuperinterface(ConfigurationBuilder::class.java) + val primaryConstructor = CodeBlock.builder() + val builder = ClassName(Strings.PACKAGE, builderName) + elements.filterIsInstance().forEach { it.addToBuilder(classBuilder, builder, primaryConstructor) } + classBuilder.primaryConstructor(constructor.addCode(primaryConstructor.build()).build()) + val build = BuildMethodCreator(Types.getOnlyMethod(processingEnv, ConfigurationBuilder::class.java.name), ClassName(Strings.PACKAGE, configName)) + elements.stream().filter { obj: Element? -> ValidatedElement::class.java.isInstance(obj) }.map { obj: Element? -> ValidatedElement::class.java.cast(obj) }.forEach { element: ValidatedElement -> element.addToBuildMethod(build) } + classBuilder.addFunction(build.build()) + Strings.writeClass(processingEnv, classBuilder.build()) + } + + private fun createConfigClass(elements: List) { + val classBuilder = TypeSpec.classBuilder(configName) + .addOriginatingElement(baseAnnotation) + .addSuperinterface(Serializable::class.java) + .addSuperinterface(org.acra.config.Configuration::class.java) + Strings.addClassKdoc(classBuilder, baseAnnotation.asType().asTypeName()) + val constructor = FunSpec.constructorBuilder().addModifiers(KModifier.PUBLIC) + .addParameter(ParameterSpec.builder(Strings.PARAM_0, ClassName(Strings.PACKAGE, builderName)).build()) + elements.filterIsInstance().forEach { it.addToConfig(classBuilder, constructor) } + classBuilder.addFunction(constructor.build()) + Strings.writeClass(processingEnv, classBuilder.build()) + } + + private fun createFactoryClass() { + val configurationBuilderFactory = Types.CONFIGURATION_BUILDER_FACTORY + Strings.writeClass(processingEnv, TypeSpec.classBuilder(factoryName) + .addOriginatingElement(baseAnnotation) + .addModifiers(KModifier.PUBLIC) + .addSuperinterface(configurationBuilderFactory) + .addAnnotation(AnnotationSpec.builder(AutoService::class.java).addMember("value = [%T::class]", configurationBuilderFactory).build()) + .addFunction(Types.overriding(Types.getOnlyMethod(processingEnv, Strings.CONFIGURATION_BUILDER_FACTORY)) + .addStatement("return %T(%L)", ClassName(Strings.PACKAGE, builderName), Strings.PARAM_0) + .build()) + .build()) + } + + private fun createExtensions() { + val builder = ClassName(Strings.PACKAGE, builderName) + FileSpec.builder(Strings.PACKAGE, "${baseName}Extensions") + .addFunction(FunSpec.builder(WordUtils.uncapitalize(baseName)) + .receiver(ClassName(Strings.PACKAGE, "CoreConfigurationBuilder")) + .addParameter(ParameterSpec("initializer", LambdaTypeName.get(builder, returnType = Unit::class.asTypeName()))) + .addStatement("this.getPluginConfigurationBuilder(%T::class.java).initializer()", builder) + .build()) + .writeTo(processingEnv) + } +} \ No newline at end of file diff --git a/annotationprocessor/src/main/java/org/acra/processor/creator/ModelBuilder.java b/annotationprocessor/src/main/java/org/acra/processor/creator/ModelBuilder.java deleted file mode 100644 index 7720301b1b..0000000000 --- a/annotationprocessor/src/main/java/org/acra/processor/creator/ModelBuilder.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright (c) 2018 the ACRA team - * - * 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.processor.creator; - -import androidx.annotation.NonNull; -import androidx.annotation.StringRes; - -import com.google.auto.common.MoreElements; -import com.google.auto.common.MoreTypes; -import com.squareup.javapoet.TypeName; - -import org.acra.annotation.BuilderMethod; -import org.acra.annotation.Configuration; -import org.acra.annotation.ConfigurationValue; -import org.acra.annotation.PreBuild; -import org.acra.annotation.Transform; -import org.acra.processor.element.BuilderElement; -import org.acra.processor.element.Element; -import org.acra.processor.element.ElementFactory; -import org.acra.processor.util.Types; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Optional; - -import javax.annotation.processing.Messager; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.util.ElementFilter; -import javax.tools.Diagnostic; - -/** - * @author F43nd1r - * @since 10.01.2018 - */ - -class ModelBuilder { - private final TypeElement baseAnnotation; - private final ElementFactory modelFactory; - private final Messager messager; - private final List elements; - private final TypeElement baseBuilder; - - ModelBuilder(@NonNull TypeElement baseAnnotation, @NonNull ElementFactory modelFactory, @NonNull TypeElement baseBuilder, @NonNull Messager messager) { - this.baseAnnotation = baseAnnotation; - this.modelFactory = modelFactory; - this.messager = messager; - this.elements = new ArrayList<>(); - this.baseBuilder = baseBuilder; - } - - private void handleParameter() { - elements.add(new BuilderElement.Context()); - elements.add(new BuilderElement.Enabled()); - } - - private void handleAnnotationMethods() { - for (ExecutableElement method : ElementFilter.methodsIn(baseAnnotation.getEnclosedElements())) { - elements.add(MoreElements.isAnnotationPresent(method, StringRes.class) ? modelFactory.fromStringResourceAnnotationMethod(method) : modelFactory.fromAnnotationMethod(method)); - } - } - - private void handleBaseBuilder() { - if (!MoreTypes.isTypeOf(Object.class, baseBuilder.asType())) { - final List constructors = ElementFilter.constructorsIn(baseBuilder.getEnclosedElements()); - Optional constructor = constructors.stream().filter(c -> c.getParameters().size() == 0).findAny(); - if (constructor.isPresent()) { - elements.add(modelFactory.fromDelegateConstructor(constructor.get(), false)); - } else { - constructor = constructors.stream().filter(c -> c.getParameters().size() == 1 && Types.CONTEXT.equals(TypeName.get(c.getParameters().get(0).asType()))).findAny(); - if (constructor.isPresent()) { - elements.add(modelFactory.fromDelegateConstructor(constructor.get(), true)); - } else { - final AnnotationMirror mirror = baseAnnotation.getAnnotationMirrors().stream() - .filter(m -> MoreTypes.isTypeOf(Configuration.class, m.getAnnotationType())) - .findAny().orElseThrow(IllegalArgumentException::new); - messager.printMessage(Diagnostic.Kind.ERROR, "Classes used as base builder must have a constructor which takes no arguments, " + - "or exactly one argument of type Class", baseAnnotation, mirror, mirror.getElementValues().entrySet().stream() - .filter(entry -> entry.getKey().getSimpleName().toString().equals("builderSuperClass")).findAny().map(Map.Entry::getValue).orElse(null)); - throw new IllegalArgumentException(); - } - } - handleBaseBuilderMethods(); - } - } - - private void handleBaseBuilderMethods() { - for (ExecutableElement method : ElementFilter.methodsIn(baseBuilder.getEnclosedElements())) { - if (method.getAnnotation(PreBuild.class) != null) { - elements.add(modelFactory.fromPreBuildDelegateMethod(method)); - } else if (method.getAnnotation(Transform.class) != null) { - final String transform = method.getAnnotation(Transform.class).methodName(); - elements.stream().filter(field -> field.getName().equals(transform)).findAny() - .ifPresent(element -> elements.set(elements.indexOf(element), modelFactory.fromTransformDelegateMethod(method, element))); - } else if (method.getAnnotation(ConfigurationValue.class) != null) { - elements.add(modelFactory.fromConfigDelegateMethod(method)); - } else if (method.getAnnotation(BuilderMethod.class) != null) { - elements.add(modelFactory.fromBuilderDelegateMethod(method)); - } - } - } - - @NonNull - List build() { - handleParameter(); - handleAnnotationMethods(); - handleBaseBuilder(); - return elements; - } -} diff --git a/annotationprocessor/src/main/java/org/acra/processor/creator/ModelBuilder.kt b/annotationprocessor/src/main/java/org/acra/processor/creator/ModelBuilder.kt new file mode 100644 index 0000000000..92dd9dc451 --- /dev/null +++ b/annotationprocessor/src/main/java/org/acra/processor/creator/ModelBuilder.kt @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2018 the ACRA team + * + * 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.processor.creator + +import androidx.annotation.StringRes +import com.google.auto.common.MoreElements +import com.google.auto.common.MoreTypes +import com.squareup.kotlinpoet.TypeName +import com.squareup.kotlinpoet.asTypeName +import org.acra.annotation.BuilderMethod +import org.acra.annotation.Configuration +import org.acra.annotation.ConfigurationValue +import org.acra.annotation.PreBuild +import org.acra.annotation.Transform +import org.acra.processor.element.BuilderElement +import org.acra.processor.element.Element +import org.acra.processor.element.ElementFactory +import org.acra.processor.util.Types +import java.util.* +import javax.annotation.processing.Messager +import javax.lang.model.element.AnnotationMirror +import javax.lang.model.element.AnnotationValue +import javax.lang.model.element.ExecutableElement +import javax.lang.model.element.TypeElement +import javax.lang.model.util.ElementFilter +import javax.tools.Diagnostic + +/** + * @author F43nd1r + * @since 10.01.2018 + */ +internal class ModelBuilder(private val baseAnnotation: TypeElement, private val modelFactory: ElementFactory, baseBuilder: TypeElement, private val messager: Messager) { + private val elements: MutableList + private val baseBuilder: TypeElement + private fun handleParameter() { + elements.add(BuilderElement.Context()) + elements.add(BuilderElement.Enabled()) + } + + private fun handleAnnotationMethods() { + for (method in ElementFilter.methodsIn(baseAnnotation.enclosedElements)) { + elements.add(when { + MoreElements.isAnnotationPresent(method, StringRes::class.java) -> modelFactory.fromStringResourceAnnotationMethod(method) + method.returnType.toString().contains("Class") -> modelFactory.fromClassAnnotationMethod(method) + else -> modelFactory.fromAnnotationMethod(method) + }) + } + } + + private fun handleBaseBuilder() { + if (!MoreTypes.isTypeOf(Any::class.java, baseBuilder.asType())) { + val constructors = ElementFilter.constructorsIn(baseBuilder.enclosedElements) + var constructor = constructors.stream().filter { c: ExecutableElement -> c.parameters.size == 0 }.findAny() + if (constructor.isPresent) { + elements.add(modelFactory.fromDelegateConstructor(constructor.get(), false)) + } else { + constructor = constructors.stream().filter { c: ExecutableElement -> c.parameters.size == 1 && Types.CONTEXT == c.parameters[0].asType().asTypeName() }.findAny() + if (constructor.isPresent) { + elements.add(modelFactory.fromDelegateConstructor(constructor.get(), true)) + } else { + val mirror = baseAnnotation.annotationMirrors.stream() + .filter { m: AnnotationMirror -> MoreTypes.isTypeOf(Configuration::class.java, m.annotationType) } + .findAny().orElseThrow { IllegalArgumentException() } + messager.printMessage(Diagnostic.Kind.ERROR, "Classes used as base builder must have a constructor which takes no arguments, " + + "or exactly one argument of type Class", baseAnnotation, mirror, mirror.elementValues.entries.firstOrNull { it.key.simpleName.toString() == "builderSuperClass" }?.value) + throw java.lang.IllegalArgumentException() + } + } + handleBaseBuilderMethods() + } + } + + private fun handleBaseBuilderMethods() { + for (method in ElementFilter.methodsIn(baseBuilder.enclosedElements)) { + when { + method.getAnnotation(PreBuild::class.java) != null -> { + elements.add(modelFactory.fromPreBuildDelegateMethod(method)) + } + method.getAnnotation(Transform::class.java) != null -> { + val transform: String = method.getAnnotation(Transform::class.java).methodName + elements.stream().filter { field: Element -> field.name == transform }.findAny() + .ifPresent { element: Element -> elements[elements.indexOf(element)] = modelFactory.fromTransformDelegateMethod(method, element) } + } + method.getAnnotation(ConfigurationValue::class.java) != null -> { + elements.add(modelFactory.fromConfigDelegateMethod(method)) + } + method.getAnnotation(BuilderMethod::class.java) != null -> { + elements.add(modelFactory.fromBuilderDelegateMethod(method)) + } + } + } + } + + fun build(): List { + handleParameter() + handleAnnotationMethods() + handleBaseBuilder() + return elements + } + + init { + elements = ArrayList() + this.baseBuilder = baseBuilder + } +} \ No newline at end of file diff --git a/annotationprocessor/src/main/java/org/acra/processor/element/AbstractElement.java b/annotationprocessor/src/main/java/org/acra/processor/element/AbstractElement.java deleted file mode 100644 index e222eb9284..0000000000 --- a/annotationprocessor/src/main/java/org/acra/processor/element/AbstractElement.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (c) 2018 the ACRA team - * - * 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.processor.element; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import com.squareup.javapoet.AnnotationSpec; -import com.squareup.javapoet.TypeName; - -import java.util.Collection; - -/** - * @author F43nd1r - * @since 12.01.2018 - */ - -class AbstractElement implements Element { - private final String name; - private final TypeName type; - private final Collection annotations; - - AbstractElement(@NonNull String name, @Nullable TypeName type, @NonNull Collection annotations) { - this.type = type; - this.name = name; - this.annotations = annotations; - } - - @Override - public String getName() { - return name; - } - - @Override - public TypeName getType() { - return type; - } - - @Override - public Collection getAnnotations() { - return annotations; - } -} diff --git a/annotationprocessor/src/main/java/org/acra/processor/element/Element.java b/annotationprocessor/src/main/java/org/acra/processor/element/AbstractElement.kt similarity index 70% rename from annotationprocessor/src/main/java/org/acra/processor/element/Element.java rename to annotationprocessor/src/main/java/org/acra/processor/element/AbstractElement.kt index 46df8e08e7..0afd7eaef3 100644 --- a/annotationprocessor/src/main/java/org/acra/processor/element/Element.java +++ b/annotationprocessor/src/main/java/org/acra/processor/element/AbstractElement.kt @@ -13,23 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +package org.acra.processor.element -package org.acra.processor.element; - -import com.squareup.javapoet.AnnotationSpec; -import com.squareup.javapoet.TypeName; - -import java.util.Collection; +import com.squareup.kotlinpoet.AnnotationSpec +import com.squareup.kotlinpoet.TypeName /** * @author F43nd1r * @since 12.01.2018 */ - -public interface Element { - String getName(); - - TypeName getType(); - - Collection getAnnotations(); -} +open class AbstractElement(override val name: String, override val type: TypeName, override val annotations: Collection) : Element { +} \ No newline at end of file diff --git a/annotationprocessor/src/main/java/org/acra/processor/element/AnnotationField.java b/annotationprocessor/src/main/java/org/acra/processor/element/AnnotationField.java deleted file mode 100644 index f8407be46c..0000000000 --- a/annotationprocessor/src/main/java/org/acra/processor/element/AnnotationField.java +++ /dev/null @@ -1,181 +0,0 @@ -/* - * Copyright (c) 2018 the ACRA team - * - * 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.processor.element; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import com.squareup.javapoet.*; -import org.acra.processor.creator.BuildMethodCreator; -import org.acra.processor.util.ToCodeBlockVisitor; -import org.acra.processor.util.IsValidResourceVisitor; -import org.acra.processor.util.Strings; -import org.acra.processor.util.Types; -import org.apache.commons.text.WordUtils; - -import javax.lang.model.element.AnnotationValue; -import javax.lang.model.element.Modifier; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; - -/** - * @author F43nd1r - * @since 12.01.2018 - */ - -abstract class AnnotationField extends AbstractElement implements TransformedField.Transformable { - private final Collection markers; - private final String javadoc; - private final AnnotationValue defaultValue; - - AnnotationField(@NonNull String name, @NonNull TypeName type, @NonNull Collection annotations, @Nullable String javadoc, @NonNull Collection markers, AnnotationValue defaultValue) { - super(name, type, annotations); - this.javadoc = javadoc; - this.markers = markers; - this.defaultValue = defaultValue; - } - - boolean hasMarker(@NonNull ClassName marker) { - return markers.contains(marker); - } - - @Override - public final void addToBuilder(@NonNull TypeSpec.Builder builder, @NonNull ClassName builderName, @NonNull CodeBlock.Builder constructorAlways, @NonNull CodeBlock.Builder constructorWhenAnnotationPresent, CodeBlock.Builder constructorWhenAnnotationMissing) { - addWithoutGetter(builder, builderName, constructorAlways, constructorWhenAnnotationPresent, constructorWhenAnnotationMissing); - addGetter(builder); - } - - @Override - public final void addWithoutGetter(@NonNull TypeSpec.Builder builder, ClassName builderName, CodeBlock.Builder constructorAlways, CodeBlock.Builder constructorWhenAnnotationPresent, CodeBlock.Builder constructorWhenAnnotationMissing) { - TransformedField.Transformable.super.addToBuilder(builder, builderName, constructorAlways, constructorWhenAnnotationPresent, constructorWhenAnnotationMissing); - addSetter(builder, builderName); - addInitializer(constructorWhenAnnotationPresent, constructorWhenAnnotationMissing); - } - - protected abstract void addInitializer(CodeBlock.Builder constructorWhenAnnotationPresent, CodeBlock.Builder constructorWhenAnnotationMissing); - - AnnotationValue getDefaultValue() { - return defaultValue; - } - - @Override - public void configureSetter(@NonNull MethodSpec.Builder builder) { - if (javadoc != null) { - final String name = builder.build().parameters.get(0).name; - builder.addJavadoc("$L", javadoc.replaceAll("(\n|^) ", "$1").replaceAll("@return ((.|\n)*)$", "@param " + name + " $1@return this instance\n")); - } - } - - static class Normal extends AnnotationField { - - Normal(String name, TypeName type, Collection annotations, Collection markers, AnnotationValue defaultValue, String javadoc) { - super(name, type, annotations, javadoc, markers, defaultValue); - } - - @Override - public void addInitializer(@NonNull CodeBlock.Builder constructorWhenAnnotationPresent, CodeBlock.Builder constructorWhenAnnotationMissing) { - constructorWhenAnnotationPresent.addStatement("$1L = $2L.$1L()", getName(), Strings.VAR_ANNOTATION); - if (getDefaultValue() != null) { - constructorWhenAnnotationMissing.addStatement("$L = $L", getName(), getDefaultValue().accept(new ToCodeBlockVisitor(getType()), null)); - } - } - - @Override - public void addToBuildMethod(@NonNull BuildMethodCreator method) { - if (getDefaultValue() == null) { - method.addNotUnset(getName(), getType()); - } - if (hasMarker(Types.NON_EMPTY)) { - method.addNotEmpty(getName()); - } - if (hasMarker(Types.INSTANTIATABLE)) { - method.addInstantiatable(getName()); - } - if (hasMarker(Types.ANY_NON_DEFAULT)) { - method.addAnyNonDefault(getName(), getDefaultValue().accept(new ToCodeBlockVisitor(getType()), null)); - } - } - - } - - static class StringResource extends AnnotationField { - private final String originalName; - private final boolean hasDefault; - - StringResource(String name, Collection annotations, Collection markers, - AnnotationValue defaultValue, String javadoc) { - super((name.startsWith(Strings.PREFIX_RES)) ? WordUtils.uncapitalize(name.substring(Strings.PREFIX_RES.length())) : name, Types.STRING, annotations, javadoc, markers, defaultValue); - this.originalName = name; - this.hasDefault = defaultValue != null && getDefaultValue().accept(new IsValidResourceVisitor(), null); - getAnnotations().remove(Types.STRING_RES); - getAnnotations().add(hasDefault ? Types.NON_NULL : Types.NULLABLE); - } - - @Override - public void addInitializer(@NonNull CodeBlock.Builder constructorWhenAnnotationPresent, CodeBlock.Builder constructorWhenAnnotationMissing) { - constructorWhenAnnotationPresent.beginControlFlow("if ($L.$L() != 0)", Strings.VAR_ANNOTATION, originalName) - .addStatement("$L = $L.getString($L.$L())", getName(), Strings.FIELD_CONTEXT, Strings.VAR_ANNOTATION, originalName) - .endControlFlow(); - if (hasDefault) { - constructorWhenAnnotationMissing.addStatement("$L = $L.getString($L)", getName(), Strings.FIELD_CONTEXT, getDefaultValue()); - } - - } - - @Override - public void addSetter(@NonNull TypeSpec.Builder builder, @NonNull ClassName builderName) { - super.addSetter(builder, builderName); - final MethodSpec.Builder setter = baseResSetter(builderName) - .addStatement("this.$L = $L.getString($L)", getName(), Strings.FIELD_CONTEXT, Strings.PREFIX_RES + WordUtils.capitalize(getName())) - .addStatement("return this"); - configureSetter(setter); - builder.addMethod(setter.build()); - } - - private MethodSpec.Builder baseResSetter(ClassName builderName) { - final String parameterName = Strings.PREFIX_RES + WordUtils.capitalize(getName()); - final List annotations = new ArrayList<>(getAnnotations()); - annotations.removeIf(Arrays.asList(Types.NULLABLE, Types.NON_NULL)::contains); - annotations.add(Types.STRING_RES); - return MethodSpec.methodBuilder(Strings.PREFIX_SETTER + WordUtils.capitalize(parameterName)) - .addParameter(ParameterSpec.builder(TypeName.INT, parameterName).addAnnotations(annotations).build()) - .addModifiers(Modifier.PUBLIC) - .addAnnotation(Types.NON_NULL) - .returns(builderName); - } - - @Override - public void addToBuilderInterface(@NonNull TypeSpec.Builder builder, @NonNull ClassName builderName) { - super.addToBuilderInterface(builder, builderName); - final MethodSpec.Builder setter = baseResSetter(builderName).addModifiers(Modifier.ABSTRACT); - configureSetter(setter); - builder.addMethod(setter.build()); - } - - @Override - public void addToBuildMethod(@NonNull BuildMethodCreator method) { - if (getDefaultValue() == null) { - method.addNotUnset(getName(), getType()); - } - if (hasMarker(Types.ANY_NON_DEFAULT)) { - method.addAnyNonDefault(getName(), CodeBlock.of("null")); - } - } - } - -} diff --git a/annotationprocessor/src/main/java/org/acra/processor/element/AnnotationField.kt b/annotationprocessor/src/main/java/org/acra/processor/element/AnnotationField.kt new file mode 100644 index 0000000000..0b58b51156 --- /dev/null +++ b/annotationprocessor/src/main/java/org/acra/processor/element/AnnotationField.kt @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2018 the ACRA team + * + * 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.processor.element + +import com.squareup.kotlinpoet.AnnotationSpec +import com.squareup.kotlinpoet.ClassName +import com.squareup.kotlinpoet.CodeBlock +import com.squareup.kotlinpoet.FunSpec +import com.squareup.kotlinpoet.KModifier +import com.squareup.kotlinpoet.ParameterSpec +import com.squareup.kotlinpoet.PropertySpec +import com.squareup.kotlinpoet.TypeName +import com.squareup.kotlinpoet.TypeSpec +import org.acra.processor.creator.BuildMethodCreator +import org.acra.processor.util.IsValidResourceVisitor +import org.acra.processor.util.Strings +import org.acra.processor.util.ToCodeBlockVisitor +import org.acra.processor.util.Types +import org.apache.commons.text.WordUtils +import java.util.* +import javax.lang.model.element.AnnotationValue + +/** + * @author F43nd1r + * @since 12.01.2018 + */ +abstract class AnnotationField(override val name: String, override val type: TypeName, override val annotations: Collection, private val javadoc: String?, private val markers: Collection, val defaultValue: AnnotationValue?) : TransformedField.Transformable { + fun hasMarker(marker: ClassName): Boolean { + return markers.contains(marker) + } + + override fun addToBuilder(builder: TypeSpec.Builder, builderName: ClassName, constructor: CodeBlock.Builder) { + super.addToBuilder(builder, builderName, constructor) + addSetter(builder, builderName) + addInitializer(constructor) + } + + override fun configureField(builder: PropertySpec.Builder) { + if (javadoc != null) { + builder.addKdoc("%L", javadoc.replace("(\n|^) ".toRegex(), "$1")) + } + } + + protected abstract fun addInitializer(constructor: CodeBlock.Builder) + override fun configureSetter(builder: FunSpec.Builder) { + if (javadoc != null) { + val name = builder.build().parameters[0].name + builder.addKdoc("%L", javadoc.replace("(\n|^) ".toRegex(), "$1").replace("@return ((.|\n)*)$".toRegex(), "@param $name $1@return this instance\n")) + } + } + + open class Normal(name: String, type: TypeName, annotations: Collection, markers: Collection, defaultValue: AnnotationValue?, javadoc: String) : AnnotationField(name, type, annotations, javadoc, markers, defaultValue) { + public override fun addInitializer(constructor: CodeBlock.Builder) { + constructor.addStatement("%1L = %2L?.%1L ?: %3L", name, Strings.VAR_ANNOTATION, defaultValue?.accept(ToCodeBlockVisitor(type), null)) + } + + override fun addToBuildMethod(method: BuildMethodCreator) { + if (defaultValue == null) { + method.addNotUnset(name) + } + if (hasMarker(Types.NON_EMPTY)) { + method.addNotEmpty(name) + } + if (hasMarker(Types.INSTANTIATABLE)) { + method.addInstantiatable(name) + } + if (hasMarker(Types.ANY_NON_DEFAULT)) { + method.addAnyNonDefault(name, defaultValue?.accept(ToCodeBlockVisitor(type), null) ?: CodeBlock.of("null")) + } + } + } + + class Clazz(name: String, type: TypeName, annotations: Collection, markers: Collection, defaultValue: AnnotationValue?, javadoc: String) : Normal(name, type, annotations, markers, defaultValue, javadoc) { + public override fun addInitializer(constructor: CodeBlock.Builder) { + constructor.addStatement("%1L = %2L?.%1L?.java ?: %3L", name, Strings.VAR_ANNOTATION, defaultValue?.accept(ToCodeBlockVisitor(type), null)) + } + } + + class StringResource(private val originalName: String, annotations: Collection, markers: Collection, + defaultValue: AnnotationValue?, javadoc: String?) : AnnotationField(if (originalName.startsWith(Strings.PREFIX_RES)) WordUtils.uncapitalize(originalName.substring(Strings.PREFIX_RES.length)) else originalName, Types.STRING, annotations - Types.STRING_RES, javadoc, markers, defaultValue) { + private val hasDefault: Boolean = defaultValue != null && defaultValue.accept(IsValidResourceVisitor(), null) + + public override fun addInitializer(constructor: CodeBlock.Builder) { + if(hasDefault) { + constructor.addStatement("%L = (%L?.%L.takeIf·{ it != 0 } ?: %L)?.let·{ %L.getString(it) } ?: \"\"", name, Strings.VAR_ANNOTATION, originalName, defaultValue, Strings.FIELD_CONTEXT) + } else { + constructor.addStatement("%L = %L?.%L.takeIf·{ it != 0 }?.let·{ %L.getString(it) } ?: \"\"", name, Strings.VAR_ANNOTATION, originalName, Strings.FIELD_CONTEXT) + } + } + + override fun addSetter(builder: TypeSpec.Builder, builderName: ClassName) { + super.addSetter(builder, builderName) + val setter = baseResSetter(builderName) + .addStatement("this.%L = %L.getString(%L)", name, Strings.FIELD_CONTEXT, Strings.ensurePrefix(Strings.PREFIX_RES, name)) + .addStatement("return this") + configureSetter(setter) + builder.addFunction(setter.build()) + } + + private fun baseResSetter(builderName: ClassName): FunSpec.Builder { + val parameterName = Strings.ensurePrefix(Strings.PREFIX_RES, name) + val annotations: MutableList = ArrayList(annotations) + annotations.add(Types.STRING_RES) + return FunSpec.builder(Strings.ensurePrefix(Strings.PREFIX_SETTER, parameterName)) + .addParameter(ParameterSpec.builder(parameterName, Int::class).addAnnotations(annotations).build()) + .addModifiers(KModifier.PUBLIC) + .returns(builderName) + } + + override fun addToBuildMethod(method: BuildMethodCreator) { + if (defaultValue == null) { + method.addNotUnset(name) + } + if (hasMarker(Types.ANY_NON_DEFAULT)) { + method.addAnyNonDefault(name, CodeBlock.of("\"\"")) + } + } + } +} \ No newline at end of file diff --git a/annotationprocessor/src/main/java/org/acra/processor/element/BuilderElement.java b/annotationprocessor/src/main/java/org/acra/processor/element/BuilderElement.java deleted file mode 100644 index 39f9f0086a..0000000000 --- a/annotationprocessor/src/main/java/org/acra/processor/element/BuilderElement.java +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Copyright (c) 2018 the ACRA team - * - * 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.processor.element; - -import androidx.annotation.NonNull; -import com.squareup.javapoet.*; -import org.acra.processor.util.Strings; -import org.acra.processor.util.Types; -import org.apache.commons.text.WordUtils; - -import javax.lang.model.element.Modifier; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; - -/** - * @author F43nd1r - * @since 11.01.2018 - */ - -public interface BuilderElement extends Element { - - default void addToBuilder(@NonNull TypeSpec.Builder builder, @NonNull ClassName builderName, @NonNull CodeBlock.Builder constructorAlways, - @NonNull CodeBlock.Builder constructorWhenAnnotationPresent, CodeBlock.Builder constructorWhenAnnotationMissing) { - final FieldSpec.Builder field = FieldSpec.builder(getType(), getName(), Modifier.PRIVATE).addAnnotations(getAnnotations()); - configureField(field); - builder.addField(field.build()); - } - - default void configureField(@NonNull FieldSpec.Builder builder) { - } - - default void addGetter(@NonNull TypeSpec.Builder builder) { - final MethodSpec.Builder method = MethodSpec.methodBuilder(getName()) - .addAnnotations(getAnnotations()) - .returns(getType()); - configureGetter(method); - builder.addMethod(method.build()); - } - - default void configureGetter(@NonNull MethodSpec.Builder builder) { - builder.addStatement("return $L", getName()); - } - - default void addSetter(@NonNull TypeSpec.Builder builder, @NonNull ClassName builderName) { - final MethodSpec.Builder method = baseSetter(builderName) - .addStatement("this.$1L = $1L", getName()) - .addStatement("return this"); - configureSetter(method); - builder.addMethod(method.build()); - } - - default MethodSpec.Builder baseSetter(ClassName builderName) { - MethodSpec.Builder builder = MethodSpec.methodBuilder(Strings.PREFIX_SETTER + WordUtils.capitalize(getName())); - Collection annotations = new ArrayList<>(getAnnotations()); - boolean deprecated = annotations.contains(Types.DEPRECATED); - if (deprecated) { - annotations.remove(Types.DEPRECATED); - builder.addAnnotation(Types.DEPRECATED); - } - return builder - .addParameter(ParameterSpec.builder(getType(), getName()).addAnnotations(annotations).build()) - .varargs(getType() instanceof ArrayTypeName) - .addModifiers(Modifier.PUBLIC) - .addAnnotation(Types.NON_NULL) - .returns(builderName); - } - - default void configureSetter(@NonNull MethodSpec.Builder builder) { - } - - interface Final extends BuilderElement { - default void configureField(@NonNull FieldSpec.Builder builder) { - builder.addModifiers(Modifier.FINAL); - } - } - - interface Interface extends BuilderElement { - - default void addToBuilderInterface(@NonNull TypeSpec.Builder builder, @NonNull ClassName builderName) { - MethodSpec.Builder setter = MethodSpec.methodBuilder(Strings.PREFIX_SETTER + WordUtils.capitalize(getName())) - .addParameter(ParameterSpec.builder(getType(), getName()).addAnnotations(getAnnotations()).build()) - .varargs(getType() instanceof ArrayTypeName) - .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT) - .addAnnotation(Types.NON_NULL) - .returns(builderName); - configureSetter(setter); - builder.addMethod(setter.build()); - } - } - - class Context extends AbstractElement implements Final { - public Context() { - super(Strings.FIELD_CONTEXT, Types.CONTEXT, Collections.singleton(Types.NON_NULL)); - } - - @Override - public void addToBuilder(@NonNull TypeSpec.Builder builder, @NonNull ClassName builderName, @NonNull CodeBlock.Builder constructorAlways, - @NonNull CodeBlock.Builder constructorWhenAnnotationPresent, CodeBlock.Builder constructorWhenAnnotationMissing) { - Final.super.addToBuilder(builder, builderName, constructorAlways, constructorWhenAnnotationPresent, constructorWhenAnnotationMissing); - constructorAlways.addStatement("$L = $L", getName(), Strings.PARAM_0); - } - } - - class Delegate extends AbstractElement implements Final { - private final boolean hasContextParameter; - - Delegate(@NonNull TypeName type, boolean hasContextParameter) { - super(Strings.FIELD_DELEGATE, type, Collections.singleton(Types.NON_NULL)); - this.hasContextParameter = hasContextParameter; - } - - @Override - public void addToBuilder(@NonNull TypeSpec.Builder builder, @NonNull ClassName builderName, @NonNull CodeBlock.Builder constructorAlways, - @NonNull CodeBlock.Builder constructorWhenAnnotationPresent, CodeBlock.Builder constructorWhenAnnotationMissing) { - Final.super.addToBuilder(builder, builderName, constructorAlways, constructorWhenAnnotationPresent, constructorWhenAnnotationMissing); - if (hasContextParameter) { - constructorAlways.addStatement("$L = new $T($L)", getName(), getType(), Strings.PARAM_0); - } else { - constructorAlways.addStatement("$L = new $T()", getName(), getType()); - } - } - } - - class Enabled extends AbstractElement implements Interface, ConfigElement { - public Enabled() { - super(Strings.FIELD_ENABLED, TypeName.BOOLEAN, Collections.emptyList()); - } - - @Override - public void addToBuilder(@NonNull TypeSpec.Builder builder, @NonNull ClassName builderName, @NonNull CodeBlock.Builder constructorAlways, - @NonNull CodeBlock.Builder constructorWhenAnnotationPresent, CodeBlock.Builder constructorWhenAnnotationMissing) { - Interface.super.addToBuilder(builder, builderName, constructorAlways, constructorWhenAnnotationPresent, constructorWhenAnnotationMissing); - addSetter(builder, builderName); - addGetter(builder); - constructorAlways.addStatement("$L = $L != null", getName(), Strings.VAR_ANNOTATION); - } - } -} diff --git a/annotationprocessor/src/main/java/org/acra/processor/element/BuilderElement.kt b/annotationprocessor/src/main/java/org/acra/processor/element/BuilderElement.kt new file mode 100644 index 0000000000..ad31ec3d80 --- /dev/null +++ b/annotationprocessor/src/main/java/org/acra/processor/element/BuilderElement.kt @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2018 the ACRA team + * + * 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.processor.element + +import com.squareup.kotlinpoet.ClassName +import com.squareup.kotlinpoet.CodeBlock +import com.squareup.kotlinpoet.FunSpec +import com.squareup.kotlinpoet.KModifier +import com.squareup.kotlinpoet.ParameterSpec +import com.squareup.kotlinpoet.PropertySpec +import com.squareup.kotlinpoet.TypeName +import com.squareup.kotlinpoet.TypeSpec +import com.squareup.kotlinpoet.asClassName +import com.squareup.kotlinpoet.asTypeName +import org.acra.processor.util.Strings +import org.acra.processor.util.Types +import java.util.* + +/** + * @author F43nd1r + * @since 11.01.2018 + */ +interface BuilderElement : Element { + fun addToBuilder(builder: TypeSpec.Builder, builderName: ClassName, constructor: CodeBlock.Builder) { + val field = PropertySpec.builder(name, type).addAnnotations(annotations).mutable() + configureField(field) + builder.addProperty(field.build()) + } + + fun configureField(builder: PropertySpec.Builder) {} + + fun addSetter(builder: TypeSpec.Builder, builderName: ClassName) { + val method = baseSetter(builderName) + .addStatement("return apply·{ this.%1L·= %1L }", name) + configureSetter(method) + builder.addFunction(method.build()) + } + + fun baseSetter(builderName: ClassName): FunSpec.Builder { + val builder = FunSpec.builder(Strings.ensurePrefix(Strings.PREFIX_SETTER, name)) + val annotations = ArrayList(annotations) + val deprecated = annotations.find { it.typeName == Deprecated::class.asTypeName() } + if (deprecated != null) { + annotations.remove(deprecated) + builder.addAnnotation(deprecated) + } + return builder + .addParameter(ParameterSpec.builder(name, type).addAnnotations(annotations).build()) + .addModifiers(KModifier.PUBLIC) + .returns(builderName) + } + + fun configureSetter(builder: FunSpec.Builder) {} + + interface Final : BuilderElement { + override fun configureField(builder: PropertySpec.Builder) { + builder.addModifiers(KModifier.FINAL) + } + } + + class Context : AbstractElement(Strings.FIELD_CONTEXT, Types.CONTEXT, emptyList()), Final { + override fun addToBuilder(builder: TypeSpec.Builder, builderName: ClassName, constructor: CodeBlock.Builder + ) { + super.addToBuilder(builder, builderName, constructor) + constructor.addStatement("%L = %L", name, Strings.PARAM_0) + } + } + + class Delegate internal constructor(type: TypeName, private val hasContextParameter: Boolean) : AbstractElement(Strings.FIELD_DELEGATE, type, emptyList()), Final { + override fun addToBuilder(builder: TypeSpec.Builder, builderName: ClassName, constructor: CodeBlock.Builder + ) { + super.addToBuilder(builder, builderName, constructor) + if (hasContextParameter) { + constructor.addStatement("%L = %T(%L)", name, type, Strings.PARAM_0) + } else { + constructor.addStatement("%L = %T()", name, type) + } + } + } + + class Enabled : AbstractElement(Strings.FIELD_ENABLED, Boolean::class.asClassName(), emptyList()), BuilderElement, ConfigElement { + override fun addToBuilder(builder: TypeSpec.Builder, builderName: ClassName, constructor: CodeBlock.Builder + ) { + super.addToBuilder(builder, builderName, constructor) + addSetter(builder, builderName) + constructor.addStatement("%L = %L != null", name, Strings.VAR_ANNOTATION) + } + + override fun addToConfig(builder: TypeSpec.Builder, constructor: FunSpec.Builder) { + super.addToConfig(builder, constructor) + builder.addFunction(FunSpec.builder(Strings.FIELD_ENABLED).addStatement("return %L", Strings.FIELD_ENABLED).addModifiers(KModifier.OVERRIDE).returns(Boolean::class).build()) + } + } +} \ No newline at end of file diff --git a/annotationprocessor/src/main/java/org/acra/processor/element/ConfigElement.java b/annotationprocessor/src/main/java/org/acra/processor/element/ConfigElement.java deleted file mode 100644 index e4dfe83ccd..0000000000 --- a/annotationprocessor/src/main/java/org/acra/processor/element/ConfigElement.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (c) 2018 the ACRA team - * - * 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.processor.element; - -import androidx.annotation.NonNull; - -import com.squareup.javapoet.FieldSpec; -import com.squareup.javapoet.MethodSpec; -import com.squareup.javapoet.TypeName; -import com.squareup.javapoet.TypeSpec; - -import org.acra.processor.util.Types; - -import javax.lang.model.element.Modifier; - -import static org.acra.processor.util.Strings.PARAM_0; - -/** - * @author F43nd1r - * @since 10.01.2018 - */ - -public interface ConfigElement extends Element { - - default void addToConfig(@NonNull TypeSpec.Builder builder, @NonNull MethodSpec.Builder constructor) { - //add field - final TypeName type = Types.getImmutableType(getType()); - builder.addField(FieldSpec.builder(type, getName(), Modifier.PRIVATE, Modifier.FINAL).addAnnotations(getAnnotations()).build()); - if (!type.equals(getType())) { - constructor.addStatement("$1L = new $2T($3L.$1L())", getName(), type, PARAM_0); - } else { - constructor.addStatement("$1L = $2L.$1L()", getName(), PARAM_0); - } - //add getter - builder.addMethod(MethodSpec.methodBuilder(getName()) - .addAnnotations(getAnnotations()) - .returns(type) - .addStatement("return $L", getName()) - .addModifiers(Modifier.PUBLIC) - .build()); - } -} diff --git a/annotationprocessor/src/main/java/org/acra/processor/element/ConfigElement.kt b/annotationprocessor/src/main/java/org/acra/processor/element/ConfigElement.kt new file mode 100644 index 0000000000..4dd02e3be8 --- /dev/null +++ b/annotationprocessor/src/main/java/org/acra/processor/element/ConfigElement.kt @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2018 the ACRA team + * + * 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.processor.element + +import com.squareup.kotlinpoet.FunSpec +import com.squareup.kotlinpoet.KModifier +import com.squareup.kotlinpoet.PropertySpec +import com.squareup.kotlinpoet.TypeSpec +import org.acra.processor.util.Strings + +/** + * @author F43nd1r + * @since 10.01.2018 + */ +interface ConfigElement : Element { + + fun addToConfig(builder: TypeSpec.Builder, constructor: FunSpec.Builder) { + //add property + builder.addProperty(PropertySpec.builder(name, type).build()) + constructor.addStatement("%1L = %2L.%1L", name, Strings.PARAM_0) + } +} \ No newline at end of file diff --git a/annotationprocessor/src/main/java/org/acra/processor/element/DelegateMethod.java b/annotationprocessor/src/main/java/org/acra/processor/element/DelegateMethod.java deleted file mode 100644 index b9170e55fc..0000000000 --- a/annotationprocessor/src/main/java/org/acra/processor/element/DelegateMethod.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright (c) 2018 the ACRA team - * - * 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.processor.element; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import com.squareup.javapoet.*; -import org.acra.processor.util.Strings; -import org.acra.processor.util.Types; - -import javax.lang.model.element.Modifier; -import java.util.Collection; -import java.util.stream.Collectors; - -/** - * @author F43nd1r - * @since 10.01.2018 - */ - -class DelegateMethod extends AbstractElement implements BuilderElement.Interface { - private final Collection parameters; - private final Collection typeVariables; - private final Collection modifiers; - private final String javadoc; - - DelegateMethod(@NonNull String name, @NonNull TypeName type, @NonNull Collection annotations, @NonNull Collection parameters, - @NonNull Collection typeVariables, @NonNull Collection modifiers, @Nullable String javadoc) { - super(name, type, annotations); - this.parameters = parameters; - this.typeVariables = typeVariables; - this.modifiers = modifiers; - this.javadoc = javadoc; - } - - @Override - public void addToBuilder(@NonNull TypeSpec.Builder builder, @NonNull ClassName builderName, @NonNull CodeBlock.Builder constructorAlways, @NonNull CodeBlock.Builder constructorWhenAnnotationPresent, CodeBlock.Builder constructorWhenAnnotationMissing) { - final MethodSpec.Builder method = baseMethod(builderName); - if (getType().equals(TypeName.VOID)) { - method.addStatement("$L.$L($L)", Strings.FIELD_DELEGATE, getName(), parameters.stream().map(p -> p.name).collect(Collectors.joining(", "))) - .addStatement("return this"); - } else { - method.addStatement("return $L.$L($L)", Strings.FIELD_DELEGATE, getName(), parameters.stream().map(p -> p.name).collect(Collectors.joining(", "))); - } - builder.addMethod(method.build()); - } - - private MethodSpec.Builder baseMethod(@NonNull ClassName builderName) { - final MethodSpec.Builder method = MethodSpec.methodBuilder(getName()) - .addModifiers(modifiers) - .addParameters(parameters) - .addTypeVariables(typeVariables) - .addAnnotations(getAnnotations()); - if (javadoc != null) { - method.addJavadoc(javadoc.replaceAll("(\n|^) ", "$1")); - } - if (getType().equals(TypeName.VOID)) { - method.returns(builderName) - .addAnnotation(Types.NON_NULL) - .addJavadoc("@return this instance\n"); - } else { - method.returns(getType()); - } - return method; - } - - @Override - public void addToBuilderInterface(@NonNull TypeSpec.Builder builder, @NonNull ClassName builderName) { - if (modifiers.contains(Modifier.PUBLIC)) { - builder.addMethod(baseMethod(builderName).addModifiers(Modifier.ABSTRACT).build()); - } - } - - static class Config extends DelegateMethod implements org.acra.processor.element.ConfigElement { - Config(@NonNull String name, @NonNull TypeName type, @NonNull Collection annotations, @NonNull Collection parameters, - @NonNull Collection typeVariables, @NonNull Collection modifiers, @NonNull String javadoc) { - super(name, type, annotations, parameters, typeVariables, modifiers, javadoc); - } - } -} diff --git a/annotationprocessor/src/main/java/org/acra/processor/element/DelegateMethod.kt b/annotationprocessor/src/main/java/org/acra/processor/element/DelegateMethod.kt new file mode 100644 index 0000000000..a60b50cf31 --- /dev/null +++ b/annotationprocessor/src/main/java/org/acra/processor/element/DelegateMethod.kt @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2018 the ACRA team + * + * 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.processor.element + +import com.squareup.kotlinpoet.AnnotationSpec +import com.squareup.kotlinpoet.ClassName +import com.squareup.kotlinpoet.CodeBlock +import com.squareup.kotlinpoet.FunSpec +import com.squareup.kotlinpoet.KModifier +import com.squareup.kotlinpoet.ParameterSpec +import com.squareup.kotlinpoet.PropertySpec +import com.squareup.kotlinpoet.TypeName +import com.squareup.kotlinpoet.TypeSpec +import com.squareup.kotlinpoet.TypeVariableName +import com.squareup.kotlinpoet.asTypeName +import org.acra.processor.util.Strings +import org.acra.processor.util.Types +import java.util.stream.Collectors + +/** + * @author F43nd1r + * @since 10.01.2018 + */ +internal open class DelegateMethod(override val name: String, override val type: TypeName, override val annotations: Collection, private val parameters: Collection, + private val typeVariables: Collection, private val modifiers: Collection, private val javadoc: String?) : BuilderElement { + override fun addToBuilder(builder: TypeSpec.Builder, builderName: ClassName, constructor: CodeBlock.Builder) { + val method = baseMethod(builderName) + if (type == Unit::class.asTypeName()) { + method.addStatement("%L.%L(%L)", Strings.FIELD_DELEGATE, name, parameters.stream().map { p: ParameterSpec -> p.name }.collect(Collectors.joining(", "))) + .addStatement("return this") + } else { + method.addStatement("return %L.%L(%L)", Strings.FIELD_DELEGATE, name, parameters.stream().map { p: ParameterSpec -> p.name }.collect(Collectors.joining(", "))) + } + builder.addFunction(method.build()) + } + + private fun baseMethod(builderName: ClassName): FunSpec.Builder { + val method = FunSpec.builder(name) + .addModifiers(modifiers) + .addParameters(parameters) + .addTypeVariables(typeVariables) + .addAnnotations(annotations) + if (javadoc != null) { + method.addKdoc(javadoc.replace("(\n|^) ".toRegex(), "$1")) + } + if (type == Unit::class.asTypeName()) { + method.returns(builderName) + .addKdoc("@return this instance\n") + } else { + method.returns(type) + } + return method + } + + internal class Config(name: String, type: TypeName, annotations: Collection, parameters: Collection, + typeVariables: Collection, modifiers: Collection, javadoc: String?) : DelegateMethod(name, type, annotations, parameters, typeVariables, modifiers, javadoc), ConfigElement{ + + + override fun addToConfig(builder: TypeSpec.Builder, constructor: FunSpec.Builder) { + builder.addProperty(PropertySpec.builder(name, type).build()) + constructor.addStatement("%1L = %2L.%1L()", name, Strings.PARAM_0) + } + } +} \ No newline at end of file diff --git a/acra-core-ktx/build.gradle.kts b/annotationprocessor/src/main/java/org/acra/processor/element/Element.kt similarity index 64% rename from acra-core-ktx/build.gradle.kts rename to annotationprocessor/src/main/java/org/acra/processor/element/Element.kt index da59a9b856..0caa58ef3e 100644 --- a/acra-core-ktx/build.gradle.kts +++ b/annotationprocessor/src/main/java/org/acra/processor/element/Element.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 + * Copyright (c) 2018 the ACRA team * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,15 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -plugins { - kotlin("android") apply false - `acra-android-library` - `acra-publish` -} -//late apply is necessary because android plugin can't be detected otherwise -apply(plugin = "kotlin-android") +package org.acra.processor.element -dependencies { - implementation(kotlin("stdlib-jdk8")) - acraCore() +import com.squareup.kotlinpoet.AnnotationSpec +import com.squareup.kotlinpoet.TypeName + +/** + * @author F43nd1r + * @since 12.01.2018 + */ +interface Element { + val name: String + val type: TypeName + val annotations: Collection } \ No newline at end of file diff --git a/annotationprocessor/src/main/java/org/acra/processor/element/ElementFactory.java b/annotationprocessor/src/main/java/org/acra/processor/element/ElementFactory.java deleted file mode 100644 index e303d980f2..0000000000 --- a/annotationprocessor/src/main/java/org/acra/processor/element/ElementFactory.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright (c) 2018 the ACRA team - * - * 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.processor.element; - -import androidx.annotation.NonNull; -import com.squareup.javapoet.*; -import org.apache.commons.lang3.tuple.Pair; - -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.util.Elements; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; - -/** - * @author F43nd1r - * @since 10.01.2018 - */ - -public class ElementFactory { - - private final Elements elements; - - - public ElementFactory(@NonNull Elements elements) { - this.elements = elements; - } - - @NonNull - private static Pair, Set> getAnnotations(@NonNull ExecutableElement method) { - final List specs = method.getAnnotationMirrors().stream().map(AnnotationSpec::get).collect(Collectors.toList()); - final Set markerAnnotations = new HashSet<>(); - for (final Iterator iterator = specs.iterator(); iterator.hasNext(); ) { - final AnnotationSpec spec = iterator.next(); - for (ClassName a : org.acra.processor.util.Types.MARKER_ANNOTATIONS) { - if (a.equals(spec.type)) { - iterator.remove(); - markerAnnotations.add(a); - } - } - } - return Pair.of(specs, markerAnnotations); - } - - @NonNull - public Element fromAnnotationMethod(@NonNull ExecutableElement method) { - final Pair, Set> annotations = getAnnotations(method); - return new AnnotationField.Normal(method.getSimpleName().toString(), TypeName.get(method.getReturnType()), annotations.getLeft(), annotations.getRight(), method.getDefaultValue(), - elements.getDocComment(method)); - } - - @NonNull - public Element fromStringResourceAnnotationMethod(@NonNull ExecutableElement method) { - final Pair, Set> annotations = getAnnotations(method); - return new AnnotationField.StringResource(method.getSimpleName().toString(), annotations.getLeft(), annotations.getRight(), - method.getDefaultValue(), elements.getDocComment(method)); - } - - @NonNull - public Element fromBuilderDelegateMethod(@NonNull ExecutableElement method) { - return new DelegateMethod(method.getSimpleName().toString(), TypeName.get(method.getReturnType()), getAnnotations(method).getLeft(), - method.getParameters().stream().map(p -> ParameterSpec.builder(TypeName.get(p.asType()), p.getSimpleName().toString()).build()).collect(Collectors.toList()), - method.getTypeParameters().stream().map(TypeVariableName::get).collect(Collectors.toList()), method.getModifiers(), elements.getDocComment(method)); - } - - @NonNull - public Element fromConfigDelegateMethod(@NonNull ExecutableElement method) { - return new DelegateMethod.Config(method.getSimpleName().toString(), TypeName.get(method.getReturnType()), getAnnotations(method).getLeft(), - method.getParameters().stream().map(p -> ParameterSpec.builder(TypeName.get(p.asType()), p.getSimpleName().toString()).build()).collect(Collectors.toList()), - method.getTypeParameters().stream().map(TypeVariableName::get).collect(Collectors.toList()), method.getModifiers(), elements.getDocComment(method)); - } - - @NonNull - public Element fromPreBuildDelegateMethod(@NonNull ExecutableElement method) { - return new PreBuildMethod(method.getSimpleName().toString()); - } - - @NonNull - public Element fromTransformDelegateMethod(@NonNull ExecutableElement method, Element transform) { - if (transform instanceof TransformedField.Transformable) { - return new TransformedField(method.getSimpleName().toString(), TypeName.get(method.getReturnType()), (TransformedField.Transformable) transform); - } - return transform; - } - - @NonNull - public Element fromDelegateConstructor(@NonNull ExecutableElement constructor, boolean hasContextParameter) { - return new BuilderElement.Delegate(TypeName.get(constructor.getEnclosingElement().asType()), hasContextParameter); - } -} diff --git a/annotationprocessor/src/main/java/org/acra/processor/element/ElementFactory.kt b/annotationprocessor/src/main/java/org/acra/processor/element/ElementFactory.kt new file mode 100644 index 0000000000..f8b4377a40 --- /dev/null +++ b/annotationprocessor/src/main/java/org/acra/processor/element/ElementFactory.kt @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2018 the ACRA team + * + * 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.processor.element + +import androidx.annotation.NonNull +import androidx.annotation.Nullable +import com.squareup.kotlinpoet.AnnotationSpec +import com.squareup.kotlinpoet.ClassName +import com.squareup.kotlinpoet.ParameterSpec +import com.squareup.kotlinpoet.asTypeName +import com.squareup.kotlinpoet.asTypeVariableName +import org.acra.processor.util.Types +import org.acra.processor.util.javaToKotlinType +import org.acra.processor.util.toKModifier +import org.apache.commons.lang3.tuple.Pair +import java.util.* +import javax.lang.model.element.ExecutableElement +import javax.lang.model.util.Elements + +/** + * @author F43nd1r + * @since 10.01.2018 + */ +class ElementFactory(private val elements: Elements) { + fun fromAnnotationMethod(method: ExecutableElement): Element { + val annotations = getAnnotations(method) + return AnnotationField.Normal(method.simpleName.toString(), method.returnType.asTypeName().javaToKotlinType(), annotations.left, annotations.right, method.defaultValue, + elements.getDocComment(method)) + } + + fun fromClassAnnotationMethod(method: ExecutableElement): Element { + val annotations = getAnnotations(method) + return AnnotationField.Clazz(method.simpleName.toString(), method.returnType.asTypeName().javaToKotlinType(), annotations.left, annotations.right, method.defaultValue, + elements.getDocComment(method)) + } + + fun fromStringResourceAnnotationMethod(method: ExecutableElement): Element { + val annotations = getAnnotations(method) + return AnnotationField.StringResource(method.simpleName.toString(), annotations.left, annotations.right, + method.defaultValue, elements.getDocComment(method)) + } + + fun fromBuilderDelegateMethod(method: ExecutableElement): Element { + return DelegateMethod(method.simpleName.toString(), method.returnType.asTypeName().javaToKotlinType(), getAnnotations(method).left, + method.parameters.map { ParameterSpec.builder(it.simpleName.toString(), it.asType().asTypeName().javaToKotlinType()).build() }, + method.typeParameters.map { it.asTypeVariableName() }, method.modifiers.mapNotNull { it.toKModifier() }, elements.getDocComment(method)) + } + + fun fromConfigDelegateMethod(method: ExecutableElement): Element { + return DelegateMethod.Config(method.simpleName.toString(), method.returnType.asTypeName().javaToKotlinType(), getAnnotations(method).left, + method.parameters.map { ParameterSpec.builder(it.simpleName.toString(), it.asType().asTypeName().javaToKotlinType()).build() }, + method.typeParameters.map { it.asTypeVariableName() }, method.modifiers.mapNotNull { it.toKModifier() }, elements.getDocComment(method)) + } + + fun fromPreBuildDelegateMethod(method: ExecutableElement): Element { + return PreBuildMethod(method.simpleName.toString()) + } + + fun fromTransformDelegateMethod(method: ExecutableElement, transform: Element): Element { + return if (transform is TransformedField.Transformable) { + TransformedField(method.simpleName.toString(), method.returnType.asTypeName().javaToKotlinType(), transform) + } else transform + } + + fun fromDelegateConstructor(constructor: ExecutableElement, hasContextParameter: Boolean): Element { + return BuilderElement.Delegate(constructor.enclosingElement.asType().asTypeName(), hasContextParameter) + } + + companion object { + private fun getAnnotations(method: ExecutableElement): Pair, Set> { + val specs = method.annotationMirrors.map { AnnotationSpec.get(it) }.toMutableList() + specs.remove(AnnotationSpec.builder(NonNull::class).build()) + specs.remove(AnnotationSpec.builder(Nullable::class).build()) + specs.replaceAll { if (it == AnnotationSpec.builder(java.lang.Deprecated::class).build()) AnnotationSpec.builder(Deprecated::class).addMember("message = \"see doc\"").build() else it } + val markerAnnotations: MutableSet = HashSet() + val iterator = specs.iterator() + while (iterator.hasNext()) { + val spec = iterator.next() + for (a in Types.MARKER_ANNOTATIONS) { + if (a == spec.typeName) { + iterator.remove() + markerAnnotations.add(a) + } + } + } + return Pair.of(specs, markerAnnotations) + } + } +} \ No newline at end of file diff --git a/annotationprocessor/src/main/java/org/acra/processor/element/PreBuildMethod.java b/annotationprocessor/src/main/java/org/acra/processor/element/PreBuildMethod.kt similarity index 58% rename from annotationprocessor/src/main/java/org/acra/processor/element/PreBuildMethod.java rename to annotationprocessor/src/main/java/org/acra/processor/element/PreBuildMethod.kt index 2e013b2083..03ed58d439 100644 --- a/annotationprocessor/src/main/java/org/acra/processor/element/PreBuildMethod.java +++ b/annotationprocessor/src/main/java/org/acra/processor/element/PreBuildMethod.kt @@ -13,27 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +package org.acra.processor.element -package org.acra.processor.element; - -import androidx.annotation.NonNull; - -import org.acra.processor.creator.BuildMethodCreator; - -import java.util.Collections; +import com.squareup.kotlinpoet.asTypeName +import org.acra.processor.creator.BuildMethodCreator /** * @author F43nd1r * @since 11.01.2018 */ - -class PreBuildMethod extends AbstractElement implements ValidatedElement { - PreBuildMethod(@NonNull String name) { - super(name, null, Collections.emptyList()); - } - - @Override - public void addToBuildMethod(@NonNull BuildMethodCreator method) { - method.addDelegateCall(getName()); +internal class PreBuildMethod(name: String) : AbstractElement(name, Unit::class.asTypeName(), emptyList()), ValidatedElement { + override fun addToBuildMethod(method: BuildMethodCreator) { + method.addDelegateCall(name) } -} +} \ No newline at end of file diff --git a/annotationprocessor/src/main/java/org/acra/processor/element/TransformedField.java b/annotationprocessor/src/main/java/org/acra/processor/element/TransformedField.java deleted file mode 100644 index 8dbb755997..0000000000 --- a/annotationprocessor/src/main/java/org/acra/processor/element/TransformedField.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (c) 2018 the ACRA team - * - * 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.processor.element; - -import androidx.annotation.NonNull; - -import com.squareup.javapoet.AnnotationSpec; -import com.squareup.javapoet.ClassName; -import com.squareup.javapoet.CodeBlock; -import com.squareup.javapoet.MethodSpec; -import com.squareup.javapoet.TypeName; -import com.squareup.javapoet.TypeSpec; - -import org.acra.processor.creator.BuildMethodCreator; -import org.acra.processor.util.Strings; - -import java.util.Collection; - -/** - * @author F43nd1r - * @since 11.01.2018 - */ - -class TransformedField implements ConfigElement, BuilderElement.Interface, ValidatedElement { - private final String name; - private final TypeName type; - private final Transformable transform; - - TransformedField(@NonNull String name, @NonNull TypeName type, @NonNull Transformable transform) { - this.name = name; - this.type = type; - this.transform = transform; - } - - @Override - public void addToBuilder(@NonNull TypeSpec.Builder builder, @NonNull ClassName builderName, @NonNull CodeBlock.Builder constructorAlways, - @NonNull CodeBlock.Builder constructorWhenAnnotationPresent, CodeBlock.Builder constructorWhenAnnotationMissing) { - transform.addWithoutGetter(builder, builderName, constructorAlways, constructorWhenAnnotationPresent, constructorWhenAnnotationMissing); - addGetter(builder); - } - - @Override - public void configureGetter(@NonNull MethodSpec.Builder builder) { - builder.addStatement("return $L.$L($L)", Strings.FIELD_DELEGATE, name, getName()); - } - - @Override - public void addToBuildMethod(@NonNull BuildMethodCreator method) { - transform.addToBuildMethod(method); - } - - @Override - public void addToBuilderInterface(@NonNull TypeSpec.Builder builder, @NonNull ClassName builderName) { - transform.addToBuilderInterface(builder, builderName); - } - - @Override - public String getName() { - return transform.getName(); - } - - @Override - public TypeName getType() { - return type; - } - - @Override - public Collection getAnnotations() { - return transform.getAnnotations(); - } - - public interface Transformable extends ConfigElement, Interface, ValidatedElement { - void addWithoutGetter(TypeSpec.Builder builder, ClassName builderName, CodeBlock.Builder constructorAlways, CodeBlock.Builder constructorWhenAnnotationPresent, CodeBlock.Builder constructorWhenAnnotationMissing); - } -} diff --git a/annotationprocessor/src/main/java/org/acra/processor/element/TransformedField.kt b/annotationprocessor/src/main/java/org/acra/processor/element/TransformedField.kt new file mode 100644 index 0000000000..7ff2682856 --- /dev/null +++ b/annotationprocessor/src/main/java/org/acra/processor/element/TransformedField.kt @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2018 the ACRA team + * + * 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.processor.element + +import com.squareup.kotlinpoet.ClassName +import com.squareup.kotlinpoet.CodeBlock +import com.squareup.kotlinpoet.FunSpec +import com.squareup.kotlinpoet.PropertySpec +import com.squareup.kotlinpoet.TypeName +import com.squareup.kotlinpoet.TypeSpec +import org.acra.processor.creator.BuildMethodCreator +import org.acra.processor.util.Strings + +/** + * @author F43nd1r + * @since 11.01.2018 + */ +class TransformedField(val transformName: String, override val type: TypeName, private val transform: Transformable) : ConfigElement, BuilderElement, ValidatedElement { + override val name: String + get() = transform.name + override fun addToBuilder(builder: TypeSpec.Builder, builderName: ClassName, constructor: CodeBlock.Builder) { + transform.addToBuilder(builder, builderName, constructor) + builder.addFunction(FunSpec.builder(transformName).returns(type).addStatement("return %L.%L(%L)", Strings.FIELD_DELEGATE, transformName, name).build()) + } + + override fun addToBuildMethod(method: BuildMethodCreator) { + transform.addToBuildMethod(method) + } + + override fun addToConfig(builder: TypeSpec.Builder, constructor: FunSpec.Builder) { + builder.addProperty(PropertySpec.builder(name, type).build()) + constructor.addStatement("%L = %L.%L()", name, Strings.PARAM_0, transformName) + } + + override val annotations: Collection + get() = transform.annotations + + interface Transformable : ConfigElement, BuilderElement, ValidatedElement { + } +} \ No newline at end of file diff --git a/annotationprocessor/src/main/java/org/acra/processor/element/ValidatedElement.java b/annotationprocessor/src/main/java/org/acra/processor/element/ValidatedElement.kt similarity index 72% rename from annotationprocessor/src/main/java/org/acra/processor/element/ValidatedElement.java rename to annotationprocessor/src/main/java/org/acra/processor/element/ValidatedElement.kt index 7cec45ff12..87c97834fd 100644 --- a/annotationprocessor/src/main/java/org/acra/processor/element/ValidatedElement.java +++ b/annotationprocessor/src/main/java/org/acra/processor/element/ValidatedElement.kt @@ -13,17 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +package org.acra.processor.element -package org.acra.processor.element; - -import androidx.annotation.NonNull; -import org.acra.processor.creator.BuildMethodCreator; +import org.acra.processor.creator.BuildMethodCreator /** * @author F43nd1r * @since 11.01.2018 */ - -public interface ValidatedElement extends Element { - void addToBuildMethod(@NonNull BuildMethodCreator method); -} +interface ValidatedElement : Element { + fun addToBuildMethod(method: BuildMethodCreator) +} \ No newline at end of file diff --git a/annotationprocessor/src/main/java/org/acra/processor/util/IsValidResourceVisitor.java b/annotationprocessor/src/main/java/org/acra/processor/util/IsValidResourceVisitor.java deleted file mode 100644 index 7c7dd41996..0000000000 --- a/annotationprocessor/src/main/java/org/acra/processor/util/IsValidResourceVisitor.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.acra.processor.util; - -import javax.lang.model.util.SimpleAnnotationValueVisitor8; - -public class IsValidResourceVisitor extends SimpleAnnotationValueVisitor8 { - @Override - protected Boolean defaultAction(Object o, Void aVoid) { - return false; - } - - @Override - public Boolean visitInt(int i, Void aVoid) { - return i != 0; - } -} diff --git a/annotationprocessor/src/main/java/org/acra/processor/util/IsValidResourceVisitor.kt b/annotationprocessor/src/main/java/org/acra/processor/util/IsValidResourceVisitor.kt new file mode 100644 index 0000000000..a501110e4f --- /dev/null +++ b/annotationprocessor/src/main/java/org/acra/processor/util/IsValidResourceVisitor.kt @@ -0,0 +1,13 @@ +package org.acra.processor.util + +import javax.lang.model.util.SimpleAnnotationValueVisitor8 + +class IsValidResourceVisitor : SimpleAnnotationValueVisitor8() { + override fun defaultAction(o: Any, u: Unit?): Boolean { + return false + } + + override fun visitInt(i: Int, u: Unit?): Boolean { + return i != 0 + } +} \ No newline at end of file diff --git a/annotationprocessor/src/main/java/org/acra/processor/util/Strings.java b/annotationprocessor/src/main/java/org/acra/processor/util/Strings.java deleted file mode 100644 index 104a2052ec..0000000000 --- a/annotationprocessor/src/main/java/org/acra/processor/util/Strings.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (c) 2018 the ACRA team - * - * 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.processor.util; - -import androidx.annotation.NonNull; -import com.squareup.javapoet.JavaFile; -import com.squareup.javapoet.TypeName; -import com.squareup.javapoet.TypeSpec; - -import javax.annotation.processing.Filer; -import java.io.IOException; -import java.text.DateFormat; -import java.util.Calendar; - -/** - * @author F43nd1r - * @since 08.01.2018 - */ - -public final class Strings { - public static final String PREFIX_RES = "res"; - public static final String PREFIX_SETTER = "set"; - public static final String PARAM_0 = "arg0"; - public static final String VAR_ANNOTATION = "annotation"; - public static final String FIELD_DELEGATE = "delegate"; - public static final String FIELD_CONTEXT = "context"; - public static final String FIELD_ENABLED = "enabled"; - public static final String PACKAGE = "org.acra.config"; - public static final String CONTEXT = "android.content.Context"; - public static final String CONFIGURATION_BUILDER_FACTORY = "org.acra.config.ConfigurationBuilderFactory"; - private static final DateFormat DATE_FORMAT = DateFormat.getDateTimeInstance(); - - private Strings() { - } - - public static void addClassJavadoc(@NonNull TypeSpec.Builder builder, @NonNull TypeName base) { - builder.addJavadoc("Class generated based on {@link $T} ($L)\n", base, DATE_FORMAT.format(Calendar.getInstance().getTime())); - } - - /** - * Writes the given class to a respective file in the configuration package - * - * @param filer filer to write to - * @param typeSpec the class - * @throws IOException if writing fails - */ - public static void writeClass(@NonNull Filer filer, @NonNull TypeSpec typeSpec) throws IOException { - JavaFile.builder(PACKAGE, typeSpec) - .skipJavaLangImports(true) - .indent(" ") - .addFileComment("Copyright (c) " + Calendar.getInstance().get(Calendar.YEAR) + "\n\n" + - "Licensed under the Apache License, Version 2.0 (the \"License\");\n" + - "you may not use this file except in compliance with the License.\n\n" + - "http://www.apache.org/licenses/LICENSE-2.0\n\n" + - "Unless required by applicable law or agreed to in writing, software\n" + - "distributed under the License is distributed on an \"AS IS\" BASIS,\n" + - "WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n" + - "See the License for the specific language governing permissions and\n" + - "limitations under the License.") - .build() - .writeTo(filer); - } -} diff --git a/annotationprocessor/src/main/java/org/acra/processor/util/Strings.kt b/annotationprocessor/src/main/java/org/acra/processor/util/Strings.kt new file mode 100644 index 0000000000..fb56ca4999 --- /dev/null +++ b/annotationprocessor/src/main/java/org/acra/processor/util/Strings.kt @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2018 the ACRA team + * + * 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.processor.util + +import com.squareup.kotlinpoet.FileSpec +import com.squareup.kotlinpoet.TypeName +import com.squareup.kotlinpoet.TypeSpec +import org.apache.commons.text.WordUtils +import java.io.File +import java.io.IOException +import java.text.DateFormat +import java.util.* +import javax.annotation.processing.ProcessingEnvironment +import javax.tools.Diagnostic + +/** + * @author F43nd1r + * @since 08.01.2018 + */ +object Strings { + const val PREFIX_RES = "res" + const val PREFIX_SETTER = "set" + const val PREFIX_GETTER = "get" + const val PARAM_0 = "arg0" + const val VAR_ANNOTATION = "annotation" + const val FIELD_DELEGATE = "delegate" + const val FIELD_CONTEXT = "context" + const val FIELD_ENABLED = "enabled" + const val PACKAGE = "org.acra.config" + const val CONTEXT = "android.content.Context" + const val CONFIGURATION_BUILDER_FACTORY = "org.acra.config.ConfigurationBuilderFactory" + private val DATE_FORMAT = DateFormat.getDateTimeInstance() + fun ensurePrefix(prefix: String, value: String): String { + return if (value.startsWith(prefix)) value else prefix + WordUtils.capitalize(value) + } + + fun addClassKdoc(builder: TypeSpec.Builder, base: TypeName) { + builder.addKdoc("Class generated based on {@link %T} (%L)\n", base, DATE_FORMAT.format(Calendar.getInstance().time)) + } + + /** + * Writes the given class to a respective file in the configuration package + * + * @param filer filer to write to + * @param typeSpec the class + * @throws IOException if writing fails + */ + @Throws(IOException::class) + fun writeClass(processingEnv: ProcessingEnvironment, typeSpec: TypeSpec) { + FileSpec.builder(PACKAGE, typeSpec.name!!) + .addType(typeSpec) + .indent(" ") + .writeTo(processingEnv) + } +} \ No newline at end of file diff --git a/annotationprocessor/src/main/java/org/acra/processor/util/ToCodeBlockVisitor.java b/annotationprocessor/src/main/java/org/acra/processor/util/ToCodeBlockVisitor.java deleted file mode 100644 index e6f4cf21f8..0000000000 --- a/annotationprocessor/src/main/java/org/acra/processor/util/ToCodeBlockVisitor.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (c) 2018 the ACRA team - * - * 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.processor.util; - -import androidx.annotation.NonNull; - -import com.squareup.javapoet.ArrayTypeName; -import com.squareup.javapoet.CodeBlock; -import com.squareup.javapoet.ParameterizedTypeName; -import com.squareup.javapoet.TypeName; - -import java.util.List; - -import javax.lang.model.element.AnnotationValue; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.SimpleAnnotationValueVisitor8; - -/** - * @author F43nd1r - * @since 12.01.2018 - */ -public class ToCodeBlockVisitor extends SimpleAnnotationValueVisitor8 { - private final TypeName type; - - public ToCodeBlockVisitor(TypeName type) { - this.type = type; - } - - @NonNull - @Override - protected CodeBlock defaultAction(Object o, Void v) { - return CodeBlock.of("$L", o); - } - - @NonNull - @Override - public CodeBlock visitString(String s, Void v) { - return CodeBlock.of("$S", s); - } - - @NonNull - @Override - public CodeBlock visitEnumConstant(@NonNull VariableElement c, Void v) { - return CodeBlock.of("$T.$L", c.asType(), c.getSimpleName()); - } - - @NonNull - @Override - public CodeBlock visitType(TypeMirror t, Void v) { - return CodeBlock.of("$T.class", t); - } - - @NonNull - @Override - public CodeBlock visitArray(@NonNull List values, Void v) { - ArrayTypeName arrayTypeName = (ArrayTypeName) type; - if (arrayTypeName.componentType instanceof ParameterizedTypeName) { - arrayTypeName = ArrayTypeName.of(((ParameterizedTypeName) arrayTypeName.componentType).rawType); - } - return CodeBlock.of("new $T{$L}", arrayTypeName, values.stream().map(value -> value.accept(this, null)) - .reduce((c1, c2) -> CodeBlock.builder().add(c1).add(", ").add(c2).build()).orElseGet(() -> CodeBlock.builder().build())); - } -} diff --git a/annotationprocessor/src/main/java/org/acra/processor/util/ToCodeBlockVisitor.kt b/annotationprocessor/src/main/java/org/acra/processor/util/ToCodeBlockVisitor.kt new file mode 100644 index 0000000000..69e14b887a --- /dev/null +++ b/annotationprocessor/src/main/java/org/acra/processor/util/ToCodeBlockVisitor.kt @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2018 the ACRA team + * + * 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.processor.util + +import com.squareup.kotlinpoet.CodeBlock +import com.squareup.kotlinpoet.TypeName +import javax.lang.model.element.AnnotationValue +import javax.lang.model.element.VariableElement +import javax.lang.model.type.TypeMirror +import javax.lang.model.util.SimpleAnnotationValueVisitor8 + +/** + * @author F43nd1r + * @since 12.01.2018 + */ +class ToCodeBlockVisitor(private val type: TypeName) : SimpleAnnotationValueVisitor8() { + override fun defaultAction(o: Any, u: Unit?): CodeBlock { + return CodeBlock.of("%L", o) + } + + override fun visitString(s: String, u: Unit?): CodeBlock { + return CodeBlock.of("%S", s) + } + + override fun visitEnumConstant(c: VariableElement, u: Unit?): CodeBlock { + return CodeBlock.of("%T.%L", c.asType(), c.simpleName) + } + + override fun visitType(t: TypeMirror, u: Unit?): CodeBlock { + return CodeBlock.of("%T::class.java", t) + } + + override fun visitArray(values: List, u: Unit?): CodeBlock { + return CodeBlock.of("arrayOf(%L)", values.map { value: AnnotationValue? -> value!!.accept(this, null) } + .reduceOrNull { c1: CodeBlock, c2: CodeBlock -> CodeBlock.builder().add(c1).add(", ").add(c2).build() } ?: CodeBlock.builder().build()) + } +} \ No newline at end of file diff --git a/annotationprocessor/src/main/java/org/acra/processor/util/Types.java b/annotationprocessor/src/main/java/org/acra/processor/util/Types.java deleted file mode 100644 index 202a0accbc..0000000000 --- a/annotationprocessor/src/main/java/org/acra/processor/util/Types.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright (c) 2018 the ACRA team - * - * 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.processor.util; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.StringRes; -import com.squareup.javapoet.*; -import org.acra.annotation.*; -import org.acra.collections.ImmutableList; -import org.acra.collections.ImmutableMap; -import org.acra.collections.ImmutableSet; - -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.Modifier; -import javax.lang.model.element.TypeElement; -import javax.lang.model.util.ElementFilter; -import javax.tools.Diagnostic; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; - -/** - * @author F43nd1r - * @since 11.01.2018 - */ - -public final class Types { - public static final ClassName IMMUTABLE_MAP = ClassName.get(ImmutableMap.class); - public static final ClassName IMMUTABLE_SET = ClassName.get(ImmutableSet.class); - public static final ClassName IMMUTABLE_LIST = ClassName.get(ImmutableList.class); - public static final ClassName MAP = ClassName.get(Map.class); - public static final ClassName SET = ClassName.get(Set.class); - public static final ClassName LIST = ClassName.get(List.class); - public static final ClassName STRING = ClassName.get(String.class); - public static final AnnotationSpec NULLABLE = AnnotationSpec.builder(Nullable.class).build(); - public static final AnnotationSpec NON_NULL = AnnotationSpec.builder(NonNull.class).build(); - public static final AnnotationSpec STRING_RES = AnnotationSpec.builder(StringRes.class).build(); - public static final AnnotationSpec DEPRECATED = AnnotationSpec.builder(Deprecated.class).build(); - public static final ClassName ANY_NON_DEFAULT = ClassName.get(AnyNonDefault.class); - public static final ClassName BUILDER_METHOD = ClassName.get(BuilderMethod.class); - public static final ClassName CONFIGURATION = ClassName.get(Configuration.class); - public static final ClassName CONFIGURATION_VALUE = ClassName.get(ConfigurationValue.class); - public static final ClassName INSTANTIATABLE = ClassName.get(Instantiatable.class); - public static final ClassName NON_EMPTY = ClassName.get(NonEmpty.class); - public static final ClassName PRE_BUILD = ClassName.get(PreBuild.class); - public static final ClassName TRANSFORM = ClassName.get(Transform.class); - public static final ClassName CONTEXT = ClassName.bestGuess(Strings.CONTEXT); - public static final ClassName CONFIGURATION_BUILDER_FACTORY = ClassName.bestGuess(Strings.CONFIGURATION_BUILDER_FACTORY); - public static final List MARKER_ANNOTATIONS = Arrays.asList(ANY_NON_DEFAULT, BUILDER_METHOD, CONFIGURATION, CONFIGURATION_VALUE, INSTANTIATABLE, NON_EMPTY, PRE_BUILD, TRANSFORM); - - private Types() { - } - - public static MethodSpec.Builder overriding(ExecutableElement method) { - return MethodSpec.methodBuilder(method.getSimpleName().toString()) - .addAnnotation(Override.class) - .addModifiers(method.getModifiers().stream().filter(modifier -> modifier != Modifier.ABSTRACT).collect(Collectors.toList())) - .returns(TypeName.get(method.getReturnType())) - .varargs(method.isVarArgs()) - .addExceptions(method.getThrownTypes().stream().map(TypeName::get).collect(Collectors.toList())) - .addTypeVariables(method.getTypeParameters().stream().map(TypeVariableName::get).collect(Collectors.toList())) - .addParameters(method.getParameters().stream().map(element -> ParameterSpec.get(element).toBuilder() - .addAnnotations(element.getAnnotationMirrors().stream().map(AnnotationSpec::get).collect(Collectors.toList())).build()).collect(Collectors.toList())); - } - - public static ExecutableElement getOnlyMethod(ProcessingEnvironment processingEnv, String className) { - final TypeElement typeElement = processingEnv.getElementUtils().getTypeElement(className); - final List elements = ElementFilter.methodsIn(typeElement.getEnclosedElements()); - if (elements.size() == 1) { - return elements.get(0); - } else { - processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Needs exactly one method", typeElement); - throw new IllegalArgumentException(); - } - } - - @NonNull - public static TypeName getImmutableType(TypeName type) { - if (type instanceof ParameterizedTypeName) { - final TypeName genericType = ((ParameterizedTypeName) type).rawType; - if (MAP.equals(genericType)) { - type = getWithParams(IMMUTABLE_MAP, (ParameterizedTypeName) type); - } else if (SET.equals(genericType)) { - return getWithParams(IMMUTABLE_SET, (ParameterizedTypeName) type); - } else if (LIST.equals(genericType)) { - type = getWithParams(IMMUTABLE_LIST, (ParameterizedTypeName) type); - } - } else if (type instanceof ArrayTypeName) { - type = ParameterizedTypeName.get(IMMUTABLE_LIST, ((ArrayTypeName) type).componentType); - } - return type; - } - - private static TypeName getWithParams(@NonNull ClassName baseType, @NonNull ParameterizedTypeName parameterType) { - return ParameterizedTypeName.get(baseType, parameterType.typeArguments.toArray(new TypeName[parameterType.typeArguments.size()])); - } -} diff --git a/annotationprocessor/src/main/java/org/acra/processor/util/Types.kt b/annotationprocessor/src/main/java/org/acra/processor/util/Types.kt new file mode 100644 index 0000000000..77f3f5a305 --- /dev/null +++ b/annotationprocessor/src/main/java/org/acra/processor/util/Types.kt @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2018 the ACRA team + * + * 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.processor.util + +import androidx.annotation.StringRes +import com.squareup.kotlinpoet.AnnotationSpec +import com.squareup.kotlinpoet.ClassName +import com.squareup.kotlinpoet.FunSpec +import com.squareup.kotlinpoet.KModifier +import com.squareup.kotlinpoet.ParameterSpec +import com.squareup.kotlinpoet.ParameterizedTypeName +import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy +import com.squareup.kotlinpoet.TypeName +import com.squareup.kotlinpoet.asClassName +import com.squareup.kotlinpoet.asTypeName +import com.squareup.kotlinpoet.asTypeVariableName +import org.acra.annotation.AnyNonDefault +import org.acra.annotation.BuilderMethod +import org.acra.annotation.Configuration +import org.acra.annotation.ConfigurationValue +import org.acra.annotation.Instantiatable +import org.acra.annotation.NonEmpty +import org.acra.annotation.PreBuild +import org.acra.annotation.Transform +import org.acra.collections.ImmutableList +import org.acra.collections.ImmutableMap +import org.acra.collections.ImmutableSet +import java.lang.Deprecated +import javax.annotation.processing.ProcessingEnvironment +import javax.lang.model.element.ExecutableElement +import javax.lang.model.element.VariableElement +import javax.lang.model.util.ElementFilter +import javax.tools.Diagnostic +import kotlin.IllegalArgumentException +import kotlin.String + +/** + * @author F43nd1r + * @since 11.01.2018 + */ +object Types { + val IMMUTABLE_MAP: ClassName = ImmutableMap::class.asClassName() + val IMMUTABLE_SET: ClassName = ImmutableSet::class.asClassName() + val IMMUTABLE_LIST: ClassName = ImmutableList::class.asClassName() + val MAP: ClassName = MutableMap::class.asClassName() + val SET: ClassName = MutableSet::class.asClassName() + val LIST: ClassName = MutableList::class.asClassName() + @JvmField + val STRING: ClassName = ClassName("kotlin", "String") + @JvmField + val STRING_RES = AnnotationSpec.builder(StringRes::class.java).build() + @JvmField + val DEPRECATED = AnnotationSpec.builder(kotlin.Deprecated::class.java).build() + @JvmField + val ANY_NON_DEFAULT: ClassName = AnyNonDefault::class.asClassName() + val BUILDER_METHOD: ClassName = BuilderMethod::class.asClassName() + val CONFIGURATION: ClassName = Configuration::class.asClassName() + val CONFIGURATION_VALUE: ClassName = ConfigurationValue::class.asClassName() + @JvmField + val INSTANTIATABLE: ClassName = Instantiatable::class.asClassName() + @JvmField + val NON_EMPTY: ClassName = NonEmpty::class.asClassName() + val PRE_BUILD: ClassName = PreBuild::class.asClassName() + val TRANSFORM: ClassName = Transform::class.asClassName() + @JvmField + val CONTEXT: ClassName = ClassName.bestGuess(Strings.CONTEXT) + val CONFIGURATION_BUILDER_FACTORY: ClassName = ClassName.bestGuess(Strings.CONFIGURATION_BUILDER_FACTORY) + @JvmField + val MARKER_ANNOTATIONS = listOf(ANY_NON_DEFAULT, BUILDER_METHOD, CONFIGURATION, CONFIGURATION_VALUE, INSTANTIATABLE, NON_EMPTY, PRE_BUILD, TRANSFORM) + @JvmStatic + fun overriding(method: ExecutableElement): FunSpec.Builder { + return FunSpec.builder(method.simpleName.toString()) + .addModifiers(KModifier.OVERRIDE, *method.modifiers.mapNotNull { it.toKModifier() }.minus(KModifier.ABSTRACT).toTypedArray()) + .returns(method.returnType.asTypeName()) + .addTypeVariables(method.typeParameters.map { it.asTypeVariableName() }) + .addParameters(method.parameters.map { element: VariableElement -> + ParameterSpec.builder(element.simpleName.toString(), element.asType().asTypeName()).addAnnotations(element.annotationMirrors.map { AnnotationSpec.get(it) }).build() + }) + } + + fun getOnlyMethod(processingEnv: ProcessingEnvironment, className: String?): ExecutableElement { + val typeElement = processingEnv.elementUtils.getTypeElement(className) + val elements = ElementFilter.methodsIn(typeElement.enclosedElements) + return if (elements.size == 1) { + elements[0] + } else { + processingEnv.messager.printMessage(Diagnostic.Kind.ERROR, "Needs exactly one method", typeElement) + throw IllegalArgumentException() + } + } +} \ No newline at end of file diff --git a/annotationprocessor/src/main/java/org/acra/processor/util/utils.kt b/annotationprocessor/src/main/java/org/acra/processor/util/utils.kt new file mode 100644 index 0000000000..315e98a908 --- /dev/null +++ b/annotationprocessor/src/main/java/org/acra/processor/util/utils.kt @@ -0,0 +1,67 @@ +package org.acra.processor.util + +import com.squareup.kotlinpoet.ClassName +import com.squareup.kotlinpoet.FileSpec +import com.squareup.kotlinpoet.KModifier +import com.squareup.kotlinpoet.ParameterizedTypeName +import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy +import com.squareup.kotlinpoet.TypeName +import com.squareup.kotlinpoet.WildcardTypeName +import java.io.File +import java.util.* +import javax.annotation.processing.ProcessingEnvironment +import javax.lang.model.element.Modifier +import javax.tools.Diagnostic +import kotlin.reflect.jvm.internal.impl.builtins.jvm.JavaToKotlinClassMap +import kotlin.reflect.jvm.internal.impl.name.FqName + + +fun Modifier.toKModifier(): KModifier? { + return when (this) { + Modifier.PUBLIC -> KModifier.PUBLIC + Modifier.PROTECTED -> KModifier.PROTECTED + Modifier.PRIVATE -> KModifier.PRIVATE + Modifier.ABSTRACT -> KModifier.ABSTRACT + Modifier.DEFAULT -> null + Modifier.STATIC -> null + Modifier.FINAL -> KModifier.FINAL + Modifier.TRANSIENT -> null + Modifier.VOLATILE -> null + Modifier.SYNCHRONIZED -> null + Modifier.NATIVE -> null + Modifier.STRICTFP -> null + } +} + + +fun TypeName.javaToKotlinType(): TypeName { + return when (this) { + is ParameterizedTypeName -> (rawType.javaToKotlinType() as ClassName).parameterizedBy(*typeArguments.map { it.javaToKotlinType() }.toTypedArray()) + is WildcardTypeName -> { + if (inTypes.isNotEmpty()) WildcardTypeName.consumerOf(inTypes[0].javaToKotlinType()) + else WildcardTypeName.producerOf(outTypes[0].javaToKotlinType()) + } + else -> JavaToKotlinClassMap.INSTANCE.mapJavaToKotlin(FqName(toString()))?.asSingleFqName()?.asString()?.let { ClassName.bestGuess(it) } ?: this + } +} + +fun FileSpec.Builder.writeTo(processingEnv: ProcessingEnvironment) { + val dir = (processingEnv.options["kapt.kotlin.generated"] + ?: processingEnv.messager.printMessage(Diagnostic.Kind.ERROR, "Can't find the target directory for generated Kotlin files.").let { throw IllegalArgumentException() }) + addComment(""" + |Copyright (c) ${Calendar.getInstance()[Calendar.YEAR]} + | + |Licensed under the Apache License, Version 2.0 (the "License"); + |you may not use this file except in compliance with the License. + | + |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. + """.trimMargin()) + .build() + .writeTo(File(dir)) +} \ No newline at end of file diff --git a/annotations/src/main/java/org/acra/annotation/Instantiatable.java b/annotations/src/main/java/org/acra/annotation/Instantiatable.java index 374320fb28..60624d8e03 100644 --- a/annotations/src/main/java/org/acra/annotation/Instantiatable.java +++ b/annotations/src/main/java/org/acra/annotation/Instantiatable.java @@ -28,6 +28,6 @@ * @since 03.06.2017 */ @Retention(RetentionPolicy.SOURCE) -@Target(ElementType.METHOD) +@Target({ElementType.METHOD, ElementType.PARAMETER}) public @interface Instantiatable { } diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 7611e4415b..f913f9fa1e 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -33,6 +33,8 @@ dependencies { implementation("com.jfrog.bintray.gradle:gradle-bintray-plugin:$bintrayPluginVersion") val releasePluginVersion: String by project implementation("com.faendir.gradle:gradle-release:$releasePluginVersion") + val kotlinVersion: String by project + implementation(kotlin("gradle-plugin:$kotlinVersion")) } val compileKotlin: KotlinCompile by tasks diff --git a/buildSrc/src/main/kotlin/acra-android-library.gradle.kts b/buildSrc/src/main/kotlin/acra-android-library.gradle.kts index f5c4bd18ba..e0d5f6eb84 100644 --- a/buildSrc/src/main/kotlin/acra-android-library.gradle.kts +++ b/buildSrc/src/main/kotlin/acra-android-library.gradle.kts @@ -15,19 +15,19 @@ */ plugins { id("com.android.library") - id("acra-base") } +apply(plugin = "kotlin-android") +apply(plugin = "kotlin-kapt") +apply(plugin = "acra-base") android { val androidVersion: String by project - val buildToolsVersion: String by project val androidMinVersion: String by project compileSdkVersion(Integer.parseInt(androidVersion)) - buildToolsVersion(buildToolsVersion) defaultConfig { minSdkVersion(androidMinVersion) targetSdkVersion(androidVersion) - versionNameSuffix = "$version" + buildConfigField("String", "VERSION_NAME", "\"$version\"") testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } buildTypes { @@ -67,9 +67,9 @@ dependencies { val hamcrestVersion: String by project androidTestImplementation("org.hamcrest:hamcrest:$hamcrestVersion") val autoServiceVersion: String by project - testAnnotationProcessor("com.google.auto.service:auto-service:$autoServiceVersion") + "kaptTest"("com.google.auto.service:auto-service:$autoServiceVersion") testCompileOnly("com.google.auto.service:auto-service-annotations:$autoServiceVersion") - annotationProcessor(project(":annotationprocessor")) + "kapt"(project(":annotationprocessor")) compileOnly(project(":annotations")) } diff --git a/buildSrc/src/main/kotlin/acra-application.gradle.kts b/buildSrc/src/main/kotlin/acra-application.gradle.kts index 609e3aebf0..a7f180c0eb 100644 --- a/buildSrc/src/main/kotlin/acra-application.gradle.kts +++ b/buildSrc/src/main/kotlin/acra-application.gradle.kts @@ -15,5 +15,7 @@ */ plugins { id("com.android.application") - id("acra-base") -} \ No newline at end of file +} +apply(plugin = "kotlin-android") +apply(plugin = "kotlin-kapt") +apply(plugin = "acra-base") \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/acra-base.gradle.kts b/buildSrc/src/main/kotlin/acra-base.gradle.kts index 8b62d4f41c..f429370e2d 100644 --- a/buildSrc/src/main/kotlin/acra-base.gradle.kts +++ b/buildSrc/src/main/kotlin/acra-base.gradle.kts @@ -20,6 +20,6 @@ repositories { dependencies { val autoServiceVersion: String by project - "annotationProcessor"("com.google.auto.service:auto-service:$autoServiceVersion") + "kapt"("com.google.auto.service:auto-service:$autoServiceVersion") "compileOnly"("com.google.auto.service:auto-service-annotations:$autoServiceVersion") } \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/acra-java-library.gradle.kts b/buildSrc/src/main/kotlin/acra-java-library.gradle.kts index bfeed074e8..a37700e0d1 100644 --- a/buildSrc/src/main/kotlin/acra-java-library.gradle.kts +++ b/buildSrc/src/main/kotlin/acra-java-library.gradle.kts @@ -15,6 +15,8 @@ */ plugins { `java-library` + kotlin("jvm") + id("kotlin-kapt") id("acra-base") } diff --git a/gradle.properties b/gradle.properties index abc66f3272..f63874747f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -23,6 +23,7 @@ org.gradle.configureondemand=false org.gradle.parallel=true org.gradle.caching=true android.useAndroidX=true +kapt.includeCompileClasspath=false # upload properties group=ch.acra @@ -30,15 +31,14 @@ group=ch.acra version=5.7.1-SNAPSHOT androidVersion=30 androidMinVersion=14 -androidBuildPluginVersion=4.0.0 -buildToolsVersion=30.0.1 +androidBuildPluginVersion=4.1.0 bintrayPluginVersion=1.8.5 releasePluginVersion=3.2.0 autoServiceVersion=1.0-rc6 junitVersion=4.12 androidxCoreVersion=1.1.0 androidxAnnotationVersion=1.1.0 -kotlinVersion=1.3.72 +kotlinVersion=1.4.10 hamcrestVersion=2.2 androidXTestVersion=1.2.0 androidXJunitVersion=1.1.1 diff --git a/settings.gradle.kts b/settings.gradle.kts index c3018ca1e6..d480206f5d 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -24,7 +24,6 @@ include("acra-toast") include("acra-notification") include("acra-limiter") include("acra-advanced-scheduler") -include("acra-core-ktx") pluginManagement { repositories { From c6e04aaa56cb4f8ae0fb255e93b0193c90ae270c Mon Sep 17 00:00:00 2001 From: f43nd1r Date: Mon, 19 Oct 2020 19:56:04 +0200 Subject: [PATCH 02/16] maybe fix kapt build --- .../java/org/acra/processor/creator/BuildMethodCreator.kt | 6 ++---- gradle.properties | 2 ++ 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/annotationprocessor/src/main/java/org/acra/processor/creator/BuildMethodCreator.kt b/annotationprocessor/src/main/java/org/acra/processor/creator/BuildMethodCreator.kt index 16c6b4d548..8a38fc3fb5 100644 --- a/annotationprocessor/src/main/java/org/acra/processor/creator/BuildMethodCreator.kt +++ b/annotationprocessor/src/main/java/org/acra/processor/creator/BuildMethodCreator.kt @@ -24,9 +24,7 @@ import org.acra.config.ACRAConfigurationException import org.acra.config.ClassValidator import org.acra.processor.util.Strings import org.acra.processor.util.Types -import java.util.stream.Collectors import javax.lang.model.element.ExecutableElement -import kotlin.jvm.Throws /** * @author F43nd1r @@ -39,7 +37,7 @@ class BuildMethodCreator(override: ExecutableElement, private val config: ClassN .beginControlFlow("if (%L)", Strings.FIELD_ENABLED) private val anyNonDefault: MutableMap = mutableMapOf() private val statements: MutableList = mutableListOf() - + fun addNotUnset(name: String) { methodBuilder.beginControlFlow("if (%L == null)", name) .addStatement("throw %T(\"%L has to be set\")", ACRAConfigurationException::class.java, name) @@ -67,7 +65,7 @@ class BuildMethodCreator(override: ExecutableElement, private val config: ClassN fun build(): FunSpec { if (anyNonDefault.isNotEmpty()) { methodBuilder.beginControlFlow("if (%L)", anyNonDefault.map { CodeBlock.of("%L == %L", it.key, it.value) } - .reduceOrNull { c1: CodeBlock, c2: CodeBlock -> CodeBlock.builder().add(c1).add(" && ").add(c2).build() } ?: CodeBlock.of("true")) + .reduce { c1: CodeBlock, c2: CodeBlock -> CodeBlock.builder().add(c1).add(" && ").add(c2).build() }) .addStatement("throw %T(\"One·of·%L·must·not·be·default\")", ACRAConfigurationException::class.java, anyNonDefault.keys.joinToString(",·")) .endControlFlow() } diff --git a/gradle.properties b/gradle.properties index f63874747f..39beedc23b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -24,6 +24,8 @@ org.gradle.parallel=true org.gradle.caching=true android.useAndroidX=true kapt.includeCompileClasspath=false +kapt.incremental.apt=false +kapt.generateStubs=true # upload properties group=ch.acra From e6a01754cd7d3102a495acaa8fc986cee65b8511 Mon Sep 17 00:00:00 2001 From: f43nd1r Date: Thu, 19 Nov 2020 21:53:34 +0100 Subject: [PATCH 03/16] core to kotlin --- acra-core/src/main/java/org/acra/ACRA.java | 284 ---------------- acra-core/src/main/java/org/acra/ACRA.kt | 200 +++++++++++ .../src/main/java/org/acra/ACRAConstants.java | 78 ----- .../src/main/java/org/acra/ACRAConstants.kt | 79 +++++ .../{ErrorReporter.java => ErrorReporter.kt} | 48 ++- .../java/org/acra/annotation/AcraCore.java | 9 - .../acra/attachment/AcraContentProvider.java | 259 -------------- .../acra/attachment/AcraContentProvider.kt | 220 ++++++++++++ ...Provider.java => AttachmentUriProvider.kt} | 21 +- ...ider.java => DefaultAttachmentProvider.kt} | 39 +-- .../org/acra/builder/LastActivityManager.java | 131 ------- .../org/acra/builder/LastActivityManager.kt | 115 +++++++ .../java/org/acra/builder/ReportBuilder.java | 185 ---------- .../java/org/acra/builder/ReportBuilder.kt | 153 +++++++++ .../java/org/acra/builder/ReportExecutor.java | 278 --------------- .../java/org/acra/builder/ReportExecutor.kt | 226 ++++++++++++ ...or.java => ApplicationStartupCollector.kt} | 17 +- .../collector/BaseReportFieldCollector.java | 93 ----- .../collector/BaseReportFieldCollector.kt | 77 +++++ .../{Collector.java => Collector.kt} | 35 +- ...orException.java => CollectorException.kt} | 27 +- .../collector/ConfigurationCollector.java | 273 --------------- .../acra/collector/ConfigurationCollector.kt | 205 +++++++++++ .../acra/collector/CustomDataCollector.java | 46 --- .../org/acra/collector/CustomDataCollector.kt | 37 ++ .../collector/DeviceFeaturesCollector.java | 59 ---- .../acra/collector/DeviceFeaturesCollector.kt | 49 +++ .../org/acra/collector/DeviceIdCollector.java | 60 ---- .../org/acra/collector/DeviceIdCollector.kt | 52 +++ .../collector/DisplayManagerCollector.java | 236 ------------- .../acra/collector/DisplayManagerCollector.kt | 224 ++++++++++++ .../org/acra/collector/DropBoxCollector.java | 129 ------- .../org/acra/collector/DropBoxCollector.kt | 111 ++++++ .../org/acra/collector/LogCatCollector.java | 144 -------- .../org/acra/collector/LogCatCollector.kt | 122 +++++++ .../org/acra/collector/LogFileCollector.java | 55 --- .../org/acra/collector/LogFileCollector.kt | 42 +++ .../collector/MediaCodecListCollector.java | 274 --------------- .../acra/collector/MediaCodecListCollector.kt | 230 +++++++++++++ .../acra/collector/MemoryInfoCollector.java | 141 -------- .../org/acra/collector/MemoryInfoCollector.kt | 115 +++++++ .../collector/PackageManagerCollector.java | 59 ---- .../acra/collector/PackageManagerCollector.kt | 47 +++ .../acra/collector/ReflectionCollector.java | 144 -------- .../org/acra/collector/ReflectionCollector.kt | 128 +++++++ .../org/acra/collector/SettingsCollector.java | 106 ------ .../org/acra/collector/SettingsCollector.kt | 91 +++++ .../collector/SharedPreferencesCollector.java | 125 ------- .../collector/SharedPreferencesCollector.kt | 106 ++++++ .../acra/collector/SimpleValuesCollector.java | 119 ------- .../acra/collector/SimpleValuesCollector.kt | 88 +++++ .../acra/collector/StacktraceCollector.java | 101 ------ .../org/acra/collector/StacktraceCollector.kt | 76 +++++ .../org/acra/collector/ThreadCollector.java | 63 ---- .../org/acra/collector/ThreadCollector.kt | 50 +++ .../org/acra/collector/TimeCollector.java | 83 ----- .../java/org/acra/collector/TimeCollector.kt | 61 ++++ .../config/BaseCoreConfigurationBuilder.java | 149 -------- .../config/BaseCoreConfigurationBuilder.kt | 128 +++++++ .../{ConfigUtils.java => ConfigUtils.kt} | 30 +- ...ry.java => ConfigurationBuilderFactory.kt} | 15 +- ...RetryPolicy.java => DefaultRetryPolicy.kt} | 20 +- .../org/acra/config/KtCoreConfiguration.kt | 284 ---------------- ...strator.java => ReportingAdministrator.kt} | 41 +-- .../{RetryPolicy.java => RetryPolicy.kt} | 39 +-- .../java/org/acra/data/CrashReportData.java | 194 ----------- .../java/org/acra/data/CrashReportData.kt | 194 +++++++++++ .../org/acra/data/CrashReportDataFactory.java | 122 ------- .../org/acra/data/CrashReportDataFactory.kt | 93 +++++ .../main/java/org/acra/data/StringFormat.java | 140 -------- .../main/java/org/acra/data/StringFormat.kt | 105 ++++++ ...eportDeleter.java => BulkReportDeleter.kt} | 39 +-- ...rser.java => CrashReportFileNameParser.kt} | 59 ++-- ...Persister.java => CrashReportPersister.kt} | 39 +-- .../main/java/org/acra/file/Directory.java | 131 ------- .../src/main/java/org/acra/file/Directory.kt | 119 +++++++ .../org/acra/file/LastModifiedComparator.java | 34 -- .../java/org/acra/file/ReportLocator.java | 74 ---- .../main/java/org/acra/file/ReportLocator.kt | 42 +++ ...tInteraction.java => ReportInteraction.kt} | 20 +- .../ReportInteractionExecutor.java | 77 ----- .../interaction/ReportInteractionExecutor.kt | 60 ++++ .../src/main/java/org/acra/ktx/Extensions.kt | 4 +- .../org/acra/legacy/LegacyFileHandler.java | 56 --- .../java/org/acra/legacy/ReportConverter.java | 321 ------------------ .../java/org/acra/legacy/ReportMigrator.java | 88 ----- .../src/main/java/org/acra/log/ACRALog.java | 46 --- .../src/main/java/org/acra/log/ACRALog.kt | 43 +++ .../java/org/acra/log/AndroidLogDelegate.java | 79 ----- .../java/org/acra/log/AndroidLogDelegate.kt | 50 +++ .../src/main/java/org/acra/log/HollowLog.java | 86 ----- .../src/main/java/org/acra/log/HollowLog.kt | 45 +++ .../src/main/java/org/acra/log/extensions.kt | 28 ++ .../org/acra/plugins/HasConfigPlugin.java | 39 --- .../java/org/acra/plugins/HasConfigPlugin.kt | 28 ++ .../acra/plugins/{Plugin.java => Plugin.kt} | 14 +- .../{PluginLoader.java => PluginLoader.kt} | 19 +- .../org/acra/plugins/ServicePluginLoader.java | 74 ---- .../org/acra/plugins/ServicePluginLoader.kt | 62 ++++ .../org/acra/plugins/SimplePluginLoader.java | 73 ---- .../org/acra/plugins/SimplePluginLoader.kt | 47 +++ .../acra/prefs/SharedPreferencesFactory.java | 79 ----- .../acra/prefs/SharedPreferencesFactory.kt | 68 ++++ .../org/acra/reporter/ErrorReporterImpl.java | 232 ------------- .../org/acra/reporter/ErrorReporterImpl.kt | 176 ++++++++++ .../scheduler/DefaultSenderScheduler.java | 92 ----- .../acra/scheduler/DefaultSenderScheduler.kt | 81 +++++ .../org/acra/scheduler/SchedulerStarter.java | 72 ---- .../org/acra/scheduler/SchedulerStarter.kt | 61 ++++ ...enderScheduler.java => SenderScheduler.kt} | 10 +- ...Factory.java => SenderSchedulerFactory.kt} | 19 +- .../org/acra/sender/JobSenderService.java | 37 -- .../java/org/acra/sender/JobSenderService.kt | 34 ++ .../org/acra/sender/LegacySenderService.java | 61 ---- .../org/acra/sender/LegacySenderService.kt | 54 +++ .../sender/{NullSender.java => NullSender.kt} | 22 +- .../org/acra/sender/ReportDistributor.java | 153 --------- .../java/org/acra/sender/ReportDistributor.kt | 132 +++++++ .../{ReportSender.java => ReportSender.kt} | 44 +-- ...xception.java => ReportSenderException.kt} | 24 +- ...derFactory.java => ReportSenderFactory.kt} | 30 +- .../org/acra/sender/SendingConductor.java | 105 ------ .../java/org/acra/sender/SendingConductor.kt | 72 ++++ .../main/java/org/acra/startup/Report.java | 61 ---- .../Predicate.java => startup/Report.kt} | 14 +- ...rtupProcessor.java => StartupProcessor.kt} | 18 +- .../startup/StartupProcessorExecutor.java | 91 ----- .../acra/startup/StartupProcessorExecutor.kt | 62 ++++ .../startup/UnapprovedStartupProcessor.java | 57 ---- .../startup/UnapprovedStartupProcessor.kt | 46 +++ .../util/ApplicationStartupProcessor.java | 77 ----- .../java/org/acra/util/BundleWrapper.java | 191 ----------- .../src/main/java/org/acra/util/IOUtils.java | 99 ------ .../src/main/java/org/acra/util/IOUtils.kt | 102 ++++++ .../main/java/org/acra/util/Installation.java | 63 ---- .../main/java/org/acra/util/Installation.kt | 60 ++++ .../java/org/acra/util/InstanceCreator.java | 86 ----- .../java/org/acra/util/InstanceCreator.kt | 55 +++ ...rWrapper.java => PackageManagerWrapper.kt} | 79 ++--- .../java/org/acra/util/ProcessFinisher.java | 110 ------ .../java/org/acra/util/ProcessFinisher.kt | 94 +++++ .../main/java/org/acra/util/StreamReader.java | 145 -------- .../main/java/org/acra/util/StreamReader.kt | 100 ++++++ .../main/java/org/acra/util/StubCreator.java | 28 -- .../main/java/org/acra/util/StubCreator.kt | 25 ++ .../java/org/acra/util/SystemServices.java | 69 ---- .../main/java/org/acra/util/SystemServices.kt | 49 +++ .../util/{ToastSender.java => ToastSender.kt} | 31 +- .../src/main/java/org/acra/util/bundle.kt | 80 +++++ .../src/main/java/org/acra/util/utils.kt | 9 + .../acra/startup/LimiterStartupProcessor.java | 3 +- .../kotlin/acra-android-library.gradle.kts | 1 + gradle.properties | 2 +- 153 files changed, 5627 insertions(+), 8013 deletions(-) delete mode 100644 acra-core/src/main/java/org/acra/ACRA.java create mode 100644 acra-core/src/main/java/org/acra/ACRA.kt delete mode 100644 acra-core/src/main/java/org/acra/ACRAConstants.java create mode 100644 acra-core/src/main/java/org/acra/ACRAConstants.kt rename acra-core/src/main/java/org/acra/{ErrorReporter.java => ErrorReporter.kt} (58%) delete mode 100644 acra-core/src/main/java/org/acra/attachment/AcraContentProvider.java create mode 100644 acra-core/src/main/java/org/acra/attachment/AcraContentProvider.kt rename acra-core/src/main/java/org/acra/attachment/{AttachmentUriProvider.java => AttachmentUriProvider.kt} (71%) rename acra-core/src/main/java/org/acra/attachment/{DefaultAttachmentProvider.java => DefaultAttachmentProvider.kt} (50%) delete mode 100644 acra-core/src/main/java/org/acra/builder/LastActivityManager.java create mode 100644 acra-core/src/main/java/org/acra/builder/LastActivityManager.kt delete mode 100644 acra-core/src/main/java/org/acra/builder/ReportBuilder.java create mode 100644 acra-core/src/main/java/org/acra/builder/ReportBuilder.kt delete mode 100644 acra-core/src/main/java/org/acra/builder/ReportExecutor.java create mode 100644 acra-core/src/main/java/org/acra/builder/ReportExecutor.kt rename acra-core/src/main/java/org/acra/collector/{ApplicationStartupCollector.java => ApplicationStartupCollector.kt} (70%) delete mode 100644 acra-core/src/main/java/org/acra/collector/BaseReportFieldCollector.java create mode 100644 acra-core/src/main/java/org/acra/collector/BaseReportFieldCollector.kt rename acra-core/src/main/java/org/acra/collector/{Collector.java => Collector.kt} (61%) rename acra-core/src/main/java/org/acra/collector/{CollectorException.java => CollectorException.kt} (61%) delete mode 100644 acra-core/src/main/java/org/acra/collector/ConfigurationCollector.java create mode 100644 acra-core/src/main/java/org/acra/collector/ConfigurationCollector.kt delete mode 100644 acra-core/src/main/java/org/acra/collector/CustomDataCollector.java create mode 100644 acra-core/src/main/java/org/acra/collector/CustomDataCollector.kt delete mode 100644 acra-core/src/main/java/org/acra/collector/DeviceFeaturesCollector.java create mode 100644 acra-core/src/main/java/org/acra/collector/DeviceFeaturesCollector.kt delete mode 100644 acra-core/src/main/java/org/acra/collector/DeviceIdCollector.java create mode 100644 acra-core/src/main/java/org/acra/collector/DeviceIdCollector.kt delete mode 100644 acra-core/src/main/java/org/acra/collector/DisplayManagerCollector.java create mode 100644 acra-core/src/main/java/org/acra/collector/DisplayManagerCollector.kt delete mode 100644 acra-core/src/main/java/org/acra/collector/DropBoxCollector.java create mode 100644 acra-core/src/main/java/org/acra/collector/DropBoxCollector.kt delete mode 100644 acra-core/src/main/java/org/acra/collector/LogCatCollector.java create mode 100644 acra-core/src/main/java/org/acra/collector/LogCatCollector.kt delete mode 100644 acra-core/src/main/java/org/acra/collector/LogFileCollector.java create mode 100644 acra-core/src/main/java/org/acra/collector/LogFileCollector.kt delete mode 100644 acra-core/src/main/java/org/acra/collector/MediaCodecListCollector.java create mode 100644 acra-core/src/main/java/org/acra/collector/MediaCodecListCollector.kt delete mode 100644 acra-core/src/main/java/org/acra/collector/MemoryInfoCollector.java create mode 100644 acra-core/src/main/java/org/acra/collector/MemoryInfoCollector.kt delete mode 100644 acra-core/src/main/java/org/acra/collector/PackageManagerCollector.java create mode 100644 acra-core/src/main/java/org/acra/collector/PackageManagerCollector.kt delete mode 100644 acra-core/src/main/java/org/acra/collector/ReflectionCollector.java create mode 100644 acra-core/src/main/java/org/acra/collector/ReflectionCollector.kt delete mode 100644 acra-core/src/main/java/org/acra/collector/SettingsCollector.java create mode 100644 acra-core/src/main/java/org/acra/collector/SettingsCollector.kt delete mode 100644 acra-core/src/main/java/org/acra/collector/SharedPreferencesCollector.java create mode 100644 acra-core/src/main/java/org/acra/collector/SharedPreferencesCollector.kt delete mode 100644 acra-core/src/main/java/org/acra/collector/SimpleValuesCollector.java create mode 100644 acra-core/src/main/java/org/acra/collector/SimpleValuesCollector.kt delete mode 100644 acra-core/src/main/java/org/acra/collector/StacktraceCollector.java create mode 100644 acra-core/src/main/java/org/acra/collector/StacktraceCollector.kt delete mode 100644 acra-core/src/main/java/org/acra/collector/ThreadCollector.java create mode 100644 acra-core/src/main/java/org/acra/collector/ThreadCollector.kt delete mode 100644 acra-core/src/main/java/org/acra/collector/TimeCollector.java create mode 100644 acra-core/src/main/java/org/acra/collector/TimeCollector.kt delete mode 100644 acra-core/src/main/java/org/acra/config/BaseCoreConfigurationBuilder.java create mode 100644 acra-core/src/main/java/org/acra/config/BaseCoreConfigurationBuilder.kt rename acra-core/src/main/java/org/acra/config/{ConfigUtils.java => ConfigUtils.kt} (53%) rename acra-core/src/main/java/org/acra/config/{ConfigurationBuilderFactory.java => ConfigurationBuilderFactory.kt} (77%) rename acra-core/src/main/java/org/acra/config/{DefaultRetryPolicy.java => DefaultRetryPolicy.kt} (58%) delete mode 100644 acra-core/src/main/java/org/acra/config/KtCoreConfiguration.kt rename acra-core/src/main/java/org/acra/config/{ReportingAdministrator.java => ReportingAdministrator.kt} (59%) rename acra-core/src/main/java/org/acra/config/{RetryPolicy.java => RetryPolicy.kt} (51%) delete mode 100644 acra-core/src/main/java/org/acra/data/CrashReportData.java create mode 100644 acra-core/src/main/java/org/acra/data/CrashReportData.kt delete mode 100644 acra-core/src/main/java/org/acra/data/CrashReportDataFactory.java create mode 100644 acra-core/src/main/java/org/acra/data/CrashReportDataFactory.kt delete mode 100644 acra-core/src/main/java/org/acra/data/StringFormat.java create mode 100644 acra-core/src/main/java/org/acra/data/StringFormat.kt rename acra-core/src/main/java/org/acra/file/{BulkReportDeleter.java => BulkReportDeleter.kt} (51%) rename acra-core/src/main/java/org/acra/file/{CrashReportFileNameParser.java => CrashReportFileNameParser.kt} (52%) rename acra-core/src/main/java/org/acra/file/{CrashReportPersister.java => CrashReportPersister.kt} (69%) delete mode 100644 acra-core/src/main/java/org/acra/file/Directory.java create mode 100644 acra-core/src/main/java/org/acra/file/Directory.kt delete mode 100644 acra-core/src/main/java/org/acra/file/LastModifiedComparator.java delete mode 100644 acra-core/src/main/java/org/acra/file/ReportLocator.java create mode 100644 acra-core/src/main/java/org/acra/file/ReportLocator.kt rename acra-core/src/main/java/org/acra/interaction/{ReportInteraction.java => ReportInteraction.kt} (71%) delete mode 100644 acra-core/src/main/java/org/acra/interaction/ReportInteractionExecutor.java create mode 100644 acra-core/src/main/java/org/acra/interaction/ReportInteractionExecutor.kt delete mode 100644 acra-core/src/main/java/org/acra/legacy/LegacyFileHandler.java delete mode 100644 acra-core/src/main/java/org/acra/legacy/ReportConverter.java delete mode 100644 acra-core/src/main/java/org/acra/legacy/ReportMigrator.java delete mode 100644 acra-core/src/main/java/org/acra/log/ACRALog.java create mode 100644 acra-core/src/main/java/org/acra/log/ACRALog.kt delete mode 100644 acra-core/src/main/java/org/acra/log/AndroidLogDelegate.java create mode 100644 acra-core/src/main/java/org/acra/log/AndroidLogDelegate.kt delete mode 100644 acra-core/src/main/java/org/acra/log/HollowLog.java create mode 100644 acra-core/src/main/java/org/acra/log/HollowLog.kt create mode 100644 acra-core/src/main/java/org/acra/log/extensions.kt delete mode 100644 acra-core/src/main/java/org/acra/plugins/HasConfigPlugin.java create mode 100644 acra-core/src/main/java/org/acra/plugins/HasConfigPlugin.kt rename acra-core/src/main/java/org/acra/plugins/{Plugin.java => Plugin.kt} (79%) rename acra-core/src/main/java/org/acra/plugins/{PluginLoader.java => PluginLoader.kt} (61%) delete mode 100644 acra-core/src/main/java/org/acra/plugins/ServicePluginLoader.java create mode 100644 acra-core/src/main/java/org/acra/plugins/ServicePluginLoader.kt delete mode 100644 acra-core/src/main/java/org/acra/plugins/SimplePluginLoader.java create mode 100644 acra-core/src/main/java/org/acra/plugins/SimplePluginLoader.kt delete mode 100644 acra-core/src/main/java/org/acra/prefs/SharedPreferencesFactory.java create mode 100644 acra-core/src/main/java/org/acra/prefs/SharedPreferencesFactory.kt delete mode 100644 acra-core/src/main/java/org/acra/reporter/ErrorReporterImpl.java create mode 100644 acra-core/src/main/java/org/acra/reporter/ErrorReporterImpl.kt delete mode 100644 acra-core/src/main/java/org/acra/scheduler/DefaultSenderScheduler.java create mode 100644 acra-core/src/main/java/org/acra/scheduler/DefaultSenderScheduler.kt delete mode 100644 acra-core/src/main/java/org/acra/scheduler/SchedulerStarter.java create mode 100644 acra-core/src/main/java/org/acra/scheduler/SchedulerStarter.kt rename acra-core/src/main/java/org/acra/scheduler/{SenderScheduler.java => SenderScheduler.kt} (82%) rename acra-core/src/main/java/org/acra/scheduler/{SenderSchedulerFactory.java => SenderSchedulerFactory.kt} (71%) delete mode 100644 acra-core/src/main/java/org/acra/sender/JobSenderService.java create mode 100644 acra-core/src/main/java/org/acra/sender/JobSenderService.kt delete mode 100644 acra-core/src/main/java/org/acra/sender/LegacySenderService.java create mode 100644 acra-core/src/main/java/org/acra/sender/LegacySenderService.kt rename acra-core/src/main/java/org/acra/sender/{NullSender.java => NullSender.kt} (57%) delete mode 100644 acra-core/src/main/java/org/acra/sender/ReportDistributor.java create mode 100644 acra-core/src/main/java/org/acra/sender/ReportDistributor.kt rename acra-core/src/main/java/org/acra/sender/{ReportSender.java => ReportSender.kt} (59%) rename acra-core/src/main/java/org/acra/sender/{ReportSenderException.java => ReportSenderException.kt} (67%) rename acra-core/src/main/java/org/acra/sender/{ReportSenderFactory.java => ReportSenderFactory.kt} (60%) delete mode 100644 acra-core/src/main/java/org/acra/sender/SendingConductor.java create mode 100644 acra-core/src/main/java/org/acra/sender/SendingConductor.kt delete mode 100644 acra-core/src/main/java/org/acra/startup/Report.java rename acra-core/src/main/java/org/acra/{util/Predicate.java => startup/Report.kt} (78%) rename acra-core/src/main/java/org/acra/startup/{StartupProcessor.java => StartupProcessor.kt} (64%) delete mode 100644 acra-core/src/main/java/org/acra/startup/StartupProcessorExecutor.java create mode 100644 acra-core/src/main/java/org/acra/startup/StartupProcessorExecutor.kt delete mode 100644 acra-core/src/main/java/org/acra/startup/UnapprovedStartupProcessor.java create mode 100644 acra-core/src/main/java/org/acra/startup/UnapprovedStartupProcessor.kt delete mode 100644 acra-core/src/main/java/org/acra/util/ApplicationStartupProcessor.java delete mode 100644 acra-core/src/main/java/org/acra/util/BundleWrapper.java delete mode 100644 acra-core/src/main/java/org/acra/util/IOUtils.java create mode 100644 acra-core/src/main/java/org/acra/util/IOUtils.kt delete mode 100644 acra-core/src/main/java/org/acra/util/Installation.java create mode 100644 acra-core/src/main/java/org/acra/util/Installation.kt delete mode 100644 acra-core/src/main/java/org/acra/util/InstanceCreator.java create mode 100644 acra-core/src/main/java/org/acra/util/InstanceCreator.kt rename acra-core/src/main/java/org/acra/util/{PackageManagerWrapper.java => PackageManagerWrapper.kt} (54%) delete mode 100644 acra-core/src/main/java/org/acra/util/ProcessFinisher.java create mode 100644 acra-core/src/main/java/org/acra/util/ProcessFinisher.kt delete mode 100644 acra-core/src/main/java/org/acra/util/StreamReader.java create mode 100644 acra-core/src/main/java/org/acra/util/StreamReader.kt delete mode 100644 acra-core/src/main/java/org/acra/util/StubCreator.java create mode 100644 acra-core/src/main/java/org/acra/util/StubCreator.kt delete mode 100644 acra-core/src/main/java/org/acra/util/SystemServices.java create mode 100644 acra-core/src/main/java/org/acra/util/SystemServices.kt rename acra-core/src/main/java/org/acra/util/{ToastSender.java => ToastSender.kt} (63%) create mode 100644 acra-core/src/main/java/org/acra/util/bundle.kt create mode 100644 acra-core/src/main/java/org/acra/util/utils.kt diff --git a/acra-core/src/main/java/org/acra/ACRA.java b/acra-core/src/main/java/org/acra/ACRA.java deleted file mode 100644 index 6a76eb6095..0000000000 --- a/acra-core/src/main/java/org/acra/ACRA.java +++ /dev/null @@ -1,284 +0,0 @@ -/* - * Copyright (c) 2017 - * - * 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; - -import android.app.Application; -import android.content.Context; -import android.content.SharedPreferences; -import android.os.Build; -import android.os.Handler; -import android.os.Process; -import androidx.annotation.Keep; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import org.acra.config.ACRAConfigurationException; -import org.acra.config.CoreConfiguration; -import org.acra.config.CoreConfigurationBuilder; -import org.acra.legacy.LegacyFileHandler; -import org.acra.log.ACRALog; -import org.acra.log.AndroidLogDelegate; -import org.acra.prefs.SharedPreferencesFactory; -import org.acra.reporter.ErrorReporterImpl; -import org.acra.util.StreamReader; -import org.acra.util.StubCreator; - -import java.io.IOException; - -/** - * Use this class to initialize the crash reporting feature using - * {@link #init(Application)} as soon as possible in your {@link Application} - * subclass {@link Application#onCreate()} method. Configuration items must have - * been set by using {@link org.acra.annotation.AcraCore} above the declaration of your - * {@link Application} subclass. - * - * @author Kevin Gaudin - */ -@SuppressWarnings({"WeakerAccess", "unused"}) -@Keep -public final class ACRA { - private ACRA() { - } - - public static /*non-final*/ boolean DEV_LOGGING = false; // Should be false for release. - - public static final String LOG_TAG = ACRA.class.getSimpleName(); - - @NonNull - public static ACRALog log = new AndroidLogDelegate(); - - private static final String ACRA_PRIVATE_PROCESS_NAME = ":acra"; - - /** - * The key of the application default SharedPreference where you can put a - * 'true' Boolean value to disable ACRA. - */ - public static final String PREF_DISABLE_ACRA = "acra.disable"; - - /** - * Alternatively, you can use this key if you prefer your users to have the - * checkbox ticked to enable crash reports. If both acra.disable and - * acra.enable are set, the value of acra.disable takes over the other. - */ - public static final String PREF_ENABLE_ACRA = "acra.enable"; - - /** - * The key of the SharedPreference allowing the user to disable sending - * content of logcat/dropbox. System logs collection is also dependent of - * the READ_LOGS permission. - */ - public static final String PREF_ENABLE_SYSTEM_LOGS = "acra.syslog.enable"; - - /** - * The key of the SharedPreference allowing the user to disable sending his - * device id. Device ID collection is also dependent of the READ_PHONE_STATE - * permission. - */ - public static final String PREF_ENABLE_DEVICE_ID = "acra.deviceid.enable"; - - /** - * The key of the SharedPreference allowing the user to always include his - * email address. - */ - public static final String PREF_USER_EMAIL_ADDRESS = "acra.user.email"; - - /** - * The key of the SharedPreference allowing the user to automatically accept - * sending reports. - */ - public static final String PREF_ALWAYS_ACCEPT = "acra.alwaysaccept"; - - /** - * The version number of the application the last time ACRA was started. - * This is used to determine whether unsent reports should be discarded - * because they are old and out of date. - */ - public static final String PREF_LAST_VERSION_NR = "acra.lastVersionNr"; - - @NonNull - private static ErrorReporter errorReporterSingleton = StubCreator.createErrorReporterStub(); - - /** - *

- * Initialize ACRA for a given Application. - *

- * The call to this method should be placed as soon as possible in the {@link Application#attachBaseContext(Context)} method. - *

- * Uses the configuration as configured with the @ReportCrashes annotation. - * Sends any unsent reports. - *

- * - * @param app Your Application class. - * @throws IllegalStateException if it is called more than once. - */ - public static void init(@NonNull Application app) { - init(app, new CoreConfigurationBuilder(app)); - } - - /** - *

- * Initialize ACRA for a given Application. - *

- * The call to this method should be placed as soon as possible in the {@link Application#attachBaseContext(Context)} method. - *

- * Uses the configuration as configured with the @ReportCrashes annotation. - * Sends any unsent reports. - *

- * - * @param app Your Application class. - * @param builder ConfigurationBuilder to manually set up ACRA configuration. - */ - public static void init(@NonNull Application app, @NonNull CoreConfigurationBuilder builder) { - init(app, builder, true); - } - - /** - *

- * Initialize ACRA for a given Application. - *

- * The call to this method should be placed as soon as possible in the {@link Application#attachBaseContext(Context)} method. - *

- * - * @param app Your Application class. - * @param builder ConfigurationBuilder to manually set up ACRA configuration. - * @param checkReportsOnApplicationStart Whether to invoke ErrorReporter.checkReportsOnApplicationStart(). - */ - public static void init(@NonNull Application app, @NonNull CoreConfigurationBuilder builder, boolean checkReportsOnApplicationStart) { - try { - init(app, builder.build(), checkReportsOnApplicationStart); - } catch (ACRAConfigurationException e) { - log.w(LOG_TAG, "Configuration Error - ACRA not started : " + e.getMessage()); - } - } - - /** - *

- * Initialize ACRA for a given Application. - *

- * The call to this method should be placed as soon as possible in the {@link Application#attachBaseContext(Context)} method. - *

- * Sends any unsent reports. - *

- * - * @param app Your Application class. - * @param config CoreConfiguration to manually set up ACRA configuration. - * @throws IllegalStateException if it is called more than once. - */ - public static void init(@NonNull Application app, @NonNull CoreConfiguration config) { - init(app, config, true); - } - - /** - *

- * Initialize ACRA for a given Application. The call to this method should - * be placed as soon as possible in the {@link Application#attachBaseContext(Context)} - * method. - *

- * - * @param app Your Application class. - * @param config CoreConfiguration to manually set up ACRA configuration. - * @param checkReportsOnApplicationStart Whether to invoke ErrorReporter.checkReportsOnApplicationStart(). - * @throws IllegalStateException if it is called more than once. - */ - public static void init(@NonNull Application app, @NonNull CoreConfiguration config, boolean checkReportsOnApplicationStart) { - - final boolean senderServiceProcess = isACRASenderServiceProcess(); - if (senderServiceProcess) { - if (ACRA.DEV_LOGGING) - log.d(LOG_TAG, "Not initialising ACRA to listen for uncaught Exceptions as this is the SendWorker process and we only send reports, we don't capture them to avoid infinite loops"); - } - - final boolean supportedAndroidVersion = Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH; - if (!supportedAndroidVersion) { - // NB We keep initialising so that everything is configured. But ACRA is never enabled below. - log.w(LOG_TAG, "ACRA 5.1.0+ requires ICS or greater. ACRA is disabled and will NOT catch crashes or send messages."); - } - - if (isInitialised()) { - log.w(LOG_TAG, "ACRA#init called more than once. This might have unexpected side effects. Doing this outside of tests is discouraged."); - if(DEV_LOGGING) log.d(LOG_TAG, "Removing old ACRA config..."); - ((ErrorReporterImpl) errorReporterSingleton).unregister(); - errorReporterSingleton = StubCreator.createErrorReporterStub(); - } - - //noinspection ConstantConditions - if (config == null) { - log.e(LOG_TAG, "ACRA#init called but no CoreConfiguration provided"); - return; - } - - final SharedPreferences prefs = new SharedPreferencesFactory(app, config).create(); - - new LegacyFileHandler(app, prefs).updateToCurrentVersionIfNecessary(); - if (!senderServiceProcess) { - // Initialize ErrorReporter with all required data - final boolean enableAcra = supportedAndroidVersion && SharedPreferencesFactory.shouldEnableACRA(prefs); - // Indicate that ACRA is or is not listening for crashes. - log.i(LOG_TAG, "ACRA is " + (enableAcra ? "enabled" : "disabled") + " for " + app.getPackageName() + ", initializing..."); - ErrorReporterImpl reporter = new ErrorReporterImpl(app, config, enableAcra, supportedAndroidVersion, checkReportsOnApplicationStart); - errorReporterSingleton = reporter; - - // register after initAcra is called to avoid a - // NPE in ErrorReporter.disable() because - // the context could be null at this moment. - prefs.registerOnSharedPreferenceChangeListener(reporter); - } - } - - /** - * @return true is ACRA has been initialised. - */ - public static boolean isInitialised() { - return errorReporterSingleton instanceof ErrorReporterImpl; - } - - /** - * @return true if the current process is the process running the SenderService. - * NB this assumes that your SenderService is configured to used the default ':acra' process. - */ - public static boolean isACRASenderServiceProcess() { - final String processName = getCurrentProcessName(); - if (ACRA.DEV_LOGGING) log.d(LOG_TAG, "ACRA processName='" + processName + '\''); - //processName sometimes (or always?) starts with the package name, so we use endsWith instead of equals - return processName != null && processName.endsWith(ACRA_PRIVATE_PROCESS_NAME); - } - - @Nullable - private static String getCurrentProcessName() { - try { - return new StreamReader("/proc/self/cmdline").read().trim(); - } catch (IOException e) { - return null; - } - } - - /** - * @return the current instance of ErrorReporter. - * @throws IllegalStateException if {@link ACRA#init(android.app.Application)} has not yet been called. - */ - @NonNull - public static ErrorReporter getErrorReporter() { - return errorReporterSingleton; - } - - - public static void setLog(@NonNull ACRALog log) { - //noinspection ConstantConditions (do not rely on annotation alone) - if (log == null) { - throw new NullPointerException("ACRALog cannot be null"); - } - ACRA.log = log; - } -} diff --git a/acra-core/src/main/java/org/acra/ACRA.kt b/acra-core/src/main/java/org/acra/ACRA.kt new file mode 100644 index 0000000000..2c58c9bf7c --- /dev/null +++ b/acra-core/src/main/java/org/acra/ACRA.kt @@ -0,0 +1,200 @@ +/* + * Copyright (c) 2017 + * + * 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 + +import android.app.Application +import android.os.Build +import org.acra.config.ACRAConfigurationException +import org.acra.config.CoreConfiguration +import org.acra.config.CoreConfigurationBuilder +import org.acra.log.ACRALog +import org.acra.log.AndroidLogDelegate +import org.acra.log.debug +import org.acra.log.info +import org.acra.log.warn +import org.acra.prefs.SharedPreferencesFactory +import org.acra.reporter.ErrorReporterImpl +import org.acra.util.StreamReader +import org.acra.util.StubCreator +import java.io.IOException + +/** + * Use this class to initialize the crash reporting feature using + * [.init] as soon as possible in your [Application] + * subclass [Application.onCreate] method. Configuration items must have + * been set by using [org.acra.annotation.AcraCore] above the declaration of your + * [Application] subclass. + * + * @author Kevin Gaudin + */ +@Suppress("MemberVisibilityCanBePrivate") +object ACRA { + @JvmField + var /*non-final*/ DEV_LOGGING = false // Should be false for release. + + @JvmField + val LOG_TAG: String = ACRA::class.java.simpleName + + @JvmField + var log: ACRALog = AndroidLogDelegate() + private const val ACRA_PRIVATE_PROCESS_NAME = ":acra" + + /** + * The key of the application default SharedPreference where you can put a + * 'true' Boolean value to disable ACRA. + */ + const val PREF_DISABLE_ACRA = "acra.disable" + + /** + * Alternatively, you can use this key if you prefer your users to have the + * checkbox ticked to enable crash reports. If both acra.disable and + * acra.enable are set, the value of acra.disable takes over the other. + */ + const val PREF_ENABLE_ACRA = "acra.enable" + + /** + * The key of the SharedPreference allowing the user to disable sending + * content of logcat/dropbox. System logs collection is also dependent of + * the READ_LOGS permission. + */ + const val PREF_ENABLE_SYSTEM_LOGS = "acra.syslog.enable" + + /** + * The key of the SharedPreference allowing the user to disable sending his + * device id. Device ID collection is also dependent of the READ_PHONE_STATE + * permission. + */ + const val PREF_ENABLE_DEVICE_ID = "acra.deviceid.enable" + + /** + * The key of the SharedPreference allowing the user to always include his + * email address. + */ + const val PREF_USER_EMAIL_ADDRESS = "acra.user.email" + + /** + * The key of the SharedPreference allowing the user to automatically accept + * sending reports. + */ + const val PREF_ALWAYS_ACCEPT = "acra.alwaysaccept" + + /** + * The version number of the application the last time ACRA was started. + * This is used to determine whether unsent reports should be discarded + * because they are old and out of date. + */ + const val PREF_LAST_VERSION_NR = "acra.lastVersionNr" + + /** + * the current instance of ErrorReporter. + * not available if [ACRA.init] has not yet been called. + */ + var errorReporter = StubCreator.createErrorReporterStub() + private set + + /** + * + * + * Initialize ACRA for a given Application. + * + * + * The call to this method should be placed as soon as possible in the [Application.attachBaseContext] method. + * + * + * @param app Your Application class. + * @param builder ConfigurationBuilder to manually set up ACRA configuration. + * @param checkReportsOnApplicationStart Whether to invoke ErrorReporter.checkReportsOnApplicationStart(). + */ + @JvmOverloads + fun init(app: Application, builder: CoreConfigurationBuilder = CoreConfigurationBuilder(app), checkReportsOnApplicationStart: Boolean = true) { + try { + init(app, builder.build(), checkReportsOnApplicationStart) + } catch (e: ACRAConfigurationException) { + warn(e) { "Configuration Error - ACRA not started." } + } + } + + /** + * + * + * Initialize ACRA for a given Application. The call to this method should + * be placed as soon as possible in the [Application.attachBaseContext] + * method. + * + * + * @param app Your Application class. + * @param config CoreConfiguration to manually set up ACRA configuration. + * @param checkReportsOnApplicationStart Whether to invoke ErrorReporter.checkReportsOnApplicationStart(). + * @throws IllegalStateException if it is called more than once. + */ + @JvmOverloads + fun init(app: Application, config: CoreConfiguration, checkReportsOnApplicationStart: Boolean = true) { + val senderServiceProcess = isACRASenderServiceProcess() + if (senderServiceProcess) { + debug { + "Not initialising ACRA to listen for uncaught Exceptions as this is the SendWorker process and we only send reports, we don't capture them to avoid infinite loops" + } + } + val supportedAndroidVersion = Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH + if (!supportedAndroidVersion) { + // NB We keep initialising so that everything is configured. But ACRA is never enabled below. + warn { "ACRA 5.1.0+ requires ICS or greater. ACRA is disabled and will NOT catch crashes or send messages." } + } + if (isInitialised) { + warn { "ACRA#init called more than once. This might have unexpected side effects. Doing this outside of tests is discouraged." } + debug { "Removing old ACRA config..." } + (errorReporter as ErrorReporterImpl).unregister() + errorReporter = StubCreator.createErrorReporterStub() + } + val prefs = SharedPreferencesFactory(app, config).create() + if (!senderServiceProcess) { + // Initialize ErrorReporter with all required data + val enableAcra = supportedAndroidVersion && SharedPreferencesFactory.shouldEnableACRA(prefs) + // Indicate that ACRA is or is not listening for crashes. + info { "ACRA is ${if (enableAcra) "enabled" else "disabled"} for ${app.packageName}, initializing..." } + val reporter = ErrorReporterImpl(app, config, enableAcra, supportedAndroidVersion, checkReportsOnApplicationStart) + errorReporter = reporter + + // register after initAcra is called to avoid a + // NPE in ErrorReporter.disable() because + // the context could be null at this moment. + prefs.registerOnSharedPreferenceChangeListener(reporter) + } + } + + /** + * @return true is ACRA has been initialised. + */ + val isInitialised: Boolean + get() = errorReporter is ErrorReporterImpl + + /** + * @return true if the current process is the process running the SenderService. + * NB this assumes that your SenderService is configured to used the default ':acra' process. + */ + fun isACRASenderServiceProcess(): Boolean { + val processName = currentProcessName() + debug { "ACRA processName='$processName'" } + //processName sometimes (or always?) starts with the package name, so we use endsWith instead of equals + return processName != null && processName.endsWith(ACRA_PRIVATE_PROCESS_NAME) + } + + private fun currentProcessName(): String? = try { + StreamReader("/proc/self/cmdline").read().trim { it <= ' ' } + } catch (e: IOException) { + null + } +} \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/ACRAConstants.java b/acra-core/src/main/java/org/acra/ACRAConstants.java deleted file mode 100644 index c875fbaeb3..0000000000 --- a/acra-core/src/main/java/org/acra/ACRAConstants.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (c) 2017 - * - * 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; - -import static org.acra.ReportField.*; - -/** - * Responsible for collating those constants shared among the ACRA components. - * - * @author William Ferguson - * @since 4.3.0 - */ -public final class ACRAConstants { - private ACRAConstants() { - } - - public static final String REPORTFILE_EXTENSION = ".stacktrace"; - - /** - * Suffix to be added to report files when they have been approved by the - * user in NOTIFICATION mode - */ - public static final String APPROVED_SUFFIX = "-approved"; - /** - * This key is used to store the silent state of a report sent by - * handleSilentException(). - */ - public static final String SILENT_SUFFIX = "-" + IS_SILENT; - /** - * This is the maximum number of previously stored reports that we send - * in one batch to avoid overloading the network. - */ - public static final int MAX_SEND_REPORTS = 5; - - /** - * A special String value to allow the usage of a pseudo-null default value - * in annotation parameters. - */ - public static final String NULL_VALUE = "ACRA-NULL-STRING"; - - public static final int DEFAULT_RES_VALUE = 0; - - public static final String DEFAULT_STRING_VALUE = ""; - - public static final int DEFAULT_LOG_LINES = 100; - - public static final int DEFAULT_BUFFER_SIZE_IN_BYTES = 8192; - - /** - * Default list of {@link ReportField}s to be sent in reports. You can set - * your own list with - * {@link org.acra.annotation.AcraCore#reportContent()}. - */ - public static final ReportField[] DEFAULT_REPORT_FIELDS = {REPORT_ID, APP_VERSION_CODE, APP_VERSION_NAME, PACKAGE_NAME, FILE_PATH, PHONE_MODEL, BRAND, PRODUCT, ANDROID_VERSION, - BUILD, TOTAL_MEM_SIZE, AVAILABLE_MEM_SIZE, BUILD_CONFIG, CUSTOM_DATA, IS_SILENT, STACK_TRACE, INITIAL_CONFIGURATION, CRASH_CONFIGURATION, DISPLAY, USER_COMMENT, USER_EMAIL, - USER_APP_START_DATE, USER_CRASH_DATE, DUMPSYS_MEMINFO, LOGCAT, INSTALLATION_ID, DEVICE_FEATURES, ENVIRONMENT, SHARED_PREFERENCES}; - - public static final String DATE_TIME_FORMAT_STRING = "yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ"; - - public static final String DEFAULT_CERTIFICATE_TYPE = "X.509"; - - public static final String NOT_AVAILABLE = "N/A"; - - public static final String UTF8 = "UTF-8"; -} diff --git a/acra-core/src/main/java/org/acra/ACRAConstants.kt b/acra-core/src/main/java/org/acra/ACRAConstants.kt new file mode 100644 index 0000000000..9ac3f0db7b --- /dev/null +++ b/acra-core/src/main/java/org/acra/ACRAConstants.kt @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2017 + * + * 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 + +/** + * Responsible for collating those constants shared among the ACRA components. + * + * @author William Ferguson + * @since 4.3.0 + */ +object ACRAConstants { + const val REPORTFILE_EXTENSION = ".stacktrace" + + /** + * Suffix to be added to report files when they have been approved by the + * user in NOTIFICATION mode + */ + const val APPROVED_SUFFIX = "-approved" + + /** + * This key is used to store the silent state of a report sent by + * handleSilentException(). + */ + @JvmField + val SILENT_SUFFIX = "-" + ReportField.IS_SILENT + + /** + * This is the maximum number of previously stored reports that we send + * in one batch to avoid overloading the network. + */ + const val MAX_SEND_REPORTS = 5 + + /** + * A special String value to allow the usage of a pseudo-null default value + * in annotation parameters. + */ + const val NULL_VALUE = "ACRA-NULL-STRING" + + const val DEFAULT_RES_VALUE = 0 + + const val DEFAULT_STRING_VALUE = "" + + const val DEFAULT_LOG_LINES = 100 + + const val DEFAULT_BUFFER_SIZE_IN_BYTES = 8192 + + /** + * Default list of [ReportField]s to be sent in reports. You can set + * your own list with + * [org.acra.annotation.AcraCore.reportContent]. + */ + @JvmField + val DEFAULT_REPORT_FIELDS = arrayOf(ReportField.REPORT_ID, ReportField.APP_VERSION_CODE, ReportField.APP_VERSION_NAME, ReportField.PACKAGE_NAME, ReportField.FILE_PATH, + ReportField.PHONE_MODEL, ReportField.BRAND, ReportField.PRODUCT, ReportField.ANDROID_VERSION, ReportField.BUILD, ReportField.TOTAL_MEM_SIZE, + ReportField.AVAILABLE_MEM_SIZE, ReportField.BUILD_CONFIG, ReportField.CUSTOM_DATA, ReportField.IS_SILENT, ReportField.STACK_TRACE, ReportField.INITIAL_CONFIGURATION, + ReportField.CRASH_CONFIGURATION, ReportField.DISPLAY, ReportField.USER_COMMENT, ReportField.USER_EMAIL, ReportField.USER_APP_START_DATE, ReportField.USER_CRASH_DATE, + ReportField.DUMPSYS_MEMINFO, ReportField.LOGCAT, ReportField.INSTALLATION_ID, ReportField.DEVICE_FEATURES, ReportField.ENVIRONMENT, ReportField.SHARED_PREFERENCES) + + const val DATE_TIME_FORMAT_STRING = "yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ" + + const val DEFAULT_CERTIFICATE_TYPE = "X.509" + + const val NOT_AVAILABLE = "N/A" + + const val UTF8 = "UTF-8" +} \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/ErrorReporter.java b/acra-core/src/main/java/org/acra/ErrorReporter.kt similarity index 58% rename from acra-core/src/main/java/org/acra/ErrorReporter.java rename to acra-core/src/main/java/org/acra/ErrorReporter.kt index 919c7bcae3..ddd59afc1b 100644 --- a/acra-core/src/main/java/org/acra/ErrorReporter.java +++ b/acra-core/src/main/java/org/acra/ErrorReporter.kt @@ -1,8 +1,6 @@ -package org.acra; +package org.acra -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import org.acra.scheduler.SenderScheduler; +import org.acra.scheduler.SenderScheduler /** * This interface contains methods used to interact with ACRA after it has been initialized @@ -10,8 +8,7 @@ * @author F43nd1r * @since 29.12.2017 */ - -public interface ErrorReporter { +interface ErrorReporter { /** * Use this method to provide the ErrorReporter with data of your running application. * You should call this at several key places in your code the same way as you would output important debug data in a log file. @@ -20,70 +17,69 @@ public interface ErrorReporter { * @param key A key for your custom data. * @param value The value associated to your key. * @return The previous value for this key if there was one, or null. - * @see #removeCustomData(String) - * @see #getCustomData(String) + * @see .removeCustomData + * @see .getCustomData */ - String putCustomData(@NonNull String key, String value); + fun putCustomData(key: String, value: String): String? /** * Removes a key/value pair from your reports custom data field. * * @param key The key of the data to be removed. * @return The value for this key before removal. - * @see #putCustomData(String, String) - * @see #getCustomData(String) + * @see .putCustomData + * @see .getCustomData */ - String removeCustomData(@NonNull String key); + fun removeCustomData(key: String): String? /** * Removes all key/value pairs from your reports custom data field. */ - void clearCustomData(); + fun clearCustomData() /** * Gets the current value for a key in your reports custom data field. * * @param key The key of the data to be retrieved. * @return The value for this key. - * @see #putCustomData(String, String) - * @see #removeCustomData(String) + * @see .putCustomData + * @see .removeCustomData */ - String getCustomData(@NonNull String key); + fun getCustomData(key: String): String? /** * Send a silent report for the given exception * - * @param e The {@link Throwable} to be reported. If null the report will contain a new Exception("Report requested by developer"). + * @param e The [Throwable] to be reported. If null the report will contain a new Exception("Report requested by developer"). */ - void handleSilentException(@Nullable Throwable e); - + fun handleSilentException(e: Throwable) /** * Enable or disable this ErrorReporter. By default it is enabled. * * @param enabled Whether this ErrorReporter should capture Exceptions and forward them as crash reports. */ - void setEnabled(boolean enabled); + fun setEnabled(enabled: Boolean) /** * Send a normal report for the given exception * - * @param e The {@link Throwable} to be reported. If null the report will contain a new Exception("Report requested by developer"). + * @param e The [Throwable] to be reported. If null the report will contain a new Exception("Report requested by developer"). * @param endApplication if you want the application to be ended after sending the report. */ - void handleException(@Nullable Throwable e, boolean endApplication); + fun handleException(e: Throwable, endApplication: Boolean) /** * Send a normal report for the given exception. * - * @param e The {@link Throwable} to be reported. If null the report will contain a new Exception("Report requested by developer"). + * @param e The [Throwable] to be reported. If null the report will contain a new Exception("Report requested by developer"). */ - void handleException(@Nullable Throwable e); + fun handleException(e: Throwable) /** * Access point to manual report scheduling * * @return current SenderScheduler */ - SenderScheduler getReportScheduler(); -} + val reportScheduler: SenderScheduler? +} \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/annotation/AcraCore.java b/acra-core/src/main/java/org/acra/annotation/AcraCore.java index 5a0f657463..7c46548651 100644 --- a/acra-core/src/main/java/org/acra/annotation/AcraCore.java +++ b/acra-core/src/main/java/org/acra/annotation/AcraCore.java @@ -147,15 +147,6 @@ */ boolean deleteUnapprovedReportsOnApplicationStart() default true; - /** - * This property can be used to determine whether old (out of date) reports should be sent or not. - * - * @return if ACRA should delete any unsent reports on startup if the application has been updated since the last time the application was started. - * @deprecated use @AcraLimiter.deleteReportsOnAppUpdate() instead - */ - @Deprecated - boolean deleteOldUnsentReportsOnApplicationStart() default true; - /** * Set this to true if you prefer displaying the native force close dialog after ACRA is done. * Recommended: Keep this set to false if using interactions with user input. diff --git a/acra-core/src/main/java/org/acra/attachment/AcraContentProvider.java b/acra-core/src/main/java/org/acra/attachment/AcraContentProvider.java deleted file mode 100644 index f1ed493e5d..0000000000 --- a/acra-core/src/main/java/org/acra/attachment/AcraContentProvider.java +++ /dev/null @@ -1,259 +0,0 @@ -/* - * Copyright (c) 2017 - * - * 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.attachment; - -import android.content.ContentProvider; -import android.content.ContentResolver; -import android.content.ContentValues; -import android.content.Context; -import android.database.Cursor; -import android.database.MatrixCursor; -import android.net.Uri; -import android.os.Build; -import android.os.ParcelFileDescriptor; -import android.provider.OpenableColumns; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import android.text.TextUtils; -import android.webkit.MimeTypeMap; - -import org.acra.ACRA; -import org.acra.annotation.AcraCore; -import org.acra.file.Directory; - -import java.io.File; -import java.io.FileNotFoundException; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.regex.Pattern; - -/** - * Provides access to attachments for senders - * For uri schema, see {@link AcraCore#attachmentUris()} - * - * @author F43nd1r - * @since 13.03.2017 - */ - -public class AcraContentProvider extends ContentProvider { - private static final String[] COLUMNS = { - OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE}; - private static final String MIME_TYPE_OCTET_STREAM = "application/octet-stream"; - private String authority; - - @Override - public boolean onCreate() { - authority = getAuthority(getContext()); - if (ACRA.DEV_LOGGING) ACRA.log.d(ACRA.LOG_TAG, "Registered content provider for authority " + authority); - return true; - } - - /** - * Provides file metadata - * - * @param uri the file uri - * @param projection any combination of {@link OpenableColumns#DISPLAY_NAME} and {@link OpenableColumns#SIZE} - * @param selection ignored - * @param selectionArgs ignored - * @param sortOrder ignored - * @return file metadata in a cursor with a single row - */ - @Nullable - @Override - public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) { - if (ACRA.DEV_LOGGING) ACRA.log.d(ACRA.LOG_TAG, "Query: " + uri); - final File file = getFileForUri(uri); - if (file == null) { - return null; - } - if (projection == null) { - projection = COLUMNS; - } - final Map columnValueMap = new LinkedHashMap<>(); - for (String column : projection) { - if (column.equals(OpenableColumns.DISPLAY_NAME)) { - columnValueMap.put(OpenableColumns.DISPLAY_NAME, file.getName()); - } else if (column.equals(OpenableColumns.SIZE)) { - columnValueMap.put(OpenableColumns.SIZE, file.length()); - } - } - final MatrixCursor cursor = new MatrixCursor(columnValueMap.keySet().toArray(new String[0]), 1); - cursor.addRow(columnValueMap.values()); - return cursor; - } - - /** - * @param uri the file uri - * @return file represented by uri, or null if it can't be resolved - */ - @Nullable - private File getFileForUri(@NonNull Uri uri) { - if (!ContentResolver.SCHEME_CONTENT.equals(uri.getScheme()) || !authority.equals(uri.getAuthority())) { - return null; - } - final List segments = new ArrayList<>(uri.getPathSegments()); - if (segments.size() < 2) return null; - final String dir = segments.remove(0).toUpperCase(); - try { - final Directory directory = Directory.valueOf(dir); - return directory.getFile(getContext(), TextUtils.join(File.separator, segments)); - } catch (IllegalArgumentException e) { - return null; - } - } - - /** - * Provides file mimeType - * - * @param uri the file uri - * @return mimeType, default is {@link #MIME_TYPE_OCTET_STREAM} - * @see #guessMimeType(Uri) - */ - @NonNull - @Override - public String getType(@NonNull Uri uri) { - return guessMimeType(uri); - } - - /** - * @param uri ignored - * @param values ignored - * @throws UnsupportedOperationException always - */ - @Nullable - @Override - public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) { - throw new UnsupportedOperationException("No insert supported"); - } - - /** - * @param uri ignored - * @param selection ignored - * @param selectionArgs ignored - * @throws UnsupportedOperationException always - */ - @Override - public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) { - throw new UnsupportedOperationException("No delete supported"); - } - - /** - * @param uri ignored - * @param values ignored - * @param selection ignored - * @param selectionArgs ignored - * @throws UnsupportedOperationException always - */ - @Override - public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) { - throw new UnsupportedOperationException("No update supported"); - } - - /** - * Open a file for read - * - * @param uri the file uri - * @param mode ignored - * @return a {@link ParcelFileDescriptor} for the File - * @throws FileNotFoundException if the file cannot be resolved - */ - @NonNull - @Override - public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode) throws FileNotFoundException { - final File file = getFileForUri(uri); - if (file == null || !file.exists()) throw new FileNotFoundException("File represented by uri " + uri + " could not be found"); - if (ACRA.DEV_LOGGING) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - ACRA.log.d(ACRA.LOG_TAG, getCallingPackage() + " opened " + file.getPath()); - } else { - ACRA.log.d(ACRA.LOG_TAG, file.getPath() + " was opened by an application"); - } - } - return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY); - } - - /** - * @param context a a context - * @return authority of this provider - */ - @NonNull - private static String getAuthority(@NonNull Context context) { - return context.getPackageName() + ".acra"; - } - - /** - * Get an uri for this content provider for the given file - * - * @param context a context - * @param file the file - * @return the uri - */ - @NonNull - public static Uri getUriForFile(@NonNull Context context, @NonNull File file) { - return getUriForFile(context, Directory.ROOT, file.getPath()); - } - - /** - * Get an uri for this content provider for the given file - * - * @param context a context - * @param directory the directory, to with the path is relative - * @param relativePath the file path - * @return the uri - */ - @SuppressWarnings("WeakerAccess") - @NonNull - public static Uri getUriForFile(@NonNull Context context, @NonNull Directory directory, @NonNull String relativePath) { - final Uri.Builder builder = new Uri.Builder() - .scheme(ContentResolver.SCHEME_CONTENT) - .authority(getAuthority(context)) - .appendPath(directory.name().toLowerCase()); - for (String segment : relativePath.split(Pattern.quote(File.separator))) { - if (segment.length() > 0) { - builder.appendPath(segment); - } - } - return builder.build(); - } - - - /** - * Tries to guess the mime type from uri extension - * - * @param uri the uri - * @return the mime type of the uri, with fallback {@link #MIME_TYPE_OCTET_STREAM} - */ - @NonNull - public static String guessMimeType(@NonNull Uri uri) { - String type = null; - final String fileExtension = MimeTypeMap.getFileExtensionFromUrl(uri - .toString()); - if (fileExtension != null) { - type = MimeTypeMap.getSingleton().getMimeTypeFromExtension( - fileExtension.toLowerCase()); - if(type == null && "json".equals(fileExtension)) { - type = "application/json"; - } - } - if (type == null) { - type = MIME_TYPE_OCTET_STREAM; - } - return type; - } -} diff --git a/acra-core/src/main/java/org/acra/attachment/AcraContentProvider.kt b/acra-core/src/main/java/org/acra/attachment/AcraContentProvider.kt new file mode 100644 index 0000000000..3343e9eeb4 --- /dev/null +++ b/acra-core/src/main/java/org/acra/attachment/AcraContentProvider.kt @@ -0,0 +1,220 @@ +/* + * Copyright (c) 2017 + * + * 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.attachment + +import android.content.ContentProvider +import android.content.ContentResolver +import android.content.ContentValues +import android.content.Context +import android.database.Cursor +import android.database.MatrixCursor +import android.net.Uri +import android.os.Build +import android.os.ParcelFileDescriptor +import android.provider.OpenableColumns +import android.text.TextUtils +import android.webkit.MimeTypeMap +import org.acra.ACRA +import org.acra.file.Directory +import org.acra.log.debug +import java.io.File +import java.io.FileNotFoundException +import java.util.* +import java.util.regex.Pattern + +/** + * Provides access to attachments for senders + * For uri schema, see [AcraCore.attachmentUris] + * + * @author F43nd1r + * @since 13.03.2017 + */ +class AcraContentProvider : ContentProvider() { + private var authority: String? = null + override fun onCreate(): Boolean { + authority = getAuthority(context!!) + if (ACRA.DEV_LOGGING) ACRA.log.d(ACRA.LOG_TAG, "Registered content provider for authority $authority") + return true + } + + /** + * Provides file metadata + * + * @param uri the file uri + * @param projection any combination of [OpenableColumns.DISPLAY_NAME] and [OpenableColumns.SIZE] + * @param selection ignored + * @param selectionArgs ignored + * @param sortOrder ignored + * @return file metadata in a cursor with a single row + */ + override fun query(uri: Uri, projection: Array?, selection: String?, selectionArgs: Array?, sortOrder: String?): Cursor? { + var proj = projection + if (ACRA.DEV_LOGGING) ACRA.log.d(ACRA.LOG_TAG, "Query: $uri") + val file = getFileForUri(uri) ?: return null + if (proj == null) { + proj = COLUMNS + } + val columnValueMap: MutableMap = LinkedHashMap() + for (column in proj) { + if (column == OpenableColumns.DISPLAY_NAME) { + columnValueMap[OpenableColumns.DISPLAY_NAME] = file.name + } else if (column == OpenableColumns.SIZE) { + columnValueMap[OpenableColumns.SIZE] = file.length() + } + } + val cursor = MatrixCursor(columnValueMap.keys.toTypedArray(), 1) + cursor.addRow(columnValueMap.values) + return cursor + } + + /** + * @param uri the file uri + * @return file represented by uri, or null if it can't be resolved + */ + private fun getFileForUri(uri: Uri): File? { + if (ContentResolver.SCHEME_CONTENT != uri.scheme || authority != uri.authority) { + return null + } + val segments = uri.pathSegments.toMutableList() + if (segments.size < 2) return null + val dir: String = segments.removeAt(0).toUpperCase(Locale.ROOT) + return try { + val directory = Directory.valueOf(dir) + directory.getFile(context!!, TextUtils.join(File.separator, segments)) + } catch (e: IllegalArgumentException) { + null + } + } + + /** + * Provides file mimeType + * + * @param uri the file uri + * @return mimeType, default is [.MIME_TYPE_OCTET_STREAM] + * @see .guessMimeType + */ + override fun getType(uri: Uri): String = guessMimeType(uri) + + /** + * @param uri ignored + * @param values ignored + * @throws UnsupportedOperationException always + */ + override fun insert(uri: Uri, values: ContentValues?): Uri? = throw UnsupportedOperationException("No insert supported") + + /** + * @param uri ignored + * @param selection ignored + * @param selectionArgs ignored + * @throws UnsupportedOperationException always + */ + override fun delete(uri: Uri, selection: String?, selectionArgs: Array?): Int = throw UnsupportedOperationException("No delete supported") + + /** + * @param uri ignored + * @param values ignored + * @param selection ignored + * @param selectionArgs ignored + * @throws UnsupportedOperationException always + */ + override fun update(uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array?): Int = throw UnsupportedOperationException("No update supported") + + /** + * Open a file for read + * + * @param uri the file uri + * @param mode ignored + * @return a [ParcelFileDescriptor] for the File + * @throws FileNotFoundException if the file cannot be resolved + */ + @Throws(FileNotFoundException::class) + override fun openFile(uri: Uri, mode: String): ParcelFileDescriptor { + val file = getFileForUri(uri)?.takeIf { it.exists() } ?: throw FileNotFoundException("File represented by uri $uri could not be found") + debug { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + "$callingPackage opened ${file.path}" + } else { + "${file.path} was opened by an application" + } + } + return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY) + } + + companion object { + private val COLUMNS = arrayOf(OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE) + private const val MIME_TYPE_OCTET_STREAM = "application/octet-stream" + + /** + * @param context a a context + * @return authority of this provider + */ + private fun getAuthority(context: Context): String { + return context.packageName + ".acra" + } + + /** + * Get an uri for this content provider for the given file + * + * @param context a context + * @param file the file + * @return the uri + */ + fun getUriForFile(context: Context, file: File): Uri { + return getUriForFile(context, Directory.ROOT, file.path) + } + + /** + * Get an uri for this content provider for the given file + * + * @param context a context + * @param directory the directory, to with the path is relative + * @param relativePath the file path + * @return the uri + */ + fun getUriForFile(context: Context, directory: Directory, relativePath: String): Uri { + val builder = Uri.Builder() + .scheme(ContentResolver.SCHEME_CONTENT) + .authority(getAuthority(context)) + .appendPath(directory.name.toLowerCase(Locale.ROOT)) + for (segment in relativePath.split(Pattern.quote(File.separator)).toTypedArray()) { + if (segment.isNotEmpty()) { + builder.appendPath(segment) + } + } + return builder.build() + } + + /** + * Tries to guess the mime type from uri extension + * + * @param uri the uri + * @return the mime type of the uri, with fallback [.MIME_TYPE_OCTET_STREAM] + */ + @JvmStatic + fun guessMimeType(uri: Uri): String { + var type: String? = null + val fileExtension = MimeTypeMap.getFileExtensionFromUrl(uri + .toString()) + if (fileExtension != null) { + type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(fileExtension.toLowerCase(Locale.ROOT)) + if (type == null && "json" == fileExtension) { + type = "application/json" + } + } + return type ?: MIME_TYPE_OCTET_STREAM + } + } +} \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/attachment/AttachmentUriProvider.java b/acra-core/src/main/java/org/acra/attachment/AttachmentUriProvider.kt similarity index 71% rename from acra-core/src/main/java/org/acra/attachment/AttachmentUriProvider.java rename to acra-core/src/main/java/org/acra/attachment/AttachmentUriProvider.kt index 890492955b..d3b9b22294 100644 --- a/acra-core/src/main/java/org/acra/attachment/AttachmentUriProvider.java +++ b/acra-core/src/main/java/org/acra/attachment/AttachmentUriProvider.kt @@ -13,16 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +package org.acra.attachment -package org.acra.attachment; - -import android.content.Context; -import android.net.Uri; -import androidx.annotation.NonNull; - -import org.acra.config.CoreConfiguration; - -import java.util.List; +import android.content.Context +import android.net.Uri +import org.acra.config.CoreConfiguration /** * Provides attachment uris to ACRA @@ -30,13 +25,11 @@ * @author F43nd1r * @since 09.03.2017 */ -public interface AttachmentUriProvider { - +interface AttachmentUriProvider { /** * @param context a context * @param configuration ACRA configuration * @return all file uris that should be attached to the report */ - @NonNull - List getAttachments(@NonNull Context context, @NonNull CoreConfiguration configuration); -} + fun getAttachments(context: Context, configuration: CoreConfiguration): List +} \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/attachment/DefaultAttachmentProvider.java b/acra-core/src/main/java/org/acra/attachment/DefaultAttachmentProvider.kt similarity index 50% rename from acra-core/src/main/java/org/acra/attachment/DefaultAttachmentProvider.java rename to acra-core/src/main/java/org/acra/attachment/DefaultAttachmentProvider.kt index 755c440be2..25e1e79022 100644 --- a/acra-core/src/main/java/org/acra/attachment/DefaultAttachmentProvider.java +++ b/acra-core/src/main/java/org/acra/attachment/DefaultAttachmentProvider.kt @@ -13,20 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +package org.acra.attachment -package org.acra.attachment; - -import android.content.Context; -import android.net.Uri; -import androidx.annotation.NonNull; - -import org.acra.ACRA; -import org.acra.config.CoreConfiguration; - -import java.util.ArrayList; -import java.util.List; - -import static org.acra.ACRA.LOG_TAG; +import android.content.Context +import android.net.Uri +import org.acra.config.CoreConfiguration +import org.acra.log.error /** * Reads attachment uris from the configuration @@ -34,23 +26,18 @@ * @author F43nd1r * @since 10.03.2017 */ - -public class DefaultAttachmentProvider implements AttachmentUriProvider { - +class DefaultAttachmentProvider : AttachmentUriProvider { /** * {@inheritDoc} */ - @NonNull - @Override - public List getAttachments(@NonNull Context context, @NonNull CoreConfiguration configuration) { - final ArrayList result = new ArrayList<>(); - for (String s : configuration.getAttachmentUris()) { + override fun getAttachments(context: Context, configuration: CoreConfiguration): List { + return configuration.attachmentUris.mapNotNull { try { - result.add(Uri.parse(s)); - } catch (Exception e) { - ACRA.log.e(LOG_TAG, "Failed to parse Uri " + s, e); + Uri.parse(it) + } catch (e: Exception) { + error(e) { "Failed to parse Uri $it" } + null } } - return result; } -} +} \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/builder/LastActivityManager.java b/acra-core/src/main/java/org/acra/builder/LastActivityManager.java deleted file mode 100644 index a3dd33ef29..0000000000 --- a/acra-core/src/main/java/org/acra/builder/LastActivityManager.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright (c) 2017 - * - * 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.builder; - -import android.app.Activity; -import android.app.Application; -import android.os.Bundle; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import org.acra.ACRA; -import org.acra.collections.WeakStack; - -import java.util.ArrayList; -import java.util.List; - -import static org.acra.ACRA.LOG_TAG; - -/** - * Responsible for tracking the last Activity that was created. - * - * @since 4.8.0 - */ -public final class LastActivityManager { - - @NonNull - private final WeakStack activityStack = new WeakStack<>(); - - /** - * Create and register a new instance - * - * @param application the application to attach to - */ - public LastActivityManager(@NonNull Application application) { - application.registerActivityLifecycleCallbacks(new Application.ActivityLifecycleCallbacks() { - @Override - public void onActivityCreated(@NonNull Activity activity, Bundle savedInstanceState) { - if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "onActivityCreated " + activity.getClass()); - activityStack.add(activity); - } - - @Override - public void onActivityStarted(@NonNull Activity activity) { - if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "onActivityStarted " + activity.getClass()); - } - - @Override - public void onActivityResumed(@NonNull Activity activity) { - if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "onActivityResumed " + activity.getClass()); - } - - @Override - public void onActivityPaused(@NonNull Activity activity) { - if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "onActivityPaused " + activity.getClass()); - } - - @Override - public void onActivityStopped(@NonNull Activity activity) { - if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "onActivityStopped " + activity.getClass()); - } - - @Override - public void onActivitySaveInstanceState(@NonNull Activity activity, Bundle outState) { - if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "onActivitySaveInstanceState " + activity.getClass()); - } - - @Override - public void onActivityDestroyed(@NonNull Activity activity) { - if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "onActivityDestroyed " + activity.getClass()); - synchronized (activityStack) { - activityStack.remove(activity); - activityStack.notify(); - } - } - }); - } - - /** - * @return last created activity, if any - */ - @Nullable - public Activity getLastActivity() { - return activityStack.peek(); - } - - /** - * @return a list of activities in the current process - */ - @NonNull - public List getLastActivities() { - return new ArrayList<>(activityStack); - } - - /** - * clear saved activities - */ - public void clearLastActivities() { - activityStack.clear(); - } - - /** - * wait until the last activity is stopped - * - * @param timeOutInMillis timeout for wait - */ - public void waitForAllActivitiesDestroy(int timeOutInMillis) { - synchronized (activityStack) { - long start = System.currentTimeMillis(); - long now = start; - while (!activityStack.isEmpty() && start + timeOutInMillis > now) { - try { - activityStack.wait(start - now + timeOutInMillis); - } catch (InterruptedException ignored) { - } - now = System.currentTimeMillis(); - } - } - } -} diff --git a/acra-core/src/main/java/org/acra/builder/LastActivityManager.kt b/acra-core/src/main/java/org/acra/builder/LastActivityManager.kt new file mode 100644 index 0000000000..93e37e2c16 --- /dev/null +++ b/acra-core/src/main/java/org/acra/builder/LastActivityManager.kt @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2017 + * + * 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.builder + +import android.app.Activity +import android.app.Application +import android.app.Application.ActivityLifecycleCallbacks +import android.os.Bundle +import org.acra.collections.WeakStack +import org.acra.log.debug +import java.util.* +import java.util.concurrent.TimeUnit +import java.util.concurrent.locks.ReentrantLock +import kotlin.concurrent.withLock + +/** + * Responsible for tracking the last Activity that was created. + * + * @since 4.8.0 + */ +class LastActivityManager(application: Application) { + private val activityStack = WeakStack() + private val lock = ReentrantLock() + private val destroyedCondition = lock.newCondition() + + /** + * @return last created activity, if any + */ + val lastActivity: Activity? + get() = activityStack.peek() + + /** + * @return a list of activities in the current process + */ + val lastActivities: List + get() = ArrayList(activityStack) + + /** + * clear saved activities + */ + fun clearLastActivities() { + activityStack.clear() + } + + /** + * wait until the last activity is stopped + * + * @param timeOutInMillis timeout for wait + */ + fun waitForAllActivitiesDestroy(timeOutInMillis: Int) { + lock.withLock { + val start = System.currentTimeMillis() + var now = start + while (!activityStack.isEmpty() && start + timeOutInMillis > now) { + destroyedCondition.await(start - now + timeOutInMillis, TimeUnit.MILLISECONDS) + now = System.currentTimeMillis() + } + } + } + + /** + * Create and register a new instance + * + * @param application the application to attach to + */ + init { + application.registerActivityLifecycleCallbacks(object : ActivityLifecycleCallbacks { + override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) { + debug { "onActivityCreated ${activity.javaClass}" } + activityStack.add(activity) + } + + override fun onActivityStarted(activity: Activity) { + debug { "onActivityStarted ${activity.javaClass}" } + } + + override fun onActivityResumed(activity: Activity) { + debug { "onActivityResumed ${activity.javaClass}" } + } + + override fun onActivityPaused(activity: Activity) { + debug { "onActivityPaused ${activity.javaClass}" } + } + + override fun onActivityStopped(activity: Activity) { + debug { "onActivityStopped ${activity.javaClass}" } + } + + override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) { + debug { "onActivitySaveInstanceState ${activity.javaClass}" } + } + + override fun onActivityDestroyed(activity: Activity) { + debug { "onActivityDestroyed ${activity.javaClass}" } + lock.withLock { + activityStack.remove(activity) + destroyedCondition.signalAll() + } + } + }) + } +} \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/builder/ReportBuilder.java b/acra-core/src/main/java/org/acra/builder/ReportBuilder.java deleted file mode 100644 index b6533ff4bd..0000000000 --- a/acra-core/src/main/java/org/acra/builder/ReportBuilder.java +++ /dev/null @@ -1,185 +0,0 @@ -/* - * Copyright (c) 2017 - * - * 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.builder; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import java.util.HashMap; -import java.util.Map; - -/** - * Fluent API used to assemble the different options used for a crash report. - * - * @since 4.8.0 - */ -public final class ReportBuilder { - - private String message; - private Thread uncaughtExceptionThread; - private Throwable exception; - private final Map customData = new HashMap<>(); - - private boolean sendSilently = false; - private boolean endApplication = false; - - /** - * Set the error message to be reported. - * - * @param msg the error message - * @return the updated {@code ReportBuilder} - */ - @NonNull - @SuppressWarnings("unused") - public ReportBuilder message(@Nullable String msg) { - message = msg; - return this; - } - - /** - * @return the error message, or null if none is present - */ - @Nullable - public String getMessage() { - return message; - } - - /** - * Sets the Thread on which an uncaught Exception occurred. - * - * @param thread Thread on which an uncaught Exception occurred. - * @return the updated {@code ReportBuilder} - */ - @NonNull - public ReportBuilder uncaughtExceptionThread(@Nullable Thread thread) { - uncaughtExceptionThread = thread; - return this; - } - - /** - * @return the Thread on which an uncaught Exception occurred, or null if none present - */ - @Nullable - public Thread getUncaughtExceptionThread() { - return uncaughtExceptionThread; - } - - /** - * Set the stack trace to be reported - * - * @param e The exception that should be associated with this report - * @return the updated {@code ReportBuilder} - */ - @NonNull - public ReportBuilder exception(@Nullable Throwable e) { - exception = e; - return this; - } - - /** - * @return the exception, or null if none present - */ - @Nullable - public Throwable getException() { - return exception; - } - - /** - * Sets additional values to be added to {@link org.acra.ReportField#CUSTOM_DATA}. Values - * specified here take precedence over globally specified custom data. - * - * @param customData a map of custom key-values to be attached to the report - * @return the updated {@code ReportBuilder} - */ - @NonNull - @SuppressWarnings("unused") - public ReportBuilder customData(@NonNull Map customData) { - this.customData.putAll(customData); - return this; - } - - /** - * Sets an additional value to be added to {@link org.acra.ReportField#CUSTOM_DATA}. The value - * specified here takes precedence over globally specified custom data. - * - * @param key the key identifying the custom data - * @param value the value for the custom data entry - * @return the updated {@code ReportBuilder} - */ - @NonNull - @SuppressWarnings("unused") - public ReportBuilder customData(@NonNull String key, String value) { - customData.put(key, value); - return this; - } - - /** - * @return a map with all custom data - */ - @NonNull - public Map getCustomData() { - return new HashMap<>(customData); - } - - /** - * Forces the report to be sent silently, ignoring all interactions - * - * @return the updated {@code ReportBuilder} - */ - @NonNull - public ReportBuilder sendSilently() { - sendSilently = true; - return this; - } - - /** - * @return if this should send silently - */ - public boolean isSendSilently() { - return sendSilently; - } - - /** - * Ends the application after sending the crash report - * - * @return the updated {@code ReportBuilder} - */ - @NonNull - public ReportBuilder endApplication() { - endApplication = true; - return this; - } - - /** - * @return if this should stop the application after collecting - */ - public boolean isEndApplication() { - return endApplication; - } - - /** - * Assembles and sends the crash report. - * - * @param reportExecutor ReportExecutor to use to build the report. - */ - public void build(@NonNull ReportExecutor reportExecutor) { - if (message == null && exception == null) { - message = "Report requested by developer"; - } - - reportExecutor.execute(this); - } -} diff --git a/acra-core/src/main/java/org/acra/builder/ReportBuilder.kt b/acra-core/src/main/java/org/acra/builder/ReportBuilder.kt new file mode 100644 index 0000000000..a19b689e3b --- /dev/null +++ b/acra-core/src/main/java/org/acra/builder/ReportBuilder.kt @@ -0,0 +1,153 @@ +/* + * Copyright (c) 2017 + * + * 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.builder + +import java.util.* + +/** + * Fluent API used to assemble the different options used for a crash report. + * + * @since 4.8.0 + */ +class ReportBuilder { + /** + * @return the error message, or null if none is present + */ + var message: String? = null + private set + + /** + * @return the Thread on which an uncaught Exception occurred, or null if none present + */ + var uncaughtExceptionThread: Thread? = null + private set + + /** + * @return the exception, or null if none present + */ + var exception: Throwable? = null + private set + private val customData: MutableMap = HashMap() + + /** + * @return if this should send silently + */ + var isSendSilently = false + private set + + /** + * @return if this should stop the application after collecting + */ + var isEndApplication = false + private set + + /** + * Set the error message to be reported. + * + * @param msg the error message + * @return the updated `ReportBuilder` + */ + fun message(msg: String?): ReportBuilder { + message = msg + return this + } + + /** + * Sets the Thread on which an uncaught Exception occurred. + * + * @param thread Thread on which an uncaught Exception occurred. + * @return the updated `ReportBuilder` + */ + fun uncaughtExceptionThread(thread: Thread?): ReportBuilder { + uncaughtExceptionThread = thread + return this + } + + /** + * Set the stack trace to be reported + * + * @param e The exception that should be associated with this report + * @return the updated `ReportBuilder` + */ + fun exception(e: Throwable?): ReportBuilder { + exception = e + return this + } + + /** + * Sets additional values to be added to [org.acra.ReportField.CUSTOM_DATA]. Values + * specified here take precedence over globally specified custom data. + * + * @param customData a map of custom key-values to be attached to the report + * @return the updated `ReportBuilder` + */ + fun customData(customData: Map): ReportBuilder { + this.customData.putAll(customData) + return this + } + + /** + * Sets an additional value to be added to [org.acra.ReportField.CUSTOM_DATA]. The value + * specified here takes precedence over globally specified custom data. + * + * @param key the key identifying the custom data + * @param value the value for the custom data entry + * @return the updated `ReportBuilder` + */ + fun customData(key: String, value: String): ReportBuilder { + customData[key] = value + return this + } + + /** + * @return a map with all custom data + */ + fun getCustomData(): Map { + return HashMap(customData) + } + + /** + * Forces the report to be sent silently, ignoring all interactions + * + * @return the updated `ReportBuilder` + */ + fun sendSilently(): ReportBuilder { + isSendSilently = true + return this + } + + /** + * Ends the application after sending the crash report + * + * @return the updated `ReportBuilder` + */ + fun endApplication(): ReportBuilder { + isEndApplication = true + return this + } + + /** + * Assembles and sends the crash report. + * + * @param reportExecutor ReportExecutor to use to build the report. + */ + fun build(reportExecutor: ReportExecutor) { + if (message == null && exception == null) { + message = "Report requested by developer" + } + reportExecutor.execute(this) + } +} \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/builder/ReportExecutor.java b/acra-core/src/main/java/org/acra/builder/ReportExecutor.java deleted file mode 100644 index 9d7eca6cdf..0000000000 --- a/acra-core/src/main/java/org/acra/builder/ReportExecutor.java +++ /dev/null @@ -1,278 +0,0 @@ -/* - * Copyright (c) 2017 - * - * 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.builder; - -import android.content.Context; -import android.os.Debug; -import android.os.Looper; -import android.os.StrictMode; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import android.widget.Toast; -import org.acra.ACRA; -import org.acra.ACRAConstants; -import org.acra.config.CoreConfiguration; -import org.acra.config.ReportingAdministrator; -import org.acra.data.CrashReportData; -import org.acra.data.CrashReportDataFactory; -import org.acra.file.CrashReportPersister; -import org.acra.file.ReportLocator; -import org.acra.interaction.ReportInteractionExecutor; -import org.acra.scheduler.SchedulerStarter; -import org.acra.sender.LegacySenderService; -import org.acra.util.ProcessFinisher; -import org.acra.util.ToastSender; - -import java.io.File; -import java.util.List; - -import static org.acra.ACRA.LOG_TAG; -import static org.acra.ReportField.IS_SILENT; -import static org.acra.ReportField.USER_CRASH_DATE; - -/** - * Collates, records and initiates the sending of a report. - * - * @since 4.8.0 - */ -public class ReportExecutor { - - private final Context context; - private final CoreConfiguration config; - 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. - private final Thread.UncaughtExceptionHandler defaultExceptionHandler; - - private final ProcessFinisher processFinisher; - - private boolean enabled = false; - - /** - * Creates a new instance - * - * @param context a context - * @param config the config - * @param crashReportDataFactory factory used to collect data - * @param defaultExceptionHandler pass-through handler - * @param processFinisher used to end process after reporting - * @param schedulerStarter used to start report sending - * @param lastActivityManager used to finish activities after reporting - */ - public ReportExecutor(@NonNull Context context, @NonNull CoreConfiguration config, @NonNull CrashReportDataFactory crashReportDataFactory, - @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 = config.getPluginLoader().loadEnabled(config, ReportingAdministrator.class); - this.schedulerStarter = schedulerStarter; - this.lastActivityManager = lastActivityManager; - } - - /** - * pass-through to default handler - * - * @param t the crashed thread - * @param e the uncaught exception - */ - public void handReportToDefaultExceptionHandler(@Nullable Thread t, @NonNull Throwable e) { - if (defaultExceptionHandler != null) { - ACRA.log.i(LOG_TAG, "ACRA is disabled for " + context.getPackageName() - + " - forwarding uncaught Exception on to default ExceptionHandler"); - defaultExceptionHandler.uncaughtException(t, e); - } else { - ACRA.log.e(LOG_TAG, "ACRA is disabled for " + context.getPackageName() + " - no default ExceptionHandler"); - ACRA.log.e(LOG_TAG, "ACRA caught a " + e.getClass().getSimpleName() + " for " + context.getPackageName(), e); - } - - } - - public boolean isEnabled() { - return enabled; - } - - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - - /** - * Try to create a report. Also starts {@link LegacySenderService} - * - * @param reportBuilder The report builder used to assemble the report - */ - public final void execute(@NonNull final ReportBuilder reportBuilder) { - - if (!enabled) { - ACRA.log.v(LOG_TAG, "ACRA is disabled. Report not sent."); - return; - } - - ReportingAdministrator blockingAdministrator = null; - for (ReportingAdministrator administrator : reportingAdministrators) { - try { - if (!administrator.shouldStartCollecting(context, config, reportBuilder)) { - blockingAdministrator = administrator; - } - } catch (Exception t) { - ACRA.log.w(LOG_TAG, "ReportingAdministrator " + administrator.getClass().getName() + " threw exception", t); - } - } - final CrashReportData crashReportData; - if (blockingAdministrator == null) { - crashReportData = crashReportDataFactory.createCrashData(reportBuilder); - for (ReportingAdministrator administrator : reportingAdministrators) { - try { - if (!administrator.shouldSendReport(context, config, crashReportData)) { - blockingAdministrator = administrator; - } - } catch (Exception t) { - ACRA.log.w(LOG_TAG, "ReportingAdministrator " + administrator.getClass().getName() + " threw exception", t); - } - } - } else { - crashReportData = null; - if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "Not collecting crash report because of ReportingAdministrator " + blockingAdministrator.getClass().getName()); - } - if (reportBuilder.isEndApplication()) { - boolean finishActivity = true; - for (ReportingAdministrator administrator : reportingAdministrators) { - try { - if (!administrator.shouldFinishActivity(context, config, lastActivityManager)) { - finishActivity = false; - } - } catch (Exception 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(); - final File reportFile = getReportFileName(crashReportData); - saveCrashReportFile(reportFile, crashReportData); - - final ReportInteractionExecutor executor = new ReportInteractionExecutor(context, config); - if (reportBuilder.isSendSilently()) { - //if no interactions are present we can send all reports - sendReport(reportFile, executor.hasInteractions()); - } else { - if (executor.performInteractions(reportFile)) { - sendReport(reportFile, false); - } - } - StrictMode.setThreadPolicy(oldPolicy); - } else { - if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "Not sending crash report because of ReportingAdministrator " + blockingAdministrator.getClass().getName()); - try { - blockingAdministrator.notifyReportDropped(context, config); - } catch (Exception t) { - ACRA.log.w(LOG_TAG, "ReportingAdministrator " + blockingAdministrator.getClass().getName() + " threw exeption", t); - } - } - if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "Wait for Interactions + worker ended. Kill Application ? " + reportBuilder.isEndApplication()); - - if (reportBuilder.isEndApplication()) { - boolean endApplication = true; - for (ReportingAdministrator administrator : reportingAdministrators) { - try { - if (!administrator.shouldKillApplication(context, config, reportBuilder, crashReportData)) { - endApplication = false; - } - } catch (Exception t) { - ACRA.log.w(LOG_TAG, "ReportingAdministrator " + administrator.getClass().getName() + " threw exception", t); - } - } - if (endApplication) { - if (Debug.isDebuggerConnected()) { - //Killing a process with a debugger attached would kill the whole application including our service, so we can't do that. - final String warning = "Warning: Acra may behave differently with a debugger attached"; - new Thread(() -> { - Looper.prepare(); - ToastSender.sendToast(context, warning, Toast.LENGTH_LONG); - Looper.loop(); - }).start(); - ACRA.log.w(LOG_TAG, warning); - } else { - endApplication(reportBuilder.getUncaughtExceptionThread(), reportBuilder.getException()); - } - } - } - } - - /** - * End the application. - */ - private void endApplication(@Nullable Thread uncaughtExceptionThread, Throwable th) { - final boolean letDefaultHandlerEndApplication = config.getAlsoReportToAndroidFramework(); - - final boolean handlingUncaughtException = uncaughtExceptionThread != null; - if (handlingUncaughtException && letDefaultHandlerEndApplication && defaultExceptionHandler != null) { - // Let the system default handler do it's job and display the force close dialog. - if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "Handing Exception on to default ExceptionHandler"); - defaultExceptionHandler.uncaughtException(uncaughtExceptionThread, th); - } else { - processFinisher.endApplication(); - } - } - - /** - * Starts a Process to start sending outstanding error reports. - * - * @param onlySendSilentReports If true then only send silent reports. - */ - private void sendReport(@NonNull File report, boolean onlySendSilentReports) { - if (enabled) { - schedulerStarter.scheduleReports(report, onlySendSilentReports); - } else { - ACRA.log.w(LOG_TAG, "Would be sending reports, but ACRA is disabled"); - } - } - - @NonNull - private File getReportFileName(@NonNull CrashReportData crashData) { - final String timestamp = crashData.getString(USER_CRASH_DATE); - final String isSilent = crashData.getString(IS_SILENT); - final String fileName = timestamp + (isSilent != null && Boolean.parseBoolean(isSilent) ? ACRAConstants.SILENT_SUFFIX : "") + ACRAConstants.REPORTFILE_EXTENSION; - final ReportLocator reportLocator = new ReportLocator(context); - return new File(reportLocator.getUnapprovedFolder(), fileName); - } - - /** - * Store a report - * - * @param file the file to store in - * @param crashData the content - */ - private void saveCrashReportFile(@NonNull File file, @NonNull CrashReportData crashData) { - try { - if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "Writing crash report file " + file); - final CrashReportPersister persister = new CrashReportPersister(); - persister.store(crashData, file); - } catch (Exception e) { - ACRA.log.e(LOG_TAG, "An error occurred while writing the report file...", e); - } - } -} diff --git a/acra-core/src/main/java/org/acra/builder/ReportExecutor.kt b/acra-core/src/main/java/org/acra/builder/ReportExecutor.kt new file mode 100644 index 0000000000..014ae5a7a5 --- /dev/null +++ b/acra-core/src/main/java/org/acra/builder/ReportExecutor.kt @@ -0,0 +1,226 @@ +/* + * Copyright (c) 2017 + * + * 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.builder + +import android.content.Context +import android.os.Debug +import android.os.Looper +import android.os.StrictMode +import android.widget.Toast +import org.acra.ACRAConstants +import org.acra.ReportField +import org.acra.config.CoreConfiguration +import org.acra.config.ReportingAdministrator +import org.acra.data.CrashReportData +import org.acra.data.CrashReportDataFactory +import org.acra.file.CrashReportPersister +import org.acra.file.ReportLocator +import org.acra.interaction.ReportInteractionExecutor +import org.acra.log.debug +import org.acra.log.error +import org.acra.log.info +import org.acra.log.warn +import org.acra.plugins.loadEnabled +import org.acra.scheduler.SchedulerStarter +import org.acra.util.ProcessFinisher +import org.acra.util.ToastSender.sendToast +import java.io.File +import java.lang.RuntimeException + +/** + * Collates, records and initiates the sending of a report. + * + * @since 4.8.0 + */ +class ReportExecutor(private val context: Context, private val config: CoreConfiguration, private val crashReportDataFactory: CrashReportDataFactory, + // A reference to the system's previous default UncaughtExceptionHandler + // kept in order to execute the default exception handling after sending the report. + private val defaultExceptionHandler: Thread.UncaughtExceptionHandler?, private val processFinisher: ProcessFinisher, + private val schedulerStarter: SchedulerStarter, + private val lastActivityManager: LastActivityManager) { + private val reportingAdministrators: List = config.pluginLoader.loadEnabled(config) + var isEnabled = false + + /** + * pass-through to default handler + * + * @param t the crashed thread + * @param e the uncaught exception + */ + fun handReportToDefaultExceptionHandler(t: Thread, e: Throwable) { + if (defaultExceptionHandler != null) { + info { "ACRA is disabled for " + context.packageName + " - forwarding uncaught Exception on to default ExceptionHandler" } + defaultExceptionHandler.uncaughtException(t, e) + } else { + error { "ACRA is disabled for ${context.packageName} - no default ExceptionHandler" } + error(e) { "ACRA caught a ${e.javaClass.simpleName} for ${context.packageName}" } + } + } + + /** + * Try to create a report. Also starts [LegacySenderService] + * + * @param reportBuilder The report builder used to assemble the report + */ + fun execute(reportBuilder: ReportBuilder) { + if (!isEnabled) { + warn { "ACRA is disabled. Report not sent." } + return + } + var blockingAdministrator: ReportingAdministrator? = null + for (administrator in reportingAdministrators) { + try { + if (!administrator.shouldStartCollecting(context, config, reportBuilder)) { + blockingAdministrator = administrator + } + } catch (t: Exception) { + warn(t) { "ReportingAdministrator ${administrator.javaClass.name} threw exception" } + } + } + val crashReportData: CrashReportData? + if (blockingAdministrator == null) { + crashReportData = crashReportDataFactory.createCrashData(reportBuilder) + for (administrator in reportingAdministrators) { + try { + if (!administrator.shouldSendReport(context, config, crashReportData)) { + blockingAdministrator = administrator + } + } catch (t: Exception) { + warn(t) { "ReportingAdministrator ${administrator.javaClass.name} threw exception" } + } + } + } else { + crashReportData = null + debug { "Not collecting crash report because of ReportingAdministrator " + blockingAdministrator.javaClass.name } + } + if (reportBuilder.isEndApplication) { + var finishActivity = true + for (administrator in reportingAdministrators) { + try { + if (!administrator.shouldFinishActivity(context, config, lastActivityManager)) { + finishActivity = false + } + } catch (t: Exception) { + warn(t) { "ReportingAdministrator " + administrator.javaClass.name + " threw exception" } + } + } + if (finishActivity) { + // Finish the last activity early to prevent restarts on android 7+ + processFinisher.finishLastActivity(reportBuilder.uncaughtExceptionThread) + } + } + if (blockingAdministrator == null) { + val oldPolicy = StrictMode.allowThreadDiskWrites() + val reportFile = getReportFileName(crashReportData!!) + saveCrashReportFile(reportFile, crashReportData) + val executor = ReportInteractionExecutor(context, config) + if (reportBuilder.isSendSilently) { + //if no interactions are present we can send all reports + sendReport(reportFile, executor.hasInteractions()) + } else { + if (executor.performInteractions(reportFile)) { + sendReport(reportFile, false) + } + } + StrictMode.setThreadPolicy(oldPolicy) + } else { + debug { "Not sending crash report because of ReportingAdministrator ${blockingAdministrator.javaClass.name}" } + try { + blockingAdministrator.notifyReportDropped(context, config) + } catch (t: Exception) { + warn(t) { "ReportingAdministrator ${blockingAdministrator.javaClass.name} threw exeption" } + } + } + debug { "Wait for Interactions + worker ended. Kill Application ? ${reportBuilder.isEndApplication}" } + if (reportBuilder.isEndApplication) { + var endApplication = true + for (administrator in reportingAdministrators) { + try { + if (!administrator.shouldKillApplication(context, config, reportBuilder, crashReportData)) { + endApplication = false + } + } catch (t: Exception) { + warn(t) { "ReportingAdministrator ${administrator.javaClass.name} threw exception" } + } + } + if (endApplication) { + if (Debug.isDebuggerConnected()) { + //Killing a process with a debugger attached would kill the whole application including our service, so we can't do that. + val warning = "Warning: Acra may behave differently with a debugger attached" + Thread { + Looper.prepare() + sendToast(context, warning, Toast.LENGTH_LONG) + Looper.loop() + }.start() + warn { warning } + } else { + endApplication(reportBuilder.uncaughtExceptionThread, reportBuilder.exception?: RuntimeException()) + } + } + } + } + + /** + * End the application. + */ + private fun endApplication(uncaughtExceptionThread: Thread?, th: Throwable) { + val letDefaultHandlerEndApplication: Boolean = config.alsoReportToAndroidFramework + if (uncaughtExceptionThread != null && letDefaultHandlerEndApplication && defaultExceptionHandler != null) { + // Let the system default handler do it's job and display the force close dialog. + debug { "Handing Exception on to default ExceptionHandler" } + defaultExceptionHandler.uncaughtException(uncaughtExceptionThread, th) + } else { + processFinisher.endApplication() + } + } + + /** + * Starts a Process to start sending outstanding error reports. + * + * @param onlySendSilentReports If true then only send silent reports. + */ + private fun sendReport(report: File, onlySendSilentReports: Boolean) { + if (isEnabled) { + schedulerStarter.scheduleReports(report, onlySendSilentReports) + } else { + warn { "Would be sending reports, but ACRA is disabled" } + } + } + + private fun getReportFileName(crashData: CrashReportData): File { + val timestamp = crashData.getString(ReportField.USER_CRASH_DATE) + val isSilent = crashData.getString(ReportField.IS_SILENT) + val fileName = timestamp + (if (isSilent != null && java.lang.Boolean.parseBoolean(isSilent)) ACRAConstants.SILENT_SUFFIX else "") + ACRAConstants.REPORTFILE_EXTENSION + val reportLocator = ReportLocator(context) + return File(reportLocator.unapprovedFolder, fileName) + } + + /** + * Store a report + * + * @param file the file to store in + * @param crashData the content + */ + private fun saveCrashReportFile(file: File, crashData: CrashReportData) { + try { + debug { "Writing crash report file $file" } + CrashReportPersister().store(crashData, file) + } catch (e: Exception) { + error(e) { "An error occurred while writing the report file..." } + } + } + +} \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/collector/ApplicationStartupCollector.java b/acra-core/src/main/java/org/acra/collector/ApplicationStartupCollector.kt similarity index 70% rename from acra-core/src/main/java/org/acra/collector/ApplicationStartupCollector.java rename to acra-core/src/main/java/org/acra/collector/ApplicationStartupCollector.kt index f8f2d49246..689f116792 100644 --- a/acra-core/src/main/java/org/acra/collector/ApplicationStartupCollector.java +++ b/acra-core/src/main/java/org/acra/collector/ApplicationStartupCollector.kt @@ -13,13 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +package org.acra.collector -package org.acra.collector; - -import android.content.Context; -import androidx.annotation.NonNull; - -import org.acra.config.CoreConfiguration; +import android.content.Context +import org.acra.config.CoreConfiguration /** * A collector that is also called at startup @@ -27,14 +24,12 @@ * @author F43nd1r * @since 29.09.2017 */ - -@SuppressWarnings("WeakerAccess") -public interface ApplicationStartupCollector extends Collector { +interface ApplicationStartupCollector : Collector { /** * collect startup data * * @param context a context * @param config the config */ - void collectApplicationStartUp(@NonNull Context context, @NonNull CoreConfiguration config); -} + fun collectApplicationStartUp(context: Context, config: CoreConfiguration) +} \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/collector/BaseReportFieldCollector.java b/acra-core/src/main/java/org/acra/collector/BaseReportFieldCollector.java deleted file mode 100644 index fa0ed1bd27..0000000000 --- a/acra-core/src/main/java/org/acra/collector/BaseReportFieldCollector.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright 2016 - * - * 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.collector; - -import android.content.Context; -import androidx.annotation.NonNull; - -import org.acra.ReportField; -import org.acra.builder.ReportBuilder; -import org.acra.config.CoreConfiguration; -import org.acra.data.CrashReportData; - -/** - * Base implementation of a collector. - * Maintains information on which fields can be collected by this collector. - * Validates constraints in which a field should (not) be collected. - * - * @author F43nd1r - * @since 4.9.1 - */ -abstract class BaseReportFieldCollector implements Collector { - private final ReportField[] reportFields; - - /** - * create a new Collector that is able to collect these reportFields - * - * @param firstField the first supported field (split away to ensure each collector supports at least one field) - * @param reportFields the supported reportFields - */ - BaseReportFieldCollector(@NonNull ReportField firstField, @NonNull ReportField... reportFields) { - this.reportFields = new ReportField[reportFields.length + 1]; - this.reportFields[0] = firstField; - if (reportFields.length > 0) { - System.arraycopy(reportFields, 0, this.reportFields, 1, reportFields.length); - } - } - - /** - * this should check if the config contains the field, but may add additional checks like permissions etc. - * - * @param context a context - * @param config current configuration - * @param collect the field to collect - * @param reportBuilder the current reportBuilder - * @return if this field should be collected now - */ - boolean shouldCollect(@NonNull Context context, @NonNull CoreConfiguration config, @NonNull ReportField collect, @NonNull ReportBuilder reportBuilder) { - return config.getReportContent().contains(collect); - } - - /** - * Calls {@link #shouldCollect(Context, CoreConfiguration, ReportField, ReportBuilder)} for each ReportField - * and then {@link #collect(ReportField, Context, CoreConfiguration, ReportBuilder, CrashReportData)} if it returned true - */ - @Override - public final void collect(@NonNull Context context, @NonNull CoreConfiguration config, @NonNull ReportBuilder reportBuilder, @NonNull CrashReportData target) throws CollectorException { - for (ReportField field : reportFields) { - try { - if (shouldCollect(context, config, field, reportBuilder)) { - collect(field, context, config, reportBuilder, target); - } - } catch (Exception t) { - target.put(field, (String) null); - throw new CollectorException("Error while retrieving " + field.name() + " data:" + t.getMessage(), t); - } - } - } - - /** - * Collect a ReportField - * - * @param reportField the reportField to collect - * @param context a context - * @param config current Configuration - * @param reportBuilder current ReportBuilder - * @param target put results here - * @throws Exception if collection failed - */ - abstract void collect(@NonNull ReportField reportField, @NonNull Context context, @NonNull CoreConfiguration config, @NonNull ReportBuilder reportBuilder, @NonNull CrashReportData target) throws Exception; -} diff --git a/acra-core/src/main/java/org/acra/collector/BaseReportFieldCollector.kt b/acra-core/src/main/java/org/acra/collector/BaseReportFieldCollector.kt new file mode 100644 index 0000000000..fbd3e7948e --- /dev/null +++ b/acra-core/src/main/java/org/acra/collector/BaseReportFieldCollector.kt @@ -0,0 +1,77 @@ +/* + * Copyright 2016 + * + * 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.collector + +import android.content.Context +import org.acra.ReportField +import org.acra.builder.ReportBuilder +import org.acra.config.CoreConfiguration +import org.acra.data.CrashReportData + +/** + * Base implementation of a collector. + * Maintains information on which fields can be collected by this collector. + * Validates constraints in which a field should (not) be collected. + * + * @author F43nd1r + * @since 4.9.1 + */ +abstract class BaseReportFieldCollector(private vararg val reportFields: ReportField) : Collector { + + /** + * this should check if the config contains the field, but may add additional checks like permissions etc. + * + * @param context a context + * @param config current configuration + * @param collect the field to collect + * @param reportBuilder the current reportBuilder + * @return if this field should be collected now + */ + open fun shouldCollect(context: Context, config: CoreConfiguration, collect: ReportField, reportBuilder: ReportBuilder): Boolean { + return config.reportContent.contains(collect) + } + + /** + * Calls [.shouldCollect] for each ReportField + * and then [.collect] if it returned true + */ + @Throws(CollectorException::class) + override fun collect(context: Context, config: CoreConfiguration, reportBuilder: ReportBuilder, crashReportData: CrashReportData) { + for (field in reportFields) { + try { + if (shouldCollect(context, config, field, reportBuilder)) { + collect(field, context, config, reportBuilder, crashReportData) + } + } catch (t: Exception) { + crashReportData.put(field, null as String?) + throw CollectorException("Error while retrieving " + field.name + " data:" + t.message, t) + } + } + } + + /** + * Collect a ReportField + * + * @param reportField the reportField to collect + * @param context a context + * @param config current Configuration + * @param reportBuilder current ReportBuilder + * @param target put results here + * @throws Exception if collection failed + */ + @Throws(Exception::class) + abstract fun collect(reportField: ReportField, context: Context, config: CoreConfiguration, reportBuilder: ReportBuilder, target: CrashReportData) +} \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/collector/Collector.java b/acra-core/src/main/java/org/acra/collector/Collector.kt similarity index 61% rename from acra-core/src/main/java/org/acra/collector/Collector.java rename to acra-core/src/main/java/org/acra/collector/Collector.kt index 98f0472ce9..f3658a15dc 100644 --- a/acra-core/src/main/java/org/acra/collector/Collector.java +++ b/acra-core/src/main/java/org/acra/collector/Collector.kt @@ -13,21 +13,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +package org.acra.collector -package org.acra.collector; - -import android.content.Context; -import androidx.annotation.NonNull; -import org.acra.builder.ReportBuilder; -import org.acra.config.CoreConfiguration; -import org.acra.data.CrashReportData; -import org.acra.plugins.Plugin; +import android.content.Context +import org.acra.builder.ReportBuilder +import org.acra.config.CoreConfiguration +import org.acra.data.CrashReportData +import org.acra.plugins.Plugin /** * @author F43nd1r * @since 29.09.2017 */ -public interface Collector extends Plugin { +interface Collector : Plugin { /** * Execute collection * @@ -37,21 +35,16 @@ public interface Collector extends Plugin { * @param crashReportData put results here * @throws CollectorException if collection failed */ - void collect(@NonNull Context context, @NonNull CoreConfiguration config, @NonNull ReportBuilder reportBuilder, @NonNull CrashReportData crashReportData) throws CollectorException; + @Throws(CollectorException::class) + fun collect(context: Context, config: CoreConfiguration, reportBuilder: ReportBuilder, crashReportData: CrashReportData) /** * @return when this collector should be called compared to other collectors */ - @NonNull - default Order getOrder() { - return Order.NORMAL; - } + val order: Order + get() = Order.NORMAL - enum Order { - FIRST, - EARLY, - NORMAL, - LATE, - LAST + enum class Order { + FIRST, EARLY, NORMAL, LATE, LAST } -} +} \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/collector/CollectorException.java b/acra-core/src/main/java/org/acra/collector/CollectorException.kt similarity index 61% rename from acra-core/src/main/java/org/acra/collector/CollectorException.java rename to acra-core/src/main/java/org/acra/collector/CollectorException.kt index 6f05aec4d4..54266ad342 100644 --- a/acra-core/src/main/java/org/acra/collector/CollectorException.java +++ b/acra-core/src/main/java/org/acra/collector/CollectorException.kt @@ -13,8 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -package org.acra.collector; +package org.acra.collector /** * Thrown by collectors @@ -22,21 +21,9 @@ * @author F43nd1r * @since 29.09.2017 */ - -@SuppressWarnings("WeakerAccess") -public class CollectorException extends Exception { - public CollectorException() { - } - - public CollectorException(String message) { - super(message); - } - - public CollectorException(String message, Throwable cause) { - super(message, cause); - } - - public CollectorException(Throwable cause) { - super(cause); - } -} +class CollectorException : Exception { + constructor() + constructor(message: String?) : super(message) + constructor(message: String?, cause: Throwable?) : super(message, cause) + constructor(cause: Throwable?) : super(cause) +} \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/collector/ConfigurationCollector.java b/acra-core/src/main/java/org/acra/collector/ConfigurationCollector.java deleted file mode 100644 index 6fb61dc28f..0000000000 --- a/acra-core/src/main/java/org/acra/collector/ConfigurationCollector.java +++ /dev/null @@ -1,273 +0,0 @@ -/* - * Copyright 2010 Emmanuel Astier & 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.collector; - -import android.content.Context; -import android.content.res.Configuration; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import android.util.SparseArray; - -import com.google.auto.service.AutoService; - -import org.acra.ACRA; -import org.acra.ReportField; -import org.acra.builder.ReportBuilder; -import org.acra.config.CoreConfiguration; -import org.acra.data.CrashReportData; -import org.json.JSONException; -import org.json.JSONObject; - -import java.lang.reflect.Field; -import java.lang.reflect.Modifier; -import java.util.HashMap; -import java.util.Map; - -import static org.acra.ACRA.LOG_TAG; - -/** - * Inspects a {@link Configuration} object through reflection API in order to generate a human readable String with values replaced with their constants names. - * The {@link Configuration#toString()} method was not enough as values like 0, 1, 2 or 3 aren't readable. - * Using reflection API allows to retrieve hidden fields and can make us hope to be compatible with all Android API levels, even those which are not published yet. - * - * @author Kevin Gaudin & F43nd1r - */ -@AutoService(Collector.class) -public final class ConfigurationCollector extends BaseReportFieldCollector implements ApplicationStartupCollector { - - private static final String SUFFIX_MASK = "_MASK"; - private static final String FIELD_SCREENLAYOUT = "screenLayout"; - private static final String FIELD_UIMODE = "uiMode"; - private static final String FIELD_MNC = "mnc"; - private static final String FIELD_MCC = "mcc"; - private static final String PREFIX_UI_MODE = "UI_MODE_"; - private static final String PREFIX_TOUCHSCREEN = "TOUCHSCREEN_"; - private static final String PREFIX_SCREENLAYOUT = "SCREENLAYOUT_"; - private static final String PREFIX_ORIENTATION = "ORIENTATION_"; - private static final String PREFIX_NAVIGATIONHIDDEN = "NAVIGATIONHIDDEN_"; - private static final String PREFIX_NAVIGATION = "NAVIGATION_"; - private static final String PREFIX_KEYBOARDHIDDEN = "KEYBOARDHIDDEN_"; - private static final String PREFIX_KEYBOARD = "KEYBOARD_"; - private static final String PREFIX_HARDKEYBOARDHIDDEN = "HARDKEYBOARDHIDDEN_"; - - private JSONObject initialConfiguration; - - public ConfigurationCollector() { - super(ReportField.INITIAL_CONFIGURATION, ReportField.CRASH_CONFIGURATION); - } - - @Override - void collect(@NonNull ReportField reportField, @NonNull Context context, @NonNull CoreConfiguration config, - @NonNull ReportBuilder reportBuilder, @NonNull CrashReportData target) { - switch (reportField) { - case INITIAL_CONFIGURATION: - target.put(ReportField.INITIAL_CONFIGURATION, initialConfiguration); - break; - case CRASH_CONFIGURATION: - target.put(ReportField.CRASH_CONFIGURATION, collectConfiguration(context)); - break; - default: - throw new IllegalArgumentException(); - } - } - - /** - * {@inheritDoc} - */ - @Override - public void collectApplicationStartUp(@NonNull Context context, @NonNull CoreConfiguration config) { - if(config.getReportContent().contains(ReportField.INITIAL_CONFIGURATION)) { - initialConfiguration = collectConfiguration(context); - } - } - - /** - * Creates a {@link JSONObject} listing all values human readable - * from the provided Configuration instance. - * - * @param conf The Configuration to be described. - * @return A JSONObject with all fields of the given Configuration, - * with values replaced by constant names. - */ - @NonNull - private JSONObject configToJson(@NonNull Configuration conf) { - final JSONObject result = new JSONObject(); - final Map> valueArrays = getValueArrays(); - for (final Field f : conf.getClass().getFields()) { - try { - if (!Modifier.isStatic(f.getModifiers())) { - final String fieldName = f.getName(); - try { - if (f.getType().equals(int.class)) { - result.put(fieldName, getFieldValueName(valueArrays, conf, f)); - } else if (f.get(conf) != null) { - result.put(fieldName, f.get(conf)); - } - } catch (JSONException e) { - ACRA.log.w(LOG_TAG, "Could not collect configuration field " + fieldName, e); - } - } - } catch (@NonNull IllegalArgumentException e) { - ACRA.log.e(LOG_TAG, "Error while inspecting device configuration: ", e); - } catch (@NonNull IllegalAccessException e) { - ACRA.log.e(LOG_TAG, "Error while inspecting device configuration: ", e); - } - } - return result; - } - - @NonNull - private Map> getValueArrays() { - final Map> valueArrays = new HashMap<>(); - final SparseArray hardKeyboardHiddenValues = new SparseArray<>(); - final SparseArray keyboardValues = new SparseArray<>(); - final SparseArray keyboardHiddenValues = new SparseArray<>(); - final SparseArray navigationValues = new SparseArray<>(); - final SparseArray navigationHiddenValues = new SparseArray<>(); - final SparseArray orientationValues = new SparseArray<>(); - final SparseArray screenLayoutValues = new SparseArray<>(); - final SparseArray touchScreenValues = new SparseArray<>(); - final SparseArray uiModeValues = new SparseArray<>(); - - for (final Field f : Configuration.class.getFields()) { - if (Modifier.isStatic(f.getModifiers()) && Modifier.isFinal(f.getModifiers())) { - final String fieldName = f.getName(); - try { - if (fieldName.startsWith(PREFIX_HARDKEYBOARDHIDDEN)) { - hardKeyboardHiddenValues.put(f.getInt(null), fieldName); - } else if (fieldName.startsWith(PREFIX_KEYBOARD)) { - keyboardValues.put(f.getInt(null), fieldName); - } else if (fieldName.startsWith(PREFIX_KEYBOARDHIDDEN)) { - keyboardHiddenValues.put(f.getInt(null), fieldName); - } else if (fieldName.startsWith(PREFIX_NAVIGATION)) { - navigationValues.put(f.getInt(null), fieldName); - } else if (fieldName.startsWith(PREFIX_NAVIGATIONHIDDEN)) { - navigationHiddenValues.put(f.getInt(null), fieldName); - } else if (fieldName.startsWith(PREFIX_ORIENTATION)) { - orientationValues.put(f.getInt(null), fieldName); - } else if (fieldName.startsWith(PREFIX_SCREENLAYOUT)) { - screenLayoutValues.put(f.getInt(null), fieldName); - } else if (fieldName.startsWith(PREFIX_TOUCHSCREEN)) { - touchScreenValues.put(f.getInt(null), fieldName); - } else if (fieldName.startsWith(PREFIX_UI_MODE)) { - uiModeValues.put(f.getInt(null), fieldName); - } - } catch (@NonNull IllegalArgumentException e) { - ACRA.log.w(LOG_TAG, "Error while inspecting device configuration: ", e); - } catch (@NonNull IllegalAccessException e) { - ACRA.log.w(LOG_TAG, "Error while inspecting device configuration: ", e); - } - } - } - - valueArrays.put(PREFIX_HARDKEYBOARDHIDDEN, hardKeyboardHiddenValues); - valueArrays.put(PREFIX_KEYBOARD, keyboardValues); - valueArrays.put(PREFIX_KEYBOARDHIDDEN, keyboardHiddenValues); - valueArrays.put(PREFIX_NAVIGATION, navigationValues); - valueArrays.put(PREFIX_NAVIGATIONHIDDEN, navigationHiddenValues); - valueArrays.put(PREFIX_ORIENTATION, orientationValues); - valueArrays.put(PREFIX_SCREENLAYOUT, screenLayoutValues); - valueArrays.put(PREFIX_TOUCHSCREEN, touchScreenValues); - valueArrays.put(PREFIX_UI_MODE, uiModeValues); - return valueArrays; - } - - /** - * Retrieve the name of the constant defined in the {@link Configuration} - * class which defines the value of a field in a {@link Configuration} - * instance. - * - * @param conf The instance of {@link Configuration} where the value is - * stored. - * @param f The {@link Field} to be inspected in the {@link Configuration} - * instance. - * @return The value of the field f in instance conf translated to its - * constant name. - * @throws IllegalAccessException if the supplied field is inaccessible. - */ - private Object getFieldValueName(@NonNull Map> valueArrays, @NonNull Configuration conf, @NonNull Field f) throws IllegalAccessException { - final String fieldName = f.getName(); - switch (fieldName) { - case FIELD_MCC: - case FIELD_MNC: - return f.getInt(conf); - case FIELD_UIMODE: - return activeFlags(valueArrays.get(PREFIX_UI_MODE), f.getInt(conf)); - case FIELD_SCREENLAYOUT: - return activeFlags(valueArrays.get(PREFIX_SCREENLAYOUT), f.getInt(conf)); - default: - final SparseArray values = valueArrays.get(fieldName.toUpperCase() + '_'); - if (values == null) { - // Unknown field, return the raw int as String - return f.getInt(conf); - } - - final String value = values.get(f.getInt(conf)); - if (value == null) { - // Unknown value, return the raw int as String - return f.getInt(conf); - } - return value; - } - } - - /** - * Some fields contain multiple value types which can be isolated by - * applying a bitmask. That method returns the concatenation of active - * values. - * - * @param valueNames The array containing the different values and names for this - * field. Must contain mask values too. - * @param bitfield The bitfield to inspect. - * @return The names of the different values contained in the bitfield, - * separated by '+'. - */ - @NonNull - private String activeFlags(@NonNull SparseArray valueNames, int bitfield) { - final StringBuilder result = new StringBuilder(); - - // Look for masks, apply it an retrieve the masked value - for (int i = 0; i < valueNames.size(); i++) { - final int maskValue = valueNames.keyAt(i); - if (valueNames.get(maskValue).endsWith(SUFFIX_MASK)) { - final int value = bitfield & maskValue; - if (value > 0) { - if (result.length() > 0) { - result.append('+'); - } - result.append(valueNames.get(value)); - } - } - } - return result.toString(); - } - - /** - * Returns the current Configuration for this application. - * - * @param context Context for the application being reported. - * @return A String representation of the current configuration for the application. - */ - @Nullable - private JSONObject collectConfiguration(@NonNull Context context) { - try { - return configToJson(context.getResources().getConfiguration()); - } catch (RuntimeException e) { - ACRA.log.w(LOG_TAG, "Couldn't retrieve CrashConfiguration for : " + context.getPackageName(), e); - return null; - } - } -} diff --git a/acra-core/src/main/java/org/acra/collector/ConfigurationCollector.kt b/acra-core/src/main/java/org/acra/collector/ConfigurationCollector.kt new file mode 100644 index 0000000000..65c1546ada --- /dev/null +++ b/acra-core/src/main/java/org/acra/collector/ConfigurationCollector.kt @@ -0,0 +1,205 @@ +/* + * Copyright 2010 Emmanuel Astier & 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.collector + +import android.content.Context +import android.content.res.Configuration +import android.util.SparseArray +import com.google.auto.service.AutoService +import org.acra.ReportField +import org.acra.builder.ReportBuilder +import org.acra.config.CoreConfiguration +import org.acra.data.CrashReportData +import org.acra.log.error +import org.acra.log.warn +import org.acra.util.mapNotNullToSparseArray +import org.json.JSONException +import org.json.JSONObject +import java.lang.reflect.Field +import java.lang.reflect.Modifier +import java.util.* + +/** + * Inspects a [Configuration] object through reflection API in order to generate a human readable String with values replaced with their constants names. + * The [Configuration.toString] method was not enough as values like 0, 1, 2 or 3 aren't readable. + * Using reflection API allows to retrieve hidden fields and can make us hope to be compatible with all Android API levels, even those which are not published yet. + * + * @author Kevin Gaudin & F43nd1r + */ +@AutoService(Collector::class) +class ConfigurationCollector : BaseReportFieldCollector(ReportField.INITIAL_CONFIGURATION, ReportField.CRASH_CONFIGURATION), ApplicationStartupCollector { + + private var initialConfiguration: JSONObject? = null + + override fun collect(reportField: ReportField, context: Context, config: CoreConfiguration, + reportBuilder: ReportBuilder, target: CrashReportData) { + when (reportField) { + ReportField.INITIAL_CONFIGURATION -> target.put(ReportField.INITIAL_CONFIGURATION, initialConfiguration) + ReportField.CRASH_CONFIGURATION -> target.put(ReportField.CRASH_CONFIGURATION, collectConfiguration(context)) + else -> throw IllegalArgumentException() + } + } + + override fun collectApplicationStartUp(context: Context, config: CoreConfiguration) { + if (config.reportContent.contains(ReportField.INITIAL_CONFIGURATION)) { + initialConfiguration = collectConfiguration(context) + } + } + + /** + * Creates a [JSONObject] listing all values human readable + * from the provided Configuration instance. + * + * @param conf The Configuration to be described. + * @return A JSONObject with all fields of the given Configuration, + * with values replaced by constant names. + */ + private fun configToJson(conf: Configuration): JSONObject { + val result = JSONObject() + val valueArrays = getValueArrays() + for (f in conf.javaClass.fields) { + try { + if (!Modifier.isStatic(f.modifiers)) { + val fieldName = f.name + try { + if (f.type == Int::class.javaPrimitiveType) { + result.put(fieldName, getFieldValueName(valueArrays, conf, f)) + } else if (f[conf] != null) { + result.put(fieldName, f[conf]) + } + } catch (e: JSONException) { + warn(e) { "Could not collect configuration field $fieldName" } + } + } + } catch (e: IllegalArgumentException) { + error(e) { "Error while inspecting device configuration: " } + } catch (e: IllegalAccessException) { + error(e) { "Error while inspecting device configuration: " } + } + } + return result + } + + private fun getValueArrays(): Map> { + return Configuration::class.java.fields.filter { Modifier.isStatic(it.modifiers) && Modifier.isFinal(it.modifiers) } + .groupBy { field -> Prefix.values().firstOrNull { field.name.startsWith(it.text) } } + .filterKeys { it != null }.map { (prefix, fields) -> + prefix!! to fields.mapNotNullToSparseArray { + try { + return@mapNotNullToSparseArray it.getInt(null) to it.name + } catch (e: IllegalArgumentException) { + warn(e) { "Error while inspecting device configuration: " } + } catch (e: IllegalAccessException) { + warn(e) { "Error while inspecting device configuration: " } + } + null + } + }.toMap(EnumMap(Prefix::class.java)).withDefault { SparseArray() } + } + + /** + * Retrieve the name of the constant defined in the [Configuration] + * class which defines the value of a field in a [Configuration] + * instance. + * + * @param conf The instance of [Configuration] where the value is + * stored. + * @param f The [Field] to be inspected in the [Configuration] + * instance. + * @return The value of the field f in instance conf translated to its + * constant name. + * @throws IllegalAccessException if the supplied field is inaccessible. + */ + @Throws(IllegalAccessException::class) + private fun getFieldValueName(valueArrays: Map>, conf: Configuration, f: Field): Any { + return when (val fieldName = f.name) { + FIELD_MCC, FIELD_MNC -> f.getInt(conf) + FIELD_UIMODE -> activeFlags(valueArrays.getValue(Prefix.UI_MODE), f.getInt(conf)) + FIELD_SCREENLAYOUT -> activeFlags(valueArrays.getValue(Prefix.SCREENLAYOUT), f.getInt(conf)) + else -> { + val values = Prefix.values().firstOrNull { it.text == fieldName.toUpperCase(Locale.ROOT) + '_' }?.let { valueArrays.getValue(it) } ?: return f.getInt( + conf) // Unknown field, return the raw int as String + val value = values[f.getInt(conf)] ?: return f.getInt(conf) // Unknown value, return the raw int as String + value + } + } + } + + /** + * Some fields contain multiple value types which can be isolated by + * applying a bitmask. That method returns the concatenation of active + * values. + * + * @param valueNames The array containing the different values and names for this + * field. Must contain mask values too. + * @param bitfield The bitfield to inspect. + * @return The names of the different values contained in the bitfield, + * separated by '+'. + */ + private fun activeFlags(valueNames: SparseArray, bitfield: Int): String { + val result = StringBuilder() + + // Look for masks, apply it an retrieve the masked value + for (i in 0 until valueNames.size()) { + val maskValue = valueNames.keyAt(i) + if (valueNames[maskValue].endsWith(SUFFIX_MASK)) { + val value = bitfield and maskValue + if (value > 0) { + if (result.isNotEmpty()) { + result.append('+') + } + result.append(valueNames[value]) + } + } + } + return result.toString() + } + + /** + * Returns the current Configuration for this application. + * + * @param context Context for the application being reported. + * @return A String representation of the current configuration for the application. + */ + private fun collectConfiguration(context: Context): JSONObject? { + return try { + configToJson(context.resources.configuration) + } catch (e: RuntimeException) { + warn(e) { "Couldn't retrieve CrashConfiguration for : " + context.packageName } + null + } + } + + enum class Prefix(val text: String) { + UI_MODE("UI_MODE_"), + TOUCHSCREEN("TOUCHSCREEN_"), + SCREENLAYOUT("SCREENLAYOUT_"), + ORIENTATION("ORIENTATION_"), + NAVIGATIONHIDDEN("NAVIGATIONHIDDEN_"), + NAVIGATION("NAVIGATION_"), + KEYBOARDHIDDEN("KEYBOARDHIDDEN_"), + KEYBOARD("KEYBOARD_"), + HARDKEYBOARDHIDDEN("HARDKEYBOARDHIDDEN_"), + } + + companion object { + private const val SUFFIX_MASK = "_MASK" + private const val FIELD_SCREENLAYOUT = "screenLayout" + private const val FIELD_UIMODE = "uiMode" + private const val FIELD_MNC = "mnc" + private const val FIELD_MCC = "mcc" + } +} \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/collector/CustomDataCollector.java b/acra-core/src/main/java/org/acra/collector/CustomDataCollector.java deleted file mode 100644 index dc7c8c052a..0000000000 --- a/acra-core/src/main/java/org/acra/collector/CustomDataCollector.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2016 - * - * 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.collector; - -import android.content.Context; -import androidx.annotation.NonNull; - -import com.google.auto.service.AutoService; - -import org.acra.ReportField; -import org.acra.builder.ReportBuilder; -import org.acra.config.CoreConfiguration; -import org.acra.data.CrashReportData; -import org.json.JSONObject; - -/** - * Collects custom data supplied by the user - * - * @author F43nd1r - * @since 4.9.1 - */ -@AutoService(Collector.class) -public final class CustomDataCollector extends BaseReportFieldCollector { - - public CustomDataCollector(){ - super(ReportField.CUSTOM_DATA); - } - - @Override - void collect(@NonNull ReportField reportField, @NonNull Context context, @NonNull CoreConfiguration config, @NonNull ReportBuilder reportBuilder, @NonNull CrashReportData target) { - target.put(ReportField.CUSTOM_DATA, new JSONObject(reportBuilder.getCustomData())); - } -} diff --git a/acra-core/src/main/java/org/acra/collector/CustomDataCollector.kt b/acra-core/src/main/java/org/acra/collector/CustomDataCollector.kt new file mode 100644 index 0000000000..d406c8a8a9 --- /dev/null +++ b/acra-core/src/main/java/org/acra/collector/CustomDataCollector.kt @@ -0,0 +1,37 @@ +/* + * Copyright 2016 + * + * 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.collector + +import android.content.Context +import com.google.auto.service.AutoService +import org.acra.ReportField +import org.acra.builder.ReportBuilder +import org.acra.config.CoreConfiguration +import org.acra.data.CrashReportData +import org.json.JSONObject + +/** + * Collects custom data supplied by the user + * + * @author F43nd1r + * @since 4.9.1 + */ +@AutoService(Collector::class) +class CustomDataCollector : BaseReportFieldCollector(ReportField.CUSTOM_DATA) { + override fun collect(reportField: ReportField, context: Context, config: CoreConfiguration, reportBuilder: ReportBuilder, target: CrashReportData) { + target.put(ReportField.CUSTOM_DATA, JSONObject(reportBuilder.getCustomData())) + } +} \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/collector/DeviceFeaturesCollector.java b/acra-core/src/main/java/org/acra/collector/DeviceFeaturesCollector.java deleted file mode 100644 index 5d99c2beb8..0000000000 --- a/acra-core/src/main/java/org/acra/collector/DeviceFeaturesCollector.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2010 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.collector; - -import android.content.Context; -import android.content.pm.FeatureInfo; -import android.content.pm.PackageManager; -import androidx.annotation.NonNull; - -import com.google.auto.service.AutoService; - -import org.acra.ReportField; -import org.acra.builder.ReportBuilder; -import org.acra.config.CoreConfiguration; -import org.acra.data.CrashReportData; -import org.json.JSONException; -import org.json.JSONObject; - -/** - * Collects features declared as available on the device. - * - * @author Kevin Gaudin & F43nd1r - */ -@AutoService(Collector.class) -public final class DeviceFeaturesCollector extends BaseReportFieldCollector { - - public DeviceFeaturesCollector() { - super(ReportField.DEVICE_FEATURES); - } - - @Override - void collect(@NonNull ReportField reportField, @NonNull Context context, @NonNull CoreConfiguration config, @NonNull ReportBuilder reportBuilder, @NonNull CrashReportData target) throws JSONException { - final JSONObject result = new JSONObject(); - final PackageManager pm = context.getPackageManager(); - final FeatureInfo[] features = pm.getSystemAvailableFeatures(); - for (final FeatureInfo feature : features) { - final String featureName = feature.name; - if (featureName != null) { - result.put(featureName, true); - } else { - result.put("glEsVersion", feature.getGlEsVersion()); - } - } - target.put(ReportField.DEVICE_FEATURES, result); - } -} diff --git a/acra-core/src/main/java/org/acra/collector/DeviceFeaturesCollector.kt b/acra-core/src/main/java/org/acra/collector/DeviceFeaturesCollector.kt new file mode 100644 index 0000000000..fdce82348b --- /dev/null +++ b/acra-core/src/main/java/org/acra/collector/DeviceFeaturesCollector.kt @@ -0,0 +1,49 @@ +/* + * Copyright 2010 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.collector + +import android.content.Context +import com.google.auto.service.AutoService +import org.acra.ReportField +import org.acra.builder.ReportBuilder +import org.acra.config.CoreConfiguration +import org.acra.data.CrashReportData +import org.json.JSONException +import org.json.JSONObject + +/** + * Collects features declared as available on the device. + * + * @author Kevin Gaudin & F43nd1r + */ +@AutoService(Collector::class) +class DeviceFeaturesCollector : BaseReportFieldCollector(ReportField.DEVICE_FEATURES) { + @Throws(JSONException::class) + override fun collect(reportField: ReportField, context: Context, config: CoreConfiguration, reportBuilder: ReportBuilder, target: CrashReportData) { + val result = JSONObject() + val pm = context.packageManager + val features = pm.systemAvailableFeatures + for (feature in features) { + val featureName = feature.name + if (featureName != null) { + result.put(featureName, true) + } else { + result.put("glEsVersion", feature.glEsVersion) + } + } + target.put(ReportField.DEVICE_FEATURES, result) + } +} \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/collector/DeviceIdCollector.java b/acra-core/src/main/java/org/acra/collector/DeviceIdCollector.java deleted file mode 100644 index de42d08d0a..0000000000 --- a/acra-core/src/main/java/org/acra/collector/DeviceIdCollector.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2016 - * - * 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.collector; - -import android.Manifest; -import android.annotation.SuppressLint; -import android.content.Context; -import androidx.annotation.NonNull; -import androidx.annotation.RequiresPermission; - -import com.google.auto.service.AutoService; - -import org.acra.ACRA; -import org.acra.ReportField; -import org.acra.builder.ReportBuilder; -import org.acra.config.CoreConfiguration; -import org.acra.data.CrashReportData; -import org.acra.prefs.SharedPreferencesFactory; -import org.acra.util.PackageManagerWrapper; -import org.acra.util.SystemServices; - -/** - * Collects the device ID - * - * @author F43nd1r - * @since 4.9.1 - */ -@AutoService(Collector.class) -public final class DeviceIdCollector extends BaseReportFieldCollector { - - public DeviceIdCollector() { - super(ReportField.DEVICE_ID); - } - - @Override - boolean shouldCollect(@NonNull Context context, @NonNull CoreConfiguration config, @NonNull ReportField collect, @NonNull ReportBuilder reportBuilder) { - return super.shouldCollect(context, config, collect, reportBuilder) && new SharedPreferencesFactory(context, config).create().getBoolean(ACRA.PREF_ENABLE_DEVICE_ID, true) - && new PackageManagerWrapper(context).hasPermission(Manifest.permission.READ_PHONE_STATE); - } - - @SuppressLint("HardwareIds") - @RequiresPermission(Manifest.permission.READ_PHONE_STATE) - @Override - void collect(@NonNull ReportField reportField, @NonNull Context context, @NonNull CoreConfiguration config, @NonNull ReportBuilder reportBuilder, @NonNull CrashReportData target) throws Exception { - target.put(ReportField.DEVICE_ID, SystemServices.getTelephonyManager(context).getDeviceId()); - } -} diff --git a/acra-core/src/main/java/org/acra/collector/DeviceIdCollector.kt b/acra-core/src/main/java/org/acra/collector/DeviceIdCollector.kt new file mode 100644 index 0000000000..09edaac028 --- /dev/null +++ b/acra-core/src/main/java/org/acra/collector/DeviceIdCollector.kt @@ -0,0 +1,52 @@ +/* + * Copyright 2016 + * + * 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.collector + +import android.Manifest +import android.annotation.SuppressLint +import android.content.Context +import androidx.annotation.RequiresPermission +import com.google.auto.service.AutoService +import org.acra.ACRA +import org.acra.ReportField +import org.acra.builder.ReportBuilder +import org.acra.config.CoreConfiguration +import org.acra.data.CrashReportData +import org.acra.prefs.SharedPreferencesFactory +import org.acra.util.PackageManagerWrapper +import org.acra.util.SystemServices.getTelephonyManager + +/** + * Collects the device ID + * + * @author F43nd1r + * @since 4.9.1 + */ +@AutoService(Collector::class) +class DeviceIdCollector : BaseReportFieldCollector(ReportField.DEVICE_ID) { + override fun shouldCollect(context: Context, config: CoreConfiguration, collect: ReportField, reportBuilder: ReportBuilder): Boolean { + return (super.shouldCollect(context, config, collect, reportBuilder) && SharedPreferencesFactory(context, config).create().getBoolean(ACRA.PREF_ENABLE_DEVICE_ID, true) + && PackageManagerWrapper(context).hasPermission(Manifest.permission.READ_PHONE_STATE)) + } + + @SuppressLint("MissingPermission", "HardwareIds") + @RequiresPermission(Manifest.permission.READ_PHONE_STATE) + @Throws(Exception::class) + override fun collect(reportField: ReportField, context: Context, config: CoreConfiguration, reportBuilder: ReportBuilder, target: CrashReportData) { + @Suppress("DEPRECATION") + target.put(ReportField.DEVICE_ID, getTelephonyManager(context).deviceId) + } +} \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/collector/DisplayManagerCollector.java b/acra-core/src/main/java/org/acra/collector/DisplayManagerCollector.java deleted file mode 100644 index 8ffa04f167..0000000000 --- a/acra-core/src/main/java/org/acra/collector/DisplayManagerCollector.java +++ /dev/null @@ -1,236 +0,0 @@ -/* - * Copyright 2016 - * - * 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.collector; - -import android.content.Context; -import android.graphics.Point; -import android.graphics.Rect; -import android.hardware.display.DisplayManager; -import android.os.Build; -import androidx.annotation.NonNull; -import android.util.DisplayMetrics; -import android.util.SparseArray; -import android.view.Display; -import android.view.Surface; -import android.view.WindowManager; -import com.google.auto.service.AutoService; -import org.acra.ACRA; -import org.acra.ReportField; -import org.acra.builder.ReportBuilder; -import org.acra.config.CoreConfiguration; -import org.acra.data.CrashReportData; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import java.lang.reflect.Field; -import java.util.Arrays; - -/** - * Collects information about the connected display(s) - * - * @author F43nd1r & Various - */ -@AutoService(Collector.class) -public final class DisplayManagerCollector extends BaseReportFieldCollector { - - public DisplayManagerCollector() { - super(ReportField.DISPLAY); - } - - @Override - void collect(@NonNull ReportField reportField, @NonNull Context context, @NonNull CoreConfiguration config, @NonNull ReportBuilder reportBuilder, @NonNull CrashReportData target) { - final JSONObject result = new JSONObject(); - for (Display display : getDisplays(context)) { - try { - result.put(String.valueOf(display.getDisplayId()), collectDisplayData(display)); - } catch (JSONException e) { - ACRA.log.w(ACRA.LOG_TAG, "Failed to collect data for display " + display.getDisplayId(), e); - } - } - target.put(ReportField.DISPLAY, result); - } - - @NonNull - private Display[] getDisplays(@NonNull Context context) { - if (Build.VERSION.SDK_INT >= 17) { - return ((DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE)).getDisplays(); - } else { - Display display = ((WindowManager)context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay(); - return new Display[]{display}; - } - } - - @NonNull - private JSONObject collectDisplayData(@NonNull Display display) throws JSONException { - final DisplayMetrics metrics = new DisplayMetrics(); - display.getMetrics(metrics); - - final JSONObject result = new JSONObject(); - collectCurrentSizeRange(display, result); - collectFlags(display, result); - collectMetrics(display, result); - collectRealMetrics(display, result); - collectName(display, result); - collectRealSize(display, result); - collectRectSize(display, result); - collectSize(display, result); - collectRotation(display, result); - collectIsValid(display, result); - result.put("orientation", display.getRotation()) - .put("refreshRate", display.getRefreshRate()); - //noinspection deprecation - result.put("height", display.getHeight()) - .put("width", display.getWidth()) - .put("pixelFormat", display.getPixelFormat()); - return result; - } - - private void collectIsValid(@NonNull Display display, @NonNull JSONObject container) throws JSONException { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { - container.put("isValid", display.isValid()); - } - } - - private void collectRotation(@NonNull Display display, JSONObject container) throws JSONException { - container.put("rotation", rotationToString(display.getRotation())); - } - - @NonNull - private String rotationToString(int rotation) { - switch (rotation) { - case Surface.ROTATION_0: - return "ROTATION_0"; - case Surface.ROTATION_90: - return "ROTATION_90"; - case Surface.ROTATION_180: - return "ROTATION_180"; - case Surface.ROTATION_270: - return "ROTATION_270"; - default: - return String.valueOf(rotation); - } - } - - private void collectRectSize(@NonNull Display display, @NonNull JSONObject container) throws JSONException { - final Rect size = new Rect(); - display.getRectSize(size); - container.put("rectSize", new JSONArray(Arrays.asList(size.top, size.left, size.width(), size.height()))); - } - - private void collectSize(@NonNull Display display, @NonNull JSONObject container) throws JSONException { - final Point size = new Point(); - display.getSize(size); - container.put("size", new JSONArray(Arrays.asList(size.x, size.y))); - } - - private void collectRealSize(@NonNull Display display, @NonNull JSONObject container) throws JSONException { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { - final Point size = new Point(); - display.getRealSize(size); - container.put("realSize", new JSONArray(Arrays.asList(size.x, size.y))); - } - } - - private void collectCurrentSizeRange(@NonNull Display display, @NonNull JSONObject container) throws JSONException { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { - final Point smallest = new Point(); - final Point largest = new Point(); - display.getCurrentSizeRange(smallest, largest); - final JSONObject result = new JSONObject(); - result.put("smallest", new JSONArray(Arrays.asList(smallest.x, smallest.y))); - result.put("largest", new JSONArray(Arrays.asList(largest.x, largest.y))); - container.put("currentSizeRange", result); - } - } - - private void collectFlags(@NonNull Display display, @NonNull JSONObject container) throws JSONException { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { - final SparseArray flagNames = new SparseArray<>(); - final int flags = display.getFlags(); - for (Field field : display.getClass().getFields()) { - if (field.getName().startsWith("FLAG_")) { - try { - flagNames.put(field.getInt(null), field.getName()); - } catch (IllegalAccessException ignored) { - } - } - } - container.put("flags", activeFlags(flagNames, flags)); - } - } - - private void collectName(@NonNull Display display, @NonNull JSONObject container) throws JSONException { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { - container.put("name", display.getName()); - } - } - - private void collectMetrics(@NonNull Display display, @NonNull JSONObject container) throws JSONException { - final DisplayMetrics metrics = new DisplayMetrics(); - display.getMetrics(metrics); - final JSONObject result = new JSONObject(); - collectMetrics(metrics, result); - container.put("metrics", result); - } - - private void collectRealMetrics(@NonNull Display display, @NonNull JSONObject container) throws JSONException { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { - final DisplayMetrics metrics = new DisplayMetrics(); - display.getRealMetrics(metrics); - final JSONObject result = new JSONObject(); - collectMetrics(metrics, result); - container.put("realMetrics", result); - } - } - - private void collectMetrics(@NonNull DisplayMetrics metrics, @NonNull JSONObject container) throws JSONException { - container.put("density", metrics.density) - .put("densityDpi", metrics.densityDpi) - .put("scaledDensity", "x" + metrics.scaledDensity) - .put("widthPixels", metrics.widthPixels) - .put("heightPixels", metrics.heightPixels) - .put("xdpi", metrics.xdpi) - .put("ydpi", metrics.ydpi); - } - - /** - * Some fields contain multiple value types which can be isolated by - * applying a bitmask. That method returns the concatenation of active - * values. - * - * @param bitfield The bitfield to inspect. - * @return The names of the different values contained in the bitfield, - * separated by '+'. - */ - @NonNull - private String activeFlags(SparseArray flagNames, int bitfield) { - final StringBuilder result = new StringBuilder(); - - // Look for masks, apply it an retrieve the masked value - for (int i = 0; i < flagNames.size(); i++) { - final int maskValue = flagNames.keyAt(i); - final int value = bitfield & maskValue; - if (value > 0) { - if (result.length() > 0) { - result.append('+'); - } - result.append(flagNames.get(value)); - } - } - return result.toString(); - } -} diff --git a/acra-core/src/main/java/org/acra/collector/DisplayManagerCollector.kt b/acra-core/src/main/java/org/acra/collector/DisplayManagerCollector.kt new file mode 100644 index 0000000000..4b8a0e89f1 --- /dev/null +++ b/acra-core/src/main/java/org/acra/collector/DisplayManagerCollector.kt @@ -0,0 +1,224 @@ +/* + * Copyright 2016 + * + * 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.collector + +import android.content.Context +import android.graphics.Point +import android.graphics.Rect +import android.hardware.display.DisplayManager +import android.os.Build +import android.util.DisplayMetrics +import android.util.SparseArray +import android.view.Display +import android.view.Surface +import android.view.WindowManager +import com.google.auto.service.AutoService +import org.acra.ACRA +import org.acra.ReportField +import org.acra.builder.ReportBuilder +import org.acra.config.CoreConfiguration +import org.acra.data.CrashReportData +import org.json.JSONArray +import org.json.JSONException +import org.json.JSONObject +import java.util.* + +/** + * Collects information about the connected display(s) + * + * @author F43nd1r & Various + */ +@Suppress("DEPRECATION") +@AutoService(Collector::class) +class DisplayManagerCollector : BaseReportFieldCollector(ReportField.DISPLAY) { + override fun collect(reportField: ReportField, context: Context, config: CoreConfiguration, reportBuilder: ReportBuilder, target: CrashReportData) { + val result = JSONObject() + for (display in getDisplays(context)) { + try { + result.put(display.displayId.toString(), collectDisplayData(display)) + } catch (e: JSONException) { + ACRA.log.w(ACRA.LOG_TAG, "Failed to collect data for display " + display.displayId, e) + } + } + target.put(ReportField.DISPLAY, result) + } + + private fun getDisplays(context: Context): Array { + return if (Build.VERSION.SDK_INT >= 17) { + (context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager).displays + } else { + arrayOf((context.getSystemService(Context.WINDOW_SERVICE) as WindowManager).defaultDisplay) + } + } + + @Throws(JSONException::class) + private fun collectDisplayData(display: Display): JSONObject { + display.getMetrics(DisplayMetrics()) + val result = JSONObject() + collectCurrentSizeRange(display, result) + collectFlags(display, result) + collectMetrics(display, result) + collectRealMetrics(display, result) + collectName(display, result) + collectRealSize(display, result) + collectRectSize(display, result) + collectSize(display, result) + collectRotation(display, result) + collectIsValid(display, result) + result.put("orientation", display.rotation) + .put("refreshRate", display.refreshRate.toDouble()) + .put("height", display.height) + .put("width", display.width) + .put("pixelFormat", display.pixelFormat) + return result + } + + @Throws(JSONException::class) + private fun collectIsValid(display: Display, container: JSONObject) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + container.put("isValid", display.isValid) + } + } + + @Throws(JSONException::class) + private fun collectRotation(display: Display, container: JSONObject) { + container.put("rotation", rotationToString(display.rotation)) + } + + private fun rotationToString(rotation: Int): String { + return when (rotation) { + Surface.ROTATION_0 -> "ROTATION_0" + Surface.ROTATION_90 -> "ROTATION_90" + Surface.ROTATION_180 -> "ROTATION_180" + Surface.ROTATION_270 -> "ROTATION_270" + else -> rotation.toString() + } + } + + @Throws(JSONException::class) + private fun collectRectSize(display: Display, container: JSONObject) { + val size = Rect().also { display.getRectSize(it) } + container.put("rectSize", JSONArray(listOf(size.top, size.left, size.width(), size.height()))) + } + + @Throws(JSONException::class) + private fun collectSize(display: Display, container: JSONObject) { + val size = Point().also { display.getSize(it) } + container.put("size", JSONArray(listOf(size.x, size.y))) + } + + @Throws(JSONException::class) + private fun collectRealSize(display: Display, container: JSONObject) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + val size = Point().also { display.getRealSize(it) } + container.put("realSize", JSONArray(listOf(size.x, size.y))) + } + } + + @Throws(JSONException::class) + private fun collectCurrentSizeRange(display: Display, container: JSONObject) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + val smallest = Point() + val largest = Point() + display.getCurrentSizeRange(smallest, largest) + val result = JSONObject() + result.put("smallest", JSONArray(listOf(smallest.x, smallest.y))) + result.put("largest", JSONArray(listOf(largest.x, largest.y))) + container.put("currentSizeRange", result) + } + } + + @Throws(JSONException::class) + private fun collectFlags(display: Display, container: JSONObject) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + val flagNames = SparseArray() + val flags = display.flags + for (field in display.javaClass.fields) { + if (field.name.startsWith("FLAG_")) { + try { + flagNames.put(field.getInt(null), field.name) + } catch (ignored: IllegalAccessException) { + } + } + } + container.put("flags", activeFlags(flagNames, flags)) + } + } + + @Throws(JSONException::class) + private fun collectName(display: Display, container: JSONObject) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + container.put("name", display.name) + } + } + + @Throws(JSONException::class) + private fun collectMetrics(display: Display, container: JSONObject) { + val metrics = DisplayMetrics() + display.getMetrics(metrics) + val result = JSONObject() + collectMetrics(metrics, result) + container.put("metrics", result) + } + + @Throws(JSONException::class) + private fun collectRealMetrics(display: Display, container: JSONObject) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + val metrics = DisplayMetrics() + display.getRealMetrics(metrics) + val result = JSONObject() + collectMetrics(metrics, result) + container.put("realMetrics", result) + } + } + + @Throws(JSONException::class) + private fun collectMetrics(metrics: DisplayMetrics, container: JSONObject) { + container.put("density", metrics.density.toDouble()) + .put("densityDpi", metrics.densityDpi) + .put("scaledDensity", "x" + metrics.scaledDensity) + .put("widthPixels", metrics.widthPixels) + .put("heightPixels", metrics.heightPixels) + .put("xdpi", metrics.xdpi.toDouble()) + .put("ydpi", metrics.ydpi.toDouble()) + } + + /** + * Some fields contain multiple value types which can be isolated by + * applying a bitmask. That method returns the concatenation of active + * values. + * + * @param bitfield The bitfield to inspect. + * @return The names of the different values contained in the bitfield, + * separated by '+'. + */ + private fun activeFlags(flagNames: SparseArray, bitfield: Int): String { + val result = StringBuilder() + + // Look for masks, apply it an retrieve the masked value + for (i in 0 until flagNames.size()) { + val maskValue = flagNames.keyAt(i) + val value = bitfield and maskValue + if (value > 0) { + if (result.isNotEmpty()) { + result.append('+') + } + result.append(flagNames[value]) + } + } + return result.toString() + } +} \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/collector/DropBoxCollector.java b/acra-core/src/main/java/org/acra/collector/DropBoxCollector.java deleted file mode 100644 index 6d6c7e30f9..0000000000 --- a/acra-core/src/main/java/org/acra/collector/DropBoxCollector.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright 2010 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.collector; - -import android.Manifest; -import android.content.Context; -import android.os.Build; -import android.os.DropBoxManager; -import androidx.annotation.NonNull; - -import com.google.auto.service.AutoService; - -import org.acra.ACRA; -import org.acra.ReportField; -import org.acra.builder.ReportBuilder; -import org.acra.config.CoreConfiguration; -import org.acra.data.CrashReportData; -import org.acra.prefs.SharedPreferencesFactory; -import org.acra.util.PackageManagerWrapper; -import org.acra.util.SystemServices; -import org.json.JSONException; -import org.json.JSONObject; - -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Calendar; -import java.util.List; -import java.util.Locale; - -import static org.acra.ACRA.LOG_TAG; - -/** - * Collects data from the {@link DropBoxManager}. A set of DropBox tags have been identified in the Android source code. - * We collect data associated to these tags with hope that some of them could help debugging applications. - * Application specific tags can be provided by the app dev to track his own usage of the DropBoxManager. - * - * @author Kevin Gaudin & F43nd1r - */ -@AutoService(Collector.class) -public final class DropBoxCollector extends BaseReportFieldCollector { - - private static final String[] SYSTEM_TAGS = {"system_app_anr", "system_app_wtf", "system_app_crash", - "system_server_anr", "system_server_wtf", "system_server_crash", "BATTERY_DISCHARGE_INFO", - "SYSTEM_RECOVERY_LOG", "SYSTEM_BOOT", "SYSTEM_LAST_KMSG", "APANIC_CONSOLE", "APANIC_THREADS", - "SYSTEM_RESTART", "SYSTEM_TOMBSTONE", "data_app_strictmode"}; - - private final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd'T'HHmmss", Locale.getDefault()); //iCal format (used for backwards compatibility) - - public DropBoxCollector() { - super(ReportField.DROPBOX); - } - - @NonNull - @Override - public Order getOrder() { - return Order.FIRST; - } - - @Override - void collect(@NonNull ReportField reportField, @NonNull Context context, @NonNull CoreConfiguration config, @NonNull ReportBuilder reportBuilder, @NonNull CrashReportData target) throws Exception{ - final DropBoxManager dropbox = SystemServices.getDropBoxManager(context); - - final Calendar calendar = Calendar.getInstance(); - calendar.roll(Calendar.MINUTE, -config.getDropboxCollectionMinutes()); - final long time = calendar.getTimeInMillis(); - dateFormat.format(calendar.getTime()); - - final List tags = new ArrayList<>(); - if (config.getIncludeDropBoxSystemTags()) { - tags.addAll(Arrays.asList(SYSTEM_TAGS)); - } - final List additionalTags = Arrays.asList(config.getAdditionalDropBoxTags()); - if (!additionalTags.isEmpty()) { - tags.addAll(additionalTags); - } - - if (!tags.isEmpty()) { - final JSONObject dropboxContent = new JSONObject(); - for (String tag : tags) { - final StringBuilder builder = new StringBuilder(); - DropBoxManager.Entry entry = dropbox.getNextEntry(tag, time); - if (entry == null) { - builder.append("Nothing.").append('\n'); - continue; - } - while (entry != null) { - final long msec = entry.getTimeMillis(); - calendar.setTimeInMillis(msec); - builder.append('@').append(dateFormat.format(calendar.getTime())).append('\n'); - final String text = entry.getText(500); - if (text != null) { - builder.append("Text: ").append(text).append('\n'); - } else { - builder.append("Not Text!").append('\n'); - } - entry.close(); - entry = dropbox.getNextEntry(tag, msec); - } - try { - dropboxContent.put(tag, builder.toString()); - } catch (JSONException e) { - ACRA.log.w(LOG_TAG, "Failed to collect data for tag " + tag, e); - } - } - target.put(ReportField.DROPBOX, dropboxContent); - } - } - - @Override - boolean shouldCollect(@NonNull Context context, @NonNull CoreConfiguration config, @NonNull ReportField collect, @NonNull ReportBuilder reportBuilder) { - return super.shouldCollect(context, config, collect, reportBuilder) && - (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN || new PackageManagerWrapper(context).hasPermission(Manifest.permission.READ_LOGS)) - && new SharedPreferencesFactory(context, config).create().getBoolean(ACRA.PREF_ENABLE_SYSTEM_LOGS, true); - } -} diff --git a/acra-core/src/main/java/org/acra/collector/DropBoxCollector.kt b/acra-core/src/main/java/org/acra/collector/DropBoxCollector.kt new file mode 100644 index 0000000000..acffd169b5 --- /dev/null +++ b/acra-core/src/main/java/org/acra/collector/DropBoxCollector.kt @@ -0,0 +1,111 @@ +/* + * Copyright 2010 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.collector + +import android.Manifest +import android.annotation.SuppressLint +import android.content.Context +import android.os.Build +import android.os.DropBoxManager +import com.google.auto.service.AutoService +import org.acra.ACRA +import org.acra.ReportField +import org.acra.builder.ReportBuilder +import org.acra.config.CoreConfiguration +import org.acra.data.CrashReportData +import org.acra.log.warn +import org.acra.prefs.SharedPreferencesFactory +import org.acra.util.PackageManagerWrapper +import org.acra.util.SystemServices.getDropBoxManager +import org.json.JSONException +import org.json.JSONObject +import java.text.SimpleDateFormat +import java.util.* + +/** + * Collects data from the [DropBoxManager]. A set of DropBox tags have been identified in the Android source code. + * We collect data associated to these tags with hope that some of them could help debugging applications. + * Application specific tags can be provided by the app dev to track his own usage of the DropBoxManager. + * + * @author Kevin Gaudin & F43nd1r + */ +@AutoService(Collector::class) +class DropBoxCollector : BaseReportFieldCollector(ReportField.DROPBOX) { + private val dateFormat = SimpleDateFormat("yyyyMMdd'T'HHmmss", Locale.getDefault()) //iCal format (used for backwards compatibility) + override val order: Collector.Order + get() = Collector.Order.FIRST + + @SuppressLint("MissingPermission") + @Throws(Exception::class) + override fun collect(reportField: ReportField, context: Context, config: CoreConfiguration, reportBuilder: ReportBuilder, target: CrashReportData) { + val dropbox = getDropBoxManager(context) + val calendar = Calendar.getInstance() + calendar.roll(Calendar.MINUTE, -config.dropboxCollectionMinutes) + val time = calendar.timeInMillis + dateFormat.format(calendar.time) + val tags: MutableList = ArrayList() + if (config.includeDropBoxSystemTags) { + tags.addAll(SYSTEM_TAGS) + } + val additionalTags: List = config.additionalDropBoxTags.toList() + if (additionalTags.isNotEmpty()) { + tags.addAll(additionalTags) + } + if (tags.isNotEmpty()) { + val dropboxContent = JSONObject() + for (tag in tags) { + val builder = StringBuilder() + var entry = dropbox.getNextEntry(tag, time) + if (entry == null) { + builder.append("Nothing.").append('\n') + continue + } + while (entry != null) { + val msec = entry.timeMillis + calendar.timeInMillis = msec + builder.append('@').append(dateFormat.format(calendar.time)).append('\n') + val text = entry.getText(500) + if (text != null) { + builder.append("Text: ").append(text).append('\n') + } else { + builder.append("Not Text!").append('\n') + } + entry.close() + entry = dropbox.getNextEntry(tag, msec) + } + try { + dropboxContent.put(tag, builder.toString()) + } catch (e: JSONException) { + warn(e) { "Failed to collect data for tag $tag" } + } + } + target.put(ReportField.DROPBOX, dropboxContent) + } + } + + override fun shouldCollect(context: Context, config: CoreConfiguration, collect: ReportField, reportBuilder: ReportBuilder): Boolean { + return (super.shouldCollect(context, config, collect, reportBuilder) && + (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN || PackageManagerWrapper(context).hasPermission(Manifest.permission.READ_LOGS)) + && SharedPreferencesFactory(context, config).create().getBoolean(ACRA.PREF_ENABLE_SYSTEM_LOGS, true)) + } + + companion object { + private val SYSTEM_TAGS = arrayOf("system_app_anr", "system_app_wtf", "system_app_crash", + "system_server_anr", "system_server_wtf", "system_server_crash", "BATTERY_DISCHARGE_INFO", + "SYSTEM_RECOVERY_LOG", "SYSTEM_BOOT", "SYSTEM_LAST_KMSG", "APANIC_CONSOLE", "APANIC_THREADS", + "SYSTEM_RESTART", "SYSTEM_TOMBSTONE", "data_app_strictmode") + } +} \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/collector/LogCatCollector.java b/acra-core/src/main/java/org/acra/collector/LogCatCollector.java deleted file mode 100644 index d7af7184a5..0000000000 --- a/acra-core/src/main/java/org/acra/collector/LogCatCollector.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright 2010 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.collector; - -import android.Manifest; -import android.content.Context; -import android.os.Build; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import com.google.auto.service.AutoService; -import org.acra.ACRA; -import org.acra.ReportField; -import org.acra.builder.ReportBuilder; -import org.acra.config.CoreConfiguration; -import org.acra.data.CrashReportData; -import org.acra.prefs.SharedPreferencesFactory; -import org.acra.util.PackageManagerWrapper; -import org.acra.util.Predicate; -import org.acra.util.StreamReader; - -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import static org.acra.ACRA.LOG_TAG; - - -/** - * Executes logcat commands and collects it's output. - * - * @author Kevin Gaudin & F43nd1r - */ -@AutoService(Collector.class) -public final class LogCatCollector extends BaseReportFieldCollector { - private static final int READ_TIMEOUT = 3000; - - public LogCatCollector() { - super(ReportField.LOGCAT, ReportField.EVENTSLOG, ReportField.RADIOLOG); - } - - @NonNull - @Override - public Order getOrder() { - return Order.FIRST; - } - - /** - * Executes the logcat command with arguments taken from {@link org.acra.annotation.AcraCore#logcatArguments()} - * - * @param bufferName The name of the buffer to be read: "main" (default), "radio" or "events". - * @return A string containing the latest lines of the output. - * Default is 100 lines, use "-t", "300" in {@link org.acra.annotation.AcraCore#logcatArguments()} if you want 300 lines. - * You should be aware that increasing this value causes a longer report generation time and a bigger footprint on the device data plan consumption. - */ - private String collectLogCat(@NonNull CoreConfiguration config, @Nullable String bufferName) throws IOException { - final int myPid = android.os.Process.myPid(); - // no need to filter on jellybean onwards, android does that anyway - final String myPidStr = Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN && config.getLogcatFilterByPid() && myPid > 0 ? myPid + "):" : null; - - final List commandLine = new ArrayList<>(); - commandLine.add("logcat"); - if (bufferName != null) { - commandLine.add("-b"); - commandLine.add(bufferName); - } - - final int tailCount; - final List logcatArgumentsList = Arrays.asList(config.getLogcatArguments()); - - final int tailIndex = logcatArgumentsList.indexOf("-t"); - if (tailIndex > -1 && tailIndex < logcatArgumentsList.size()) { - tailCount = Integer.parseInt(logcatArgumentsList.get(tailIndex + 1)); - } else { - tailCount = -1; - } - - commandLine.addAll(logcatArgumentsList); - final Process process = new ProcessBuilder().command(commandLine).redirectErrorStream(true).start(); - if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "Retrieving logcat output (buffer:" + (bufferName == null ? "default" : bufferName) + ")..."); - - try { - return streamToString(config, process.getInputStream(), myPidStr == null ? null : s -> s.contains(myPidStr), tailCount); - } finally { - process.destroy(); - } - } - - @Override - boolean shouldCollect(@NonNull Context context, @NonNull CoreConfiguration config, @NonNull ReportField collect, @NonNull ReportBuilder reportBuilder) { - return super.shouldCollect(context, config, collect, reportBuilder) && - (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN || new PackageManagerWrapper(context).hasPermission(Manifest.permission.READ_LOGS)) - && new SharedPreferencesFactory(context, config).create().getBoolean(ACRA.PREF_ENABLE_SYSTEM_LOGS, true); - } - - @Override - void collect(@NonNull ReportField reportField, @NonNull Context context, @NonNull CoreConfiguration config, @NonNull ReportBuilder reportBuilder, @NonNull CrashReportData target) throws IOException { - String bufferName = null; - switch (reportField) { - case LOGCAT: - bufferName = null; - break; - case EVENTSLOG: - bufferName = "events"; - break; - case RADIOLOG: - bufferName = "radio"; - break; - } - target.put(reportField, collectLogCat(config, bufferName)); - } - - /** - * Reads an InputStream into a string respecting blocking settings. - * - * @param input the stream - * @param filter should return false for lines which should be excluded - * @param limit the maximum number of lines to read (the last x lines are kept) - * @return the String that was read. - * @throws IOException if the stream cannot be read. - */ - @NonNull - private String streamToString(@NonNull CoreConfiguration config, @NonNull InputStream input, @Nullable Predicate filter, int limit) throws IOException { - final StreamReader reader = new StreamReader(input).setFilter(filter).setLimit(limit); - if (config.getLogcatReadNonBlocking()) { - reader.setTimeout(READ_TIMEOUT); - } - return reader.read(); - } -} diff --git a/acra-core/src/main/java/org/acra/collector/LogCatCollector.kt b/acra-core/src/main/java/org/acra/collector/LogCatCollector.kt new file mode 100644 index 0000000000..154d74a8ff --- /dev/null +++ b/acra-core/src/main/java/org/acra/collector/LogCatCollector.kt @@ -0,0 +1,122 @@ +/* + * Copyright 2010 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.collector + +import android.Manifest +import android.content.Context +import android.os.Build +import android.os.Process +import com.google.auto.service.AutoService +import org.acra.ACRA +import org.acra.ReportField +import org.acra.builder.ReportBuilder +import org.acra.config.CoreConfiguration +import org.acra.data.CrashReportData +import org.acra.log.debug +import org.acra.prefs.SharedPreferencesFactory +import org.acra.util.PackageManagerWrapper +import org.acra.util.StreamReader +import java.io.IOException +import java.io.InputStream +import java.util.* + +/** + * Executes logcat commands and collects it's output. + * + * @author Kevin Gaudin & F43nd1r + */ +@AutoService(Collector::class) +class LogCatCollector : BaseReportFieldCollector(ReportField.LOGCAT, ReportField.EVENTSLOG, ReportField.RADIOLOG) { + override val order: Collector.Order + get() = Collector.Order.FIRST + + /** + * Executes the logcat command with arguments taken from [org.acra.annotation.AcraCore.logcatArguments] + * + * @param bufferName The name of the buffer to be read: "main" (default), "radio" or "events". + * @return A string containing the latest lines of the output. + * Default is 100 lines, use "-t", "300" in [org.acra.annotation.AcraCore.logcatArguments] if you want 300 lines. + * You should be aware that increasing this value causes a longer report generation time and a bigger footprint on the device data plan consumption. + */ + @Throws(IOException::class) + private fun collectLogCat(config: CoreConfiguration, bufferName: String?): String { + val myPid = Process.myPid() + // no need to filter on jellybean onwards, android does that anyway + val myPidStr = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN && config.logcatFilterByPid && myPid > 0) "$myPid):" else null + val commandLine: MutableList = ArrayList() + commandLine.add("logcat") + if (bufferName != null) { + commandLine.add("-b") + commandLine.add(bufferName) + } + val tailCount: Int + val logcatArgumentsList: List = config.logcatArguments.toList() + val tailIndex = logcatArgumentsList.indexOf("-t") + tailCount = if (tailIndex > -1 && tailIndex < logcatArgumentsList.size) { + logcatArgumentsList[tailIndex + 1].toInt() + } else { + -1 + } + commandLine.addAll(logcatArgumentsList) + val process = ProcessBuilder().command(commandLine).redirectErrorStream(true).start() + debug { "Retrieving logcat output (buffer:${bufferName ?: "default"})..." } + return try { + streamToString(config, process.inputStream, myPidStr?.let { pid -> { it.contains(pid) } }, tailCount) + } finally { + process.destroy() + } + } + + override fun shouldCollect(context: Context, config: CoreConfiguration, collect: ReportField, reportBuilder: ReportBuilder): Boolean { + return (super.shouldCollect(context, config, collect, reportBuilder) && + (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN || PackageManagerWrapper(context).hasPermission(Manifest.permission.READ_LOGS)) + && SharedPreferencesFactory(context, config).create().getBoolean(ACRA.PREF_ENABLE_SYSTEM_LOGS, true)) + } + + @Throws(IOException::class) + override fun collect(reportField: ReportField, context: Context, config: CoreConfiguration, reportBuilder: ReportBuilder, target: CrashReportData) { + var bufferName: String? = null + @Suppress("NON_EXHAUSTIVE_WHEN") + when (reportField) { + ReportField.LOGCAT -> bufferName = null + ReportField.EVENTSLOG -> bufferName = "events" + ReportField.RADIOLOG -> bufferName = "radio" + } + target.put(reportField, collectLogCat(config, bufferName)) + } + + /** + * Reads an InputStream into a string respecting blocking settings. + * + * @param input the stream + * @param filter should return false for lines which should be excluded + * @param limit the maximum number of lines to read (the last x lines are kept) + * @return the String that was read. + * @throws IOException if the stream cannot be read. + */ + @Throws(IOException::class) + private fun streamToString(config: CoreConfiguration, input: InputStream, filter: ((String) -> Boolean)?, limit: Int): String { + val reader = StreamReader(input).setFilter(filter).setLimit(limit) + if (config.logcatReadNonBlocking) { + reader.setTimeout(READ_TIMEOUT) + } + return reader.read() + } + + companion object { + private const val READ_TIMEOUT = 3000 + } +} \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/collector/LogFileCollector.java b/acra-core/src/main/java/org/acra/collector/LogFileCollector.java deleted file mode 100644 index 04f6dea85d..0000000000 --- a/acra-core/src/main/java/org/acra/collector/LogFileCollector.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * 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.collector; - -import android.content.Context; -import androidx.annotation.NonNull; - -import com.google.auto.service.AutoService; - -import org.acra.ReportField; -import org.acra.builder.ReportBuilder; -import org.acra.config.CoreConfiguration; -import org.acra.data.CrashReportData; -import org.acra.util.StreamReader; - -import java.io.IOException; - -/** - * Collects the N last lines of a text stream. Use this collector if your application handles its own logging system. - * - * @author Kevin Gaudin & F43nd1r - */ -@AutoService(Collector.class) -public final class LogFileCollector extends BaseReportFieldCollector { - - public LogFileCollector() { - super(ReportField.APPLICATION_LOG); - } - - @NonNull - @Override - public Order getOrder() { - return Order.LATE; - } - - @Override - void collect(@NonNull ReportField reportField, @NonNull Context context, @NonNull CoreConfiguration config, @NonNull ReportBuilder reportBuilder, @NonNull CrashReportData target) throws IOException { - target.put(ReportField.APPLICATION_LOG, new StreamReader(config.getApplicationLogFileDir().getFile(context, config.getApplicationLogFile())) - .setLimit(config.getApplicationLogFileLines()).read()); - } -} diff --git a/acra-core/src/main/java/org/acra/collector/LogFileCollector.kt b/acra-core/src/main/java/org/acra/collector/LogFileCollector.kt new file mode 100644 index 0000000000..ce3ea949b2 --- /dev/null +++ b/acra-core/src/main/java/org/acra/collector/LogFileCollector.kt @@ -0,0 +1,42 @@ +/* + * 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.collector + +import android.content.Context +import com.google.auto.service.AutoService +import org.acra.ReportField +import org.acra.builder.ReportBuilder +import org.acra.config.CoreConfiguration +import org.acra.data.CrashReportData +import org.acra.util.StreamReader +import java.io.IOException + +/** + * Collects the N last lines of a text stream. Use this collector if your application handles its own logging system. + * + * @author Kevin Gaudin & F43nd1r + */ +@AutoService(Collector::class) +class LogFileCollector : BaseReportFieldCollector(ReportField.APPLICATION_LOG) { + override val order: Collector.Order + get() = Collector.Order.LATE + + @Throws(IOException::class) + override fun collect(reportField: ReportField, context: Context, config: CoreConfiguration, reportBuilder: ReportBuilder, target: CrashReportData) { + target.put(ReportField.APPLICATION_LOG, StreamReader(config.applicationLogFileDir.getFile(context, config.applicationLogFile)) + .setLimit(config.applicationLogFileLines).read()) + } +} \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/collector/MediaCodecListCollector.java b/acra-core/src/main/java/org/acra/collector/MediaCodecListCollector.java deleted file mode 100644 index 0b30cf4e31..0000000000 --- a/acra-core/src/main/java/org/acra/collector/MediaCodecListCollector.java +++ /dev/null @@ -1,274 +0,0 @@ -/* - * 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.collector; - -import android.annotation.TargetApi; -import android.content.Context; -import android.media.MediaCodecInfo; -import android.media.MediaCodecList; -import android.os.Build; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import android.util.SparseArray; - -import com.google.auto.service.AutoService; - -import org.acra.ReportField; -import org.acra.builder.ReportBuilder; -import org.acra.config.CoreConfiguration; -import org.acra.data.CrashReportData; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import java.lang.reflect.Field; -import java.lang.reflect.Modifier; - -/** - * Collects data about available codecs on the device through the MediaCodecList API introduced in Android 4.1 JellyBean. - * - * @author Kevin Gaudin & F43nd1r - */ -@AutoService(Collector.class) -public final class MediaCodecListCollector extends BaseReportFieldCollector { - - private enum CodecType { - AVC, H263, MPEG4, AAC - - } - - private static final String COLOR_FORMAT_PREFIX = "COLOR_"; - private static final String[] MPEG4_TYPES = {"mp4", "mpeg4", "MP4", "MPEG4"}; - private static final String[] AVC_TYPES = {"avc", "h264", "AVC", "H264"}; - private static final String[] H263_TYPES = {"h263", "H263"}; - private static final String[] AAC_TYPES = {"aac", "AAC"}; - - private final SparseArray mColorFormatValues = new SparseArray<>(); - private final SparseArray mAVCLevelValues = new SparseArray<>(); - private final SparseArray mAVCProfileValues = new SparseArray<>(); - private final SparseArray mH263LevelValues = new SparseArray<>(); - private final SparseArray mH263ProfileValues = new SparseArray<>(); - private final SparseArray mMPEG4LevelValues = new SparseArray<>(); - private final SparseArray mMPEG4ProfileValues = new SparseArray<>(); - private final SparseArray mAACProfileValues = new SparseArray<>(); - - public MediaCodecListCollector() { - super(ReportField.MEDIA_CODEC_LIST); - } - - @NonNull - @Override - public Order getOrder() { - return Order.LATE; - } - - @Override - void collect(@NonNull ReportField reportField, @NonNull Context context, @NonNull CoreConfiguration config, @NonNull ReportBuilder reportBuilder, @NonNull CrashReportData target) throws JSONException { - target.put(ReportField.MEDIA_CODEC_LIST, collectMediaCodecList()); - } - - @Override - boolean shouldCollect(@NonNull Context context, @NonNull CoreConfiguration config, @NonNull ReportField collect, @NonNull ReportBuilder reportBuilder) { - return super.shouldCollect(context, config, collect, reportBuilder) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN; - } - - /** - * use reflection to prepare field arrays. - */ - private void prepare() { - try { - final Class codecCapabilitiesClass = Class.forName("android.media.MediaCodecInfo$CodecCapabilities"); - - // Retrieve list of possible Color Format - for (Field f : codecCapabilitiesClass.getFields()) { - if (Modifier.isStatic(f.getModifiers()) && Modifier.isFinal(f.getModifiers()) - && f.getName().startsWith(COLOR_FORMAT_PREFIX)) { - mColorFormatValues.put(f.getInt(null), f.getName()); - } - } - - // Retrieve lists of possible codecs profiles and levels - final Class codecProfileLevelClass = Class.forName("android.media.MediaCodecInfo$CodecProfileLevel"); - for (Field f : codecProfileLevelClass.getFields()) { - if (Modifier.isStatic(f.getModifiers()) && Modifier.isFinal(f.getModifiers())) { - if (f.getName().startsWith("AVCLevel")) { - mAVCLevelValues.put(f.getInt(null), f.getName()); - } else if (f.getName().startsWith("AVCProfile")) { - mAVCProfileValues.put(f.getInt(null), f.getName()); - } else if (f.getName().startsWith("H263Level")) { - mH263LevelValues.put(f.getInt(null), f.getName()); - } else if (f.getName().startsWith("H263Profile")) { - mH263ProfileValues.put(f.getInt(null), f.getName()); - } else if (f.getName().startsWith("MPEG4Level")) { - mMPEG4LevelValues.put(f.getInt(null), f.getName()); - } else if (f.getName().startsWith("MPEG4Profile")) { - mMPEG4ProfileValues.put(f.getInt(null), f.getName()); - } else if (f.getName().startsWith("AAC")) { - mAACProfileValues.put(f.getInt(null), f.getName()); - } - } - } - } catch (@NonNull ClassNotFoundException ignored) { - // NOOP - } catch (@NonNull SecurityException ignored) { - // NOOP - } catch (@NonNull IllegalAccessException ignored) { - // NOOP - } catch (@NonNull IllegalArgumentException ignored) { - // NOOP - } - - } - - /** - * Builds a JSONObject describing the list of available codecs on the device with their capabilities (supported Color Formats, Codec Profiles and Levels). - * - * @return The media codecs information - */ - @TargetApi(Build.VERSION_CODES.JELLY_BEAN) - @NonNull - private JSONObject collectMediaCodecList() throws JSONException { - prepare(); - final MediaCodecInfo[] infos; - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { - //noinspection deprecation - final int codecCount = MediaCodecList.getCodecCount(); - infos = new MediaCodecInfo[codecCount]; - for (int codecIdx = 0; codecIdx < codecCount; codecIdx++) { - //noinspection deprecation - infos[codecIdx] = MediaCodecList.getCodecInfoAt(codecIdx); - } - } else { - infos = new MediaCodecList(MediaCodecList.ALL_CODECS).getCodecInfos(); - } - - final JSONObject result = new JSONObject(); - for (int i = 0; i < infos.length; i++) { - final MediaCodecInfo codecInfo = infos[i]; - final JSONObject codec = new JSONObject(); - final String[] supportedTypes = codecInfo.getSupportedTypes(); - codec.put("name", codecInfo.getName()) - .put("isEncoder", codecInfo.isEncoder()); - final JSONObject supportedTypesJson = new JSONObject(); - for (String type : supportedTypes) { - supportedTypesJson.put(type, collectCapabilitiesForType(codecInfo, type)); - } - codec.put("supportedTypes", supportedTypesJson); - result.put(String.valueOf(i), codec); - } - return result; - } - - /** - * Retrieve capabilities (ColorFormats and CodecProfileLevels) for a specific codec type. - * - * @param codecInfo the currently inspected codec - * @param type supported type to collect - * @return the color formats and codec profile levels available for a specific codec type. - */ - @NonNull - @TargetApi(Build.VERSION_CODES.JELLY_BEAN) - private JSONObject collectCapabilitiesForType(@NonNull final MediaCodecInfo codecInfo, @NonNull String type) throws JSONException { - final JSONObject result = new JSONObject(); - final MediaCodecInfo.CodecCapabilities codecCapabilities = codecInfo.getCapabilitiesForType(type); - - // Color Formats - final int[] colorFormats = codecCapabilities.colorFormats; - if (colorFormats.length > 0) { - final JSONArray colorFormatsJson = new JSONArray(); - for (int colorFormat : colorFormats) { - colorFormatsJson.put(mColorFormatValues.get(colorFormat)); - } - result.put("colorFormats", colorFormatsJson); - } - - final CodecType codecType = identifyCodecType(codecInfo); - - // Profile Levels - final MediaCodecInfo.CodecProfileLevel[] codecProfileLevels = codecCapabilities.profileLevels; - if (codecProfileLevels.length > 0) { - final JSONArray profileLevels = new JSONArray(); - for (MediaCodecInfo.CodecProfileLevel codecProfileLevel : codecProfileLevels) { - final int profileValue = codecProfileLevel.profile; - final int levelValue = codecProfileLevel.level; - - if (codecType == null) { - // Unknown codec - profileLevels.put(profileValue + '-' + levelValue); - break; - } - - switch (codecType) { - case AVC: - profileLevels.put(profileValue + mAVCProfileValues.get(profileValue) - + '-' + mAVCLevelValues.get(levelValue)); - break; - case H263: - profileLevels.put(mH263ProfileValues.get(profileValue) - + '-' + mH263LevelValues.get(levelValue)); - break; - case MPEG4: - profileLevels.put(mMPEG4ProfileValues.get(profileValue) - + '-' + mMPEG4LevelValues.get(levelValue)); - break; - case AAC: - profileLevels.put(mAACProfileValues.get(profileValue)); - break; - default: - break; - } - } - result.put("profileLevels", profileLevels); - } - return result; - } - - /** - * Looks for keywords in the codec name to identify its nature ({@link CodecType}). - * - * @param codecInfo the currently inspected codec - * @return type of the codec or null if it could bot be guessed - */ - @Nullable - @TargetApi(Build.VERSION_CODES.JELLY_BEAN) - private CodecType identifyCodecType(@NonNull MediaCodecInfo codecInfo) { - - final String name = codecInfo.getName(); - for (String token : AVC_TYPES) { - if (name.contains(token)) { - return CodecType.AVC; - } - } - for (String token : H263_TYPES) { - if (name.contains(token)) { - return CodecType.H263; - } - } - for (String token : MPEG4_TYPES) { - if (name.contains(token)) { - return CodecType.MPEG4; - } - } - for (String token : AAC_TYPES) { - if (name.contains(token)) { - return CodecType.AAC; - } - } - - return null; - } -} diff --git a/acra-core/src/main/java/org/acra/collector/MediaCodecListCollector.kt b/acra-core/src/main/java/org/acra/collector/MediaCodecListCollector.kt new file mode 100644 index 0000000000..657aa5cc57 --- /dev/null +++ b/acra-core/src/main/java/org/acra/collector/MediaCodecListCollector.kt @@ -0,0 +1,230 @@ +/* + * 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.collector + +import android.annotation.TargetApi +import android.content.Context +import android.media.MediaCodecInfo +import android.media.MediaCodecList +import android.os.Build +import android.util.SparseArray +import com.google.auto.service.AutoService +import org.acra.ReportField +import org.acra.builder.ReportBuilder +import org.acra.config.CoreConfiguration +import org.acra.data.CrashReportData +import org.json.JSONArray +import org.json.JSONException +import org.json.JSONObject +import java.lang.reflect.Modifier + +/** + * Collects data about available codecs on the device through the MediaCodecList API introduced in Android 4.1 JellyBean. + * + * @author Kevin Gaudin & F43nd1r + */ +@AutoService(Collector::class) +class MediaCodecListCollector : BaseReportFieldCollector(ReportField.MEDIA_CODEC_LIST) { + private enum class CodecType { + AVC, H263, MPEG4, AAC + } + + private val mColorFormatValues = SparseArray() + private val mAVCLevelValues = SparseArray() + private val mAVCProfileValues = SparseArray() + private val mH263LevelValues = SparseArray() + private val mH263ProfileValues = SparseArray() + private val mMPEG4LevelValues = SparseArray() + private val mMPEG4ProfileValues = SparseArray() + private val mAACProfileValues = SparseArray() + override val order: Collector.Order + get() = Collector.Order.LATE + + @Throws(JSONException::class) + override fun collect(reportField: ReportField, context: Context, config: CoreConfiguration, reportBuilder: ReportBuilder, target: CrashReportData) { + target.put(ReportField.MEDIA_CODEC_LIST, collectMediaCodecList()) + } + + override fun shouldCollect(context: Context, config: CoreConfiguration, collect: ReportField, reportBuilder: ReportBuilder): Boolean { + return super.shouldCollect(context, config, collect, reportBuilder) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN + } + + /** + * use reflection to prepare field arrays. + */ + private fun prepare() { + try { + val codecCapabilitiesClass = Class.forName("android.media.MediaCodecInfo\$CodecCapabilities") + + // Retrieve list of possible Color Format + for (f in codecCapabilitiesClass.fields) { + if (Modifier.isStatic(f.modifiers) && Modifier.isFinal(f.modifiers) + && f.name.startsWith(COLOR_FORMAT_PREFIX)) { + mColorFormatValues.put(f.getInt(null), f.name) + } + } + + // Retrieve lists of possible codecs profiles and levels + val codecProfileLevelClass = Class.forName("android.media.MediaCodecInfo\$CodecProfileLevel") + for (f in codecProfileLevelClass.fields) { + if (Modifier.isStatic(f.modifiers) && Modifier.isFinal(f.modifiers)) { + when { + f.name.startsWith("AVCLevel") -> mAVCLevelValues.put(f.getInt(null), f.name) + f.name.startsWith("AVCProfile") -> mAVCProfileValues.put(f.getInt(null), f.name) + f.name.startsWith("H263Level") -> mH263LevelValues.put(f.getInt(null), f.name) + f.name.startsWith("H263Profile") -> mH263ProfileValues.put(f.getInt(null), f.name) + f.name.startsWith("MPEG4Level") -> mMPEG4LevelValues.put(f.getInt(null), f.name) + f.name.startsWith("MPEG4Profile") -> mMPEG4ProfileValues.put(f.getInt(null), f.name) + f.name.startsWith("AAC") -> mAACProfileValues.put(f.getInt(null), f.name) + } + } + } + } catch (ignored: ClassNotFoundException) { + // NOOP + } catch (ignored: SecurityException) { + // NOOP + } catch (ignored: IllegalAccessException) { + // NOOP + } catch (ignored: IllegalArgumentException) { + // NOOP + } + } + + /** + * Builds a JSONObject describing the list of available codecs on the device with their capabilities (supported Color Formats, Codec Profiles and Levels). + * + * @return The media codecs information + */ + @Suppress("DEPRECATION") + @TargetApi(Build.VERSION_CODES.JELLY_BEAN) + @Throws(JSONException::class) + private fun collectMediaCodecList(): JSONObject { + prepare() + val infos: Array + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + val codecCount = MediaCodecList.getCodecCount() + infos = arrayOfNulls(codecCount) + for (codecIdx in 0 until codecCount) { + infos[codecIdx] = MediaCodecList.getCodecInfoAt(codecIdx) + } + } else { + infos = MediaCodecList(MediaCodecList.ALL_CODECS).codecInfos + } + val result = JSONObject() + for (i in infos.indices) { + val codecInfo = infos[i] + val codec = JSONObject() + val supportedTypes = codecInfo!!.supportedTypes + codec.put("name", codecInfo.name) + .put("isEncoder", codecInfo.isEncoder) + val supportedTypesJson = JSONObject() + for (type in supportedTypes) { + supportedTypesJson.put(type, collectCapabilitiesForType(codecInfo, type)) + } + codec.put("supportedTypes", supportedTypesJson) + result.put(i.toString(), codec) + } + return result + } + + /** + * Retrieve capabilities (ColorFormats and CodecProfileLevels) for a specific codec type. + * + * @param codecInfo the currently inspected codec + * @param type supported type to collect + * @return the color formats and codec profile levels available for a specific codec type. + */ + @TargetApi(Build.VERSION_CODES.JELLY_BEAN) + @Throws(JSONException::class) + private fun collectCapabilitiesForType(codecInfo: MediaCodecInfo, type: String): JSONObject { + val result = JSONObject() + val codecCapabilities = codecInfo.getCapabilitiesForType(type) + + // Color Formats + val colorFormats = codecCapabilities.colorFormats + if (colorFormats.isNotEmpty()) { + val colorFormatsJson = JSONArray() + for (colorFormat in colorFormats) { + colorFormatsJson.put(mColorFormatValues[colorFormat]) + } + result.put("colorFormats", colorFormatsJson) + } + val codecType = identifyCodecType(codecInfo) + + // Profile Levels + val codecProfileLevels = codecCapabilities.profileLevels + if (codecProfileLevels.isNotEmpty()) { + val profileLevels = JSONArray() + for (codecProfileLevel in codecProfileLevels) { + val profileValue = codecProfileLevel.profile + val levelValue = codecProfileLevel.level + if (codecType == null) { + // Unknown codec + profileLevels.put(profileValue + '-'.toInt() + levelValue) + break + } + when (codecType) { + CodecType.AVC -> profileLevels.put("$profileValue${mAVCProfileValues[profileValue]}-${mAVCLevelValues[levelValue]}") + CodecType.H263 -> profileLevels.put("${mH263ProfileValues[profileValue]}-${mH263LevelValues[levelValue]}") + CodecType.MPEG4 -> profileLevels.put("${mMPEG4ProfileValues[profileValue]}-${mMPEG4LevelValues[levelValue]}") + CodecType.AAC -> profileLevels.put(mAACProfileValues[profileValue]) + } + } + result.put("profileLevels", profileLevels) + } + return result + } + + /** + * Looks for keywords in the codec name to identify its nature ([CodecType]). + * + * @param codecInfo the currently inspected codec + * @return type of the codec or null if it could bot be guessed + */ + @TargetApi(Build.VERSION_CODES.JELLY_BEAN) + private fun identifyCodecType(codecInfo: MediaCodecInfo): CodecType? { + val name = codecInfo.name + for (token in AVC_TYPES) { + if (name.contains(token)) { + return CodecType.AVC + } + } + for (token in H263_TYPES) { + if (name.contains(token)) { + return CodecType.H263 + } + } + for (token in MPEG4_TYPES) { + if (name.contains(token)) { + return CodecType.MPEG4 + } + } + for (token in AAC_TYPES) { + if (name.contains(token)) { + return CodecType.AAC + } + } + return null + } + + companion object { + private const val COLOR_FORMAT_PREFIX = "COLOR_" + private val MPEG4_TYPES = arrayOf("mp4", "mpeg4", "MP4", "MPEG4") + private val AVC_TYPES = arrayOf("avc", "h264", "AVC", "H264") + private val H263_TYPES = arrayOf("h263", "H263") + private val AAC_TYPES = arrayOf("aac", "AAC") + } +} \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/collector/MemoryInfoCollector.java b/acra-core/src/main/java/org/acra/collector/MemoryInfoCollector.java deleted file mode 100644 index 95aaf3735e..0000000000 --- a/acra-core/src/main/java/org/acra/collector/MemoryInfoCollector.java +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright 2010 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.collector; - -import android.content.Context; -import android.os.Build; -import android.os.Environment; -import android.os.StatFs; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.google.auto.service.AutoService; - -import org.acra.ACRA; -import org.acra.ReportField; -import org.acra.builder.ReportBuilder; -import org.acra.config.CoreConfiguration; -import org.acra.data.CrashReportData; -import org.acra.util.StreamReader; - -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -import static org.acra.ACRA.LOG_TAG; - -/** - * Collects results of the dumpsys command. - * - * @author Kevin Gaudin & F43nd1r - */ -@AutoService(Collector.class) -public final class MemoryInfoCollector extends BaseReportFieldCollector { - public MemoryInfoCollector() { - super(ReportField.DUMPSYS_MEMINFO, ReportField.TOTAL_MEM_SIZE, ReportField.AVAILABLE_MEM_SIZE); - } - - @Override - boolean shouldCollect(@NonNull Context context, @NonNull CoreConfiguration config, @NonNull ReportField collect, @NonNull ReportBuilder reportBuilder) { - return super.shouldCollect(context, config, collect, reportBuilder) && !(reportBuilder.getException() instanceof OutOfMemoryError); - } - - @Override - void collect(@NonNull ReportField reportField, @NonNull Context context, @NonNull CoreConfiguration config, @NonNull ReportBuilder reportBuilder, @NonNull CrashReportData target) { - switch (reportField) { - case DUMPSYS_MEMINFO: - target.put(ReportField.DUMPSYS_MEMINFO, collectMemInfo()); - break; - case TOTAL_MEM_SIZE: - target.put(ReportField.TOTAL_MEM_SIZE, getTotalInternalMemorySize()); - break; - case AVAILABLE_MEM_SIZE: - target.put(ReportField.AVAILABLE_MEM_SIZE, getAvailableInternalMemorySize()); - break; - default: - //will not happen if used correctly - throw new IllegalArgumentException(); - } - } - - /** - * Collect results of the dumpsys meminfo command restricted to this application process. - * - * @return The execution result. - */ - @Nullable - private String collectMemInfo() { - try { - final List commandLine = new ArrayList<>(); - commandLine.add("dumpsys"); - commandLine.add("meminfo"); - commandLine.add(Integer.toString(android.os.Process.myPid())); - - final Process process = Runtime.getRuntime().exec(commandLine.toArray(new String[commandLine.size()])); - return new StreamReader(process.getInputStream()).read(); - } catch (IOException e) { - ACRA.log.e(LOG_TAG, "MemoryInfoCollector.meminfo could not retrieve data", e); - return null; - } - } - - /** - * Calculates the free memory of the device. This is based on an inspection of the filesystem, which in android - * devices is stored in RAM. - * - * @return Number of bytes available. - */ - private long getAvailableInternalMemorySize() { - final File path = Environment.getDataDirectory(); - final StatFs stat = new StatFs(path.getPath()); - final long blockSize; - final long availableBlocks; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { - blockSize = stat.getBlockSizeLong(); - availableBlocks = stat.getAvailableBlocksLong(); - } else { - //noinspection deprecation - blockSize = stat.getBlockSize(); - //noinspection deprecation - availableBlocks = stat.getAvailableBlocks(); - } - return availableBlocks * blockSize; - } - - /** - * Calculates the total memory of the device. This is based on an inspection of the filesystem, which in android devices is stored in RAM. - * - * @return Total number of bytes. - */ - private long getTotalInternalMemorySize() { - final File path = Environment.getDataDirectory(); - final StatFs stat = new StatFs(path.getPath()); - final long blockSize; - final long totalBlocks; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { - blockSize = stat.getBlockSizeLong(); - totalBlocks = stat.getBlockCountLong(); - } else { - //noinspection deprecation - blockSize = stat.getBlockSize(); - //noinspection deprecation - totalBlocks = stat.getBlockCount(); - } - return totalBlocks * blockSize; - } - -} \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/collector/MemoryInfoCollector.kt b/acra-core/src/main/java/org/acra/collector/MemoryInfoCollector.kt new file mode 100644 index 0000000000..2aa5273f2d --- /dev/null +++ b/acra-core/src/main/java/org/acra/collector/MemoryInfoCollector.kt @@ -0,0 +1,115 @@ +/* + * Copyright 2010 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.collector + +import android.content.Context +import android.os.Build +import android.os.Environment +import android.os.Process +import android.os.StatFs +import com.google.auto.service.AutoService +import org.acra.ACRA +import org.acra.ReportField +import org.acra.builder.ReportBuilder +import org.acra.config.CoreConfiguration +import org.acra.data.CrashReportData +import org.acra.log.error +import org.acra.util.StreamReader +import java.io.IOException +import java.util.* + +/** + * Collects results of the `dumpsys` command. + * + * @author Kevin Gaudin & F43nd1r + */ +@AutoService(Collector::class) +class MemoryInfoCollector : BaseReportFieldCollector(ReportField.DUMPSYS_MEMINFO, ReportField.TOTAL_MEM_SIZE, ReportField.AVAILABLE_MEM_SIZE) { + override fun shouldCollect(context: Context, config: CoreConfiguration, collect: ReportField, reportBuilder: ReportBuilder): Boolean { + return super.shouldCollect(context, config, collect, reportBuilder) && reportBuilder.exception !is OutOfMemoryError + } + + override fun collect(reportField: ReportField, context: Context, config: CoreConfiguration, reportBuilder: ReportBuilder, target: CrashReportData) { + when (reportField) { + ReportField.DUMPSYS_MEMINFO -> target.put(ReportField.DUMPSYS_MEMINFO, collectMemInfo()) + ReportField.TOTAL_MEM_SIZE -> target.put(ReportField.TOTAL_MEM_SIZE, getTotalInternalMemorySize()) + ReportField.AVAILABLE_MEM_SIZE -> target.put(ReportField.AVAILABLE_MEM_SIZE, getAvailableInternalMemorySize()) + else -> throw IllegalArgumentException() + } + } + + /** + * Collect results of the `dumpsys meminfo` command restricted to this application process. + * + * @return The execution result. + */ + private fun collectMemInfo(): String? { + return try { + val commandLine: MutableList = ArrayList() + commandLine.add("dumpsys") + commandLine.add("meminfo") + commandLine.add(Process.myPid().toString()) + val process = Runtime.getRuntime().exec(commandLine.toTypedArray()) + StreamReader(process.inputStream).read() + } catch (e: IOException) { + error(e) { "MemoryInfoCollector.meminfo could not retrieve data"} + null + } + } + + /** + * Calculates the free memory of the device. This is based on an inspection of the filesystem, which in android + * devices is stored in RAM. + * + * @return Number of bytes available. + */ + @Suppress("DEPRECATION") + private fun getAvailableInternalMemorySize(): Long { + val path = Environment.getDataDirectory() + val stat = StatFs(path.path) + val blockSize: Long + val availableBlocks: Long + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { + blockSize = stat.blockSizeLong + availableBlocks = stat.availableBlocksLong + } else { + blockSize = stat.blockSize.toLong() + availableBlocks = stat.availableBlocks.toLong() + } + return availableBlocks * blockSize + } + + /** + * Calculates the total memory of the device. This is based on an inspection of the filesystem, which in android devices is stored in RAM. + * + * @return Total number of bytes. + */ + @Suppress("DEPRECATION") + private fun getTotalInternalMemorySize(): Long { + val path = Environment.getDataDirectory() + val stat = StatFs(path.path) + val blockSize: Long + val totalBlocks: Long + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { + blockSize = stat.blockSizeLong + totalBlocks = stat.blockCountLong + } else { + blockSize = stat.blockSize.toLong() + totalBlocks = stat.blockCount.toLong() + } + return totalBlocks * blockSize + } +} \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/collector/PackageManagerCollector.java b/acra-core/src/main/java/org/acra/collector/PackageManagerCollector.java deleted file mode 100644 index dca64133e5..0000000000 --- a/acra-core/src/main/java/org/acra/collector/PackageManagerCollector.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2016 - * - * 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.collector; - -import android.content.Context; -import android.content.pm.PackageInfo; -import androidx.annotation.NonNull; - -import com.google.auto.service.AutoService; - -import org.acra.ReportField; -import org.acra.builder.ReportBuilder; -import org.acra.config.CoreConfiguration; -import org.acra.data.CrashReportData; -import org.acra.util.PackageManagerWrapper; - -/** - * Collects {@link PackageInfo} values - * - * @author F43nd1r - * @since 4.9.1 - */ -@AutoService(Collector.class) -public final class PackageManagerCollector extends BaseReportFieldCollector { - - public PackageManagerCollector() { - super(ReportField.APP_VERSION_NAME, ReportField.APP_VERSION_CODE); - } - - @Override - void collect(@NonNull ReportField reportField, @NonNull Context context, @NonNull CoreConfiguration config, @NonNull ReportBuilder reportBuilder, @NonNull CrashReportData target) throws CollectorException { - final PackageInfo info = new PackageManagerWrapper(context).getPackageInfo(); - if (info == null) { - throw new CollectorException("Failed to get package info"); - } else { - switch (reportField) { - case APP_VERSION_NAME: - target.put(ReportField.APP_VERSION_NAME, info.versionName); - break; - case APP_VERSION_CODE: - target.put(ReportField.APP_VERSION_CODE, info.versionCode); - break; - } - } - } -} diff --git a/acra-core/src/main/java/org/acra/collector/PackageManagerCollector.kt b/acra-core/src/main/java/org/acra/collector/PackageManagerCollector.kt new file mode 100644 index 0000000000..e4b314a2a4 --- /dev/null +++ b/acra-core/src/main/java/org/acra/collector/PackageManagerCollector.kt @@ -0,0 +1,47 @@ +/* + * Copyright 2016 + * + * 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.collector + +import android.content.Context +import com.google.auto.service.AutoService +import org.acra.ReportField +import org.acra.builder.ReportBuilder +import org.acra.config.CoreConfiguration +import org.acra.data.CrashReportData +import org.acra.util.PackageManagerWrapper + +/** + * Collects [PackageInfo] values + * + * @author F43nd1r + * @since 4.9.1 + */ +@AutoService(Collector::class) +class PackageManagerCollector : BaseReportFieldCollector(ReportField.APP_VERSION_NAME, ReportField.APP_VERSION_CODE) { + @Throws(CollectorException::class) + override fun collect(reportField: ReportField, context: Context, config: CoreConfiguration, reportBuilder: ReportBuilder, target: CrashReportData) { + val info = PackageManagerWrapper(context).getPackageInfo() + if (info == null) { + throw CollectorException("Failed to get package info") + } else { + @Suppress("NON_EXHAUSTIVE_WHEN", "DEPRECATION") + when (reportField) { + ReportField.APP_VERSION_NAME -> target.put(ReportField.APP_VERSION_NAME, info.versionName) + ReportField.APP_VERSION_CODE -> target.put(ReportField.APP_VERSION_CODE, info.versionCode) + } + } + } +} \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/collector/ReflectionCollector.java b/acra-core/src/main/java/org/acra/collector/ReflectionCollector.java deleted file mode 100644 index c1018b794f..0000000000 --- a/acra-core/src/main/java/org/acra/collector/ReflectionCollector.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright 2010 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.collector; - -import android.content.Context; -import android.os.Build; -import android.os.Environment; -import androidx.annotation.NonNull; - -import com.google.auto.service.AutoService; - -import org.acra.ReportField; -import org.acra.builder.ReportBuilder; -import org.acra.config.CoreConfiguration; -import org.acra.data.CrashReportData; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.Arrays; - -/** - * Collector retrieving key/value pairs from static fields and getters. - * Reflection API usage allows to retrieve data without having to implement a class for each android version of each interesting class. - * It can also help find hidden properties. - * - * @author Kevin Gaudin & F43nd1r - */ -@AutoService(Collector.class) -public final class ReflectionCollector extends BaseReportFieldCollector { - - public ReflectionCollector() { - super(ReportField.BUILD, ReportField.BUILD_CONFIG, ReportField.ENVIRONMENT); - } - - @Override - void collect(@NonNull ReportField reportField, @NonNull Context context, @NonNull CoreConfiguration config, @NonNull ReportBuilder reportBuilder, @NonNull CrashReportData target) - throws JSONException, ClassNotFoundException { - final JSONObject result = new JSONObject(); - switch (reportField) { - case BUILD: - collectConstants(Build.class, result); - final JSONObject version = new JSONObject(); - collectConstants(Build.VERSION.class, version); - result.put("VERSION", version); - break; - case BUILD_CONFIG: - collectConstants(getBuildConfigClass(context, config), result); - break; - case ENVIRONMENT: - collectStaticGettersResults(Environment.class, result); - break; - default: - //will not happen if used correctly - throw new IllegalArgumentException(); - } - target.put(reportField, result); - } - - /** - * Retrieves key/value pairs from static fields of a class. - * - * @param someClass the class to be inspected. - */ - private static void collectConstants(@NonNull Class someClass, @NonNull JSONObject container) throws JSONException { - final Field[] fields = someClass.getFields(); - for (final Field field : fields) { - try { - final Object value = field.get(null); - if (value != null) { - if (field.getType().isArray()) { - container.put(field.getName(), new JSONArray(Arrays.asList((Object[]) value))); - } else { - container.put(field.getName(), value); - } - } - } catch (IllegalArgumentException ignored) { - // NOOP - } catch (IllegalAccessException ignored) { - // NOOP - } - } - } - - /** - * Retrieves key/value pairs from static getters of a class (get*() or is*()). - * - * @param someClass the class to be inspected. - */ - private void collectStaticGettersResults(@NonNull Class someClass, @NonNull JSONObject container) throws JSONException { - final Method[] methods = someClass.getMethods(); - for (final Method method : methods) { - if (method.getParameterTypes().length == 0 - && (method.getName().startsWith("get") || method.getName().startsWith("is")) - && !"getClass".equals(method.getName())) { - try { - container.put(method.getName(), method.invoke(null, (Object[]) null)); - } catch (@NonNull IllegalArgumentException ignored) { - // NOOP - } catch (@NonNull InvocationTargetException ignored) { - // NOOP - } catch (@NonNull IllegalAccessException ignored) { - // NOOP - } - } - } - } - - /** - * get the configured BuildConfigClass or guess it if not configured - * - * @return the BuildConfigClass - * @throws ClassNotFoundException if the class cannot be found - */ - @NonNull - private Class getBuildConfigClass(@NonNull Context context, @NonNull CoreConfiguration config) throws ClassNotFoundException { - final Class configuredBuildConfig = config.getBuildConfigClass(); - if (!configuredBuildConfig.equals(Object.class)) { - // If set via annotations or programmatically then it will have a real value, - // otherwise it will be Object.class (default). - return configuredBuildConfig; - } - - final String className = context.getPackageName() + ".BuildConfig"; - return Class.forName(className); - } -} diff --git a/acra-core/src/main/java/org/acra/collector/ReflectionCollector.kt b/acra-core/src/main/java/org/acra/collector/ReflectionCollector.kt new file mode 100644 index 0000000000..78b3e46331 --- /dev/null +++ b/acra-core/src/main/java/org/acra/collector/ReflectionCollector.kt @@ -0,0 +1,128 @@ +/* + * Copyright 2010 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.collector + +import android.content.Context +import android.os.Build +import android.os.Environment +import com.google.auto.service.AutoService +import org.acra.ReportField +import org.acra.builder.ReportBuilder +import org.acra.config.CoreConfiguration +import org.acra.data.CrashReportData +import org.json.JSONArray +import org.json.JSONException +import org.json.JSONObject +import java.lang.reflect.InvocationTargetException +import java.util.* + +/** + * Collector retrieving key/value pairs from static fields and getters. + * Reflection API usage allows to retrieve data without having to implement a class for each android version of each interesting class. + * It can also help find hidden properties. + * + * @author Kevin Gaudin & F43nd1r + */ +@AutoService(Collector::class) +class ReflectionCollector : BaseReportFieldCollector(ReportField.BUILD, ReportField.BUILD_CONFIG, ReportField.ENVIRONMENT) { + @Throws(JSONException::class, ClassNotFoundException::class) + override fun collect(reportField: ReportField, context: Context, config: CoreConfiguration, reportBuilder: ReportBuilder, target: CrashReportData) { + val result = JSONObject() + when (reportField) { + ReportField.BUILD -> { + collectConstants(Build::class.java, result) + val version = JSONObject() + collectConstants(Build.VERSION::class.java, version) + result.put("VERSION", version) + } + ReportField.BUILD_CONFIG -> collectConstants(getBuildConfigClass(context, config), result) + ReportField.ENVIRONMENT -> collectStaticGettersResults(Environment::class.java, result) + else -> throw IllegalArgumentException() + } + target.put(reportField, result) + } + + /** + * Retrieves key/value pairs from static getters of a class (get*() or is*()). + * + * @param someClass the class to be inspected. + */ + @Throws(JSONException::class) + private fun collectStaticGettersResults(someClass: Class<*>, container: JSONObject) { + val methods = someClass.methods + for (method in methods) { + if (method.parameterTypes.isEmpty() && (method.name.startsWith("get") || method.name.startsWith("is")) + && "getClass" != method.name) { + try { + container.put(method.name, method.invoke(null)) + } catch (ignored: IllegalArgumentException) { + // NOOP + } catch (ignored: InvocationTargetException) { + // NOOP + } catch (ignored: IllegalAccessException) { + // NOOP + } + } + } + } + + /** + * get the configured BuildConfigClass or guess it if not configured + * + * @return the BuildConfigClass + * @throws ClassNotFoundException if the class cannot be found + */ + @Throws(ClassNotFoundException::class) + private fun getBuildConfigClass(context: Context, config: CoreConfiguration): Class<*> { + val configuredBuildConfig: Class<*> = config.buildConfigClass + if (configuredBuildConfig != Any::class.java) { + // If set via annotations or programmatically then it will have a real value, + // otherwise it will be Object.class (default). + return configuredBuildConfig + } + val className = context.packageName + ".BuildConfig" + return Class.forName(className) + } + + companion object { + /** + * Retrieves key/value pairs from static fields of a class. + * + * @param someClass the class to be inspected. + */ + @Throws(JSONException::class) + private fun collectConstants(someClass: Class<*>, container: JSONObject) { + val fields = someClass.fields + for (field in fields) { + try { + val value = field[null] + if (value != null) { + if (field.type.isArray) { + @Suppress("UNCHECKED_CAST") + container.put(field.name, JSONArray(listOf(*value as Array))) + } else { + container.put(field.name, value) + } + } + } catch (ignored: IllegalArgumentException) { + // NOOP + } catch (ignored: IllegalAccessException) { + // NOOP + } + } + } + } +} \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/collector/SettingsCollector.java b/acra-core/src/main/java/org/acra/collector/SettingsCollector.java deleted file mode 100644 index 3c7b52e5a3..0000000000 --- a/acra-core/src/main/java/org/acra/collector/SettingsCollector.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright 2010 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.collector; - -import android.content.ContentResolver; -import android.content.Context; -import android.os.Build; -import android.provider.Settings.Global; -import android.provider.Settings.Secure; -import android.provider.Settings.System; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.google.auto.service.AutoService; - -import org.acra.ACRA; -import org.acra.ReportField; -import org.acra.builder.ReportBuilder; -import org.acra.config.CoreConfiguration; -import org.acra.data.CrashReportData; -import org.json.JSONObject; - -import java.lang.reflect.Field; -import java.lang.reflect.Method; - -import static org.acra.ACRA.LOG_TAG; - -/** - * collects data from {@link System}, {@link Global} and {@link Secure} Settings classes. - * - * @author Kevin Gaudin & F43nd1r - */ -@AutoService(Collector.class) -public final class SettingsCollector extends BaseReportFieldCollector { - - private static final String ERROR = "Error: "; - - public SettingsCollector() { - super(ReportField.SETTINGS_SYSTEM, ReportField.SETTINGS_SECURE, ReportField.SETTINGS_GLOBAL); - } - - @Override - void collect(@NonNull ReportField reportField, @NonNull Context context, @NonNull CoreConfiguration config, @NonNull ReportBuilder reportBuilder, @NonNull CrashReportData target) throws Exception { - switch (reportField) { - case SETTINGS_SYSTEM: - target.put(ReportField.SETTINGS_SYSTEM, collectSettings(context, config, System.class)); - break; - case SETTINGS_SECURE: - target.put(ReportField.SETTINGS_SECURE, collectSettings(context, config, Secure.class)); - break; - case SETTINGS_GLOBAL: - target.put(ReportField.SETTINGS_GLOBAL, Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 ? collectSettings(context, config, Global.class) : null); - break; - default: - //will not happen if used correctly - throw new IllegalArgumentException(); - } - } - - @NonNull - private JSONObject collectSettings(@NonNull Context context, @NonNull CoreConfiguration config, @NonNull Class settings) throws NoSuchMethodException { - final JSONObject result = new JSONObject(); - final Field[] keys = settings.getFields(); - final Method getString = settings.getMethod("getString", ContentResolver.class, String.class); - for (final Field key : keys) { - if (!key.isAnnotationPresent(Deprecated.class) && key.getType() == String.class && isAuthorized(config, key)) { - try { - //noinspection JavaReflectionInvocation - final Object value = getString.invoke(null, context.getContentResolver(), key.get(null)); - if (value != null) { - result.put(key.getName(), value); - } - } catch (@NonNull Exception e) { - ACRA.log.w(LOG_TAG, ERROR, e); - } - } - } - return result; - } - - private boolean isAuthorized(@NonNull CoreConfiguration config, @Nullable Field key) { - if (key == null || key.getName().startsWith("WIFI_AP")) { - return false; - } - for (String regex : config.getExcludeMatchingSettingsKeys()) { - if (key.getName().matches(regex)) { - return false; - } - } - return true; - } -} diff --git a/acra-core/src/main/java/org/acra/collector/SettingsCollector.kt b/acra-core/src/main/java/org/acra/collector/SettingsCollector.kt new file mode 100644 index 0000000000..c124d417af --- /dev/null +++ b/acra-core/src/main/java/org/acra/collector/SettingsCollector.kt @@ -0,0 +1,91 @@ +/* + * Copyright 2010 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.collector + +import android.content.ContentResolver +import android.content.Context +import android.os.Build +import android.provider.Settings +import android.provider.Settings.Secure +import com.google.auto.service.AutoService +import org.acra.ACRA +import org.acra.ReportField +import org.acra.builder.ReportBuilder +import org.acra.config.CoreConfiguration +import org.acra.data.CrashReportData +import org.acra.log.warn +import org.json.JSONObject +import java.lang.Deprecated +import java.lang.reflect.Field +import kotlin.Boolean +import kotlin.String +import kotlin.Throws + +/** + * collects data from [System], [Global] and [Secure] Settings classes. + * + * @author Kevin Gaudin & F43nd1r + */ +@AutoService(Collector::class) +class SettingsCollector : BaseReportFieldCollector(ReportField.SETTINGS_SYSTEM, ReportField.SETTINGS_SECURE, ReportField.SETTINGS_GLOBAL) { + @Throws(Exception::class) + override fun collect(reportField: ReportField, context: Context, config: CoreConfiguration, reportBuilder: ReportBuilder, target: CrashReportData) { + when (reportField) { + ReportField.SETTINGS_SYSTEM -> target.put(ReportField.SETTINGS_SYSTEM, collectSettings(context, config, Settings.System::class.java)) + ReportField.SETTINGS_SECURE -> target.put(ReportField.SETTINGS_SECURE, collectSettings(context, config, Secure::class.java)) + ReportField.SETTINGS_GLOBAL -> target.put( + ReportField.SETTINGS_GLOBAL, + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) collectSettings(context, config, Settings.Global::class.java) else null) + else -> throw IllegalArgumentException() + } + } + + @Throws(NoSuchMethodException::class) + private fun collectSettings(context: Context, config: CoreConfiguration, settings: Class<*>): JSONObject { + val result = JSONObject() + val keys = settings.fields + val getString = settings.getMethod("getString", ContentResolver::class.java, String::class.java) + for (key in keys) { + if (!key.isAnnotationPresent(Deprecated::class.java) && key.type == String::class.java && isAuthorized(config, key)) { + try { + val value = getString.invoke(null, context.contentResolver, key[null]) + if (value != null) { + result.put(key.name, value) + } + } catch (e: Exception) { + warn(e) { ERROR } + } + } + } + return result + } + + private fun isAuthorized(config: CoreConfiguration, key: Field?): Boolean { + if (key == null || key.name.startsWith("WIFI_AP")) { + return false + } + for (regex in config.excludeMatchingSettingsKeys) { + if (key.name.matches(Regex(regex))) { + return false + } + } + return true + } + + companion object { + private const val ERROR = "Error: " + } +} \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/collector/SharedPreferencesCollector.java b/acra-core/src/main/java/org/acra/collector/SharedPreferencesCollector.java deleted file mode 100644 index 414f5b3c81..0000000000 --- a/acra-core/src/main/java/org/acra/collector/SharedPreferencesCollector.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright 2010 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.collector; - -import android.content.Context; -import android.content.SharedPreferences; -import android.preference.PreferenceManager; -import androidx.annotation.NonNull; - -import com.google.auto.service.AutoService; - -import org.acra.ACRA; -import org.acra.ReportField; -import org.acra.builder.ReportBuilder; -import org.acra.config.CoreConfiguration; -import org.acra.data.CrashReportData; -import org.acra.prefs.SharedPreferencesFactory; -import org.json.JSONException; -import org.json.JSONObject; - -import java.util.Iterator; -import java.util.Map; -import java.util.TreeMap; - -/** - * Collects the content (key/value pairs) of SharedPreferences, from the application default preferences or any other preferences asked by the application developer. - * - * @author F43nd1r & Various - */ -@AutoService(Collector.class) -public final class SharedPreferencesCollector extends BaseReportFieldCollector { - - public SharedPreferencesCollector() { - super(ReportField.USER_EMAIL, ReportField.SHARED_PREFERENCES); - } - - @Override - void collect(@NonNull ReportField reportField, @NonNull Context context, @NonNull CoreConfiguration config, @NonNull ReportBuilder reportBuilder, @NonNull CrashReportData target) throws Exception { - switch (reportField) { - case USER_EMAIL: - target.put(ReportField.USER_EMAIL, new SharedPreferencesFactory(context, config).create().getString(ACRA.PREF_USER_EMAIL_ADDRESS, null)); - break; - case SHARED_PREFERENCES: - target.put(ReportField.SHARED_PREFERENCES, collect(context, config)); - break; - default: - //will not happen if used correctly - throw new IllegalArgumentException(); - } - } - - /** - * Collects all key/value pairs in SharedPreferences. - * The application default SharedPreferences are always - * collected, and the developer can provide additional SharedPreferences - * names in the {@link org.acra.annotation.AcraCore#additionalSharedPreferences()} - * configuration item. - * - * @return the collected key/value pairs. - */ - @NonNull - private JSONObject collect(@NonNull Context context, @NonNull CoreConfiguration config) throws JSONException { - final JSONObject result = new JSONObject(); - - // Include the default SharedPreferences - final Map sharedPrefs = new TreeMap<>(); - sharedPrefs.put("default", PreferenceManager.getDefaultSharedPreferences(context)); - - // Add in any additional SharedPreferences - for (final String sharedPrefId : config.getAdditionalSharedPreferences()) { - sharedPrefs.put(sharedPrefId, context.getSharedPreferences(sharedPrefId, Context.MODE_PRIVATE)); - } - - // Iterate over all included preference files and add the preferences from each. - for (Map.Entry entry : sharedPrefs.entrySet()) { - final String sharedPrefId = entry.getKey(); - final SharedPreferences prefs = entry.getValue(); - - final Map prefEntries = prefs.getAll(); - - // Show that we have no preferences saved for that preference file. - if (prefEntries.isEmpty()) { - result.put(sharedPrefId, "empty"); - } else { - for (final Iterator iterator = prefEntries.keySet().iterator(); iterator.hasNext(); ) { - if (filteredKey(config, iterator.next())) { - iterator.remove(); - } - } - result.put(sharedPrefId, new JSONObject(prefEntries)); - } - } - - return result; - } - - /** - * Checks if the key matches one of the patterns provided by the developer - * to exclude some preferences from reports. - * - * @param key the name of the preference to be checked - * @return true if the key has to be excluded from reports. - */ - private boolean filteredKey(@NonNull CoreConfiguration config, @NonNull String key) { - for (String regex : config.getExcludeMatchingSharedPreferencesKeys()) { - if (key.matches(regex)) { - return true; - } - } - return false; - } -} diff --git a/acra-core/src/main/java/org/acra/collector/SharedPreferencesCollector.kt b/acra-core/src/main/java/org/acra/collector/SharedPreferencesCollector.kt new file mode 100644 index 0000000000..3bf5ea1a43 --- /dev/null +++ b/acra-core/src/main/java/org/acra/collector/SharedPreferencesCollector.kt @@ -0,0 +1,106 @@ +/* + * Copyright 2010 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.collector + +import android.content.Context +import android.content.SharedPreferences +import android.preference.PreferenceManager +import com.google.auto.service.AutoService +import org.acra.ACRA +import org.acra.ReportField +import org.acra.builder.ReportBuilder +import org.acra.config.CoreConfiguration +import org.acra.data.CrashReportData +import org.acra.prefs.SharedPreferencesFactory +import org.json.JSONException +import org.json.JSONObject +import java.util.* + +/** + * Collects the content (key/value pairs) of SharedPreferences, from the application default preferences or any other preferences asked by the application developer. + * + * @author F43nd1r & Various + */ +@AutoService(Collector::class) +class SharedPreferencesCollector : BaseReportFieldCollector(ReportField.USER_EMAIL, ReportField.SHARED_PREFERENCES) { + @Throws(Exception::class) + override fun collect(reportField: ReportField, context: Context, config: CoreConfiguration, reportBuilder: ReportBuilder, target: CrashReportData) { + when (reportField) { + ReportField.USER_EMAIL -> target.put(ReportField.USER_EMAIL, SharedPreferencesFactory(context, config).create().getString(ACRA.PREF_USER_EMAIL_ADDRESS, null)) + ReportField.SHARED_PREFERENCES -> target.put(ReportField.SHARED_PREFERENCES, collect(context, config)) + else -> throw IllegalArgumentException() + } + } + + /** + * Collects all key/value pairs in SharedPreferences. + * The application default SharedPreferences are always + * collected, and the developer can provide additional SharedPreferences + * names in the [org.acra.annotation.AcraCore.additionalSharedPreferences] + * configuration item. + * + * @return the collected key/value pairs. + */ + @Suppress("DEPRECATION") + @Throws(JSONException::class) + private fun collect(context: Context, config: CoreConfiguration): JSONObject { + val result = JSONObject() + + // Include the default SharedPreferences + val sharedPrefs: MutableMap = TreeMap() + sharedPrefs["default"] = PreferenceManager.getDefaultSharedPreferences(context) + + // Add in any additional SharedPreferences + for (sharedPrefId in config.additionalSharedPreferences) { + sharedPrefs[sharedPrefId] = context.getSharedPreferences(sharedPrefId, Context.MODE_PRIVATE) + } + + // Iterate over all included preference files and add the preferences from each. + for ((sharedPrefId, prefs) in sharedPrefs) { + val prefEntries = prefs.all + + // Show that we have no preferences saved for that preference file. + if (prefEntries.isEmpty()) { + result.put(sharedPrefId, "empty") + } else { + val iterator = prefEntries.keys.iterator() + while (iterator.hasNext()) { + if (filteredKey(config, iterator.next())) { + iterator.remove() + } + } + result.put(sharedPrefId, JSONObject(prefEntries)) + } + } + return result + } + + /** + * Checks if the key matches one of the patterns provided by the developer + * to exclude some preferences from reports. + * + * @param key the name of the preference to be checked + * @return true if the key has to be excluded from reports. + */ + private fun filteredKey(config: CoreConfiguration, key: String): Boolean { + for (regex in config.excludeMatchingSharedPreferencesKeys) { + if (key.matches(Regex(regex))) { + return true + } + } + return false + } +} \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/collector/SimpleValuesCollector.java b/acra-core/src/main/java/org/acra/collector/SimpleValuesCollector.java deleted file mode 100644 index 531d4cd6f5..0000000000 --- a/acra-core/src/main/java/org/acra/collector/SimpleValuesCollector.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright 2016 - * - * 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.collector; - -import android.content.Context; -import android.os.Build; -import androidx.annotation.NonNull; - -import com.google.auto.service.AutoService; - -import org.acra.ReportField; -import org.acra.builder.ReportBuilder; -import org.acra.config.CoreConfiguration; -import org.acra.data.CrashReportData; -import org.acra.util.Installation; - -import java.net.InetAddress; -import java.net.NetworkInterface; -import java.net.SocketException; -import java.util.Enumeration; -import java.util.UUID; - -/** - * Collects various simple values - * - * @author F43nd1r - * @since 4.9.1 - */ -@AutoService(Collector.class) -public final class SimpleValuesCollector extends BaseReportFieldCollector { - - public SimpleValuesCollector() { - super(ReportField.IS_SILENT, ReportField.REPORT_ID, ReportField.INSTALLATION_ID, - ReportField.PACKAGE_NAME, ReportField.PHONE_MODEL, ReportField.ANDROID_VERSION, - ReportField.BRAND, ReportField.PRODUCT, ReportField.FILE_PATH, ReportField.USER_IP); - } - - @Override - void collect(@NonNull ReportField reportField, @NonNull Context context, @NonNull CoreConfiguration config, @NonNull ReportBuilder reportBuilder, @NonNull CrashReportData target) throws Exception { - switch (reportField) { - case IS_SILENT: - target.put(ReportField.IS_SILENT, reportBuilder.isSendSilently()); - break; - case REPORT_ID: - target.put(ReportField.REPORT_ID, UUID.randomUUID().toString()); - break; - case INSTALLATION_ID: - target.put(ReportField.INSTALLATION_ID, Installation.id(context)); - break; - case PACKAGE_NAME: - target.put(ReportField.PACKAGE_NAME, context.getPackageName()); - break; - case PHONE_MODEL: - target.put(ReportField.PHONE_MODEL, Build.MODEL); - break; - case ANDROID_VERSION: - target.put(ReportField.ANDROID_VERSION, Build.VERSION.RELEASE); - break; - case BRAND: - target.put(ReportField.BRAND, Build.BRAND); - break; - case PRODUCT: - target.put(ReportField.PRODUCT, Build.PRODUCT); - break; - case FILE_PATH: - target.put(ReportField.FILE_PATH, getApplicationFilePath(context)); - break; - case USER_IP: - target.put(ReportField.USER_IP, getLocalIpAddress()); - break; - default: - //will not happen if used correctly - throw new IllegalArgumentException(); - } - } - - @Override - boolean shouldCollect(@NonNull Context context, @NonNull CoreConfiguration config, @NonNull ReportField collect, @NonNull ReportBuilder reportBuilder) { - return collect == ReportField.IS_SILENT || collect == ReportField.REPORT_ID || super.shouldCollect(context, config, collect, reportBuilder); - } - - @NonNull - private String getApplicationFilePath(@NonNull Context context) { - return context.getFilesDir().getAbsolutePath(); - } - - @NonNull - private static String getLocalIpAddress() throws SocketException { - final StringBuilder result = new StringBuilder(); - boolean first = true; - for (final Enumeration en = NetworkInterface.getNetworkInterfaces(); en.hasMoreElements(); ) { - final NetworkInterface intf = en.nextElement(); - for (final Enumeration enumIpAddr = intf.getInetAddresses(); enumIpAddr.hasMoreElements(); ) { - final InetAddress inetAddress = enumIpAddr.nextElement(); - if (!inetAddress.isLoopbackAddress()) { - if (!first) { - result.append('\n'); - } - result.append(inetAddress.getHostAddress()); - first = false; - } - } - } - return result.toString(); - } -} diff --git a/acra-core/src/main/java/org/acra/collector/SimpleValuesCollector.kt b/acra-core/src/main/java/org/acra/collector/SimpleValuesCollector.kt new file mode 100644 index 0000000000..cbd4244410 --- /dev/null +++ b/acra-core/src/main/java/org/acra/collector/SimpleValuesCollector.kt @@ -0,0 +1,88 @@ +/* + * Copyright 2016 + * + * 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.collector + +import android.content.Context +import android.os.Build +import com.google.auto.service.AutoService +import org.acra.ReportField +import org.acra.builder.ReportBuilder +import org.acra.config.CoreConfiguration +import org.acra.data.CrashReportData +import org.acra.util.Installation.id +import java.net.NetworkInterface +import java.net.SocketException +import java.util.* + +/** + * Collects various simple values + * + * @author F43nd1r + * @since 4.9.1 + */ +@AutoService(Collector::class) +class SimpleValuesCollector : BaseReportFieldCollector(ReportField.IS_SILENT, ReportField.REPORT_ID, ReportField.INSTALLATION_ID, + ReportField.PACKAGE_NAME, ReportField.PHONE_MODEL, ReportField.ANDROID_VERSION, + ReportField.BRAND, ReportField.PRODUCT, ReportField.FILE_PATH, ReportField.USER_IP) { + @Throws(Exception::class) + override fun collect(reportField: ReportField, context: Context, config: CoreConfiguration, reportBuilder: ReportBuilder, target: CrashReportData) { + when (reportField) { + ReportField.IS_SILENT -> target.put(ReportField.IS_SILENT, reportBuilder.isSendSilently) + ReportField.REPORT_ID -> target.put(ReportField.REPORT_ID, UUID.randomUUID().toString()) + ReportField.INSTALLATION_ID -> target.put(ReportField.INSTALLATION_ID, id(context)) + ReportField.PACKAGE_NAME -> target.put(ReportField.PACKAGE_NAME, context.packageName) + ReportField.PHONE_MODEL -> target.put(ReportField.PHONE_MODEL, Build.MODEL) + ReportField.ANDROID_VERSION -> target.put(ReportField.ANDROID_VERSION, Build.VERSION.RELEASE) + ReportField.BRAND -> target.put(ReportField.BRAND, Build.BRAND) + ReportField.PRODUCT -> target.put(ReportField.PRODUCT, Build.PRODUCT) + ReportField.FILE_PATH -> target.put(ReportField.FILE_PATH, getApplicationFilePath(context)) + ReportField.USER_IP -> target.put(ReportField.USER_IP, getLocalIpAddress()) + else -> throw IllegalArgumentException() + } + } + + override fun shouldCollect(context: Context, config: CoreConfiguration, collect: ReportField, reportBuilder: ReportBuilder): Boolean { + return collect == ReportField.IS_SILENT || collect == ReportField.REPORT_ID || super.shouldCollect(context, config, collect, reportBuilder) + } + + private fun getApplicationFilePath(context: Context): String { + return context.filesDir.absolutePath + } + + companion object { + @Throws(SocketException::class) + private fun getLocalIpAddress(): String { + val result = StringBuilder() + var first = true + val en = NetworkInterface.getNetworkInterfaces() + while (en.hasMoreElements()) { + val intf = en.nextElement() + val enumIpAddr = intf.inetAddresses + while (enumIpAddr.hasMoreElements()) { + val inetAddress = enumIpAddr.nextElement() + if (!inetAddress.isLoopbackAddress) { + if (!first) { + result.append('\n') + } + result.append(inetAddress.hostAddress) + first = false + } + } + } + return result.toString() + } + } +} \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/collector/StacktraceCollector.java b/acra-core/src/main/java/org/acra/collector/StacktraceCollector.java deleted file mode 100644 index 5dee06be74..0000000000 --- a/acra-core/src/main/java/org/acra/collector/StacktraceCollector.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright 2016 - * - * 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.collector; - -import android.content.Context; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import android.text.TextUtils; - -import com.google.auto.service.AutoService; - -import org.acra.ReportField; -import org.acra.builder.ReportBuilder; -import org.acra.config.CoreConfiguration; -import org.acra.data.CrashReportData; - -import java.io.PrintWriter; -import java.io.StringWriter; -import java.io.Writer; - -/** - * Collects the holy stacktrace - * - * @author F43nd1r - * @since 4.9.1 - */ -@AutoService(Collector.class) -public final class StacktraceCollector extends BaseReportFieldCollector { - public StacktraceCollector() { - super(ReportField.STACK_TRACE, ReportField.STACK_TRACE_HASH); - } - - @NonNull - @Override - public Order getOrder() { - return Order.FIRST; - } - - @Override - void collect(@NonNull ReportField reportField, @NonNull Context context, @NonNull CoreConfiguration config, @NonNull ReportBuilder reportBuilder, @NonNull CrashReportData target) { - switch (reportField) { - case STACK_TRACE: - target.put(ReportField.STACK_TRACE, getStackTrace(reportBuilder.getMessage(), reportBuilder.getException())); - break; - case STACK_TRACE_HASH: - target.put(ReportField.STACK_TRACE_HASH, getStackTraceHash(reportBuilder.getException())); - break; - default: - //will not happen if used correctly - throw new IllegalArgumentException(); - } - } - - @Override - boolean shouldCollect(@NonNull Context context, @NonNull CoreConfiguration config, @NonNull ReportField collect, @NonNull ReportBuilder reportBuilder) { - return collect == ReportField.STACK_TRACE || super.shouldCollect(context, config, collect, reportBuilder); - } - - @NonNull - private String getStackTrace(@Nullable String msg, @Nullable Throwable th) { - final Writer result = new StringWriter(); - try (final PrintWriter printWriter = new PrintWriter(result)) { - if (msg != null && !TextUtils.isEmpty(msg)) { - printWriter.println(msg); - } - if (th != null) { - th.printStackTrace(printWriter); - } - return result.toString(); - } - } - - @NonNull - private String getStackTraceHash(@Nullable Throwable th) { - final StringBuilder res = new StringBuilder(); - Throwable cause = th; - while (cause != null) { - final StackTraceElement[] stackTraceElements = cause.getStackTrace(); - for (final StackTraceElement e : stackTraceElements) { - res.append(e.getClassName()); - res.append(e.getMethodName()); - } - cause = cause.getCause(); - } - - return Integer.toHexString(res.toString().hashCode()); - } -} diff --git a/acra-core/src/main/java/org/acra/collector/StacktraceCollector.kt b/acra-core/src/main/java/org/acra/collector/StacktraceCollector.kt new file mode 100644 index 0000000000..809fd9f83b --- /dev/null +++ b/acra-core/src/main/java/org/acra/collector/StacktraceCollector.kt @@ -0,0 +1,76 @@ +/* + * Copyright 2016 + * + * 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.collector + +import android.content.Context +import android.text.TextUtils +import com.google.auto.service.AutoService +import org.acra.ReportField +import org.acra.builder.ReportBuilder +import org.acra.config.CoreConfiguration +import org.acra.data.CrashReportData +import java.io.PrintWriter +import java.io.StringWriter +import java.io.Writer + +/** + * Collects the holy stacktrace + * + * @author F43nd1r + * @since 4.9.1 + */ +@AutoService(Collector::class) +class StacktraceCollector : BaseReportFieldCollector(ReportField.STACK_TRACE, ReportField.STACK_TRACE_HASH) { + override val order: Collector.Order + get() = Collector.Order.FIRST + + override fun collect(reportField: ReportField, context: Context, config: CoreConfiguration, reportBuilder: ReportBuilder, target: CrashReportData) { + when (reportField) { + ReportField.STACK_TRACE -> target.put(ReportField.STACK_TRACE, getStackTrace(reportBuilder.message, reportBuilder.exception)) + ReportField.STACK_TRACE_HASH -> target.put(ReportField.STACK_TRACE_HASH, getStackTraceHash(reportBuilder.exception)) + else -> throw IllegalArgumentException() + } + } + + override fun shouldCollect(context: Context, config: CoreConfiguration, collect: ReportField, reportBuilder: ReportBuilder): Boolean { + return collect == ReportField.STACK_TRACE || super.shouldCollect(context, config, collect, reportBuilder) + } + + private fun getStackTrace(msg: String?, th: Throwable?): String { + val result: Writer = StringWriter() + PrintWriter(result).use { printWriter -> + if (msg != null && !TextUtils.isEmpty(msg)) { + printWriter.println(msg) + } + th?.printStackTrace(printWriter) + return result.toString() + } + } + + private fun getStackTraceHash(th: Throwable?): String { + val res = StringBuilder() + var cause = th + while (cause != null) { + val stackTraceElements = cause.stackTrace + for (e in stackTraceElements) { + res.append(e.className) + res.append(e.methodName) + } + cause = cause.cause + } + return Integer.toHexString(res.toString().hashCode()) + } +} \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/collector/ThreadCollector.java b/acra-core/src/main/java/org/acra/collector/ThreadCollector.java deleted file mode 100644 index 4cdae5247f..0000000000 --- a/acra-core/src/main/java/org/acra/collector/ThreadCollector.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2010 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.collector; - -import android.content.Context; -import androidx.annotation.NonNull; - -import com.google.auto.service.AutoService; - -import org.acra.ReportField; -import org.acra.builder.ReportBuilder; -import org.acra.config.CoreConfiguration; -import org.acra.data.CrashReportData; -import org.json.JSONObject; - -/** - * Collects some data identifying a Thread - * - * @author Kevin Gaudin & F43nd1r - */ -@AutoService(Collector.class) -public final class ThreadCollector extends BaseReportFieldCollector { - public ThreadCollector() { - super(ReportField.THREAD_DETAILS); - } - - @NonNull - @Override - public Order getOrder() { - return Order.LATE; - } - - @Override - void collect(@NonNull ReportField reportField, @NonNull Context context, @NonNull CoreConfiguration config, @NonNull ReportBuilder reportBuilder, @NonNull CrashReportData target) throws Exception { - final Thread t = reportBuilder.getUncaughtExceptionThread(); - if (t != null) { - final JSONObject result = new JSONObject(); - result.put("id", t.getId()); - result.put("name", t.getName()); - result.put("priority", t.getPriority()); - if (t.getThreadGroup() != null) { - result.put("groupName", t.getThreadGroup().getName()); - } - target.put(ReportField.THREAD_DETAILS, result); - } else { - target.put(ReportField.THREAD_DETAILS, (String) null); - } - } -} diff --git a/acra-core/src/main/java/org/acra/collector/ThreadCollector.kt b/acra-core/src/main/java/org/acra/collector/ThreadCollector.kt new file mode 100644 index 0000000000..3c876bb08d --- /dev/null +++ b/acra-core/src/main/java/org/acra/collector/ThreadCollector.kt @@ -0,0 +1,50 @@ +/* + * Copyright 2010 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.collector + +import android.content.Context +import com.google.auto.service.AutoService +import org.acra.ReportField +import org.acra.builder.ReportBuilder +import org.acra.config.CoreConfiguration +import org.acra.data.CrashReportData +import org.json.JSONObject + +/** + * Collects some data identifying a Thread + * + * @author Kevin Gaudin & F43nd1r + */ +@AutoService(Collector::class) +class ThreadCollector : BaseReportFieldCollector(ReportField.THREAD_DETAILS) { + override val order: Collector.Order + get() = Collector.Order.LATE + + @Throws(Exception::class) + override fun collect(reportField: ReportField, context: Context, config: CoreConfiguration, reportBuilder: ReportBuilder, target: CrashReportData) { + val t = reportBuilder.uncaughtExceptionThread + if (t != null) { + val result = JSONObject() + result.put("id", t.id) + result.put("name", t.name) + result.put("priority", t.priority) + t.threadGroup?.let { result.put("groupName", it) } + target.put(ReportField.THREAD_DETAILS, result) + } else { + target.put(ReportField.THREAD_DETAILS, null as String?) + } + } +} \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/collector/TimeCollector.java b/acra-core/src/main/java/org/acra/collector/TimeCollector.java deleted file mode 100644 index 582b8a59fb..0000000000 --- a/acra-core/src/main/java/org/acra/collector/TimeCollector.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2016 - * - * 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.collector; - -import android.content.Context; -import androidx.annotation.NonNull; - -import com.google.auto.service.AutoService; - -import org.acra.ACRAConstants; -import org.acra.ReportField; -import org.acra.builder.ReportBuilder; -import org.acra.config.CoreConfiguration; -import org.acra.data.CrashReportData; - -import java.text.SimpleDateFormat; -import java.util.Calendar; -import java.util.GregorianCalendar; -import java.util.Locale; - -/** - * collects time information - * - * @author F43nd1r - * @since 4.9.1 - */ -@AutoService(Collector.class) -public final class TimeCollector extends BaseReportFieldCollector implements ApplicationStartupCollector { - private final SimpleDateFormat dateFormat; - private Calendar appStartDate; - - public TimeCollector() { - super(ReportField.USER_APP_START_DATE, ReportField.USER_CRASH_DATE); - dateFormat = new SimpleDateFormat(ACRAConstants.DATE_TIME_FORMAT_STRING, Locale.ENGLISH); - } - - @Override - void collect(@NonNull ReportField reportField, @NonNull Context context, @NonNull CoreConfiguration config, @NonNull ReportBuilder reportBuilder, @NonNull CrashReportData target) { - final Calendar time; - switch (reportField) { - case USER_APP_START_DATE: - time = appStartDate; - break; - case USER_CRASH_DATE: - time = new GregorianCalendar(); - break; - default: - //will not happen if used correctly - throw new IllegalArgumentException(); - } - target.put(reportField, getTimeString(time)); - } - - @Override - public void collectApplicationStartUp(@NonNull Context context, @NonNull CoreConfiguration config) { - if (config.getReportContent().contains(ReportField.USER_APP_START_DATE)) { - appStartDate = new GregorianCalendar(); - } - } - - @Override - boolean shouldCollect(@NonNull Context context, @NonNull CoreConfiguration config, @NonNull ReportField collect, @NonNull ReportBuilder reportBuilder) { - return collect == ReportField.USER_CRASH_DATE || super.shouldCollect(context, config, collect, reportBuilder); - } - - @NonNull - private String getTimeString(@NonNull Calendar time) { - return dateFormat.format(time.getTimeInMillis()); - } -} diff --git a/acra-core/src/main/java/org/acra/collector/TimeCollector.kt b/acra-core/src/main/java/org/acra/collector/TimeCollector.kt new file mode 100644 index 0000000000..b3265fe328 --- /dev/null +++ b/acra-core/src/main/java/org/acra/collector/TimeCollector.kt @@ -0,0 +1,61 @@ +/* + * Copyright 2016 + * + * 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.collector + +import android.content.Context +import com.google.auto.service.AutoService +import org.acra.ACRAConstants +import org.acra.ReportField +import org.acra.builder.ReportBuilder +import org.acra.config.CoreConfiguration +import org.acra.data.CrashReportData +import java.text.SimpleDateFormat +import java.util.* + +/** + * collects time information + * + * @author F43nd1r + * @since 4.9.1 + */ +@AutoService(Collector::class) +class TimeCollector : BaseReportFieldCollector(ReportField.USER_APP_START_DATE, ReportField.USER_CRASH_DATE), ApplicationStartupCollector { + private val dateFormat: SimpleDateFormat = SimpleDateFormat(ACRAConstants.DATE_TIME_FORMAT_STRING, Locale.ENGLISH) + private var appStartDate: Calendar? = null + override fun collect(reportField: ReportField, context: Context, config: CoreConfiguration, reportBuilder: ReportBuilder, target: CrashReportData) { + val time: Calendar? = when (reportField) { + ReportField.USER_APP_START_DATE -> appStartDate + ReportField.USER_CRASH_DATE -> GregorianCalendar() + else -> throw IllegalArgumentException() + } + target.put(reportField, getTimeString(time!!)) + } + + override fun collectApplicationStartUp(context: Context, config: CoreConfiguration) { + if (config.reportContent.contains(ReportField.USER_APP_START_DATE)) { + appStartDate = GregorianCalendar() + } + } + + override fun shouldCollect(context: Context, config: CoreConfiguration, collect: ReportField, reportBuilder: ReportBuilder): Boolean { + return collect == ReportField.USER_CRASH_DATE || super.shouldCollect(context, config, collect, reportBuilder) + } + + private fun getTimeString(time: Calendar): String { + return dateFormat.format(time.timeInMillis) + } + +} \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/config/BaseCoreConfigurationBuilder.java b/acra-core/src/main/java/org/acra/config/BaseCoreConfigurationBuilder.java deleted file mode 100644 index 95f500b233..0000000000 --- a/acra-core/src/main/java/org/acra/config/BaseCoreConfigurationBuilder.java +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Copyright 2011 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.config; - -import android.content.Context; -import androidx.annotation.NonNull; -import org.acra.ACRA; -import org.acra.ReportField; -import org.acra.annotation.BuilderMethod; -import org.acra.annotation.ConfigurationValue; -import org.acra.annotation.PreBuild; -import org.acra.annotation.Transform; -import org.acra.plugins.PluginLoader; -import org.acra.plugins.ServicePluginLoader; -import org.acra.util.StubCreator; - -import java.util.*; - -import static org.acra.ACRA.LOG_TAG; -import static org.acra.ACRAConstants.DEFAULT_REPORT_FIELDS; - - -/** - * Contains builder methods which can't be generated - * - * @author F43nd1r - */ -@SuppressWarnings("WeakerAccess") -public final class BaseCoreConfigurationBuilder { - - private final Map reportContentChanges; - private final Context app; - private List configurationBuilders; - private List configurations; - private PluginLoader pluginLoader; - - BaseCoreConfigurationBuilder(@NonNull Context app) { - this.app = app; - reportContentChanges = new EnumMap<>(ReportField.class); - pluginLoader = new ServicePluginLoader(); - } - - private List configurationBuilders() { - if (configurationBuilders == null) { - List factories = pluginLoader.load(ConfigurationBuilderFactory.class); - if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "Found ConfigurationBuilderFactories : " + factories); - configurationBuilders = new ArrayList<>(factories.size()); - for (ConfigurationBuilderFactory factory : factories) { - configurationBuilders.add(factory.create(app)); - } - } - return configurationBuilders; - } - - /** - * Set a custom plugin loader. Note: Call this before any call to {@link #getPluginConfigurationBuilder(Class)} - * - * @param pluginLoader the custom implementation - */ - @BuilderMethod - public void setPluginLoader(@NonNull PluginLoader pluginLoader) { - this.pluginLoader = pluginLoader; - } - - @NonNull - @ConfigurationValue - PluginLoader pluginLoader() { - return pluginLoader; - } - - @PreBuild - void preBuild() throws ACRAConfigurationException { - configurations = new ArrayList<>(); - final List builders = configurationBuilders(); - if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "Found ConfigurationBuilders : " + builders); - for (ConfigurationBuilder builder : builders) { - configurations.add(builder.build()); - } - } - - @NonNull - @Transform(methodName = "reportContent") - List transformReportContent(@NonNull ReportField[] reportFields) { - final List reportContent = new ArrayList<>(); - if (reportFields.length != 0) { - if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "Using custom Report Fields"); - reportContent.addAll(Arrays.asList(reportFields)); - } else { - if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "Using default Report Fields"); - reportContent.addAll(Arrays.asList(DEFAULT_REPORT_FIELDS)); - } - - // Add or remove any extra fields. - for (Map.Entry entry : reportContentChanges.entrySet()) { - if (entry.getValue()) { - reportContent.add(entry.getKey()); - } else { - reportContent.remove(entry.getKey()); - } - } - return reportContent; - } - - /** - * Use this if you want to keep the default configuration of reportContent, but set some fields explicitly. - * - * @param field the field to set - * @param enable if this field should be reported - */ - @BuilderMethod - public void setReportField(@NonNull ReportField field, boolean enable) { - this.reportContentChanges.put(field, enable); - } - - @ConfigurationValue - @NonNull - List pluginConfigurations() { - return configurations; - } - - @NonNull - @BuilderMethod - public R getPluginConfigurationBuilder(@NonNull Class c) { - for (ConfigurationBuilder builder : configurationBuilders()) { - if (c.isAssignableFrom(builder.getClass())) { - //noinspection unchecked - return (R) builder; - } - } - if (c.isInterface()) { - ACRA.log.w(LOG_TAG, "Couldn't find ConfigurationBuilder " + c.getSimpleName() + ". ALL CALLS TO IT WILL BE IGNORED!"); - return StubCreator.createStub(c, (proxy, method, args) -> proxy); - } - throw new IllegalArgumentException("Class " + c.getName() + " is not a registered ConfigurationBuilder"); - } -} diff --git a/acra-core/src/main/java/org/acra/config/BaseCoreConfigurationBuilder.kt b/acra-core/src/main/java/org/acra/config/BaseCoreConfigurationBuilder.kt new file mode 100644 index 0000000000..833b2095e7 --- /dev/null +++ b/acra-core/src/main/java/org/acra/config/BaseCoreConfigurationBuilder.kt @@ -0,0 +1,128 @@ +/* + * Copyright 2011 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.config + +import android.content.Context +import org.acra.ACRAConstants +import org.acra.ReportField +import org.acra.annotation.BuilderMethod +import org.acra.annotation.ConfigurationValue +import org.acra.annotation.PreBuild +import org.acra.annotation.Transform +import org.acra.log.debug +import org.acra.log.warn +import org.acra.plugins.PluginLoader +import org.acra.plugins.ServicePluginLoader +import org.acra.util.StubCreator.createStub +import java.lang.reflect.Method +import java.util.* + +/** + * Contains builder methods which can't be generated + * + * @author F43nd1r + */ +class BaseCoreConfigurationBuilder internal constructor(private val app: Context) { + private val reportContentChanges: MutableMap = EnumMap(ReportField::class.java) + private lateinit var configBuilders: List + private lateinit var configurations: List + private var pluginLoader: PluginLoader = ServicePluginLoader() + + private fun configurationBuilders(): List { + if (!this::configBuilders.isInitialized) { + val factories = pluginLoader.load(ConfigurationBuilderFactory::class.java) + debug { "Found ConfigurationBuilderFactories : $factories" } + configBuilders = factories.map { it.create(app) } + } + return configBuilders + } + + /** + * Set a custom plugin loader. Note: Call this before any call to [.getPluginConfigurationBuilder] + * + * @param pluginLoader the custom implementation + */ + @BuilderMethod + fun setPluginLoader(pluginLoader: PluginLoader) { + this.pluginLoader = pluginLoader + } + + @ConfigurationValue + fun pluginLoader(): PluginLoader { + return pluginLoader + } + + @PreBuild + @Throws(ACRAConfigurationException::class) + fun preBuild() { + val builders = configurationBuilders() + debug { "Found ConfigurationBuilders : $builders" } + configurations = builders.map { it.build() } + } + + @Transform(methodName = "reportContent") + fun transformReportContent(reportFields: Array): List { + val reportContent: MutableList = ArrayList() + if (reportFields.isNotEmpty()) { + debug { "Using custom Report Fields" } + reportContent.addAll(reportFields) + } else { + debug { "Using default Report Fields" } + reportContent.addAll(ACRAConstants.DEFAULT_REPORT_FIELDS) + } + + // Add or remove any extra fields. + for ((key, value) in reportContentChanges) { + if (value) { + reportContent.add(key) + } else { + reportContent.remove(key) + } + } + return reportContent + } + + /** + * Use this if you want to keep the default configuration of reportContent, but set some fields explicitly. + * + * @param field the field to set + * @param enable if this field should be reported + */ + @BuilderMethod + fun setReportField(field: ReportField, enable: Boolean) { + reportContentChanges[field] = enable + } + + @ConfigurationValue + fun pluginConfigurations(): List { + return configurations + } + + @BuilderMethod + fun getPluginConfigurationBuilder(c: Class): R { + for (builder in configurationBuilders()) { + if (c.isAssignableFrom(builder.javaClass)) { + @Suppress("UNCHECKED_CAST") + return builder as R + } + } + if (c.isInterface) { + warn { "Couldn't find ConfigurationBuilder ${c.simpleName}. ALL CALLS TO IT WILL BE IGNORED!" } + return createStub(c) { proxy, _, _ -> proxy } + } + throw IllegalArgumentException("Class ${c.name} is not a registered ConfigurationBuilder") + } +} \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/config/ConfigUtils.java b/acra-core/src/main/java/org/acra/config/ConfigUtils.kt similarity index 53% rename from acra-core/src/main/java/org/acra/config/ConfigUtils.java rename to acra-core/src/main/java/org/acra/config/ConfigUtils.kt index 4d4589e1a2..897eab45c2 100644 --- a/acra-core/src/main/java/org/acra/config/ConfigUtils.java +++ b/acra-core/src/main/java/org/acra/config/ConfigUtils.kt @@ -13,11 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +package org.acra.config -package org.acra.config; - -import androidx.annotation.NonNull; -import org.acra.ACRA; +import org.acra.ACRA /** * Allows easy access to Plugin configurations from the main configuration @@ -25,19 +23,17 @@ * @author F43nd1r * @since 01.06.2017 */ -public final class ConfigUtils { - - @NonNull - public static T getPluginConfiguration(@NonNull CoreConfiguration config, @NonNull Class c) { - if (ACRA.DEV_LOGGING) ACRA.log.d(ACRA.LOG_TAG, "Checking plugin Configurations : " + config.getPluginConfigurations() + " for class : " + c); - for (Configuration configuration : config.getPluginConfigurations()) { - if (ACRA.DEV_LOGGING) ACRA.log.d(ACRA.LOG_TAG, "Checking plugin Configuration : " + configuration + " against plugin class : " + c); - if (c.isAssignableFrom(configuration.getClass())) { - //noinspection unchecked - return (T) configuration; +object ConfigUtils { + @JvmStatic + fun getPluginConfiguration(config: CoreConfiguration, c: Class): T { + if (ACRA.DEV_LOGGING) ACRA.log.d(ACRA.LOG_TAG, "Checking plugin Configurations : " + config.pluginConfigurations.toString() + " for class : " + c) + for (configuration in config.pluginConfigurations) { + if (ACRA.DEV_LOGGING) ACRA.log.d(ACRA.LOG_TAG, "Checking plugin Configuration : $configuration against plugin class : $c") + if (c.isAssignableFrom(configuration.javaClass)) { + @Suppress("UNCHECKED_CAST") + return configuration as T } } - throw new IllegalArgumentException(c.getName() + " is no registered configuration"); + throw IllegalArgumentException("${c.name} is no registered configuration") } - -} +} \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/config/ConfigurationBuilderFactory.java b/acra-core/src/main/java/org/acra/config/ConfigurationBuilderFactory.kt similarity index 77% rename from acra-core/src/main/java/org/acra/config/ConfigurationBuilderFactory.java rename to acra-core/src/main/java/org/acra/config/ConfigurationBuilderFactory.kt index 69241476c2..002d2db005 100644 --- a/acra-core/src/main/java/org/acra/config/ConfigurationBuilderFactory.java +++ b/acra-core/src/main/java/org/acra/config/ConfigurationBuilderFactory.kt @@ -13,12 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +package org.acra.config -package org.acra.config; - -import android.content.Context; -import androidx.annotation.NonNull; -import org.acra.plugins.Plugin; +import android.content.Context +import org.acra.plugins.Plugin /** * A factory for configuration builders @@ -26,13 +24,12 @@ * @author F43nd1r * @since 01.06.2017 */ -public interface ConfigurationBuilderFactory extends Plugin { +interface ConfigurationBuilderFactory : Plugin { /** * creates a new builder * * @param annotatedContext the context holding the annotation from which the builder should pull its values * @return a new builder with values from the annotation */ - @NonNull - ConfigurationBuilder create(@NonNull Context annotatedContext); -} + fun create(annotatedContext: Context): ConfigurationBuilder +} \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/config/DefaultRetryPolicy.java b/acra-core/src/main/java/org/acra/config/DefaultRetryPolicy.kt similarity index 58% rename from acra-core/src/main/java/org/acra/config/DefaultRetryPolicy.java rename to acra-core/src/main/java/org/acra/config/DefaultRetryPolicy.kt index febb8e131f..f4ec575e8c 100644 --- a/acra-core/src/main/java/org/acra/config/DefaultRetryPolicy.java +++ b/acra-core/src/main/java/org/acra/config/DefaultRetryPolicy.kt @@ -13,23 +13,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.acra.config; +package org.acra.config -import androidx.annotation.NonNull; - -import org.acra.sender.ReportSender; - -import java.util.List; +import org.acra.config.RetryPolicy.FailedSender +import org.acra.sender.ReportSender /** - * Default {@link RetryPolicy}. Only resend if all senders failed. + * Default [RetryPolicy]. Only resend if all senders failed. * * @author F43nd1r * @since 4.9.1 */ -public class DefaultRetryPolicy implements RetryPolicy { - @Override - public boolean shouldRetrySend(@NonNull List senders, @NonNull List failedSenders) { - return (senders.size() == failedSenders.size()) && !senders.isEmpty(); +class DefaultRetryPolicy : RetryPolicy { + override fun shouldRetrySend(senders: List, failedSenders: List): Boolean { + return senders.size == failedSenders.size && senders.isNotEmpty() } -} +} \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/config/KtCoreConfiguration.kt b/acra-core/src/main/java/org/acra/config/KtCoreConfiguration.kt deleted file mode 100644 index 995c79b17c..0000000000 --- a/acra-core/src/main/java/org/acra/config/KtCoreConfiguration.kt +++ /dev/null @@ -1,284 +0,0 @@ -package org.acra.config - -import androidx.annotation.StringRes -import org.acra.ACRAConstants -import org.acra.ReportField -import org.acra.annotation.Instantiatable -import org.acra.attachment.AttachmentUriProvider -import org.acra.attachment.DefaultAttachmentProvider -import org.acra.data.StringFormat -import org.acra.file.Directory - -data class KtCoreConfiguration( - - /** - * Name of the SharedPreferences that will host ACRA settings which you can make accessible to your users through a preferences screen: - *
    - *
  • {@link org.acra.ACRA#PREF_DISABLE_ACRA} or {@link org.acra.ACRA#PREF_ENABLE_ACRA}
  • - *
  • {@link org.acra.ACRA#PREF_ALWAYS_ACCEPT}
  • - *
  • {@link org.acra.ACRA#PREF_ENABLE_DEVICE_ID}
  • - *
  • {@link org.acra.ACRA#PREF_ENABLE_SYSTEM_LOGS}
  • - *
- * Default is to use the application default SharedPreferences, as retrieved with {@link android.preference.PreferenceManager#getDefaultSharedPreferences(android.content.Context)} - * - * @return SharedPreferences name. - */ - val sharedPreferencesName: String = ACRAConstants.DEFAULT_STRING_VALUE, - - /** - * If enabled, DropBox events collection will include system tags: - *
    - *
  • system_app_anr
  • - *
  • system_app_wtf
  • - *
  • system_app_crash
  • - *
  • system_server_anr
  • - *
  • system_server_wtf
  • - *
  • system_server_crash
  • - *
  • BATTERY_DISCHARGE_INFO
  • - *
  • SYSTEM_RECOVERY_LOG
  • - *
  • SYSTEM_BOOT
  • - *
  • SYSTEM_LAST_KMSG
  • - *
  • APANIC_CONSOLE
  • - *
  • APANIC_THREADS
  • - *
  • SYSTEM_RESTART
  • - *
  • SYSTEM_TOMBSTONE
  • - *
  • data_app_strictmode
  • - *
- * - * @return if system tags are to be included as part of DropBox events. - */ - val includeDropBoxSystemTags: Boolean = false, - - /** - * Custom tags to be included in DropBox event collection - * - * @return tags that you want to be fetched when collecting DropBox events. - */ - val additionalDropBoxTags: List, - - /** - * DropBox event collection will look back this many minutes - * - * @return Number of minutes to look back. - */ - val dropboxCollectionMinutes: Int = 5, - - /** - *

- * Arguments to be passed to the logcat command line. Default is { "-t", "100", "-v", "time" } for: - *

- *
logcat -t 100 -v time
- *

- * Do not include -b arguments for buffer selection, include {@link ReportField#EVENTSLOG} and {@link ReportField#RADIOLOG} in {@link #reportContent()} to activate alternative logcat buffers reporting. - * They will use the same other arguments as those provided here. - *

- *

- * See Listing of logcat Command Options. - *

- * - * @return arguments to supply if retrieving the log as part of the report. - */ - val logcatArguments: List = listOf("-t", "" + ACRAConstants.DEFAULT_LOG_LINES, "-v", "time"), - - /** - *

- * Redefines the list of {@link ReportField}s collected and sent in your reports. - *

- *

- * You can also use this property to modify fields order in your reports. - *

- *

- * The default list is {@link org.acra.ACRAConstants#DEFAULT_REPORT_FIELDS} - * - * @return fields to be included in the report. - */ - val reportContent: List = emptyList(), - - /** - * Controls whether unapproved reports are deleted on application start or not. - *

- * Silent and Toast reports are automatically approved. - * Dialog and Notification reports require explicit approval by the user before they are sent. - *

- *

- * On application restart the user is prompted with approval for one unsent report. - * So you generally don't want to accumulate unapproved reports, otherwise you will prompt them multiple times. - *

- *

- * If this is set to true then all unapproved reports bar one will be deleted on application start. - * The last report is always retained because that is the report that probably just happened. - *

- * - * @return if ACRA should delete unapproved reports on application start. - */ - val deleteUnapprovedReportsOnApplicationStart: Boolean = true, - - /** - * Set this to true if you prefer displaying the native force close dialog after ACRA is done. - * Recommended: Keep this set to false if using interactions with user input. - * - * @return if the native force close dialog should be displayed. - */ - val alsoReportToAndroidFramework: Boolean = false, - - /** - * Add here your {@link android.content.SharedPreferences} identifier Strings if you use others than your application's default. They will be added to the {@link ReportField#SHARED_PREFERENCES} field. - * - * @return names of additional preferences. - */ - val additionalSharedPreferences: List = emptyList(), - - /** - * Set this to true if you want to include only logcat lines related to your Application process. Note that this is always done by android starting with API 16 (Jellybean) - * - * @return true if you want to filter logcat with your process id. - */ - val logcatFilterByPid: Boolean = true, - - /** - * Set this to true if you want to read logcat lines in a non blocking way for your thread. It has a default timeout of 3 seconds. - * - * @return if reading of logcat lines should not block the current thread. - */ - val logcatReadNonBlocking: Boolean = false, - - /** - * Set this to false if you want to disable sending reports in development mode. Only signed application packages will send reports. - * - * @return if reports should only be sent from signed packages. - */ - val sendReportsInDevMode: Boolean = true, - - /** - * Provide here regex patterns to be evaluated on each {@link android.content.SharedPreferences} key to exclude KV pairs from the collected SharedPreferences. - * This allows you to exclude sensitive user data like passwords from being collected. - * - * If you only want to include some keys, you may use regular expressions to do so: - * - * - * - *
only keys foo and bar
"^(?!foo|bar).*$"
only keys containing foo and bar
"^((?!foo|bar).)*$"
- * - * @return regex patterns, every matching key is not collected. - */ - val excludeMatchingSharedPreferencesKeys: List = emptyList(), - - /** - * Provide here regex patterns to be evaluated on each {@link android.provider.Settings.System}, {@link android.provider.Settings.Secure} and {@link android.provider.Settings.Global} key to exclude KV pairs from being collected. - * This allows you to exclude sensitive data from being collected. - * - * If you only want to include some keys, you may use regular expressions to do so: - * - * - * - *
only keys foo and bar
"^(?!foo|bar).*$"
only keys containing foo and bar
"^((?!foo|bar).)*$"
- * - * @return regex patterns, every matching key is not collected. - */ - val excludeMatchingSettingsKeys: List = emptyList(), - - /** - * The default value will be a BuildConfig class residing in the same package as the Application class. - * You only have to set this option if your BuildConfig class is obfuscated. - * - * @return BuildConfig class from which to read any BuildConfig attributes. - */ - val buildConfigClass: Class<*> = Object::class.java, - - /** - * To use in combination with {@link ReportField#APPLICATION_LOG} to set the path/name of your application log file. - * - * @return path/name of your application log file. - */ - val applicationLogFile: String = ACRAConstants.DEFAULT_STRING_VALUE, - - /** - * To use in combination with {@link ReportField#APPLICATION_LOG} to set the number of latest lines of your application log file to be collected. - * Default value is 100. - * - * @return number of lines to collect. - */ - val applicationLogFileLines: Int = ACRAConstants.DEFAULT_LOG_LINES, - - /** - * To use in combination with {@link ReportField#APPLICATION_LOG} to set the root for the path provided in {@link #applicationLogFile()} - * - * @return the directory of the application log file - */ - val applicationLogFileDir: Directory = Directory.FILES_LEGACY, - - /** - * Implement a custom {@link RetryPolicy} to decide if a failed report should be resent or not. - * - * @return a class that decides if a report should be resent (usually if one or more senders failed). - * @since 4.9.1 - */ - @Instantiatable val retryPolicyClass: Class = DefaultRetryPolicy::class.java, - - /** - * If you have services which might crash on startup android will try to restart them indefinitely. Set this to true to prevent that. - * - * @return if all services running in a process should be stopped before it is killed. - * @since 4.9.2 - */ - val stopServicesOnCrash: Boolean = false, - - /** - * Allows to attach files to crash reports. - *

- * ACRA contains a file provider under the following Uri: - * content://[applicationId].acra/[Directory]/[Path] - * where [applicationId] is your application package name, [Directory] is one of the enum constants in {@link Directory} in lower case and [Path] is the relative path to the file in that directory - * e.g. content://org.acra.test.acra/files/thisIsATest.txt - *

- * Side effects: - *
    - *
  • POST mode: requests will be sent with content-type multipart/form-data
  • - *
  • PUT mode: There will be additional requests with the attachments. Naming scheme: [report-id]-[filename]
  • - *
  • EMAIL mode: Some email clients do not support attachments, so some email may lack these attachments. Note that attachments might be readable to email clients when they are sent.
  • - *
- * - * @return uris to be attached to crash reports. - * @since 4.9.3 - */ - val attachmentUris: List = emptyList(), - - /** - * Allows {@link #attachmentUris()} configuration at runtime instead of compile time. - * - * @return a class that decides which uris should be attached to reports - * @since 4.9.3 - */ - @Instantiatable val attachmentUriProvider: Class = DefaultAttachmentProvider::class.java, - - /** - * Toast shown when a report is sent successfully - * - * @return Resource id for the Toast text triggered when a report was sent successfully. - * @since 5.0.0 - */ - @StringRes val resReportSendSuccessToast: Int = ACRAConstants.DEFAULT_RES_VALUE, - - /** - * Toast shown when report sending fails - * - * @return Resource id for the Toast text triggered when no report was sent successfully. - * @since 5.0.0 - */ - @StringRes val resReportSendFailureToast: Int = ACRAConstants.DEFAULT_RES_VALUE, - - /** - * Format in which the report should be sent - * - * @return report format - * @since 5.0.0 - */ - val reportFormat: StringFormat = StringFormat.JSON, - - /** - * Allow parallel collection. Increases performance but might pollute e.g. logcat output - * @return if parallel collection should be active - * @since 5.0.1 - */ - val parallel: Boolean = true, -) \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/config/ReportingAdministrator.java b/acra-core/src/main/java/org/acra/config/ReportingAdministrator.kt similarity index 59% rename from acra-core/src/main/java/org/acra/config/ReportingAdministrator.java rename to acra-core/src/main/java/org/acra/config/ReportingAdministrator.kt index c6ab6e4744..a84e5a93df 100644 --- a/acra-core/src/main/java/org/acra/config/ReportingAdministrator.java +++ b/acra-core/src/main/java/org/acra/config/ReportingAdministrator.kt @@ -13,16 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +package org.acra.config -package org.acra.config; - -import android.content.Context; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import org.acra.builder.LastActivityManager; -import org.acra.builder.ReportBuilder; -import org.acra.data.CrashReportData; -import org.acra.plugins.Plugin; +import android.content.Context +import org.acra.builder.LastActivityManager +import org.acra.builder.ReportBuilder +import org.acra.data.CrashReportData +import org.acra.plugins.Plugin /** * Controls if reports are sent @@ -30,7 +27,7 @@ * @author F43nd1r * @since 26.10.2017 */ -public interface ReportingAdministrator extends Plugin { +interface ReportingAdministrator : Plugin { /** * Determines if report collection should start * @@ -39,8 +36,8 @@ public interface ReportingAdministrator extends Plugin { * @param reportBuilder the reportBuilder for the report about to be collected * @return if this report should be collected */ - default boolean shouldStartCollecting(@NonNull Context context, @NonNull CoreConfiguration config, @NonNull ReportBuilder reportBuilder) { - return true; + fun shouldStartCollecting(context: Context, config: CoreConfiguration, reportBuilder: ReportBuilder): Boolean { + return true } /** @@ -51,8 +48,8 @@ default boolean shouldStartCollecting(@NonNull Context context, @NonNull CoreCon * @param crashReportData the collected report * @return if this report should be sent */ - default boolean shouldSendReport(@NonNull Context context, @NonNull CoreConfiguration config, @NonNull CrashReportData crashReportData) { - return true; + fun shouldSendReport(context: Context, config: CoreConfiguration, crashReportData: CrashReportData): Boolean { + return true } /** @@ -61,11 +58,9 @@ default boolean shouldSendReport(@NonNull Context context, @NonNull CoreConfigur * @param context a context * @param config the current config */ - default void notifyReportDropped(@NonNull Context context, @NonNull CoreConfiguration config) { - } - - default boolean shouldFinishActivity(@NonNull Context context, @NonNull CoreConfiguration config, LastActivityManager lastActivityManager) { - return true; + fun notifyReportDropped(context: Context, config: CoreConfiguration) {} + fun shouldFinishActivity(context: Context, config: CoreConfiguration, lastActivityManager: LastActivityManager?): Boolean { + return true } /** @@ -77,8 +72,8 @@ default boolean shouldFinishActivity(@NonNull Context context, @NonNull CoreConf * @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) { - return true; + fun shouldKillApplication(context: Context, config: CoreConfiguration, reportBuilder: ReportBuilder, + crashReportData: CrashReportData?): Boolean { + return true } -} +} \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/config/RetryPolicy.java b/acra-core/src/main/java/org/acra/config/RetryPolicy.kt similarity index 51% rename from acra-core/src/main/java/org/acra/config/RetryPolicy.java rename to acra-core/src/main/java/org/acra/config/RetryPolicy.kt index 9085342395..e5a4660604 100644 --- a/acra-core/src/main/java/org/acra/config/RetryPolicy.java +++ b/acra-core/src/main/java/org/acra/config/RetryPolicy.kt @@ -13,14 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.acra.config; +package org.acra.config -import androidx.annotation.NonNull; - -import org.acra.sender.ReportSender; -import org.acra.sender.ReportSenderException; - -import java.util.List; +import org.acra.sender.ReportSender +import org.acra.sender.ReportSenderException /** * A policy which determines if a report should be resent. @@ -28,33 +24,12 @@ * @author F43nd1r * @since 4.9.1 */ -public interface RetryPolicy { - +interface RetryPolicy { /** * @param senders a list of all senders. * @param failedSenders a list of all failed senders with the thrown exceptions. * @return if the request should be resent later. */ - boolean shouldRetrySend(@NonNull List senders, @NonNull List failedSenders); - - class FailedSender { - - private final ReportSender sender; - private final ReportSenderException exception; - - public FailedSender(@NonNull ReportSender sender, @NonNull ReportSenderException exception) { - this.sender = sender; - this.exception = exception; - } - - @NonNull - public ReportSender getSender() { - return sender; - } - - @NonNull - public ReportSenderException getException() { - return exception; - } - } -} + fun shouldRetrySend(senders: List, failedSenders: List): Boolean + class FailedSender(val sender: ReportSender, val exception: ReportSenderException) +} \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/data/CrashReportData.java b/acra-core/src/main/java/org/acra/data/CrashReportData.java deleted file mode 100644 index c933ea3def..0000000000 --- a/acra-core/src/main/java/org/acra/data/CrashReportData.java +++ /dev/null @@ -1,194 +0,0 @@ -/* - * Copyright (c) 2017 - * - * 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.data; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import org.acra.ACRA; -import org.acra.ACRAConstants; -import org.acra.ReportField; -import org.acra.collections.ImmutableSet; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import java.util.Collections; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; - -/** - * Stores a crash report data - */ -public final class CrashReportData { - private final JSONObject content; - - public CrashReportData() { - content = new JSONObject(); - } - - public CrashReportData(String json) throws JSONException { - content = new JSONObject(json); - } - - public synchronized void put(@NonNull String key, boolean value) { - try { - content.put(key, value); - } catch (JSONException e) { - ACRA.log.w(ACRA.LOG_TAG, "Failed to put value into CrashReportData: " + String.valueOf(value)); - } - } - - public synchronized void put(@NonNull String key, double value) { - try { - content.put(key, value); - } catch (JSONException e) { - ACRA.log.w(ACRA.LOG_TAG, "Failed to put value into CrashReportData: " + String.valueOf(value)); - } - } - - public synchronized void put(@NonNull String key, int value) { - try { - content.put(key, value); - } catch (JSONException e) { - ACRA.log.w(ACRA.LOG_TAG, "Failed to put value into CrashReportData: " + String.valueOf(value)); - } - } - - public synchronized void put(@NonNull String key, long value) { - try { - content.put(key, value); - } catch (JSONException e) { - ACRA.log.w(ACRA.LOG_TAG, "Failed to put value into CrashReportData: " + String.valueOf(value)); - } - } - - public synchronized void put(@NonNull String key, @Nullable String value) { - if (value == null) { - putNA(key); - return; - } - try { - content.put(key, value); - } catch (JSONException e) { - ACRA.log.w(ACRA.LOG_TAG, "Failed to put value into CrashReportData: " + value); - } - } - - public synchronized void put(@NonNull String key, @Nullable JSONObject value) { - if (value == null) { - putNA(key); - return; - } - try { - content.put(key, value); - } catch (JSONException e) { - ACRA.log.w(ACRA.LOG_TAG, "Failed to put value into CrashReportData: " + String.valueOf(value)); - } - } - - public synchronized void put(@NonNull String key, @Nullable JSONArray value) { - if (value == null) { - putNA(key); - return; - } - try { - content.put(key, value); - } catch (JSONException e) { - ACRA.log.w(ACRA.LOG_TAG, "Failed to put value into CrashReportData: " + String.valueOf(value)); - } - } - - public synchronized void put(@NonNull ReportField key, boolean value) { - put(key.toString(), value); - } - - public synchronized void put(@NonNull ReportField key, double value) { - put(key.toString(), value); - } - - public synchronized void put(@NonNull ReportField key, int value) { - put(key.toString(), value); - } - - public synchronized void put(@NonNull ReportField key, long value) { - put(key.toString(), value); - } - - public synchronized void put(@NonNull ReportField key, @Nullable String value) { - put(key.toString(), value); - } - - public synchronized void put(@NonNull ReportField key, @Nullable JSONObject value) { - put(key.toString(), value); - } - - public synchronized void put(@NonNull ReportField key, @Nullable JSONArray value) { - put(key.toString(), value); - } - - private void putNA(@NonNull String key) { - try { - content.put(key, ACRAConstants.NOT_AVAILABLE); - } catch (JSONException ignored) { - } - } - - /** - * Returns the property with the specified key. - * - * @param key the key of the property to find. - * @return the keyd property value, or {@code null} if it can't be found. - */ - public String getString(@NonNull ReportField key) { - return content.optString(key.toString()); - } - - public Object get(@NonNull String key) { - return content.opt(key); - } - - public boolean containsKey(@NonNull String key) { - return content.has(key); - } - - public boolean containsKey(@NonNull ReportField key) { - return containsKey(key.toString()); - } - - @NonNull - public String toJSON() throws JSONException { - try { - return StringFormat.JSON.toFormattedString(this, Collections.emptyList(), "", "", false); - } catch (JSONException e) { - throw e; - } catch (Exception e) { - throw new JSONException(e.getMessage()); - } - } - - @NonNull - public Map toMap() { - final Map map = new HashMap<>(content.length()); - for (final Iterator iterator = content.keys(); iterator.hasNext(); ) { - final String key = iterator.next(); - map.put(key, get(key)); - } - return map; - } -} diff --git a/acra-core/src/main/java/org/acra/data/CrashReportData.kt b/acra-core/src/main/java/org/acra/data/CrashReportData.kt new file mode 100644 index 0000000000..fdd0e5b3fd --- /dev/null +++ b/acra-core/src/main/java/org/acra/data/CrashReportData.kt @@ -0,0 +1,194 @@ +/* + * Copyright (c) 2017 + * + * 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.data + +import org.acra.ACRA +import org.acra.ACRAConstants +import org.acra.ReportField +import org.acra.log.warn +import org.json.JSONArray +import org.json.JSONException +import org.json.JSONObject +import java.util.* + +/** + * Stores a crash report data + */ +class CrashReportData { + private val content: JSONObject + + constructor() { + content = JSONObject() + } + + constructor(json: String) { + content = JSONObject(json) + } + + @Synchronized + fun put(key: String, value: Boolean) { + try { + content.put(key, value) + } catch (e: JSONException) { + warn { "Failed to put value into CrashReportData: $value" } + } + } + + @Synchronized + fun put(key: String, value: Double) { + try { + content.put(key, value) + } catch (e: JSONException) { + warn { "Failed to put value into CrashReportData: $value" } + } + } + + @Synchronized + fun put(key: String, value: Int) { + try { + content.put(key, value) + } catch (e: JSONException) { + warn { "Failed to put value into CrashReportData: $value" } + } + } + + @Synchronized + fun put(key: String, value: Long) { + try { + content.put(key, value) + } catch (e: JSONException) { + warn { "Failed to put value into CrashReportData: $value" } + } + } + + @Synchronized + fun put(key: String, value: String?) { + if (value == null) { + putNA(key) + return + } + try { + content.put(key, value) + } catch (e: JSONException) { + warn { "Failed to put value into CrashReportData: $value" } + } + } + + @Synchronized + fun put(key: String, value: JSONObject?) { + if (value == null) { + putNA(key) + return + } + try { + content.put(key, value) + } catch (e: JSONException) { + warn { "Failed to put value into CrashReportData: $value" } + } + } + + @Synchronized + fun put(key: String, value: JSONArray?) { + if (value == null) { + putNA(key) + return + } + try { + content.put(key, value) + } catch (e: JSONException) { + warn { "Failed to put value into CrashReportData: $value" } + } + } + + @Synchronized + fun put(key: ReportField, value: Boolean) { + put(key.toString(), value) + } + + @Synchronized + fun put(key: ReportField, value: Double) { + put(key.toString(), value) + } + + @Synchronized + fun put(key: ReportField, value: Int) { + put(key.toString(), value) + } + + @Synchronized + fun put(key: ReportField, value: Long) { + put(key.toString(), value) + } + + @Synchronized + fun put(key: ReportField, value: String?) { + put(key.toString(), value) + } + + @Synchronized + fun put(key: ReportField, value: JSONObject?) { + put(key.toString(), value) + } + + @Synchronized + fun put(key: ReportField, value: JSONArray?) { + put(key.toString(), value) + } + + private fun putNA(key: String) { + try { + content.put(key, ACRAConstants.NOT_AVAILABLE) + } catch (ignored: JSONException) { + } + } + + /** + * Returns the property with the specified key. + * + * @param key the key of the property to find. + * @return the keyd property value, or `null` if it can't be found. + */ + fun getString(key: ReportField): String? { + return content.optString(key.toString()) + } + + operator fun get(key: String): Any? { + return content.opt(key) + } + + fun containsKey(key: String): Boolean { + return content.has(key) + } + + fun containsKey(key: ReportField): Boolean { + return containsKey(key.toString()) + } + + @Throws(JSONException::class) + fun toJSON(): String { + return try { + StringFormat.JSON.toFormattedString(this, emptyList(), "", "", false) + } catch (e: JSONException) { + throw e + } catch (e: Exception) { + throw JSONException(e.message) + } + } + + fun toMap(): Map { + return content.keys().asSequence().map { it to get(it) }.toMap() + } +} \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/data/CrashReportDataFactory.java b/acra-core/src/main/java/org/acra/data/CrashReportDataFactory.java deleted file mode 100644 index 8ae092862c..0000000000 --- a/acra-core/src/main/java/org/acra/data/CrashReportDataFactory.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright (c) 2017 - * - * 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.data; - -import android.content.Context; -import androidx.annotation.NonNull; -import org.acra.ACRA; -import org.acra.builder.ReportBuilder; -import org.acra.collector.ApplicationStartupCollector; -import org.acra.collector.Collector; -import org.acra.collector.CollectorException; -import org.acra.config.CoreConfiguration; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; - -import static org.acra.ACRA.LOG_TAG; - -/** - * Responsible for collecting the CrashReportData for an Exception. - * - * @author F43nd1r - * @since 4.3.0 - */ -public final class CrashReportDataFactory { - - private final Context context; - private final CoreConfiguration config; - private final List collectors; - - public CrashReportDataFactory(@NonNull Context context, @NonNull CoreConfiguration config) { - this.context = context; - this.config = config; - collectors = config.getPluginLoader().loadEnabled(config, Collector.class); - //noinspection Java8ListSort - Collections.sort(collectors, (c1, c2) -> { - Collector.Order o1; - Collector.Order o2; - try { - o1 = c1.getOrder(); - } catch (Exception t) { - o1 = Collector.Order.NORMAL; - } - try { - o2 = c2.getOrder(); - } catch (Exception t) { - o2 = Collector.Order.NORMAL; - } - return o1.ordinal() - o2.ordinal(); - }); - } - - /** - * Collects crash data. - * - * @param builder ReportBuilder for whom to crete the crash report. - * @return CrashReportData identifying the current crash. - */ - @NonNull - public CrashReportData createCrashData(@NonNull final ReportBuilder builder) { - final ExecutorService executorService = config.getParallel() ? Executors.newCachedThreadPool() : Executors.newSingleThreadExecutor(); - final CrashReportData crashReportData = new CrashReportData(); - final List> futures = new ArrayList<>(); - for (final Collector collector : collectors) { - futures.add(executorService.submit(() -> { - //catch absolutely everything possible here so no collector obstructs the others - try { - if(ACRA.DEV_LOGGING)ACRA.log.d(LOG_TAG, "Calling collector " + collector.getClass().getName()); - collector.collect(context, config, builder, crashReportData); - if(ACRA.DEV_LOGGING)ACRA.log.d(LOG_TAG, "Collector " + collector.getClass().getName() + " completed"); - }catch (CollectorException e){ - ACRA.log.w(LOG_TAG, e); - }catch (Exception t) { - ACRA.log.e(LOG_TAG, "Error in collector " + collector.getClass().getSimpleName(), t); - } - })); - } - for (Future future : futures) { - while (!future.isDone()) { - try { - future.get(); - } catch (InterruptedException ignored) { - } catch (ExecutionException e) { - break; - } - } - } - return crashReportData; - } - - public void collectStartUp() { - for (Collector collector : collectors) { - if (collector instanceof ApplicationStartupCollector) { - //catch absolutely everything possible here so no collector obstructs the others - try { - ((ApplicationStartupCollector) collector).collectApplicationStartUp(context, config); - } catch (Exception t) { - ACRA.log.w(ACRA.LOG_TAG, collector.getClass().getSimpleName() + " failed to collect its startup data", t); - } - } - } - } -} \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/data/CrashReportDataFactory.kt b/acra-core/src/main/java/org/acra/data/CrashReportDataFactory.kt new file mode 100644 index 0000000000..ee7c68c411 --- /dev/null +++ b/acra-core/src/main/java/org/acra/data/CrashReportDataFactory.kt @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2017 + * + * 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.data + +import android.content.Context +import org.acra.builder.ReportBuilder +import org.acra.collector.ApplicationStartupCollector +import org.acra.collector.Collector +import org.acra.collector.CollectorException +import org.acra.config.CoreConfiguration +import org.acra.log.debug +import org.acra.log.warn +import java.util.concurrent.ExecutionException +import java.util.concurrent.Executors + +/** + * Responsible for collecting the CrashReportData for an Exception. + * + * @author F43nd1r + * @since 4.3.0 + */ +class CrashReportDataFactory(private val context: Context, private val config: CoreConfiguration) { + private val collectors: List = config.pluginLoader.loadEnabled(config, Collector::class.java).sortedBy { + try { + it.order + } catch (t: Exception) { + Collector.Order.NORMAL + } + } + + /** + * Collects crash data. + * + * @param builder ReportBuilder for whom to crete the crash report. + * @return CrashReportData identifying the current crash. + */ + fun createCrashData(builder: ReportBuilder): CrashReportData { + val executorService = if (config.parallel) Executors.newCachedThreadPool() else Executors.newSingleThreadExecutor() + val crashReportData = CrashReportData() + val futures = collectors.map { collector -> + executorService.submit { + //catch absolutely everything possible here so no collector obstructs the others + try { + debug { "Calling collector ${collector.javaClass.name}" } + collector.collect(context, config, builder, crashReportData) + debug { "Collector ${collector.javaClass.name} completed" } + } catch (e: CollectorException) { + warn(e) { "" } + } catch (t: Throwable) { + warn(t) { "Error in collector ${collector.javaClass.simpleName}" } + } + } + } + for (future in futures) { + while (!future.isDone) { + try { + future.get() + } catch (ignored: InterruptedException) { + } catch (e: ExecutionException) { + break + } + } + } + return crashReportData + } + + fun collectStartUp() { + for (collector in collectors) { + if (collector is ApplicationStartupCollector) { + //catch absolutely everything possible here so no collector obstructs the others + try { + collector.collectApplicationStartUp(context, config) + } catch (t: Throwable) { + warn(t) { "${collector.javaClass.simpleName} failed to collect its startup data" } + } + } + } + } + +} \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/data/StringFormat.java b/acra-core/src/main/java/org/acra/data/StringFormat.java deleted file mode 100644 index ef6c538cde..0000000000 --- a/acra-core/src/main/java/org/acra/data/StringFormat.java +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright (c) 2017 - * - * 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.data; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import android.text.TextUtils; - -import org.acra.ACRAConstants; -import org.acra.ReportField; -import org.acra.collections.ImmutableSet; -import org.json.JSONException; -import org.json.JSONObject; -import org.json.JSONStringer; - -import java.io.UnsupportedEncodingException; -import java.net.URLEncoder; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Set; - -/** - * Represents possible report formats - * - * @author F43nd1r - * @since 14.11.2017 - */ -public enum StringFormat { - JSON("application/json") { - @NonNull - @Override - public String toFormattedString(@NonNull CrashReportData data, @NonNull List order, @NonNull String mainJoiner, @NonNull String subJoiner, boolean urlEncode) throws JSONException { - final Map map = data.toMap(); - final JSONStringer stringer = new JSONStringer().object(); - for (ReportField field : order) { - stringer.key(field.toString()).value(map.remove(field.toString())); - } - for (Map.Entry entry : map.entrySet()) { - stringer.key(entry.getKey()).value(entry.getValue()); - } - return stringer.endObject().toString(); - } - }, - KEY_VALUE_LIST("application/x-www-form-urlencoded") { - @NonNull - @Override - public String toFormattedString(@NonNull CrashReportData data, @NonNull List order, @NonNull String mainJoiner, @NonNull String subJoiner, boolean urlEncode) throws UnsupportedEncodingException { - final Map map = toStringMap(data.toMap(), subJoiner); - final StringBuilder builder = new StringBuilder(); - for (ReportField field : order) { - append(builder, field.toString(), map.remove(field.toString()), mainJoiner, urlEncode); - } - for (Map.Entry entry : map.entrySet()) { - append(builder, entry.getKey(), entry.getValue(), mainJoiner, urlEncode); - } - return builder.toString(); - } - - private void append(@NonNull StringBuilder builder, @Nullable String key, @Nullable String value, @Nullable String joiner, boolean urlEncode) throws UnsupportedEncodingException { - if (builder.length() > 0) { - builder.append(joiner); - } - if (urlEncode) { - key = key != null ? URLEncoder.encode(key, ACRAConstants.UTF8) : null; - value = value != null ? URLEncoder.encode(value, ACRAConstants.UTF8) : null; - } - builder.append(key).append('=').append(value); - } - - @NonNull - private Map toStringMap(@NonNull Map map, @NonNull String joiner) { - final Map stringMap = new HashMap<>(map.size()); - for (final Map.Entry entry : map.entrySet()) { - stringMap.put(entry.getKey(), valueToString(joiner, entry.getValue())); - } - return stringMap; - } - - private String valueToString(@NonNull String joiner, @Nullable Object value) { - if (value instanceof JSONObject) { - return TextUtils.join(joiner, flatten((JSONObject) value)); - } else { - return String.valueOf(value); - } - } - - @NonNull - private List flatten(@NonNull JSONObject json) { - final List result = new ArrayList<>(); - for (final Iterator iterator = json.keys(); iterator.hasNext(); ) { - final String key = iterator.next(); - Object value; - try { - value = json.get(key); - } catch (JSONException e) { - value = null; - } - if (value instanceof JSONObject) { - for (String s : flatten((JSONObject) value)) { - result.add(key + "." + s); - } - } else { - result.add(key + "=" + value); - } - } - return result; - } - }; - - private final String contentType; - - StringFormat(@NonNull String contentType) { - this.contentType = contentType; - } - - @NonNull - public abstract String toFormattedString(@NonNull CrashReportData data, @NonNull List order, @NonNull String mainJoiner, @NonNull String subJoiner, boolean urlEncode) throws Exception; - - @NonNull - public String getMatchingHttpContentType() { - return contentType; - } -} diff --git a/acra-core/src/main/java/org/acra/data/StringFormat.kt b/acra-core/src/main/java/org/acra/data/StringFormat.kt new file mode 100644 index 0000000000..fcf8c17685 --- /dev/null +++ b/acra-core/src/main/java/org/acra/data/StringFormat.kt @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2017 + * + * 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.data + +import org.acra.ACRAConstants +import org.acra.ReportField +import org.json.JSONException +import org.json.JSONObject +import org.json.JSONStringer +import java.io.UnsupportedEncodingException +import java.net.URLEncoder + +/** + * Represents possible report formats + * + * @author F43nd1r + * @since 14.11.2017 + */ +enum class StringFormat(val matchingHttpContentType: String) { + JSON("application/json") { + @Throws(JSONException::class) + override fun toFormattedString(data: CrashReportData, order: List, mainJoiner: String, subJoiner: String, urlEncode: Boolean): String { + val map = data.toMap().toMutableMap() + val stringer = JSONStringer().`object`() + for (field in order) { + stringer.key(field.toString()).value(map.remove(field.toString())) + } + for ((key, value) in map) { + stringer.key(key).value(value) + } + return stringer.endObject().toString() + } + }, + KEY_VALUE_LIST("application/x-www-form-urlencoded") { + @Throws(UnsupportedEncodingException::class) + override fun toFormattedString(data: CrashReportData, order: List, mainJoiner: String, subJoiner: String, urlEncode: Boolean): String { + val map = toStringMap(data.toMap(), subJoiner).toMutableMap() + val builder = StringBuilder() + for (field in order) { + append(builder, field.toString(), map.remove(field.toString()), mainJoiner, urlEncode) + } + for ((key, value) in map) { + append(builder, key, value, mainJoiner, urlEncode) + } + return builder.toString() + } + + @Throws(UnsupportedEncodingException::class) + private fun append(builder: StringBuilder, key: String?, value: String?, joiner: String?, urlEncode: Boolean) { + var k = key + var v = value + if (builder.isNotEmpty()) { + builder.append(joiner) + } + if (urlEncode) { + k = k?.let { URLEncoder.encode(it, ACRAConstants.UTF8) } + v = v?.let { URLEncoder.encode(it, ACRAConstants.UTF8) } + } + builder.append(k).append('=').append(v) + } + + private fun toStringMap(map: Map, joiner: String): Map { + return map.mapValues { valueToString(joiner, it) }.toMap() + } + + private fun valueToString(joiner: String, value: Any?): String { + return if (value is JSONObject) { + flatten(value).joinToString(joiner) + } else { + value.toString() + } + } + + private fun flatten(json: JSONObject): List { + return json.keys().asSequence().toList().flatMap { key -> + val value: Any? = try { + json[key] + } catch (e: JSONException) { + null + } + if (value is JSONObject) { + flatten(value).map { "$key.$it" } + } else { + listOf("$key=$value") + } + } + } + }; + + @Throws(Exception::class) + abstract fun toFormattedString(data: CrashReportData, order: List, mainJoiner: String, subJoiner: String, urlEncode: Boolean): String +} \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/file/BulkReportDeleter.java b/acra-core/src/main/java/org/acra/file/BulkReportDeleter.kt similarity index 51% rename from acra-core/src/main/java/org/acra/file/BulkReportDeleter.java rename to acra-core/src/main/java/org/acra/file/BulkReportDeleter.kt index 414b7da145..57130f047b 100644 --- a/acra-core/src/main/java/org/acra/file/BulkReportDeleter.java +++ b/acra-core/src/main/java/org/acra/file/BulkReportDeleter.kt @@ -13,44 +13,29 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +package org.acra.file -package org.acra.file; - -import android.content.Context; -import androidx.annotation.NonNull; - -import org.acra.ACRA; - -import java.io.File; -import java.util.Arrays; - -import static org.acra.ACRA.LOG_TAG; +import android.content.Context +import org.acra.log.warn +import java.util.* /** * Deletes unsent reports. */ -public final class BulkReportDeleter { - - @NonNull - private final ReportLocator reportLocator; - - public BulkReportDeleter(@NonNull Context context) { - this.reportLocator = new ReportLocator(context); - } +class BulkReportDeleter(context: Context) { + private val reportLocator: ReportLocator = ReportLocator(context) /** * @param approved Whether to delete approved or unapproved reports. * @param nrToKeep Number of latest reports to keep. */ - public void deleteReports(boolean approved, int nrToKeep) { - final File[] files = approved ? reportLocator.getApprovedReports() : reportLocator.getUnapprovedReports(); - - Arrays.sort(files, new LastModifiedComparator()); - - for (int i = 0; i < files.length - nrToKeep; i++) { + fun deleteReports(approved: Boolean, nrToKeep: Int) { + val files = (if (approved) reportLocator.approvedReports else reportLocator.unapprovedReports).sortedBy { it.lastModified() } + for (i in 0 until files.size - nrToKeep) { if (!files[i].delete()) { - ACRA.log.w(LOG_TAG, "Could not delete report : " + files[i]); + warn { "Could not delete report : ${files[i]}" } } } } -} + +} \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/file/CrashReportFileNameParser.java b/acra-core/src/main/java/org/acra/file/CrashReportFileNameParser.kt similarity index 52% rename from acra-core/src/main/java/org/acra/file/CrashReportFileNameParser.java rename to acra-core/src/main/java/org/acra/file/CrashReportFileNameParser.kt index 05963fa13a..97583c5fb7 100644 --- a/acra-core/src/main/java/org/acra/file/CrashReportFileNameParser.java +++ b/acra-core/src/main/java/org/acra/file/CrashReportFileNameParser.kt @@ -13,17 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.acra.file; +package org.acra.file -import androidx.annotation.NonNull; - -import org.acra.ACRAConstants; -import org.acra.ErrorReporter; - -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.Calendar; -import java.util.Locale; +import org.acra.ACRAConstants +import java.text.ParseException +import java.text.SimpleDateFormat +import java.util.* /** * Responsible for determining the state of a Crash Report based on its file name. @@ -31,35 +26,32 @@ * @author William Ferguson & F43nd1r * @since 4.3.0 */ -public final class CrashReportFileNameParser { - +class CrashReportFileNameParser { /** * Guess that a report is silent from its file name. * * @param reportFileName Name of the report to check whether it should be sent silently. - * @return True if the report has been declared explicitly silent using {@link ErrorReporter#handleSilentException(Throwable)}. + * @return True if the report has been declared explicitly silent using [ErrorReporter.handleSilentException]. */ - public boolean isSilent(@NonNull String reportFileName) { - return reportFileName.contains(ACRAConstants.SILENT_SUFFIX); - } + fun isSilent(reportFileName: String): Boolean = reportFileName.contains(ACRAConstants.SILENT_SUFFIX) /** * Returns true if the report is considered as approved. - *

+ * + * * This includes: - *

- *
    - *
  • Reports which were pending when the user agreed to send a report in the NOTIFICATION mode Dialog.
  • - *
  • Explicit silent reports
  • - *
+ * + * + * * Reports which were pending when the user agreed to send a report in the NOTIFICATION mode Dialog. + * * Explicit silent reports + * * * @param reportFileName Name of report to check whether it is approved to be sent. * @return True if a report can be sent. - * @deprecated use {@link ReportLocator#getApprovedReports()} and {@link ReportLocator#getUnapprovedReports()} instead */ - @Deprecated - public boolean isApproved(@NonNull String reportFileName) { - return isSilent(reportFileName) || reportFileName.contains(ACRAConstants.APPROVED_SUFFIX); + @Deprecated("use {@link ReportLocator#getApprovedReports()} and {@link ReportLocator#getUnapprovedReports()} instead") + fun isApproved(reportFileName: String): Boolean { + return isSilent(reportFileName) || reportFileName.contains(ACRAConstants.APPROVED_SUFFIX) } /** @@ -68,14 +60,13 @@ public boolean isApproved(@NonNull String reportFileName) { * @param reportFileName Name of the report to get the timestamp from. * @return timestamp of the report */ - @NonNull - public Calendar getTimestamp(@NonNull String reportFileName) { - final String timestamp = reportFileName.replace(ACRAConstants.REPORTFILE_EXTENSION, "").replace(ACRAConstants.SILENT_SUFFIX, ""); - final Calendar calendar = Calendar.getInstance(); + fun getTimestamp(reportFileName: String): Calendar { + val timestamp = reportFileName.replace(ACRAConstants.REPORTFILE_EXTENSION, "").replace(ACRAConstants.SILENT_SUFFIX, "") + val calendar = Calendar.getInstance() try { - calendar.setTime(new SimpleDateFormat(ACRAConstants.DATE_TIME_FORMAT_STRING, Locale.ENGLISH).parse(timestamp)); - } catch (ParseException ignored) { + calendar.time = SimpleDateFormat(ACRAConstants.DATE_TIME_FORMAT_STRING, Locale.ENGLISH).parse(timestamp)!! + } catch (ignored: ParseException) { } - return calendar; + return calendar } -} +} \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/file/CrashReportPersister.java b/acra-core/src/main/java/org/acra/file/CrashReportPersister.kt similarity index 69% rename from acra-core/src/main/java/org/acra/file/CrashReportPersister.java rename to acra-core/src/main/java/org/acra/file/CrashReportPersister.kt index 9d014a7d4e..05d21071ab 100644 --- a/acra-core/src/main/java/org/acra/file/CrashReportPersister.java +++ b/acra-core/src/main/java/org/acra/file/CrashReportPersister.kt @@ -16,36 +16,28 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +package org.acra.file -package org.acra.file; - -import androidx.annotation.NonNull; - -import org.acra.data.CrashReportData; -import org.acra.util.IOUtils; -import org.acra.util.StreamReader; -import org.json.JSONException; - -import java.io.File; -import java.io.IOException; +import org.acra.data.CrashReportData +import org.acra.util.StreamReader +import org.json.JSONException +import java.io.File +import java.io.IOException /** - * Handles persistence of {@link CrashReportData} + * Handles persistence of [CrashReportData] */ -public final class CrashReportPersister { - +class CrashReportPersister { /** - * Loads properties from the specified {@code File}. + * Loads properties from the specified `File`. * * @param file Report file from which to load the CrashData. * @return CrashReportData read from the supplied File. - * @throws IOException if error occurs during reading from the {@code File}. + * @throws IOException if error occurs during reading from the `File`. * @throws JSONException if the stream cannot be parsed as a JSON object. */ - @NonNull - public CrashReportData load(@NonNull File file) throws IOException, JSONException { - return new CrashReportData(new StreamReader(file).read()); - } + @Throws(IOException::class, JSONException::class) + fun load(file: File): CrashReportData = CrashReportData(file.readText()) /** * Stores the mappings in this Properties to the specified OutputStream, @@ -57,7 +49,6 @@ public CrashReportData load(@NonNull File file) throws IOException, JSONExceptio * @throws IOException if the CrashReportData could not be written to the OutputStream. * @throws JSONException if the crashData could not be converted to JSON. */ - public void store(@NonNull CrashReportData crashData, @NonNull File file) throws IOException, JSONException { - IOUtils.writeStringToFile(file, crashData.toJSON()); - } -} + @Throws(IOException::class, JSONException::class) + fun store(crashData: CrashReportData, file: File) = file.writeText(crashData.toJSON()) +} \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/file/Directory.java b/acra-core/src/main/java/org/acra/file/Directory.java deleted file mode 100644 index ee0430cd73..0000000000 --- a/acra-core/src/main/java/org/acra/file/Directory.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright (c) 2016 - * - * 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.file; - -import android.content.Context; -import android.os.Build; -import android.os.Environment; -import androidx.annotation.NonNull; - -import java.io.File; -import java.util.regex.Pattern; - -/** - * @author F43nd1r - * @since 4.9.1 - */ -public enum Directory { - /** - * Legacy behaviour: - * If the string starts with a path separator, this behaves like {@link #ROOT}. - * Otherwise it behaves like {@link #FILES}. - */ - FILES_LEGACY { - @NonNull - @Override - public File getFile(@NonNull Context context, @NonNull String fileName) { - return (fileName.startsWith("/") ? Directory.ROOT : Directory.FILES).getFile(context, fileName); - } - }, - /** - * Directory returned by {@link Context#getFilesDir()} - */ - FILES { - @NonNull - @Override - public File getFile(@NonNull Context context, @NonNull String fileName) { - return new File(context.getFilesDir(), fileName); - } - }, - /** - * Directory returned by {@link Context#getExternalFilesDir(String)} - */ - EXTERNAL_FILES { - @NonNull - @Override - public File getFile(@NonNull Context context, @NonNull String fileName) { - return new File(context.getExternalFilesDir(null), fileName); - } - }, - /** - * Directory returned by {@link Context#getCacheDir()} - */ - CACHE { - @NonNull - @Override - public File getFile(@NonNull Context context, @NonNull String fileName) { - return new File(context.getCacheDir(), fileName); - } - }, - /** - * Directory returned by {@link Context#getExternalCacheDir()} - */ - EXTERNAL_CACHE { - @NonNull - @Override - public File getFile(@NonNull Context context, @NonNull String fileName) { - return new File(context.getExternalCacheDir(), fileName); - } - }, - /** - * Directory returned by {@link Context#getNoBackupFilesDir()}. - * Will fall back to {@link Context#getFilesDir()} on API < 21 - */ - NO_BACKUP_FILES { - @NonNull - @Override - public File getFile(@NonNull Context context, @NonNull String fileName) { - File dir; - if (Build.VERSION.SDK_INT >= 21) { - dir = context.getNoBackupFilesDir(); - } else { - dir = new File(context.getApplicationInfo().dataDir, "no_backup"); - } - return new File(dir, fileName); - } - }, - /** - * Directory returned by {@link Environment#getExternalStorageDirectory()} - */ - EXTERNAL_STORAGE { - @NonNull - @Override - public File getFile(@NonNull Context context, @NonNull String fileName) { - return new File(Environment.getExternalStorageDirectory(), fileName); - } - }, - /** - * Root Directory, paths in this directory are absolute paths - */ - ROOT { - @NonNull - @Override - public File getFile(@NonNull Context context, @NonNull String fileName) { - String[] parts = fileName.split(Pattern.quote(File.separator), 2); - if (parts.length == 1) return new File(fileName); - final File[] roots = File.listRoots(); - for (File root : roots) { - if (parts[0].equals(root.getPath().replace(File.separator, ""))) { - return new File(root, parts[1]); - } - } - return new File(roots[0], fileName); - } - }; - - @NonNull - public abstract File getFile(@NonNull Context context, @NonNull String fileName); -} diff --git a/acra-core/src/main/java/org/acra/file/Directory.kt b/acra-core/src/main/java/org/acra/file/Directory.kt new file mode 100644 index 0000000000..edc800a4f6 --- /dev/null +++ b/acra-core/src/main/java/org/acra/file/Directory.kt @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2016 + * + * 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.file + +import android.content.Context +import android.os.Build +import android.os.Environment +import java.io.File +import java.util.regex.Pattern + +/** + * @author F43nd1r + * @since 4.9.1 + */ +enum class Directory { + /** + * Legacy behaviour: + * If the string starts with a path separator, this behaves like [.ROOT]. + * Otherwise it behaves like [.FILES]. + */ + FILES_LEGACY { + override fun getFile(context: Context, fileName: String): File { + return (if (fileName.startsWith("/")) ROOT else FILES).getFile(context, fileName) + } + }, + + /** + * Directory returned by [Context.getFilesDir] + */ + FILES { + override fun getFile(context: Context, fileName: String): File { + return File(context.filesDir, fileName) + } + }, + + /** + * Directory returned by [Context.getExternalFilesDir] + */ + EXTERNAL_FILES { + override fun getFile(context: Context, fileName: String): File { + return File(context.getExternalFilesDir(null), fileName) + } + }, + + /** + * Directory returned by [Context.getCacheDir] + */ + CACHE { + override fun getFile(context: Context, fileName: String): File { + return File(context.cacheDir, fileName) + } + }, + + /** + * Directory returned by [Context.getExternalCacheDir] + */ + EXTERNAL_CACHE { + override fun getFile(context: Context, fileName: String): File { + return File(context.externalCacheDir, fileName) + } + }, + + /** + * Directory returned by [Context.getNoBackupFilesDir]. + * Will fall back to [Context.getFilesDir] on API < 21 + */ + NO_BACKUP_FILES { + override fun getFile(context: Context, fileName: String): File { + val dir: File = if (Build.VERSION.SDK_INT >= 21) { + context.noBackupFilesDir + } else { + File(context.applicationInfo.dataDir, "no_backup") + } + return File(dir, fileName) + } + }, + + /** + * Directory returned by [Environment.getExternalStorageDirectory] + */ + EXTERNAL_STORAGE { + @Suppress("DEPRECATION") + override fun getFile(context: Context, fileName: String): File { + return File(Environment.getExternalStorageDirectory(), fileName) + } + }, + + /** + * Root Directory, paths in this directory are absolute paths + */ + ROOT { + override fun getFile(context: Context, fileName: String): File { + val parts = fileName.split(File.separator, limit = 2) + if (parts.size == 1) return File(fileName) + val roots = File.listRoots() + for (root in roots) { + if (parts[0] == root.path.replace(File.separator, "")) { + return File(root, parts[1]) + } + } + return File(roots[0], fileName) + } + }; + + abstract fun getFile(context: Context, fileName: String): File +} \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/file/LastModifiedComparator.java b/acra-core/src/main/java/org/acra/file/LastModifiedComparator.java deleted file mode 100644 index ceb1b2aab7..0000000000 --- a/acra-core/src/main/java/org/acra/file/LastModifiedComparator.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (c) 2017 the ACRA team - * - * 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.file; - -import androidx.annotation.NonNull; - -import java.io.File; -import java.util.Comparator; - -/** - * Orders files from oldest to newest based on their last modified date. - */ -public final class LastModifiedComparator implements Comparator { - @Override - public int compare(@NonNull File lhs, @NonNull File rhs) { - final long l = lhs.lastModified(); - final long r = rhs.lastModified(); - return l < r ? -1 : (l == r ? 0 : 1); - } -} diff --git a/acra-core/src/main/java/org/acra/file/ReportLocator.java b/acra-core/src/main/java/org/acra/file/ReportLocator.java deleted file mode 100644 index 7c09bf6d75..0000000000 --- a/acra-core/src/main/java/org/acra/file/ReportLocator.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (c) 2017 the ACRA team - * - * 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.file; - -import android.content.Context; -import androidx.annotation.NonNull; - -import java.io.File; -import java.util.Arrays; - -/** - * Locates crash reports. - * - * @author William Ferguson - * @since 4.8.0 - */ -public final class ReportLocator { - - // Folders under the app folder. - private static final String UNAPPROVED_FOLDER_NAME = "ACRA-unapproved"; - private static final String APPROVED_FOLDER_NAME = "ACRA-approved"; - - private final Context context; - - public ReportLocator(@NonNull Context context) { - this.context = context; - } - - @NonNull - public File getUnapprovedFolder() { - return context.getDir(UNAPPROVED_FOLDER_NAME, Context.MODE_PRIVATE); - } - - @NonNull - public File[] getUnapprovedReports() { - final File[] reports = getUnapprovedFolder().listFiles(); - if (reports == null) { - return new File[0]; - } - return reports; - } - - @NonNull - public File getApprovedFolder() { - return context.getDir(APPROVED_FOLDER_NAME, Context.MODE_PRIVATE); - } - - /** - * @return Approved reports sorted by creation time. - */ - @NonNull - public File[] getApprovedReports() { - final File[] reports = getApprovedFolder().listFiles(); - if (reports == null) { - return new File[0]; - } - Arrays.sort(reports, new LastModifiedComparator()); - return reports; - } -} diff --git a/acra-core/src/main/java/org/acra/file/ReportLocator.kt b/acra-core/src/main/java/org/acra/file/ReportLocator.kt new file mode 100644 index 0000000000..a8b97806f0 --- /dev/null +++ b/acra-core/src/main/java/org/acra/file/ReportLocator.kt @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2017 the ACRA team + * + * 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.file + +import android.content.Context +import java.io.File + +/** + * Locates crash reports. + * + * @author William Ferguson + * @since 4.8.0 + */ +class ReportLocator(private val context: Context) { + val unapprovedFolder: File + get() = context.getDir(UNAPPROVED_FOLDER_NAME, Context.MODE_PRIVATE) + val unapprovedReports: Array + get() = unapprovedFolder.listFiles() ?: emptyArray() + val approvedFolder: File + get() = context.getDir(APPROVED_FOLDER_NAME, Context.MODE_PRIVATE) + val approvedReports: Array + get() = approvedFolder.listFiles()?.sortedBy { it.lastModified() }?.toTypedArray() ?: emptyArray() + + companion object { + // Folders under the app folder. + private const val UNAPPROVED_FOLDER_NAME = "ACRA-unapproved" + private const val APPROVED_FOLDER_NAME = "ACRA-approved" + } +} \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/interaction/ReportInteraction.java b/acra-core/src/main/java/org/acra/interaction/ReportInteraction.kt similarity index 71% rename from acra-core/src/main/java/org/acra/interaction/ReportInteraction.java rename to acra-core/src/main/java/org/acra/interaction/ReportInteraction.kt index 71d58379a8..67f0464e93 100644 --- a/acra-core/src/main/java/org/acra/interaction/ReportInteraction.java +++ b/acra-core/src/main/java/org/acra/interaction/ReportInteraction.kt @@ -13,15 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +package org.acra.interaction -package org.acra.interaction; - -import android.content.Context; -import androidx.annotation.NonNull; -import org.acra.config.CoreConfiguration; -import org.acra.plugins.Plugin; - -import java.io.File; +import android.content.Context +import org.acra.config.CoreConfiguration +import org.acra.plugins.Plugin +import java.io.File /** * A user interaction before reports are sent @@ -29,8 +26,7 @@ * @author F43nd1r * @since 02.06.2017 */ -public interface ReportInteraction extends Plugin { - +interface ReportInteraction : Plugin { /** * Perform interaction synchronously * @@ -39,5 +35,5 @@ public interface ReportInteraction extends Plugin { * @param reportFile the file with the report content * @return if reports should be sent instantly */ - boolean performInteraction(@NonNull Context context, @NonNull CoreConfiguration config, @NonNull File reportFile); -} + fun performInteraction(context: Context, config: CoreConfiguration, reportFile: File): Boolean +} \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/interaction/ReportInteractionExecutor.java b/acra-core/src/main/java/org/acra/interaction/ReportInteractionExecutor.java deleted file mode 100644 index 887aeee835..0000000000 --- a/acra-core/src/main/java/org/acra/interaction/ReportInteractionExecutor.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (c) 2017 - * - * 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.interaction; - -import android.content.Context; -import androidx.annotation.NonNull; -import org.acra.ACRA; -import org.acra.config.CoreConfiguration; - -import java.io.File; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; - -/** - * Manages and executes all report interactions - * - * @author F43nd1r - * @since 10.10.2017 - */ - -public class ReportInteractionExecutor { - private final List reportInteractions; - private final Context context; - private final CoreConfiguration config; - - public ReportInteractionExecutor(@NonNull final Context context, @NonNull final CoreConfiguration config) { - this.context = context; - this.config = config; - reportInteractions = config.getPluginLoader().loadEnabled(config, ReportInteraction.class); - } - - public boolean hasInteractions() { - return reportInteractions.size() > 0; - } - - public boolean performInteractions(@NonNull final File reportFile) { - final ExecutorService executorService = Executors.newCachedThreadPool(); - final List> futures = new ArrayList<>(); - for (final ReportInteraction reportInteraction : reportInteractions) { - futures.add(executorService.submit(() -> { - if (ACRA.DEV_LOGGING) ACRA.log.d(ACRA.LOG_TAG, "Calling ReportInteraction of class " + reportInteraction.getClass().getName()); - return reportInteraction.performInteraction(context, config, reportFile); - })); - } - boolean sendReports = true; - for (Future future : futures) { - do { - try { - sendReports &= future.get(); - } catch (InterruptedException ignored) { - } catch (ExecutionException e) { - //ReportInteraction crashed, so ignore it - break; - } - } while (!future.isDone()); - } - return sendReports; - } -} diff --git a/acra-core/src/main/java/org/acra/interaction/ReportInteractionExecutor.kt b/acra-core/src/main/java/org/acra/interaction/ReportInteractionExecutor.kt new file mode 100644 index 0000000000..1c08435760 --- /dev/null +++ b/acra-core/src/main/java/org/acra/interaction/ReportInteractionExecutor.kt @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2017 + * + * 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.interaction + +import android.content.Context +import org.acra.ACRA +import org.acra.config.CoreConfiguration +import org.acra.plugins.loadEnabled +import java.io.File +import java.util.concurrent.ExecutionException +import java.util.concurrent.Executors +import java.util.concurrent.Future + +/** + * Manages and executes all report interactions + * + * @author F43nd1r + * @since 10.10.2017 + */ +class ReportInteractionExecutor(private val context: Context, private val config: CoreConfiguration) { + private val reportInteractions: List = config.pluginLoader.loadEnabled(config) + fun hasInteractions(): Boolean = reportInteractions.isNotEmpty() + + fun performInteractions(reportFile: File): Boolean { + val executorService = Executors.newCachedThreadPool() + val futures: List> = reportInteractions.map { + executorService.submit { + if (ACRA.DEV_LOGGING) ACRA.log.d(ACRA.LOG_TAG, "Calling ReportInteraction of class " + it.javaClass.name) + it.performInteraction(context, config, reportFile) + } + } + var sendReports = true + for (future in futures) { + do { + try { + sendReports = sendReports and future.get() + } catch (ignored: InterruptedException) { + } catch (e: ExecutionException) { + //ReportInteraction crashed, so ignore it + break + } + } while (!future.isDone) + } + return sendReports + } + +} \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/ktx/Extensions.kt b/acra-core/src/main/java/org/acra/ktx/Extensions.kt index a9282f2fe2..afe96639d0 100644 --- a/acra-core/src/main/java/org/acra/ktx/Extensions.kt +++ b/acra-core/src/main/java/org/acra/ktx/Extensions.kt @@ -37,9 +37,9 @@ inline fun CoreConfigurationBuilder.plugin(in } fun Throwable.sendWithAcra() { - ACRA.getErrorReporter().handleException(this) + ACRA.errorReporter.handleException(this) } fun Throwable.sendSilentlyWithAcra() { - ACRA.getErrorReporter().handleSilentException(this) + ACRA.errorReporter.handleSilentException(this) } \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/legacy/LegacyFileHandler.java b/acra-core/src/main/java/org/acra/legacy/LegacyFileHandler.java deleted file mode 100644 index a6fc594b76..0000000000 --- a/acra-core/src/main/java/org/acra/legacy/LegacyFileHandler.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (c) 2016 - * - * 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.legacy; - -import android.content.Context; -import android.content.SharedPreferences; - -/** - * Converts and moves legacy files - * - * @author F43nd1r - * @since 12.10.2016 - */ - -public class LegacyFileHandler { - private static final String PREF__LEGACY_ALREADY_CONVERTED_TO_4_8_0 = "acra.legacyAlreadyConvertedTo4.8.0"; - private static final String PREF__LEGACY_ALREADY_CONVERTED_TO_JSON = "acra.legacyAlreadyConvertedToJson"; - private final Context context; - private final SharedPreferences prefs; - - public LegacyFileHandler(Context context, SharedPreferences prefs) { - this.context = context; - this.prefs = prefs; - } - - public void updateToCurrentVersionIfNecessary() { - // Check prefs to see if we have converted from legacy (pre 4.8.0) ACRA - if (!prefs.getBoolean(PREF__LEGACY_ALREADY_CONVERTED_TO_4_8_0, false)) { - // If not then move reports to approved/unapproved folders and mark as converted. - new ReportMigrator(context).migrate(); - - // Mark as converted. - prefs.edit().putBoolean(PREF__LEGACY_ALREADY_CONVERTED_TO_4_8_0, true).apply(); - } - if (!prefs.getBoolean(PREF__LEGACY_ALREADY_CONVERTED_TO_JSON, false)) { - new ReportConverter(context).convert(); - - // Mark as converted. - prefs.edit().putBoolean(PREF__LEGACY_ALREADY_CONVERTED_TO_JSON, true).apply(); - } - } -} diff --git a/acra-core/src/main/java/org/acra/legacy/ReportConverter.java b/acra-core/src/main/java/org/acra/legacy/ReportConverter.java deleted file mode 100644 index cc83edf08c..0000000000 --- a/acra-core/src/main/java/org/acra/legacy/ReportConverter.java +++ /dev/null @@ -1,321 +0,0 @@ -/* - * Copyright (c) 2016 - * - * 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.legacy; - -import android.content.Context; -import androidx.annotation.NonNull; - -import org.acra.ACRA; -import org.acra.ACRAConstants; -import org.acra.ReportField; -import org.acra.data.CrashReportData; -import org.acra.file.CrashReportPersister; -import org.acra.file.ReportLocator; -import org.acra.util.IOUtils; -import org.json.JSONException; -import org.json.JSONObject; - -import java.io.BufferedInputStream; -import java.io.BufferedReader; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.Reader; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import static org.acra.ACRA.LOG_TAG; - -/** - * Converts acras old file format to json - * - * @author F43nd1r - * @since 12.10.2016 - */ - -class ReportConverter { - private static final int NONE = 0, SLASH = 1, UNICODE = 2, CONTINUE = 3, KEY_DONE = 4, IGNORE = 5; - private final Context context; - - ReportConverter(@NonNull Context context) { - this.context = context; - } - - void convert() { - ACRA.log.i(LOG_TAG, "Converting unsent ACRA reports to json"); - final ReportLocator locator = new ReportLocator(context); - final CrashReportPersister persister = new CrashReportPersister(); - final List reportFiles = new ArrayList<>(); - reportFiles.addAll(Arrays.asList(locator.getUnapprovedReports())); - reportFiles.addAll(Arrays.asList(locator.getApprovedReports())); - int converted = 0; - for (File report : reportFiles) { - InputStream in = null; - try { - in = new BufferedInputStream(new FileInputStream(report), ACRAConstants.DEFAULT_BUFFER_SIZE_IN_BYTES); - final CrashReportData data = legacyLoad(new InputStreamReader(in, "ISO8859-1")); //$NON-NLS-1$ - if (data.containsKey(ReportField.REPORT_ID) && data.containsKey(ReportField.USER_CRASH_DATE)) { - persister.store(data, report); - converted++; - } else { - //reports without these keys are probably invalid - IOUtils.deleteFile(report); - } - } catch (Exception e) { - try { - //If this succeeds the report has already been converted, happens e.g. on preference clear. - persister.load(report); - if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "Tried to convert already converted report file " + report.getPath() + ". Ignoring"); - } catch (Exception t) { - //File matches neither of the known formats, remove it. - ACRA.log.w(LOG_TAG, "Unable to read report file " + report.getPath() + ". Deleting", e); - IOUtils.deleteFile(report); - } - } finally { - IOUtils.safeClose(in); - } - } - ACRA.log.i(LOG_TAG, "Converted " + converted + " unsent reports"); - } - - - /** - * Loads properties from the specified InputStream. The properties are of - * the form key=value, one property per line. It may be not - * encode as 'ISO-8859-1'.The {@code Properties} file is interpreted - * according to the following rules: - *
    - *
  • Empty lines are ignored.
  • - *
  • Lines starting with either a "#" or a "!" are comment lines and are - * ignored.
  • - *
  • A backslash at the end of the line escapes the following newline - * character ("\r", "\n", "\r\n"). If there's a whitespace after the - * backslash it will just escape that whitespace instead of concatenating - * the lines. This does not apply to comment lines.
  • - *
  • A property line consists of the key, the space between the key and - * the value, and the value. The key goes up to the first whitespace, "=" or - * ":" that is not escaped. The space between the key and the value contains - * either one whitespace, one "=" or one ":" and any number of additional - * whitespaces before and after that character. The value starts with the - * first character after the space between the key and the value.
  • - *
  • Following escape sequences are recognized: "\ ", "\\", "\r", "\n", - * "\!", "\#", "\t", "\b", "\f", and "\uXXXX" (unicode character).
  • - *
- * - * @param reader Reader from which to read the properties of this CrashReportData. - * @return CrashReportData read from the supplied Reader. - * @throws java.io.IOException if the properties could not be read. - * @since 1.6 - */ - @NonNull - private synchronized CrashReportData legacyLoad(@NonNull Reader reader) throws IOException { - int mode = NONE, unicode = 0, count = 0; - char nextChar; - char[] buf = new char[40]; - int offset = 0, keyLength = -1, intVal; - boolean firstChar = true; - - final CrashReportData crashData = new CrashReportData(); - final BufferedReader br = new BufferedReader(reader, ACRAConstants.DEFAULT_BUFFER_SIZE_IN_BYTES); - try { - while (true) { - intVal = br.read(); - if (intVal == -1) { - break; - } - nextChar = (char) intVal; - - if (offset == buf.length) { - final char[] newBuf = new char[buf.length * 2]; - System.arraycopy(buf, 0, newBuf, 0, offset); - buf = newBuf; - } - if (mode == UNICODE) { - final int digit = Character.digit(nextChar, 16); - if (digit >= 0) { - unicode = (unicode << 4) + digit; - if (++count < 4) { - continue; - } - } else if (count <= 4) { - // luni.09=Invalid Unicode sequence: illegal character - throw new IllegalArgumentException("luni.09"); - } - mode = NONE; - buf[offset++] = (char) unicode; - if (nextChar != '\n' && nextChar != '\u0085') { - continue; - } - } - if (mode == SLASH) { - mode = NONE; - switch (nextChar) { - case '\r': - mode = CONTINUE; // Look for a following \n - continue; - case '\u0085': - case '\n': - mode = IGNORE; // Ignore whitespace on the next line - continue; - case 'b': - nextChar = '\b'; - break; - case 'f': - nextChar = '\f'; - break; - case 'n': - nextChar = '\n'; - break; - case 'r': - nextChar = '\r'; - break; - case 't': - nextChar = '\t'; - break; - case 'u': - mode = UNICODE; - unicode = count = 0; - continue; - } - } else { - switch (nextChar) { - case '#': - case '!': - if (firstChar) { - while (true) { - intVal = br.read(); - if (intVal == -1) { - break; - } - nextChar = (char) intVal; // & 0xff - // not - // required - if (nextChar == '\r' || nextChar == '\n' || nextChar == '\u0085') { - break; - } - } - continue; - } - break; - case '\n': - if (mode == CONTINUE) { // Part of a \r\n sequence - mode = IGNORE; // Ignore whitespace on the next line - continue; - } - // fall into the next case - case '\u0085': - case '\r': - mode = NONE; - firstChar = true; - if (offset > 0 || (offset == 0 && keyLength == 0)) { - if (keyLength == -1) { - keyLength = offset; - } - final String temp = new String(buf, 0, offset); - putKeyValue(crashData, temp.substring(0, keyLength), temp.substring(keyLength)); - } - keyLength = -1; - offset = 0; - continue; - case '\\': - if (mode == KEY_DONE) { - keyLength = offset; - } - mode = SLASH; - continue; - case ':': - case '=': - if (keyLength == -1) { // if parsing the key - mode = NONE; - keyLength = offset; - continue; - } - break; - } - if (Character.isWhitespace(nextChar)) { - if (mode == CONTINUE) { - mode = IGNORE; - } - // if key length == 0 or value length == 0 - if (offset == 0 || offset == keyLength || mode == IGNORE) { - continue; - } - if (keyLength == -1) { // if parsing the key - mode = KEY_DONE; - continue; - } - } - if (mode == IGNORE || mode == CONTINUE) { - mode = NONE; - } - } - firstChar = false; - if (mode == KEY_DONE) { - keyLength = offset; - mode = NONE; - } - buf[offset++] = nextChar; - } - if (mode == UNICODE && count <= 4) { - // luni.08=Invalid Unicode sequence: expected format \\uxxxx - throw new IllegalArgumentException("luni.08"); - } - if (keyLength == -1 && offset > 0) { - keyLength = offset; - } - if (keyLength >= 0) { - final String temp = new String(buf, 0, offset); - String value = temp.substring(keyLength); - if (mode == SLASH) { - value += "\u0000"; - } - putKeyValue(crashData, temp.substring(0, keyLength), value); - } - - IOUtils.safeClose(reader); - - return crashData; - } finally { - IOUtils.safeClose(br); - } - } - - private void putKeyValue(@NonNull CrashReportData crashData, @NonNull String key, @NonNull String value){ - try { - crashData.put(key, new JSONObject(value)); - } catch (JSONException e1) { - try { - crashData.put(key, Double.valueOf(value)); - } catch (NumberFormatException e2) { - switch (value) { - case "true": - crashData.put(key, true); - break; - case "false": - crashData.put(key, false); - break; - default: - crashData.put(key, value); - break; - } - } - } - } -} diff --git a/acra-core/src/main/java/org/acra/legacy/ReportMigrator.java b/acra-core/src/main/java/org/acra/legacy/ReportMigrator.java deleted file mode 100644 index 48aa8b0e96..0000000000 --- a/acra-core/src/main/java/org/acra/legacy/ReportMigrator.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (c) 2017 the ACRA team - * - * 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.legacy; - -import android.content.Context; -import androidx.annotation.NonNull; - -import org.acra.ACRA; -import org.acra.ACRAConstants; -import org.acra.file.CrashReportFileNameParser; -import org.acra.file.ReportLocator; - -import java.io.File; -import java.io.FilenameFilter; - -import static org.acra.ACRA.LOG_TAG; - -/** - * Migrates reports from the pre 4.8.0 location to the 4.8.0+ locations. - */ -final class ReportMigrator { - - private final Context context; - private final CrashReportFileNameParser fileNameParser = new CrashReportFileNameParser(); - private final ReportLocator reportLocator; - - ReportMigrator(@NonNull Context context) { - this.context = context; - this.reportLocator = new ReportLocator(context); - } - - void migrate() { - ACRA.log.i(LOG_TAG, "Migrating unsent ACRA reports to new file locations"); - - final File[] reportFiles = getCrashReportFiles(); - - for (final File file : reportFiles) { - // Move it to unapproved or approved folders. - final String fileName = file.getName(); - if (fileNameParser.isApproved(fileName)) { - if (file.renameTo(new File(reportLocator.getApprovedFolder(), fileName))) { - if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "Cold not migrate unsent ACRA crash report : " + fileName); - } - } else { - if (file.renameTo(new File(reportLocator.getUnapprovedFolder(), fileName))) { - if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "Cold not migrate unsent ACRA crash report : " + fileName); - } - } - } - ACRA.log.i(LOG_TAG, "Migrated " + reportFiles.length + " unsent reports"); - } - - /** - * Returns an array containing the names of pending crash report files. - * - * @return an array containing the names of pending crash report files. - */ - @NonNull - private File[] getCrashReportFiles() { - final File dir = context.getFilesDir(); - if (dir == null) { - ACRA.log.w(LOG_TAG, "Application files directory does not exist! The application may not be installed correctly. Please try reinstalling."); - return new File[0]; - } - - if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "Looking for error files in " + dir.getAbsolutePath()); - - // Filter for ".stacktrace" files - final FilenameFilter filter = (dir1, name) -> name.endsWith(ACRAConstants.REPORTFILE_EXTENSION); - final File[] result = dir.listFiles(filter); - return (result == null) ? new File[0] : result; - } - -} diff --git a/acra-core/src/main/java/org/acra/log/ACRALog.java b/acra-core/src/main/java/org/acra/log/ACRALog.java deleted file mode 100644 index 5a881ded9a..0000000000 --- a/acra-core/src/main/java/org/acra/log/ACRALog.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) 2017 the ACRA team - * - * 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.log; - -import androidx.annotation.Nullable; - -/** - * Responsible for providing ACRA classes with a platform neutral way of logging. - *

- * One reason for using this mechanism is to allow ACRA classes to use a logging system, - * but be able to execute in a test environment outside of an Android JVM. - *

- * @author William Ferguson - * @since 4.3.0 - */ -public interface ACRALog { - int v(java.lang.String tag, java.lang.String msg); - int v(java.lang.String tag, java.lang.String msg, java.lang.Throwable tr); - int d(java.lang.String tag, java.lang.String msg); - int d(java.lang.String tag, java.lang.String msg, java.lang.Throwable tr); - int i(java.lang.String tag, java.lang.String msg); - int i(java.lang.String tag, java.lang.String msg, java.lang.Throwable tr); - int w(java.lang.String tag, java.lang.String msg); - int w(java.lang.String tag, java.lang.String msg, java.lang.Throwable tr); - //public native boolean isLoggable(java.lang.String tag, int level); - int w(java.lang.String tag, java.lang.Throwable tr); - int e(java.lang.String tag, java.lang.String msg); - int e(java.lang.String tag, java.lang.String msg, java.lang.Throwable tr); - @Nullable - java.lang.String getStackTraceString(java.lang.Throwable tr); - //public native int println(int priority, java.lang.String tag, java.lang.String msg); -} diff --git a/acra-core/src/main/java/org/acra/log/ACRALog.kt b/acra-core/src/main/java/org/acra/log/ACRALog.kt new file mode 100644 index 0000000000..43369accc3 --- /dev/null +++ b/acra-core/src/main/java/org/acra/log/ACRALog.kt @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2017 the ACRA team + * + * 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.log + +/** + * Responsible for providing ACRA classes with a platform neutral way of logging. + * + * + * One reason for using this mechanism is to allow ACRA classes to use a logging system, + * but be able to execute in a test environment outside of an Android JVM. + * + * @author William Ferguson + * @since 4.3.0 + */ +interface ACRALog { + fun v(tag: String, msg: String): Int + fun v(tag: String, msg: String, tr: Throwable): Int + fun d(tag: String, msg: String): Int + fun d(tag: String, msg: String, tr: Throwable): Int + fun i(tag: String, msg: String): Int + fun i(tag: String, msg: String, tr: Throwable): Int + fun w(tag: String, msg: String): Int + fun w(tag: String, msg: String, tr: Throwable): Int + //public native boolean isLoggable(java.lang.String tag, int level); + fun w(tag: String, tr: Throwable): Int + fun e(tag: String, msg: String): Int + fun e(tag: String, msg: String, tr: Throwable): Int + fun getStackTraceString(tr: Throwable): String? + //public native int println(int priority, java.lang.String tag, java.lang.String msg); +} \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/log/AndroidLogDelegate.java b/acra-core/src/main/java/org/acra/log/AndroidLogDelegate.java deleted file mode 100644 index d8de08de4a..0000000000 --- a/acra-core/src/main/java/org/acra/log/AndroidLogDelegate.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright (c) 2017 the ACRA team - * - * 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.log; - - -import android.util.Log; - -/** - * Responsible for delegating calls to the Android logging system. - * - * @author William Ferguson - * @since 4.3.0 - */ -public final class AndroidLogDelegate implements ACRALog { - @Override - public int v(String tag, String msg) { - return Log.v(tag, msg); - } - @Override - public int v(String tag, String msg, Throwable tr) { - return Log.v(tag, msg, tr); - } - @Override - public int d(String tag, String msg) { - return Log.d(tag, msg); - } - @Override - public int d(String tag, String msg, Throwable tr) { - return Log.d(tag, msg, tr); - } - @Override - public int i(String tag, String msg) { - return Log.i(tag, msg); - } - @Override - public int i(String tag, String msg, Throwable tr) { - return Log.i(tag, msg, tr); - } - @Override - public int w(String tag, String msg) { - return Log.w(tag, msg); - } - @Override - public int w(String tag, String msg, Throwable tr) { - return Log.w(tag, msg, tr); - } - //public native boolean isLoggable(java.lang.String tag, int level); - @Override - public int w(String tag, Throwable tr) { - return Log.w(tag, tr); - } - @Override - public int e(String tag, String msg) { - return Log.e(tag, msg); - } - @Override - public int e(String tag, String msg, Throwable tr) { - return Log.e(tag, msg, tr); - } - @Override - public String getStackTraceString(Throwable tr) { - return Log.getStackTraceString(tr); - } - //public native int println(int priority, java.lang.String tag, java.lang.String msg); -} diff --git a/acra-core/src/main/java/org/acra/log/AndroidLogDelegate.kt b/acra-core/src/main/java/org/acra/log/AndroidLogDelegate.kt new file mode 100644 index 0000000000..487bb3e0b7 --- /dev/null +++ b/acra-core/src/main/java/org/acra/log/AndroidLogDelegate.kt @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2017 the ACRA team + * + * 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.log + +import android.util.Log + +/** + * Responsible for delegating calls to the Android logging system. + * + * @author William Ferguson + * @since 4.3.0 + */ +class AndroidLogDelegate : ACRALog { + override fun v(tag: String, msg: String): Int = Log.v(tag, msg) + + override fun v(tag: String, msg: String, tr: Throwable): Int = Log.v(tag, msg, tr) + + override fun d(tag: String, msg: String): Int = Log.d(tag, msg) + + override fun d(tag: String, msg: String, tr: Throwable): Int = Log.d(tag, msg, tr) + + override fun i(tag: String, msg: String): Int = Log.i(tag, msg) + + override fun i(tag: String, msg: String, tr: Throwable): Int = Log.i(tag, msg, tr) + + override fun w(tag: String, msg: String): Int = Log.w(tag, msg) + + override fun w(tag: String, msg: String, tr: Throwable): Int = Log.w(tag, msg, tr) + + override fun w(tag: String, tr: Throwable): Int = Log.w(tag, tr) + + override fun e(tag: String, msg: String): Int = Log.e(tag, msg) + + override fun e(tag: String, msg: String, tr: Throwable): Int = Log.e(tag, msg, tr) + + override fun getStackTraceString(tr: Throwable): String? = Log.getStackTraceString(tr) +} \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/log/HollowLog.java b/acra-core/src/main/java/org/acra/log/HollowLog.java deleted file mode 100644 index 6b7a6541dd..0000000000 --- a/acra-core/src/main/java/org/acra/log/HollowLog.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (c) 2017 the ACRA team - * - * 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.log; - -import androidx.annotation.Nullable; - -/** - * Stub implementation of {@link org.acra.log.ACRALog}, quenches all logging. - */ -@SuppressWarnings("unused") -public final class HollowLog implements ACRALog { - @Override - public int v(String tag, String msg) { - return 0; - } - - @Override - public int v(String tag, String msg, Throwable tr) { - return 0; - } - - @Override - public int d(String tag, String msg) { - return 0; - } - - @Override - public int d(String tag, String msg, Throwable tr) { - return 0; - } - - @Override - public int i(String tag, String msg) { - return 0; - } - - @Override - public int i(String tag, String msg, Throwable tr) { - return 0; - } - - @Override - public int w(String tag, String msg) { - return 0; - } - - @Override - public int w(String tag, String msg, Throwable tr) { - return 0; - } - - @Override - public int w(String tag, Throwable tr) { - return 0; - } - - @Override - public int e(String tag, String msg) { - return 0; - } - - @Override - public int e(String tag, String msg, Throwable tr) { - return 0; - } - - @Nullable - @Override - public String getStackTraceString(Throwable tr) { - return null; - } -} diff --git a/acra-core/src/main/java/org/acra/log/HollowLog.kt b/acra-core/src/main/java/org/acra/log/HollowLog.kt new file mode 100644 index 0000000000..793f954cce --- /dev/null +++ b/acra-core/src/main/java/org/acra/log/HollowLog.kt @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2017 the ACRA team + * + * 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.log + +/** + * Stub implementation of [org.acra.log.ACRALog], quenches all logging. + */ +class HollowLog : ACRALog { + override fun v(tag: String, msg: String): Int = 0 + + override fun v(tag: String, msg: String, tr: Throwable): Int = 0 + + override fun d(tag: String, msg: String): Int = 0 + + override fun d(tag: String, msg: String, tr: Throwable): Int = 0 + + override fun i(tag: String, msg: String): Int = 0 + + override fun i(tag: String, msg: String, tr: Throwable): Int = 0 + + override fun w(tag: String, msg: String): Int = 0 + + override fun w(tag: String, msg: String, tr: Throwable): Int = 0 + + override fun w(tag: String, tr: Throwable): Int = 0 + + override fun e(tag: String, msg: String): Int = 0 + + override fun e(tag: String, msg: String, tr: Throwable): Int = 0 + + override fun getStackTraceString(tr: Throwable): String? = null +} \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/log/extensions.kt b/acra-core/src/main/java/org/acra/log/extensions.kt new file mode 100644 index 0000000000..21f8703c53 --- /dev/null +++ b/acra-core/src/main/java/org/acra/log/extensions.kt @@ -0,0 +1,28 @@ +package org.acra.log + +import org.acra.ACRA + + +inline fun debug(messageGenerator: () -> String) { + if(ACRA.DEV_LOGGING) ACRA.log.d(ACRA.LOG_TAG, messageGenerator.invoke()) +} + +inline fun info(messageGenerator: () -> String) { + ACRA.log.i(ACRA.LOG_TAG, messageGenerator.invoke()) +} + +inline fun warn(messageGenerator: () -> String) { + ACRA.log.w(ACRA.LOG_TAG, messageGenerator.invoke()) +} + +inline fun warn(throwable: Throwable, messageGenerator: () -> String) { + ACRA.log.w(ACRA.LOG_TAG, messageGenerator.invoke(), throwable) +} + +inline fun error(messageGenerator: () -> String) { + ACRA.log.e(ACRA.LOG_TAG, messageGenerator.invoke()) +} + +inline fun error(throwable: Throwable, messageGenerator: () -> String) { + ACRA.log.e(ACRA.LOG_TAG, messageGenerator.invoke(), throwable) +} \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/plugins/HasConfigPlugin.java b/acra-core/src/main/java/org/acra/plugins/HasConfigPlugin.java deleted file mode 100644 index d0c148950a..0000000000 --- a/acra-core/src/main/java/org/acra/plugins/HasConfigPlugin.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * 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.plugins; - -import androidx.annotation.NonNull; -import org.acra.config.ConfigUtils; -import org.acra.config.Configuration; -import org.acra.config.CoreConfiguration; - -/** - * @author F43nd1r - * @since 18.04.18 - */ -public abstract class HasConfigPlugin implements Plugin { - private final Class configClass; - - public HasConfigPlugin(Class configClass) { - this.configClass = configClass; - } - - @Override - public final boolean enabled(@NonNull CoreConfiguration config) { - return ConfigUtils.getPluginConfiguration(config, configClass).enabled(); - } -} diff --git a/acra-core/src/main/java/org/acra/plugins/HasConfigPlugin.kt b/acra-core/src/main/java/org/acra/plugins/HasConfigPlugin.kt new file mode 100644 index 0000000000..37e72ffbe3 --- /dev/null +++ b/acra-core/src/main/java/org/acra/plugins/HasConfigPlugin.kt @@ -0,0 +1,28 @@ +/* + * 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.plugins + +import org.acra.config.ConfigUtils +import org.acra.config.Configuration +import org.acra.config.CoreConfiguration + +/** + * @author F43nd1r + * @since 18.04.18 + */ +abstract class HasConfigPlugin(private val configClass: Class) : Plugin { + override fun enabled(config: CoreConfiguration): Boolean = ConfigUtils.getPluginConfiguration(config, configClass).enabled() +} \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/plugins/Plugin.java b/acra-core/src/main/java/org/acra/plugins/Plugin.kt similarity index 79% rename from acra-core/src/main/java/org/acra/plugins/Plugin.java rename to acra-core/src/main/java/org/acra/plugins/Plugin.kt index 0c5efe34b4..e68e941b3c 100644 --- a/acra-core/src/main/java/org/acra/plugins/Plugin.java +++ b/acra-core/src/main/java/org/acra/plugins/Plugin.kt @@ -13,11 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +package org.acra.plugins -package org.acra.plugins; - -import androidx.annotation.NonNull; -import org.acra.config.CoreConfiguration; +import org.acra.config.CoreConfiguration /** * Marker interface for ACRA plugins @@ -25,14 +23,14 @@ * @author F43nd1r * @since 18.04.2018 */ -public interface Plugin { +interface Plugin { /** * controls if this instance is active * * @param config the current config * @return if this instance should be called */ - default boolean enabled(@NonNull CoreConfiguration config) { - return true; + fun enabled(config: CoreConfiguration): Boolean { + return true } -} +} \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/plugins/PluginLoader.java b/acra-core/src/main/java/org/acra/plugins/PluginLoader.kt similarity index 61% rename from acra-core/src/main/java/org/acra/plugins/PluginLoader.java rename to acra-core/src/main/java/org/acra/plugins/PluginLoader.kt index 56f0580b39..f95d97ddf4 100644 --- a/acra-core/src/main/java/org/acra/plugins/PluginLoader.java +++ b/acra-core/src/main/java/org/acra/plugins/PluginLoader.kt @@ -13,21 +13,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +package org.acra.plugins -package org.acra.plugins; - -import androidx.annotation.NonNull; -import org.acra.config.CoreConfiguration; - -import java.io.Serializable; -import java.util.List; +import org.acra.config.CoreConfiguration +import java.io.Serializable /** * @author lukas * @since 01.07.18 */ -public interface PluginLoader extends Serializable { - List load(@NonNull Class clazz); - - List loadEnabled(@NonNull CoreConfiguration config, @NonNull Class clazz); +interface PluginLoader : Serializable { + fun load(clazz: Class): List + fun loadEnabled(config: CoreConfiguration, clazz: Class): List } + +inline fun PluginLoader.loadEnabled(config: CoreConfiguration): List = loadEnabled(config, T::class.java) \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/plugins/ServicePluginLoader.java b/acra-core/src/main/java/org/acra/plugins/ServicePluginLoader.java deleted file mode 100644 index 40503249e6..0000000000 --- a/acra-core/src/main/java/org/acra/plugins/ServicePluginLoader.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * 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.plugins; - -import androidx.annotation.NonNull; -import org.acra.ACRA; -import org.acra.config.CoreConfiguration; -import org.acra.util.Predicate; - -import java.util.*; - -import static org.acra.ACRA.LOG_TAG; - -/** - * Utility to load {@link Plugin}s - * - * @author F43nd1r - * @since 18.04.18 - */ -public class ServicePluginLoader implements PluginLoader { - - @Override - public List load(@NonNull Class clazz) { - return loadInternal(clazz, plugin -> true); - } - - @Override - public List loadEnabled(@NonNull CoreConfiguration config, @NonNull Class clazz) { - return loadInternal(clazz, plugin -> plugin.enabled(config)); - } - - private List loadInternal(@NonNull Class clazz, Predicate shouldLoadPredicate) { - List plugins = new ArrayList<>(); - final ServiceLoader serviceLoader = ServiceLoader.load(clazz, getClass().getClassLoader()); - if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "ServicePluginLoader loading services from ServiceLoader : " + serviceLoader); - final Iterator iterator = serviceLoader.iterator(); - while (true) { - try { - if (!iterator.hasNext()) { - break; - } - } catch (ServiceConfigurationError e) { - ACRA.log.e(ACRA.LOG_TAG, "Broken ServiceLoader for " + clazz.getSimpleName(), e); - break; - } - try { - final T plugin = iterator.next(); - if (shouldLoadPredicate.apply(plugin)) { - if (ACRA.DEV_LOGGING) ACRA.log.d(ACRA.LOG_TAG, "Loaded " + clazz.getSimpleName() + " of type " + plugin.getClass().getName()); - plugins.add(plugin); - } else { - if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "Ignoring disabled " + clazz.getSimpleName() + " of type " + plugin.getClass().getSimpleName()); - } - } catch (ServiceConfigurationError e) { - ACRA.log.e(ACRA.LOG_TAG, "Unable to load " + clazz.getSimpleName(), e); - } - } - return plugins; - } -} diff --git a/acra-core/src/main/java/org/acra/plugins/ServicePluginLoader.kt b/acra-core/src/main/java/org/acra/plugins/ServicePluginLoader.kt new file mode 100644 index 0000000000..46c0eb210b --- /dev/null +++ b/acra-core/src/main/java/org/acra/plugins/ServicePluginLoader.kt @@ -0,0 +1,62 @@ +/* + * 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.plugins + +import org.acra.config.CoreConfiguration +import org.acra.log.debug +import org.acra.log.error +import java.util.* + +/** + * Utility to load [Plugin]s + * + * @author F43nd1r + * @since 18.04.18 + */ +class ServicePluginLoader : PluginLoader { + override fun load(clazz: Class): List = loadInternal(clazz) { true } + + override fun loadEnabled(config: CoreConfiguration, clazz: Class): List = loadInternal(clazz) { it.enabled(config) } + + private fun loadInternal(clazz: Class, shouldLoadPredicate: (T) -> Boolean): List { + val plugins: MutableList = ArrayList() + val serviceLoader = ServiceLoader.load(clazz, javaClass.classLoader) + debug { "ServicePluginLoader loading services from ServiceLoader : $serviceLoader" } + val iterator: Iterator = serviceLoader.iterator() + while (true) { + try { + if (!iterator.hasNext()) { + break + } + } catch (e: ServiceConfigurationError) { + error(e) { "Broken ServiceLoader for ${clazz.simpleName}" } + break + } + try { + val plugin = iterator.next() + if (shouldLoadPredicate.invoke(plugin)) { + debug { "Loaded ${clazz.simpleName} of type ${plugin.javaClass.name}" } + plugins.add(plugin) + } else { + debug { "Ignoring disabled ${clazz.simpleName} of type ${plugin.javaClass.simpleName}" } + } + } catch (e: ServiceConfigurationError) { + error(e) { "Unable to load ${clazz.simpleName}" } + } + } + return plugins + } +} \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/plugins/SimplePluginLoader.java b/acra-core/src/main/java/org/acra/plugins/SimplePluginLoader.java deleted file mode 100644 index 3c324ffdc9..0000000000 --- a/acra-core/src/main/java/org/acra/plugins/SimplePluginLoader.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * 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.plugins; - -import androidx.annotation.NonNull; -import org.acra.ACRA; -import org.acra.config.CoreConfiguration; - -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; - -import static org.acra.ACRA.LOG_TAG; - -/** - * @author lukas - * @since 01.07.18 - */ -public class SimplePluginLoader implements PluginLoader { - - private final Class[] plugins; - - @SafeVarargs - public SimplePluginLoader(@NonNull Class... plugins) { - this.plugins = plugins; - } - - @Override - public List load(@NonNull Class clazz) { - List list = new ArrayList<>(); - if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "SimplePluginLoader loading services from plugin classes : " + plugins); - for (Class plugin : plugins) { - if (clazz.isAssignableFrom(plugin)) { - try { - //noinspection unchecked - list.add((T) plugin.newInstance()); - if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "Loaded plugin from class : " + plugin); - } catch (Exception e) { - if (ACRA.DEV_LOGGING) ACRA.log.w(LOG_TAG, "Could not load plugin from class : " + plugin, e); - } - } - } - return list; - } - - @Override - public List loadEnabled(@NonNull CoreConfiguration config, @NonNull Class clazz) { - List list = load(clazz); - //noinspection Java8CollectionRemoveIf - for (Iterator iterator = list.iterator(); iterator.hasNext(); ) { - T plugin = iterator.next(); - if (!plugin.enabled(config)) { - if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "Removing disabled plugin : " + plugin); - iterator.remove(); - } - } - return list; - } -} diff --git a/acra-core/src/main/java/org/acra/plugins/SimplePluginLoader.kt b/acra-core/src/main/java/org/acra/plugins/SimplePluginLoader.kt new file mode 100644 index 0000000000..3cc90755ca --- /dev/null +++ b/acra-core/src/main/java/org/acra/plugins/SimplePluginLoader.kt @@ -0,0 +1,47 @@ +/* + * 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.plugins + +import org.acra.config.CoreConfiguration +import org.acra.log.debug +import org.acra.log.warn + +/** + * @author lukas + * @since 01.07.18 + */ +class SimplePluginLoader @SafeVarargs constructor(private vararg val plugins: Class) : PluginLoader { + override fun load(clazz: Class): List { + debug { "SimplePluginLoader loading services from plugin classes : $plugins" } + return plugins.mapNotNull { + if (clazz.isAssignableFrom(it)) { + try { + @Suppress("UNCHECKED_CAST") + val instance = it.newInstance() as T + debug { "Loaded plugin from class : $it" } + return@mapNotNull instance + } catch (e: Exception) { + warn(e) { "Could not load plugin from class : $it" } + } + } + null + } + } + + override fun loadEnabled(config: CoreConfiguration, clazz: Class): List { + return load(clazz).filter { it.enabled(config) } + } +} \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/prefs/SharedPreferencesFactory.java b/acra-core/src/main/java/org/acra/prefs/SharedPreferencesFactory.java deleted file mode 100644 index 49e668b311..0000000000 --- a/acra-core/src/main/java/org/acra/prefs/SharedPreferencesFactory.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright (c) 2017 the ACRA team - * - * 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.prefs; - -import android.content.Context; -import android.content.SharedPreferences; -import android.preference.PreferenceManager; -import androidx.annotation.NonNull; - -import org.acra.ACRA; -import org.acra.ACRAConstants; -import org.acra.config.CoreConfiguration; - -/** - * Responsible for creating a SharedPreferences instance which stores ACRA settings. - *

- * Retrieves the {@link SharedPreferences} instance where user adjustable settings for ACRA are stored. - * Default are the Application default SharedPreferences, but you can provide another SharedPreferences name with {@link org.acra.annotation.AcraCore#sharedPreferencesName()}. - *

- */ -public class SharedPreferencesFactory { - - private final Context context; - private final CoreConfiguration config; - - public SharedPreferencesFactory(@NonNull Context context, @NonNull CoreConfiguration config) { - this.context = context; - this.config = config; - } - - /** - * Check if the application default shared preferences contains true for the - * key "acra.disable", do not activate ACRA. Also checks the alternative - * opposite setting "acra.enable" if "acra.disable" is not found. - * - * @param prefs SharedPreferences to check to see whether ACRA should be - * disabled. - * @return true if prefs indicate that ACRA should be enabled. - */ - public static boolean shouldEnableACRA(@NonNull SharedPreferences prefs) { - boolean enableAcra = true; - try { - final boolean disableAcra = prefs.getBoolean(ACRA.PREF_DISABLE_ACRA, false); - enableAcra = prefs.getBoolean(ACRA.PREF_ENABLE_ACRA, !disableAcra); - } catch (Exception e) { - // In case of a ClassCastException - } - return enableAcra; - } - - /** - * @return The Shared Preferences where ACRA will retrieve its user adjustable setting. - */ - @NonNull - public SharedPreferences create() { - //noinspection ConstantConditions - if (context == null) { - throw new IllegalStateException("Cannot call ACRA.getACRASharedPreferences() before ACRA.init()."); - } else if (!ACRAConstants.DEFAULT_STRING_VALUE.equals(config.getSharedPreferencesName())) { - return context.getSharedPreferences(config.getSharedPreferencesName(), Context.MODE_PRIVATE); - } else { - return PreferenceManager.getDefaultSharedPreferences(context); - } - } -} diff --git a/acra-core/src/main/java/org/acra/prefs/SharedPreferencesFactory.kt b/acra-core/src/main/java/org/acra/prefs/SharedPreferencesFactory.kt new file mode 100644 index 0000000000..166bddd4db --- /dev/null +++ b/acra-core/src/main/java/org/acra/prefs/SharedPreferencesFactory.kt @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2017 the ACRA team + * + * 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.prefs + +import android.content.Context +import android.content.SharedPreferences +import android.preference.PreferenceManager +import org.acra.ACRA +import org.acra.ACRAConstants +import org.acra.config.CoreConfiguration + +/** + * Responsible for creating a SharedPreferences instance which stores ACRA settings. + * + * + * Retrieves the [SharedPreferences] instance where user adjustable settings for ACRA are stored. + * Default are the Application default SharedPreferences, but you can provide another SharedPreferences name with [org.acra.annotation.AcraCore.sharedPreferencesName]. + * + */ +class SharedPreferencesFactory(private val context: Context, private val config: CoreConfiguration) { + + /** + * @return The Shared Preferences where ACRA will retrieve its user adjustable setting. + */ + fun create(): SharedPreferences { + return if (ACRAConstants.DEFAULT_STRING_VALUE != config.sharedPreferencesName) { + context.getSharedPreferences(config.sharedPreferencesName, Context.MODE_PRIVATE) + } else { + @Suppress("DEPRECATION") + PreferenceManager.getDefaultSharedPreferences(context) + } + } + + companion object { + /** + * Check if the application default shared preferences contains true for the + * key "acra.disable", do not activate ACRA. Also checks the alternative + * opposite setting "acra.enable" if "acra.disable" is not found. + * + * @param prefs SharedPreferences to check to see whether ACRA should be + * disabled. + * @return true if prefs indicate that ACRA should be enabled. + */ + fun shouldEnableACRA(prefs: SharedPreferences): Boolean { + var enableAcra = true + try { + val disableAcra = prefs.getBoolean(ACRA.PREF_DISABLE_ACRA, false) + enableAcra = prefs.getBoolean(ACRA.PREF_ENABLE_ACRA, !disableAcra) + } catch (e: Exception) { + // In case of a ClassCastException + } + return enableAcra + } + } +} \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/reporter/ErrorReporterImpl.java b/acra-core/src/main/java/org/acra/reporter/ErrorReporterImpl.java deleted file mode 100644 index f6747eb473..0000000000 --- a/acra-core/src/main/java/org/acra/reporter/ErrorReporterImpl.java +++ /dev/null @@ -1,232 +0,0 @@ -/* - * Copyright 2010 Emmanuel Astier & 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.reporter; - -import android.app.Application; -import android.content.SharedPreferences; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import org.acra.ACRA; -import org.acra.ErrorReporter; -import org.acra.builder.LastActivityManager; -import org.acra.builder.ReportBuilder; -import org.acra.builder.ReportExecutor; -import org.acra.config.CoreConfiguration; -import org.acra.data.CrashReportDataFactory; -import org.acra.prefs.SharedPreferencesFactory; -import org.acra.scheduler.SchedulerStarter; -import org.acra.scheduler.SenderScheduler; -import org.acra.startup.StartupProcessorExecutor; -import org.acra.util.ApplicationStartupProcessor; -import org.acra.util.InstanceCreator; -import org.acra.util.ProcessFinisher; - -import java.lang.Thread.UncaughtExceptionHandler; -import java.util.HashMap; -import java.util.Map; - -import static org.acra.ACRA.LOG_TAG; - -/** - *

- * The ErrorReporter is a Singleton object in charge of collecting crash context - * data and sending crash reports. It registers itself as the Application's - * Thread default {@link UncaughtExceptionHandler}. - *

- *

- * When a crash occurs, it collects data of the crash context (device, system, - * stack trace...) and writes a report file in the application private - * directory, which may then be sent. - *

- */ -@SuppressWarnings({"WeakerAccess", "unused", "SameParameterValue"}) -public class ErrorReporterImpl implements Thread.UncaughtExceptionHandler, SharedPreferences.OnSharedPreferenceChangeListener, ErrorReporter { - - private final boolean supportedAndroidVersion; - private final Application context; - private final ReportExecutor reportExecutor; - private final Map customData = new HashMap<>(); - private final SchedulerStarter schedulerStarter; - private final UncaughtExceptionHandler defaultExceptionHandler; - - - /** - * @param context Context for the application in which ACRA is running. - * @param config AcraConfig to use when reporting and sending errors. - * @param enabled Whether this ErrorReporter should capture Exceptions and forward their reports. - * @param supportedAndroidVersion the minimal supported version - * @param checkReportsOnApplicationStart If reports should be checked on startup - */ - public ErrorReporterImpl(@NonNull Application context, @NonNull CoreConfiguration config, - boolean enabled, boolean supportedAndroidVersion, boolean checkReportsOnApplicationStart) { - - this.context = context; - this.supportedAndroidVersion = supportedAndroidVersion; - - final CrashReportDataFactory crashReportDataFactory = new CrashReportDataFactory(context, config); - crashReportDataFactory.collectStartUp(); - - defaultExceptionHandler = Thread.getDefaultUncaughtExceptionHandler(); - Thread.setDefaultUncaughtExceptionHandler(this); - - final LastActivityManager lastActivityManager = new LastActivityManager(this.context); - final InstanceCreator instanceCreator = new InstanceCreator(); - final ProcessFinisher processFinisher = new ProcessFinisher(context, config, lastActivityManager); - - schedulerStarter = new SchedulerStarter(context, config); - - reportExecutor = new ReportExecutor(context, config, crashReportDataFactory, defaultExceptionHandler, processFinisher, schedulerStarter, lastActivityManager); - reportExecutor.setEnabled(enabled); - - // Check for approved reports and send them (if enabled). - if (checkReportsOnApplicationStart) { - new StartupProcessorExecutor(context, config, schedulerStarter).processReports(enabled); - new ApplicationStartupProcessor(context, config).checkReports(); - } - } - - /** - * {@inheritDoc} - */ - @Override - public String putCustomData(@NonNull String key, @Nullable String value) { - return customData.put(key, value); - } - - /** - * {@inheritDoc} - */ - @Override - @Nullable - public String removeCustomData(@NonNull String key) { - return customData.remove(key); - } - - /** - * {@inheritDoc} - */ - @Override - public void clearCustomData() { - customData.clear(); - } - - /** - * {@inheritDoc} - */ - @Override - @Nullable - public String getCustomData(@NonNull String key) { - return customData.get(key); - } - - /* - * (non-Javadoc) - * - * @see - * java.lang.Thread.UncaughtExceptionHandler#uncaughtException(java.lang - * .Thread, java.lang.Throwable) - */ - @Override - public void uncaughtException(@Nullable Thread t, @NonNull Throwable e) { - - // If we're not enabled then just pass the Exception on to the defaultExceptionHandler. - if (!reportExecutor.isEnabled()) { - reportExecutor.handReportToDefaultExceptionHandler(t, e); - return; - } - - try { - ACRA.log.e(LOG_TAG, "ACRA caught a " + e.getClass().getSimpleName() + " for " + context.getPackageName(), e); - if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "Building report"); - - // Generate and send crash report - new ReportBuilder() - .uncaughtExceptionThread(t) - .exception(e) - .customData(customData) - .endApplication() - .build(reportExecutor); - - } catch (Exception fatality) { - // ACRA failed. Prevent any recursive call to ACRA.uncaughtException(), let the native reporter do its job. - ACRA.log.e(LOG_TAG, "ACRA failed to capture the error - handing off to native error reporter", fatality); - reportExecutor.handReportToDefaultExceptionHandler(t, e); - } - } - - /** - * {@inheritDoc} - */ - @Override - public void handleSilentException(@Nullable Throwable e) { - new ReportBuilder() - .exception(e) - .customData(customData) - .sendSilently() - .build(reportExecutor); - } - - /** - * {@inheritDoc} - */ - @Override - public void setEnabled(boolean enabled) { - if (supportedAndroidVersion) { - ACRA.log.i(LOG_TAG, "ACRA is " + (enabled ? "enabled" : "disabled") + " for " + context.getPackageName()); - reportExecutor.setEnabled(enabled); - } else { - ACRA.log.w(LOG_TAG, "ACRA requires ICS or greater. ACRA is disabled and will NOT catch crashes or send messages."); - } - } - - /** - * {@inheritDoc} - */ - @Override - public void handleException(@Nullable Throwable e, boolean endApplication) { - final ReportBuilder builder = new ReportBuilder(); - builder.exception(e) - .customData(customData); - if (endApplication) { - builder.endApplication(); - } - builder.build(reportExecutor); - } - - /** - * {@inheritDoc} - */ - @Override - public void handleException(@Nullable Throwable e) { - handleException(e, false); - } - - @Override - public SenderScheduler getReportScheduler() { - return schedulerStarter.getSenderScheduler(); - } - - @Override - public void onSharedPreferenceChanged(@NonNull SharedPreferences sharedPreferences, @Nullable String key) { - if (ACRA.PREF_DISABLE_ACRA.equals(key) || ACRA.PREF_ENABLE_ACRA.equals(key)) { - setEnabled(SharedPreferencesFactory.shouldEnableACRA(sharedPreferences)); - } - } - - public void unregister() { - Thread.setDefaultUncaughtExceptionHandler(defaultExceptionHandler); - } -} \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/reporter/ErrorReporterImpl.kt b/acra-core/src/main/java/org/acra/reporter/ErrorReporterImpl.kt new file mode 100644 index 0000000000..ef0c3353a1 --- /dev/null +++ b/acra-core/src/main/java/org/acra/reporter/ErrorReporterImpl.kt @@ -0,0 +1,176 @@ +/* + * Copyright 2010 Emmanuel Astier & 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.reporter + +import android.app.Application +import android.content.SharedPreferences +import android.content.SharedPreferences.OnSharedPreferenceChangeListener +import org.acra.ACRA +import org.acra.ErrorReporter +import org.acra.builder.LastActivityManager +import org.acra.builder.ReportBuilder +import org.acra.builder.ReportExecutor +import org.acra.config.CoreConfiguration +import org.acra.data.CrashReportDataFactory +import org.acra.log.debug +import org.acra.log.error +import org.acra.log.info +import org.acra.log.warn +import org.acra.prefs.SharedPreferencesFactory +import org.acra.scheduler.SchedulerStarter +import org.acra.scheduler.SenderScheduler +import org.acra.startup.StartupProcessorExecutor +import org.acra.util.InstanceCreator +import org.acra.util.ProcessFinisher +import java.util.* + +/** + * + * + * The ErrorReporter is a Singleton object in charge of collecting crash context + * data and sending crash reports. It registers itself as the Application's + * Thread default [Thread.UncaughtExceptionHandler]. + * + * + * + * When a crash occurs, it collects data of the crash context (device, system, + * stack trace...) and writes a report file in the application private + * directory, which may then be sent. + * + * @param context Context for the application in which ACRA is running. + * @param config AcraConfig to use when reporting and sending errors. + * @param enabled Whether this ErrorReporter should capture Exceptions and forward their reports. + * @param supportedAndroidVersion the minimal supported version + * @param checkReportsOnApplicationStart If reports should be checked on startup + */ +class ErrorReporterImpl(private val context: Application, config: CoreConfiguration, + enabled: Boolean, private val supportedAndroidVersion: Boolean, checkReportsOnApplicationStart: Boolean) : Thread.UncaughtExceptionHandler, + OnSharedPreferenceChangeListener, ErrorReporter { + private val reportExecutor: ReportExecutor + private val customData: MutableMap = HashMap() + private val schedulerStarter: SchedulerStarter + private val defaultExceptionHandler: Thread.UncaughtExceptionHandler + + override fun putCustomData(key: String, value: String): String? { + return customData.put(key, value) + } + + override fun removeCustomData(key: String): String? { + return customData.remove(key) + } + + override fun clearCustomData() { + customData.clear() + } + + override fun getCustomData(key: String): String? { + return customData[key] + } + + /* + * (non-Javadoc) + * + * @see + * java.lang.Thread.UncaughtExceptionHandler#uncaughtException(java.lang + * .Thread, java.lang.Throwable) + */ + override fun uncaughtException(t: Thread?, e: Throwable) { + // If we're not enabled then just pass the Exception on to the defaultExceptionHandler. + if (!reportExecutor.isEnabled) { + reportExecutor.handReportToDefaultExceptionHandler(t, e) + return + } + try { + error(e) { "ACRA caught a ${e.javaClass.simpleName} for ${context.packageName}" } + debug { "Building report" } + + // Generate and send crash report + ReportBuilder() + .uncaughtExceptionThread(t) + .exception(e) + .customData(customData) + .endApplication() + .build(reportExecutor) + } catch (fatality: Exception) { + // ACRA failed. Prevent any recursive call to ACRA.uncaughtException(), let the native reporter do its job. + error(fatality) { "ACRA failed to capture the error - handing off to native error reporter" } + reportExecutor.handReportToDefaultExceptionHandler(t, e) + } + } + + override fun handleSilentException(e: Throwable) { + ReportBuilder() + .exception(e) + .customData(customData) + .sendSilently() + .build(reportExecutor) + } + + override fun setEnabled(enabled: Boolean) { + if (supportedAndroidVersion) { + info { "ACRA is ${if (enabled) "enabled" else "disabled"} for ${context.packageName}" } + reportExecutor.isEnabled = enabled + } else { + warn { "ACRA requires ICS or greater. ACRA is disabled and will NOT catch crashes or send messages." } + } + } + + override fun handleException(e: Throwable, endApplication: Boolean) { + val builder = ReportBuilder() + builder.exception(e) + .customData(customData) + if (endApplication) { + builder.endApplication() + } + builder.build(reportExecutor) + } + + override fun handleException(e: Throwable) { + handleException(e, false) + } + + override val reportScheduler: SenderScheduler + get() = schedulerStarter.senderScheduler + + override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String?) { + if (ACRA.PREF_DISABLE_ACRA == key || ACRA.PREF_ENABLE_ACRA == key) { + setEnabled(SharedPreferencesFactory.shouldEnableACRA(sharedPreferences)) + } + } + + fun unregister() { + Thread.setDefaultUncaughtExceptionHandler(defaultExceptionHandler) + } + + /** + */ + init { + val crashReportDataFactory = CrashReportDataFactory(context, config) + crashReportDataFactory.collectStartUp() + defaultExceptionHandler = Thread.getDefaultUncaughtExceptionHandler()!! + Thread.setDefaultUncaughtExceptionHandler(this) + val lastActivityManager = LastActivityManager(context) + val processFinisher = ProcessFinisher(context, config, lastActivityManager) + schedulerStarter = SchedulerStarter(context, config) + reportExecutor = ReportExecutor(context, config, crashReportDataFactory, defaultExceptionHandler, processFinisher, schedulerStarter, lastActivityManager) + reportExecutor.isEnabled = enabled + + // Check for approved reports and send them (if enabled). + if (checkReportsOnApplicationStart) { + StartupProcessorExecutor(context, config, schedulerStarter).processReports(enabled) + } + } +} \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/scheduler/DefaultSenderScheduler.java b/acra-core/src/main/java/org/acra/scheduler/DefaultSenderScheduler.java deleted file mode 100644 index 82ae7f41b9..0000000000 --- a/acra-core/src/main/java/org/acra/scheduler/DefaultSenderScheduler.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * 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.job.JobInfo; -import android.app.job.JobScheduler; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.os.Build; -import androidx.annotation.NonNull; -import androidx.annotation.RequiresApi; -import org.acra.config.CoreConfiguration; -import org.acra.sender.JobSenderService; -import org.acra.sender.LegacySenderService; -import org.acra.sender.SendingConductor; -import org.acra.util.BundleWrapper; -import org.acra.util.IOUtils; - -/** - * Simply schedules sending instantly - * - * @author F43nd1r - * @since 18.04.18 - */ -public class DefaultSenderScheduler implements SenderScheduler { - private final Context context; - private final CoreConfiguration config; - - public DefaultSenderScheduler(@NonNull Context context, @NonNull CoreConfiguration config) { - this.context = context; - this.config = config; - } - - @Override - public void scheduleReportSending(boolean onlySendSilentReports) { - BundleWrapper.Internal extras = BundleWrapper.create(); - extras.putString(LegacySenderService.EXTRA_ACRA_CONFIG, IOUtils.serialize(config)); - extras.putBoolean(LegacySenderService.EXTRA_ONLY_SEND_SILENT_REPORTS, onlySendSilentReports); - configureExtras(extras); - SendingConductor conductor = new SendingConductor(context, config); - if (!conductor.getSenderInstances(false).isEmpty()) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) { - JobScheduler scheduler = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE); - assert scheduler != null; - JobInfo.Builder builder = new JobInfo.Builder(0, new ComponentName(context, JobSenderService.class)).setExtras(extras.asPersistableBundle()); - configureJob(builder); - scheduler.schedule(builder.build()); - } else { - final Intent intent = new Intent(); - intent.putExtras(extras.asBundle()); - intent.setComponent(new ComponentName(context, LegacySenderService.class)); - context.startService(intent); - } - } - if (!conductor.getSenderInstances(true).isEmpty()) { - conductor.sendReports(true, extras); - } - } - - /** - * allows to perform additional configuration in subclasses - * - * @param job the job builder - */ - @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) - protected void configureJob(@NonNull JobInfo.Builder job) { - job.setOverrideDeadline(0); - } - - /** - * allows to provide additional extras to senders - * - * @param extras the extras bundle - */ - protected void configureExtras(@NonNull BundleWrapper extras) { - } -} diff --git a/acra-core/src/main/java/org/acra/scheduler/DefaultSenderScheduler.kt b/acra-core/src/main/java/org/acra/scheduler/DefaultSenderScheduler.kt new file mode 100644 index 0000000000..7aab4dd2f7 --- /dev/null +++ b/acra-core/src/main/java/org/acra/scheduler/DefaultSenderScheduler.kt @@ -0,0 +1,81 @@ +/* + * 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.job.JobInfo +import android.app.job.JobScheduler +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.os.Build +import android.os.Bundle +import androidx.annotation.RequiresApi +import org.acra.config.CoreConfiguration +import org.acra.sender.JobSenderService +import org.acra.sender.LegacySenderService +import org.acra.sender.SendingConductor +import org.acra.util.IOUtils +import org.acra.util.toPersistableBundle + +/** + * Simply schedules sending instantly + * + * @author F43nd1r + * @since 18.04.18 + */ +@Suppress("MemberVisibilityCanBePrivate", "UNUSED_PARAMETER") +open class DefaultSenderScheduler(private val context: Context, private val config: CoreConfiguration) : SenderScheduler { + override fun scheduleReportSending(onlySendSilentReports: Boolean) { + val extras = Bundle() + extras.putString(LegacySenderService.EXTRA_ACRA_CONFIG, IOUtils.serialize(config)) + extras.putBoolean(LegacySenderService.EXTRA_ONLY_SEND_SILENT_REPORTS, onlySendSilentReports) + configureExtras(extras) + val conductor = SendingConductor(context, config) + if (conductor.getSenderInstances(false).isNotEmpty()) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) { + val scheduler = (context.getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler) + val builder = JobInfo.Builder(0, ComponentName(context, JobSenderService::class.java)).setExtras(extras.toPersistableBundle()) + configureJob(builder) + scheduler.schedule(builder.build()) + } else { + val intent = Intent() + intent.putExtras(extras) + intent.component = ComponentName(context, LegacySenderService::class.java) + context.startService(intent) + } + } + if (conductor.getSenderInstances(true).isNotEmpty()) { + conductor.sendReports(true, extras) + } + } + + /** + * allows to perform additional configuration in subclasses + * + * @param job the job builder + */ + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + protected open fun configureJob(job: JobInfo.Builder) { + job.setOverrideDeadline(0) + } + + /** + * allows to provide additional extras to senders + * + * @param extras the extras bundle + */ + protected fun configureExtras(extras: Bundle) {} +} \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/scheduler/SchedulerStarter.java b/acra-core/src/main/java/org/acra/scheduler/SchedulerStarter.java deleted file mode 100644 index ff89a55a19..0000000000 --- a/acra-core/src/main/java/org/acra/scheduler/SchedulerStarter.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * 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 androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import org.acra.ACRA; -import org.acra.config.CoreConfiguration; -import org.acra.file.ReportLocator; - -import java.io.File; -import java.util.List; - -import static org.acra.ACRA.LOG_TAG; - -/** - * @author F43nd1r - * @since 18.04.18 - */ -public class SchedulerStarter { - - private final ReportLocator locator; - private final SenderScheduler senderScheduler; - - public SchedulerStarter(@NonNull Context context, @NonNull CoreConfiguration config) { - locator = new ReportLocator(context); - List schedulerFactories = config.getPluginLoader().loadEnabled(config, SenderSchedulerFactory.class); - if (schedulerFactories.isEmpty()) { - senderScheduler = new DefaultSenderScheduler(context, config); - } else { - senderScheduler = schedulerFactories.get(0).create(context, config); - if (schedulerFactories.size() > 1) ACRA.log.w(ACRA.LOG_TAG, "More than one SenderScheduler found. Will use only " + senderScheduler.getClass().getSimpleName()); - } - } - - /** - * Starts a process to start sending outstanding error reports. - * - * @param report If not null, this report will be approved before scheduling. - * @param onlySendSilentReports If true then only send silent reports. - */ - public void scheduleReports(@Nullable File report, boolean onlySendSilentReports) { - if (report != null) { - if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "Mark " + report.getName() + " as approved."); - final File approvedReport = new File(locator.getApprovedFolder(), report.getName()); - if (!report.renameTo(approvedReport)) { - ACRA.log.w(LOG_TAG, "Could not rename approved report from " + report + " to " + approvedReport); - } - } - if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "Schedule report sending"); - senderScheduler.scheduleReportSending(onlySendSilentReports); - } - - public SenderScheduler getSenderScheduler() { - return senderScheduler; - } -} diff --git a/acra-core/src/main/java/org/acra/scheduler/SchedulerStarter.kt b/acra-core/src/main/java/org/acra/scheduler/SchedulerStarter.kt new file mode 100644 index 0000000000..6f249f10bb --- /dev/null +++ b/acra-core/src/main/java/org/acra/scheduler/SchedulerStarter.kt @@ -0,0 +1,61 @@ +/* + * 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 org.acra.config.CoreConfiguration +import org.acra.file.ReportLocator +import org.acra.log.debug +import org.acra.log.warn +import org.acra.plugins.loadEnabled +import java.io.File + +/** + * @author F43nd1r + * @since 18.04.18 + */ +class SchedulerStarter(context: Context, config: CoreConfiguration) { + private val locator = ReportLocator(context) + val senderScheduler: SenderScheduler + + /** + * Starts a process to start sending outstanding error reports. + * + * @param report If not null, this report will be approved before scheduling. + * @param onlySendSilentReports If true then only send silent reports. + */ + fun scheduleReports(report: File?, onlySendSilentReports: Boolean) { + if (report != null) { + debug { "Mark ${report.name} as approved." } + val approvedReport = File(locator.approvedFolder, report.name) + if (!report.renameTo(approvedReport)) { + warn { "Could not rename approved report from $report to $approvedReport" } + } + } + debug { "Schedule report sending" } + senderScheduler.scheduleReportSending(onlySendSilentReports) + } + + init { + val schedulerFactories: List = config.pluginLoader.loadEnabled(config) + if (schedulerFactories.isEmpty()) { + senderScheduler = DefaultSenderScheduler(context, config) + } else { + senderScheduler = schedulerFactories[0].create(context, config) + if (schedulerFactories.size > 1) warn { "More than one SenderScheduler found. Will use only " + senderScheduler.javaClass.simpleName } + } + } +} \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/scheduler/SenderScheduler.java b/acra-core/src/main/java/org/acra/scheduler/SenderScheduler.kt similarity index 82% rename from acra-core/src/main/java/org/acra/scheduler/SenderScheduler.java rename to acra-core/src/main/java/org/acra/scheduler/SenderScheduler.kt index 151103ac0d..9b6e692041 100644 --- a/acra-core/src/main/java/org/acra/scheduler/SenderScheduler.java +++ b/acra-core/src/main/java/org/acra/scheduler/SenderScheduler.kt @@ -13,14 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -package org.acra.scheduler; +package org.acra.scheduler /** * @author F43nd1r * @since 18.04.18 */ -public interface SenderScheduler { - - void scheduleReportSending(boolean onlySendSilentReports); -} +interface SenderScheduler { + fun scheduleReportSending(onlySendSilentReports: Boolean) +} \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/scheduler/SenderSchedulerFactory.java b/acra-core/src/main/java/org/acra/scheduler/SenderSchedulerFactory.kt similarity index 71% rename from acra-core/src/main/java/org/acra/scheduler/SenderSchedulerFactory.java rename to acra-core/src/main/java/org/acra/scheduler/SenderSchedulerFactory.kt index 9aa6b4d698..3434e9af7e 100644 --- a/acra-core/src/main/java/org/acra/scheduler/SenderSchedulerFactory.java +++ b/acra-core/src/main/java/org/acra/scheduler/SenderSchedulerFactory.kt @@ -13,26 +13,21 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +package org.acra.scheduler -package org.acra.scheduler; - -import android.content.Context; -import androidx.annotation.NonNull; -import org.acra.config.CoreConfiguration; -import org.acra.plugins.Plugin; +import android.content.Context +import org.acra.config.CoreConfiguration +import org.acra.plugins.Plugin /** * @author F43nd1r * @since 20.04.18 */ -public interface SenderSchedulerFactory extends Plugin { - - +interface SenderSchedulerFactory : Plugin { /** * @param context a context. * @param config Configuration to use when sending reports. * @return Fully configured instance of the relevant SenderScheduler. */ - @NonNull - SenderScheduler create(@NonNull Context context, @NonNull CoreConfiguration config); -} + fun create(context: Context, config: CoreConfiguration): SenderScheduler +} \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/sender/JobSenderService.java b/acra-core/src/main/java/org/acra/sender/JobSenderService.java deleted file mode 100644 index fe4c7e4727..0000000000 --- a/acra-core/src/main/java/org/acra/sender/JobSenderService.java +++ /dev/null @@ -1,37 +0,0 @@ -package org.acra.sender; - -import android.app.job.JobParameters; -import android.app.job.JobService; -import android.os.Build; -import android.os.PersistableBundle; -import androidx.annotation.RequiresApi; -import org.acra.config.CoreConfiguration; -import org.acra.util.BundleWrapper; -import org.acra.util.IOUtils; - -/** - * Job service sending reports. has to run in the :acra process - * - * @author Lukas - * @since 31.12.2018 - */ -@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP_MR1) -public class JobSenderService extends JobService { - @Override - public boolean onStartJob(JobParameters params) { - PersistableBundle extras = params.getExtras(); - CoreConfiguration config = IOUtils.deserialize(CoreConfiguration.class, extras.getString(LegacySenderService.EXTRA_ACRA_CONFIG)); - if (config != null) { - new Thread(() -> { - new SendingConductor(this, config).sendReports(false, BundleWrapper.wrap(extras)); - jobFinished(params, false); - }).start(); - } - return true; - } - - @Override - public boolean onStopJob(JobParameters params) { - return true; - } -} diff --git a/acra-core/src/main/java/org/acra/sender/JobSenderService.kt b/acra-core/src/main/java/org/acra/sender/JobSenderService.kt new file mode 100644 index 0000000000..ed030d1770 --- /dev/null +++ b/acra-core/src/main/java/org/acra/sender/JobSenderService.kt @@ -0,0 +1,34 @@ +package org.acra.sender + +import android.app.job.JobParameters +import android.app.job.JobService +import android.os.Build +import android.os.Bundle +import androidx.annotation.RequiresApi +import org.acra.config.CoreConfiguration +import org.acra.util.IOUtils + +/** + * Job service sending reports. has to run in the :acra process + * + * @author Lukas + * @since 31.12.2018 + */ +@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP_MR1) +class JobSenderService : JobService() { + override fun onStartJob(params: JobParameters): Boolean { + val extras = params.extras + val config = IOUtils.deserialize(CoreConfiguration::class.java, extras.getString(LegacySenderService.EXTRA_ACRA_CONFIG)) + if (config != null) { + Thread { + SendingConductor(this, config).sendReports(false, Bundle(extras)) + jobFinished(params, false) + }.start() + } + return true + } + + override fun onStopJob(params: JobParameters): Boolean { + return true + } +} \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/sender/LegacySenderService.java b/acra-core/src/main/java/org/acra/sender/LegacySenderService.java deleted file mode 100644 index 06e48bb175..0000000000 --- a/acra-core/src/main/java/org/acra/sender/LegacySenderService.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2017 - * - * 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.app.Service; -import android.content.Intent; -import android.os.IBinder; -import androidx.annotation.Nullable; -import org.acra.ACRA; -import org.acra.config.CoreConfiguration; -import org.acra.util.BundleWrapper; -import org.acra.util.IOUtils; - -import static org.acra.ACRA.LOG_TAG; - -/** - * Plain service sending reports. has to run in the :acra process. - * Only used when no JobScheduler is available. - * - * @author Lukas - */ -public class LegacySenderService extends Service { - - public static final String EXTRA_ONLY_SEND_SILENT_REPORTS = "onlySendSilentReports"; - public static final String EXTRA_ACRA_CONFIG = "acraConfig"; - - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - if (intent.hasExtra(EXTRA_ACRA_CONFIG)) { - final CoreConfiguration config = IOUtils.deserialize(CoreConfiguration.class, intent.getStringExtra(LegacySenderService.EXTRA_ACRA_CONFIG)); - if (config != null) { - new Thread(() -> { - new SendingConductor(this, config).sendReports( false, BundleWrapper.wrap(intent.getExtras())); - stopSelf(); - }).start(); - } - } else { - if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "SenderService was started but no valid intent was delivered, will now quit"); - } - return START_REDELIVER_INTENT; - } - - @Nullable - @Override - public IBinder onBind(Intent intent) { - return null; - } -} diff --git a/acra-core/src/main/java/org/acra/sender/LegacySenderService.kt b/acra-core/src/main/java/org/acra/sender/LegacySenderService.kt new file mode 100644 index 0000000000..bd4f3a8320 --- /dev/null +++ b/acra-core/src/main/java/org/acra/sender/LegacySenderService.kt @@ -0,0 +1,54 @@ +/* + * Copyright 2017 + * + * 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.app.Service +import android.content.Intent +import android.os.Bundle +import android.os.IBinder +import org.acra.config.CoreConfiguration +import org.acra.log.debug +import org.acra.util.IOUtils + +/** + * Plain service sending reports. has to run in the :acra process. + * Only used when no JobScheduler is available. + * + * @author Lukas + */ +class LegacySenderService : Service() { + override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int { + if (intent.hasExtra(EXTRA_ACRA_CONFIG)) { + val config = IOUtils.deserialize(CoreConfiguration::class.java, intent.getStringExtra(EXTRA_ACRA_CONFIG)) + if (config != null) { + Thread { + SendingConductor(this, config).sendReports(false, intent.extras ?: Bundle()) + stopSelf() + }.start() + } + } else { + debug { "SenderService was started but no valid intent was delivered, will now quit" } + } + return START_REDELIVER_INTENT + } + + override fun onBind(intent: Intent): IBinder? = null + + companion object { + const val EXTRA_ONLY_SEND_SILENT_REPORTS = "onlySendSilentReports" + const val EXTRA_ACRA_CONFIG = "acraConfig" + } +} \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/sender/NullSender.java b/acra-core/src/main/java/org/acra/sender/NullSender.kt similarity index 57% rename from acra-core/src/main/java/org/acra/sender/NullSender.java rename to acra-core/src/main/java/org/acra/sender/NullSender.kt index 9d2ab11376..16a499457b 100644 --- a/acra-core/src/main/java/org/acra/sender/NullSender.java +++ b/acra-core/src/main/java/org/acra/sender/NullSender.kt @@ -13,23 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +package org.acra.sender -package org.acra.sender; - -import android.content.Context; -import androidx.annotation.NonNull; - -import org.acra.ACRA; -import org.acra.data.CrashReportData; - -import static org.acra.ACRA.LOG_TAG; +import android.content.Context +import org.acra.data.CrashReportData +import org.acra.log.warn /** * Sends no report. */ -final class NullSender implements ReportSender { - @Override - public void send(@NonNull Context context, @NonNull CrashReportData errorContent) { - ACRA.log.w(LOG_TAG, context.getPackageName() + " reports will NOT be sent - no valid ReportSender was found!"); +internal class NullSender : ReportSender { + override fun send(context: Context, errorContent: CrashReportData) { + warn { "${context.packageName} reports will NOT be sent - no valid ReportSender was found!" } } -} +} \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/sender/ReportDistributor.java b/acra-core/src/main/java/org/acra/sender/ReportDistributor.java deleted file mode 100644 index f1f02ac4ca..0000000000 --- a/acra-core/src/main/java/org/acra/sender/ReportDistributor.java +++ /dev/null @@ -1,153 +0,0 @@ -/* - * 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 androidx.annotation.NonNull; -import org.acra.ACRA; -import org.acra.config.CoreConfiguration; -import org.acra.config.DefaultRetryPolicy; -import org.acra.config.RetryPolicy; -import org.acra.data.CrashReportData; -import org.acra.file.CrashReportPersister; -import org.acra.util.BundleWrapper; -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 CoreConfiguration config; - private final List reportSenders; - private final BundleWrapper extras; - - /** - * 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. - * @param extras additional information set in a {@link org.acra.scheduler.DefaultSenderScheduler} - */ - ReportDistributor(@NonNull Context context, @NonNull CoreConfiguration config, @NonNull List reportSenders, BundleWrapper extras) { - this.context = context; - this.config = config; - this.reportSenders = reportSenders; - this.extras = extras; - } - - /** - * Send report via all senders. - * - * @param reportFile Report to send. - * @return if distributing was successful - */ - public boolean 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.deleteFile(reportFile); - return true; - } catch (RuntimeException e) { - ACRA.log.e(LOG_TAG, "Failed to send crash reports for " + reportFile, e); - IOUtils.deleteFile(reportFile); - } catch (IOException e) { - ACRA.log.e(LOG_TAG, "Failed to load crash report for " + reportFile, e); - IOUtils.deleteFile(reportFile); - } catch (JSONException e) { - ACRA.log.e(LOG_TAG, "Failed to load crash report for " + reportFile, e); - IOUtils.deleteFile(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. - } - return false; - } - - /** - * 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.getSendReportsInDevMode()) { - final List failedSenders = new LinkedList<>(); - for (ReportSender sender : reportSenders) { - try { - if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "Sending report using " + sender.getClass().getName()); - sender.send(context, errorContent, extras); - 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.getRetryPolicyClass(), DefaultRetryPolicy::new).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; - } - } -} diff --git a/acra-core/src/main/java/org/acra/sender/ReportDistributor.kt b/acra-core/src/main/java/org/acra/sender/ReportDistributor.kt new file mode 100644 index 0000000000..3b414c5ab0 --- /dev/null +++ b/acra-core/src/main/java/org/acra/sender/ReportDistributor.kt @@ -0,0 +1,132 @@ +/* + * 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.os.Bundle +import org.acra.config.CoreConfiguration +import org.acra.config.DefaultRetryPolicy +import org.acra.config.RetryPolicy.FailedSender +import org.acra.data.CrashReportData +import org.acra.file.CrashReportPersister +import org.acra.log.debug +import org.acra.log.info +import org.acra.log.warn +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.* + +/** + * Distributes reports to all Senders. + * + * @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. + * @param extras additional information set in a [org.acra.scheduler.DefaultSenderScheduler] + * + * @author William Ferguson + * @since 4.8.0 + */ +internal class ReportDistributor(private val context: Context, private val config: CoreConfiguration, private val reportSenders: List, private val extras: Bundle) { + /** + * Send report via all senders. + * + * @param reportFile Report to send. + * @return if distributing was successful + */ + fun distribute(reportFile: File): Boolean { + info { "Sending report $reportFile" } + try { + val persister = CrashReportPersister() + val previousCrashReport = persister.load(reportFile) + sendCrashReport(previousCrashReport) + IOUtils.deleteFile(reportFile) + return true + } catch (e: RuntimeException) { + org.acra.log.error(e) { "Failed to send crash reports for $reportFile" } + IOUtils.deleteFile(reportFile) + } catch (e: IOException) { + org.acra.log.error(e) { "Failed to send crash reports for $reportFile" } + IOUtils.deleteFile(reportFile) + } catch (e: JSONException) { + org.acra.log.error(e) { "Failed to send crash reports for $reportFile" } + IOUtils.deleteFile(reportFile) + } catch (e: ReportSenderException) { + org.acra.log.error(e) { "Failed to send crash reports for $reportFile" } + // 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. + } + return false + } + + /** + * 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. + */ + @Throws(ReportSenderException::class) + private fun sendCrashReport(errorContent: CrashReportData) { + if (!isDebuggable || config.sendReportsInDevMode) { + val failedSenders: MutableList = LinkedList() + for (sender in reportSenders) { + try { + debug { "Sending report using " + sender.javaClass.name } + sender.send(context, errorContent, extras) + debug { "Sent report using " + sender.javaClass.name } + } catch (e: ReportSenderException) { + failedSenders.add(FailedSender(sender, e)) + } + } + when { + failedSenders.isEmpty() -> debug { "Report was sent by all senders" } + InstanceCreator.create(config.retryPolicyClass) { DefaultRetryPolicy() }.shouldRetrySend(reportSenders, failedSenders) -> throw ReportSenderException( + "Policy marked this task as incomplete. ACRA will try to send this report again.", failedSenders[0].exception) + else -> warn { + val builder = StringBuilder("ReportSenders of classes [") + for (failedSender in failedSenders) { + builder.append(failedSender.sender.javaClass.name) + builder.append(", ") + } + builder.append("] failed, but Policy marked this task as complete. ACRA will not send this report again.") + builder.toString() + } + } + } + } + + /** + * Returns true if the application is debuggable. + * + * @return true if the application is debuggable. + */ + private val isDebuggable: Boolean + get() { + return try { + context.packageManager.getApplicationInfo(context.packageName, 0).flags and ApplicationInfo.FLAG_DEBUGGABLE > 0 + } catch (e: PackageManager.NameNotFoundException) { + false + } + } +} \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/sender/ReportSender.java b/acra-core/src/main/java/org/acra/sender/ReportSender.kt similarity index 59% rename from acra-core/src/main/java/org/acra/sender/ReportSender.java rename to acra-core/src/main/java/org/acra/sender/ReportSender.kt index 4dafa9cd3e..af11db016b 100644 --- a/acra-core/src/main/java/org/acra/sender/ReportSender.java +++ b/acra-core/src/main/java/org/acra/sender/ReportSender.kt @@ -13,48 +13,48 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.acra.sender; +package org.acra.sender -import android.content.Context; -import androidx.annotation.NonNull; - -import org.acra.data.CrashReportData; -import org.acra.util.BundleWrapper; +import android.content.Context +import android.os.Bundle +import org.acra.data.CrashReportData +import org.acra.sender.ReportSenderException /** * A simple interface for defining various crash report senders. * * @author Kevin Gaudin */ -public interface ReportSender { - +interface ReportSender { /** * Send crash report data. - *

- * Method will be called from the {@link LegacySenderService}. + * + * + * Method will be called from the [ReportDistributor]. * * @param context Android Context in which to send the crash report. * @param errorContent Stores key/value pairs for each report field. - * @throws ReportSenderException If anything goes fatally wrong during the handling of crash data, you can (should) throw a {@link ReportSenderException} with a custom message. + * @throws ReportSenderException If anything goes fatally wrong during the handling of crash data, you can (should) throw a [ReportSenderException] with a custom message. */ - default void send(@NonNull Context context, @NonNull CrashReportData errorContent) throws ReportSenderException { + @Throws(ReportSenderException::class) + fun send(context: Context, errorContent: CrashReportData) { } /** * Send crash report data. - *

- * Method will be called from the {@link LegacySenderService}. + * + * + * Method will be called from the [ReportDistributor]. * * @param context Android Context in which to send the crash report. * @param errorContent Stores key/value pairs for each report field. - * @param extras additional information set in a {@link org.acra.scheduler.DefaultSenderScheduler} - * @throws ReportSenderException If anything goes fatally wrong during the handling of crash data, you can (should) throw a {@link ReportSenderException} with a custom message. + * @param extras additional information set in a [org.acra.scheduler.DefaultSenderScheduler] + * @throws ReportSenderException If anything goes fatally wrong during the handling of crash data, you can (should) throw a [ReportSenderException] with a custom message. */ - default void send(@NonNull Context context, @NonNull CrashReportData errorContent, @NonNull BundleWrapper extras) throws ReportSenderException { - send(context, errorContent); - } + @Throws(ReportSenderException::class) + fun send(context: Context, errorContent: CrashReportData, extras: Bundle) = send(context, errorContent) - default boolean requiresForeground() { - return false; + fun requiresForeground(): Boolean { + return false } -} +} \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/sender/ReportSenderException.java b/acra-core/src/main/java/org/acra/sender/ReportSenderException.kt similarity index 67% rename from acra-core/src/main/java/org/acra/sender/ReportSenderException.java rename to acra-core/src/main/java/org/acra/sender/ReportSenderException.kt index 16196280a1..2dd2665b19 100644 --- a/acra-core/src/main/java/org/acra/sender/ReportSenderException.java +++ b/acra-core/src/main/java/org/acra/sender/ReportSenderException.kt @@ -13,35 +13,29 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.acra.sender; +package org.acra.sender /** * This exception is thrown when an error occurred while sending crash data in a - * {@link ReportSender} implementation. + * [ReportSender] implementation. * * @author Kevin Gaudin */ -@SuppressWarnings("serial") -public class ReportSenderException extends Exception { - +class ReportSenderException : Exception { /** - * Creates a new {@link ReportSenderException} instance. You can provide a + * Creates a new [ReportSenderException] instance. You can provide a * detailed message to explain what went wrong. * * @param detailMessage A message to explain the cause of this exception. * @param throwable An optional throwable which caused this Exception. */ - public ReportSenderException(String detailMessage, Throwable throwable) { - super(detailMessage, throwable); - } + constructor(detailMessage: String?, throwable: Throwable?) : super(detailMessage, throwable) /** - * Creates a new {@link ReportSenderException} instance. You can provide a + * Creates a new [ReportSenderException] instance. You can provide a * detailed message to explain what went wrong. * * @param detailMessage A message to explain the cause of this exception. - **/ - public ReportSenderException(String detailMessage) { - super(detailMessage); - } -} + */ + constructor(detailMessage: String?) : super(detailMessage) +} \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/sender/ReportSenderFactory.java b/acra-core/src/main/java/org/acra/sender/ReportSenderFactory.kt similarity index 60% rename from acra-core/src/main/java/org/acra/sender/ReportSenderFactory.java rename to acra-core/src/main/java/org/acra/sender/ReportSenderFactory.kt index d40d270250..c7e35437c6 100644 --- a/acra-core/src/main/java/org/acra/sender/ReportSenderFactory.java +++ b/acra-core/src/main/java/org/acra/sender/ReportSenderFactory.kt @@ -13,30 +13,28 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +package org.acra.sender -package org.acra.sender; - -import android.content.Context; -import androidx.annotation.NonNull; -import org.acra.config.CoreConfiguration; -import org.acra.plugins.Plugin; +import android.content.Context +import org.acra.config.CoreConfiguration +import org.acra.plugins.Plugin /** - * Factory for creating and configuring a {@link ReportSender} instance. + * Factory for creating and configuring a [ReportSender] instance. * Implementations must have a no argument constructor. - *

- * Each configured ReportSenderFactory is created within the {@link LegacySenderService} - * and is used to construct and configure a single {@link ReportSender}. - *

+ * + * + * Each configured ReportSenderFactory is created within the [LegacySenderService] + * and is used to construct and configure a single [ReportSender]. + * + * * Created by William on 4-JAN-2016. */ -public interface ReportSenderFactory extends Plugin { - +interface ReportSenderFactory : Plugin { /** * @param context a context. * @param config Configuration to use when sending reports. * @return Fully configured instance of the relevant ReportSender. */ - @NonNull - ReportSender create(@NonNull Context context, @NonNull CoreConfiguration config); -} + fun create(context: Context, config: CoreConfiguration): ReportSender +} \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/sender/SendingConductor.java b/acra-core/src/main/java/org/acra/sender/SendingConductor.java deleted file mode 100644 index d5a74f5f10..0000000000 --- a/acra-core/src/main/java/org/acra/sender/SendingConductor.java +++ /dev/null @@ -1,105 +0,0 @@ -package org.acra.sender; - -import android.content.Context; -import android.os.Handler; -import android.os.Looper; -import android.widget.Toast; - -import androidx.annotation.NonNull; - -import org.acra.ACRA; -import org.acra.ACRAConstants; -import org.acra.config.CoreConfiguration; -import org.acra.file.CrashReportFileNameParser; -import org.acra.file.ReportLocator; -import org.acra.plugins.PluginLoader; -import org.acra.util.BundleWrapper; -import org.acra.util.ToastSender; - -import java.io.File; -import java.util.ArrayList; -import java.util.List; - -import static org.acra.ACRA.LOG_TAG; - -/** - * @author Lukas - * @since 31.12.2018 - */ -public class SendingConductor { - private final Context context; - private final CoreConfiguration config; - private final ReportLocator locator; - - public SendingConductor(@NonNull Context context, @NonNull CoreConfiguration config) { - this.context = context; - this.config = config; - locator = new ReportLocator(context); - } - - public void sendReports(boolean foreground, BundleWrapper extras) { - if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "About to start sending reports from SenderService"); - try { - final List senderInstances = getSenderInstances(foreground); - - if (senderInstances.isEmpty()) { - if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "No ReportSenders configured - adding NullSender"); - senderInstances.add(new NullSender()); - } - - // Get approved reports - final File[] reports = locator.getApprovedReports(); - - final ReportDistributor reportDistributor = new ReportDistributor(context, config, senderInstances, extras); - - // Iterate over approved reports and send via all Senders. - int reportsSentCount = 0; // Use to rate limit sending - final CrashReportFileNameParser fileNameParser = new CrashReportFileNameParser(); - boolean anyNonSilent = false; - for (final File report : reports) { - final boolean isNonSilent = !fileNameParser.isSilent(report.getName()); - if (extras.getBoolean(LegacySenderService.EXTRA_ONLY_SEND_SILENT_REPORTS) && isNonSilent) { - continue; - } - anyNonSilent |= isNonSilent; - - if (reportsSentCount >= ACRAConstants.MAX_SEND_REPORTS) { - break; // send only a few reports to avoid overloading the network - } - - if (reportDistributor.distribute(report)) { - reportsSentCount++; - } - } - final String toast; - if (anyNonSilent && !(toast = reportsSentCount > 0 ? config.getReportSendSuccessToast() : config.getReportSendFailureToast()).isEmpty()) { - if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "About to show " + (reportsSentCount > 0 ? "success" : "failure") + " toast"); - new Handler(Looper.getMainLooper()).post(() -> ToastSender.sendToast(context, toast, Toast.LENGTH_LONG)); - } - } catch (Exception e) { - ACRA.log.e(LOG_TAG, "", e); - } - - if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "Finished sending reports from SenderService"); - } - - @NonNull - public List getSenderInstances(boolean foreground) { - final List factories; - if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "Using PluginLoader to find ReportSender factories"); - final PluginLoader loader = config.getPluginLoader(); - factories = loader.loadEnabled(config, ReportSenderFactory.class); - if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "reportSenderFactories : " + factories); - - - final List reportSenders = new ArrayList<>(); - for (ReportSenderFactory factory : factories) { - final ReportSender sender = factory.create(context, config); - if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "Adding reportSender : " + sender); - if (foreground == sender.requiresForeground()) { - reportSenders.add(sender); - } - } - return reportSenders; - } -} diff --git a/acra-core/src/main/java/org/acra/sender/SendingConductor.kt b/acra-core/src/main/java/org/acra/sender/SendingConductor.kt new file mode 100644 index 0000000000..f25e695435 --- /dev/null +++ b/acra-core/src/main/java/org/acra/sender/SendingConductor.kt @@ -0,0 +1,72 @@ +package org.acra.sender + +import android.content.Context +import android.os.Bundle +import android.os.Handler +import android.os.Looper +import android.widget.Toast +import org.acra.ACRAConstants +import org.acra.config.CoreConfiguration +import org.acra.file.CrashReportFileNameParser +import org.acra.file.ReportLocator +import org.acra.log.debug +import org.acra.log.error +import org.acra.plugins.loadEnabled +import org.acra.util.ToastSender.sendToast + +/** + * @author Lukas + * @since 31.12.2018 + */ +class SendingConductor(private val context: Context, private val config: CoreConfiguration) { + private val locator: ReportLocator = ReportLocator(context) + + fun sendReports(foreground: Boolean, extras: Bundle) { + debug { "About to start sending reports from SenderService" } + try { + val senderInstances = getSenderInstances(foreground).toMutableList() + if (senderInstances.isEmpty()) { + debug { "No ReportSenders configured - adding NullSender" } + senderInstances.add(NullSender()) + } + + // Get approved reports + val reports = locator.approvedReports + val reportDistributor = ReportDistributor(context, config, senderInstances, extras) + + // Iterate over approved reports and send via all Senders. + var reportsSentCount = 0 // Use to rate limit sending + val fileNameParser = CrashReportFileNameParser() + var anyNonSilent = false + for (report in reports) { + val isNonSilent = !fileNameParser.isSilent(report.name) + if (extras.getBoolean(LegacySenderService.EXTRA_ONLY_SEND_SILENT_REPORTS) && isNonSilent) { + continue + } + anyNonSilent = anyNonSilent or isNonSilent + if (reportsSentCount >= ACRAConstants.MAX_SEND_REPORTS) { + break // send only a few reports to avoid overloading the network + } + if (reportDistributor.distribute(report)) { + reportsSentCount++ + } + } + val toast: String = if (reportsSentCount > 0) config.reportSendSuccessToast else config.reportSendFailureToast + if (anyNonSilent && toast.isNotEmpty()) { + debug { "About to show " + (if (reportsSentCount > 0) "success" else "failure") + " toast" } + Handler(Looper.getMainLooper()).post { sendToast(context, toast, Toast.LENGTH_LONG) } + } + } catch (e: Exception) { + error(e) { "" } + } + debug { "Finished sending reports from SenderService" } + } + + fun getSenderInstances(foreground: Boolean): List { + debug { "Using PluginLoader to find ReportSender factories" } + val factories: List = config.pluginLoader.loadEnabled(config) + debug { "reportSenderFactories : $factories" } + return factories.map { it.create(context, config).also { debug { "Adding reportSender : $it" } } }.filter { foreground == it.requiresForeground() } + } + +} \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/startup/Report.java b/acra-core/src/main/java/org/acra/startup/Report.java deleted file mode 100644 index 96ec599b39..0000000000 --- a/acra-core/src/main/java/org/acra/startup/Report.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * 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.startup; - -import java.io.File; - -/** - * @author lukas - * @since 15.09.18 - */ -public class Report { - private final File file; - private final boolean approved; - private boolean delete; - private boolean approve; - - public Report(File file, boolean approved) { - this.file = file; - this.approved = approved; - delete = false; - approve = false; - } - - public File getFile() { - return file; - } - - public boolean isApproved() { - return approved; - } - - public void delete() { - delete = true; - } - - public void approve() { - approve = true; - } - - boolean isDelete() { - return delete; - } - - boolean isApprove() { - return approve; - } -} diff --git a/acra-core/src/main/java/org/acra/util/Predicate.java b/acra-core/src/main/java/org/acra/startup/Report.kt similarity index 78% rename from acra-core/src/main/java/org/acra/util/Predicate.java rename to acra-core/src/main/java/org/acra/startup/Report.kt index 0330420f4c..3cf2585d07 100644 --- a/acra-core/src/main/java/org/acra/util/Predicate.java +++ b/acra-core/src/main/java/org/acra/startup/Report.kt @@ -13,15 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +package org.acra.startup -package org.acra.util; +import java.io.File /** * @author lukas - * @since 19.07.18 + * @since 15.09.18 */ -@FunctionalInterface -public interface Predicate { - - boolean apply(T t); -} +class Report(val file: File, val approved: Boolean) { + var delete = false + var approve = false +} \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/startup/StartupProcessor.java b/acra-core/src/main/java/org/acra/startup/StartupProcessor.kt similarity index 64% rename from acra-core/src/main/java/org/acra/startup/StartupProcessor.java rename to acra-core/src/main/java/org/acra/startup/StartupProcessor.kt index 17e99623ce..06212cb6d6 100644 --- a/acra-core/src/main/java/org/acra/startup/StartupProcessor.java +++ b/acra-core/src/main/java/org/acra/startup/StartupProcessor.kt @@ -13,20 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +package org.acra.startup -package org.acra.startup; - -import android.content.Context; -import androidx.annotation.NonNull; -import org.acra.config.CoreConfiguration; -import org.acra.plugins.Plugin; - -import java.util.List; +import android.content.Context +import org.acra.config.CoreConfiguration +import org.acra.plugins.Plugin /** * @author lukas * @since 15.09.18 */ -public interface StartupProcessor extends Plugin { - void processReports(@NonNull Context context, @NonNull CoreConfiguration config, List reports); -} +interface StartupProcessor : Plugin { + fun processReports(context: Context, config: CoreConfiguration, reports: List) +} \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/startup/StartupProcessorExecutor.java b/acra-core/src/main/java/org/acra/startup/StartupProcessorExecutor.java deleted file mode 100644 index df0ec773d7..0000000000 --- a/acra-core/src/main/java/org/acra/startup/StartupProcessorExecutor.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * 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.startup; - -import android.content.Context; -import android.os.Handler; -import androidx.annotation.NonNull; -import org.acra.ACRA; -import org.acra.config.CoreConfiguration; -import org.acra.file.CrashReportFileNameParser; -import org.acra.file.ReportLocator; -import org.acra.interaction.ReportInteractionExecutor; -import org.acra.scheduler.SchedulerStarter; - -import java.io.File; -import java.util.ArrayList; -import java.util.Calendar; -import java.util.List; - -import static org.acra.ACRA.LOG_TAG; - -/** - * @author lukas - * @since 15.09.18 - */ -public class StartupProcessorExecutor { - private final Context context; - private final CoreConfiguration config; - private final ReportLocator reportLocator; - private final SchedulerStarter schedulerStarter; - private final CrashReportFileNameParser fileNameParser; - - public StartupProcessorExecutor(@NonNull final Context context, @NonNull final CoreConfiguration config, @NonNull SchedulerStarter schedulerStarter) { - this.context = context; - this.config = config; - this.reportLocator = new ReportLocator(context); - this.schedulerStarter = schedulerStarter; - this.fileNameParser = new CrashReportFileNameParser(); - } - - public void processReports(boolean isAcraEnabled) { - final Calendar now = Calendar.getInstance(); - //application is not ready in onAttachBaseContext, so delay this. also run it on a background thread because we're doing disk I/O - new Handler(context.getMainLooper()).post(() -> new Thread(() -> { - final List reports = new ArrayList<>(); - for (File r : reportLocator.getUnapprovedReports()) { - reports.add(new Report(r, false)); - } - for (File r : reportLocator.getApprovedReports()) { - reports.add(new Report(r, true)); - } - final List startupProcessors = config.getPluginLoader().loadEnabled(config, StartupProcessor.class); - for (StartupProcessor processor : startupProcessors) { - processor.processReports(context, config, reports); - } - boolean send = false; - for (Report report : reports) { - // ignore reports that were just created for now, they might be handled in another thread - if (fileNameParser.getTimestamp(report.getFile().getName()).before(now)) { - if (report.isDelete()) { - if (!report.getFile().delete()) { - ACRA.log.w(LOG_TAG, "Could not delete report " + report.getFile()); - } - } else if (report.isApproved()) { - send = true; - } else if (report.isApprove() && isAcraEnabled) { - new ReportInteractionExecutor(context, config).performInteractions(report.getFile()); - } - } - } - if(send && isAcraEnabled) { - schedulerStarter.scheduleReports(null, false); - } - }).start()); - - } -} diff --git a/acra-core/src/main/java/org/acra/startup/StartupProcessorExecutor.kt b/acra-core/src/main/java/org/acra/startup/StartupProcessorExecutor.kt new file mode 100644 index 0000000000..fca7465f91 --- /dev/null +++ b/acra-core/src/main/java/org/acra/startup/StartupProcessorExecutor.kt @@ -0,0 +1,62 @@ +/* + * 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.startup + +import android.content.Context +import android.os.Handler +import org.acra.config.CoreConfiguration +import org.acra.file.CrashReportFileNameParser +import org.acra.file.ReportLocator +import org.acra.interaction.ReportInteractionExecutor +import org.acra.log.warn +import org.acra.plugins.loadEnabled +import org.acra.scheduler.SchedulerStarter +import java.util.* + +/** + * @author lukas + * @since 15.09.18 + */ +class StartupProcessorExecutor(private val context: Context, private val config: CoreConfiguration, private val schedulerStarter: SchedulerStarter) { + private val reportLocator: ReportLocator = ReportLocator(context) + private val fileNameParser: CrashReportFileNameParser = CrashReportFileNameParser() + + fun processReports(isAcraEnabled: Boolean) { + val now = Calendar.getInstance() + //application is not ready in onAttachBaseContext, so delay this. also run it on a background thread because we're doing disk I/O + Handler(context.mainLooper).post { + Thread { + val reports = reportLocator.unapprovedReports.map { Report(it, false) } + reportLocator.approvedReports.map { Report(it, true) } + config.pluginLoader.loadEnabled(config).forEach { it.processReports(context, config, reports) } + var send = false + for (report in reports) { + // ignore reports that were just created for now, they might be handled in another thread + if (fileNameParser.getTimestamp(report.file.name).before(now)) { + when { + report.delete -> if (!report.file.delete()) warn { "Could not delete report ${report.file}" } + report.approved -> send = true + report.approve && isAcraEnabled -> ReportInteractionExecutor(context, config).performInteractions(report.file) + } + } + } + if (send && isAcraEnabled) { + schedulerStarter.scheduleReports(null, false) + } + }.start() + } + } + +} \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/startup/UnapprovedStartupProcessor.java b/acra-core/src/main/java/org/acra/startup/UnapprovedStartupProcessor.java deleted file mode 100644 index 05c6c872c0..0000000000 --- a/acra-core/src/main/java/org/acra/startup/UnapprovedStartupProcessor.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * 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.startup; - -import android.content.Context; - -import androidx.annotation.NonNull; - -import com.google.auto.service.AutoService; - -import org.acra.config.CoreConfiguration; -import org.acra.file.LastModifiedComparator; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -/** - * @author lukas - * @since 15.09.18 - */ -@AutoService(StartupProcessor.class) -public class UnapprovedStartupProcessor implements StartupProcessor { - @Override - public void processReports(@NonNull Context context, @NonNull CoreConfiguration config, List reports) { - if (config.getDeleteUnapprovedReportsOnApplicationStart()) { - final List sort = new ArrayList<>(); - for (Report report : reports) { - if (!report.isApproved()) { - sort.add(report); - } - } - if (!sort.isEmpty()) { - final LastModifiedComparator comparator = new LastModifiedComparator(); - Collections.sort(sort, (r1, r2) -> comparator.compare(r1.getFile(), r2.getFile())); - for (int i = 0; i < sort.size() - 1; i++) { - sort.get(i).delete(); - } - sort.get(sort.size() - 1).approve(); - } - } - } -} diff --git a/acra-core/src/main/java/org/acra/startup/UnapprovedStartupProcessor.kt b/acra-core/src/main/java/org/acra/startup/UnapprovedStartupProcessor.kt new file mode 100644 index 0000000000..e3f1d81a6a --- /dev/null +++ b/acra-core/src/main/java/org/acra/startup/UnapprovedStartupProcessor.kt @@ -0,0 +1,46 @@ +/* + * 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.startup + +import android.content.Context +import com.google.auto.service.AutoService +import org.acra.config.CoreConfiguration +import java.util.* + +/** + * @author lukas + * @since 15.09.18 + */ +@AutoService(StartupProcessor::class) +class UnapprovedStartupProcessor : StartupProcessor { + override fun processReports(context: Context, config: CoreConfiguration, reports: List) { + if (config.deleteUnapprovedReportsOnApplicationStart) { + val sort: MutableList = ArrayList() + for (report in reports) { + if (!report.approved) { + sort.add(report) + } + } + if (sort.isNotEmpty()) { + sort.sortBy { it.file.lastModified() } + for (i in 0 until sort.size - 1) { + sort[i].delete = true + } + sort[sort.size - 1].approve = true + } + } + } +} \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/util/ApplicationStartupProcessor.java b/acra-core/src/main/java/org/acra/util/ApplicationStartupProcessor.java deleted file mode 100644 index d4cf123961..0000000000 --- a/acra-core/src/main/java/org/acra/util/ApplicationStartupProcessor.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (c) 2017 the ACRA team - * - * 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.util; - -import android.content.Context; -import android.content.SharedPreferences; -import android.content.pm.PackageInfo; -import androidx.annotation.NonNull; -import org.acra.ACRA; -import org.acra.config.CoreConfiguration; -import org.acra.file.BulkReportDeleter; -import org.acra.prefs.SharedPreferencesFactory; - -/** - * Looks for any existing reports and starts sending them. - */ -@Deprecated -public final class ApplicationStartupProcessor { - - private final Context context; - private final CoreConfiguration config; - private final BulkReportDeleter reportDeleter; - - public ApplicationStartupProcessor(@NonNull Context context, @NonNull CoreConfiguration config) { - this.context = context; - this.config = config; - reportDeleter = new BulkReportDeleter(context); - } - - public void checkReports() { - //run it on a background thread because we're doing disk I/O - new Thread(() -> { - if (config.getDeleteOldUnsentReportsOnApplicationStart()) { - deleteUnsentReportsFromOldAppVersion(); - } - }).start(); - } - - /** - * Delete any old unsent reports if this is a newer version of the app than when we last started. - */ - private void deleteUnsentReportsFromOldAppVersion() { - final SharedPreferences prefs = new SharedPreferencesFactory(context, config).create(); - final long lastVersionNr = prefs.getInt(ACRA.PREF_LAST_VERSION_NR, 0); - final int appVersion = getAppVersion(); - - if (appVersion > lastVersionNr) { - reportDeleter.deleteReports(true, 0); - reportDeleter.deleteReports(false, 0); - - prefs.edit().putInt(ACRA.PREF_LAST_VERSION_NR, appVersion).apply(); - } - } - - /** - * @return app version or 0 if PackageInfo was not available. - */ - private int getAppVersion() { - final PackageManagerWrapper packageManagerWrapper = new PackageManagerWrapper(context); - final PackageInfo packageInfo = packageManagerWrapper.getPackageInfo(); - return (packageInfo == null) ? 0 : packageInfo.versionCode; - } -} diff --git a/acra-core/src/main/java/org/acra/util/BundleWrapper.java b/acra-core/src/main/java/org/acra/util/BundleWrapper.java deleted file mode 100644 index b3d6887c5f..0000000000 --- a/acra-core/src/main/java/org/acra/util/BundleWrapper.java +++ /dev/null @@ -1,191 +0,0 @@ -/* - * Copyright (c) 2019 - * - * 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.util; - -import android.os.Build; -import android.os.Bundle; -import android.os.PersistableBundle; -import androidx.annotation.Keep; -import androidx.annotation.Nullable; -import androidx.annotation.RequiresApi; - -import java.lang.reflect.Proxy; -import java.util.Set; - -/** - * Works like a {@link PersistableBundle}, but falls back to {@link Bundle} on older versions - * - * @author lukas - * @since 29.11.19 - */ -@Keep -public interface BundleWrapper { - - int size(); - - boolean isEmpty(); - - void clear(); - - boolean containsKey(String key); - - @Nullable - Object get(String key); - - void remove(String key); - - - @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) - void putAll(PersistableBundle bundle); - - Set keySet(); - - void putBoolean(@Nullable String key, boolean value); - - void putInt(@Nullable String key, int value); - - void putLong(@Nullable String key, long value); - - void putDouble(@Nullable String key, double value); - - void putString(@Nullable String key, - @Nullable String value); - - void putBooleanArray(@Nullable String key, - @Nullable boolean[] value); - - void putIntArray(@Nullable String key, - @Nullable int[] value); - - void putLongArray(@Nullable String key, - @Nullable long[] value); - - void putDoubleArray(@Nullable String key, - @Nullable double[] value); - - void putStringArray(@Nullable String key, - @Nullable String[] value); - - boolean getBoolean(String key); - - boolean getBoolean(String key, boolean defaultValue); - - int getInt(String key); - - int getInt(String key, int defaultValue); - - long getLong(String key); - - long getLong(String key, long defaultValue); - - double getDouble(String key); - - double getDouble(String key, double defaultValue); - - @Nullable - String getString(@Nullable String key); - - String getString(@Nullable String key, String defaultValue); - - @Nullable - boolean[] getBooleanArray(@Nullable String key); - - @Nullable - int[] getIntArray(@Nullable String key); - - @Nullable - long[] getLongArray(@Nullable String key); - - @Nullable - double[] getDoubleArray(@Nullable String key); - - @Nullable - String[] getStringArray(@Nullable String key); - - @Keep - interface Internal extends BundleWrapper { - - @RequiresApi(Build.VERSION_CODES.LOLLIPOP_MR1) - PersistableBundle asPersistableBundle(); - - /** - * Only works on API < 22 - * - * @return this as bundle - */ - Bundle asBundle(); - } - - static BundleWrapper wrap(@Nullable Bundle bundle) { - BundleWrapper wrapper = create(); - if (bundle != null) { - for (String key : bundle.keySet()) { - Object o = bundle.get(key); - if (o instanceof Integer) { - wrapper.putInt(key, (Integer) o); - } else if (o instanceof int[]) { - wrapper.putIntArray(key, (int[]) o); - } else if (o instanceof Long) { - wrapper.putLong(key, (Long) o); - } else if (o instanceof long[]) { - wrapper.putLongArray(key, (long[]) o); - } else if (o instanceof Double) { - wrapper.putDouble(key, (Double) o); - } else if (o instanceof double[]) { - wrapper.putDoubleArray(key, (double[]) o); - } else if (o instanceof String) { - wrapper.putString(key, (String) o); - } else if (o instanceof String[]) { - wrapper.putStringArray(key, (String[]) o); - } else if (o instanceof Boolean) { - wrapper.putBoolean(key, (Boolean) o); - } else if (o instanceof boolean[]) { - wrapper.putBooleanArray(key, (boolean[]) o); - } - } - } - return wrapper; - } - - @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) - static BundleWrapper wrap(@Nullable PersistableBundle bundle) { - BundleWrapper wrapper = create(); - if (bundle != null) { - wrapper.putAll(bundle); - } - return wrapper; - } - - static BundleWrapper.Internal create() { - final Object wrap = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1 ? new PersistableBundle() : new Bundle(); - return (BundleWrapper.Internal) Proxy.newProxyInstance(BundleWrapper.class.getClassLoader(), new Class[]{BundleWrapper.Internal.class}, (proxy, method, args) -> { - if (method.getName().equals("asPersistableBundle")) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) { - return wrap; - } - return null; - } - if (method.getName().equals("asBundle")) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP_MR1) { - return wrap; - } - return null; - } - return wrap.getClass().getMethod(method.getName(), method.getParameterTypes()).invoke(wrap, args); - }); - } -} diff --git a/acra-core/src/main/java/org/acra/util/IOUtils.java b/acra-core/src/main/java/org/acra/util/IOUtils.java deleted file mode 100644 index 3683005025..0000000000 --- a/acra-core/src/main/java/org/acra/util/IOUtils.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright 2016 - * - * 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.util; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import android.util.Base64; -import org.acra.ACRA; -import org.acra.ACRAConstants; - -import java.io.*; - -import static org.acra.ACRA.LOG_TAG; - -/** - * @author William Ferguson & F43nd1r - * @since 4.6.0 - */ -public final class IOUtils { - - private IOUtils() { - } - - - /** - * Closes a Closeable. - * - * @param closeable Closeable to close. If closeable is null then method just returns. - */ - public static void safeClose(@Nullable Closeable closeable) { - if (closeable == null) return; - - try { - closeable.close(); - } catch (IOException ignored) { - // We made out best effort to release this resource. Nothing more we can do. - } - } - - public static void deleteFile(@NonNull File file) { - final boolean deleted = file.delete(); - if (!deleted) { - ACRA.log.w(LOG_TAG, "Could not delete file: " + file); - } - } - - public static void writeStringToFile(@NonNull File file, @NonNull String content) throws IOException { - final OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(file), ACRAConstants.UTF8); - try { - writer.write(content); - writer.flush(); - } finally { - safeClose(writer); - } - } - - @Nullable - public static String serialize(@NonNull Serializable serializable) { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - try (ObjectOutputStream outputStream = new ObjectOutputStream(out)) { - outputStream.writeObject(serializable); - return Base64.encodeToString(out.toByteArray(), Base64.DEFAULT); - } catch (IOException e) { - e.printStackTrace(); - } - return null; - } - - @Nullable - public static T deserialize(@NonNull Class clazz, @Nullable String s) { - if (s != null) { - try (ObjectInputStream inputStream = new ObjectInputStream(new ByteArrayInputStream(Base64.decode(s, Base64.DEFAULT)))) { - Object o = inputStream.readObject(); - if (clazz.isInstance(o)) { - return clazz.cast(o); - } - } catch (IOException e) { - e.printStackTrace(); - } catch (ClassNotFoundException e) { - e.printStackTrace(); - } - } - return null; - } -} diff --git a/acra-core/src/main/java/org/acra/util/IOUtils.kt b/acra-core/src/main/java/org/acra/util/IOUtils.kt new file mode 100644 index 0000000000..099c08cf5d --- /dev/null +++ b/acra-core/src/main/java/org/acra/util/IOUtils.kt @@ -0,0 +1,102 @@ +/* + * Copyright 2016 + * + * 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.util + +import android.util.Base64 +import org.acra.ACRAConstants +import org.acra.log.warn +import java.io.ByteArrayInputStream +import java.io.ByteArrayOutputStream +import java.io.Closeable +import java.io.File +import java.io.FileOutputStream +import java.io.IOException +import java.io.ObjectInputStream +import java.io.ObjectOutputStream +import java.io.OutputStreamWriter +import java.io.Serializable + +/** + * @author William Ferguson & F43nd1r + * @since 4.6.0 + */ +object IOUtils { + /** + * Closes a Closeable. + * + * @param closeable Closeable to close. If closeable is null then method just returns. + */ + @JvmStatic + fun safeClose(closeable: Closeable?) { + if (closeable == null) return + try { + closeable.close() + } catch (ignored: IOException) { + // We made out best effort to release this resource. Nothing more we can do. + } + } + + @JvmStatic + fun deleteFile(file: File) { + val deleted = file.delete() + if (!deleted) { + warn { "Could not delete file: $file" } + } + } + + @JvmStatic + @Throws(IOException::class) + fun writeStringToFile(file: File, content: String) { + val writer = OutputStreamWriter(FileOutputStream(file), ACRAConstants.UTF8) + try { + writer.write(content) + writer.flush() + } finally { + safeClose(writer) + } + } + + fun serialize(serializable: Serializable): String? { + val out = ByteArrayOutputStream() + try { + ObjectOutputStream(out).use { outputStream -> + outputStream.writeObject(serializable) + return Base64.encodeToString(out.toByteArray(), Base64.DEFAULT) + } + } catch (e: IOException) { + e.printStackTrace() + } + return null + } + + fun deserialize(clazz: Class, s: String?): T? { + if (s != null) { + try { + ObjectInputStream(ByteArrayInputStream(Base64.decode(s, Base64.DEFAULT))).use { inputStream -> + val o = inputStream.readObject() + if (clazz.isInstance(o)) { + return clazz.cast(o) + } + } + } catch (e: IOException) { + e.printStackTrace() + } catch (e: ClassNotFoundException) { + e.printStackTrace() + } + } + return null + } +} \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/util/Installation.java b/acra-core/src/main/java/org/acra/util/Installation.java deleted file mode 100644 index fbe57d7e50..0000000000 --- a/acra-core/src/main/java/org/acra/util/Installation.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (c) 2017 the ACRA team - * - * 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. - */ - -/* - * Class copied from the Android Developers Blog: - * http://android-developers.blogspot.com/2011/03/identifying-app-installations.html - */ -package org.acra.util; - -import android.content.Context; -import androidx.annotation.NonNull; - -import org.acra.ACRA; - -import java.io.File; -import java.io.IOException; -import java.util.UUID; - -import static org.acra.ACRA.LOG_TAG; - -/** - *

- * Creates a file storing a UUID on the first application start. This UUID can then be used as a identifier of this - * specific application installation. - *

- *

- * This was taken from the - * android developers blog. - *

- */ -public final class Installation { - private Installation() { - } - - private static final String INSTALLATION = "ACRA-INSTALLATION"; - - @NonNull - public static synchronized String id(@NonNull Context context) { - final File installation = new File(context.getFilesDir(), INSTALLATION); - try { - if (!installation.exists()) { - IOUtils.writeStringToFile(installation, UUID.randomUUID().toString()); - } - return new StreamReader(installation).read(); - } catch (IOException | RuntimeException e) { - ACRA.log.w(LOG_TAG, "Couldn't retrieve InstallationId for " + context.getPackageName(), e); - return "Couldn't retrieve InstallationId"; - } - } -} diff --git a/acra-core/src/main/java/org/acra/util/Installation.kt b/acra-core/src/main/java/org/acra/util/Installation.kt new file mode 100644 index 0000000000..ed0c6d48cc --- /dev/null +++ b/acra-core/src/main/java/org/acra/util/Installation.kt @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2017 the ACRA team + * + * 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. + */ +/* + * Class copied from the Android Developers Blog: + * http://android-developers.blogspot.com/2011/03/identifying-app-installations.html + */ +package org.acra.util + +import android.content.Context +import org.acra.log.warn +import java.io.File +import java.io.IOException +import java.util.* + +/** + * + * + * Creates a file storing a UUID on the first application start. This UUID can then be used as a identifier of this + * specific application installation. + * + * + * + * This was taken from [ the + * android developers blog.](http://android-developers.blogspot.com/2011/03/identifying-app-installations.html) + * + */ +object Installation { + private const val INSTALLATION = "ACRA-INSTALLATION" + + @JvmStatic + @Synchronized + fun id(context: Context): String { + val installation = File(context.filesDir, INSTALLATION) + return try { + if (!installation.exists()) { + installation.writeText(UUID.randomUUID().toString()) + } + installation.readText() + } catch (e: IOException) { + warn(e) { "Couldn't retrieve InstallationId for " + context.packageName } + "Couldn't retrieve InstallationId" + } catch (e: RuntimeException) { + warn(e) { "Couldn't retrieve InstallationId for " + context.packageName } + "Couldn't retrieve InstallationId" + } + } +} \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/util/InstanceCreator.java b/acra-core/src/main/java/org/acra/util/InstanceCreator.java deleted file mode 100644 index 39a2a9f84e..0000000000 --- a/acra-core/src/main/java/org/acra/util/InstanceCreator.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright 2017 - * - * 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.util; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.VisibleForTesting; -import org.acra.ACRA; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -import static org.acra.ACRA.LOG_TAG; - -/** - * @author F43nd1r - * @since 09.03.2017 - */ -public final class InstanceCreator { - - /** - * Create an instance of clazz - * - * @param clazz the clazz to create an instance of - * @param fallback a value provider which provides a fallback in case of a failure - * @param the return type - * @return a new instance of clazz or fallback - */ - @NonNull - public T create(@NonNull Class clazz, @NonNull Fallback fallback) { - T t = create(clazz); - return t != null ? t : fallback.get(); - } - - @VisibleForTesting - @Nullable - T create(@NonNull Class clazz) { - try { - return clazz.newInstance(); - } catch (InstantiationException e) { - ACRA.log.e(LOG_TAG, "Failed to create instance of class " + clazz.getName(), e); - } catch (IllegalAccessException e) { - ACRA.log.e(LOG_TAG, "Failed to create instance of class " + clazz.getName(), e); - } - return null; - } - - /** - * Create instances of the given classes - * - * @param classes the classes to create insatnces of - * @param the return type - * @return a list of successfully created instances, does not contain null - */ - @NonNull - public List create(@NonNull Collection> classes) { - final List result = new ArrayList<>(); - for (Class clazz : classes) { - final T instance = create(clazz); - if (instance != null) { - result.add(instance); - } - } - return result; - } - - @FunctionalInterface - public interface Fallback { - @NonNull - T get(); - } -} diff --git a/acra-core/src/main/java/org/acra/util/InstanceCreator.kt b/acra-core/src/main/java/org/acra/util/InstanceCreator.kt new file mode 100644 index 0000000000..6f26db4e97 --- /dev/null +++ b/acra-core/src/main/java/org/acra/util/InstanceCreator.kt @@ -0,0 +1,55 @@ +/* + * Copyright 2017 + * + * 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.util + +import androidx.annotation.VisibleForTesting + +/** + * @author F43nd1r + * @since 09.03.2017 + */ +object InstanceCreator { + /** + * Create an instance of clazz + * + * @param clazz the clazz to create an instance of + * @param fallback a value provider which provides a fallback in case of a failure + * @param the return type + * @return a new instance of clazz or fallback + */ + fun create(clazz: Class, fallback: () -> T): T = create(clazz) ?: fallback.invoke() + + @VisibleForTesting + fun create(clazz: Class): T? { + try { + return clazz.newInstance() + } catch (e: InstantiationException) { + org.acra.log.error(e) { "Failed to create instance of class ${clazz.name}" } + } catch (e: IllegalAccessException) { + org.acra.log.error(e) { "Failed to create instance of class ${clazz.name}" } + } + return null + } + + /** + * Create instances of the given classes + * + * @param classes the classes to create insatnces of + * @param the return type + * @return a list of successfully created instances, does not contain null + */ + fun create(classes: Collection>): List = classes.mapNotNull { create(it) } +} \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/util/PackageManagerWrapper.java b/acra-core/src/main/java/org/acra/util/PackageManagerWrapper.kt similarity index 54% rename from acra-core/src/main/java/org/acra/util/PackageManagerWrapper.java rename to acra-core/src/main/java/org/acra/util/PackageManagerWrapper.kt index 5bbfe919a3..1f18cbd668 100644 --- a/acra-core/src/main/java/org/acra/util/PackageManagerWrapper.java +++ b/acra-core/src/main/java/org/acra/util/PackageManagerWrapper.kt @@ -13,81 +13,58 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +package org.acra.util -package org.acra.util; - -import android.content.Context; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import org.acra.ACRA; - -import static org.acra.ACRA.LOG_TAG; +import android.content.Context +import android.content.pm.PackageInfo +import android.content.pm.PackageManager +import org.acra.log.warn /** * Responsible for wrapping calls to PackageManager to ensure that they always complete without throwing RuntimeExceptions. * Depending upon the state of the application it is possible that - *
    - *
  • Context has no PackageManager.
  • - *
  • PackageManager returned by Context throws RuntimeException("Package manager has died") - * because it cannot contact the remote PackageManager service. - *
  • - *
+ * + * * Context has no PackageManager. + * * PackageManager returned by Context throws RuntimeException("Package manager has died") + * because it cannot contact the remote PackageManager service. + * + * * I suspect that PackageManager death is caused during app installation. * But we need to make ACRA bullet proof, so it's better to handle the condition safely so that the error report itself doesn't fail. * * @author William Ferguson * @since 4.3.0 */ -public final class PackageManagerWrapper { - - @NonNull - private final Context context; - - public PackageManagerWrapper(@NonNull Context context) { - this.context = context; - } - +class PackageManagerWrapper(private val context: Context) { /** * @param permission Manifest.permission to check whether it has been granted. * @return true if the permission has been granted to the app, false if it hasn't been granted or the PackageManager could not be contacted. */ - public boolean hasPermission(@NonNull String permission) { - final PackageManager pm = context.getPackageManager(); - if (pm == null) { - return false; - } - - try { - return pm.checkPermission(permission, context.getPackageName()) == PackageManager.PERMISSION_GRANTED; - } catch (Exception e) { + fun hasPermission(permission: String): Boolean { + val pm = context.packageManager ?: return false + return try { + pm.checkPermission(permission, context.packageName) == PackageManager.PERMISSION_GRANTED + } catch (e: Exception) { // To catch RuntimeException("Package manager has died") that can occur on some version of Android, // when the remote PackageManager is unavailable. I suspect this sometimes occurs when the App is being reinstalled. - return false; + false } } /** * @return PackageInfo for the current application or null if the PackageManager could not be contacted. */ - @Nullable - public PackageInfo getPackageInfo() { - final PackageManager pm = context.getPackageManager(); - if (pm == null) { - return null; - } - - try { - return pm.getPackageInfo(context.getPackageName(), 0); - } catch (PackageManager.NameNotFoundException e) { - ACRA.log.w(LOG_TAG, "Failed to find PackageInfo for current App : " + context.getPackageName()); - return null; - } catch (Exception e) { + fun getPackageInfo(): PackageInfo? { + val pm = context.packageManager ?: return null + return try { + pm.getPackageInfo(context.packageName, 0) + } catch (e: PackageManager.NameNotFoundException) { + warn { "Failed to find PackageInfo for current App : ${context.packageName}" } + null + } catch (e: Exception) { // To catch RuntimeException("Package manager has died") that can occur on some version of Android, // when the remote PackageManager is unavailable. I suspect this sometimes occurs when the App is being reinstalled. - return null; + null } } -} +} \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/util/ProcessFinisher.java b/acra-core/src/main/java/org/acra/util/ProcessFinisher.java deleted file mode 100644 index 0d6bac6931..0000000000 --- a/acra-core/src/main/java/org/acra/util/ProcessFinisher.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright (c) 2016 - * - * 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.util; - -import android.app.Activity; -import android.app.ActivityManager; -import android.content.Context; -import android.content.Intent; -import android.os.Process; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import org.acra.ACRA; -import org.acra.builder.LastActivityManager; -import org.acra.config.CoreConfiguration; -import org.acra.sender.JobSenderService; -import org.acra.sender.LegacySenderService; - -import java.util.List; - -import static org.acra.ACRA.LOG_TAG; - -/** - * Takes care of cleaning up a process and killing it. - * - * @author F43nd1r - * @since 4.9.2 - */ - -public final class ProcessFinisher { - private final Context context; - private final CoreConfiguration config; - private final LastActivityManager lastActivityManager; - - public ProcessFinisher(@NonNull Context context, @NonNull CoreConfiguration config, @NonNull LastActivityManager lastActivityManager) { - this.context = context; - this.config = config; - this.lastActivityManager = lastActivityManager; - } - - public void endApplication() { - stopServices(); - killProcessAndExit(); - } - - public void finishLastActivity(@Nullable Thread uncaughtExceptionThread) { - if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "Finishing activities prior to killing the Process"); - boolean wait = false; - for(Activity activity : lastActivityManager.getLastActivities()) { - final boolean isMainThread = uncaughtExceptionThread == activity.getMainLooper().getThread(); - final Runnable finisher = () -> { - activity.finish(); - if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "Finished " + activity.getClass()); - }; - if (isMainThread) { - finisher.run(); - } else { - // A crashed activity won't continue its lifecycle. So we only wait if something else crashed - wait = true; - activity.runOnUiThread(finisher); - } - } - if (wait) { - lastActivityManager.waitForAllActivitiesDestroy(100); - } - lastActivityManager.clearLastActivities(); - } - - private void stopServices() { - if (config.getStopServicesOnCrash()) { - try { - final ActivityManager activityManager = SystemServices.getActivityManager(context); - final List runningServices = activityManager.getRunningServices(Integer.MAX_VALUE); - final int pid = Process.myPid(); - for (ActivityManager.RunningServiceInfo serviceInfo : runningServices) { - if (serviceInfo.pid == pid && !LegacySenderService.class.getName().equals(serviceInfo.service.getClassName()) && !JobSenderService.class.getName().equals(serviceInfo.service.getClassName())) { - try { - final Intent intent = new Intent(); - intent.setComponent(serviceInfo.service); - context.stopService(intent); - } catch (SecurityException e) { - if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "Unable to stop Service " + serviceInfo.service.getClassName() + ". Permission denied"); - } - } - } - } catch (SystemServices.ServiceNotReachedException e) { - ACRA.log.e(LOG_TAG, "Unable to stop services", e); - } - } - } - - private void killProcessAndExit() { - Process.killProcess(Process.myPid()); - System.exit(10); - } -} diff --git a/acra-core/src/main/java/org/acra/util/ProcessFinisher.kt b/acra-core/src/main/java/org/acra/util/ProcessFinisher.kt new file mode 100644 index 0000000000..2b2f1978c1 --- /dev/null +++ b/acra-core/src/main/java/org/acra/util/ProcessFinisher.kt @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2016 + * + * 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.util + +import android.content.Context +import android.content.Intent +import android.os.Process +import org.acra.builder.LastActivityManager +import org.acra.config.CoreConfiguration +import org.acra.log.debug +import org.acra.log.error +import org.acra.sender.JobSenderService +import org.acra.sender.LegacySenderService +import org.acra.util.SystemServices.ServiceNotReachedException +import org.acra.util.SystemServices.getActivityManager +import kotlin.system.exitProcess + +/** + * Takes care of cleaning up a process and killing it. + * + * @author F43nd1r + * @since 4.9.2 + */ +class ProcessFinisher(private val context: Context, private val config: CoreConfiguration, private val lastActivityManager: LastActivityManager) { + fun endApplication() { + stopServices() + killProcessAndExit() + } + + fun finishLastActivity(uncaughtExceptionThread: Thread?) { + debug { "Finishing activities prior to killing the Process" } + var wait = false + for (activity in lastActivityManager.lastActivities) { + val finisher = Runnable { + activity.finish() + debug { "Finished ${activity.javaClass}" } + } + if (uncaughtExceptionThread === activity.mainLooper.thread) { + finisher.run() + } else { + // A crashed activity won't continue its lifecycle. So we only wait if something else crashed + wait = true + activity.runOnUiThread(finisher) + } + } + if (wait) { + lastActivityManager.waitForAllActivitiesDestroy(100) + } + lastActivityManager.clearLastActivities() + } + + @Suppress("DEPRECATION") + private fun stopServices() { + if (config.stopServicesOnCrash) { + try { + val activityManager = getActivityManager(context) + val runningServices = activityManager.getRunningServices(Int.MAX_VALUE) + val pid = Process.myPid() + for (serviceInfo in runningServices) { + if (serviceInfo.pid == pid && LegacySenderService::class.java.name != serviceInfo.service.className + && JobSenderService::class.java.name != serviceInfo.service.className) { + try { + val intent = Intent() + intent.component = serviceInfo.service + context.stopService(intent) + } catch (e: SecurityException) { + debug { "Unable to stop Service ${serviceInfo.service.className}. Permission denied" } + } + } + } + } catch (e: ServiceNotReachedException) { + error(e) { "Unable to stop services" } + } + } + } + + private fun killProcessAndExit() { + Process.killProcess(Process.myPid()) + exitProcess(10) + } +} \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/util/StreamReader.java b/acra-core/src/main/java/org/acra/util/StreamReader.java deleted file mode 100644 index 4773b7931b..0000000000 --- a/acra-core/src/main/java/org/acra/util/StreamReader.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Copyright (c) 2017 the ACRA team - * - * 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.util; - -import androidx.annotation.NonNull; -import android.text.TextUtils; - -import org.acra.ACRAConstants; -import org.acra.collections.BoundedLinkedList; - -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.Reader; -import java.io.StringWriter; -import java.util.Arrays; -import java.util.LinkedList; -import java.util.List; - -/** - * @author F43nd1r - * @since 30.11.2017 - */ - -public class StreamReader { - private static final int NO_LIMIT = -1; - private static final int INDEFINITE = -1; - private final InputStream inputStream; - private int limit = NO_LIMIT; - private int timeout = INDEFINITE; - private Predicate filter = null; - - public StreamReader(@NonNull String filename) throws FileNotFoundException { - this(new File(filename)); - } - - public StreamReader(@NonNull File file) throws FileNotFoundException { - this(new FileInputStream(file)); - } - - public StreamReader(@NonNull InputStream inputStream) { - this.inputStream = inputStream; - } - - @NonNull - public StreamReader setLimit(int limit) { - this.limit = limit; - return this; - } - - @NonNull - public StreamReader setTimeout(int timeout) { - this.timeout = timeout; - return this; - } - - @NonNull - public StreamReader setFilter(Predicate filter) { - this.filter = filter; - return this; - } - - @NonNull - public String read() throws IOException { - final String text = timeout == INDEFINITE ? readFully() : readWithTimeout(); - if (filter == null) { - if (limit == NO_LIMIT) { - return text; - } - final String[] lines = text.split("\\r?\\n"); - if(lines.length <= limit){ - return text; - } - return TextUtils.join("\n", Arrays.copyOfRange(lines, lines.length - limit, lines.length)); - } - final String[] lines = text.split("\\r?\\n"); - final List buffer = limit == NO_LIMIT ? new LinkedList<>() : new BoundedLinkedList<>(limit); - for (String line : lines) { - if (filter.apply(line)) { - buffer.add(line); - } - } - return TextUtils.join("\n", buffer); - } - - @NonNull - private String readFully() throws IOException { - final Reader input = new InputStreamReader(inputStream); - try { - final StringWriter output = new StringWriter(); - final char[] buffer = new char[ACRAConstants.DEFAULT_BUFFER_SIZE_IN_BYTES]; - int count; - while ((count = input.read(buffer)) != -1) { - output.write(buffer, 0, count); - } - return output.toString(); - } finally { - IOUtils.safeClose(input); - } - } - - @NonNull - private String readWithTimeout() throws IOException { - final long until = System.currentTimeMillis() + timeout; - try { - final ByteArrayOutputStream output = new ByteArrayOutputStream(); - final byte[] buffer = new byte[ACRAConstants.DEFAULT_BUFFER_SIZE_IN_BYTES]; - int count; - while ((count = fillBufferUntil(buffer, until)) != -1) { - output.write(buffer, 0, count); - } - return output.toString(); - } finally { - IOUtils.safeClose(inputStream); - } - } - - private int fillBufferUntil(@NonNull byte[] buffer, long until) throws IOException { - int bufferOffset = 0; - while (System.currentTimeMillis() < until && bufferOffset < buffer.length) { - final int readResult = inputStream.read(buffer, bufferOffset, Math.min(inputStream.available(), buffer.length - bufferOffset)); - if (readResult == -1) break; - bufferOffset += readResult; - } - return bufferOffset; - } -} diff --git a/acra-core/src/main/java/org/acra/util/StreamReader.kt b/acra-core/src/main/java/org/acra/util/StreamReader.kt new file mode 100644 index 0000000000..95db8e30dc --- /dev/null +++ b/acra-core/src/main/java/org/acra/util/StreamReader.kt @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2017 the ACRA team + * + * 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.util + +import kotlinx.coroutines.async +import kotlinx.coroutines.runBlocking +import org.acra.ACRAConstants +import java.io.BufferedReader +import java.io.ByteArrayOutputStream +import java.io.File +import java.io.FileInputStream +import java.io.IOException +import java.io.InputStream +import java.io.InputStreamReader +import java.io.Reader +import java.io.StringWriter +import kotlin.math.min + +/** + * @author F43nd1r + * @since 30.11.2017 + */ +class StreamReader(private val inputStream: InputStream, var limit: Int = NO_LIMIT, var timeout: Int = INDEFINITE, var filter: ((String) -> Boolean)? = null) { + + constructor(filename: String) : this(File(filename)) + constructor(file: File) : this(FileInputStream(file)) + + fun setLimit(limit: Int): StreamReader { + this.limit = limit + return this + } + + fun setTimeout(timeout: Int): StreamReader { + this.timeout = timeout + return this + } + + fun setFilter(filter: ((String) -> Boolean)?): StreamReader { + this.filter = filter + return this + } + + @Throws(IOException::class) + fun read(): String { + val text = if (timeout == INDEFINITE) readFully() else readWithTimeout() + return filter?.let { + text.split("\\r?\\n").filter(it).run { if (limit == NO_LIMIT) this else takeLast(limit) }.joinToString("\n") + } ?: if (limit == NO_LIMIT) { + text + } else { + text.split("\\r?\\n").takeLast(limit).joinToString("\n") + } + } + + @Throws(IOException::class) + private fun readFully(): String = inputStream.bufferedReader().use(BufferedReader::readText) + + @Throws(IOException::class) + private fun readWithTimeout(): String { + val until = System.currentTimeMillis() + timeout + return inputStream.use { input -> + val output = ByteArrayOutputStream() + val buffer = ByteArray(ACRAConstants.DEFAULT_BUFFER_SIZE_IN_BYTES) + var count: Int + while (input.readUntil(buffer, until).also { count = it } != -1) { + output.write(buffer, 0, count) + } + output.toString() + } + } + + @Throws(IOException::class) + private fun InputStream.readUntil(buffer: ByteArray, until: Long): Int { + var bufferOffset = 0 + while (System.currentTimeMillis() < until && bufferOffset < buffer.size) { + val readResult = read(buffer, bufferOffset, min(inputStream.available(), buffer.size - bufferOffset)) + if (readResult == -1) break + bufferOffset += readResult + } + return bufferOffset + } + + companion object { + private const val NO_LIMIT = -1 + private const val INDEFINITE = -1 + } +} \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/util/StubCreator.java b/acra-core/src/main/java/org/acra/util/StubCreator.java deleted file mode 100644 index 0a36ac2397..0000000000 --- a/acra-core/src/main/java/org/acra/util/StubCreator.java +++ /dev/null @@ -1,28 +0,0 @@ -package org.acra.util; - -import androidx.annotation.NonNull; -import org.acra.ACRA; -import org.acra.ErrorReporter; - -import java.lang.reflect.InvocationHandler; -import java.lang.reflect.Proxy; - -public final class StubCreator { - private StubCreator() { - } - - @NonNull - public static ErrorReporter createErrorReporterStub() { - return createStub(ErrorReporter.class, (proxy, method, args) -> { - String message = ACRA.isACRASenderServiceProcess() ? "in SenderService process" : "before ACRA#init (if you did call #init, check if your configuration is valid)"; - ACRA.log.w(ACRA.LOG_TAG, String.format("ErrorReporter#%s called %s. THIS CALL WILL BE IGNORED!", method.getName(), message)); - return null; - }); - } - - @NonNull - public static T createStub(Class interfaceClass, InvocationHandler handler) { - //noinspection unchecked - return (T) Proxy.newProxyInstance(StubCreator.class.getClassLoader(), new Class[]{interfaceClass}, handler); - } -} diff --git a/acra-core/src/main/java/org/acra/util/StubCreator.kt b/acra-core/src/main/java/org/acra/util/StubCreator.kt new file mode 100644 index 0000000000..dfed592bdd --- /dev/null +++ b/acra-core/src/main/java/org/acra/util/StubCreator.kt @@ -0,0 +1,25 @@ +package org.acra.util + +import org.acra.ACRA.isACRASenderServiceProcess +import org.acra.ErrorReporter +import org.acra.log.warn +import java.lang.reflect.InvocationHandler +import java.lang.reflect.Proxy + +object StubCreator { + fun createErrorReporterStub(): ErrorReporter { + return createStub { _, method, _ -> + val message = if (isACRASenderServiceProcess()) "in SenderService process" else "before ACRA#init (if you did call #init, check if your configuration is valid)" + warn { "ErrorReporter#${method.name} called $message. THIS CALL WILL BE IGNORED!" } + null + } + } + + inline fun createStub(handler: InvocationHandler): T = createStub(T::class.java, handler) + + @JvmStatic + fun createStub(interfaceClass: Class, handler: InvocationHandler): T { + @Suppress("UNCHECKED_CAST") + return Proxy.newProxyInstance(StubCreator::class.java.classLoader, arrayOf>(interfaceClass), handler) as T + } +} \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/util/SystemServices.java b/acra-core/src/main/java/org/acra/util/SystemServices.java deleted file mode 100644 index 01142b3fa7..0000000000 --- a/acra-core/src/main/java/org/acra/util/SystemServices.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (c) 2017 - * - * 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.util; - -import android.app.ActivityManager; -import android.app.NotificationManager; -import android.content.Context; -import android.os.DropBoxManager; -import androidx.annotation.NonNull; -import android.telephony.TelephonyManager; - -/** - * @author F43nd1r - * @since 29.09.2017 - */ - -public final class SystemServices { - private SystemServices() { - } - - @NonNull - public static TelephonyManager getTelephonyManager(@NonNull Context context) throws ServiceNotReachedException { - return (TelephonyManager) getService(context, Context.TELEPHONY_SERVICE); - } - - @NonNull - public static DropBoxManager getDropBoxManager(@NonNull Context context) throws ServiceNotReachedException { - return (DropBoxManager) getService(context, Context.DROPBOX_SERVICE); - } - - @NonNull - public static NotificationManager getNotificationManager(@NonNull Context context) throws ServiceNotReachedException { - return (NotificationManager) getService(context, Context.NOTIFICATION_SERVICE); - } - - @NonNull - public static ActivityManager getActivityManager(@NonNull Context context) throws ServiceNotReachedException { - return (ActivityManager) getService(context, Context.ACTIVITY_SERVICE); - } - - @NonNull - private static Object getService(@NonNull Context context, @NonNull String id) throws ServiceNotReachedException { - final Object service = context.getSystemService(id); - if (service == null) { - throw new ServiceNotReachedException("Unable to load SystemService " + id); - } - return service; - } - - static class ServiceNotReachedException extends Exception { - ServiceNotReachedException(String message) { - super(message); - } - } -} diff --git a/acra-core/src/main/java/org/acra/util/SystemServices.kt b/acra-core/src/main/java/org/acra/util/SystemServices.kt new file mode 100644 index 0000000000..6b34cfde32 --- /dev/null +++ b/acra-core/src/main/java/org/acra/util/SystemServices.kt @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2017 + * + * 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.util + +import android.app.ActivityManager +import android.app.NotificationManager +import android.content.Context +import android.os.DropBoxManager +import android.telephony.TelephonyManager + +/** + * @author F43nd1r + * @since 29.09.2017 + */ +object SystemServices { + @JvmStatic + @Throws(ServiceNotReachedException::class) + fun getTelephonyManager(context: Context): TelephonyManager = getService(context, Context.TELEPHONY_SERVICE) as TelephonyManager + + @JvmStatic + @Throws(ServiceNotReachedException::class) + fun getDropBoxManager(context: Context): DropBoxManager = getService(context, Context.DROPBOX_SERVICE) as DropBoxManager + + @JvmStatic + @Throws(ServiceNotReachedException::class) + fun getNotificationManager(context: Context): NotificationManager = getService(context, Context.NOTIFICATION_SERVICE) as NotificationManager + + @JvmStatic + @Throws(ServiceNotReachedException::class) + fun getActivityManager(context: Context): ActivityManager = getService(context, Context.ACTIVITY_SERVICE) as ActivityManager + + @Throws(ServiceNotReachedException::class) + private fun getService(context: Context, id: String): Any = context.getSystemService(id) ?: throw ServiceNotReachedException("Unable to load SystemService $id") + + internal class ServiceNotReachedException(message: String?) : Exception(message) +} \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/util/ToastSender.java b/acra-core/src/main/java/org/acra/util/ToastSender.kt similarity index 63% rename from acra-core/src/main/java/org/acra/util/ToastSender.java rename to acra-core/src/main/java/org/acra/util/ToastSender.kt index 7ade55af90..183fde04fe 100644 --- a/acra-core/src/main/java/org/acra/util/ToastSender.java +++ b/acra-core/src/main/java/org/acra/util/ToastSender.kt @@ -13,17 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +package org.acra.util -package org.acra.util; - -import android.content.Context; -import androidx.annotation.IntRange; -import androidx.annotation.NonNull; -import android.widget.Toast; - -import org.acra.ACRA; - -import static org.acra.ACRA.LOG_TAG; +import android.content.Context +import android.widget.Toast +import androidx.annotation.IntRange +import org.acra.log.warn /** * Responsible for sending Toasts under all circumstances. @@ -31,10 +26,7 @@ * @author William Ferguson * @since 4.3.0 */ -public final class ToastSender { - private ToastSender() { - } - +object ToastSender { /** * Sends a Toast and ensures that any Exception thrown during sending is handled. * @@ -42,11 +34,12 @@ private ToastSender() { * @param toast toast message. * @param toastLength Length of the Toast. */ - public static void sendToast(@NonNull Context context, String toast, @IntRange(from = 0, to = 1) int toastLength) { + @JvmStatic + fun sendToast(context: Context, toast: String?, @IntRange(from = 0, to = 1) toastLength: Int) { try { - Toast.makeText(context, toast, toastLength).show(); - } catch (RuntimeException e) { - ACRA.log.w(LOG_TAG, "Could not send crash Toast", e); + Toast.makeText(context, toast, toastLength).show() + } catch (e: RuntimeException) { + warn(e) { "Could not send crash Toast" } } } -} +} \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/util/bundle.kt b/acra-core/src/main/java/org/acra/util/bundle.kt new file mode 100644 index 0000000000..3271743e9e --- /dev/null +++ b/acra-core/src/main/java/org/acra/util/bundle.kt @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2020 + * + * 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.util + +import android.os.Build +import android.os.Bundle +import android.os.PersistableBundle +import androidx.annotation.RequiresApi + + +/** + * Creates a new [PersistableBundle] from the specified [Bundle]. + * Will ignore all values that are not persistable, according + * to [.isPersistableBundleType]. + */ +@RequiresApi(Build.VERSION_CODES.LOLLIPOP_MR1) +fun Bundle.toPersistableBundle(): PersistableBundle { + val persistableBundle = PersistableBundle() + for (key in keySet()) { + val value = this[key] + if (isPersistableBundleType(value)) { + persistableBundle.put(key, value) + } + } + return persistableBundle +} + +/** + * Checks if the specified object can be put into a [PersistableBundle]. + * + * @see [PersistableBundle Implementation](https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/os/PersistableBundle.java.49) + */ +@RequiresApi(Build.VERSION_CODES.LOLLIPOP_MR1) +private fun isPersistableBundleType(value: Any?): Boolean { + return value is PersistableBundle || + value is Int || value is IntArray || + value is Long || value is LongArray || + value is Double || value is DoubleArray || + value is String || (value is Array<*> && value.isArrayOf()) || + value is Boolean || value is BooleanArray +} + +/** + * Attempts to insert the specified key value pair into the specified bundle. + * + * @throws IllegalArgumentException if the value type can not be put into the bundle. + */ +@RequiresApi(Build.VERSION_CODES.LOLLIPOP_MR1) +@Throws(IllegalArgumentException::class) +private fun PersistableBundle.put(key: String, value: Any?) { + requireNotNull(value) { "Unable to determine type of null values" } + when { + value is Int -> putInt(key, value) + value is IntArray -> putIntArray(key, value) + value is Long -> putLong(key, value) + value is LongArray -> putLongArray(key, value) + value is Double -> putDouble(key, value) + value is DoubleArray -> putDoubleArray(key, value) + value is String -> putString(key, value as String?) + value is Array<*> && value.isArrayOf() -> @Suppress("UNCHECKED_CAST") + putStringArray(key, value as Array) + value is Boolean -> putBoolean(key, value) + value is BooleanArray -> putBooleanArray(key, value) + value is PersistableBundle -> putAll(value) + else -> throw IllegalArgumentException("Objects of type ${value.javaClass.simpleName} can not be put into a PersistableBundle") + } +} \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/util/utils.kt b/acra-core/src/main/java/org/acra/util/utils.kt new file mode 100644 index 0000000000..85c87812e2 --- /dev/null +++ b/acra-core/src/main/java/org/acra/util/utils.kt @@ -0,0 +1,9 @@ +package org.acra.util + +import android.util.SparseArray + +public inline fun Iterable.mapNotNullToSparseArray(transform: (T) -> Pair?): SparseArray { + val destination = SparseArray() + forEach { element -> transform(element)?.let { (key, value) -> destination.put(key, value) } } + return destination +} \ No newline at end of file diff --git a/acra-limiter/src/main/java/org/acra/startup/LimiterStartupProcessor.java b/acra-limiter/src/main/java/org/acra/startup/LimiterStartupProcessor.java index 57b3f3c653..b1cd1bf0d1 100644 --- a/acra-limiter/src/main/java/org/acra/startup/LimiterStartupProcessor.java +++ b/acra-limiter/src/main/java/org/acra/startup/LimiterStartupProcessor.java @@ -29,6 +29,7 @@ import org.acra.plugins.HasConfigPlugin; import org.acra.prefs.SharedPreferencesFactory; import org.acra.util.PackageManagerWrapper; +import org.jetbrains.annotations.NotNull; import java.io.IOException; import java.util.List; @@ -46,7 +47,7 @@ public LimiterStartupProcessor() { } @Override - public void processReports(@NonNull Context context, @NonNull CoreConfiguration config, List reports) { + public void processReports(@NotNull Context context, @NotNull CoreConfiguration config, @NotNull List reports) { final LimiterConfiguration limiterConfiguration = ConfigUtils.getPluginConfiguration(config, LimiterConfiguration.class); if(limiterConfiguration.getDeleteReportsOnAppUpdate() || limiterConfiguration.getResetLimitsOnAppUpdate()) { final SharedPreferences prefs = new SharedPreferencesFactory(context, config).create(); diff --git a/buildSrc/src/main/kotlin/acra-android-library.gradle.kts b/buildSrc/src/main/kotlin/acra-android-library.gradle.kts index e0d5f6eb84..2ec77863ab 100644 --- a/buildSrc/src/main/kotlin/acra-android-library.gradle.kts +++ b/buildSrc/src/main/kotlin/acra-android-library.gradle.kts @@ -71,6 +71,7 @@ dependencies { testCompileOnly("com.google.auto.service:auto-service-annotations:$autoServiceVersion") "kapt"(project(":annotationprocessor")) compileOnly(project(":annotations")) + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1") } tasks.register("sourcesJar") { diff --git a/gradle.properties b/gradle.properties index 39beedc23b..4e9436cb41 100644 --- a/gradle.properties +++ b/gradle.properties @@ -33,7 +33,7 @@ group=ch.acra version=5.7.1-SNAPSHOT androidVersion=30 androidMinVersion=14 -androidBuildPluginVersion=4.1.0 +androidBuildPluginVersion=4.1.1 bintrayPluginVersion=1.8.5 releasePluginVersion=3.2.0 autoServiceVersion=1.0-rc6 From 69d565cb3b224d02289d2b73ee896c05764f486f Mon Sep 17 00:00:00 2001 From: f43nd1r Date: Mon, 23 Nov 2020 01:43:50 +0100 Subject: [PATCH 04/16] advanced-scheduler to kotlin --- .../scheduler/AdvancedSenderScheduler.java | 75 ------------------- .../acra/scheduler/AdvancedSenderScheduler.kt | 56 ++++++++++++++ .../scheduler/RestartingAdministrator.java | 74 ------------------ .../acra/scheduler/RestartingAdministrator.kt | 69 +++++++++++++++++ .../org/acra/scheduler/RestartingService.java | 38 ---------- .../org/acra/scheduler/RestartingService.kt | 36 +++++++++ .../org/acra/config/ReportingAdministrator.kt | 2 +- 7 files changed, 162 insertions(+), 188 deletions(-) delete mode 100644 acra-advanced-scheduler/src/main/java/org/acra/scheduler/AdvancedSenderScheduler.java create mode 100644 acra-advanced-scheduler/src/main/java/org/acra/scheduler/AdvancedSenderScheduler.kt delete mode 100644 acra-advanced-scheduler/src/main/java/org/acra/scheduler/RestartingAdministrator.java create mode 100644 acra-advanced-scheduler/src/main/java/org/acra/scheduler/RestartingAdministrator.kt delete mode 100644 acra-advanced-scheduler/src/main/java/org/acra/scheduler/RestartingService.java create mode 100644 acra-advanced-scheduler/src/main/java/org/acra/scheduler/RestartingService.kt 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 deleted file mode 100644 index bbedc5e553..0000000000 --- a/acra-advanced-scheduler/src/main/java/org/acra/scheduler/AdvancedSenderScheduler.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * 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.job.JobInfo; -import android.content.Context; -import android.os.Build; -import androidx.annotation.NonNull; -import com.google.auto.service.AutoService; -import org.acra.config.ConfigUtils; -import org.acra.config.CoreConfiguration; -import org.acra.config.SchedulerConfiguration; -import org.acra.plugins.HasConfigPlugin; - -/** - * Utilizes jobservice to delay report sending - * - * @author F43nd1r - * @since 18.04.18 - */ -public class AdvancedSenderScheduler extends DefaultSenderScheduler { - private final SchedulerConfiguration schedulerConfiguration; - - private AdvancedSenderScheduler(@NonNull Context context, @NonNull CoreConfiguration config) { - super(context, config); - schedulerConfiguration = ConfigUtils.getPluginConfiguration(config, SchedulerConfiguration.class); - } - - @Override - protected void configureJob(@NonNull JobInfo.Builder job) { - job.setRequiredNetworkType(schedulerConfiguration.getRequiresNetworkType()); - job.setRequiresCharging(schedulerConfiguration.getRequiresCharging()); - job.setRequiresDeviceIdle(schedulerConfiguration.getRequiresDeviceIdle()); - boolean constrained = schedulerConfiguration.getRequiresNetworkType() != JobInfo.NETWORK_TYPE_NONE || - schedulerConfiguration.getRequiresCharging() || - schedulerConfiguration.getRequiresDeviceIdle(); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - job.setRequiresBatteryNotLow(schedulerConfiguration.getRequiresBatteryNotLow()); - constrained |= schedulerConfiguration.getRequiresBatteryNotLow(); - } - if (!constrained) { - job.setOverrideDeadline(0); - } - } - - @AutoService(SenderSchedulerFactory.class) - public static class Factory extends HasConfigPlugin implements SenderSchedulerFactory { - - public Factory() { - super(SchedulerConfiguration.class); - } - - @NonNull - @Override - public SenderScheduler create(@NonNull Context context, @NonNull CoreConfiguration config) { - return new AdvancedSenderScheduler(context, config); - } - - } - -} diff --git a/acra-advanced-scheduler/src/main/java/org/acra/scheduler/AdvancedSenderScheduler.kt b/acra-advanced-scheduler/src/main/java/org/acra/scheduler/AdvancedSenderScheduler.kt new file mode 100644 index 0000000000..77a90f81d9 --- /dev/null +++ b/acra-advanced-scheduler/src/main/java/org/acra/scheduler/AdvancedSenderScheduler.kt @@ -0,0 +1,56 @@ +/* + * 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.job.JobInfo +import android.content.Context +import android.os.Build +import com.google.auto.service.AutoService +import org.acra.config.ConfigUtils.getPluginConfiguration +import org.acra.config.CoreConfiguration +import org.acra.config.SchedulerConfiguration +import org.acra.plugins.HasConfigPlugin + +/** + * Utilizes jobservice to delay report sending + * + * @author F43nd1r + * @since 18.04.18 + */ +class AdvancedSenderScheduler private constructor(context: Context, config: CoreConfiguration) : DefaultSenderScheduler(context, config) { + private val schedulerConfiguration: SchedulerConfiguration = getPluginConfiguration(config, SchedulerConfiguration::class.java) + override fun configureJob(job: JobInfo.Builder) { + job.setRequiredNetworkType(schedulerConfiguration.requiresNetworkType) + job.setRequiresCharging(schedulerConfiguration.requiresCharging) + job.setRequiresDeviceIdle(schedulerConfiguration.requiresDeviceIdle) + var constrained = schedulerConfiguration.requiresNetworkType != JobInfo.NETWORK_TYPE_NONE || schedulerConfiguration.requiresCharging || schedulerConfiguration.requiresDeviceIdle + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + job.setRequiresBatteryNotLow(schedulerConfiguration.requiresBatteryNotLow) + constrained = constrained or schedulerConfiguration.requiresBatteryNotLow + } + if (!constrained) { + job.setOverrideDeadline(0) + } + } + + @AutoService(SenderSchedulerFactory::class) + class Factory : HasConfigPlugin(SchedulerConfiguration::class.java), SenderSchedulerFactory { + override fun create(context: Context, config: CoreConfiguration): SenderScheduler { + return AdvancedSenderScheduler(context, config) + } + } + +} \ No newline at end of file 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 deleted file mode 100644 index 380abfd592..0000000000 --- a/acra-advanced-scheduler/src/main/java/org/acra/scheduler/RestartingAdministrator.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * 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.app.job.JobInfo; -import android.app.job.JobScheduler; -import android.content.ComponentName; -import android.content.Context; -import android.os.PersistableBundle; -import androidx.annotation.NonNull; -import com.google.auto.service.AutoService; -import org.acra.ACRA; -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.HasConfigPlugin; - -/** - * @author F43nd1r - * @since 07.05.18 - */ -@AutoService(ReportingAdministrator.class) -public class RestartingAdministrator extends HasConfigPlugin implements ReportingAdministrator { - public static final String EXTRA_LAST_ACTIVITY = "lastActivity"; - public static final String EXTRA_ACTIVITY_RESTART_AFTER_CRASH = "restartAfterCrash"; - - public RestartingAdministrator() { - super(SchedulerConfiguration.class); - } - - @Override - public boolean shouldFinishActivity(@NonNull Context context, @NonNull CoreConfiguration config, LastActivityManager lastActivityManager) { - if (ACRA.DEV_LOGGING) ACRA.log.d(ACRA.LOG_TAG, "RestartingAdministrator entry"); - if (ConfigUtils.getPluginConfiguration(config, SchedulerConfiguration.class).getRestartAfterCrash()) { - Activity activity = lastActivityManager.getLastActivity(); - if (activity != null) { - if (ACRA.DEV_LOGGING) ACRA.log.d(ACRA.LOG_TAG, "Try to schedule last activity (" + activity.getClass().getName() + ") for restart"); - try { - JobScheduler scheduler = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE); - assert scheduler != null; - PersistableBundle extras = new PersistableBundle(); - extras.putString(RestartingAdministrator.EXTRA_LAST_ACTIVITY, activity.getClass().getName()); - scheduler.schedule(new JobInfo.Builder(1, new ComponentName(context, RestartingService.class)) - .setExtras(extras) - .setOverrideDeadline(100) - .build()); - if (ACRA.DEV_LOGGING) ACRA.log.d(ACRA.LOG_TAG, "Successfully scheduled last activity (" + activity.getClass().getName() + ") for restart"); - } catch (Exception e) { - ACRA.log.w(ACRA.LOG_TAG, "Failed to schedule last activity for restart", e); - } - } else { - ACRA.log.i(ACRA.LOG_TAG, "Activity restart is enabled but no activity was found. Nothing to do."); - } - } - return true; - } -} diff --git a/acra-advanced-scheduler/src/main/java/org/acra/scheduler/RestartingAdministrator.kt b/acra-advanced-scheduler/src/main/java/org/acra/scheduler/RestartingAdministrator.kt new file mode 100644 index 0000000000..0f8d32442e --- /dev/null +++ b/acra-advanced-scheduler/src/main/java/org/acra/scheduler/RestartingAdministrator.kt @@ -0,0 +1,69 @@ +/* + * 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.job.JobInfo +import android.app.job.JobScheduler +import android.content.ComponentName +import android.content.Context +import android.os.PersistableBundle +import com.google.auto.service.AutoService +import org.acra.builder.LastActivityManager +import org.acra.config.ConfigUtils.getPluginConfiguration +import org.acra.config.CoreConfiguration +import org.acra.config.ReportingAdministrator +import org.acra.config.SchedulerConfiguration +import org.acra.log.debug +import org.acra.log.info +import org.acra.log.warn +import org.acra.plugins.HasConfigPlugin + +/** + * @author F43nd1r + * @since 07.05.18 + */ +@AutoService(ReportingAdministrator::class) +class RestartingAdministrator : HasConfigPlugin(SchedulerConfiguration::class.java), ReportingAdministrator { + override fun shouldFinishActivity(context: Context, config: CoreConfiguration, lastActivityManager: LastActivityManager): Boolean { + debug { "RestartingAdministrator entry" } + if (getPluginConfiguration(config, SchedulerConfiguration::class.java).restartAfterCrash) { + val activity = lastActivityManager.lastActivity + if (activity != null) { + debug { "Try to schedule last activity (" + activity.javaClass.name + ") for restart" } + try { + val scheduler = (context.getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler) + val extras = PersistableBundle() + extras.putString(EXTRA_LAST_ACTIVITY, activity.javaClass.name) + scheduler.schedule(JobInfo.Builder(1, ComponentName(context, RestartingService::class.java)) + .setExtras(extras) + .setOverrideDeadline(100) + .build()) + debug { "Successfully scheduled last activity (" + activity.javaClass.name + ") for restart" } + } catch (e: Exception) { + warn(e) { "Failed to schedule last activity for restart" } + } + } else { + info { "Activity restart is enabled but no activity was found. Nothing to do." } + } + } + return true + } + + companion object { + const val EXTRA_LAST_ACTIVITY = "lastActivity" + const val EXTRA_ACTIVITY_RESTART_AFTER_CRASH = "restartAfterCrash" + } +} \ No newline at end of file diff --git a/acra-advanced-scheduler/src/main/java/org/acra/scheduler/RestartingService.java b/acra-advanced-scheduler/src/main/java/org/acra/scheduler/RestartingService.java deleted file mode 100644 index 07a29d32d0..0000000000 --- a/acra-advanced-scheduler/src/main/java/org/acra/scheduler/RestartingService.java +++ /dev/null @@ -1,38 +0,0 @@ -package org.acra.scheduler; - -import android.app.Activity; -import android.app.job.JobParameters; -import android.app.job.JobService; -import android.content.Intent; -import org.acra.ACRA; - -/** - * @author Lukas - * @since 31.12.2018 - */ -public class RestartingService extends JobService { - @Override - public boolean onStartJob(JobParameters params) { - String className = params.getExtras().getString(RestartingAdministrator.EXTRA_LAST_ACTIVITY); - if(ACRA.DEV_LOGGING) ACRA.log.d(ACRA.LOG_TAG, "Restarting activity " + className + "..."); - if (className != null) { - try { - //noinspection unchecked - Class activityClass = (Class) Class.forName(className); - final Intent intent = new Intent(this, activityClass); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - intent.putExtra(RestartingAdministrator.EXTRA_ACTIVITY_RESTART_AFTER_CRASH, true); - startActivity(intent); - if(ACRA.DEV_LOGGING) ACRA.log.d(ACRA.LOG_TAG, className + " was successfully restarted"); - } catch (ClassNotFoundException e) { - ACRA.log.w(ACRA.LOG_TAG, "Unable to find activity class" + className, e); - } - } - return false; - } - - @Override - public boolean onStopJob(JobParameters params) { - return false; - } -} diff --git a/acra-advanced-scheduler/src/main/java/org/acra/scheduler/RestartingService.kt b/acra-advanced-scheduler/src/main/java/org/acra/scheduler/RestartingService.kt new file mode 100644 index 0000000000..d747205851 --- /dev/null +++ b/acra-advanced-scheduler/src/main/java/org/acra/scheduler/RestartingService.kt @@ -0,0 +1,36 @@ +package org.acra.scheduler + +import android.app.Activity +import android.app.job.JobParameters +import android.app.job.JobService +import android.content.Intent +import org.acra.log.debug +import org.acra.log.warn + +/** + * @author Lukas + * @since 31.12.2018 + */ +class RestartingService : JobService() { + override fun onStartJob(params: JobParameters): Boolean { + val className = params.extras.getString(RestartingAdministrator.EXTRA_LAST_ACTIVITY) + debug { "Restarting activity $className..." } + if (className != null) { + try { + @Suppress("UNCHECKED_CAST") val activityClass = Class.forName(className) as Class + val intent = Intent(this, activityClass) + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + intent.putExtra(RestartingAdministrator.EXTRA_ACTIVITY_RESTART_AFTER_CRASH, true) + startActivity(intent) + debug { "$className was successfully restarted" } + } catch (e: ClassNotFoundException) { + warn(e) { "Unable to find activity class$className" } + } + } + return false + } + + override fun onStopJob(params: JobParameters): Boolean { + return false + } +} \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/config/ReportingAdministrator.kt b/acra-core/src/main/java/org/acra/config/ReportingAdministrator.kt index a84e5a93df..7b023e0d3b 100644 --- a/acra-core/src/main/java/org/acra/config/ReportingAdministrator.kt +++ b/acra-core/src/main/java/org/acra/config/ReportingAdministrator.kt @@ -59,7 +59,7 @@ interface ReportingAdministrator : Plugin { * @param config the current config */ fun notifyReportDropped(context: Context, config: CoreConfiguration) {} - fun shouldFinishActivity(context: Context, config: CoreConfiguration, lastActivityManager: LastActivityManager?): Boolean { + fun shouldFinishActivity(context: Context, config: CoreConfiguration, lastActivityManager: LastActivityManager): Boolean { return true } From 52208bb29764d7168d4a2b8d0c8f9fa354ea7ae7 Mon Sep 17 00:00:00 2001 From: f43nd1r Date: Mon, 23 Nov 2020 02:10:57 +0100 Subject: [PATCH 05/16] dialog to kotlin --- .../org/acra/dialog/CrashReportDialog.java | 297 ------------------ .../java/org/acra/dialog/CrashReportDialog.kt | 233 ++++++++++++++ .../acra/dialog/CrashReportDialogHelper.java | 139 -------- .../acra/dialog/CrashReportDialogHelper.kt | 121 +++++++ .../acra/interaction/DialogInteraction.java | 89 ------ .../org/acra/interaction/DialogInteraction.kt | 79 +++++ 6 files changed, 433 insertions(+), 525 deletions(-) delete mode 100644 acra-dialog/src/main/java/org/acra/dialog/CrashReportDialog.java create mode 100644 acra-dialog/src/main/java/org/acra/dialog/CrashReportDialog.kt delete mode 100644 acra-dialog/src/main/java/org/acra/dialog/CrashReportDialogHelper.java create mode 100644 acra-dialog/src/main/java/org/acra/dialog/CrashReportDialogHelper.kt delete mode 100644 acra-dialog/src/main/java/org/acra/interaction/DialogInteraction.java create mode 100644 acra-dialog/src/main/java/org/acra/interaction/DialogInteraction.kt diff --git a/acra-dialog/src/main/java/org/acra/dialog/CrashReportDialog.java b/acra-dialog/src/main/java/org/acra/dialog/CrashReportDialog.java deleted file mode 100644 index 7e3e57ce0f..0000000000 --- a/acra-dialog/src/main/java/org/acra/dialog/CrashReportDialog.java +++ /dev/null @@ -1,297 +0,0 @@ -/* - * Copyright (c) 2017 the ACRA team - * - * 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.dialog; - -import android.app.Activity; -import android.app.AlertDialog; -import android.content.DialogInterface; -import android.content.SharedPreferences; -import android.os.Bundle; -import android.text.InputType; -import android.util.TypedValue; -import android.view.View; -import android.view.ViewGroup.LayoutParams; -import android.widget.EditText; -import android.widget.LinearLayout; -import android.widget.ScrollView; -import android.widget.TextView; -import androidx.annotation.CallSuper; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import org.acra.ACRA; -import org.acra.ACRAConstants; -import org.acra.config.ConfigUtils; -import org.acra.config.DialogConfiguration; -import org.acra.prefs.SharedPreferencesFactory; - - -/** - * This is the dialog Activity used by ACRA to get authorization from the user - * to send reports. - * - * @author F43nd1r & Various - **/ -@SuppressWarnings({"WeakerAccess", "unused"}) -public class CrashReportDialog extends Activity implements DialogInterface.OnClickListener { - - private static final String STATE_EMAIL = "email"; - private static final String STATE_COMMENT = "comment"; - - private LinearLayout scrollable; - private EditText userCommentView; - private EditText userEmailView; - private SharedPreferencesFactory sharedPreferencesFactory; - private DialogConfiguration dialogConfiguration; - private CrashReportDialogHelper helper; - private int padding; - - private AlertDialog mDialog; - - @Override - protected void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - try { - helper = new CrashReportDialogHelper(this, getIntent()); - scrollable = new LinearLayout(this); - scrollable.setOrientation(LinearLayout.VERTICAL); - sharedPreferencesFactory = new SharedPreferencesFactory(getApplicationContext(), helper.getConfig()); - dialogConfiguration = ConfigUtils.getPluginConfiguration(helper.getConfig(), DialogConfiguration.class); - final int themeResourceId = dialogConfiguration.getResTheme(); - if (themeResourceId != ACRAConstants.DEFAULT_RES_VALUE) setTheme(themeResourceId); - padding = loadPaddingFromTheme(); - - buildAndShowDialog(savedInstanceState); - } catch (IllegalArgumentException e) { - finish(); - } - } - - /** - * Build the dialog from the values in config - * - * @param savedInstanceState old state to restore - */ - protected void buildAndShowDialog(@Nullable Bundle savedInstanceState) { - final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this); - final String title = dialogConfiguration.getTitle(); - if (!title.isEmpty()) { - dialogBuilder.setTitle(title); - } - final int iconResourceId = dialogConfiguration.getResIcon(); - if (iconResourceId != ACRAConstants.DEFAULT_RES_VALUE) { - dialogBuilder.setIcon(iconResourceId); - } - dialogBuilder.setView(buildCustomView(savedInstanceState)) - .setPositiveButton(dialogConfiguration.getPositiveButtonText(), this) - .setNegativeButton(dialogConfiguration.getNegativeButtonText(), this); - - mDialog = dialogBuilder.create(); - mDialog.setCanceledOnTouchOutside(false); - mDialog.show(); - } - - @NonNull - protected View buildCustomView(@Nullable Bundle savedInstanceState) { - final ScrollView root = new ScrollView(this); - root.setPadding(padding, padding, padding, padding); - root.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); - root.setFocusable(true); - root.setFocusableInTouchMode(true); - root.addView(scrollable); - - addViewToDialog(getMainView()); - - // Add an optional prompt for user comments - final View comment = getCommentLabel(); - if (comment != null) { - comment.setPadding(comment.getPaddingLeft(), padding, comment.getPaddingRight(), comment.getPaddingBottom()); - addViewToDialog(comment); - String savedComment = null; - if (savedInstanceState != null) { - savedComment = savedInstanceState.getString(STATE_COMMENT); - } - userCommentView = getCommentPrompt(savedComment); - addViewToDialog(userCommentView); - } - - // Add an optional user email field - final View email = getEmailLabel(); - if (email != null) { - email.setPadding(email.getPaddingLeft(), padding, email.getPaddingRight(), email.getPaddingBottom()); - addViewToDialog(email); - String savedEmail = null; - if (savedInstanceState != null) { - savedEmail = savedInstanceState.getString(STATE_EMAIL); - } - userEmailView = getEmailPrompt(savedEmail); - addViewToDialog(userEmailView); - } - return root; - } - - /** - * adds a view to the end of the dialog - * - * @param v the view to add - */ - protected final void addViewToDialog(@NonNull View v) { - scrollable.addView(v); - } - - /** - * Creates a main view containing text of resText, or nothing if not found - * - * @return the main view - */ - @NonNull - protected View getMainView() { - final TextView text = new TextView(this); - final String dialogText = dialogConfiguration.getText(); - if (!dialogText.isEmpty()) { - text.setText(dialogText); - } - return text; - } - - /** - * creates a comment label view with resCommentPrompt as text - * - * @return the label or null if there is no resource - */ - @Nullable - protected View getCommentLabel() { - final String commentPrompt = dialogConfiguration.getCommentPrompt(); - if (!commentPrompt.isEmpty()) { - final TextView labelView = new TextView(this); - labelView.setText(commentPrompt); - return labelView; - } - return null; - } - - /** - * creates a comment prompt - * - * @param savedComment the content of the prompt (usually from a saved state) - * @return the comment prompt - */ - @NonNull - protected EditText getCommentPrompt(@Nullable CharSequence savedComment) { - final EditText userCommentView = new EditText(this); - userCommentView.setLines(2); - if (savedComment != null) { - userCommentView.setText(savedComment); - } - return userCommentView; - } - - /** - * creates a email label view with resEmailPrompt as text - * - * @return the label or null if there is no resource - */ - @Nullable - protected View getEmailLabel() { - final String emailPrompt = dialogConfiguration.getEmailPrompt(); - if (!emailPrompt.isEmpty()) { - final TextView labelView = new TextView(this); - labelView.setText(emailPrompt); - return labelView; - } - return null; - } - - /** - * creates an email prompt - * - * @param savedEmail the content of the prompt (usually from a saved state or settings) - * @return the email prompt - */ - @NonNull - protected EditText getEmailPrompt(@Nullable CharSequence savedEmail) { - final EditText userEmailView = new EditText(this); - userEmailView.setSingleLine(); - userEmailView.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS); - - if (savedEmail != null) { - userEmailView.setText(savedEmail); - } else { - final SharedPreferences prefs = sharedPreferencesFactory.create(); - userEmailView.setText(prefs.getString(ACRA.PREF_USER_EMAIL_ADDRESS, "")); - } - return userEmailView; - } - - @Override - public void onClick(DialogInterface dialog, int which) { - if (which == DialogInterface.BUTTON_POSITIVE) { - // Retrieve user comment - final String comment = userCommentView != null ? userCommentView.getText().toString() : ""; - - // Store the user email - final String userEmail; - final SharedPreferences prefs = sharedPreferencesFactory.create(); - if (userEmailView != null) { - userEmail = userEmailView.getText().toString(); - prefs.edit().putString(ACRA.PREF_USER_EMAIL_ADDRESS, userEmail).apply(); - } else { - userEmail = prefs.getString(ACRA.PREF_USER_EMAIL_ADDRESS, ""); - } - helper.sendCrash(comment, userEmail); - } else { - helper.cancelReports(); - } - - finish(); - } - - /* - * (non-Javadoc) - * - * @see android.app.Activity#onSaveInstanceState(android.os.Bundle) - */ - @CallSuper - @Override - protected void onSaveInstanceState(@NonNull Bundle outState) { - super.onSaveInstanceState(outState); - if (userCommentView != null && userCommentView.getText() != null) { - outState.putString(STATE_COMMENT, userCommentView.getText().toString()); - } - if (userEmailView != null && userEmailView.getText() != null) { - outState.putString(STATE_EMAIL, userEmailView.getText().toString()); - } - } - - /** - * @return the AlertDialog displayed by this Activity - */ - protected AlertDialog getDialog() { - return mDialog; - } - - /** - * @return value of ?dialogPreferredPadding from theme or 10 if not set. - */ - protected int loadPaddingFromTheme() { - TypedValue value = new TypedValue(); - if (getTheme().resolveAttribute(android.R.attr.dialogPreferredPadding, value, true)) { - return TypedValue.complexToDimensionPixelSize(value.data, getResources().getDisplayMetrics()); - } - //attribute not set, fall back to a default value - return 10; - } -} \ No newline at end of file diff --git a/acra-dialog/src/main/java/org/acra/dialog/CrashReportDialog.kt b/acra-dialog/src/main/java/org/acra/dialog/CrashReportDialog.kt new file mode 100644 index 0000000000..ee5f83638f --- /dev/null +++ b/acra-dialog/src/main/java/org/acra/dialog/CrashReportDialog.kt @@ -0,0 +1,233 @@ +/* + * Copyright (c) 2017 the ACRA team + * + * 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.dialog + +import android.R +import android.app.Activity +import android.app.AlertDialog +import android.content.DialogInterface +import android.os.Bundle +import android.text.InputType +import android.util.TypedValue +import android.view.View +import android.view.ViewGroup +import android.widget.EditText +import android.widget.LinearLayout +import android.widget.ScrollView +import android.widget.TextView +import androidx.annotation.CallSuper +import org.acra.ACRA +import org.acra.ACRAConstants +import org.acra.config.ConfigUtils.getPluginConfiguration +import org.acra.config.DialogConfiguration +import org.acra.prefs.SharedPreferencesFactory + +/** + * This is the dialog Activity used by ACRA to get authorization from the user + * to send reports. + * + * @author F43nd1r & Various + */ +@Suppress("MemberVisibilityCanBePrivate") +open class CrashReportDialog : Activity(), DialogInterface.OnClickListener { + private lateinit var scrollable: LinearLayout + private var userCommentView: EditText? = null + private var userEmailView: EditText? = null + private lateinit var sharedPreferencesFactory: SharedPreferencesFactory + private lateinit var dialogConfiguration: DialogConfiguration + private lateinit var helper: CrashReportDialogHelper + private var padding = 0 + + /** + * @return the AlertDialog displayed by this Activity + */ + protected lateinit var dialog: AlertDialog + private set + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + try { + helper = CrashReportDialogHelper(this, intent) + scrollable = LinearLayout(this) + scrollable.orientation = LinearLayout.VERTICAL + sharedPreferencesFactory = SharedPreferencesFactory(applicationContext, helper.config) + dialogConfiguration = getPluginConfiguration(helper.config, DialogConfiguration::class.java) + dialogConfiguration.resTheme.takeIf { it != ACRAConstants.DEFAULT_RES_VALUE }?.let { setTheme(it) } + padding = loadPaddingFromTheme() + buildAndShowDialog(savedInstanceState) + } catch (e: IllegalArgumentException) { + finish() + } + } + + /** + * Build the dialog from the values in config + * + * @param savedInstanceState old state to restore + */ + protected fun buildAndShowDialog(savedInstanceState: Bundle?) { + val dialogBuilder = AlertDialog.Builder(this) + dialogConfiguration.title.takeIf { it.isNotEmpty() }?.let { dialogBuilder.setTitle(title) } + dialogConfiguration.resIcon.takeIf { it != ACRAConstants.DEFAULT_RES_VALUE }?.let { dialogBuilder.setIcon(it) } + dialogBuilder.setView(buildCustomView(savedInstanceState)) + .setPositiveButton(dialogConfiguration.positiveButtonText, this) + .setNegativeButton(dialogConfiguration.negativeButtonText, this) + dialog = dialogBuilder.create() + dialog.setCanceledOnTouchOutside(false) + dialog.show() + } + + protected fun buildCustomView(savedInstanceState: Bundle?): View { + val root = ScrollView(this) + .apply { + setPadding(padding, padding, padding, padding) + layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT) + isFocusable = true + isFocusableInTouchMode = true + addView(scrollable) + } + addViewToDialog(getMainView()) + + // Add an optional prompt for user comments + getCommentLabel()?.let { + it.setPadding(it.paddingLeft, padding, it.paddingRight, it.paddingBottom) + addViewToDialog(it) + var savedComment: String? = null + if (savedInstanceState != null) { + savedComment = savedInstanceState.getString(STATE_COMMENT) + } + userCommentView = getCommentPrompt(savedComment).apply { addViewToDialog(this) } + } + + // Add an optional user email field + getEmailLabel()?.let { + it.setPadding(it.paddingLeft, padding, it.paddingRight, it.paddingBottom) + addViewToDialog(it) + var savedEmail: String? = null + if (savedInstanceState != null) { + savedEmail = savedInstanceState.getString(STATE_EMAIL) + } + userEmailView = getEmailPrompt(savedEmail).apply { addViewToDialog(this) } + } + return root + } + + /** + * adds a view to the end of the dialog + * + * @param v the view to add + */ + protected fun addViewToDialog(v: View) { + scrollable.addView(v) + } + + /** + * Creates a main view containing text of resText, or nothing if not found + * + * @return the main view + */ + protected fun getMainView(): View { + return TextView(this).apply { dialogConfiguration.text.takeIf { it.isNotEmpty() }?.let { text = it } } + } + + /** + * creates a comment label view with resCommentPrompt as text + * + * @return the label or null if there is no resource + */ + protected fun getCommentLabel(): View? { + return dialogConfiguration.commentPrompt.takeIf { it.isNotEmpty() }?.let { TextView(this).apply { text = it } } + } + + /** + * creates a comment prompt + * + * @param savedComment the content of the prompt (usually from a saved state) + * @return the comment prompt + */ + protected fun getCommentPrompt(savedComment: CharSequence?): EditText { + return EditText(this).apply { + setLines(2) + savedComment?.let { setText(it) } + } + } + + /** + * creates a email label view with resEmailPrompt as text + * + * @return the label or null if there is no resource + */ + protected fun getEmailLabel(): View? { + return dialogConfiguration.emailPrompt.takeIf { it.isNotEmpty() }?.let { TextView(this).apply { text = it } } + } + + /** + * creates an email prompt + * + * @param savedEmail the content of the prompt (usually from a saved state or settings) + * @return the email prompt + */ + protected fun getEmailPrompt(savedEmail: CharSequence?): EditText { + return EditText(this).apply { + setSingleLine() + inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS + setText(savedEmail ?: sharedPreferencesFactory.create().getString(ACRA.PREF_USER_EMAIL_ADDRESS, "")) + } + } + + override fun onClick(dialog: DialogInterface, which: Int) { + if (which == DialogInterface.BUTTON_POSITIVE) { + // Retrieve user comment + val comment = userCommentView?.text?.toString() ?: "" + + // Store the user email + val prefs = sharedPreferencesFactory.create() + val userEmail: String = userEmailView?.text?.toString()?.also { prefs.edit().putString(ACRA.PREF_USER_EMAIL_ADDRESS, it).apply() } ?: prefs.getString( + ACRA.PREF_USER_EMAIL_ADDRESS, "")!! + helper.sendCrash(comment, userEmail) + } else { + helper.cancelReports() + } + finish() + } + + /* + * (non-Javadoc) + * + * @see android.app.Activity#onSaveInstanceState(android.os.Bundle) + */ + @CallSuper + override fun onSaveInstanceState(outState: Bundle) { + super.onSaveInstanceState(outState) + userCommentView?.text?.let { outState.putString(STATE_COMMENT, it.toString()) } + userEmailView?.text?.let { outState.putString(STATE_EMAIL, it.toString()) } + } + + /** + * @return value of ?dialogPreferredPadding from theme or 10 if not set. + */ + protected fun loadPaddingFromTheme(): Int { + val value = TypedValue() + return if (theme.resolveAttribute(R.attr.dialogPreferredPadding, value, true)) { + TypedValue.complexToDimensionPixelSize(value.data, resources.displayMetrics) + } else 10 //attribute not set, fall back to a default value + } + + companion object { + private const val STATE_EMAIL = "email" + private const val STATE_COMMENT = "comment" + } +} \ No newline at end of file diff --git a/acra-dialog/src/main/java/org/acra/dialog/CrashReportDialogHelper.java b/acra-dialog/src/main/java/org/acra/dialog/CrashReportDialogHelper.java deleted file mode 100644 index 8bf1050331..0000000000 --- a/acra-dialog/src/main/java/org/acra/dialog/CrashReportDialogHelper.java +++ /dev/null @@ -1,139 +0,0 @@ -/* - * Copyright (c) 2019 - * - * 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.dialog; - -import android.content.Context; -import android.content.Intent; -import android.os.Bundle; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.WorkerThread; -import org.acra.ACRA; -import org.acra.config.CoreConfiguration; -import org.acra.data.CrashReportData; -import org.acra.file.BulkReportDeleter; -import org.acra.file.CrashReportPersister; -import org.acra.interaction.DialogInteraction; -import org.acra.scheduler.SchedulerStarter; -import org.json.JSONException; - -import java.io.File; -import java.io.IOException; -import java.io.Serializable; - -import static org.acra.ACRA.LOG_TAG; -import static org.acra.ReportField.USER_COMMENT; -import static org.acra.ReportField.USER_EMAIL; - -/** - * Use this class to integrate your custom crash report dialog with ACRA. - * @author f43nd1r - * @since 5.4.0 - */ -public class CrashReportDialogHelper { - - private final File reportFile; - private final CoreConfiguration config; - private final Context context; - private CrashReportData reportData; - - /** - * Call this in your {@link android.app.Activity#onCreate(Bundle)}. - * The intent must contain two extras: - *
    - *
  1. {@link DialogInteraction#EXTRA_REPORT_FILE}
  2. - *
  3. {@link DialogInteraction#EXTRA_REPORT_CONFIG}
  4. - *
- * - * @param context a context - * @param intent the intent which started this activity - * @throws IllegalArgumentException if the intent cannot be parsed or does not contain the correct data - */ - public CrashReportDialogHelper(@NonNull Context context, @NonNull Intent intent) throws IllegalArgumentException { - this.context = context; - final Serializable sConfig = intent.getSerializableExtra(DialogInteraction.EXTRA_REPORT_CONFIG); - final Serializable sReportFile = intent.getSerializableExtra(DialogInteraction.EXTRA_REPORT_FILE); - - if ((sConfig instanceof CoreConfiguration) && (sReportFile instanceof File)) { - config = (CoreConfiguration) sConfig; - reportFile = (File) sReportFile; - } else { - ACRA.log.w(LOG_TAG, "Illegal or incomplete call of " + getClass().getSimpleName()); - throw new IllegalArgumentException(); - } - } - - /** - * loads the current report data - * - * @return report data - * @throws IOException if there was a problem with the report file - */ - @WorkerThread - @NonNull - public CrashReportData getReportData() throws IOException { - if (reportData == null) { - try { - reportData = new CrashReportPersister().load(reportFile); - } catch (JSONException e) { - throw new IOException(e); - } - } - return reportData; - } - - - /** - * Cancel any pending crash reports. - */ - public void cancelReports() { - new Thread(() -> new BulkReportDeleter(context).deleteReports(false, 0)).start(); - } - - - /** - * Send crash report given user's comment and email address. - * - * @param comment Comment (may be null) provided by the user. - * @param userEmail Email address (may be null) provided by the user. - */ - public void sendCrash(@Nullable String comment, @Nullable String userEmail) { - new Thread(() -> { - try { - if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "Add user comment to " + reportFile); - final CrashReportData crashData = getReportData(); - crashData.put(USER_COMMENT, comment == null ? "" : comment); - crashData.put(USER_EMAIL, userEmail == null ? "" : userEmail); - new CrashReportPersister().store(crashData, reportFile); - } catch (IOException | JSONException e) { - ACRA.log.w(LOG_TAG, "User comment not added: ", e); - } - - // Start the report sending task - new SchedulerStarter(context, config).scheduleReports(reportFile, false); - }).start(); - } - - /** - * Provides the configuration - * - * @return the main config - */ - public CoreConfiguration getConfig() { - return config; - } -} diff --git a/acra-dialog/src/main/java/org/acra/dialog/CrashReportDialogHelper.kt b/acra-dialog/src/main/java/org/acra/dialog/CrashReportDialogHelper.kt new file mode 100644 index 0000000000..4c5cec9c14 --- /dev/null +++ b/acra-dialog/src/main/java/org/acra/dialog/CrashReportDialogHelper.kt @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2019 + * + * 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.dialog + +import android.content.Context +import android.content.Intent +import androidx.annotation.WorkerThread +import org.acra.ACRA +import org.acra.ReportField +import org.acra.config.CoreConfiguration +import org.acra.data.CrashReportData +import org.acra.file.BulkReportDeleter +import org.acra.file.CrashReportPersister +import org.acra.interaction.DialogInteraction +import org.acra.log.debug +import org.acra.log.error +import org.acra.log.warn +import org.acra.scheduler.SchedulerStarter +import org.json.JSONException +import java.io.File +import java.io.IOException + +/** + * Use this class to integrate your custom crash report dialog with ACRA. + * + * Call this in your [android.app.Activity.onCreate]. + * The intent must contain two extras: + * + * 1. [DialogInteraction.EXTRA_REPORT_FILE] + * 1. [DialogInteraction.EXTRA_REPORT_CONFIG] + * + * + * @param context a context + * @param intent the intent which started this activity + * @throws IllegalArgumentException if the intent cannot be parsed or does not contain the correct data + * @author f43nd1r + * @since 5.4.0 + */ +class CrashReportDialogHelper(private val context: Context, intent: Intent) { + private val reportFile: File + + /** + * Provides the configuration + * + * @return the main config + */ + val config: CoreConfiguration + + init { + val sConfig = intent.getSerializableExtra(DialogInteraction.EXTRA_REPORT_CONFIG) + val sReportFile = intent.getSerializableExtra(DialogInteraction.EXTRA_REPORT_FILE) + if (sConfig is CoreConfiguration && sReportFile is File) { + config = sConfig + reportFile = sReportFile + } else { + error { "Illegal or incomplete call of " + javaClass.simpleName } + throw IllegalArgumentException() + } + } + + /** + * loads the current report data + * + * @return report data + * @throws IOException if there was a problem with the report file + */ + @get:Throws(IOException::class) + @get:WorkerThread + val reportData: CrashReportData by lazy { + try { + CrashReportPersister().load(reportFile) + } catch (e: JSONException) { + throw IOException(e) + } + } + + /** + * Cancel any pending crash reports. + */ + fun cancelReports() { + Thread { BulkReportDeleter(context).deleteReports(false, 0) }.start() + } + + /** + * Send crash report given user's comment and email address. + * + * @param comment Comment (may be null) provided by the user. + * @param userEmail Email address (may be null) provided by the user. + */ + fun sendCrash(comment: String?, userEmail: String?) { + Thread { + try { + debug { "Add user comment to $reportFile"} + val crashData = reportData + crashData.put(ReportField.USER_COMMENT, comment ?: "") + crashData.put(ReportField.USER_EMAIL, userEmail ?: "") + CrashReportPersister().store(crashData, reportFile) + } catch (e: IOException) { + warn(e) { "User comment not added: "} + } catch (e: JSONException) { + warn(e) { "User comment not added: "} + } + + // Start the report sending task + SchedulerStarter(context, config).scheduleReports(reportFile, false) + }.start() + } +} \ No newline at end of file diff --git a/acra-dialog/src/main/java/org/acra/interaction/DialogInteraction.java b/acra-dialog/src/main/java/org/acra/interaction/DialogInteraction.java deleted file mode 100644 index 09f57856e4..0000000000 --- a/acra-dialog/src/main/java/org/acra/interaction/DialogInteraction.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (c) 2017 - * - * 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.interaction; - -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import androidx.annotation.NonNull; - -import com.google.auto.service.AutoService; - -import org.acra.ACRA; -import org.acra.config.ConfigUtils; -import org.acra.config.CoreConfiguration; -import org.acra.config.DialogConfiguration; -import org.acra.plugins.HasConfigPlugin; -import org.acra.prefs.SharedPreferencesFactory; - -import java.io.File; - -import static org.acra.ACRA.LOG_TAG; - -/** - * @author F43nd1r - * @since 02.06.2017 - */ -@AutoService(ReportInteraction.class) -public class DialogInteraction extends HasConfigPlugin implements ReportInteraction { - /** - * Used in the intent starting CrashReportDialog to provide the name of the - * latest generated report file in order to be able to associate the user - * comment. - */ - public static final String EXTRA_REPORT_FILE = "REPORT_FILE"; - /** - * Used in the intent starting CrashReportDialog to provide the AcraConfig to use when gathering the crash info. - *

- * This can be used by any BaseCrashReportDialog subclass to custom the dialog. - */ - public static final String EXTRA_REPORT_CONFIG = "REPORT_CONFIG"; - - public DialogInteraction() { - super(DialogConfiguration.class); - } - - @Override - public boolean performInteraction(@NonNull Context context, @NonNull CoreConfiguration config, @NonNull File reportFile) { - final SharedPreferences prefs = new SharedPreferencesFactory(context, config).create(); - if (prefs.getBoolean(ACRA.PREF_ALWAYS_ACCEPT, false)) { - return true; - } - // Create a new activity task with the confirmation dialog. - // This new task will be persisted on application restart - // right after its death. - if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "Creating CrashReportDialog for " + reportFile); - final Intent dialogIntent = createCrashReportDialogIntent(context, config, reportFile); - dialogIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - context.startActivity(dialogIntent); - return false; - } - - /** - * Creates an Intent that can be used to create and show a CrashReportDialog. - * - * @param reportFile Error report file to display in the crash report dialog. - */ - @NonNull - private Intent createCrashReportDialogIntent(@NonNull Context context, @NonNull CoreConfiguration config, @NonNull File reportFile) { - if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "Creating DialogIntent for " + reportFile); - final Intent dialogIntent = new Intent(context, ConfigUtils.getPluginConfiguration(config, DialogConfiguration.class).getReportDialogClass()); - dialogIntent.putExtra(EXTRA_REPORT_FILE, reportFile); - dialogIntent.putExtra(EXTRA_REPORT_CONFIG, config); - return dialogIntent; - } -} diff --git a/acra-dialog/src/main/java/org/acra/interaction/DialogInteraction.kt b/acra-dialog/src/main/java/org/acra/interaction/DialogInteraction.kt new file mode 100644 index 0000000000..717f56ba26 --- /dev/null +++ b/acra-dialog/src/main/java/org/acra/interaction/DialogInteraction.kt @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2017 + * + * 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.interaction + +import android.content.Context +import android.content.Intent +import com.google.auto.service.AutoService +import org.acra.ACRA +import org.acra.config.ConfigUtils.getPluginConfiguration +import org.acra.config.CoreConfiguration +import org.acra.config.DialogConfiguration +import org.acra.log.debug +import org.acra.plugins.HasConfigPlugin +import org.acra.prefs.SharedPreferencesFactory +import java.io.File + +/** + * @author F43nd1r + * @since 02.06.2017 + */ +@AutoService(ReportInteraction::class) +class DialogInteraction : HasConfigPlugin(DialogConfiguration::class.java), ReportInteraction { + override fun performInteraction(context: Context, config: CoreConfiguration, reportFile: File): Boolean { + if (SharedPreferencesFactory(context, config).create().getBoolean(ACRA.PREF_ALWAYS_ACCEPT, false)) { + return true + } + // Create a new activity task with the confirmation dialog. + // This new task will be persisted on application restart + // right after its death. + debug { "Creating CrashReportDialog for $reportFile" } + val dialogIntent = createCrashReportDialogIntent(context, config, reportFile) + dialogIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK + context.startActivity(dialogIntent) + return false + } + + /** + * Creates an Intent that can be used to create and show a CrashReportDialog. + * + * @param reportFile Error report file to display in the crash report dialog. + */ + private fun createCrashReportDialogIntent(context: Context, config: CoreConfiguration, reportFile: File): Intent { + debug { "Creating DialogIntent for $reportFile" } + val dialogIntent = Intent(context, getPluginConfiguration(config, DialogConfiguration::class.java).reportDialogClass) + dialogIntent.putExtra(EXTRA_REPORT_FILE, reportFile) + dialogIntent.putExtra(EXTRA_REPORT_CONFIG, config) + return dialogIntent + } + + companion object { + /** + * Used in the intent starting CrashReportDialog to provide the name of the + * latest generated report file in order to be able to associate the user + * comment. + */ + const val EXTRA_REPORT_FILE = "REPORT_FILE" + + /** + * Used in the intent starting CrashReportDialog to provide the AcraConfig to use when gathering the crash info. + * + * + * This can be used by any BaseCrashReportDialog subclass to custom the dialog. + */ + const val EXTRA_REPORT_CONFIG = "REPORT_CONFIG" + } +} \ No newline at end of file From 10cb4017c30d4b4a834e19fb4b66031bfbf4fba5 Mon Sep 17 00:00:00 2001 From: f43nd1r Date: Mon, 23 Nov 2020 02:56:21 +0100 Subject: [PATCH 06/16] http to kotlin --- ...r.java => BaseHttpConfigurationBuilder.kt} | 41 ++-- .../java/org/acra/http/BaseHttpRequest.java | 218 ----------------- .../java/org/acra/http/BaseHttpRequest.kt | 169 +++++++++++++ .../java/org/acra/http/BinaryHttpRequest.java | 56 ----- .../java/org/acra/http/BinaryHttpRequest.kt | 41 ++++ .../org/acra/http/DefaultHttpRequest.java | 54 ---- .../java/org/acra/http/DefaultHttpRequest.kt | 40 +++ .../http/{HttpRequest.java => HttpRequest.kt} | 16 +- .../org/acra/http/MultipartHttpRequest.java | 89 ------- .../org/acra/http/MultipartHttpRequest.kt | 75 ++++++ .../acra/security/AssetKeyStoreFactory.java | 57 ----- .../org/acra/security/AssetKeyStoreFactory.kt | 42 ++++ .../acra/security/BaseKeyStoreFactory.java | 120 --------- .../org/acra/security/BaseKeyStoreFactory.kt | 84 +++++++ .../acra/security/FileKeyStoreFactory.java | 58 ----- .../org/acra/security/FileKeyStoreFactory.kt | 43 ++++ ...eyStoreFactory.java => KeyStoreFactory.kt} | 17 +- .../org/acra/security/KeyStoreHelper.java | 70 ------ .../java/org/acra/security/KeyStoreHelper.kt | 61 +++++ ...StoreFactory.java => NoKeyStoreFactory.kt} | 19 +- .../ProtocolSocketFactoryWrapper.java | 100 -------- .../security/ProtocolSocketFactoryWrapper.kt | 75 ++++++ .../security/ResourceKeyStoreFactory.java | 49 ---- .../acra/security/ResourceKeyStoreFactory.kt | 34 +++ .../org/acra/security/{TLS.java => TLS.kt} | 22 +- .../main/java/org/acra/sender/HttpSender.java | 230 ------------------ .../main/java/org/acra/sender/HttpSender.kt | 191 +++++++++++++++ .../org/acra/sender/HttpSenderFactory.java | 42 ---- .../java/org/acra/sender/HttpSenderFactory.kt | 30 +++ .../src/main/java/org/acra/util/UriUtils.java | 75 ------ .../src/main/java/org/acra/util/UriUtils.kt | 55 +++++ 31 files changed, 980 insertions(+), 1293 deletions(-) rename acra-http/src/main/java/org/acra/config/{BaseHttpConfigurationBuilder.java => BaseHttpConfigurationBuilder.kt} (55%) delete mode 100644 acra-http/src/main/java/org/acra/http/BaseHttpRequest.java create mode 100644 acra-http/src/main/java/org/acra/http/BaseHttpRequest.kt delete mode 100644 acra-http/src/main/java/org/acra/http/BinaryHttpRequest.java create mode 100644 acra-http/src/main/java/org/acra/http/BinaryHttpRequest.kt delete mode 100644 acra-http/src/main/java/org/acra/http/DefaultHttpRequest.java create mode 100644 acra-http/src/main/java/org/acra/http/DefaultHttpRequest.kt rename acra-http/src/main/java/org/acra/http/{HttpRequest.java => HttpRequest.kt} (71%) delete mode 100644 acra-http/src/main/java/org/acra/http/MultipartHttpRequest.java create mode 100644 acra-http/src/main/java/org/acra/http/MultipartHttpRequest.kt delete mode 100644 acra-http/src/main/java/org/acra/security/AssetKeyStoreFactory.java create mode 100644 acra-http/src/main/java/org/acra/security/AssetKeyStoreFactory.kt delete mode 100644 acra-http/src/main/java/org/acra/security/BaseKeyStoreFactory.java create mode 100644 acra-http/src/main/java/org/acra/security/BaseKeyStoreFactory.kt delete mode 100644 acra-http/src/main/java/org/acra/security/FileKeyStoreFactory.java create mode 100644 acra-http/src/main/java/org/acra/security/FileKeyStoreFactory.kt rename acra-http/src/main/java/org/acra/security/{KeyStoreFactory.java => KeyStoreFactory.kt} (72%) delete mode 100644 acra-http/src/main/java/org/acra/security/KeyStoreHelper.java create mode 100644 acra-http/src/main/java/org/acra/security/KeyStoreHelper.kt rename acra-http/src/main/java/org/acra/security/{NoKeyStoreFactory.java => NoKeyStoreFactory.kt} (67%) delete mode 100644 acra-http/src/main/java/org/acra/security/ProtocolSocketFactoryWrapper.java create mode 100644 acra-http/src/main/java/org/acra/security/ProtocolSocketFactoryWrapper.kt delete mode 100644 acra-http/src/main/java/org/acra/security/ResourceKeyStoreFactory.java create mode 100644 acra-http/src/main/java/org/acra/security/ResourceKeyStoreFactory.kt rename acra-http/src/main/java/org/acra/security/{TLS.java => TLS.kt} (69%) delete mode 100644 acra-http/src/main/java/org/acra/sender/HttpSender.java create mode 100644 acra-http/src/main/java/org/acra/sender/HttpSender.kt delete mode 100644 acra-http/src/main/java/org/acra/sender/HttpSenderFactory.java create mode 100644 acra-http/src/main/java/org/acra/sender/HttpSenderFactory.kt delete mode 100644 acra-http/src/main/java/org/acra/util/UriUtils.java create mode 100644 acra-http/src/main/java/org/acra/util/UriUtils.kt diff --git a/acra-http/src/main/java/org/acra/config/BaseHttpConfigurationBuilder.java b/acra-http/src/main/java/org/acra/config/BaseHttpConfigurationBuilder.kt similarity index 55% rename from acra-http/src/main/java/org/acra/config/BaseHttpConfigurationBuilder.java rename to acra-http/src/main/java/org/acra/config/BaseHttpConfigurationBuilder.kt index f1059e6b46..4d4039ec18 100644 --- a/acra-http/src/main/java/org/acra/config/BaseHttpConfigurationBuilder.java +++ b/acra-http/src/main/java/org/acra/config/BaseHttpConfigurationBuilder.kt @@ -13,44 +13,37 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +package org.acra.config -package org.acra.config; - -import androidx.annotation.NonNull; - -import org.acra.annotation.BuilderMethod; -import org.acra.annotation.ConfigurationValue; - -import java.util.HashMap; -import java.util.Map; +import org.acra.annotation.BuilderMethod +import org.acra.annotation.ConfigurationValue +import java.util.* /** * @author F43nd1r * @since 01.06.2017 */ -public class BaseHttpConfigurationBuilder { - - private final Map httpHeaders; - - BaseHttpConfigurationBuilder() { - httpHeaders = new HashMap<>(); - } +class BaseHttpConfigurationBuilder internal constructor() { + private val httpHeaders: MutableMap /** - * Set custom HTTP headers to be sent by the provided {@link org.acra.sender.HttpSender} + * Set custom HTTP headers to be sent by the provided [org.acra.sender.HttpSender] * This should be used also by third party senders. * * @param headers A map associating HTTP header names to their values. */ @BuilderMethod - public void setHttpHeaders(@NonNull Map headers) { - this.httpHeaders.clear(); - this.httpHeaders.putAll(headers); + fun setHttpHeaders(headers: Map) { + httpHeaders.clear() + httpHeaders.putAll(headers) } @ConfigurationValue - @NonNull - Map httpHeaders() { - return httpHeaders; + fun httpHeaders(): Map { + return httpHeaders + } + + init { + httpHeaders = HashMap() } -} +} \ No newline at end of file diff --git a/acra-http/src/main/java/org/acra/http/BaseHttpRequest.java b/acra-http/src/main/java/org/acra/http/BaseHttpRequest.java deleted file mode 100644 index 6e9e4d40d7..0000000000 --- a/acra-http/src/main/java/org/acra/http/BaseHttpRequest.java +++ /dev/null @@ -1,218 +0,0 @@ -/* - * Copyright (c) 2017 - * - * 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.http; - -import android.content.Context; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import android.util.Base64; -import android.util.Log; -import org.acra.ACRA; -import org.acra.ACRAConstants; -import org.acra.BuildConfig; -import org.acra.config.ConfigUtils; -import org.acra.config.CoreConfiguration; -import org.acra.config.HttpSenderConfiguration; -import org.acra.security.KeyStoreHelper; -import org.acra.security.ProtocolSocketFactoryWrapper; -import org.acra.sender.HttpSender.Method; -import org.acra.util.IOUtils; - -import javax.net.ssl.HttpsURLConnection; -import javax.net.ssl.SSLContext; -import javax.net.ssl.TrustManagerFactory; -import java.io.BufferedOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.net.HttpURLConnection; -import java.net.SocketTimeoutException; -import java.net.URL; -import java.security.GeneralSecurityException; -import java.security.KeyStore; -import java.util.Arrays; -import java.util.Map; -import java.util.zip.GZIPOutputStream; - -import static org.acra.ACRA.LOG_TAG; - -/** - * @author F43nd1r - * @since 03.03.2017 - */ -@SuppressWarnings("WeakerAccess") -public abstract class BaseHttpRequest implements HttpRequest { - - private final CoreConfiguration config; - private final Context context; - private final Method method; - private final String login; - private final String password; - private final int connectionTimeOut; - private final int socketTimeOut; - private final Map headers; - private final HttpSenderConfiguration senderConfiguration; - - public BaseHttpRequest(@NonNull CoreConfiguration config, @NonNull Context context, @NonNull Method method, - @Nullable String login, @Nullable String password, int connectionTimeOut, int socketTimeOut, @Nullable Map headers) { - this.config = config; - this.context = context; - this.method = method; - this.login = login; - this.password = password; - this.connectionTimeOut = connectionTimeOut; - this.socketTimeOut = socketTimeOut; - this.headers = headers; - senderConfiguration = ConfigUtils.getPluginConfiguration(config, HttpSenderConfiguration.class); - } - - - /** - * Sends to a URL. - * - * @param url URL to which to send. - * @param content content to send. - * @throws IOException if the data cannot be sent. - */ - @Override - public void send(@NonNull URL url, @NonNull T content) throws IOException { - - final HttpURLConnection urlConnection = createConnection(url); - if (urlConnection instanceof HttpsURLConnection) { - try { - configureHttps((HttpsURLConnection) urlConnection); - } catch (GeneralSecurityException e) { - ACRA.log.e(LOG_TAG, "Could not configure SSL for ACRA request to " + url, e); - } - } - configureTimeouts(urlConnection, connectionTimeOut, socketTimeOut); - configureHeaders(urlConnection, login, password, headers, content); - if (ACRA.DEV_LOGGING) { - ACRA.log.d(LOG_TAG, "Sending request to " + url); - ACRA.log.d(LOG_TAG, "Http " + method.name() + " content : "); - ACRA.log.d(LOG_TAG, content.toString()); - } - try { - writeContent(urlConnection, method, content); - handleResponse(urlConnection.getResponseCode(), urlConnection.getResponseMessage()); - urlConnection.disconnect(); - } catch (SocketTimeoutException e) { - if (senderConfiguration.getDropReportsOnTimeout()) { - Log.w(ACRA.LOG_TAG, "Dropped report due to timeout"); - } else { - throw e; - } - } - } - - @SuppressWarnings("WeakerAccess") - @NonNull - protected HttpURLConnection createConnection(@NonNull URL url) throws IOException { - return (HttpURLConnection) url.openConnection(); - } - - @SuppressWarnings("WeakerAccess") - protected void configureHttps(@NonNull HttpsURLConnection connection) throws GeneralSecurityException { - // Configure SSL - final String algorithm = TrustManagerFactory.getDefaultAlgorithm(); - final TrustManagerFactory tmf = TrustManagerFactory.getInstance(algorithm); - final KeyStore keyStore = KeyStoreHelper.getKeyStore(context, config); - - tmf.init(keyStore); - - final SSLContext sslContext = SSLContext.getInstance("TLS"); - sslContext.init(null, tmf.getTrustManagers(), null); - - connection.setSSLSocketFactory(new ProtocolSocketFactoryWrapper(sslContext.getSocketFactory(), Arrays.asList(senderConfiguration.getTlsProtocols()))); - } - - @SuppressWarnings("WeakerAccess") - protected void configureTimeouts(@NonNull HttpURLConnection connection, int connectionTimeOut, int socketTimeOut) { - connection.setConnectTimeout(connectionTimeOut); - connection.setReadTimeout(socketTimeOut); - } - - @SuppressWarnings("WeakerAccess") - protected void configureHeaders(@NonNull HttpURLConnection connection, @Nullable String login, @Nullable String password, - @Nullable Map customHeaders, @NonNull T t) throws IOException { - // Set Headers - connection.setRequestProperty("User-Agent", String.format("Android ACRA %1$s", BuildConfig.VERSION_NAME)); //sent ACRA version to server - connection.setRequestProperty("Accept", - "text/html,application/xml,application/json,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5"); - connection.setRequestProperty("Content-Type", getContentType(context, t)); - - // Set Credentials - if (login != null && password != null) { - final String credentials = login + ':' + password; - final String encoded = new String(Base64.encode(credentials.getBytes(ACRAConstants.UTF8), Base64.NO_WRAP), ACRAConstants.UTF8); - connection.setRequestProperty("Authorization", "Basic " + encoded); - } - - if (senderConfiguration.getCompress()) { - connection.setRequestProperty("Content-Encoding", "gzip"); - } - - if (customHeaders != null) { - for (final Map.Entry header : customHeaders.entrySet()) { - connection.setRequestProperty(header.getKey(), header.getValue()); - } - } - } - - @NonNull - protected abstract String getContentType(@NonNull Context context, @NonNull T t); - - @SuppressWarnings("WeakerAccess") - protected void writeContent(@NonNull HttpURLConnection connection, @NonNull Method method, @NonNull T content) throws IOException { - // write output - see http://developer.android.com/reference/java/net/HttpURLConnection.html - connection.setRequestMethod(method.name()); - connection.setDoOutput(true); - - // Disable ConnectionPooling because otherwise OkHttp ConnectionPool will try to start a Thread on #connect - System.setProperty("http.keepAlive", "false"); - - connection.connect(); - - final OutputStream outputStream = senderConfiguration.getCompress() ? new GZIPOutputStream(connection.getOutputStream(), ACRAConstants.DEFAULT_BUFFER_SIZE_IN_BYTES) - : new BufferedOutputStream(connection.getOutputStream()); - try { - write(outputStream, content); - outputStream.flush(); - } finally { - IOUtils.safeClose(outputStream); - } - } - - protected abstract void write(OutputStream outputStream, @NonNull T content) throws IOException; - - @SuppressWarnings("WeakerAccess") - protected void handleResponse(int responseCode, String responseMessage) throws IOException { - if (ACRA.DEV_LOGGING) - ACRA.log.d(LOG_TAG, "Request response : " + responseCode + " : " + responseMessage); - if (responseCode >= HttpURLConnection.HTTP_OK && responseCode < HttpURLConnection.HTTP_MULT_CHOICE) { - // All is good - ACRA.log.i(LOG_TAG, "Request received by server"); - } else if (responseCode == HttpURLConnection.HTTP_CLIENT_TIMEOUT || responseCode >= HttpURLConnection.HTTP_INTERNAL_ERROR) { - //timeout or server error. Repeat the request later. - ACRA.log.w(LOG_TAG, "Could not send ACRA Post responseCode=" + responseCode + " message=" + responseMessage); - throw new IOException("Host returned error code " + responseCode); - } else if (responseCode >= HttpURLConnection.HTTP_BAD_REQUEST) { - // Client error. The request must not be repeated. Discard it. - ACRA.log.w(LOG_TAG, responseCode + ": Client error - request will be discarded"); - } else { - ACRA.log.w(LOG_TAG, "Could not send ACRA Post - request will be discarded. responseCode=" + responseCode + " message=" + responseMessage); - } - } -} diff --git a/acra-http/src/main/java/org/acra/http/BaseHttpRequest.kt b/acra-http/src/main/java/org/acra/http/BaseHttpRequest.kt new file mode 100644 index 0000000000..108f1cfd53 --- /dev/null +++ b/acra-http/src/main/java/org/acra/http/BaseHttpRequest.kt @@ -0,0 +1,169 @@ +/* + * Copyright (c) 2017 + * + * 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.http + +import android.content.Context +import android.util.Base64 +import android.util.Log +import org.acra.ACRA +import org.acra.ACRAConstants +import org.acra.BuildConfig +import org.acra.config.ConfigUtils.getPluginConfiguration +import org.acra.config.CoreConfiguration +import org.acra.config.HttpSenderConfiguration +import org.acra.log.debug +import org.acra.log.error +import org.acra.log.info +import org.acra.log.warn +import org.acra.security.KeyStoreHelper +import org.acra.security.ProtocolSocketFactoryWrapper +import org.acra.sender.HttpSender +import java.io.BufferedOutputStream +import java.io.IOException +import java.io.OutputStream +import java.net.HttpURLConnection +import java.net.SocketTimeoutException +import java.net.URL +import java.security.GeneralSecurityException +import java.util.zip.GZIPOutputStream +import javax.net.ssl.HttpsURLConnection +import javax.net.ssl.SSLContext +import javax.net.ssl.TrustManagerFactory + +/** + * @author F43nd1r + * @since 03.03.2017 + */ +@Suppress("MemberVisibilityCanBePrivate") +abstract class BaseHttpRequest(private val config: CoreConfiguration, private val context: Context, private val method: HttpSender.Method, + private val login: String?, private val password: String?, private val connectionTimeOut: Int, private val socketTimeOut: Int, + private val headers: Map?) : HttpRequest { + private val senderConfiguration: HttpSenderConfiguration = getPluginConfiguration(config, HttpSenderConfiguration::class.java) + + /** + * Sends to a URL. + * + * @param url URL to which to send. + * @param content content to send. + * @throws IOException if the data cannot be sent. + */ + @Throws(IOException::class) + override fun send(url: URL, content: T) { + val urlConnection = createConnection(url) + if (urlConnection is HttpsURLConnection) { + try { + configureHttps(urlConnection) + } catch (e: GeneralSecurityException) { + error(e) { "Could not configure SSL for ACRA request to $url" } + } + } + configureTimeouts(urlConnection, connectionTimeOut, socketTimeOut) + configureHeaders(urlConnection, login, password, headers, content) + debug { "Sending request to $url" } + debug { "Http ${method.name} content : " } + debug { content.toString() } + try { + writeContent(urlConnection, method, content) + handleResponse(urlConnection.responseCode, urlConnection.responseMessage) + urlConnection.disconnect() + } catch (e: SocketTimeoutException) { + if (senderConfiguration.dropReportsOnTimeout) { + Log.w(ACRA.LOG_TAG, "Dropped report due to timeout") + } else { + throw e + } + } + } + + @Throws(IOException::class) + protected fun createConnection(url: URL): HttpURLConnection { + return url.openConnection() as HttpURLConnection + } + + @Throws(GeneralSecurityException::class) + protected fun configureHttps(connection: HttpsURLConnection) { + // Configure SSL + val tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()) + val keyStore = KeyStoreHelper.getKeyStore(context, config) + tmf.init(keyStore) + val sslContext = SSLContext.getInstance("TLS") + sslContext.init(null, tmf.trustManagers, null) + connection.sslSocketFactory = ProtocolSocketFactoryWrapper(sslContext.socketFactory, senderConfiguration.tlsProtocols.asList()) + } + + protected fun configureTimeouts(connection: HttpURLConnection, connectionTimeOut: Int, socketTimeOut: Int) { + connection.connectTimeout = connectionTimeOut + connection.readTimeout = socketTimeOut + } + + @Throws(IOException::class) + protected fun configureHeaders(connection: HttpURLConnection, login: String?, password: String?, customHeaders: Map?, t: T) { + // Set Headers + connection.setRequestProperty("User-Agent", String.format("Android ACRA %1\$s", BuildConfig.VERSION_NAME)) //sent ACRA version to server + connection.setRequestProperty("Accept", "text/html,application/xml,application/json,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5") + connection.setRequestProperty("Content-Type", getContentType(context, t)) + + // Set Credentials + if (login != null && password != null) { + val encoded = String(Base64.encode("$login:$password".toByteArray(Charsets.UTF_8), Base64.NO_WRAP), Charsets.UTF_8) + connection.setRequestProperty("Authorization", "Basic $encoded") + } + if (senderConfiguration.compress) { + connection.setRequestProperty("Content-Encoding", "gzip") + } + customHeaders?.forEach { (key, value) -> connection.setRequestProperty(key, value) } + } + + protected abstract fun getContentType(context: Context, t: T): String + + @Throws(IOException::class) + protected fun writeContent(connection: HttpURLConnection, method: HttpSender.Method, content: T) { + // write output - see http://developer.android.com/reference/java/net/HttpURLConnection.html + connection.requestMethod = method.name + connection.doOutput = true + + // Disable ConnectionPooling because otherwise OkHttp ConnectionPool will try to start a Thread on #connect + System.setProperty("http.keepAlive", "false") + connection.connect() + (if (senderConfiguration.compress) GZIPOutputStream(connection.outputStream, ACRAConstants.DEFAULT_BUFFER_SIZE_IN_BYTES) + else BufferedOutputStream(connection.outputStream)).use { + write(it, content) + it.flush() + } + } + + @Throws(IOException::class) + protected abstract fun write(outputStream: OutputStream, content: T) + + @Throws(IOException::class) + protected fun handleResponse(responseCode: Int, responseMessage: String) { + debug { "Request response : $responseCode : $responseMessage" } + if (responseCode >= HttpURLConnection.HTTP_OK && responseCode < HttpURLConnection.HTTP_MULT_CHOICE) { + // All is good + info { "Request received by server" } + } else if (responseCode == HttpURLConnection.HTTP_CLIENT_TIMEOUT || responseCode >= HttpURLConnection.HTTP_INTERNAL_ERROR) { + //timeout or server error. Repeat the request later. + warn { "Could not send ACRA Post responseCode=$responseCode message=$responseMessage" } + throw IOException("Host returned error code $responseCode") + } else if (responseCode >= HttpURLConnection.HTTP_BAD_REQUEST) { + // Client error. The request must not be repeated. Discard it. + warn { "$responseCode: Client error - request will be discarded" } + } else { + warn { "Could not send ACRA Post - request will be discarded. responseCode=$responseCode message=$responseMessage" } + } + } + +} \ No newline at end of file diff --git a/acra-http/src/main/java/org/acra/http/BinaryHttpRequest.java b/acra-http/src/main/java/org/acra/http/BinaryHttpRequest.java deleted file mode 100644 index 6df6d3a5e4..0000000000 --- a/acra-http/src/main/java/org/acra/http/BinaryHttpRequest.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (c) 2017 the ACRA team - * - * 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.http; - -import android.content.Context; -import android.net.Uri; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import org.acra.config.CoreConfiguration; -import org.acra.sender.HttpSender; -import org.acra.util.UriUtils; - -import java.io.IOException; -import java.io.OutputStream; -import java.util.Map; - -/** - * @author F43nd1r - * @since 10.03.2017 - */ - -public class BinaryHttpRequest extends BaseHttpRequest { - private final Context context; - - public BinaryHttpRequest(@NonNull CoreConfiguration config, @NonNull Context context, - @Nullable String login, @Nullable String password, int connectionTimeOut, int socketTimeOut, @Nullable Map headers) { - super(config, context, HttpSender.Method.PUT, login, password, connectionTimeOut, socketTimeOut, headers); - this.context = context; - } - - @NonNull - @Override - protected String getContentType(@NonNull Context context, @NonNull Uri uri) { - return UriUtils.getMimeType(context, uri); - } - - @Override - protected void write(OutputStream outputStream, @NonNull Uri content) throws IOException { - UriUtils.copyFromUri(context, outputStream, content); - } -} diff --git a/acra-http/src/main/java/org/acra/http/BinaryHttpRequest.kt b/acra-http/src/main/java/org/acra/http/BinaryHttpRequest.kt new file mode 100644 index 0000000000..a0f27c1f78 --- /dev/null +++ b/acra-http/src/main/java/org/acra/http/BinaryHttpRequest.kt @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2017 the ACRA team + * + * 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.http + +import android.content.Context +import android.net.Uri +import org.acra.config.CoreConfiguration +import org.acra.sender.HttpSender +import org.acra.util.UriUtils +import java.io.IOException +import java.io.OutputStream + +/** + * @author F43nd1r + * @since 10.03.2017 + */ +class BinaryHttpRequest(config: CoreConfiguration, private val context: Context, + login: String?, password: String?, connectionTimeOut: Int, socketTimeOut: Int, headers: Map?) : + BaseHttpRequest(config, context, HttpSender.Method.PUT, login, password, connectionTimeOut, socketTimeOut, headers) { + override fun getContentType(context: Context, uri: Uri): String { + return UriUtils.getMimeType(context, uri) + } + + @Throws(IOException::class) + override fun write(outputStream: OutputStream, content: Uri) { + UriUtils.copyFromUri(context, outputStream, content) + } +} \ No newline at end of file diff --git a/acra-http/src/main/java/org/acra/http/DefaultHttpRequest.java b/acra-http/src/main/java/org/acra/http/DefaultHttpRequest.java deleted file mode 100644 index 738087d64d..0000000000 --- a/acra-http/src/main/java/org/acra/http/DefaultHttpRequest.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (c) 2017 - * - * 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.http; - -import android.content.Context; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import org.acra.ACRAConstants; -import org.acra.config.CoreConfiguration; -import org.acra.sender.HttpSender; - -import java.io.IOException; -import java.io.OutputStream; -import java.util.Map; - -/** - * @author F43nd1r - * @since 10.03.2017 - */ - -public class DefaultHttpRequest extends BaseHttpRequest { - private final String contentType; - - public DefaultHttpRequest(@NonNull CoreConfiguration config, @NonNull Context context, @NonNull HttpSender.Method method, @NonNull String contentType, - @Nullable String login, @Nullable String password, int connectionTimeOut, int socketTimeOut, @Nullable Map headers) { - super(config, context, method, login, password, connectionTimeOut, socketTimeOut, headers); - this.contentType = contentType; - } - - @NonNull - @Override - protected String getContentType(@NonNull Context context, @NonNull String s) { - return contentType; - } - - @Override - protected void write(OutputStream outputStream, @NonNull String content) throws IOException { - outputStream.write(content.getBytes(ACRAConstants.UTF8)); - } -} diff --git a/acra-http/src/main/java/org/acra/http/DefaultHttpRequest.kt b/acra-http/src/main/java/org/acra/http/DefaultHttpRequest.kt new file mode 100644 index 0000000000..9f66e0ef41 --- /dev/null +++ b/acra-http/src/main/java/org/acra/http/DefaultHttpRequest.kt @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2017 + * + * 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.http + +import android.content.Context +import org.acra.ACRAConstants +import org.acra.config.CoreConfiguration +import org.acra.sender.HttpSender +import java.io.IOException +import java.io.OutputStream + +/** + * @author F43nd1r + * @since 10.03.2017 + */ +class DefaultHttpRequest(config: CoreConfiguration, context: Context, method: HttpSender.Method, private val contentType: String, + login: String?, password: String?, connectionTimeOut: Int, socketTimeOut: Int, headers: Map?) : + BaseHttpRequest(config, context, method, login, password, connectionTimeOut, socketTimeOut, headers) { + override fun getContentType(context: Context, t: String): String { + return contentType + } + + @Throws(IOException::class) + override fun write(outputStream: OutputStream, content: String) { + outputStream.write(content.toByteArray(Charsets.UTF_8)) + } +} \ No newline at end of file diff --git a/acra-http/src/main/java/org/acra/http/HttpRequest.java b/acra-http/src/main/java/org/acra/http/HttpRequest.kt similarity index 71% rename from acra-http/src/main/java/org/acra/http/HttpRequest.java rename to acra-http/src/main/java/org/acra/http/HttpRequest.kt index 2acbf9d933..02c75082e2 100644 --- a/acra-http/src/main/java/org/acra/http/HttpRequest.java +++ b/acra-http/src/main/java/org/acra/http/HttpRequest.kt @@ -13,18 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.acra.http; +package org.acra.http -import androidx.annotation.NonNull; - -import java.io.IOException; -import java.net.URL; +import java.io.IOException +import java.net.URL /** * @author F43nd1r * @since 03.03.2017 */ -@SuppressWarnings("WeakerAccess") -public interface HttpRequest { - void send(@NonNull URL url, @NonNull T content) throws IOException; -} +interface HttpRequest { + @Throws(IOException::class) + fun send(url: URL, content: T) +} \ No newline at end of file diff --git a/acra-http/src/main/java/org/acra/http/MultipartHttpRequest.java b/acra-http/src/main/java/org/acra/http/MultipartHttpRequest.java deleted file mode 100644 index 500126de0e..0000000000 --- a/acra-http/src/main/java/org/acra/http/MultipartHttpRequest.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (c) 2017 - * - * 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.http; - -import android.content.Context; -import android.net.Uri; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import android.util.Pair; -import org.acra.ACRA; -import org.acra.ACRAConstants; -import org.acra.config.CoreConfiguration; -import org.acra.sender.HttpSender; -import org.acra.util.UriUtils; - -import java.io.*; -import java.util.List; -import java.util.Map; - -/** - * Produces RFC 7578 compliant requests - * - * @author F43nd1r - * @since 11.03.2017 - */ - -public class MultipartHttpRequest extends BaseHttpRequest>> { - - private static final String BOUNDARY = "%&ACRA_REPORT_DIVIDER&%"; - private static final String BOUNDARY_FIX = "--"; - private static final String NEW_LINE = "\r\n"; - private static final String SECTION_START = NEW_LINE + BOUNDARY_FIX + BOUNDARY + NEW_LINE; - private static final String MESSAGE_END = NEW_LINE + BOUNDARY_FIX + BOUNDARY + BOUNDARY_FIX + NEW_LINE; - private static final String CONTENT_DISPOSITION = "Content-Disposition: form-data; name=\"%s\"; filename=\"%s\"" + NEW_LINE; - private static final String CONTENT_TYPE = "Content-Type: %s" + NEW_LINE; - private final Context context; - private final String contentType; - - public MultipartHttpRequest(@NonNull CoreConfiguration config, @NonNull Context context, @NonNull String contentType, @Nullable String login, @Nullable String password, - int connectionTimeOut, int socketTimeOut, @Nullable Map headers) { - super(config, context, HttpSender.Method.POST, login, password, connectionTimeOut, socketTimeOut, headers); - this.context = context; - this.contentType = contentType; - } - - @NonNull - @Override - protected String getContentType(@NonNull Context context, @NonNull Pair> stringListPair) { - return "multipart/form-data; boundary=" + BOUNDARY; - } - - @Override - protected void write(OutputStream outputStream, @NonNull Pair> content) throws IOException { - final PrintWriter writer = new PrintWriter(new OutputStreamWriter(outputStream, ACRAConstants.UTF8)); - writer.append(SECTION_START) - .format(CONTENT_DISPOSITION, "ACRA_REPORT", "") - .format(CONTENT_TYPE, contentType) - .append(NEW_LINE) - .append(content.first); - for (Uri uri : content.second) { - try { - String name = UriUtils.getFileNameFromUri(context, uri); - writer.append(SECTION_START) - .format(CONTENT_DISPOSITION, "ACRA_ATTACHMENT", name) - .format(CONTENT_TYPE, UriUtils.getMimeType(context, uri)) - .append(NEW_LINE) - .flush(); - UriUtils.copyFromUri(context, outputStream, uri); - } catch (FileNotFoundException e) { - ACRA.log.w("Not sending attachment", e); - } - } - writer.append(MESSAGE_END).flush(); - } -} diff --git a/acra-http/src/main/java/org/acra/http/MultipartHttpRequest.kt b/acra-http/src/main/java/org/acra/http/MultipartHttpRequest.kt new file mode 100644 index 0000000000..00f5e56bb4 --- /dev/null +++ b/acra-http/src/main/java/org/acra/http/MultipartHttpRequest.kt @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2017 + * + * 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.http + +import android.content.Context +import android.net.Uri +import org.acra.ACRAConstants +import org.acra.config.CoreConfiguration +import org.acra.log.warn +import org.acra.sender.HttpSender +import org.acra.util.UriUtils +import java.io.FileNotFoundException +import java.io.IOException +import java.io.OutputStream +import java.io.OutputStreamWriter +import java.io.PrintWriter + +/** + * Produces [RFC 7578](https://tools.ietf.org/html/rfc7578) compliant requests + * + * @author F43nd1r + * @since 11.03.2017 + */ +class MultipartHttpRequest(config: CoreConfiguration, private val context: Context, private val contentType: String, login: String?, password: String?, + connectionTimeOut: Int, socketTimeOut: Int, headers: Map?) : + BaseHttpRequest>>(config, context, HttpSender.Method.POST, login, password, connectionTimeOut, socketTimeOut, headers) { + override fun getContentType(context: Context, t: Pair>): String = "multipart/form-data; boundary=$BOUNDARY" + + @Throws(IOException::class) + override fun write(outputStream: OutputStream, content: Pair>) { + val writer = PrintWriter(OutputStreamWriter(outputStream, ACRAConstants.UTF8)) + writer.append(SECTION_START) + .format(CONTENT_DISPOSITION, "ACRA_REPORT", "") + .format(CONTENT_TYPE, contentType) + .append(NEW_LINE) + .append(content.first) + for (uri in content.second) { + try { + val name = UriUtils.getFileNameFromUri(context, uri) + writer.append(SECTION_START) + .format(CONTENT_DISPOSITION, "ACRA_ATTACHMENT", name) + .format(CONTENT_TYPE, UriUtils.getMimeType(context, uri)) + .append(NEW_LINE) + .flush() + UriUtils.copyFromUri(context, outputStream, uri) + } catch (e: FileNotFoundException) { + warn(e) { "Not sending attachment" } + } + } + writer.append(MESSAGE_END).flush() + } + + companion object { + private const val BOUNDARY = "%&ACRA_REPORT_DIVIDER&%" + private const val BOUNDARY_FIX = "--" + private const val NEW_LINE = "\r\n" + private const val SECTION_START = NEW_LINE + BOUNDARY_FIX + BOUNDARY + NEW_LINE + private const val MESSAGE_END = NEW_LINE + BOUNDARY_FIX + BOUNDARY + BOUNDARY_FIX + NEW_LINE + private const val CONTENT_DISPOSITION = "Content-Disposition: form-data; name=\"%s\"; filename=\"%s\"$NEW_LINE" + private const val CONTENT_TYPE = "Content-Type: %s$NEW_LINE" + } +} \ No newline at end of file diff --git a/acra-http/src/main/java/org/acra/security/AssetKeyStoreFactory.java b/acra-http/src/main/java/org/acra/security/AssetKeyStoreFactory.java deleted file mode 100644 index 979b29b0a8..0000000000 --- a/acra-http/src/main/java/org/acra/security/AssetKeyStoreFactory.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2016 - * - * 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.security; - -import android.content.Context; -import androidx.annotation.NonNull; - -import org.acra.ACRA; - -import java.io.IOException; -import java.io.InputStream; - -import static org.acra.ACRA.LOG_TAG; - -/** - * KeyStoreFactory for a certificate stored in an asset file - * - * @author F43nd1r - * @since 4.8.3 - */ -final class AssetKeyStoreFactory extends BaseKeyStoreFactory { - - private final String assetName; - - /** - * creates a new KeyStoreFactory for the specified asset with a custom certificate type - * @param certificateType the certificate type - * @param assetName the asset - */ - AssetKeyStoreFactory(String certificateType, String assetName) { - super(certificateType); - this.assetName = assetName; - } - - @Override - public InputStream getInputStream(@NonNull Context context) { - try { - return context.getAssets().open(assetName); - } catch (IOException e) { - ACRA.log.e(LOG_TAG, "Could not open certificate in asset://"+assetName, e); - } - return null; - } -} diff --git a/acra-http/src/main/java/org/acra/security/AssetKeyStoreFactory.kt b/acra-http/src/main/java/org/acra/security/AssetKeyStoreFactory.kt new file mode 100644 index 0000000000..4ceeb3ee0a --- /dev/null +++ b/acra-http/src/main/java/org/acra/security/AssetKeyStoreFactory.kt @@ -0,0 +1,42 @@ +/* + * Copyright 2016 + * + * 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.security + +import android.content.Context +import org.acra.log.error +import java.io.IOException +import java.io.InputStream + +/** + * KeyStoreFactory for a certificate stored in an asset file + * + * creates a new KeyStoreFactory for the specified asset with a custom certificate type + * @param certificateType the certificate type + * @param assetName the asset + * + * @author F43nd1r + * @since 4.8.3 + */ +internal class AssetKeyStoreFactory(certificateType: String, private val assetName: String) : BaseKeyStoreFactory(certificateType) { + public override fun getInputStream(context: Context): InputStream? { + try { + return context.assets.open(assetName) + } catch (e: IOException) { + error(e) { "Could not open certificate in asset://$assetName" } + } + return null + } +} \ No newline at end of file diff --git a/acra-http/src/main/java/org/acra/security/BaseKeyStoreFactory.java b/acra-http/src/main/java/org/acra/security/BaseKeyStoreFactory.java deleted file mode 100644 index c49b1260fd..0000000000 --- a/acra-http/src/main/java/org/acra/security/BaseKeyStoreFactory.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright 2016 - * - * 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.security; - -import android.content.Context; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import org.acra.ACRA; -import org.acra.ACRAConstants; -import org.acra.util.IOUtils; - -import java.io.BufferedInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.cert.Certificate; -import java.security.cert.CertificateException; -import java.security.cert.CertificateFactory; - -import static org.acra.ACRA.LOG_TAG; - -/** - * Provides base KeyStoreFactory implementation - * - * @author F43nd1r - * @since 4.8.3 - */ -@SuppressWarnings({"WeakerAccess", "unused"}) -public abstract class BaseKeyStoreFactory implements KeyStoreFactory { - - public enum Type { - CERTIFICATE, - KEYSTORE - } - - private final String certificateType; - - /** - * creates a new KeyStoreFactory for the default certificate type {@link ACRAConstants#DEFAULT_CERTIFICATE_TYPE} - */ - public BaseKeyStoreFactory() { - this(ACRAConstants.DEFAULT_CERTIFICATE_TYPE); - } - - /** - * creates a new KeyStoreFactory with the specified certificate type - * - * @param certificateType the certificate type - */ - public BaseKeyStoreFactory(String certificateType) { - this.certificateType = certificateType; - } - - @Nullable - protected abstract InputStream getInputStream(@NonNull Context context); - - protected String getKeyStoreType() { - return KeyStore.getDefaultType(); - } - - @NonNull - protected Type getStreamType() { - return Type.CERTIFICATE; - } - - @Nullable - protected char[] getPassword() { - return null; - } - - @Override - @Nullable - public final KeyStore create(@NonNull Context context) { - final InputStream inputStream = getInputStream(context); - if (inputStream != null) { - final BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream); - try { - final KeyStore keyStore = KeyStore.getInstance(getKeyStoreType()); - switch (getStreamType()) { - case CERTIFICATE: - final CertificateFactory certificateFactory = CertificateFactory.getInstance(certificateType); - final Certificate certificate = certificateFactory.generateCertificate(bufferedInputStream); - keyStore.load(null, null); - keyStore.setCertificateEntry("ca", certificate); - break; - case KEYSTORE: - keyStore.load(bufferedInputStream, getPassword()); - } - return keyStore; - } catch (CertificateException e) { - ACRA.log.e(LOG_TAG, "Could not load certificate", e); - } catch (KeyStoreException e) { - ACRA.log.e(LOG_TAG, "Could not load keystore", e); - } catch (NoSuchAlgorithmException e) { - ACRA.log.e(LOG_TAG, "Could not load keystore", e); - } catch (IOException e) { - ACRA.log.e(LOG_TAG, "Could not load keystore", e); - } finally { - IOUtils.safeClose(bufferedInputStream); - } - } - return null; - } -} diff --git a/acra-http/src/main/java/org/acra/security/BaseKeyStoreFactory.kt b/acra-http/src/main/java/org/acra/security/BaseKeyStoreFactory.kt new file mode 100644 index 0000000000..125f828f32 --- /dev/null +++ b/acra-http/src/main/java/org/acra/security/BaseKeyStoreFactory.kt @@ -0,0 +1,84 @@ +/* + * Copyright 2016 + * + * 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.security + +import android.content.Context +import org.acra.ACRAConstants +import org.acra.log.error +import org.acra.util.IOUtils.safeClose +import java.io.BufferedInputStream +import java.io.IOException +import java.io.InputStream +import java.security.KeyStore +import java.security.KeyStoreException +import java.security.NoSuchAlgorithmException +import java.security.cert.CertificateException +import java.security.cert.CertificateFactory + +/** + * Provides base KeyStoreFactory implementation + * + * creates a new KeyStoreFactory with the specified certificate type + * + * @param certificateType the certificate type + * + * @author F43nd1r + * @since 4.8.3 + */ +@Suppress("MemberVisibilityCanBePrivate") +abstract class BaseKeyStoreFactory +/** + * creates a new KeyStoreFactory for the default certificate type [ACRAConstants.DEFAULT_CERTIFICATE_TYPE] + */ @JvmOverloads constructor(private val certificateType: String = ACRAConstants.DEFAULT_CERTIFICATE_TYPE) : KeyStoreFactory { + enum class Type { + CERTIFICATE, KEYSTORE + } + + protected abstract fun getInputStream(context: Context): InputStream? + protected val keyStoreType: String + get() = KeyStore.getDefaultType() + protected val streamType: Type + get() = Type.CERTIFICATE + protected val password: CharArray? + get() = null + + override fun create(context: Context): KeyStore? { + return getInputStream(context)?.let { BufferedInputStream(it) }?.use { + try { + val keyStore = KeyStore.getInstance(keyStoreType) + when (streamType) { + Type.CERTIFICATE -> { + val certificateFactory = CertificateFactory.getInstance(certificateType) + val certificate = certificateFactory.generateCertificate(it) + keyStore.load(null, null) + keyStore.setCertificateEntry("ca", certificate) + } + Type.KEYSTORE -> keyStore.load(it, password) + } + return@use keyStore + } catch (e: CertificateException) { + error(e) { "Could not load certificate" } + } catch (e: KeyStoreException) { + error(e) { "Could not load keystore" } + } catch (e: NoSuchAlgorithmException) { + error(e) { "Could not load keystore" } + } catch (e: IOException) { + error(e) { "Could not load keystore" } + } + return@use null + } + } +} \ No newline at end of file diff --git a/acra-http/src/main/java/org/acra/security/FileKeyStoreFactory.java b/acra-http/src/main/java/org/acra/security/FileKeyStoreFactory.java deleted file mode 100644 index f1eace93c5..0000000000 --- a/acra-http/src/main/java/org/acra/security/FileKeyStoreFactory.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2016 - * - * 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.security; - -import android.content.Context; -import androidx.annotation.NonNull; - -import org.acra.ACRA; - -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.InputStream; - -import static org.acra.ACRA.LOG_TAG; - -/** - * KeyStoreFactory for a certificate stored in a file - * - * @author F43nd1r - * @since 4.8.3 - */ -final class FileKeyStoreFactory extends BaseKeyStoreFactory { - - private final String filePath; - - /** - * creates a new KeyStoreFactory for the specified file with a custom certificate type - * @param certificateType the certificate type - * @param filePath path to the file - */ - FileKeyStoreFactory(String certificateType, String filePath) { - super(certificateType); - this.filePath = filePath; - } - - @Override - public InputStream getInputStream(@NonNull Context context) { - try { - return new FileInputStream(filePath); - } catch (FileNotFoundException e) { - ACRA.log.e(LOG_TAG, "Could not find File "+filePath, e); - } - return null; - } -} diff --git a/acra-http/src/main/java/org/acra/security/FileKeyStoreFactory.kt b/acra-http/src/main/java/org/acra/security/FileKeyStoreFactory.kt new file mode 100644 index 0000000000..3db193b575 --- /dev/null +++ b/acra-http/src/main/java/org/acra/security/FileKeyStoreFactory.kt @@ -0,0 +1,43 @@ +/* + * Copyright 2016 + * + * 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.security + +import android.content.Context +import java.io.FileInputStream +import java.io.FileNotFoundException +import java.io.InputStream + +/** + * KeyStoreFactory for a certificate stored in a file + * + * @author F43nd1r + * @since 4.8.3 + */ +internal class FileKeyStoreFactory +/** + * creates a new KeyStoreFactory for the specified file with a custom certificate type + * @param certificateType the certificate type + * @param filePath path to the file + */(certificateType: String?, private val filePath: String) : BaseKeyStoreFactory(certificateType!!) { + public override fun getInputStream(context: Context): InputStream? { + try { + return FileInputStream(filePath) + } catch (e: FileNotFoundException) { + org.acra.log.error(e) { "Could not find File $filePath" } + } + return null + } +} \ No newline at end of file diff --git a/acra-http/src/main/java/org/acra/security/KeyStoreFactory.java b/acra-http/src/main/java/org/acra/security/KeyStoreFactory.kt similarity index 72% rename from acra-http/src/main/java/org/acra/security/KeyStoreFactory.java rename to acra-http/src/main/java/org/acra/security/KeyStoreFactory.kt index 714bff8c82..2005ab9960 100644 --- a/acra-http/src/main/java/org/acra/security/KeyStoreFactory.java +++ b/acra-http/src/main/java/org/acra/security/KeyStoreFactory.kt @@ -13,13 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.acra.security; +package org.acra.security -import android.content.Context; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import java.security.KeyStore; +import android.content.Context +import java.security.KeyStore /** * The interface can be used to provide a KeyStore with certificates. @@ -27,8 +24,6 @@ * @author F43nd1r * @since 4.8.3 */ -public interface KeyStoreFactory { - - @Nullable - KeyStore create(@NonNull Context context); -} +interface KeyStoreFactory { + fun create(context: Context): KeyStore? +} \ No newline at end of file diff --git a/acra-http/src/main/java/org/acra/security/KeyStoreHelper.java b/acra-http/src/main/java/org/acra/security/KeyStoreHelper.java deleted file mode 100644 index f024c1d751..0000000000 --- a/acra-http/src/main/java/org/acra/security/KeyStoreHelper.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright 2016 - * - * 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.security; - -import android.content.Context; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import org.acra.ACRAConstants; -import org.acra.config.ConfigUtils; -import org.acra.config.CoreConfiguration; -import org.acra.config.HttpSenderConfiguration; -import org.acra.util.InstanceCreator; - -import java.security.KeyStore; - -/** - * Helper to get a KeyStore from a configuration - * - * @author F43nd1r - * @since 4.9.0 - */ -public final class KeyStoreHelper { - private static final String ASSET_PREFIX = "asset://"; - - private KeyStoreHelper() { - } - - /** - * try to get the keystore - * @param context a context - * @param config the configuration - * @return the keystore, or null if none provided / failure - */ - @Nullable - public static KeyStore getKeyStore(@NonNull Context context, @NonNull CoreConfiguration config) { - final HttpSenderConfiguration senderConfiguration = ConfigUtils.getPluginConfiguration(config, HttpSenderConfiguration.class); - final InstanceCreator instanceCreator = new InstanceCreator(); - KeyStore keyStore = instanceCreator.create(senderConfiguration.getKeyStoreFactoryClass(), NoKeyStoreFactory::new).create(context); - if(keyStore == null) { - //either users factory did not create a keystore, or the configuration is default {@link NoKeyStoreFactory} - final int certificateRes = senderConfiguration.getResCertificate(); - final String certificatePath = senderConfiguration.getCertificatePath(); - final String certificateType = senderConfiguration.getCertificateType(); - if(certificateRes != ACRAConstants.DEFAULT_RES_VALUE){ - keyStore = new ResourceKeyStoreFactory(certificateType, certificateRes).create(context); - }else if(!certificatePath.equals(ACRAConstants.DEFAULT_STRING_VALUE)){ - if(certificatePath.startsWith(ASSET_PREFIX)) { - keyStore = new AssetKeyStoreFactory(certificateType, certificatePath.substring(ASSET_PREFIX.length())).create(context); - } else { - keyStore = new FileKeyStoreFactory(certificateType, certificatePath).create(context); - } - } - } - return keyStore; - } -} diff --git a/acra-http/src/main/java/org/acra/security/KeyStoreHelper.kt b/acra-http/src/main/java/org/acra/security/KeyStoreHelper.kt new file mode 100644 index 0000000000..391133d71c --- /dev/null +++ b/acra-http/src/main/java/org/acra/security/KeyStoreHelper.kt @@ -0,0 +1,61 @@ +/* + * Copyright 2016 + * + * 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.security + +import android.content.Context +import org.acra.ACRAConstants +import org.acra.config.ConfigUtils.getPluginConfiguration +import org.acra.config.CoreConfiguration +import org.acra.config.HttpSenderConfiguration +import org.acra.util.InstanceCreator +import java.security.KeyStore + +/** + * Helper to get a KeyStore from a configuration + * + * @author F43nd1r + * @since 4.9.0 + */ +object KeyStoreHelper { + private const val ASSET_PREFIX = "asset://" + + /** + * try to get the keystore + * @param context a context + * @param config the configuration + * @return the keystore, or null if none provided / failure + */ + fun getKeyStore(context: Context, config: CoreConfiguration): KeyStore? { + val senderConfiguration = getPluginConfiguration(config, HttpSenderConfiguration::class.java) + var keyStore = InstanceCreator.create(senderConfiguration.keyStoreFactoryClass) { NoKeyStoreFactory() }.create(context) + if (keyStore == null) { + //either users factory did not create a keystore, or the configuration is default {@link NoKeyStoreFactory} + val certificateRes: Int = senderConfiguration.resCertificate + val certificatePath: String = senderConfiguration.certificatePath + val certificateType: String = senderConfiguration.certificateType + if (certificateRes != ACRAConstants.DEFAULT_RES_VALUE) { + keyStore = ResourceKeyStoreFactory(certificateType, certificateRes).create(context) + } else if (certificatePath != ACRAConstants.DEFAULT_STRING_VALUE) { + keyStore = if (certificatePath.startsWith(ASSET_PREFIX)) { + AssetKeyStoreFactory(certificateType, certificatePath.substring(ASSET_PREFIX.length)).create(context) + } else { + FileKeyStoreFactory(certificateType, certificatePath).create(context) + } + } + } + return keyStore + } +} \ No newline at end of file diff --git a/acra-http/src/main/java/org/acra/security/NoKeyStoreFactory.java b/acra-http/src/main/java/org/acra/security/NoKeyStoreFactory.kt similarity index 67% rename from acra-http/src/main/java/org/acra/security/NoKeyStoreFactory.java rename to acra-http/src/main/java/org/acra/security/NoKeyStoreFactory.kt index bf9961248b..87d4f41cbd 100644 --- a/acra-http/src/main/java/org/acra/security/NoKeyStoreFactory.java +++ b/acra-http/src/main/java/org/acra/security/NoKeyStoreFactory.kt @@ -13,13 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.acra.security; +package org.acra.security -import android.content.Context; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import java.security.KeyStore; +import android.content.Context +import java.security.KeyStore /** * Default KeyStoreFactory. Does not provide any KeyStore @@ -27,10 +24,6 @@ * @author F43nd1r * @since 4.9.0 */ -public class NoKeyStoreFactory implements KeyStoreFactory { - @Nullable - @Override - public KeyStore create(@NonNull Context context) { - return null; - } -} +class NoKeyStoreFactory : KeyStoreFactory { + override fun create(context: Context): KeyStore? = null +} \ No newline at end of file diff --git a/acra-http/src/main/java/org/acra/security/ProtocolSocketFactoryWrapper.java b/acra-http/src/main/java/org/acra/security/ProtocolSocketFactoryWrapper.java deleted file mode 100644 index ea8b4b01f2..0000000000 --- a/acra-http/src/main/java/org/acra/security/ProtocolSocketFactoryWrapper.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright (c) 2020 - * - * 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.security; - -import android.os.Build; - -import javax.net.ssl.SSLSocket; -import javax.net.ssl.SSLSocketFactory; -import java.io.IOException; -import java.net.InetAddress; -import java.net.Socket; -import java.net.UnknownHostException; -import java.util.ArrayList; -import java.util.List; - -public class ProtocolSocketFactoryWrapper extends SSLSocketFactory { - private final SSLSocketFactory delegate; - private final List protocols; - - public ProtocolSocketFactoryWrapper(SSLSocketFactory delegate, List protocols) { - this.delegate = delegate; - ArrayList list = new ArrayList<>(protocols); - if(Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { - list.remove(TLS.V1_3); - if(Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { - list.remove(TLS.V1_2); - list.remove(TLS.V1_1); - } - } - this.protocols = new ArrayList<>(list.size()); - for (TLS tls : list) { - this.protocols.add(tls.getId()); - } - } - - private Socket setProtocols(Socket socket) { - if ((socket instanceof SSLSocket) && isTLSServerEnabled((SSLSocket) socket)) { - ((SSLSocket) socket).setEnabledProtocols(protocols.toArray(new String[0])); - } - return socket; - } - - private boolean isTLSServerEnabled(SSLSocket sslSocket) { - for (String protocol : sslSocket.getSupportedProtocols()) { - if (protocols.contains(protocol)) { - return true; - } - } - return false; - } - - @Override - public String[] getDefaultCipherSuites() { - return delegate.getDefaultCipherSuites(); - } - - @Override - public String[] getSupportedCipherSuites() { - return delegate.getSupportedCipherSuites(); - } - - @Override - public Socket createSocket(Socket socket, String s, int i, boolean b) throws IOException { - return setProtocols(delegate.createSocket(socket, s, i, b)); - } - - @Override - public Socket createSocket(String s, int i) throws IOException, UnknownHostException { - return setProtocols(delegate.createSocket(s, i)); - } - - @Override - public Socket createSocket(String s, int i, InetAddress inetAddress, int i1) throws IOException, UnknownHostException { - return setProtocols(delegate.createSocket(s, i, inetAddress, i1)); - } - - @Override - public Socket createSocket(InetAddress inetAddress, int i) throws IOException { - return setProtocols(delegate.createSocket(inetAddress, i)); - } - - @Override - public Socket createSocket(InetAddress inetAddress, int i, InetAddress inetAddress1, int i1) throws IOException { - return setProtocols(delegate.createSocket(inetAddress, i, inetAddress1, i1)); - } -} diff --git a/acra-http/src/main/java/org/acra/security/ProtocolSocketFactoryWrapper.kt b/acra-http/src/main/java/org/acra/security/ProtocolSocketFactoryWrapper.kt new file mode 100644 index 0000000000..d0ee595b18 --- /dev/null +++ b/acra-http/src/main/java/org/acra/security/ProtocolSocketFactoryWrapper.kt @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2020 + * + * 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.security + +import android.os.Build +import java.io.IOException +import java.net.InetAddress +import java.net.Socket +import java.net.UnknownHostException +import javax.net.ssl.SSLSocket +import javax.net.ssl.SSLSocketFactory + +class ProtocolSocketFactoryWrapper(private val delegate: SSLSocketFactory, protocols: List) : SSLSocketFactory() { + private val protocols: List + + init { + val list = protocols.toMutableList() + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { + list.remove(TLS.V1_3) + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { + list.remove(TLS.V1_2) + list.remove(TLS.V1_1) + } + } + this.protocols = list.map { it.id } + } + + private fun setProtocols(socket: Socket): Socket { + if (socket is SSLSocket && isTLSServerEnabled(socket)) { + socket.enabledProtocols = protocols.toTypedArray() + } + return socket + } + + private fun isTLSServerEnabled(sslSocket: SSLSocket): Boolean { + for (protocol in sslSocket.supportedProtocols) { + if (protocols.contains(protocol)) { + return true + } + } + return false + } + + override fun getDefaultCipherSuites(): Array = delegate.defaultCipherSuites + + override fun getSupportedCipherSuites(): Array = delegate.supportedCipherSuites + + @Throws(IOException::class) + override fun createSocket(socket: Socket, s: String, i: Int, b: Boolean): Socket = setProtocols(delegate.createSocket(socket, s, i, b)) + + @Throws(IOException::class, UnknownHostException::class) + override fun createSocket(s: String, i: Int): Socket = setProtocols(delegate.createSocket(s, i)) + + @Throws(IOException::class, UnknownHostException::class) + override fun createSocket(s: String, i: Int, inetAddress: InetAddress, i1: Int): Socket = setProtocols(delegate.createSocket(s, i, inetAddress, i1)) + + @Throws(IOException::class) + override fun createSocket(inetAddress: InetAddress, i: Int): Socket = setProtocols(delegate.createSocket(inetAddress, i)) + + @Throws(IOException::class) + override fun createSocket(inetAddress: InetAddress, i: Int, inetAddress1: InetAddress, i1: Int): Socket = setProtocols(delegate.createSocket(inetAddress, i, inetAddress1, i1)) +} \ No newline at end of file diff --git a/acra-http/src/main/java/org/acra/security/ResourceKeyStoreFactory.java b/acra-http/src/main/java/org/acra/security/ResourceKeyStoreFactory.java deleted file mode 100644 index 07a01a1466..0000000000 --- a/acra-http/src/main/java/org/acra/security/ResourceKeyStoreFactory.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2016 - * - * 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.security; - -import android.content.Context; -import androidx.annotation.NonNull; -import androidx.annotation.RawRes; - -import java.io.InputStream; - -/** - * KeyStoreFactory for a certificate stored in a raw resource - * - * @author F43nd1r - * @since 4.8.3 - */ -final class ResourceKeyStoreFactory extends BaseKeyStoreFactory { - - @RawRes - private final int rawRes; - - /** - * creates a new KeyStoreFactory for the specified resource with a custom certificate type - * @param certificateType the certificate type - * @param rawRes raw resource id - */ - ResourceKeyStoreFactory(String certificateType, @RawRes int rawRes) { - super(certificateType); - this.rawRes = rawRes; - } - - @Override - public InputStream getInputStream(@NonNull Context context) { - return context.getResources().openRawResource(rawRes); - } -} diff --git a/acra-http/src/main/java/org/acra/security/ResourceKeyStoreFactory.kt b/acra-http/src/main/java/org/acra/security/ResourceKeyStoreFactory.kt new file mode 100644 index 0000000000..109434802b --- /dev/null +++ b/acra-http/src/main/java/org/acra/security/ResourceKeyStoreFactory.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2016 + * + * 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.security + +import android.content.Context +import androidx.annotation.RawRes +import java.io.InputStream + +/** + * KeyStoreFactory for a certificate stored in a raw resource + * + * creates a new KeyStoreFactory for the specified resource with a custom certificate type + * @param certificateType the certificate type + * @param rawRes raw resource id + * + * @author F43nd1r + * @since 4.8.3 + */ +internal class ResourceKeyStoreFactory(certificateType: String, @RawRes private val rawRes: Int) : BaseKeyStoreFactory(certificateType) { + public override fun getInputStream(context: Context): InputStream? = context.resources.openRawResource(rawRes) +} \ No newline at end of file diff --git a/acra-http/src/main/java/org/acra/security/TLS.java b/acra-http/src/main/java/org/acra/security/TLS.kt similarity index 69% rename from acra-http/src/main/java/org/acra/security/TLS.java rename to acra-http/src/main/java/org/acra/security/TLS.kt index 85ea5c78fb..e18ef650b0 100644 --- a/acra-http/src/main/java/org/acra/security/TLS.java +++ b/acra-http/src/main/java/org/acra/security/TLS.kt @@ -13,22 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +package org.acra.security -package org.acra.security; - -public enum TLS { - V1("TLSv1"), - V1_1("TLSv1.1"), - V1_2("TLSv1.2"), - V1_3("TLSv1.3"); - - private final String id; - - TLS(String id) { - this.id = id; - } - - public String getId() { - return id; - } -} +enum class TLS(val id: String) { + V1("TLSv1"), V1_1("TLSv1.1"), V1_2("TLSv1.2"), V1_3("TLSv1.3"); +} \ No newline at end of file diff --git a/acra-http/src/main/java/org/acra/sender/HttpSender.java b/acra-http/src/main/java/org/acra/sender/HttpSender.java deleted file mode 100644 index 395a971a44..0000000000 --- a/acra-http/src/main/java/org/acra/sender/HttpSender.java +++ /dev/null @@ -1,230 +0,0 @@ -/* - * Copyright (c) 2017 - * - * 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.net.Uri; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import android.util.Pair; -import org.acra.ACRA; -import org.acra.ACRAConstants; -import org.acra.ReportField; -import org.acra.attachment.DefaultAttachmentProvider; -import org.acra.config.ConfigUtils; -import org.acra.config.CoreConfiguration; -import org.acra.config.HttpSenderConfiguration; -import org.acra.data.CrashReportData; -import org.acra.data.StringFormat; -import org.acra.http.BinaryHttpRequest; -import org.acra.http.DefaultHttpRequest; -import org.acra.http.MultipartHttpRequest; -import org.acra.util.InstanceCreator; -import org.acra.util.UriUtils; - -import java.io.FileNotFoundException; -import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.List; -import java.util.Map; - -import static org.acra.ACRA.LOG_TAG; - -/** - * The {@link ReportSender} used by ACRA for http sending - * - * @author F43nd1r & Various - */ -public class HttpSender implements ReportSender { - - private final CoreConfiguration config; - private final HttpSenderConfiguration httpConfig; - private final Uri mFormUri; - private final Method mMethod; - private final StringFormat mType; - private String mUsername; - private String mPassword; - - /** - *

- * Create a new HttpSender instance with its destination taken from the supplied config. - *

- * - * @param config AcraConfig declaring the - * @param method HTTP {@link Method} to be used to send data. Currently only {@link Method#POST} and {@link Method#PUT} are available. - * If {@link Method#PUT} is used, the {@link ReportField#REPORT_ID} is appended to the formUri to be compliant with RESTful APIs. - * @param type {@link StringFormat} of encoding used to send the report body. - * {@link StringFormat#KEY_VALUE_LIST} is a simple Key/Value pairs list as defined by the application/x-www-form-urlencoded mime type. - */ - public HttpSender(@NonNull CoreConfiguration config, @Nullable Method method, @Nullable StringFormat type) { - this(config, method, type, null); - } - - /** - *

- * Create a new HttpPostSender instance with a fixed destination provided as - * a parameter. Configuration changes to the formUri are not applied. - *

- * - * @param config AcraConfig declaring the - * @param method HTTP {@link Method} to be used to send data. Currently only {@link Method#POST} and {@link Method#PUT} are available. - * If {@link Method#PUT} is used, the {@link ReportField#REPORT_ID} is appended to the formUri to be compliant with RESTful APIs. - * @param type {@link StringFormat} of encoding used to send the report body. - * {@link StringFormat#KEY_VALUE_LIST} is a simple Key/Value pairs list as defined by the application/x-www-form-urlencoded mime type. - * @param formUri The URL of your server-side crash report collection script. - */ - public HttpSender(@NonNull CoreConfiguration config, @Nullable Method method, @Nullable StringFormat type, @Nullable String formUri) { - this.config = config; - this.httpConfig = ConfigUtils.getPluginConfiguration(config, HttpSenderConfiguration.class); - mMethod = (method == null) ? httpConfig.getHttpMethod() : method; - mFormUri = Uri.parse((formUri == null) ? httpConfig.getUri() : formUri); - mType = (type == null) ? config.getReportFormat() : type; - mUsername = null; - mPassword = null; - } - - /** - *

- * Set credentials for this HttpSender that override (if present) the ones set globally. - *

- * - * @param username The username to set for HTTP Basic Auth. - * @param password The password to set for HTTP Basic Auth. - */ - @SuppressWarnings("unused") - public void setBasicAuth(@Nullable String username, @Nullable String password) { - mUsername = username; - mPassword = password; - } - - @Override - public void send(@NonNull Context context, @NonNull CrashReportData report) throws ReportSenderException { - try { - final String baseUrl = mFormUri.toString(); - if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "Connect to " + baseUrl); - - final String login = mUsername != null ? mUsername : isNull(httpConfig.getBasicAuthLogin()) ? null : httpConfig.getBasicAuthLogin(); - final String password = mPassword != null ? mPassword : isNull(httpConfig.getBasicAuthPassword()) ? null : httpConfig.getBasicAuthPassword(); - - final InstanceCreator instanceCreator = new InstanceCreator(); - final List uris = instanceCreator.create(config.getAttachmentUriProvider(), DefaultAttachmentProvider::new).getAttachments(context, config); - - // Generate report body depending on requested type - final String reportAsString = convertToString(report, mType); - - // Adjust URL depending on method - final URL reportUrl = mMethod.createURL(baseUrl, report); - - sendHttpRequests(config, context, mMethod, mType.getMatchingHttpContentType(), login, password, httpConfig.getConnectionTimeout(), - httpConfig.getSocketTimeout(), httpConfig.getHttpHeaders(), reportAsString, reportUrl, uris); - - } catch (@NonNull Exception e) { - throw new ReportSenderException("Error while sending " + config.getReportFormat() - + " report via Http " + mMethod.name(), e); - } - } - - @SuppressWarnings("WeakerAccess") - protected void sendHttpRequests(@NonNull CoreConfiguration configuration, @NonNull Context context, @NonNull Method method, @NonNull String contentType, - @Nullable String login, @Nullable String password, int connectionTimeOut, int socketTimeOut, @Nullable Map headers, - @NonNull String content, @NonNull URL url, @NonNull List attachments) throws IOException { - switch (method) { - case POST: - if (attachments.isEmpty()) { - sendWithoutAttachments(configuration, context, method, contentType, login, password, connectionTimeOut, socketTimeOut, headers, content, url); - } else { - postMultipart(configuration, context, contentType, login, password, connectionTimeOut, socketTimeOut, headers, content, url, attachments); - } - break; - case PUT: - sendWithoutAttachments(configuration, context, method, contentType, login, password, connectionTimeOut, socketTimeOut, headers, content, url); - for (Uri uri : attachments) { - putAttachment(configuration, context, login, password, connectionTimeOut, socketTimeOut, headers, url, uri); - } - break; - } - } - - @SuppressWarnings("WeakerAccess") - protected void sendWithoutAttachments(@NonNull CoreConfiguration configuration, @NonNull Context context, @NonNull Method method, @NonNull String contentType, - @Nullable String login, @Nullable String password, int connectionTimeOut, int socketTimeOut, @Nullable Map headers, - @NonNull String content, @NonNull URL url) throws IOException { - new DefaultHttpRequest(configuration, context, method, contentType, login, password, connectionTimeOut, socketTimeOut, headers).send(url, content); - } - - @SuppressWarnings("WeakerAccess") - protected void postMultipart(@NonNull CoreConfiguration configuration, @NonNull Context context, @NonNull String contentType, - @Nullable String login, @Nullable String password, int connectionTimeOut, int socketTimeOut, @Nullable Map headers, - @NonNull String content, @NonNull URL url, @NonNull List attachments) throws IOException { - new MultipartHttpRequest(configuration, context, contentType, login, password, connectionTimeOut, socketTimeOut, headers).send(url, Pair.create(content, attachments)); - } - - @SuppressWarnings("WeakerAccess") - protected void putAttachment(@NonNull CoreConfiguration configuration, @NonNull Context context, - @Nullable String login, @Nullable String password, int connectionTimeOut, int socketTimeOut, @Nullable Map headers, - @NonNull URL url, @NonNull Uri attachment) throws IOException { - try { - final URL attachmentUrl = new URL(url.toString() + "-" + UriUtils.getFileNameFromUri(context, attachment)); - new BinaryHttpRequest(configuration, context, login, password, connectionTimeOut, socketTimeOut, headers).send(attachmentUrl, attachment); - } catch (FileNotFoundException e) { - ACRA.log.w("Not sending attachment", e); - } - } - - /** - * Convert a report to string - * - * @param report the report to convert - * @param format the format to convert to - * @return a string representation of the report - * @throws Exception if conversion failed - */ - @NonNull - @SuppressWarnings("WeakerAccess") - protected String convertToString(CrashReportData report, @NonNull StringFormat format) throws Exception { - return format.toFormattedString(report, config.getReportContent(), "&", "\n", true); - } - - private boolean isNull(@Nullable String aString) { - return aString == null || aString.length() == 0 || ACRAConstants.NULL_VALUE.equals(aString); - } - - /** - * Available HTTP methods to send data. Only POST and PUT are currently - * supported. - */ - public enum Method { - POST { - @NonNull - @Override - URL createURL(@NonNull String baseUrl, @NonNull CrashReportData report) throws MalformedURLException { - return new URL(baseUrl); - } - }, - PUT { - @NonNull - @Override - URL createURL(@NonNull String baseUrl, @NonNull CrashReportData report) throws MalformedURLException { - return new URL(baseUrl + '/' + report.getString(ReportField.REPORT_ID)); - } - }; - - @NonNull - abstract URL createURL(@NonNull String baseUrl, @NonNull CrashReportData report) throws MalformedURLException; - } - -} \ No newline at end of file diff --git a/acra-http/src/main/java/org/acra/sender/HttpSender.kt b/acra-http/src/main/java/org/acra/sender/HttpSender.kt new file mode 100644 index 0000000000..a295e64179 --- /dev/null +++ b/acra-http/src/main/java/org/acra/sender/HttpSender.kt @@ -0,0 +1,191 @@ +/* + * Copyright (c) 2017 + * + * 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.net.Uri +import android.util.Pair +import org.acra.ACRA +import org.acra.ACRAConstants +import org.acra.ReportField +import org.acra.attachment.DefaultAttachmentProvider +import org.acra.config.ConfigUtils.getPluginConfiguration +import org.acra.config.CoreConfiguration +import org.acra.config.HttpSenderConfiguration +import org.acra.data.CrashReportData +import org.acra.data.StringFormat +import org.acra.http.BinaryHttpRequest +import org.acra.http.DefaultHttpRequest +import org.acra.http.MultipartHttpRequest +import org.acra.log.debug +import org.acra.util.InstanceCreator +import org.acra.util.UriUtils +import java.io.FileNotFoundException +import java.io.IOException +import java.net.MalformedURLException +import java.net.URL + +/** + * The [ReportSender] used by ACRA for http sending + * + * Create a new HttpPostSender instance with a fixed destination provided as + * a parameter. Configuration changes to the formUri are not applied. + * + * + * @param config AcraConfig declaring the + * @param method HTTP [Method] to be used to send data. Currently only [Method.POST] and [Method.PUT] are available. + * If [Method.PUT] is used, the [ReportField.REPORT_ID] is appended to the formUri to be compliant with RESTful APIs. + * @param type [StringFormat] of encoding used to send the report body. + * [StringFormat.KEY_VALUE_LIST] is a simple Key/Value pairs list as defined by the application/x-www-form-urlencoded mime type. + * @param formUri The URL of your server-side crash report collection script. + */ +/** + * + * + * Create a new HttpSender instance with its destination taken from the supplied config. + * + * + * @param config AcraConfig declaring the + * @param method HTTP [Method] to be used to send data. Currently only [Method.POST] and [Method.PUT] are available. + * If [Method.PUT] is used, the [ReportField.REPORT_ID] is appended to the formUri to be compliant with RESTful APIs. + * @param type [StringFormat] of encoding used to send the report body. + * [StringFormat.KEY_VALUE_LIST] is a simple Key/Value pairs list as defined by the application/x-www-form-urlencoded mime type. + * @author F43nd1r & Various + */ +@Suppress("unused") +open class HttpSender @JvmOverloads constructor(private val config: CoreConfiguration, method: Method?, type: StringFormat?, formUri: String? = null) : ReportSender { + private val httpConfig: HttpSenderConfiguration = getPluginConfiguration(config, HttpSenderConfiguration::class.java) + private val mFormUri: Uri = Uri.parse(formUri ?: httpConfig.uri) + private val mMethod: Method = method ?: httpConfig.httpMethod + private val mType: StringFormat = type ?: config.reportFormat + private var mUsername: String?= null + private var mPassword: String? = null + + /** + * + * + * Set credentials for this HttpSender that override (if present) the ones set globally. + * + * + * @param username The username to set for HTTP Basic Auth. + * @param password The password to set for HTTP Basic Auth. + */ + fun setBasicAuth(username: String?, password: String?) { + mUsername = username + mPassword = password + } + + @Throws(ReportSenderException::class) + override fun send(context: Context, errorContent: CrashReportData) { + try { + val baseUrl = mFormUri.toString() + debug { "Connect to $baseUrl" } + val login = if (mUsername != null) mUsername!! else (if (isNull(httpConfig.basicAuthLogin)) null else httpConfig.basicAuthLogin)!! + val password = if (mPassword != null) mPassword!! else (if (isNull(httpConfig.basicAuthPassword)) null else httpConfig.basicAuthPassword)!! + val uris = InstanceCreator.create(config.attachmentUriProvider) { DefaultAttachmentProvider() }.getAttachments(context, config) + + // Generate report body depending on requested type + val reportAsString = convertToString(errorContent, mType) + + // Adjust URL depending on method + val reportUrl = mMethod.createURL(baseUrl, errorContent) + sendHttpRequests(config, context, mMethod, mType.matchingHttpContentType, login, password, httpConfig.connectionTimeout, + httpConfig.socketTimeout, httpConfig.httpHeaders, reportAsString, reportUrl, uris) + } catch (e: Exception) { + throw ReportSenderException("Error while sending " + config.reportFormat.toString() + " report via Http " + mMethod.name, e) + } + } + + @Throws(IOException::class) + protected fun sendHttpRequests(configuration: CoreConfiguration, context: Context, method: Method, contentType: String, + login: String?, password: String?, connectionTimeOut: Int, socketTimeOut: Int, headers: Map?, + content: String, url: URL, attachments: List) { + when (method) { + Method.POST -> if (attachments.isEmpty()) { + sendWithoutAttachments(configuration, context, method, contentType, login, password, connectionTimeOut, socketTimeOut, headers, content, url) + } else { + postMultipart(configuration, context, contentType, login, password, connectionTimeOut, socketTimeOut, headers, content, url, attachments) + } + Method.PUT -> { + sendWithoutAttachments(configuration, context, method, contentType, login, password, connectionTimeOut, socketTimeOut, headers, content, url) + for (uri in attachments) { + putAttachment(configuration, context, login, password, connectionTimeOut, socketTimeOut, headers, url, uri) + } + } + } + } + + @Throws(IOException::class) + protected fun sendWithoutAttachments(configuration: CoreConfiguration, context: Context, method: Method, contentType: String, + login: String?, password: String?, connectionTimeOut: Int, socketTimeOut: Int, headers: Map?, + content: String, url: URL) { + DefaultHttpRequest(configuration, context, method, contentType, login, password, connectionTimeOut, socketTimeOut, headers).send(url, content) + } + + @Throws(IOException::class) + protected fun postMultipart(configuration: CoreConfiguration, context: Context, contentType: String, + login: String?, password: String?, connectionTimeOut: Int, socketTimeOut: Int, headers: Map?, + content: String, url: URL, attachments: List) { + MultipartHttpRequest(configuration, context, contentType, login, password, connectionTimeOut, socketTimeOut, headers).send(url, content to attachments) + } + + @Throws(IOException::class) + protected fun putAttachment(configuration: CoreConfiguration, context: Context, + login: String?, password: String?, connectionTimeOut: Int, socketTimeOut: Int, headers: Map?, + url: URL, attachment: Uri) { + try { + val attachmentUrl = URL(url.toString() + "-" + UriUtils.getFileNameFromUri(context, attachment)) + BinaryHttpRequest(configuration, context, login, password, connectionTimeOut, socketTimeOut, headers).send(attachmentUrl, attachment) + } catch (e: FileNotFoundException) { + ACRA.log.w("Not sending attachment", e) + } + } + + /** + * Convert a report to string + * + * @param report the report to convert + * @param format the format to convert to + * @return a string representation of the report + * @throws Exception if conversion failed + */ + @Throws(Exception::class) + protected fun convertToString(report: CrashReportData?, format: StringFormat): String { + return format.toFormattedString(report!!, config.reportContent, "&", "\n", true) + } + + private fun isNull(aString: String?): Boolean { + return aString == null || aString.isEmpty() || ACRAConstants.NULL_VALUE == aString + } + + /** + * Available HTTP methods to send data. Only POST and PUT are currently + * supported. + */ + enum class Method { + POST { + @Throws(MalformedURLException::class) + override fun createURL(baseUrl: String, report: CrashReportData): URL = URL(baseUrl) + }, + PUT { + @Throws(MalformedURLException::class) + override fun createURL(baseUrl: String, report: CrashReportData): URL = URL(baseUrl + '/' + report.getString(ReportField.REPORT_ID)) + }; + + @Throws(MalformedURLException::class) + abstract fun createURL(baseUrl: String, report: CrashReportData): URL + } +} \ No newline at end of file diff --git a/acra-http/src/main/java/org/acra/sender/HttpSenderFactory.java b/acra-http/src/main/java/org/acra/sender/HttpSenderFactory.java deleted file mode 100644 index 80f2f9f409..0000000000 --- a/acra-http/src/main/java/org/acra/sender/HttpSenderFactory.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (c) 2017 - * - * 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 androidx.annotation.NonNull; - -import com.google.auto.service.AutoService; - -import org.acra.config.CoreConfiguration; -import org.acra.config.HttpSenderConfiguration; -import org.acra.plugins.HasConfigPlugin; - -/** - * Constructs a {@link HttpSender} with no report field mappings. - */ -@AutoService(ReportSenderFactory.class) -public final class HttpSenderFactory extends HasConfigPlugin implements ReportSenderFactory { - public HttpSenderFactory() { - super(HttpSenderConfiguration.class); - } - - @NonNull - @Override - public ReportSender create(@NonNull Context context, @NonNull CoreConfiguration config) { - return new HttpSender(config, null, null); - } -} diff --git a/acra-http/src/main/java/org/acra/sender/HttpSenderFactory.kt b/acra-http/src/main/java/org/acra/sender/HttpSenderFactory.kt new file mode 100644 index 0000000000..9dbb4f7686 --- /dev/null +++ b/acra-http/src/main/java/org/acra/sender/HttpSenderFactory.kt @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2017 + * + * 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 com.google.auto.service.AutoService +import org.acra.config.CoreConfiguration +import org.acra.config.HttpSenderConfiguration +import org.acra.plugins.HasConfigPlugin + +/** + * Constructs a [HttpSender] with no report field mappings. + */ +@AutoService(ReportSenderFactory::class) +class HttpSenderFactory : HasConfigPlugin(HttpSenderConfiguration::class.java), ReportSenderFactory { + override fun create(context: Context, config: CoreConfiguration): ReportSender = HttpSender(config, null, null) +} \ No newline at end of file diff --git a/acra-http/src/main/java/org/acra/util/UriUtils.java b/acra-http/src/main/java/org/acra/util/UriUtils.java deleted file mode 100644 index 75e48c49c6..0000000000 --- a/acra-http/src/main/java/org/acra/util/UriUtils.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (c) 2017 - * - * 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.util; - -import android.content.ContentResolver; -import android.content.Context; -import android.database.Cursor; -import android.net.Uri; -import android.provider.OpenableColumns; -import androidx.annotation.NonNull; -import org.acra.ACRAConstants; -import org.acra.attachment.AcraContentProvider; - -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -/** - * @author F43nd1r - * @since 11.03.2017 - */ - -public final class UriUtils { - private UriUtils() { - } - - public static void copyFromUri(@NonNull Context context, @NonNull OutputStream outputStream, @NonNull Uri uri) throws IOException { - try (final InputStream inputStream = context.getContentResolver().openInputStream(uri)) { - if (inputStream == null) { - throw new FileNotFoundException("Could not open " + uri.toString()); - } - final byte[] buffer = new byte[ACRAConstants.DEFAULT_BUFFER_SIZE_IN_BYTES]; - int length; - while ((length = inputStream.read(buffer)) > 0) { - outputStream.write(buffer, 0, length); - } - } - } - - @NonNull - public static String getFileNameFromUri(@NonNull Context context, @NonNull Uri uri) throws FileNotFoundException { - try (Cursor cursor = context.getContentResolver().query(uri, new String[]{OpenableColumns.DISPLAY_NAME}, null, null, null)) { - if (cursor != null && cursor.moveToFirst()) { - return cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)); - } - } - throw new FileNotFoundException("Could not resolve filename of " + uri); - } - - @NonNull - public static String getMimeType(@NonNull Context context, @NonNull Uri uri) { - if (uri.getScheme().equals(ContentResolver.SCHEME_CONTENT)) { - final ContentResolver contentResolver = context.getContentResolver(); - String type = contentResolver.getType(uri); - if (type != null) return type; - } - return AcraContentProvider.guessMimeType(uri); - } - -} diff --git a/acra-http/src/main/java/org/acra/util/UriUtils.kt b/acra-http/src/main/java/org/acra/util/UriUtils.kt new file mode 100644 index 0000000000..4e488f77d6 --- /dev/null +++ b/acra-http/src/main/java/org/acra/util/UriUtils.kt @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2017 + * + * 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.util + +import android.content.ContentResolver +import android.content.Context +import android.net.Uri +import android.provider.OpenableColumns +import org.acra.ACRAConstants +import org.acra.attachment.AcraContentProvider.Companion.guessMimeType +import java.io.FileNotFoundException +import java.io.IOException +import java.io.OutputStream + +/** + * @author F43nd1r + * @since 11.03.2017 + */ +object UriUtils { + @Throws(IOException::class) + fun copyFromUri(context: Context, outputStream: OutputStream, uri: Uri) { + context.contentResolver.openInputStream(uri)?.copyTo(outputStream, bufferSize = ACRAConstants.DEFAULT_BUFFER_SIZE_IN_BYTES) ?: throw FileNotFoundException( + "Could not open $uri") + } + + @Throws(FileNotFoundException::class) + fun getFileNameFromUri(context: Context, uri: Uri): String { + context.contentResolver.query(uri, arrayOf(OpenableColumns.DISPLAY_NAME), null, null, null)?.use { cursor -> + if (cursor.moveToFirst()) { + return cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)) + } + } + throw FileNotFoundException("Could not resolve filename of $uri") + } + + fun getMimeType(context: Context, uri: Uri): String { + if (uri.scheme == ContentResolver.SCHEME_CONTENT) { + context.contentResolver.getType(uri)?.let { return it } + } + return guessMimeType(uri) + } +} \ No newline at end of file From 585de340d5db722fcde96dd40912c1e5ba27d680 Mon Sep 17 00:00:00 2001 From: f43nd1r Date: Mon, 23 Nov 2020 03:07:20 +0100 Subject: [PATCH 07/16] javacore to kotlin --- .../acra/{ReportField.java => ReportField.kt} | 54 +++++- .../acra/collections/BoundedLinkedList.java | 157 ---------------- .../org/acra/collections/BoundedLinkedList.kt | 124 +++++++++++++ .../org/acra/collections/ImmutableList.java | 172 ------------------ .../org/acra/collections/ImmutableMap.java | 132 -------------- .../org/acra/collections/ImmutableSet.java | 143 --------------- .../UnmodifiableIteratorWrapper.java | 47 ----- .../UnmodifiableListIteratorWrapper.java | 78 -------- .../java/org/acra/collections/WeakStack.java | 135 -------------- .../java/org/acra/collections/WeakStack.kt | 116 ++++++++++++ ...ion.java => ACRAConfigurationException.kt} | 20 +- .../java/org/acra/config/ClassValidator.java | 46 ----- .../java/org/acra/config/ClassValidator.kt | 43 +++++ .../{Configuration.java => Configuration.kt} | 10 +- ...onBuilder.java => ConfigurationBuilder.kt} | 16 +- 15 files changed, 346 insertions(+), 947 deletions(-) rename acra-javacore/src/main/java/org/acra/{ReportField.java => ReportField.kt} (97%) delete mode 100644 acra-javacore/src/main/java/org/acra/collections/BoundedLinkedList.java create mode 100644 acra-javacore/src/main/java/org/acra/collections/BoundedLinkedList.kt delete mode 100644 acra-javacore/src/main/java/org/acra/collections/ImmutableList.java delete mode 100644 acra-javacore/src/main/java/org/acra/collections/ImmutableMap.java delete mode 100644 acra-javacore/src/main/java/org/acra/collections/ImmutableSet.java delete mode 100644 acra-javacore/src/main/java/org/acra/collections/UnmodifiableIteratorWrapper.java delete mode 100644 acra-javacore/src/main/java/org/acra/collections/UnmodifiableListIteratorWrapper.java delete mode 100644 acra-javacore/src/main/java/org/acra/collections/WeakStack.java create mode 100644 acra-javacore/src/main/java/org/acra/collections/WeakStack.kt rename acra-javacore/src/main/java/org/acra/config/{ACRAConfigurationException.java => ACRAConfigurationException.kt} (66%) delete mode 100644 acra-javacore/src/main/java/org/acra/config/ClassValidator.java create mode 100644 acra-javacore/src/main/java/org/acra/config/ClassValidator.kt rename acra-javacore/src/main/java/org/acra/config/{Configuration.java => Configuration.kt} (90%) rename acra-javacore/src/main/java/org/acra/config/{ConfigurationBuilder.java => ConfigurationBuilder.kt} (79%) diff --git a/acra-javacore/src/main/java/org/acra/ReportField.java b/acra-javacore/src/main/java/org/acra/ReportField.kt similarity index 97% rename from acra-javacore/src/main/java/org/acra/ReportField.java rename to acra-javacore/src/main/java/org/acra/ReportField.kt index 2bb94b0d74..f45881d767 100644 --- a/acra-javacore/src/main/java/org/acra/ReportField.java +++ b/acra-javacore/src/main/java/org/acra/ReportField.kt @@ -13,190 +13,226 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -package org.acra; - +package org.acra /** * Specifies all the different fields available in a crash report. - * + * * @author Normal - * */ -public enum ReportField { +enum class ReportField { /** * Report Identifier. */ REPORT_ID, + /** * Application version code. This is the incremental integer version code * used to differentiate versions on the android market. */ APP_VERSION_CODE, + /** * Application version name. */ APP_VERSION_NAME, + /** * Application package name. */ PACKAGE_NAME, + /** * Base path of the application's private file folder. */ FILE_PATH, + /** * Device model name. */ PHONE_MODEL, + /** * Device android version name. */ ANDROID_VERSION, + /** * Android Build details. */ BUILD, + /** * Device brand (manufacturer or carrier). */ BRAND, + /** * Device overall product code. */ PRODUCT, + /** * Estimation of the total device memory size based on filesystem stats. */ TOTAL_MEM_SIZE, + /** * Estimation of the available device memory size based on filesystem stats. */ AVAILABLE_MEM_SIZE, + /** * Contains key = value pairs defined by the application developer during * the application build. */ BUILD_CONFIG, + /** * Contains key = value pairs defined by the application developer during * the application execution. */ CUSTOM_DATA, + /** * The Holy Stack Trace. */ STACK_TRACE, + /** - * A hash of the stack trace, taking only method names into account.
+ * A hash of the stack trace, taking only method names into account.

* Line numbers are stripped out before computing the hash. This can help you * uniquely identify stack traces. */ STACK_TRACE_HASH, + /** * android.content.res.Configuration fields state on the application start. * */ INITIAL_CONFIGURATION, + /** * android.content.res.Configuration fields state on the application crash. */ CRASH_CONFIGURATION, + /** * Device display specifications. */ DISPLAY, + /** * Comment added by the user in the CrashReportDialog. */ USER_COMMENT, + /** * User date on application start. */ USER_APP_START_DATE, + /** * User date immediately after the crash occurred. */ USER_CRASH_DATE, + /** * Memory state details for the application process. */ DUMPSYS_MEMINFO, + /** * Content of the android.os.DropBoxManager (introduced in API level 8). * Requires READ_LOGS permission. */ DROPBOX, + /** * Logcat default extract. Requires READ_LOGS permission. */ LOGCAT, + /** * Logcat eventslog extract. Requires READ_LOGS permission. */ EVENTSLOG, + /** * Logcat radio extract. Requires READ_LOGS permission. */ RADIOLOG, + /** * True if the report has been explicitly sent silently by the developer. */ IS_SILENT, + /** * Device unique ID (IMEI). Requires READ_PHONE_STATE permission. */ DEVICE_ID, + /** * Installation unique ID. This identifier allow you to track a specific * user application installation without using any personal data. */ INSTALLATION_ID, + /** * User email address. Can be provided by the user in SharedPreferences. */ USER_EMAIL, + /** * Features declared as available on this device by the system. */ DEVICE_FEATURES, + /** * External storage state and standard directories. */ ENVIRONMENT, + /** * System settings. */ SETTINGS_SYSTEM, + /** * Secure settings (applications can't modify them). */ SETTINGS_SECURE, + /** * Global settings, introduced in Android 4.2 (API level 17) to centralize settings for multiple users. */ SETTINGS_GLOBAL, + /** * SharedPreferences contents */ SHARED_PREFERENCES, + /** * Content of your own application log file. */ APPLICATION_LOG, + /** * Since Android API Level 16 (Android 4.1 - Jelly Beans), retrieve the list * of supported Media codecs and their capabilities (color format, profile * and level). */ MEDIA_CODEC_LIST, + /** * Retrieves details of the failing thread (id, name, group name). */ THREAD_DETAILS, + /** * Retrieves the user IP address(es). */ USER_IP - -} +} \ No newline at end of file diff --git a/acra-javacore/src/main/java/org/acra/collections/BoundedLinkedList.java b/acra-javacore/src/main/java/org/acra/collections/BoundedLinkedList.java deleted file mode 100644 index 4968964fbe..0000000000 --- a/acra-javacore/src/main/java/org/acra/collections/BoundedLinkedList.java +++ /dev/null @@ -1,157 +0,0 @@ -/* - * Copyright 2010 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.collections; - -import androidx.annotation.NonNull; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.LinkedList; - -/** - * A {@link LinkedList} version with a maximum number of elements. When adding elements to the end of the list, first elements in the list are discarded if the maximum size is reached. - * - * @author Kevin Gaudin - */ -@SuppressWarnings("serial") -public final class BoundedLinkedList extends LinkedList { - - private final int maxSize; - - public BoundedLinkedList(int maxSize) { - this.maxSize = maxSize; - } - - /* - * (non-Javadoc) - * - * @see java.util.LinkedList#add(java.lang.Object) - */ - @Override - public boolean add(E object) { - if (size() == maxSize) { - removeFirst(); - } - return super.add(object); - } - - /* - * (non-Javadoc) - * - * @see java.util.LinkedList#add(int, java.lang.Object) - */ - @Override - public void add(int location, E object) { - if (size() == maxSize) { - removeFirst(); - } - super.add(location, object); - } - - /* - * (non-Javadoc) - * - * @see java.util.LinkedList#addAll(java.util.Collection) - */ - @Override - public boolean addAll(@NonNull Collection collection) { - final int size = collection.size(); - if (size > maxSize) { - collection = new ArrayList<>(collection).subList(size - maxSize, size); - } - final int overhead = size() + collection.size() - maxSize; - if (overhead > 0) { - removeRange(0, overhead); - } - return super.addAll(collection); - } - - /* - * (non-Javadoc) - * - * @see java.util.LinkedList#addAll(int, java.util.Collection) - */ - @Override - public boolean addAll(int location, Collection collection) { - // int totalNeededSize = size() + collection.size(); - // int overhead = totalNeededSize - maxSize; - // if(overhead > 0) { - // removeRange(0, overhead); - // } - // return super.addAll(location, collection); - if(location == size()){ - return super.addAll(location, collection); - } - throw new UnsupportedOperationException(); - } - - /* - * (non-Javadoc) - * - * @see java.util.LinkedList#addFirst(java.lang.Object) - */ - @Override - public void addFirst(E object) { - // super.addFirst(object); - throw new UnsupportedOperationException(); - } - - /* - * (non-Javadoc) - * - * @see java.util.LinkedList#addLast(java.lang.Object) - */ - @Override - public void addLast(E object) { - add(object); - } - - /* - * (non-Javadoc) - * - * @see java.util.AbstractCollection#toString() - */ - @NonNull - @Override - public String toString() { - final StringBuilder result = new StringBuilder(); - for (E object : this) { - result.append(object.toString()); - } - return result.toString(); - } - - @Override - public boolean offer(E o) { - return add(o); - } - - @Override - public boolean offerFirst(E e) { - addFirst(e); - return true; - } - - @Override - public boolean offerLast(E e) { - return add(e); - } - - @Override - public void push(E e) { - add(e); - } -} diff --git a/acra-javacore/src/main/java/org/acra/collections/BoundedLinkedList.kt b/acra-javacore/src/main/java/org/acra/collections/BoundedLinkedList.kt new file mode 100644 index 0000000000..a5805845a5 --- /dev/null +++ b/acra-javacore/src/main/java/org/acra/collections/BoundedLinkedList.kt @@ -0,0 +1,124 @@ +/* + * Copyright 2010 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.collections + +import java.util.* + +/** + * A [LinkedList] version with a maximum number of elements. When adding elements to the end of the list, first elements in the list are discarded if the maximum size is reached. + * + * @author Kevin Gaudin + */ +class BoundedLinkedList(private val maxSize: Int) : LinkedList() { + /* + * (non-Javadoc) + * + * @see java.util.LinkedList#add(java.lang.Object) + */ + override fun add(element: E): Boolean { + if (size == maxSize) { + removeFirst() + } + return super.add(element) + } + + /* + * (non-Javadoc) + * + * @see java.util.LinkedList#add(int, java.lang.Object) + */ + override fun add(index: Int, element: E) { + if (size == maxSize) { + removeFirst() + } + super.add(index, element) + } + + /* + * (non-Javadoc) + * + * @see java.util.LinkedList#addAll(java.util.Collection) + */ + override fun addAll(elements: Collection): Boolean { + var collection = elements + val size = collection.size + if (size > maxSize) { + collection = ArrayList(collection).subList(size - maxSize, size) + } + val overhead = size + collection.size - maxSize + if (overhead > 0) { + removeRange(0, overhead) + } + return super.addAll(collection) + } + + /* + * (non-Javadoc) + * + * @see java.util.LinkedList#addAll(int, java.util.Collection) + */ + override fun addAll(index: Int, elements: Collection): Boolean { + // int totalNeededSize = size() + collection.size(); + // int overhead = totalNeededSize - maxSize; + // if(overhead > 0) { + // removeRange(0, overhead); + // } + // return super.addAll(location, collection); + if (index == size) { + return super.addAll(index, elements) + } + throw UnsupportedOperationException() + } + + /* + * (non-Javadoc) + * + * @see java.util.LinkedList#addFirst(java.lang.Object) + */ + override fun addFirst(`object`: E) { + // super.addFirst(object); + throw UnsupportedOperationException() + } + + /* + * (non-Javadoc) + * + * @see java.util.LinkedList#addLast(java.lang.Object) + */ + override fun addLast(`object`: E) { + add(`object`) + } + + /* + * (non-Javadoc) + * + * @see java.util.AbstractCollection#toString() + */ + override fun toString(): String = joinToString() + + override fun offer(o: E): Boolean = add(o) + + override fun offerFirst(e: E): Boolean { + addFirst(e) + return true + } + + override fun offerLast(e: E): Boolean = add(e) + + override fun push(e: E) { + add(e) + } +} \ No newline at end of file diff --git a/acra-javacore/src/main/java/org/acra/collections/ImmutableList.java b/acra-javacore/src/main/java/org/acra/collections/ImmutableList.java deleted file mode 100644 index 9714e04374..0000000000 --- a/acra-javacore/src/main/java/org/acra/collections/ImmutableList.java +++ /dev/null @@ -1,172 +0,0 @@ -/* - * Copyright 2016 - * - * 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.collections; - -import androidx.annotation.NonNull; - -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Iterator; -import java.util.List; -import java.util.ListIterator; - -/** - * Naive (not optimized) implementation of an Immutable List - * - * @author F43nd1r - * @since 4.9.0 - */ -public final class ImmutableList implements List, Serializable { - - private final List mList; - - @SafeVarargs - public ImmutableList(E... elements) { - this(Arrays.asList(elements)); - } - - public ImmutableList(@NonNull Collection collection) { - this.mList = new ArrayList<>(collection); - } - - @Override - public void add(int location, E object) { - throw new UnsupportedOperationException(); - } - - @Override - public boolean add(E object) { - throw new UnsupportedOperationException(); - } - - @Override - public boolean addAll(int location, @NonNull Collection collection) { - throw new UnsupportedOperationException(); - } - - @Override - public boolean addAll(@NonNull Collection collection) { - throw new UnsupportedOperationException(); - } - - @Override - public void clear() { - throw new UnsupportedOperationException(); - } - - @Override - public boolean contains(Object object) { - return mList.contains(object); - } - - @Override - public boolean containsAll(@NonNull Collection collection) { - return mList.containsAll(collection); - } - - @Override - public E get(int location) { - return mList.get(location); - } - - @Override - public int indexOf(Object object) { - return mList.indexOf(object); - } - - @Override - public boolean isEmpty() { - return mList.isEmpty(); - } - - @NonNull - @Override - public Iterator iterator() { - return new UnmodifiableIteratorWrapper<>(mList.iterator()); - } - - @Override - public int lastIndexOf(Object object) { - return mList.lastIndexOf(object); - } - - @Override - public ListIterator listIterator() { - return new UnmodifiableListIteratorWrapper<>(mList.listIterator()); - } - - @NonNull - @Override - public ListIterator listIterator(int location) { - return new UnmodifiableListIteratorWrapper<>(mList.listIterator(location)); - } - - @Override - public E remove(int location) { - throw new UnsupportedOperationException(); - } - - @Override - public boolean remove(Object object) { - throw new UnsupportedOperationException(); - } - - @Override - public boolean removeAll(@NonNull Collection collection) { - throw new UnsupportedOperationException(); - } - - @Override - public boolean retainAll(@NonNull Collection collection) { - throw new UnsupportedOperationException(); - } - - @Override - public E set(int location, E object) { - throw new UnsupportedOperationException(); - } - - @Override - public int size() { - return mList.size(); - } - - @NonNull - @Override - public List subList(int start, int end) { - throw new UnsupportedOperationException(); - } - - @NonNull - @Override - public Object[] toArray() { - return mList.toArray(); - } - - @NonNull - @Override - public T[] toArray(@NonNull T[] array) { - //noinspection SuspiciousToArrayCall - return mList.toArray(array); - } - - @Override - public String toString() { - return "ImmutableList{" + mList.toString() + "}"; - } -} diff --git a/acra-javacore/src/main/java/org/acra/collections/ImmutableMap.java b/acra-javacore/src/main/java/org/acra/collections/ImmutableMap.java deleted file mode 100644 index bf3af41266..0000000000 --- a/acra-javacore/src/main/java/org/acra/collections/ImmutableMap.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright 2016 - * - * 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.collections; - -import androidx.annotation.NonNull; - -import java.io.Serializable; -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; - -/** - * Naive (not optimized) implementation of an Immutable Map - * - * @author F43nd1r - * @since 4.9.0 - */ -public final class ImmutableMap implements Map, Serializable { - - private final Map mMap; - - public ImmutableMap(@NonNull Map map) { - this.mMap = new HashMap<>(map); - } - - @Override - public void clear() { - throw new UnsupportedOperationException(); - } - - @Override - public boolean containsKey(Object key) { - return mMap.containsKey(key); - } - - @Override - public boolean containsValue(Object value) { - return mMap.containsValue(value); - } - - @NonNull - @Override - public Set> entrySet() { - final Set> original = mMap.entrySet(); - final ImmutableSet.Builder> builder = new ImmutableSet.Builder<>(); - for (Entry entry : original) { - builder.add(new ImmutableEntryWrapper<>(entry)); - } - return builder.build(); - } - - @Override - public V get(Object key) { - return mMap.get(key); - } - - @Override - public boolean isEmpty() { - return mMap.isEmpty(); - } - - @NonNull - @Override - public Set keySet() { - return new ImmutableSet<>(mMap.keySet()); - } - - @Override - public V put(K key, V value) { - throw new UnsupportedOperationException(); - } - - @Override - public void putAll(@NonNull Map map) { - throw new UnsupportedOperationException(); - } - - @Override - public V remove(Object object) { - throw new UnsupportedOperationException(); - } - - @Override - public int size() { - return mMap.size(); - } - - @NonNull - @Override - public Collection values() { - return new ImmutableList<>(mMap.values()); - } - - private static class ImmutableEntryWrapper implements Map.Entry { - private final Map.Entry mEntry; - - ImmutableEntryWrapper(Entry mEntry) { - this.mEntry = mEntry; - } - - @Override - public K getKey() { - return mEntry.getKey(); - } - - @Override - public V getValue() { - return mEntry.getValue(); - } - - @NonNull - @Override - public V setValue(Object object) { - throw new UnsupportedOperationException(); - } - } - -} diff --git a/acra-javacore/src/main/java/org/acra/collections/ImmutableSet.java b/acra-javacore/src/main/java/org/acra/collections/ImmutableSet.java deleted file mode 100644 index 085982a851..0000000000 --- a/acra-javacore/src/main/java/org/acra/collections/ImmutableSet.java +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Copyright 2016 - * - * 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.collections; - -import androidx.annotation.NonNull; - -import java.io.Serializable; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.Iterator; -import java.util.LinkedHashSet; -import java.util.Set; - -/** - * Naive (not optimized) implementation of an Immutable Set with reliable, user-specified iteration order. - * - * @author F43nd1r - * @since 4.9.0 - */ -public final class ImmutableSet implements Set, Serializable { - private static final ImmutableSet EMPTY = new ImmutableSet<>(); - - @NonNull - public static ImmutableSet empty() { - //noinspection unchecked - return (ImmutableSet) EMPTY; - } - - private final Set mSet; - - private ImmutableSet(){ - this.mSet = Collections.emptySet(); - } - - @SafeVarargs - public ImmutableSet(E... elements) { - this(Arrays.asList(elements)); - } - - public ImmutableSet(@NonNull Collection collection) { - this.mSet = new LinkedHashSet<>(collection); - } - - @Override - public boolean add(E object) { - throw new UnsupportedOperationException(); - } - - @Override - public boolean addAll(@NonNull Collection collection) { - throw new UnsupportedOperationException(); - } - - @Override - public void clear() { - throw new UnsupportedOperationException(); - } - - @Override - public boolean contains(Object object) { - return mSet.contains(object); - } - - @Override - public boolean containsAll(@NonNull Collection collection) { - return mSet.containsAll(collection); - } - - @Override - public boolean isEmpty() { - return mSet.isEmpty(); - } - - @NonNull - @Override - public Iterator iterator() { - return new UnmodifiableIteratorWrapper<>(mSet.iterator()); - } - - @Override - public boolean remove(Object object) { - throw new UnsupportedOperationException(); - } - - @Override - public boolean removeAll(@NonNull Collection collection) { - throw new UnsupportedOperationException(); - } - - @Override - public boolean retainAll(@NonNull Collection collection) { - throw new UnsupportedOperationException(); - } - - @Override - public int size() { - return mSet.size(); - } - - @NonNull - @Override - public Object[] toArray() { - return mSet.toArray(); - } - - @NonNull - @Override - public T[] toArray(@NonNull T[] array) { - //noinspection SuspiciousToArrayCall - return mSet.toArray(array); - } - - public static final class Builder { - private final Set mSet; - - public Builder() { - mSet = new LinkedHashSet<>(); - } - - public void add(E element) { - mSet.add(element); - } - - public ImmutableSet build() { - return new ImmutableSet<>(mSet); - } - } - -} diff --git a/acra-javacore/src/main/java/org/acra/collections/UnmodifiableIteratorWrapper.java b/acra-javacore/src/main/java/org/acra/collections/UnmodifiableIteratorWrapper.java deleted file mode 100644 index c0308479d2..0000000000 --- a/acra-javacore/src/main/java/org/acra/collections/UnmodifiableIteratorWrapper.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2016 - * - * 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.collections; - -import java.util.Iterator; - -/** - * Wrapper around an Iterator which prevents modifications - * - * @author F43nd1r - * @since 4.9.0 - */ -class UnmodifiableIteratorWrapper implements Iterator { - private final Iterator mIterator; - - UnmodifiableIteratorWrapper(Iterator mIterator) { - this.mIterator = mIterator; - } - - @Override - public boolean hasNext() { - return mIterator.hasNext(); - } - - @Override - public E next() { - return mIterator.next(); - } - - @Override - public void remove() { - throw new UnsupportedOperationException(); - } -} diff --git a/acra-javacore/src/main/java/org/acra/collections/UnmodifiableListIteratorWrapper.java b/acra-javacore/src/main/java/org/acra/collections/UnmodifiableListIteratorWrapper.java deleted file mode 100644 index 97f0ab597b..0000000000 --- a/acra-javacore/src/main/java/org/acra/collections/UnmodifiableListIteratorWrapper.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright 2016 - * - * 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.collections; - -import java.util.ListIterator; - -/** - * Wrapper around a ListIterator which prevents modifications - * - * @author F43nd1r - * @since 4.9.0 - */ -class UnmodifiableListIteratorWrapper implements ListIterator { - private final ListIterator mIterator; - - UnmodifiableListIteratorWrapper(ListIterator mIterator) { - this.mIterator = mIterator; - } - - @Override - public void add(E object) { - throw new UnsupportedOperationException(); - } - - @Override - public boolean hasNext() { - return mIterator.hasNext(); - } - - @Override - public boolean hasPrevious() { - return mIterator.hasPrevious(); - } - - @Override - public E next() { - return mIterator.next(); - } - - @Override - public int nextIndex() { - return mIterator.nextIndex(); - } - - @Override - public E previous() { - return mIterator.previous(); - } - - @Override - public int previousIndex() { - return mIterator.previousIndex(); - } - - @Override - public void remove() { - throw new UnsupportedOperationException(); - } - - @Override - public void set(E object) { - throw new UnsupportedOperationException(); - } -} - diff --git a/acra-javacore/src/main/java/org/acra/collections/WeakStack.java b/acra-javacore/src/main/java/org/acra/collections/WeakStack.java deleted file mode 100644 index 8a83d64937..0000000000 --- a/acra-javacore/src/main/java/org/acra/collections/WeakStack.java +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright (c) 2019 - * - * 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.collections; - -import androidx.annotation.NonNull; - -import java.lang.ref.WeakReference; -import java.util.*; - -/** - * A stack which keeps only weak references - * - * @author F43nd1r - * @since 5.3.0 - */ -public class WeakStack extends AbstractCollection { - private final List> contents = new ArrayList<>(); - - private void cleanup() { - for (WeakReference weakReference : contents) { - if (weakReference.get() == null) contents.remove(weakReference); - } - } - - @Override - public int size() { - cleanup(); - return contents.size(); - } - - @Override - public boolean contains(Object o) { - if (o != null) { - for (WeakReference weakReference : contents) { - if (o.equals(weakReference.get())) return true; - } - } - return false; - } - - @NonNull - @Override - public Iterator iterator() { - return new WeakIterator<>(contents.iterator()); - } - - @Override - public boolean add(T t) { - return contents.add(new WeakReference(t)); - } - - @Override - public boolean remove(Object o) { - if (o != null) { - for (int i = 0; i < contents.size(); i++) { - if (o.equals(contents.get(i).get())) { - contents.remove(i); - return true; - } - } - } - return false; - } - - public T peek() { - for (int i = contents.size() - 1; i >= 0; i--) { - T result = contents.get(i).get(); - if (result != null) return result; - } - throw new EmptyStackException(); - } - - public T pop() { - T result = peek(); - remove(result); - return result; - } - - @Override - public void clear() { - contents.clear(); - } - - private static class WeakIterator implements Iterator { - private final Iterator> iterator; - private T next; - - private WeakIterator(Iterator> iterator) { - this.iterator = iterator; - } - - @Override - public boolean hasNext() { - if (next != null) return true; - while (iterator.hasNext()) { - T t = iterator.next().get(); - if (t != null) { - //to ensure next() can't throw after hasNext() returned true, we need to dereference this - next = t; - return true; - } - } - return false; - } - - @Override - public T next() { - T result = next; - next = null; - while (result == null) { - result = iterator.next().get(); - } - return result; - } - - @Override - public void remove() { - iterator.remove(); - } - } -} diff --git a/acra-javacore/src/main/java/org/acra/collections/WeakStack.kt b/acra-javacore/src/main/java/org/acra/collections/WeakStack.kt new file mode 100644 index 0000000000..48654aa1e4 --- /dev/null +++ b/acra-javacore/src/main/java/org/acra/collections/WeakStack.kt @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2019 + * + * 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.collections + +import java.lang.ref.WeakReference +import java.util.* + +/** + * A stack which keeps only weak references + * + * @author F43nd1r + * @since 5.3.0 + */ +class WeakStack : AbstractCollection() { + private val contents: MutableList> = ArrayList() + private fun cleanup() { + for (weakReference in contents) { + if (weakReference.get() == null) contents.remove(weakReference) + } + } + + override val size: Int + get() { + cleanup() + return contents.size + } + + override fun contains(element: T): Boolean { + return element?.let { + for (weakReference in contents) { + if (element == weakReference.get()) return@let true + } + false + } ?: false + } + + override fun iterator(): MutableIterator { + return WeakIterator(contents.iterator()) + } + + override fun add(t: T?): Boolean { + return contents.add(WeakReference(t)) + } + + override fun remove(element: T): Boolean { + if (element != null) { + for (i in contents.indices) { + if (element == contents[i].get()) { + contents.removeAt(i) + return true + } + } + } + return false + } + + fun peek(): T { + for (i in contents.indices.reversed()) { + val result = contents[i].get() + if (result != null) return result + } + throw EmptyStackException() + } + + fun pop(): T { + val result = peek() + remove(result) + return result + } + + override fun clear() { + contents.clear() + } + + private class WeakIterator(private val iterator: MutableIterator>) : MutableIterator { + private var next: T? = null + override fun hasNext(): Boolean { + if (next != null) return true + while (iterator.hasNext()) { + val t = iterator.next().get() + if (t != null) { + //to ensure next() can't throw after hasNext() returned true, we need to dereference this + next = t + return true + } + } + return false + } + + override fun next(): T { + var result = next + next = null + while (result == null) { + result = iterator.next().get() + } + return result + } + + override fun remove() { + iterator.remove() + } + } +} \ No newline at end of file diff --git a/acra-javacore/src/main/java/org/acra/config/ACRAConfigurationException.java b/acra-javacore/src/main/java/org/acra/config/ACRAConfigurationException.kt similarity index 66% rename from acra-javacore/src/main/java/org/acra/config/ACRAConfigurationException.java rename to acra-javacore/src/main/java/org/acra/config/ACRAConfigurationException.kt index 380d3e7919..6d20f2f66c 100644 --- a/acra-javacore/src/main/java/org/acra/config/ACRAConfigurationException.java +++ b/acra-javacore/src/main/java/org/acra/config/ACRAConfigurationException.kt @@ -13,22 +13,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.acra.config; +package org.acra.config /** * A simple Exception used when required configuration items are missing. - * + * * @author Kevin Gaudin */ -public class ACRAConfigurationException extends Exception { - - private static final long serialVersionUID = -7355339673505996110L; - - public ACRAConfigurationException(String msg) { - super(msg); - } +class ACRAConfigurationException : Exception { + constructor(msg: String?) : super(msg) + constructor(detailMessage: String?, throwable: Throwable?) : super(detailMessage, throwable) - public ACRAConfigurationException(String detailMessage, Throwable throwable) { - super(detailMessage, throwable); + companion object { + private const val serialVersionUID = -7355339673505996110L } -} +} \ No newline at end of file diff --git a/acra-javacore/src/main/java/org/acra/config/ClassValidator.java b/acra-javacore/src/main/java/org/acra/config/ClassValidator.java deleted file mode 100644 index 1e910028ac..0000000000 --- a/acra-javacore/src/main/java/org/acra/config/ClassValidator.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) 2017 - * - * 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.config; - -import java.lang.reflect.Modifier; - -/** - * @author F43nd1r - * @since 07.06.2017 - */ - -public final class ClassValidator { - private ClassValidator() { - } - - public static void check(Class... classes) throws ACRAConfigurationException { - for (Class clazz : classes) { - if (clazz.isInterface()) { - throw new ACRAConfigurationException("Expected class, but found interface " + clazz.getName() + "."); - } else if (Modifier.isAbstract(clazz.getModifiers())) { - throw new ACRAConfigurationException("Class " + clazz.getName() + " cannot be abstract."); - } else if (clazz.getEnclosingClass() != null && !Modifier.isStatic(clazz.getModifiers())) { - throw new ACRAConfigurationException("Class " + clazz.getName() + " has to be static."); - } - try { - clazz.getConstructor(); - } catch (NoSuchMethodException e) { - throw new ACRAConfigurationException("Class " + clazz.getName() + " is missing a no-args Constructor.", e); - } - } - } -} diff --git a/acra-javacore/src/main/java/org/acra/config/ClassValidator.kt b/acra-javacore/src/main/java/org/acra/config/ClassValidator.kt new file mode 100644 index 0000000000..14c355bfe4 --- /dev/null +++ b/acra-javacore/src/main/java/org/acra/config/ClassValidator.kt @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2017 + * + * 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.config + +import java.lang.reflect.Modifier + +/** + * @author F43nd1r + * @since 07.06.2017 + */ +object ClassValidator { + @JvmStatic + @Throws(ACRAConfigurationException::class) + fun check(vararg classes: Class<*>) { + for (clazz in classes) { + if (clazz.isInterface) { + throw ACRAConfigurationException("Expected class, but found interface ${clazz.name}.") + } else if (Modifier.isAbstract(clazz.modifiers)) { + throw ACRAConfigurationException("Class ${clazz.name} cannot be abstract.") + } else if (clazz.enclosingClass != null && !Modifier.isStatic(clazz.modifiers)) { + throw ACRAConfigurationException("Class ${clazz.name} has to be static.") + } + try { + clazz.getConstructor() + } catch (e: NoSuchMethodException) { + throw ACRAConfigurationException("""Class ${clazz.name} is missing a no-args Constructor.""", e) + } + } + } +} \ No newline at end of file diff --git a/acra-javacore/src/main/java/org/acra/config/Configuration.java b/acra-javacore/src/main/java/org/acra/config/Configuration.kt similarity index 90% rename from acra-javacore/src/main/java/org/acra/config/Configuration.java rename to acra-javacore/src/main/java/org/acra/config/Configuration.kt index d4b22b6571..2837c9d257 100644 --- a/acra-javacore/src/main/java/org/acra/config/Configuration.java +++ b/acra-javacore/src/main/java/org/acra/config/Configuration.kt @@ -13,8 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -package org.acra.config; +package org.acra.config /** * A configuration object @@ -22,12 +21,11 @@ * @author F43nd1r * @since 01.06.2017 */ - -public interface Configuration { +interface Configuration { /** * checks if the corresponding plugin is enabled * * @return if this is enabled */ - boolean enabled(); -} + fun enabled(): Boolean +} \ No newline at end of file diff --git a/acra-javacore/src/main/java/org/acra/config/ConfigurationBuilder.java b/acra-javacore/src/main/java/org/acra/config/ConfigurationBuilder.kt similarity index 79% rename from acra-javacore/src/main/java/org/acra/config/ConfigurationBuilder.java rename to acra-javacore/src/main/java/org/acra/config/ConfigurationBuilder.kt index d08de5a75c..a0e8692d0d 100644 --- a/acra-javacore/src/main/java/org/acra/config/ConfigurationBuilder.java +++ b/acra-javacore/src/main/java/org/acra/config/ConfigurationBuilder.kt @@ -13,25 +13,21 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -package org.acra.config; - -import androidx.annotation.NonNull; +package org.acra.config /** - * A {@link Configuration} builder + * A [Configuration] builder * * @author F43nd1r * @since 01.06.2017 */ - -public interface ConfigurationBuilder { +interface ConfigurationBuilder { /** * Builds the configuration * * @return the fully configured and immutable configuration * @throws ACRAConfigurationException if the configuration is invalid */ - @NonNull - Configuration build() throws ACRAConfigurationException; -} + @Throws(ACRAConfigurationException::class) + fun build(): Configuration +} \ No newline at end of file From 46dfccae65a89ffda178b89cfe41e9b8bd3a3bbd Mon Sep 17 00:00:00 2001 From: f43nd1r Date: Mon, 23 Nov 2020 03:21:11 +0100 Subject: [PATCH 08/16] limiter to kotlin --- .../java/org/acra/config/LimiterData.java | 156 ------------------ .../main/java/org/acra/config/LimiterData.kt | 136 +++++++++++++++ .../config/LimitingReportAdministrator.java | 147 ----------------- .../config/LimitingReportAdministrator.kt | 138 ++++++++++++++++ .../acra/startup/LimiterStartupProcessor.java | 83 ---------- .../acra/startup/LimiterStartupProcessor.kt | 67 ++++++++ 6 files changed, 341 insertions(+), 386 deletions(-) delete mode 100644 acra-limiter/src/main/java/org/acra/config/LimiterData.java create mode 100644 acra-limiter/src/main/java/org/acra/config/LimiterData.kt delete mode 100644 acra-limiter/src/main/java/org/acra/config/LimitingReportAdministrator.java create mode 100644 acra-limiter/src/main/java/org/acra/config/LimitingReportAdministrator.kt delete mode 100644 acra-limiter/src/main/java/org/acra/startup/LimiterStartupProcessor.java create mode 100644 acra-limiter/src/main/java/org/acra/startup/LimiterStartupProcessor.kt diff --git a/acra-limiter/src/main/java/org/acra/config/LimiterData.java b/acra-limiter/src/main/java/org/acra/config/LimiterData.java deleted file mode 100644 index 45ea30315a..0000000000 --- a/acra-limiter/src/main/java/org/acra/config/LimiterData.java +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright (c) 2017 - * - * 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.config; - -import android.content.Context; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import org.acra.ACRA; -import org.acra.ACRAConstants; -import org.acra.ReportField; -import org.acra.data.CrashReportData; -import org.acra.util.IOUtils; -import org.acra.util.StreamReader; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import java.io.FileNotFoundException; -import java.io.IOException; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.*; - -import static org.acra.ACRA.LOG_TAG; - -/** - * @author F43nd1r - * @since 26.10.2017 - */ - -public class LimiterData { - private static final String FILE_LIMITER_DATA = "ACRA-limiter.json"; - private final List list; - - public static LimiterData load(@NonNull Context context) { - try { - return new LimiterData(new StreamReader(context.openFileInput(FILE_LIMITER_DATA)).read()); - } catch (FileNotFoundException e){ - return new LimiterData(); - }catch (IOException | JSONException e) { - ACRA.log.w(LOG_TAG, "Failed to load LimiterData", e); - return new LimiterData(); - } - } - - public LimiterData() { - list = new ArrayList<>(); - } - - private LimiterData(@Nullable String json) throws JSONException { - this(); - if (json != null && !json.isEmpty()) { - final JSONArray array = new JSONArray(json); - final int length = array.length(); - for (int i = 0; i < length; i++) { - list.add(new ReportMetadata(array.optJSONObject(i))); - } - } - } - - public void store(@NonNull Context context) throws IOException { - IOUtils.writeStringToFile(context.getFileStreamPath(FILE_LIMITER_DATA), toJSON()); - } - - @NonNull - List getReportMetadata() { - return list; - } - - public void purgeOldData(Calendar keepAfter) { - for (final Iterator iterator = list.iterator(); iterator.hasNext(); ) { - if (keepAfter.after(iterator.next().getTimestamp())) { - iterator.remove(); - } - } - } - - String toJSON() { - return new JSONArray(list).toString(); - } - - public static class ReportMetadata extends JSONObject { - private static final String KEY_STACK_TRACE = "stacktrace"; - private static final String KEY_EXCEPTION_CLASS = "class"; - private static final String KEY_TIMESTAMP = "timestamp"; - - ReportMetadata(@NonNull CrashReportData crashReportData) throws JSONException { - final String stacktrace = crashReportData.getString(ReportField.STACK_TRACE); - put(KEY_STACK_TRACE, stacktrace); - final int index = stacktrace.indexOf('\n'); - final String firstLine = index == -1 ? stacktrace : stacktrace.substring(0, index); - final int index2 = firstLine.indexOf(':'); - final String className = index2 == -1 ? firstLine : firstLine.substring(0, index2); - try { - Class.forName(className); - put(KEY_EXCEPTION_CLASS, className); - } catch (ClassNotFoundException ignored) { - } - put(KEY_TIMESTAMP, crashReportData.getString(ReportField.USER_CRASH_DATE)); - - - } - - ReportMetadata(@NonNull JSONObject copyFrom) throws JSONException { - super(copyFrom, jsonArrayToList(copyFrom.names())); - } - - public String getStacktrace() { - return optString(KEY_STACK_TRACE); - } - - public String getExceptionClass() { - return optString(KEY_EXCEPTION_CLASS); - } - - @Nullable - Calendar getTimestamp() { - final String timestamp = optString(KEY_TIMESTAMP); - if (timestamp != null) { - try { - final Calendar calendar = Calendar.getInstance(); - calendar.setTime(new SimpleDateFormat(ACRAConstants.DATE_TIME_FORMAT_STRING, Locale.ENGLISH).parse(timestamp)); - return calendar; - } catch (ParseException ignored) { - } - } - return null; - } - } - - @NonNull - private static String[] jsonArrayToList(@Nullable JSONArray array) { - final List list = new ArrayList<>(); - if (array != null) { - final int length = array.length(); - for (int i = 0; i < length; i++) { - list.add(String.valueOf(array.opt(i))); - } - } - return list.toArray(new String[list.size()]); - } -} diff --git a/acra-limiter/src/main/java/org/acra/config/LimiterData.kt b/acra-limiter/src/main/java/org/acra/config/LimiterData.kt new file mode 100644 index 0000000000..1df5fe1f16 --- /dev/null +++ b/acra-limiter/src/main/java/org/acra/config/LimiterData.kt @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2017 + * + * 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.config + +import android.content.Context +import org.acra.ACRAConstants +import org.acra.ReportField +import org.acra.data.CrashReportData +import org.acra.log.warn +import org.acra.util.IOUtils.writeStringToFile +import org.acra.util.StreamReader +import org.json.JSONArray +import org.json.JSONException +import org.json.JSONObject +import java.io.FileNotFoundException +import java.io.IOException +import java.text.ParseException +import java.text.SimpleDateFormat +import java.util.* + +/** + * @author F43nd1r + * @since 26.10.2017 + */ +class LimiterData() { + val reportMetadata: MutableList = mutableListOf() + + private constructor(json: String?) : this() { + if (json != null && json.isNotEmpty()) { + val array = JSONArray(json) + val length = array.length() + for (i in 0 until length) { + reportMetadata.add(ReportMetadata(array.optJSONObject(i))) + } + } + } + + @Throws(IOException::class) + fun store(context: Context) { + writeStringToFile(context.getFileStreamPath(FILE_LIMITER_DATA), toJSON()) + } + + fun purgeOldData(keepAfter: Calendar) { + reportMetadata.removeAll { keepAfter.after(it.timestamp) } + } + + fun toJSON(): String { + return JSONArray(reportMetadata).toString() + } + + class ReportMetadata : JSONObject { + internal constructor(crashReportData: CrashReportData) { + val stacktrace = crashReportData.getString(ReportField.STACK_TRACE) + put(KEY_STACK_TRACE, stacktrace) + val index = stacktrace!!.indexOf('\n') + val firstLine = if (index == -1) stacktrace else stacktrace.substring(0, index) + val index2 = firstLine.indexOf(':') + val className = if (index2 == -1) firstLine else firstLine.substring(0, index2) + try { + Class.forName(className) + put(KEY_EXCEPTION_CLASS, className) + } catch (ignored: ClassNotFoundException) { + } + put(KEY_TIMESTAMP, crashReportData.getString(ReportField.USER_CRASH_DATE)) + } + + internal constructor(copyFrom: JSONObject) : super(copyFrom, jsonArrayToList(copyFrom.names())) {} + + val stacktrace: String + get() = optString(KEY_STACK_TRACE) + val exceptionClass: String + get() = optString(KEY_EXCEPTION_CLASS) + val timestamp: Calendar? + get() { + val timestamp = optString(KEY_TIMESTAMP).takeIf { it.isNotEmpty() } + if (timestamp != null) { + try { + val calendar = Calendar.getInstance() + calendar.time = SimpleDateFormat(ACRAConstants.DATE_TIME_FORMAT_STRING, Locale.ENGLISH).parse(timestamp)!! + return calendar + } catch (ignored: ParseException) { + } + } + return null + } + + companion object { + private const val KEY_STACK_TRACE = "stacktrace" + private const val KEY_EXCEPTION_CLASS = "class" + private const val KEY_TIMESTAMP = "timestamp" + } + } + + companion object { + private const val FILE_LIMITER_DATA = "ACRA-limiter.json" + + @JvmStatic + fun load(context: Context): LimiterData { + return try { + LimiterData(StreamReader(context.openFileInput(FILE_LIMITER_DATA)).read()) + } catch (e: FileNotFoundException) { + LimiterData() + } catch (e: IOException) { + warn(e) { "Failed to load LimiterData" } + LimiterData() + } catch (e: JSONException) { + warn(e) { "Failed to load LimiterData" } + LimiterData() + } + } + + private fun jsonArrayToList(array: JSONArray?): Array { + val list: MutableList = ArrayList() + if (array != null) { + val length = array.length() + for (i in 0 until length) { + list.add(array.opt(i).toString()) + } + } + return list.toTypedArray() + } + } +} \ No newline at end of file diff --git a/acra-limiter/src/main/java/org/acra/config/LimitingReportAdministrator.java b/acra-limiter/src/main/java/org/acra/config/LimitingReportAdministrator.java deleted file mode 100644 index 61dd8a079d..0000000000 --- a/acra-limiter/src/main/java/org/acra/config/LimitingReportAdministrator.java +++ /dev/null @@ -1,147 +0,0 @@ -/* - * Copyright (c) 2017 - * - * 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.config; - -import android.content.Context; -import android.os.Build; -import android.os.Handler; -import android.os.Looper; -import androidx.annotation.NonNull; -import android.widget.Toast; -import com.google.auto.service.AutoService; -import org.acra.ACRA; -import org.acra.builder.ReportBuilder; -import org.acra.data.CrashReportData; -import org.acra.file.ReportLocator; -import org.acra.plugins.HasConfigPlugin; -import org.acra.util.ToastSender; -import org.json.JSONException; - -import java.io.IOException; -import java.util.Calendar; -import java.util.List; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; - -import static org.acra.ACRA.LOG_TAG; - -/** - * @author F43nd1r - * @since 26.10.2017 - */ -@AutoService(ReportingAdministrator.class) -public class LimitingReportAdministrator extends HasConfigPlugin implements ReportingAdministrator { - - public LimitingReportAdministrator() { - super(LimiterConfiguration.class); - } - - @Override - public boolean shouldStartCollecting(@NonNull Context context, @NonNull CoreConfiguration config, @NonNull ReportBuilder reportBuilder) { - try { - final LimiterConfiguration limiterConfiguration = ConfigUtils.getPluginConfiguration(config, LimiterConfiguration.class); - final ReportLocator reportLocator = new ReportLocator(context); - if (reportLocator.getApprovedReports().length + reportLocator.getUnapprovedReports().length >= limiterConfiguration.getFailedReportLimit()) { - if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "Reached failedReportLimit, not collecting"); - return false; - } - final List reportMetadata = loadLimiterData(context, limiterConfiguration).getReportMetadata(); - if (reportMetadata.size() >= limiterConfiguration.getOverallLimit()) { - if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "Reached overallLimit, not collecting"); - return false; - } - } catch (IOException e) { - ACRA.log.w(LOG_TAG, "Failed to load LimiterData", e); - } - return true; - } - - @Override - public boolean shouldSendReport(@NonNull Context context, @NonNull CoreConfiguration config, @NonNull CrashReportData crashReportData) { - try { - final LimiterConfiguration limiterConfiguration = ConfigUtils.getPluginConfiguration(config, LimiterConfiguration.class); - final LimiterData limiterData = loadLimiterData(context, limiterConfiguration); - int sameTrace = 0; - int sameClass = 0; - final LimiterData.ReportMetadata m = new LimiterData.ReportMetadata(crashReportData); - for (LimiterData.ReportMetadata metadata : limiterData.getReportMetadata()) { - if (m.getStacktrace().equals(metadata.getStacktrace())) { - sameTrace++; - } - if (m.getExceptionClass().equals(metadata.getExceptionClass())) { - sameClass++; - } - } - if (sameTrace >= limiterConfiguration.getStacktraceLimit()) { - if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "Reached stacktraceLimit, not sending"); - return false; - } - if (sameClass >= limiterConfiguration.getExceptionClassLimit()) { - if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "Reached exceptionClassLimit, not sending"); - return false; - } - limiterData.getReportMetadata().add(m); - limiterData.store(context); - } catch (IOException | JSONException e) { - ACRA.log.w(LOG_TAG, "Failed to load LimiterData", e); - } - return true; - } - - @Override - public void notifyReportDropped(@NonNull final Context context, @NonNull final CoreConfiguration config) { - final LimiterConfiguration limiterConfiguration = ConfigUtils.getPluginConfiguration(config, LimiterConfiguration.class); - if (!limiterConfiguration.getIgnoredCrashToast().isEmpty()) { - final Future future = Executors.newSingleThreadExecutor().submit(() -> { - Looper.prepare(); - ToastSender.sendToast(context, limiterConfiguration.getIgnoredCrashToast(), Toast.LENGTH_LONG); - final Looper looper = Looper.myLooper(); - if (looper != null) { - new Handler(looper).postDelayed(() -> { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { - looper.quitSafely(); - } else { - looper.quit(); - } - }, 4000); - Looper.loop(); - } - }); - while (!future.isDone()) { - try { - future.get(); - } catch (InterruptedException ignored) { - } catch (ExecutionException e) { - //ReportInteraction crashed, so ignore it - break; - } - } - } - } - - @NonNull - private LimiterData loadLimiterData(@NonNull Context context, @NonNull LimiterConfiguration limiterConfiguration) throws IOException { - final LimiterData limiterData = LimiterData.load(context); - final Calendar keepAfter = Calendar.getInstance(); - keepAfter.add(Calendar.MINUTE, (int) -limiterConfiguration.getPeriodUnit().toMinutes(limiterConfiguration.getPeriod())); - if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "purging reports older than " + keepAfter.getTime().toString()); - limiterData.purgeOldData(keepAfter); - limiterData.store(context); - return limiterData; - } -} diff --git a/acra-limiter/src/main/java/org/acra/config/LimitingReportAdministrator.kt b/acra-limiter/src/main/java/org/acra/config/LimitingReportAdministrator.kt new file mode 100644 index 0000000000..5a7c57ac4b --- /dev/null +++ b/acra-limiter/src/main/java/org/acra/config/LimitingReportAdministrator.kt @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2017 + * + * 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.config + +import android.content.Context +import android.os.Build +import android.os.Handler +import android.os.Looper +import android.widget.Toast +import com.google.auto.service.AutoService +import org.acra.builder.ReportBuilder +import org.acra.config.ConfigUtils.getPluginConfiguration +import org.acra.config.LimiterData.Companion.load +import org.acra.config.LimiterData.ReportMetadata +import org.acra.data.CrashReportData +import org.acra.file.ReportLocator +import org.acra.log.debug +import org.acra.log.warn +import org.acra.plugins.HasConfigPlugin +import org.acra.util.ToastSender.sendToast +import org.json.JSONException +import java.io.IOException +import java.util.* +import java.util.concurrent.ExecutionException +import java.util.concurrent.Executors + +/** + * @author F43nd1r + * @since 26.10.2017 + */ +@AutoService(ReportingAdministrator::class) +class LimitingReportAdministrator : HasConfigPlugin(LimiterConfiguration::class.java), ReportingAdministrator { + override fun shouldStartCollecting(context: Context, config: CoreConfiguration, reportBuilder: ReportBuilder): Boolean { + try { + val limiterConfiguration = getPluginConfiguration(config, LimiterConfiguration::class.java) + val reportLocator = ReportLocator(context) + if (reportLocator.approvedReports.size + reportLocator.unapprovedReports.size >= limiterConfiguration.failedReportLimit) { + debug { "Reached failedReportLimit, not collecting" } + return false + } + val reportMetadata = loadLimiterData(context, limiterConfiguration).reportMetadata + if (reportMetadata.size >= limiterConfiguration.overallLimit) { + debug { "Reached overallLimit, not collecting" } + return false + } + } catch (e: IOException) { + warn(e) { "Failed to load LimiterData" } + } + return true + } + + override fun shouldSendReport(context: Context, config: CoreConfiguration, crashReportData: CrashReportData): Boolean { + try { + val limiterConfiguration = getPluginConfiguration(config, LimiterConfiguration::class.java) + val limiterData = loadLimiterData(context, limiterConfiguration) + var sameTrace = 0 + var sameClass = 0 + val m = ReportMetadata(crashReportData) + for (metadata in limiterData.reportMetadata) { + if (m.stacktrace == metadata.stacktrace) { + sameTrace++ + } + if (m.exceptionClass == metadata.exceptionClass) { + sameClass++ + } + } + if (sameTrace >= limiterConfiguration.stacktraceLimit) { + debug { "Reached stacktraceLimit, not sending" } + return false + } + if (sameClass >= limiterConfiguration.exceptionClassLimit) { + debug { "Reached exceptionClassLimit, not sending" } + return false + } + limiterData.reportMetadata.add(m) + limiterData.store(context) + } catch (e: IOException) { + warn(e) { "Failed to load LimiterData" } + } catch (e: JSONException) { + warn(e) { "Failed to load LimiterData" } + } + return true + } + + override fun notifyReportDropped(context: Context, config: CoreConfiguration) { + val limiterConfiguration = getPluginConfiguration(config, LimiterConfiguration::class.java) + if (limiterConfiguration.ignoredCrashToast.isNotEmpty()) { + val future = Executors.newSingleThreadExecutor().submit { + Looper.prepare() + sendToast(context, limiterConfiguration.ignoredCrashToast, Toast.LENGTH_LONG) + val looper = Looper.myLooper() + if (looper != null) { + Handler(looper).postDelayed({ + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { + looper.quitSafely() + } else { + looper.quit() + } + }, 4000) + Looper.loop() + } + } + while (!future.isDone) { + try { + future.get() + } catch (ignored: InterruptedException) { + } catch (e: ExecutionException) { + //ReportInteraction crashed, so ignore it + break + } + } + } + } + + @Throws(IOException::class) + private fun loadLimiterData(context: Context, limiterConfiguration: LimiterConfiguration): LimiterData { + val limiterData = load(context) + val keepAfter = Calendar.getInstance() + keepAfter.add(Calendar.MINUTE, (-limiterConfiguration.periodUnit.toMinutes(limiterConfiguration.period)).toInt()) + debug { "purging reports older than ${keepAfter.time}" } + limiterData.purgeOldData(keepAfter) + limiterData.store(context) + return limiterData + } +} \ No newline at end of file diff --git a/acra-limiter/src/main/java/org/acra/startup/LimiterStartupProcessor.java b/acra-limiter/src/main/java/org/acra/startup/LimiterStartupProcessor.java deleted file mode 100644 index b1cd1bf0d1..0000000000 --- a/acra-limiter/src/main/java/org/acra/startup/LimiterStartupProcessor.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * 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.startup; - -import android.content.Context; -import android.content.SharedPreferences; -import android.content.pm.PackageInfo; -import androidx.annotation.NonNull; -import com.google.auto.service.AutoService; -import org.acra.ACRA; -import org.acra.config.ConfigUtils; -import org.acra.config.CoreConfiguration; -import org.acra.config.LimiterConfiguration; -import org.acra.config.LimiterData; -import org.acra.plugins.HasConfigPlugin; -import org.acra.prefs.SharedPreferencesFactory; -import org.acra.util.PackageManagerWrapper; -import org.jetbrains.annotations.NotNull; - -import java.io.IOException; -import java.util.List; - -import static org.acra.ACRA.LOG_TAG; - -/** - * @author lukas - * @since 15.09.18 - */ -@AutoService(StartupProcessor.class) -public class LimiterStartupProcessor extends HasConfigPlugin implements StartupProcessor { - public LimiterStartupProcessor() { - super(LimiterConfiguration.class); - } - - @Override - public void processReports(@NotNull Context context, @NotNull CoreConfiguration config, @NotNull List reports) { - final LimiterConfiguration limiterConfiguration = ConfigUtils.getPluginConfiguration(config, LimiterConfiguration.class); - if(limiterConfiguration.getDeleteReportsOnAppUpdate() || limiterConfiguration.getResetLimitsOnAppUpdate()) { - final SharedPreferences prefs = new SharedPreferencesFactory(context, config).create(); - final long lastVersionNr = prefs.getInt(ACRA.PREF_LAST_VERSION_NR, 0); - final int appVersion = getAppVersion(context); - - if (appVersion > lastVersionNr) { - if(limiterConfiguration.getDeleteReportsOnAppUpdate()) { - prefs.edit().putInt(ACRA.PREF_LAST_VERSION_NR, appVersion).apply(); - for (Report report : reports) { - report.delete(); - } - } - if(limiterConfiguration.getResetLimitsOnAppUpdate()) { - try { - new LimiterData().store(context); - } catch (IOException e) { - ACRA.log.w(LOG_TAG, "Failed to reset LimiterData", e); - } - } - } - } - } - - /** - * @return app version or 0 if PackageInfo was not available. - */ - private int getAppVersion(@NonNull Context context) { - final PackageManagerWrapper packageManagerWrapper = new PackageManagerWrapper(context); - final PackageInfo packageInfo = packageManagerWrapper.getPackageInfo(); - return (packageInfo == null) ? 0 : packageInfo.versionCode; - } -} diff --git a/acra-limiter/src/main/java/org/acra/startup/LimiterStartupProcessor.kt b/acra-limiter/src/main/java/org/acra/startup/LimiterStartupProcessor.kt new file mode 100644 index 0000000000..1713271a74 --- /dev/null +++ b/acra-limiter/src/main/java/org/acra/startup/LimiterStartupProcessor.kt @@ -0,0 +1,67 @@ +/* + * 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.startup + +import android.content.Context +import com.google.auto.service.AutoService +import org.acra.ACRA +import org.acra.config.ConfigUtils.getPluginConfiguration +import org.acra.config.CoreConfiguration +import org.acra.config.LimiterConfiguration +import org.acra.config.LimiterData +import org.acra.log.warn +import org.acra.plugins.HasConfigPlugin +import org.acra.prefs.SharedPreferencesFactory +import org.acra.util.PackageManagerWrapper +import java.io.IOException + +/** + * @author lukas + * @since 15.09.18 + */ +@AutoService(StartupProcessor::class) +class LimiterStartupProcessor : HasConfigPlugin(LimiterConfiguration::class.java), StartupProcessor { + override fun processReports(context: Context, config: CoreConfiguration, reports: List) { + val limiterConfiguration = getPluginConfiguration(config, LimiterConfiguration::class.java) + if (limiterConfiguration.deleteReportsOnAppUpdate || limiterConfiguration.resetLimitsOnAppUpdate) { + val prefs = SharedPreferencesFactory(context, config).create() + val lastVersionNr = prefs.getInt(ACRA.PREF_LAST_VERSION_NR, 0).toLong() + val appVersion = getAppVersion(context) + if (appVersion > lastVersionNr) { + if (limiterConfiguration.deleteReportsOnAppUpdate) { + prefs.edit().putInt(ACRA.PREF_LAST_VERSION_NR, appVersion).apply() + for (report in reports) { + report.delete = true + } + } + if (limiterConfiguration.resetLimitsOnAppUpdate) { + try { + LimiterData().store(context) + } catch (e: IOException) { + warn(e) { "Failed to reset LimiterData" } + } + } + } + } + } + + /** + * @return app version or 0 if PackageInfo was not available. + */ + private fun getAppVersion(context: Context): Int { + return PackageManagerWrapper(context).getPackageInfo()?.versionCode ?: 0 + } +} \ No newline at end of file From 022676944ecd4020ad0ff3bd816ac020afa65730 Mon Sep 17 00:00:00 2001 From: f43nd1r Date: Mon, 23 Nov 2020 03:28:21 +0100 Subject: [PATCH 09/16] mail to kotlin --- .../org/acra/sender/EmailIntentSender.java | 263 ------------------ .../java/org/acra/sender/EmailIntentSender.kt | 252 +++++++++++++++++ .../acra/sender/EmailIntentSenderFactory.java | 40 --- .../acra/sender/EmailIntentSenderFactory.kt | 30 ++ 4 files changed, 282 insertions(+), 303 deletions(-) delete mode 100644 acra-mail/src/main/java/org/acra/sender/EmailIntentSender.java create mode 100644 acra-mail/src/main/java/org/acra/sender/EmailIntentSender.kt delete mode 100644 acra-mail/src/main/java/org/acra/sender/EmailIntentSenderFactory.java create mode 100644 acra-mail/src/main/java/org/acra/sender/EmailIntentSenderFactory.kt diff --git a/acra-mail/src/main/java/org/acra/sender/EmailIntentSender.java b/acra-mail/src/main/java/org/acra/sender/EmailIntentSender.java deleted file mode 100644 index b1b871fa6c..0000000000 --- a/acra-mail/src/main/java/org/acra/sender/EmailIntentSender.java +++ /dev/null @@ -1,263 +0,0 @@ -/* - * Copyright (c) 2017 - * - * 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.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.net.Uri; -import android.os.Build; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import org.acra.ACRA; -import org.acra.ACRAConstants; -import org.acra.attachment.AcraContentProvider; -import org.acra.attachment.DefaultAttachmentProvider; -import org.acra.config.ConfigUtils; -import org.acra.config.CoreConfiguration; -import org.acra.config.MailSenderConfiguration; -import org.acra.data.CrashReportData; -import org.acra.util.IOUtils; -import org.acra.util.InstanceCreator; - -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -import static org.acra.ACRA.LOG_TAG; - -/** - * Send reports through an email intent. - *

- * The user will be asked to chose his preferred email client if no default is set. Included report fields can be defined using - * {@link org.acra.annotation.AcraCore#reportContent()}. Crash receiving mailbox has to be - * defined with {@link org.acra.annotation.AcraMailSender#mailTo()}. - */ -@SuppressWarnings("WeakerAccess") -public class EmailIntentSender implements ReportSender { - public static final String DEFAULT_REPORT_FILENAME = "ACRA-report" + ACRAConstants.REPORTFILE_EXTENSION; - - private final CoreConfiguration config; - private final MailSenderConfiguration mailConfig; - - public EmailIntentSender(@NonNull CoreConfiguration config) { - this.config = config; - this.mailConfig = ConfigUtils.getPluginConfiguration(config, MailSenderConfiguration.class); - } - - @Override - public void send(@NonNull Context context, @NonNull CrashReportData errorContent) throws ReportSenderException { - final PackageManager pm = context.getPackageManager(); - - final String subject = buildSubject(context); - final String reportText; - try { - reportText = config.getReportFormat().toFormattedString(errorContent, config.getReportContent(), "\n", "\n\t", false); - } catch (Exception e) { - throw new ReportSenderException("Failed to convert Report to text", e); - } - final String bodyPrefix = mailConfig.getBody(); - final String body = !bodyPrefix.isEmpty() ? bodyPrefix + "\n" + reportText : reportText; - final ArrayList attachments = new ArrayList<>(); - final boolean contentAttached = fillAttachmentList(context, reportText, attachments); - - //we have to resolve with sendto, because send is supported by non-email apps - final Intent resolveIntent = buildResolveIntent(subject, body); - final ComponentName resolveActivity = resolveIntent.resolveActivity(pm); - if (resolveActivity != null) { - if (attachments.size() == 0) { - //no attachments, send directly - context.startActivity(resolveIntent); - } else { - final Intent attachmentIntent = buildAttachmentIntent(subject, contentAttached ? bodyPrefix : body, attachments); - final List initialIntents = buildInitialIntents(pm, resolveIntent, attachmentIntent); - final String packageName = getPackageName(resolveActivity, initialIntents); - attachmentIntent.setPackage(packageName); - if (packageName == null) { - //let user choose email client - for (Intent intent : initialIntents) { - grantPermission(context, intent, intent.getPackage(), attachments); - } - showChooser(context, initialIntents); - } else if (attachmentIntent.resolveActivity(pm) != null) { - //use default email client - grantPermission(context, attachmentIntent, packageName, attachments); - context.startActivity(attachmentIntent); - } else { - ACRA.log.w(LOG_TAG, "No email client supporting attachments found. Attachments will be ignored"); - context.startActivity(resolveIntent); - } - } - } else { - throw new ReportSenderException("No email client found"); - } - } - - @Override - public boolean requiresForeground() { - return Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q; - } - - /** - * Finds the package name of the default email client supporting attachments - * - * @param resolveActivity the resolved activity - * @param initialIntents a list of intents to be used when - * @return package name of the default email client, or null if more than one app match - */ - @Nullable - private String getPackageName(@NonNull ComponentName resolveActivity, @NonNull List initialIntents) { - String packageName = resolveActivity.getPackageName(); - if (packageName.equals("android")) { - //multiple activities support the intent and no default is set - if (initialIntents.size() > 1) { - packageName = null; - } else if (initialIntents.size() == 1) { - //only one of them supports attachments, use that one - packageName = initialIntents.get(0).getPackage(); - } - } - return packageName; - } - - /** - * Builds an email intent with attachments - * - * @param subject the message subject - * @param body the message body - * @param attachments the attachments - * @return email intent - */ - @NonNull - protected Intent buildAttachmentIntent(@NonNull String subject, @Nullable String body, @NonNull ArrayList attachments) { - final Intent intent = new Intent(Intent.ACTION_SEND_MULTIPLE); - intent.putExtra(Intent.EXTRA_EMAIL, new String[]{mailConfig.getMailTo()}); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - intent.putExtra(Intent.EXTRA_SUBJECT, subject); - intent.setType("message/rfc822"); - intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, attachments); - intent.putExtra(Intent.EXTRA_TEXT, body); - return intent; - } - - /** - * Builds an intent used to resolve email clients and to send reports without attachments or as fallback if no attachments are supported - * - * @param subject the message subject - * @param body the message body - * @return email intent - */ - @NonNull - protected Intent buildResolveIntent(@NonNull String subject, @NonNull String body) { - final Intent intent = new Intent(Intent.ACTION_SENDTO); - intent.setData(Uri.fromParts("mailto", mailConfig.getMailTo(), null)); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - intent.putExtra(Intent.EXTRA_SUBJECT, subject); - intent.putExtra(Intent.EXTRA_TEXT, body); - return intent; - } - - @NonNull - private List buildInitialIntents(@NonNull PackageManager pm, @NonNull Intent resolveIntent, @NonNull Intent emailIntent) { - final List resolveInfoList = pm.queryIntentActivities(resolveIntent, PackageManager.MATCH_DEFAULT_ONLY); - final List initialIntents = new ArrayList<>(); - for (ResolveInfo info : resolveInfoList) { - final Intent packageSpecificIntent = new Intent(emailIntent); - packageSpecificIntent.setPackage(info.activityInfo.packageName); - if (packageSpecificIntent.resolveActivity(pm) != null) { - initialIntents.add(packageSpecificIntent); - } - } - return initialIntents; - } - - private void showChooser(@NonNull Context context, @NonNull List initialIntents) { - final Intent chooser = new Intent(Intent.ACTION_CHOOSER); - chooser.putExtra(Intent.EXTRA_INTENT, initialIntents.remove(0)); - chooser.putExtra(Intent.EXTRA_INITIAL_INTENTS, initialIntents.toArray(new Intent[0])); - chooser.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - context.startActivity(chooser); - } - - private void grantPermission(@NonNull Context context, @NonNull Intent intent, String packageName, @NonNull List attachments) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - } else { - //flags do not work on extras prior to lollipop, so we have to grant read permissions manually - for (Uri uri : attachments) { - context.grantUriPermission(packageName, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION); - } - } - } - - /** - * Creates the message subject - * - * @param context a context - * @return the message subject - */ - @NonNull - protected String buildSubject(@NonNull Context context) { - final String subject = mailConfig.getSubject(); - if (!subject.isEmpty()) { - return subject; - } - return context.getPackageName() + " Crash Report"; - } - - /** - * Adds all attachment uris into the given list - * - * @param context a context - * @param reportText the report content - * @param attachments the target list - * @return if the attachments contain the content - */ - protected boolean fillAttachmentList(@NonNull Context context, @NonNull String reportText, @NonNull List attachments) { - final InstanceCreator instanceCreator = new InstanceCreator(); - attachments.addAll(instanceCreator.create(config.getAttachmentUriProvider(), DefaultAttachmentProvider::new).getAttachments(context, config)); - if (mailConfig.getReportAsFile()) { - final Uri report = createAttachmentFromString(context, mailConfig.getReportFileName(), reportText); - if (report != null) { - attachments.add(report); - return true; - } - } - return false; - } - - /** - * Creates a temporary file with the given content and name, to be used as an email attachment - * - * @param context a context - * @param name the name - * @param content the content - * @return a content uri for the file - */ - @Nullable - protected Uri createAttachmentFromString(@NonNull Context context, @NonNull String name, @NonNull String content) { - final File cache = new File(context.getCacheDir(), name); - try { - IOUtils.writeStringToFile(cache, content); - return AcraContentProvider.getUriForFile(context, cache); - } catch (IOException ignored) { - } - return null; - } -} diff --git a/acra-mail/src/main/java/org/acra/sender/EmailIntentSender.kt b/acra-mail/src/main/java/org/acra/sender/EmailIntentSender.kt new file mode 100644 index 0000000000..6d231fc3f1 --- /dev/null +++ b/acra-mail/src/main/java/org/acra/sender/EmailIntentSender.kt @@ -0,0 +1,252 @@ +/* + * Copyright (c) 2017 + * + * 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.ComponentName +import android.content.Context +import android.content.Intent +import android.content.pm.PackageManager +import android.net.Uri +import android.os.Build +import org.acra.ACRAConstants +import org.acra.attachment.AcraContentProvider +import org.acra.attachment.DefaultAttachmentProvider +import org.acra.config.ConfigUtils.getPluginConfiguration +import org.acra.config.CoreConfiguration +import org.acra.config.MailSenderConfiguration +import org.acra.data.CrashReportData +import org.acra.log.warn +import org.acra.sender.ReportSenderException +import org.acra.util.IOUtils.writeStringToFile +import org.acra.util.InstanceCreator +import java.io.File +import java.io.IOException +import java.util.* + +/** + * Send reports through an email intent. + * + * + * The user will be asked to chose his preferred email client if no default is set. Included report fields can be defined using + * [org.acra.annotation.AcraCore.reportContent]. Crash receiving mailbox has to be + * defined with [org.acra.annotation.AcraMailSender.mailTo]. + */ +@Suppress("MemberVisibilityCanBePrivate") +open class EmailIntentSender(private val config: CoreConfiguration) : ReportSender { + private val mailConfig: MailSenderConfiguration = getPluginConfiguration(config, MailSenderConfiguration::class.java) + + @Throws(ReportSenderException::class) + override fun send(context: Context, errorContent: CrashReportData) { + val pm = context.packageManager + val subject = buildSubject(context) + val reportText: String + reportText = try { + config.reportFormat.toFormattedString(errorContent, config.reportContent, "\n", "\n\t", false) + } catch (e: Exception) { + throw ReportSenderException("Failed to convert Report to text", e) + } + val bodyPrefix: String = mailConfig.body + val body = if (bodyPrefix.isNotEmpty()) """ + |$bodyPrefix + |$reportText + """.trimMargin() else reportText + val attachments = ArrayList() + val contentAttached = fillAttachmentList(context, reportText, attachments) + + //we have to resolve with sendto, because send is supported by non-email apps + val resolveIntent = buildResolveIntent(subject, body) + val resolveActivity = resolveIntent.resolveActivity(pm) + if (resolveActivity != null) { + if (attachments.size == 0) { + //no attachments, send directly + context.startActivity(resolveIntent) + } else { + val attachmentIntent = buildAttachmentIntent(subject, if (contentAttached) bodyPrefix else body, attachments) + val initialIntents = buildInitialIntents(pm, resolveIntent, attachmentIntent) + val packageName = getPackageName(resolveActivity, initialIntents) + attachmentIntent.setPackage(packageName) + when { + packageName == null -> { + //let user choose email client + for (intent in initialIntents) { + grantPermission(context, intent, intent.getPackage(), attachments) + } + showChooser(context, initialIntents.toMutableList()) + } + attachmentIntent.resolveActivity(pm) != null -> { + //use default email client + grantPermission(context, attachmentIntent, packageName, attachments) + context.startActivity(attachmentIntent) + } + else -> { + warn { "No email client supporting attachments found. Attachments will be ignored" } + context.startActivity(resolveIntent) + } + } + } + } else { + throw ReportSenderException("No email client found") + } + } + + override fun requiresForeground(): Boolean { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q + } + + /** + * Finds the package name of the default email client supporting attachments + * + * @param resolveActivity the resolved activity + * @param initialIntents a list of intents to be used when + * @return package name of the default email client, or null if more than one app match + */ + private fun getPackageName(resolveActivity: ComponentName, initialIntents: List): String? { + var packageName: String? = resolveActivity.packageName + if (packageName == "android") { + //multiple activities support the intent and no default is set + if (initialIntents.size > 1) { + packageName = null + } else if (initialIntents.size == 1) { + //only one of them supports attachments, use that one + packageName = initialIntents[0].getPackage() + } + } + return packageName + } + + /** + * Builds an email intent with attachments + * + * @param subject the message subject + * @param body the message body + * @param attachments the attachments + * @return email intent + */ + protected fun buildAttachmentIntent(subject: String, body: String?, attachments: ArrayList): Intent { + val intent = Intent(Intent.ACTION_SEND_MULTIPLE) + intent.putExtra(Intent.EXTRA_EMAIL, arrayOf(mailConfig.mailTo)) + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + intent.putExtra(Intent.EXTRA_SUBJECT, subject) + intent.type = "message/rfc822" + intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, attachments) + intent.putExtra(Intent.EXTRA_TEXT, body) + return intent + } + + /** + * Builds an intent used to resolve email clients and to send reports without attachments or as fallback if no attachments are supported + * + * @param subject the message subject + * @param body the message body + * @return email intent + */ + protected fun buildResolveIntent(subject: String, body: String): Intent { + val intent = Intent(Intent.ACTION_SENDTO) + intent.data = Uri.fromParts("mailto", mailConfig.mailTo, null) + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + intent.putExtra(Intent.EXTRA_SUBJECT, subject) + intent.putExtra(Intent.EXTRA_TEXT, body) + return intent + } + + private fun buildInitialIntents(pm: PackageManager, resolveIntent: Intent, emailIntent: Intent): List { + val resolveInfoList = pm.queryIntentActivities(resolveIntent, PackageManager.MATCH_DEFAULT_ONLY) + val initialIntents: MutableList = ArrayList() + for (info in resolveInfoList) { + val packageSpecificIntent = Intent(emailIntent) + packageSpecificIntent.setPackage(info.activityInfo.packageName) + if (packageSpecificIntent.resolveActivity(pm) != null) { + initialIntents.add(packageSpecificIntent) + } + } + return initialIntents + } + + private fun showChooser(context: Context, initialIntents: MutableList) { + val chooser = Intent(Intent.ACTION_CHOOSER) + chooser.putExtra(Intent.EXTRA_INTENT, initialIntents.removeAt(0)) + chooser.putExtra(Intent.EXTRA_INITIAL_INTENTS, initialIntents.toTypedArray()) + chooser.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + context.startActivity(chooser) + } + + private fun grantPermission(context: Context, intent: Intent, packageName: String?, attachments: List) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + } else { + //flags do not work on extras prior to lollipop, so we have to grant read permissions manually + for (uri in attachments) { + context.grantUriPermission(packageName, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION) + } + } + } + + /** + * Creates the message subject + * + * @param context a context + * @return the message subject + */ + protected fun buildSubject(context: Context): String { + val subject: String = mailConfig.subject + return if (subject.isNotEmpty()) { + subject + } else context.packageName + " Crash Report" + } + + /** + * Adds all attachment uris into the given list + * + * @param context a context + * @param reportText the report content + * @param attachments the target list + * @return if the attachments contain the content + */ + protected fun fillAttachmentList(context: Context, reportText: String, attachments: MutableList): Boolean { + attachments.addAll(InstanceCreator.create(config.attachmentUriProvider) { DefaultAttachmentProvider() }.getAttachments(context, config)) + if (mailConfig.reportAsFile) { + val report = createAttachmentFromString(context, mailConfig.reportFileName, reportText) + if (report != null) { + attachments.add(report) + return true + } + } + return false + } + + /** + * Creates a temporary file with the given content and name, to be used as an email attachment + * + * @param context a context + * @param name the name + * @param content the content + * @return a content uri for the file + */ + protected fun createAttachmentFromString(context: Context, name: String, content: String): Uri? { + val cache = File(context.cacheDir, name) + try { + writeStringToFile(cache, content) + return AcraContentProvider.getUriForFile(context, cache) + } catch (ignored: IOException) { + } + return null + } + + companion object { + const val DEFAULT_REPORT_FILENAME = "ACRA-report" + ACRAConstants.REPORTFILE_EXTENSION + } + +} \ No newline at end of file diff --git a/acra-mail/src/main/java/org/acra/sender/EmailIntentSenderFactory.java b/acra-mail/src/main/java/org/acra/sender/EmailIntentSenderFactory.java deleted file mode 100644 index dd1af2e259..0000000000 --- a/acra-mail/src/main/java/org/acra/sender/EmailIntentSenderFactory.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) 2017 - * - * 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 androidx.annotation.NonNull; -import com.google.auto.service.AutoService; -import org.acra.config.CoreConfiguration; -import org.acra.config.MailSenderConfiguration; -import org.acra.plugins.HasConfigPlugin; - -/** - * Constructs an {@link EmailIntentSender}. - */ -@AutoService(ReportSenderFactory.class) -public final class EmailIntentSenderFactory extends HasConfigPlugin implements ReportSenderFactory { - public EmailIntentSenderFactory() { - super(MailSenderConfiguration.class); - } - - @NonNull - @Override - public ReportSender create(@NonNull Context context, @NonNull CoreConfiguration config) { - return new EmailIntentSender(config); - } -} diff --git a/acra-mail/src/main/java/org/acra/sender/EmailIntentSenderFactory.kt b/acra-mail/src/main/java/org/acra/sender/EmailIntentSenderFactory.kt new file mode 100644 index 0000000000..4b140a637e --- /dev/null +++ b/acra-mail/src/main/java/org/acra/sender/EmailIntentSenderFactory.kt @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2017 + * + * 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 com.google.auto.service.AutoService +import org.acra.config.CoreConfiguration +import org.acra.config.MailSenderConfiguration +import org.acra.plugins.HasConfigPlugin + +/** + * Constructs an [EmailIntentSender]. + */ +@AutoService(ReportSenderFactory::class) +class EmailIntentSenderFactory : HasConfigPlugin(MailSenderConfiguration::class.java), ReportSenderFactory { + override fun create(context: Context, config: CoreConfiguration): ReportSender = EmailIntentSender(config) +} \ No newline at end of file From fa95e87f683ab8f2463046426f9959a1b7242860 Mon Sep 17 00:00:00 2001 From: f43nd1r Date: Mon, 23 Nov 2020 03:32:25 +0100 Subject: [PATCH 10/16] notification to kotlin --- .../interaction/NotificationInteraction.java | 159 ------------------ .../interaction/NotificationInteraction.kt | 144 ++++++++++++++++ .../NotificationBroadcastReceiver.java | 92 ---------- .../receiver/NotificationBroadcastReceiver.kt | 84 +++++++++ 4 files changed, 228 insertions(+), 251 deletions(-) delete mode 100644 acra-notification/src/main/java/org/acra/interaction/NotificationInteraction.java create mode 100644 acra-notification/src/main/java/org/acra/interaction/NotificationInteraction.kt delete mode 100644 acra-notification/src/main/java/org/acra/receiver/NotificationBroadcastReceiver.java create mode 100644 acra-notification/src/main/java/org/acra/receiver/NotificationBroadcastReceiver.kt diff --git a/acra-notification/src/main/java/org/acra/interaction/NotificationInteraction.java b/acra-notification/src/main/java/org/acra/interaction/NotificationInteraction.java deleted file mode 100644 index 0ace032e79..0000000000 --- a/acra-notification/src/main/java/org/acra/interaction/NotificationInteraction.java +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright (c) 2017 - * - * 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.interaction; - -import android.app.NotificationChannel; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.os.Build; -import android.widget.RemoteViews; - -import androidx.annotation.NonNull; -import androidx.core.app.NotificationCompat; -import androidx.core.app.RemoteInput; - -import com.google.auto.service.AutoService; - -import org.acra.ACRA; -import org.acra.config.ConfigUtils; -import org.acra.config.CoreConfiguration; -import org.acra.config.NotificationConfiguration; -import org.acra.notification.R; -import org.acra.plugins.HasConfigPlugin; -import org.acra.prefs.SharedPreferencesFactory; -import org.acra.receiver.NotificationBroadcastReceiver; -import org.acra.sender.LegacySenderService; - -import java.io.File; - -/** - * @author F43nd1r - * @since 15.09.2017 - */ - -@AutoService(ReportInteraction.class) -public class NotificationInteraction extends HasConfigPlugin implements ReportInteraction { - public static final String INTENT_ACTION_SEND = "org.acra.intent.send"; - public static final String INTENT_ACTION_DISCARD = "org.acra.intent.discard"; - public static final String KEY_COMMENT = "comment"; - public static final String EXTRA_REPORT_FILE = "REPORT_FILE"; - public static final int NOTIFICATION_ID = 666; - private static final int ACTION_SEND = 667; - private static final int ACTION_DISCARD = 668; - private static final String CHANNEL = "ACRA"; - - public NotificationInteraction() { - super(NotificationConfiguration.class); - } - - @Override - public boolean performInteraction(@NonNull Context context, @NonNull CoreConfiguration config, @NonNull File reportFile) { - final SharedPreferences prefs = new SharedPreferencesFactory(context, config).create(); - if (prefs.getBoolean(ACRA.PREF_ALWAYS_ACCEPT, false)) { - return true; - } - final NotificationManager notificationManager = ((NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE)); - //can't post notifications - if (notificationManager == null) { - return true; - } - final NotificationConfiguration notificationConfig = ConfigUtils.getPluginConfiguration(config, NotificationConfiguration.class); - //We have to create a channel on Oreo+, because notifications without one aren't allowed - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - final NotificationChannel channel = new NotificationChannel(CHANNEL, notificationConfig.getChannelName(), notificationConfig.getResChannelImportance()); - channel.setSound(null, null); - if (!notificationConfig.getChannelDescription().isEmpty()) { - channel.setDescription(notificationConfig.getChannelDescription()); - } - notificationManager.createNotificationChannel(channel); - } - //configure base notification - final NotificationCompat.Builder notification = new NotificationCompat.Builder(context, CHANNEL) - .setWhen(System.currentTimeMillis()) - .setContentTitle(notificationConfig.getTitle()) - .setContentText(notificationConfig.getText()) - .setSmallIcon(notificationConfig.getResIcon()) - .setPriority(NotificationCompat.PRIORITY_HIGH); - //add ticker if set - if (!notificationConfig.getTickerText().isEmpty()) { - notification.setTicker(notificationConfig.getTickerText()); - } - final PendingIntent sendIntent = getSendIntent(context, config, reportFile); - final PendingIntent discardIntent = getDiscardIntent(context); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && !notificationConfig.getSendWithCommentButtonText().isEmpty()) { - final RemoteInput.Builder remoteInput = new RemoteInput.Builder(KEY_COMMENT); - if (!notificationConfig.getCommentPrompt().isEmpty()) { - remoteInput.setLabel(notificationConfig.getCommentPrompt()); - } - notification.addAction(new NotificationCompat.Action.Builder(notificationConfig.getResSendWithCommentButtonIcon(), notificationConfig.getSendWithCommentButtonText(), sendIntent) - .addRemoteInput(remoteInput.build()).build()); - } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { - final RemoteViews bigView = getBigView(context, notificationConfig); - notification.addAction(notificationConfig.getResSendButtonIcon(), notificationConfig.getSendButtonText(), sendIntent) - .addAction(notificationConfig.getResDiscardButtonIcon(), notificationConfig.getDiscardButtonText(), discardIntent) - .setCustomContentView(getSmallView(context, notificationConfig, sendIntent, discardIntent)) - .setCustomBigContentView(bigView) - .setCustomHeadsUpContentView(bigView) - .setStyle(new NotificationCompat.DecoratedCustomViewStyle()); - } - //On old devices we have no notification buttons, so we have to set the intent to the only possible interaction: click - if (notificationConfig.getSendOnClick() || Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { - notification.setContentIntent(sendIntent); - } - notification.setDeleteIntent(discardIntent); - notificationManager.notify(NOTIFICATION_ID, notification.build()); - return false; - } - - private PendingIntent getSendIntent(@NonNull Context context, @NonNull CoreConfiguration config, @NonNull File reportFile) { - final Intent intent = new Intent(context, NotificationBroadcastReceiver.class); - intent.setAction(INTENT_ACTION_SEND); - intent.putExtra(LegacySenderService.EXTRA_ACRA_CONFIG, config); - intent.putExtra(EXTRA_REPORT_FILE, reportFile); - return PendingIntent.getBroadcast(context, ACTION_SEND, intent, PendingIntent.FLAG_UPDATE_CURRENT); - } - - private PendingIntent getDiscardIntent(@NonNull Context context) { - final Intent intent = new Intent(context, NotificationBroadcastReceiver.class); - intent.setAction(INTENT_ACTION_DISCARD); - return PendingIntent.getBroadcast(context, ACTION_DISCARD, intent, PendingIntent.FLAG_UPDATE_CURRENT); - } - - @NonNull - private RemoteViews getSmallView(@NonNull Context context, @NonNull NotificationConfiguration notificationConfig, @NonNull PendingIntent sendIntent, @NonNull PendingIntent discardIntent) { - final RemoteViews view = new RemoteViews(context.getPackageName(), R.layout.notification_small); - view.setTextViewText(R.id.text, notificationConfig.getText()); - view.setTextViewText(R.id.title, notificationConfig.getTitle()); - view.setImageViewResource(R.id.button_send, notificationConfig.getResSendButtonIcon()); - view.setImageViewResource(R.id.button_discard, notificationConfig.getResDiscardButtonIcon()); - view.setOnClickPendingIntent(R.id.button_send, sendIntent); - view.setOnClickPendingIntent(R.id.button_discard, discardIntent); - return view; - } - - @NonNull - private RemoteViews getBigView(@NonNull Context context, @NonNull NotificationConfiguration notificationConfig) { - final RemoteViews view = new RemoteViews(context.getPackageName(), R.layout.notification_big); - view.setTextViewText(R.id.text, notificationConfig.getText()); - view.setTextViewText(R.id.title, notificationConfig.getTitle()); - return view; - } -} diff --git a/acra-notification/src/main/java/org/acra/interaction/NotificationInteraction.kt b/acra-notification/src/main/java/org/acra/interaction/NotificationInteraction.kt new file mode 100644 index 0000000000..d0bc4615a8 --- /dev/null +++ b/acra-notification/src/main/java/org/acra/interaction/NotificationInteraction.kt @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2017 + * + * 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.interaction + +import android.app.NotificationChannel +import android.app.NotificationManager +import android.app.PendingIntent +import android.content.Context +import android.content.Intent +import android.os.Build +import android.widget.RemoteViews +import androidx.core.app.NotificationCompat +import androidx.core.app.RemoteInput +import com.google.auto.service.AutoService +import org.acra.ACRA +import org.acra.config.ConfigUtils.getPluginConfiguration +import org.acra.config.CoreConfiguration +import org.acra.config.NotificationConfiguration +import org.acra.notification.R +import org.acra.plugins.HasConfigPlugin +import org.acra.prefs.SharedPreferencesFactory +import org.acra.receiver.NotificationBroadcastReceiver +import org.acra.sender.LegacySenderService +import java.io.File + +/** + * @author F43nd1r + * @since 15.09.2017 + */ +@AutoService(ReportInteraction::class) +class NotificationInteraction : HasConfigPlugin(NotificationConfiguration::class.java), ReportInteraction { + override fun performInteraction(context: Context, config: CoreConfiguration, reportFile: File): Boolean { + val prefs = SharedPreferencesFactory(context, config).create() + if (prefs.getBoolean(ACRA.PREF_ALWAYS_ACCEPT, false)) { + return true + } + val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as? NotificationManager ?: return true + //can't post notifications + val notificationConfig = getPluginConfiguration(config, NotificationConfiguration::class.java) + //We have to create a channel on Oreo+, because notifications without one aren't allowed + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val channel = NotificationChannel(CHANNEL, notificationConfig.channelName, notificationConfig.resChannelImportance) + channel.setSound(null, null) + if (notificationConfig.channelDescription.isNotEmpty()) { + channel.description = notificationConfig.channelDescription + } + notificationManager.createNotificationChannel(channel) + } + //configure base notification + val notification = NotificationCompat.Builder(context, CHANNEL) + .setWhen(System.currentTimeMillis()) + .setContentTitle(notificationConfig.title) + .setContentText(notificationConfig.text) + .setSmallIcon(notificationConfig.resIcon) + .setPriority(NotificationCompat.PRIORITY_HIGH) + //add ticker if set + if (notificationConfig.tickerText.isNotEmpty()) { + notification.setTicker(notificationConfig.tickerText) + } + val sendIntent = getSendIntent(context, config, reportFile) + val discardIntent = getDiscardIntent(context) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && notificationConfig.sendWithCommentButtonText.isNotEmpty()) { + val remoteInput = RemoteInput.Builder(KEY_COMMENT) + if (notificationConfig.commentPrompt.isNotEmpty()) { + remoteInput.setLabel(notificationConfig.commentPrompt) + } + notification.addAction( + NotificationCompat.Action.Builder(notificationConfig.resSendWithCommentButtonIcon, notificationConfig.sendWithCommentButtonText, sendIntent) + .addRemoteInput(remoteInput.build()).build()) + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + val bigView = getBigView(context, notificationConfig) + notification.addAction(notificationConfig.resSendButtonIcon, notificationConfig.sendButtonText, sendIntent) + .addAction(notificationConfig.resDiscardButtonIcon, notificationConfig.discardButtonText, discardIntent) + .setCustomContentView(getSmallView(context, notificationConfig, sendIntent, discardIntent)) + .setCustomBigContentView(bigView) + .setCustomHeadsUpContentView(bigView) + .setStyle(NotificationCompat.DecoratedCustomViewStyle()) + } + //On old devices we have no notification buttons, so we have to set the intent to the only possible interaction: click + if (notificationConfig.sendOnClick || Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { + notification.setContentIntent(sendIntent) + } + notification.setDeleteIntent(discardIntent) + notificationManager.notify(NOTIFICATION_ID, notification.build()) + return false + } + + private fun getSendIntent(context: Context, config: CoreConfiguration, reportFile: File): PendingIntent { + val intent = Intent(context, NotificationBroadcastReceiver::class.java) + intent.action = INTENT_ACTION_SEND + intent.putExtra(LegacySenderService.EXTRA_ACRA_CONFIG, config) + intent.putExtra(EXTRA_REPORT_FILE, reportFile) + return PendingIntent.getBroadcast(context, ACTION_SEND, intent, PendingIntent.FLAG_UPDATE_CURRENT) + } + + private fun getDiscardIntent(context: Context): PendingIntent { + val intent = Intent(context, NotificationBroadcastReceiver::class.java) + intent.action = INTENT_ACTION_DISCARD + return PendingIntent.getBroadcast(context, ACTION_DISCARD, intent, PendingIntent.FLAG_UPDATE_CURRENT) + } + + private fun getSmallView(context: Context, notificationConfig: NotificationConfiguration, sendIntent: PendingIntent, discardIntent: PendingIntent): RemoteViews { + val view = RemoteViews(context.packageName, R.layout.notification_small) + view.setTextViewText(R.id.text, notificationConfig.text) + view.setTextViewText(R.id.title, notificationConfig.title) + view.setImageViewResource(R.id.button_send, notificationConfig.resSendButtonIcon) + view.setImageViewResource(R.id.button_discard, notificationConfig.resDiscardButtonIcon) + view.setOnClickPendingIntent(R.id.button_send, sendIntent) + view.setOnClickPendingIntent(R.id.button_discard, discardIntent) + return view + } + + private fun getBigView(context: Context, notificationConfig: NotificationConfiguration): RemoteViews { + val view = RemoteViews(context.packageName, R.layout.notification_big) + view.setTextViewText(R.id.text, notificationConfig.text) + view.setTextViewText(R.id.title, notificationConfig.title) + return view + } + + companion object { + const val INTENT_ACTION_SEND = "org.acra.intent.send" + const val INTENT_ACTION_DISCARD = "org.acra.intent.discard" + const val KEY_COMMENT = "comment" + const val EXTRA_REPORT_FILE = "REPORT_FILE" + const val NOTIFICATION_ID = 666 + private const val ACTION_SEND = 667 + private const val ACTION_DISCARD = 668 + private const val CHANNEL = "ACRA" + } +} \ No newline at end of file diff --git a/acra-notification/src/main/java/org/acra/receiver/NotificationBroadcastReceiver.java b/acra-notification/src/main/java/org/acra/receiver/NotificationBroadcastReceiver.java deleted file mode 100644 index 337b709f57..0000000000 --- a/acra-notification/src/main/java/org/acra/receiver/NotificationBroadcastReceiver.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (c) 2017 - * - * 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.receiver; - -import android.app.NotificationManager; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.os.Bundle; -import androidx.annotation.NonNull; -import androidx.core.app.RemoteInput; -import org.acra.ACRA; -import org.acra.config.CoreConfiguration; -import org.acra.data.CrashReportData; -import org.acra.file.BulkReportDeleter; -import org.acra.file.CrashReportPersister; -import org.acra.interaction.NotificationInteraction; -import org.acra.scheduler.SchedulerStarter; -import org.acra.sender.LegacySenderService; -import org.acra.util.SystemServices; -import org.json.JSONException; - -import java.io.File; -import java.io.IOException; - -import static org.acra.ACRA.LOG_TAG; -import static org.acra.ReportField.USER_COMMENT; - -/** - * @author F43nd1r - * @since 15.09.2017 - */ - -public class NotificationBroadcastReceiver extends BroadcastReceiver { - @Override - public void onReceive(@NonNull Context context, @NonNull Intent intent) { - try { - final NotificationManager notificationManager = SystemServices.getNotificationManager(context); - notificationManager.cancel(NotificationInteraction.NOTIFICATION_ID); - if (intent.getAction() != null) { - switch (intent.getAction()) { - case NotificationInteraction.INTENT_ACTION_SEND: - final Object reportFileObject = intent.getSerializableExtra(NotificationInteraction.EXTRA_REPORT_FILE); - final Object configObject = intent.getSerializableExtra(LegacySenderService.EXTRA_ACRA_CONFIG); - if (configObject instanceof CoreConfiguration && reportFileObject instanceof File) { - final CoreConfiguration config = (CoreConfiguration) configObject; - final File reportFile = (File) reportFileObject; - //Grab user comment from notification intent - final Bundle remoteInput = RemoteInput.getResultsFromIntent(intent); - if (remoteInput != null) { - final CharSequence comment = remoteInput.getCharSequence(NotificationInteraction.KEY_COMMENT); - if (comment != null && !"".equals(comment.toString())) { - final CrashReportPersister persister = new CrashReportPersister(); - try { - if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "Add user comment to " + reportFile); - final CrashReportData crashData = persister.load(reportFile); - crashData.put(USER_COMMENT, comment.toString()); - persister.store(crashData, reportFile); - } catch (@NonNull IOException | JSONException e) { - ACRA.log.w(LOG_TAG, "User comment not added: ", e); - } - } - } - new SchedulerStarter(context, config).scheduleReports(reportFile, false); - } - break; - case NotificationInteraction.INTENT_ACTION_DISCARD: - if (ACRA.DEV_LOGGING) ACRA.log.d(ACRA.LOG_TAG, "Discarding reports"); - new BulkReportDeleter(context).deleteReports(false, 0); - break; - } - } - - } catch (Exception t) { - ACRA.log.e(LOG_TAG, "Failed to handle notification action", t); - } - } -} diff --git a/acra-notification/src/main/java/org/acra/receiver/NotificationBroadcastReceiver.kt b/acra-notification/src/main/java/org/acra/receiver/NotificationBroadcastReceiver.kt new file mode 100644 index 0000000000..57b8bd2119 --- /dev/null +++ b/acra-notification/src/main/java/org/acra/receiver/NotificationBroadcastReceiver.kt @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2017 + * + * 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.receiver + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import androidx.core.app.RemoteInput +import org.acra.ReportField +import org.acra.config.CoreConfiguration +import org.acra.file.BulkReportDeleter +import org.acra.file.CrashReportPersister +import org.acra.interaction.NotificationInteraction +import org.acra.log.debug +import org.acra.log.error +import org.acra.log.warn +import org.acra.scheduler.SchedulerStarter +import org.acra.sender.LegacySenderService +import org.acra.util.SystemServices.getNotificationManager +import org.json.JSONException +import java.io.File +import java.io.IOException + +/** + * @author F43nd1r + * @since 15.09.2017 + */ +class NotificationBroadcastReceiver : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + try { + val notificationManager = getNotificationManager(context) + notificationManager.cancel(NotificationInteraction.NOTIFICATION_ID) + if (intent.action != null) { + when (intent.action) { + NotificationInteraction.INTENT_ACTION_SEND -> { + val reportFileObject: Any? = intent.getSerializableExtra(NotificationInteraction.EXTRA_REPORT_FILE) + val configObject: Any? = intent.getSerializableExtra(LegacySenderService.EXTRA_ACRA_CONFIG) + if (configObject is CoreConfiguration && reportFileObject is File) { + val reportFile = reportFileObject + //Grab user comment from notification intent + val remoteInput = RemoteInput.getResultsFromIntent(intent) + if (remoteInput != null) { + val comment = remoteInput.getCharSequence(NotificationInteraction.KEY_COMMENT) + if (comment != null && "" != comment.toString()) { + val persister = CrashReportPersister() + try { + debug { "Add user comment to $reportFile" } + val crashData = persister.load(reportFile) + crashData.put(ReportField.USER_COMMENT, comment.toString()) + persister.store(crashData, reportFile) + } catch (e: IOException) { + warn(e) { "User comment not added: " } + } catch (e: JSONException) { + warn(e) { "User comment not added: " } + } + } + } + SchedulerStarter(context, configObject).scheduleReports(reportFile, false) + } + } + NotificationInteraction.INTENT_ACTION_DISCARD -> { + debug { "Discarding reports" } + BulkReportDeleter(context).deleteReports(false, 0) + } + } + } + } catch (t: Exception) { + error(t) { "Failed to handle notification action" } + } + } +} \ No newline at end of file From 95305400236b7b1c92d8cd3a37419eb21659278c Mon Sep 17 00:00:00 2001 From: f43nd1r Date: Mon, 23 Nov 2020 03:33:33 +0100 Subject: [PATCH 11/16] toast to kotlin --- .../acra/interaction/ToastInteraction.java | 74 ------------------- .../org/acra/interaction/ToastInteraction.kt | 62 ++++++++++++++++ 2 files changed, 62 insertions(+), 74 deletions(-) delete mode 100644 acra-toast/src/main/java/org/acra/interaction/ToastInteraction.java create mode 100644 acra-toast/src/main/java/org/acra/interaction/ToastInteraction.kt diff --git a/acra-toast/src/main/java/org/acra/interaction/ToastInteraction.java b/acra-toast/src/main/java/org/acra/interaction/ToastInteraction.java deleted file mode 100644 index 7d83a6d165..0000000000 --- a/acra-toast/src/main/java/org/acra/interaction/ToastInteraction.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (c) 2017 the ACRA team - * - * 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.interaction; - -import android.content.Context; -import android.os.Build; -import android.os.Handler; -import android.os.Looper; -import android.widget.Toast; -import androidx.annotation.NonNull; -import com.google.auto.service.AutoService; -import org.acra.config.ConfigUtils; -import org.acra.config.CoreConfiguration; -import org.acra.config.ToastConfiguration; -import org.acra.plugins.HasConfigPlugin; -import org.acra.util.ToastSender; - -import java.io.File; - -/** - * @author F43nd1r - * @since 04.06.2017 - */ -@AutoService(ReportInteraction.class) -public class ToastInteraction extends HasConfigPlugin implements ReportInteraction { - - public ToastInteraction() { - super(ToastConfiguration.class); - } - - @Override - public boolean performInteraction(@NonNull Context context, @NonNull CoreConfiguration config, @NonNull File reportFile) { - Looper.prepare(); - ToastConfiguration pluginConfig = ConfigUtils.getPluginConfiguration(config, ToastConfiguration.class); - ToastSender.sendToast(context, pluginConfig.getText(), pluginConfig.getLength()); - final Looper looper = Looper.myLooper(); - if (looper != null) { - new Handler(looper).postDelayed(() -> { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { - looper.quitSafely(); - } else { - looper.quit(); - } - }, getLengthInMs(pluginConfig.getLength()) + 100); - Looper.loop(); - } - return true; - } - - private int getLengthInMs(int toastDuration) { - switch (toastDuration) { - case Toast.LENGTH_LONG: - return 3500; - case Toast.LENGTH_SHORT: - return 2000; - default: - return 0; - } - } -} diff --git a/acra-toast/src/main/java/org/acra/interaction/ToastInteraction.kt b/acra-toast/src/main/java/org/acra/interaction/ToastInteraction.kt new file mode 100644 index 0000000000..ba3739eb49 --- /dev/null +++ b/acra-toast/src/main/java/org/acra/interaction/ToastInteraction.kt @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2017 the ACRA team + * + * 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.interaction + +import android.content.Context +import android.os.Build +import android.os.Handler +import android.os.Looper +import android.widget.Toast +import com.google.auto.service.AutoService +import org.acra.config.ConfigUtils.getPluginConfiguration +import org.acra.config.CoreConfiguration +import org.acra.config.ToastConfiguration +import org.acra.plugins.HasConfigPlugin +import org.acra.util.ToastSender.sendToast +import java.io.File + +/** + * @author F43nd1r + * @since 04.06.2017 + */ +@AutoService(ReportInteraction::class) +class ToastInteraction : HasConfigPlugin(ToastConfiguration::class.java), ReportInteraction { + override fun performInteraction(context: Context, config: CoreConfiguration, reportFile: File): Boolean { + Looper.prepare() + val pluginConfig = getPluginConfiguration(config, ToastConfiguration::class.java) + sendToast(context, pluginConfig.text, pluginConfig.length) + val looper = Looper.myLooper() + if (looper != null) { + Handler(looper).postDelayed({ + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { + looper.quitSafely() + } else { + looper.quit() + } + }, getLengthInMs(pluginConfig.length) + 100.toLong()) + Looper.loop() + } + return true + } + + private fun getLengthInMs(toastDuration: Int): Int { + return when (toastDuration) { + Toast.LENGTH_LONG -> 3500 + Toast.LENGTH_SHORT -> 2000 + else -> 0 + } + } +} \ No newline at end of file From 8ab8abe54692a0716b5cbb03b616536302ed48be Mon Sep 17 00:00:00 2001 From: f43nd1r Date: Mon, 23 Nov 2020 03:53:43 +0100 Subject: [PATCH 12/16] jvmdefault --- acra-core/src/main/java/org/acra/collector/Collector.kt | 1 + .../main/java/org/acra/config/ReportingAdministrator.kt | 6 ++++++ acra-core/src/main/java/org/acra/plugins/Plugin.kt | 1 + .../src/main/java/org/acra/reporter/ErrorReporterImpl.kt | 2 +- .../src/main/java/org/acra/processor/util/Types.kt | 9 --------- buildSrc/src/main/kotlin/acra-android-library.gradle.kts | 9 +++++++++ 6 files changed, 18 insertions(+), 10 deletions(-) diff --git a/acra-core/src/main/java/org/acra/collector/Collector.kt b/acra-core/src/main/java/org/acra/collector/Collector.kt index f3658a15dc..ba2f418edd 100644 --- a/acra-core/src/main/java/org/acra/collector/Collector.kt +++ b/acra-core/src/main/java/org/acra/collector/Collector.kt @@ -41,6 +41,7 @@ interface Collector : Plugin { /** * @return when this collector should be called compared to other collectors */ + @JvmDefault val order: Order get() = Order.NORMAL diff --git a/acra-core/src/main/java/org/acra/config/ReportingAdministrator.kt b/acra-core/src/main/java/org/acra/config/ReportingAdministrator.kt index 7b023e0d3b..641bb1c038 100644 --- a/acra-core/src/main/java/org/acra/config/ReportingAdministrator.kt +++ b/acra-core/src/main/java/org/acra/config/ReportingAdministrator.kt @@ -36,6 +36,7 @@ interface ReportingAdministrator : Plugin { * @param reportBuilder the reportBuilder for the report about to be collected * @return if this report should be collected */ + @JvmDefault fun shouldStartCollecting(context: Context, config: CoreConfiguration, reportBuilder: ReportBuilder): Boolean { return true } @@ -48,6 +49,7 @@ interface ReportingAdministrator : Plugin { * @param crashReportData the collected report * @return if this report should be sent */ + @JvmDefault fun shouldSendReport(context: Context, config: CoreConfiguration, crashReportData: CrashReportData): Boolean { return true } @@ -58,7 +60,10 @@ interface ReportingAdministrator : Plugin { * @param context a context * @param config the current config */ + @JvmDefault fun notifyReportDropped(context: Context, config: CoreConfiguration) {} + + @JvmDefault fun shouldFinishActivity(context: Context, config: CoreConfiguration, lastActivityManager: LastActivityManager): Boolean { return true } @@ -72,6 +77,7 @@ interface ReportingAdministrator : Plugin { * @param crashReportData the collected report * @return if the application should be killed */ + @JvmDefault fun shouldKillApplication(context: Context, config: CoreConfiguration, reportBuilder: ReportBuilder, crashReportData: CrashReportData?): Boolean { return true diff --git a/acra-core/src/main/java/org/acra/plugins/Plugin.kt b/acra-core/src/main/java/org/acra/plugins/Plugin.kt index e68e941b3c..7fdd5ec1a1 100644 --- a/acra-core/src/main/java/org/acra/plugins/Plugin.kt +++ b/acra-core/src/main/java/org/acra/plugins/Plugin.kt @@ -30,6 +30,7 @@ interface Plugin { * @param config the current config * @return if this instance should be called */ + @JvmDefault fun enabled(config: CoreConfiguration): Boolean { return true } diff --git a/acra-core/src/main/java/org/acra/reporter/ErrorReporterImpl.kt b/acra-core/src/main/java/org/acra/reporter/ErrorReporterImpl.kt index ef0c3353a1..3e691c4f8d 100644 --- a/acra-core/src/main/java/org/acra/reporter/ErrorReporterImpl.kt +++ b/acra-core/src/main/java/org/acra/reporter/ErrorReporterImpl.kt @@ -87,7 +87,7 @@ class ErrorReporterImpl(private val context: Application, config: CoreConfigurat * java.lang.Thread.UncaughtExceptionHandler#uncaughtException(java.lang * .Thread, java.lang.Throwable) */ - override fun uncaughtException(t: Thread?, e: Throwable) { + override fun uncaughtException(t: Thread, e: Throwable) { // If we're not enabled then just pass the Exception on to the defaultExceptionHandler. if (!reportExecutor.isEnabled) { reportExecutor.handReportToDefaultExceptionHandler(t, e) diff --git a/annotationprocessor/src/main/java/org/acra/processor/util/Types.kt b/annotationprocessor/src/main/java/org/acra/processor/util/Types.kt index 77f3f5a305..98b5b370b6 100644 --- a/annotationprocessor/src/main/java/org/acra/processor/util/Types.kt +++ b/annotationprocessor/src/main/java/org/acra/processor/util/Types.kt @@ -35,9 +35,6 @@ import org.acra.annotation.Instantiatable import org.acra.annotation.NonEmpty import org.acra.annotation.PreBuild import org.acra.annotation.Transform -import org.acra.collections.ImmutableList -import org.acra.collections.ImmutableMap -import org.acra.collections.ImmutableSet import java.lang.Deprecated import javax.annotation.processing.ProcessingEnvironment import javax.lang.model.element.ExecutableElement @@ -52,12 +49,6 @@ import kotlin.String * @since 11.01.2018 */ object Types { - val IMMUTABLE_MAP: ClassName = ImmutableMap::class.asClassName() - val IMMUTABLE_SET: ClassName = ImmutableSet::class.asClassName() - val IMMUTABLE_LIST: ClassName = ImmutableList::class.asClassName() - val MAP: ClassName = MutableMap::class.asClassName() - val SET: ClassName = MutableSet::class.asClassName() - val LIST: ClassName = MutableList::class.asClassName() @JvmField val STRING: ClassName = ClassName("kotlin", "String") @JvmField diff --git a/buildSrc/src/main/kotlin/acra-android-library.gradle.kts b/buildSrc/src/main/kotlin/acra-android-library.gradle.kts index 2ec77863ab..718d15896f 100644 --- a/buildSrc/src/main/kotlin/acra-android-library.gradle.kts +++ b/buildSrc/src/main/kotlin/acra-android-library.gradle.kts @@ -1,3 +1,5 @@ +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + /* * Copyright (c) 2020 * @@ -74,6 +76,13 @@ dependencies { implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1") } +tasks.withType { + kotlinOptions { + jvmTarget = "1.8" + freeCompilerArgs = listOf("-Xjvm-default=enable") + } +} + tasks.register("sourcesJar") { group = "documentation" from(android.sourceSets["main"].java.srcDirs) From 48bbd4cd6b9f69e9f0557e01ca5f1394aab5bf81 Mon Sep 17 00:00:00 2001 From: f43nd1r Date: Mon, 23 Nov 2020 14:54:17 +0100 Subject: [PATCH 13/16] fix service files --- annotationprocessor/build.gradle.kts | 2 ++ .../acra/processor/AcraAnnotationProcessor.kt | 8 ++++- .../acra/processor/creator/ClassCreator.kt | 10 ++++-- .../creator/ServiceResourceCreator.kt | 33 +++++++++++++++++++ .../acra/processor/element/AnnotationField.kt | 10 +++--- .../acra/processor/util/ToCodeBlockVisitor.kt | 3 +- 6 files changed, 55 insertions(+), 11 deletions(-) create mode 100644 annotationprocessor/src/main/java/org/acra/processor/creator/ServiceResourceCreator.kt diff --git a/annotationprocessor/build.gradle.kts b/annotationprocessor/build.gradle.kts index beb47a8ab5..3a71c3f4d0 100644 --- a/annotationprocessor/build.gradle.kts +++ b/annotationprocessor/build.gradle.kts @@ -24,4 +24,6 @@ dependencies { implementation("org.apache.commons:commons-text:1.6") implementation(project(":annotations")) implementation(project(":acra-javacore")) + val autoServiceVersion: String by project + implementation("com.google.auto.service:auto-service:$autoServiceVersion") } \ No newline at end of file diff --git a/annotationprocessor/src/main/java/org/acra/processor/AcraAnnotationProcessor.kt b/annotationprocessor/src/main/java/org/acra/processor/AcraAnnotationProcessor.kt index 5e438ebfd1..01c8c1c2e6 100644 --- a/annotationprocessor/src/main/java/org/acra/processor/AcraAnnotationProcessor.kt +++ b/annotationprocessor/src/main/java/org/acra/processor/AcraAnnotationProcessor.kt @@ -19,6 +19,7 @@ import com.google.auto.common.MoreElements import com.google.auto.service.AutoService import org.acra.annotation.Configuration import org.acra.processor.creator.ClassCreator +import org.acra.processor.creator.ServiceResourceCreator import org.acra.processor.util.Types import javax.annotation.processing.AbstractProcessor import javax.annotation.processing.Processor @@ -36,6 +37,8 @@ import javax.tools.Diagnostic @AutoService(Processor::class) @SupportedSourceVersion(SourceVersion.RELEASE_8) class AcraAnnotationProcessor : AbstractProcessor() { + private val serviceResourceCreator = ServiceResourceCreator() + override fun getSupportedAnnotationTypes(): Set { return Types.MARKER_ANNOTATIONS.map { it.reflectionName() }.toSet() } @@ -44,12 +47,15 @@ class AcraAnnotationProcessor : AbstractProcessor() { try { for (e in roundEnv.getElementsAnnotatedWith(Configuration::class.java)) { if (e.kind == ElementKind.ANNOTATION_TYPE) { - ClassCreator(MoreElements.asType(e), e.getAnnotation(Configuration::class.java), processingEnv).createClasses() + ClassCreator(MoreElements.asType(e), e.getAnnotation(Configuration::class.java), processingEnv, serviceResourceCreator).createClasses() } else { processingEnv.messager.printMessage(Diagnostic.Kind.ERROR, String.format("%s is only supported on %s", Configuration::class.java.name, ElementKind.ANNOTATION_TYPE.name), e) } } + if(roundEnv.processingOver()) { + serviceResourceCreator.generateResources(processingEnv) + } } catch (e: Throwable) { e.printStackTrace() processingEnv.messager.printMessage(Diagnostic.Kind.ERROR, "Failed to generate acra classes") diff --git a/annotationprocessor/src/main/java/org/acra/processor/creator/ClassCreator.kt b/annotationprocessor/src/main/java/org/acra/processor/creator/ClassCreator.kt index 4023d7fe25..046dbc11ed 100644 --- a/annotationprocessor/src/main/java/org/acra/processor/creator/ClassCreator.kt +++ b/annotationprocessor/src/main/java/org/acra/processor/creator/ClassCreator.kt @@ -47,7 +47,8 @@ import javax.lang.model.type.MirroredTypeException * @author F43nd1r * @since 04.06.2017 */ -class ClassCreator(private val baseAnnotation: TypeElement, private val configuration: Configuration, private val processingEnv: ProcessingEnvironment) { +class ClassCreator(private val baseAnnotation: TypeElement, private val configuration: Configuration, private val processingEnv: ProcessingEnvironment, + private val serviceResourceCreator: ServiceResourceCreator) { private val baseName = baseAnnotation.simpleName.toString().replace("Acra", "") private val configName: String = baseName + "Configuration" private val builderName: String = configName + "Builder" @@ -84,7 +85,10 @@ class ClassCreator(private val baseAnnotation: TypeElement, private val configur elements.filterIsInstance().forEach { it.addToBuilder(classBuilder, builder, primaryConstructor) } classBuilder.primaryConstructor(constructor.addCode(primaryConstructor.build()).build()) val build = BuildMethodCreator(Types.getOnlyMethod(processingEnv, ConfigurationBuilder::class.java.name), ClassName(Strings.PACKAGE, configName)) - elements.stream().filter { obj: Element? -> ValidatedElement::class.java.isInstance(obj) }.map { obj: Element? -> ValidatedElement::class.java.cast(obj) }.forEach { element: ValidatedElement -> element.addToBuildMethod(build) } + elements.stream() + .filter { obj: Element? -> ValidatedElement::class.java.isInstance(obj) } + .map { obj: Element? -> ValidatedElement::class.java.cast(obj) } + .forEach { element: ValidatedElement -> element.addToBuildMethod(build) } classBuilder.addFunction(build.build()) Strings.writeClass(processingEnv, classBuilder.build()) } @@ -108,11 +112,11 @@ class ClassCreator(private val baseAnnotation: TypeElement, private val configur .addOriginatingElement(baseAnnotation) .addModifiers(KModifier.PUBLIC) .addSuperinterface(configurationBuilderFactory) - .addAnnotation(AnnotationSpec.builder(AutoService::class.java).addMember("value = [%T::class]", configurationBuilderFactory).build()) .addFunction(Types.overriding(Types.getOnlyMethod(processingEnv, Strings.CONFIGURATION_BUILDER_FACTORY)) .addStatement("return %T(%L)", ClassName(Strings.PACKAGE, builderName), Strings.PARAM_0) .build()) .build()) + serviceResourceCreator.addService(configurationBuilderFactory.canonicalName, "${Strings.PACKAGE}.$factoryName") } private fun createExtensions() { diff --git a/annotationprocessor/src/main/java/org/acra/processor/creator/ServiceResourceCreator.kt b/annotationprocessor/src/main/java/org/acra/processor/creator/ServiceResourceCreator.kt new file mode 100644 index 0000000000..9ea00e8825 --- /dev/null +++ b/annotationprocessor/src/main/java/org/acra/processor/creator/ServiceResourceCreator.kt @@ -0,0 +1,33 @@ +package org.acra.processor.creator + +import java.io.IOException +import javax.annotation.processing.ProcessingEnvironment +import javax.tools.StandardLocation + +class ServiceResourceCreator { + private val services = mutableMapOf>() + + fun addService(interfaceName: String, className: String) { + (services[interfaceName] ?: mutableSetOf().also { services[interfaceName] = it }).add(className) + } + + fun generateResources(processingEnv: ProcessingEnvironment) { + services.forEach { (interfaceName, services) -> + val resourceFile = "META-INF/services/${interfaceName}" + val oldContent: Set = try { + processingEnv.filer.getResource(StandardLocation.CLASS_OUTPUT, "", resourceFile).openInputStream().bufferedReader().use { + it.readLines().toSet() + } + } catch (e: IOException) { + emptySet() + } + val content: Set = oldContent + services + if (content.size > oldContent.size) { + processingEnv.filer.createResource(StandardLocation.CLASS_OUTPUT, "", resourceFile) + .openOutputStream().bufferedWriter().use { + it.write(content.joinToString("\n")) + } + } + } + } +} \ No newline at end of file diff --git a/annotationprocessor/src/main/java/org/acra/processor/element/AnnotationField.kt b/annotationprocessor/src/main/java/org/acra/processor/element/AnnotationField.kt index 0b58b51156..1d1109141d 100644 --- a/annotationprocessor/src/main/java/org/acra/processor/element/AnnotationField.kt +++ b/annotationprocessor/src/main/java/org/acra/processor/element/AnnotationField.kt @@ -64,7 +64,7 @@ abstract class AnnotationField(override val name: String, override val type: Typ open class Normal(name: String, type: TypeName, annotations: Collection, markers: Collection, defaultValue: AnnotationValue?, javadoc: String) : AnnotationField(name, type, annotations, javadoc, markers, defaultValue) { public override fun addInitializer(constructor: CodeBlock.Builder) { - constructor.addStatement("%1L = %2L?.%1L ?: %3L", name, Strings.VAR_ANNOTATION, defaultValue?.accept(ToCodeBlockVisitor(type), null)) + constructor.addStatement("%1L = %2L?.%1L ?: %3L", name, Strings.VAR_ANNOTATION, defaultValue?.accept(ToCodeBlockVisitor(), null)) } override fun addToBuildMethod(method: BuildMethodCreator) { @@ -78,14 +78,14 @@ abstract class AnnotationField(override val name: String, override val type: Typ method.addInstantiatable(name) } if (hasMarker(Types.ANY_NON_DEFAULT)) { - method.addAnyNonDefault(name, defaultValue?.accept(ToCodeBlockVisitor(type), null) ?: CodeBlock.of("null")) + method.addAnyNonDefault(name, defaultValue?.accept(ToCodeBlockVisitor(), null) ?: CodeBlock.of("null")) } } } class Clazz(name: String, type: TypeName, annotations: Collection, markers: Collection, defaultValue: AnnotationValue?, javadoc: String) : Normal(name, type, annotations, markers, defaultValue, javadoc) { - public override fun addInitializer(constructor: CodeBlock.Builder) { - constructor.addStatement("%1L = %2L?.%1L?.java ?: %3L", name, Strings.VAR_ANNOTATION, defaultValue?.accept(ToCodeBlockVisitor(type), null)) + override fun addInitializer(constructor: CodeBlock.Builder) { + constructor.addStatement("%1L = %2L?.%1L?.java ?: %3L", name, Strings.VAR_ANNOTATION, defaultValue?.accept(ToCodeBlockVisitor(), null)) } } @@ -95,7 +95,7 @@ abstract class AnnotationField(override val name: String, override val type: Typ public override fun addInitializer(constructor: CodeBlock.Builder) { if(hasDefault) { - constructor.addStatement("%L = (%L?.%L.takeIf·{ it != 0 } ?: %L)?.let·{ %L.getString(it) } ?: \"\"", name, Strings.VAR_ANNOTATION, originalName, defaultValue, Strings.FIELD_CONTEXT) + constructor.addStatement("%L = %L.getString(%L?.%L.takeIf·{ it != 0 } ?: %L)", name, Strings.FIELD_CONTEXT, Strings.VAR_ANNOTATION, originalName, defaultValue) } else { constructor.addStatement("%L = %L?.%L.takeIf·{ it != 0 }?.let·{ %L.getString(it) } ?: \"\"", name, Strings.VAR_ANNOTATION, originalName, Strings.FIELD_CONTEXT) } diff --git a/annotationprocessor/src/main/java/org/acra/processor/util/ToCodeBlockVisitor.kt b/annotationprocessor/src/main/java/org/acra/processor/util/ToCodeBlockVisitor.kt index 69e14b887a..c13755f3d0 100644 --- a/annotationprocessor/src/main/java/org/acra/processor/util/ToCodeBlockVisitor.kt +++ b/annotationprocessor/src/main/java/org/acra/processor/util/ToCodeBlockVisitor.kt @@ -16,7 +16,6 @@ package org.acra.processor.util import com.squareup.kotlinpoet.CodeBlock -import com.squareup.kotlinpoet.TypeName import javax.lang.model.element.AnnotationValue import javax.lang.model.element.VariableElement import javax.lang.model.type.TypeMirror @@ -26,7 +25,7 @@ import javax.lang.model.util.SimpleAnnotationValueVisitor8 * @author F43nd1r * @since 12.01.2018 */ -class ToCodeBlockVisitor(private val type: TypeName) : SimpleAnnotationValueVisitor8() { +class ToCodeBlockVisitor : SimpleAnnotationValueVisitor8() { override fun defaultAction(o: Any, u: Unit?): CodeBlock { return CodeBlock.of("%L", o) } From bea91efa6193e370d2ffd025cab5217b763360eb Mon Sep 17 00:00:00 2001 From: f43nd1r Date: Mon, 23 Nov 2020 15:35:50 +0100 Subject: [PATCH 14/16] annotation to kotlin --- .../main/java/org/acra/processor/util/Types.kt | 8 -------- .../{AnyNonDefault.java => AnyNonDefault.kt} | 16 ++++------------ .../{BuilderMethod.java => BuilderMethod.kt} | 15 ++++----------- .../{Configuration.java => Configuration.kt} | 18 +++++------------- ...urationValue.java => ConfigurationValue.kt} | 15 ++++----------- .../{Instantiatable.java => Instantiatable.kt} | 15 ++++----------- .../annotation/{NonEmpty.java => NonEmpty.kt} | 16 ++++------------ .../annotation/{PreBuild.java => PreBuild.kt} | 15 ++++----------- .../{Transform.java => Transform.kt} | 17 ++++------------- 9 files changed, 33 insertions(+), 102 deletions(-) rename annotations/src/main/java/org/acra/annotation/{AnyNonDefault.java => AnyNonDefault.kt} (72%) rename annotations/src/main/java/org/acra/annotation/{BuilderMethod.java => BuilderMethod.kt} (69%) rename annotations/src/main/java/org/acra/annotation/{Configuration.java => Configuration.kt} (65%) rename annotations/src/main/java/org/acra/annotation/{ConfigurationValue.java => ConfigurationValue.kt} (68%) rename annotations/src/main/java/org/acra/annotation/{Instantiatable.java => Instantiatable.kt} (71%) rename annotations/src/main/java/org/acra/annotation/{NonEmpty.java => NonEmpty.kt} (71%) rename annotations/src/main/java/org/acra/annotation/{PreBuild.java => PreBuild.kt} (71%) rename annotations/src/main/java/org/acra/annotation/{Transform.java => Transform.kt} (70%) diff --git a/annotationprocessor/src/main/java/org/acra/processor/util/Types.kt b/annotationprocessor/src/main/java/org/acra/processor/util/Types.kt index 98b5b370b6..ff82d48acb 100644 --- a/annotationprocessor/src/main/java/org/acra/processor/util/Types.kt +++ b/annotationprocessor/src/main/java/org/acra/processor/util/Types.kt @@ -21,9 +21,6 @@ import com.squareup.kotlinpoet.ClassName import com.squareup.kotlinpoet.FunSpec import com.squareup.kotlinpoet.KModifier import com.squareup.kotlinpoet.ParameterSpec -import com.squareup.kotlinpoet.ParameterizedTypeName -import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy -import com.squareup.kotlinpoet.TypeName import com.squareup.kotlinpoet.asClassName import com.squareup.kotlinpoet.asTypeName import com.squareup.kotlinpoet.asTypeVariableName @@ -35,14 +32,11 @@ import org.acra.annotation.Instantiatable import org.acra.annotation.NonEmpty import org.acra.annotation.PreBuild import org.acra.annotation.Transform -import java.lang.Deprecated import javax.annotation.processing.ProcessingEnvironment import javax.lang.model.element.ExecutableElement import javax.lang.model.element.VariableElement import javax.lang.model.util.ElementFilter import javax.tools.Diagnostic -import kotlin.IllegalArgumentException -import kotlin.String /** * @author F43nd1r @@ -54,8 +48,6 @@ object Types { @JvmField val STRING_RES = AnnotationSpec.builder(StringRes::class.java).build() @JvmField - val DEPRECATED = AnnotationSpec.builder(kotlin.Deprecated::class.java).build() - @JvmField val ANY_NON_DEFAULT: ClassName = AnyNonDefault::class.asClassName() val BUILDER_METHOD: ClassName = BuilderMethod::class.asClassName() val CONFIGURATION: ClassName = Configuration::class.asClassName() diff --git a/annotations/src/main/java/org/acra/annotation/AnyNonDefault.java b/annotations/src/main/java/org/acra/annotation/AnyNonDefault.kt similarity index 72% rename from annotations/src/main/java/org/acra/annotation/AnyNonDefault.java rename to annotations/src/main/java/org/acra/annotation/AnyNonDefault.kt index 97c338800a..227266664c 100644 --- a/annotations/src/main/java/org/acra/annotation/AnyNonDefault.java +++ b/annotations/src/main/java/org/acra/annotation/AnyNonDefault.kt @@ -13,13 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -package org.acra.annotation; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; +package org.acra.annotation /** * Any of the annotation methods annotated with this has to be different from the default value for the configuration to be valid @@ -27,8 +21,6 @@ * @author F43nd1r * @since 03.06.2017 */ - -@Retention(RetentionPolicy.SOURCE) -@Target(ElementType.METHOD) -public @interface AnyNonDefault { -} +@Retention(AnnotationRetention.SOURCE) +@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER) +annotation class AnyNonDefault \ No newline at end of file diff --git a/annotations/src/main/java/org/acra/annotation/BuilderMethod.java b/annotations/src/main/java/org/acra/annotation/BuilderMethod.kt similarity index 69% rename from annotations/src/main/java/org/acra/annotation/BuilderMethod.java rename to annotations/src/main/java/org/acra/annotation/BuilderMethod.kt index d7376c92ad..cb7d66be74 100644 --- a/annotations/src/main/java/org/acra/annotation/BuilderMethod.java +++ b/annotations/src/main/java/org/acra/annotation/BuilderMethod.kt @@ -13,19 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -package org.acra.annotation; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; +package org.acra.annotation /** * @author F43nd1r * @since 10.01.2018 */ -@Retention(RetentionPolicy.SOURCE) -@Target(ElementType.METHOD) -public @interface BuilderMethod { -} +@Retention(AnnotationRetention.SOURCE) +@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER) +annotation class BuilderMethod \ No newline at end of file diff --git a/annotations/src/main/java/org/acra/annotation/Configuration.java b/annotations/src/main/java/org/acra/annotation/Configuration.kt similarity index 65% rename from annotations/src/main/java/org/acra/annotation/Configuration.java rename to annotations/src/main/java/org/acra/annotation/Configuration.kt index c57c1865e4..9cab456c8f 100644 --- a/annotations/src/main/java/org/acra/annotation/Configuration.java +++ b/annotations/src/main/java/org/acra/annotation/Configuration.kt @@ -13,13 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +package org.acra.annotation -package org.acra.annotation; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; +import kotlin.reflect.KClass /** * The annotation annotated with this will be used as basis for Configurations and their Builders @@ -27,10 +23,6 @@ * @author F43nd1r * @since 17.03.2017 */ - -@Retention(RetentionPolicy.SOURCE) -@Target(ElementType.ANNOTATION_TYPE) -public @interface Configuration { - Class baseBuilderClass() default Object.class; - boolean isPlugin() default true; -} +@Retention(AnnotationRetention.SOURCE) +@Target(AnnotationTarget.ANNOTATION_CLASS) +annotation class Configuration(val baseBuilderClass: KClass<*> = Any::class, val isPlugin: Boolean = true) \ No newline at end of file diff --git a/annotations/src/main/java/org/acra/annotation/ConfigurationValue.java b/annotations/src/main/java/org/acra/annotation/ConfigurationValue.kt similarity index 68% rename from annotations/src/main/java/org/acra/annotation/ConfigurationValue.java rename to annotations/src/main/java/org/acra/annotation/ConfigurationValue.kt index 0069b6dbee..3c4ba41fb5 100644 --- a/annotations/src/main/java/org/acra/annotation/ConfigurationValue.java +++ b/annotations/src/main/java/org/acra/annotation/ConfigurationValue.kt @@ -13,19 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -package org.acra.annotation; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; +package org.acra.annotation /** * @author F43nd1r * @since 10.01.2018 */ -@Retention(RetentionPolicy.SOURCE) -@Target(ElementType.METHOD) -public @interface ConfigurationValue { -} +@Retention(AnnotationRetention.SOURCE) +@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER) +annotation class ConfigurationValue \ No newline at end of file diff --git a/annotations/src/main/java/org/acra/annotation/Instantiatable.java b/annotations/src/main/java/org/acra/annotation/Instantiatable.kt similarity index 71% rename from annotations/src/main/java/org/acra/annotation/Instantiatable.java rename to annotations/src/main/java/org/acra/annotation/Instantiatable.kt index 60624d8e03..efba4a5b6d 100644 --- a/annotations/src/main/java/org/acra/annotation/Instantiatable.java +++ b/annotations/src/main/java/org/acra/annotation/Instantiatable.kt @@ -13,13 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -package org.acra.annotation; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; +package org.acra.annotation /** * All classes in annotation methods with this have to have a public, no-args constructor for the configuration to be valid @@ -27,7 +21,6 @@ * @author F43nd1r * @since 03.06.2017 */ -@Retention(RetentionPolicy.SOURCE) -@Target({ElementType.METHOD, ElementType.PARAMETER}) -public @interface Instantiatable { -} +@Retention(AnnotationRetention.SOURCE) +@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER, AnnotationTarget.VALUE_PARAMETER) +annotation class Instantiatable \ No newline at end of file diff --git a/annotations/src/main/java/org/acra/annotation/NonEmpty.java b/annotations/src/main/java/org/acra/annotation/NonEmpty.kt similarity index 71% rename from annotations/src/main/java/org/acra/annotation/NonEmpty.java rename to annotations/src/main/java/org/acra/annotation/NonEmpty.kt index eb5927d28c..4794716b55 100644 --- a/annotations/src/main/java/org/acra/annotation/NonEmpty.java +++ b/annotations/src/main/java/org/acra/annotation/NonEmpty.kt @@ -13,13 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -package org.acra.annotation; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; +package org.acra.annotation /** * This array must not be empty for the configuration to be valid @@ -27,8 +21,6 @@ * @author F43nd1r * @since 03.06.2017 */ - -@Retention(RetentionPolicy.SOURCE) -@Target(ElementType.METHOD) -public @interface NonEmpty { -} +@Retention(AnnotationRetention.SOURCE) +@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER) +annotation class NonEmpty \ No newline at end of file diff --git a/annotations/src/main/java/org/acra/annotation/PreBuild.java b/annotations/src/main/java/org/acra/annotation/PreBuild.kt similarity index 71% rename from annotations/src/main/java/org/acra/annotation/PreBuild.java rename to annotations/src/main/java/org/acra/annotation/PreBuild.kt index f5fd743087..5ae78f5cda 100644 --- a/annotations/src/main/java/org/acra/annotation/PreBuild.java +++ b/annotations/src/main/java/org/acra/annotation/PreBuild.kt @@ -13,13 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -package org.acra.annotation; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; +package org.acra.annotation /** * This method should run before the configuration is built @@ -27,7 +21,6 @@ * @author F43nd1r * @since 03.06.2017 */ -@Retention(RetentionPolicy.SOURCE) -@Target(ElementType.METHOD) -public @interface PreBuild { -} +@Retention(AnnotationRetention.SOURCE) +@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER) +annotation class PreBuild \ No newline at end of file diff --git a/annotations/src/main/java/org/acra/annotation/Transform.java b/annotations/src/main/java/org/acra/annotation/Transform.kt similarity index 70% rename from annotations/src/main/java/org/acra/annotation/Transform.java rename to annotations/src/main/java/org/acra/annotation/Transform.kt index 39e154c906..ac3528b224 100644 --- a/annotations/src/main/java/org/acra/annotation/Transform.java +++ b/annotations/src/main/java/org/acra/annotation/Transform.kt @@ -13,13 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -package org.acra.annotation; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; +package org.acra.annotation /** * This method transforms the content of a field before it is added to the configuration @@ -27,9 +21,6 @@ * @author F43nd1r * @since 04.06.2017 */ - -@Retention(RetentionPolicy.SOURCE) -@Target(ElementType.METHOD) -public @interface Transform { - String methodName(); -} +@Retention(AnnotationRetention.SOURCE) +@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER) +annotation class Transform(val methodName: String) \ No newline at end of file From dea832075f543866d4f76415a51f6135383fe4ee Mon Sep 17 00:00:00 2001 From: f43nd1r Date: Tue, 24 Nov 2020 18:34:37 +0100 Subject: [PATCH 15/16] make all the tests work --- .../DefaultAttachmentProviderTest.java | 2 ++ .../config/CoreConfigurationBuilderTest.java | 4 +-- .../java/org/acra/data/StringFormatTest.java | 13 ++++---- .../org/acra/util/InstanceCreatorTest.java | 18 ++++------- acra-core/src/main/java/org/acra/ACRA.kt | 3 ++ .../acra/attachment/AcraContentProvider.kt | 2 ++ .../config/BaseCoreConfigurationBuilder.kt | 2 +- .../main/java/org/acra/data/StringFormat.kt | 2 +- .../java/org/acra/util/InstanceCreator.kt | 3 ++ .../acra/processor/creator/ModelBuilder.kt | 1 + .../acra/processor/element/AnnotationField.kt | 30 +++++++++++++++---- .../acra/processor/element/ElementFactory.kt | 6 ++++ 12 files changed, 59 insertions(+), 27 deletions(-) diff --git a/acra-core/src/androidTest/java/org/acra/attachment/DefaultAttachmentProviderTest.java b/acra-core/src/androidTest/java/org/acra/attachment/DefaultAttachmentProviderTest.java index 136435f504..aac4d8ea64 100644 --- a/acra-core/src/androidTest/java/org/acra/attachment/DefaultAttachmentProviderTest.java +++ b/acra-core/src/androidTest/java/org/acra/attachment/DefaultAttachmentProviderTest.java @@ -18,8 +18,10 @@ import android.app.Application; import android.net.Uri; + import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; + import org.acra.config.CoreConfigurationBuilder; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/acra-core/src/androidTest/java/org/acra/config/CoreConfigurationBuilderTest.java b/acra-core/src/androidTest/java/org/acra/config/CoreConfigurationBuilderTest.java index c44be2c140..258df15c27 100644 --- a/acra-core/src/androidTest/java/org/acra/config/CoreConfigurationBuilderTest.java +++ b/acra-core/src/androidTest/java/org/acra/config/CoreConfigurationBuilderTest.java @@ -34,8 +34,8 @@ public class CoreConfigurationBuilderTest { @Test public void enabled() { - assertTrue(new CoreConfigurationBuilder(new AnnotatedClass()).enabled()); - assertFalse(new CoreConfigurationBuilder(new NonAnnotatedClass()).enabled()); + assertTrue(new CoreConfigurationBuilder(new AnnotatedClass()).getEnabled()); + assertFalse(new CoreConfigurationBuilder(new NonAnnotatedClass()).getEnabled()); } @AcraCore diff --git a/acra-core/src/androidTest/java/org/acra/data/StringFormatTest.java b/acra-core/src/androidTest/java/org/acra/data/StringFormatTest.java index 3eca596f2c..6cc12d232c 100644 --- a/acra-core/src/androidTest/java/org/acra/data/StringFormatTest.java +++ b/acra-core/src/androidTest/java/org/acra/data/StringFormatTest.java @@ -20,12 +20,13 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import junit.framework.Assert; import org.acra.ReportField; -import org.acra.collections.ImmutableSet; import org.json.JSONObject; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import java.util.Arrays; + import static junit.framework.Assert.assertEquals; /** @@ -55,16 +56,16 @@ public void toFormattedString() throws Exception { public void testJson() throws Exception { Assert.assertEquals("{\"DEVICE_ID\":\"FAKE_ID\",\"BUILD_CONFIG\":{\"VERSION_CODE\":-1,\"VERSION_NAME\":\"Test\"}}", - StringFormat.JSON.toFormattedString(reportData, new ImmutableSet<>(ReportField.DEVICE_ID, ReportField.BUILD_CONFIG), "\n", " ", false)); + StringFormat.JSON.toFormattedString(reportData, Arrays.asList(ReportField.DEVICE_ID, ReportField.BUILD_CONFIG), "\n", " ", false)); assertEquals("{\"DEVICE_ID\":\"FAKE_ID\",\"BUILD_CONFIG\":{\"VERSION_CODE\":-1,\"VERSION_NAME\":\"Test\"}}", - StringFormat.JSON.toFormattedString(reportData, new ImmutableSet<>(ReportField.DEVICE_ID, ReportField.BUILD_CONFIG), "&", "\n", true)); + StringFormat.JSON.toFormattedString(reportData, Arrays.asList(ReportField.DEVICE_ID, ReportField.BUILD_CONFIG), "&", "\n", true)); } public void testKeyValue() throws Exception { assertEquals("DEVICE_ID=FAKE_ID\nBUILD_CONFIG=VERSION_CODE=-1 VERSION_NAME=Test", - StringFormat.KEY_VALUE_LIST.toFormattedString(reportData, new ImmutableSet<>(ReportField.DEVICE_ID, ReportField.BUILD_CONFIG), "\n", " ", false)); + StringFormat.KEY_VALUE_LIST.toFormattedString(reportData, Arrays.asList(ReportField.DEVICE_ID, ReportField.BUILD_CONFIG), "\n", " ", false)); assertEquals("DEVICE_ID=FAKE_ID&BUILD_CONFIG=VERSION_CODE%3D-1%0AVERSION_NAME%3DTest", - StringFormat.KEY_VALUE_LIST.toFormattedString(reportData, new ImmutableSet<>(ReportField.DEVICE_ID, ReportField.BUILD_CONFIG), "&", "\n", true)); + StringFormat.KEY_VALUE_LIST.toFormattedString(reportData, Arrays.asList(ReportField.DEVICE_ID, ReportField.BUILD_CONFIG), "&", "\n", true)); } @Test @@ -76,6 +77,6 @@ public void getMatchingHttpContentType() { @Test public void issue626() throws Exception { CrashReportData reportData = new CrashReportData(); - assertEquals("DEVICE_ID=null", StringFormat.KEY_VALUE_LIST.toFormattedString(reportData, new ImmutableSet<>(ReportField.DEVICE_ID), "\n", " ", true)); + assertEquals("DEVICE_ID=null", StringFormat.KEY_VALUE_LIST.toFormattedString(reportData, Arrays.asList(ReportField.DEVICE_ID), "\n", " ", true)); } } \ No newline at end of file diff --git a/acra-core/src/androidTest/java/org/acra/util/InstanceCreatorTest.java b/acra-core/src/androidTest/java/org/acra/util/InstanceCreatorTest.java index 713865a94b..e50918b27e 100644 --- a/acra-core/src/androidTest/java/org/acra/util/InstanceCreatorTest.java +++ b/acra-core/src/androidTest/java/org/acra/util/InstanceCreatorTest.java @@ -28,25 +28,19 @@ @RunWith(AndroidJUnit4.class) public class InstanceCreatorTest { - private InstanceCreator instanceCreator; - - @Before - public void setUp() { - instanceCreator = new InstanceCreator(); - } @Test public void create() { - assertNotNull(instanceCreator.create(ClassWithDefaultConstructor.class)); - assertNotNull(instanceCreator.create(ClassWithExplicitNoArgsConstructor.class)); - assertNull(instanceCreator.create(ClassWithPrivateConstructor.class)); - assertNull(instanceCreator.create(ClassWithImplicitConstructorArg.class)); - assertNull(instanceCreator.create(ClassWithExplicitConstructorArg.class)); + assertNotNull(InstanceCreator.create(ClassWithDefaultConstructor.class)); + assertNotNull(InstanceCreator.create(ClassWithExplicitNoArgsConstructor.class)); + assertNull(InstanceCreator.create(ClassWithPrivateConstructor.class)); + assertNull(InstanceCreator.create(ClassWithImplicitConstructorArg.class)); + assertNull(InstanceCreator.create(ClassWithExplicitConstructorArg.class)); } @Test public void create1() { - assertThat(instanceCreator.create(Arrays.asList(ClassWithDefaultConstructor.class, ClassWithExplicitConstructorArg.class)), hasSize(1)); + assertThat(InstanceCreator.create(Arrays.asList(ClassWithDefaultConstructor.class, ClassWithExplicitConstructorArg.class)), hasSize(1)); } public static class ClassWithDefaultConstructor { diff --git a/acra-core/src/main/java/org/acra/ACRA.kt b/acra-core/src/main/java/org/acra/ACRA.kt index 2c58c9bf7c..b73d295ade 100644 --- a/acra-core/src/main/java/org/acra/ACRA.kt +++ b/acra-core/src/main/java/org/acra/ACRA.kt @@ -102,6 +102,7 @@ object ACRA { * the current instance of ErrorReporter. * not available if [ACRA.init] has not yet been called. */ + @JvmStatic var errorReporter = StubCreator.createErrorReporterStub() private set @@ -119,6 +120,7 @@ object ACRA { * @param checkReportsOnApplicationStart Whether to invoke ErrorReporter.checkReportsOnApplicationStart(). */ @JvmOverloads + @JvmStatic fun init(app: Application, builder: CoreConfigurationBuilder = CoreConfigurationBuilder(app), checkReportsOnApplicationStart: Boolean = true) { try { init(app, builder.build(), checkReportsOnApplicationStart) @@ -141,6 +143,7 @@ object ACRA { * @throws IllegalStateException if it is called more than once. */ @JvmOverloads + @JvmStatic fun init(app: Application, config: CoreConfiguration, checkReportsOnApplicationStart: Boolean = true) { val senderServiceProcess = isACRASenderServiceProcess() if (senderServiceProcess) { diff --git a/acra-core/src/main/java/org/acra/attachment/AcraContentProvider.kt b/acra-core/src/main/java/org/acra/attachment/AcraContentProvider.kt index 3343e9eeb4..b73b6301a5 100644 --- a/acra-core/src/main/java/org/acra/attachment/AcraContentProvider.kt +++ b/acra-core/src/main/java/org/acra/attachment/AcraContentProvider.kt @@ -172,6 +172,7 @@ class AcraContentProvider : ContentProvider() { * @param file the file * @return the uri */ + @JvmStatic fun getUriForFile(context: Context, file: File): Uri { return getUriForFile(context, Directory.ROOT, file.path) } @@ -184,6 +185,7 @@ class AcraContentProvider : ContentProvider() { * @param relativePath the file path * @return the uri */ + @JvmStatic fun getUriForFile(context: Context, directory: Directory, relativePath: String): Uri { val builder = Uri.Builder() .scheme(ContentResolver.SCHEME_CONTENT) diff --git a/acra-core/src/main/java/org/acra/config/BaseCoreConfigurationBuilder.kt b/acra-core/src/main/java/org/acra/config/BaseCoreConfigurationBuilder.kt index 833b2095e7..df1f520549 100644 --- a/acra-core/src/main/java/org/acra/config/BaseCoreConfigurationBuilder.kt +++ b/acra-core/src/main/java/org/acra/config/BaseCoreConfigurationBuilder.kt @@ -74,7 +74,7 @@ class BaseCoreConfigurationBuilder internal constructor(private val app: Context } @Transform(methodName = "reportContent") - fun transformReportContent(reportFields: Array): List { + fun transformReportContent(reportFields: Array): List { val reportContent: MutableList = ArrayList() if (reportFields.isNotEmpty()) { debug { "Using custom Report Fields" } diff --git a/acra-core/src/main/java/org/acra/data/StringFormat.kt b/acra-core/src/main/java/org/acra/data/StringFormat.kt index fcf8c17685..a6439c2765 100644 --- a/acra-core/src/main/java/org/acra/data/StringFormat.kt +++ b/acra-core/src/main/java/org/acra/data/StringFormat.kt @@ -73,7 +73,7 @@ enum class StringFormat(val matchingHttpContentType: String) { } private fun toStringMap(map: Map, joiner: String): Map { - return map.mapValues { valueToString(joiner, it) }.toMap() + return map.mapValues { valueToString(joiner, it.value) }.toMap() } private fun valueToString(joiner: String, value: Any?): String { diff --git a/acra-core/src/main/java/org/acra/util/InstanceCreator.kt b/acra-core/src/main/java/org/acra/util/InstanceCreator.kt index 6f26db4e97..c1a642b751 100644 --- a/acra-core/src/main/java/org/acra/util/InstanceCreator.kt +++ b/acra-core/src/main/java/org/acra/util/InstanceCreator.kt @@ -30,9 +30,11 @@ object InstanceCreator { * @param the return type * @return a new instance of clazz or fallback */ + @JvmStatic fun create(clazz: Class, fallback: () -> T): T = create(clazz) ?: fallback.invoke() @VisibleForTesting + @JvmStatic fun create(clazz: Class): T? { try { return clazz.newInstance() @@ -51,5 +53,6 @@ object InstanceCreator { * @param the return type * @return a list of successfully created instances, does not contain null */ + @JvmStatic fun create(classes: Collection>): List = classes.mapNotNull { create(it) } } \ No newline at end of file diff --git a/annotationprocessor/src/main/java/org/acra/processor/creator/ModelBuilder.kt b/annotationprocessor/src/main/java/org/acra/processor/creator/ModelBuilder.kt index 92dd9dc451..c558577627 100644 --- a/annotationprocessor/src/main/java/org/acra/processor/creator/ModelBuilder.kt +++ b/annotationprocessor/src/main/java/org/acra/processor/creator/ModelBuilder.kt @@ -54,6 +54,7 @@ internal class ModelBuilder(private val baseAnnotation: TypeElement, private val for (method in ElementFilter.methodsIn(baseAnnotation.enclosedElements)) { elements.add(when { MoreElements.isAnnotationPresent(method, StringRes::class.java) -> modelFactory.fromStringResourceAnnotationMethod(method) + method.returnType.toString().endsWith("[]") -> modelFactory.fromArrayAnnotationMethod(method) method.returnType.toString().contains("Class") -> modelFactory.fromClassAnnotationMethod(method) else -> modelFactory.fromAnnotationMethod(method) }) diff --git a/annotationprocessor/src/main/java/org/acra/processor/element/AnnotationField.kt b/annotationprocessor/src/main/java/org/acra/processor/element/AnnotationField.kt index 1d1109141d..7e8dff1886 100644 --- a/annotationprocessor/src/main/java/org/acra/processor/element/AnnotationField.kt +++ b/annotationprocessor/src/main/java/org/acra/processor/element/AnnotationField.kt @@ -21,9 +21,12 @@ import com.squareup.kotlinpoet.CodeBlock import com.squareup.kotlinpoet.FunSpec import com.squareup.kotlinpoet.KModifier import com.squareup.kotlinpoet.ParameterSpec +import com.squareup.kotlinpoet.ParameterizedTypeName +import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy import com.squareup.kotlinpoet.PropertySpec import com.squareup.kotlinpoet.TypeName import com.squareup.kotlinpoet.TypeSpec +import com.squareup.kotlinpoet.WildcardTypeName import org.acra.processor.creator.BuildMethodCreator import org.acra.processor.util.IsValidResourceVisitor import org.acra.processor.util.Strings @@ -37,7 +40,8 @@ import javax.lang.model.element.AnnotationValue * @author F43nd1r * @since 12.01.2018 */ -abstract class AnnotationField(override val name: String, override val type: TypeName, override val annotations: Collection, private val javadoc: String?, private val markers: Collection, val defaultValue: AnnotationValue?) : TransformedField.Transformable { +abstract class AnnotationField(override val name: String, override val type: TypeName, override val annotations: Collection, private val javadoc: String?, + private val markers: Collection, val defaultValue: AnnotationValue?) : TransformedField.Transformable { fun hasMarker(marker: ClassName): Boolean { return markers.contains(marker) } @@ -62,7 +66,8 @@ abstract class AnnotationField(override val name: String, override val type: Typ } } - open class Normal(name: String, type: TypeName, annotations: Collection, markers: Collection, defaultValue: AnnotationValue?, javadoc: String) : AnnotationField(name, type, annotations, javadoc, markers, defaultValue) { + open class Normal(name: String, type: TypeName, annotations: Collection, markers: Collection, defaultValue: AnnotationValue?, javadoc: String) : + AnnotationField(name, type, annotations, javadoc, markers, defaultValue) { public override fun addInitializer(constructor: CodeBlock.Builder) { constructor.addStatement("%1L = %2L?.%1L ?: %3L", name, Strings.VAR_ANNOTATION, defaultValue?.accept(ToCodeBlockVisitor(), null)) } @@ -83,18 +88,33 @@ abstract class AnnotationField(override val name: String, override val type: Typ } } - class Clazz(name: String, type: TypeName, annotations: Collection, markers: Collection, defaultValue: AnnotationValue?, javadoc: String) : Normal(name, type, annotations, markers, defaultValue, javadoc) { + class Clazz(name: String, type: TypeName, annotations: Collection, markers: Collection, defaultValue: AnnotationValue?, javadoc: String) : + Normal(name, type, annotations, markers, defaultValue, javadoc) { override fun addInitializer(constructor: CodeBlock.Builder) { constructor.addStatement("%1L = %2L?.%1L?.java ?: %3L", name, Strings.VAR_ANNOTATION, defaultValue?.accept(ToCodeBlockVisitor(), null)) } } + class Array(name: String, type: TypeName, annotations: Collection, markers: Collection, defaultValue: AnnotationValue?, javadoc: String) : + Normal(name, (type as ParameterizedTypeName).let { it.rawType.parameterizedBy(WildcardTypeName.producerOf(it.typeArguments.first())) }, annotations, markers, + defaultValue, javadoc) { + private val originalType = type + override fun configureSetter(builder: FunSpec.Builder) { + super.configureSetter(builder) + val param = builder.parameters.first().toBuilder(type = (originalType as ParameterizedTypeName).typeArguments.first()).addModifiers(KModifier.VARARG).build() + builder.parameters.clear() + builder.addParameter(param) + } + } + class StringResource(private val originalName: String, annotations: Collection, markers: Collection, - defaultValue: AnnotationValue?, javadoc: String?) : AnnotationField(if (originalName.startsWith(Strings.PREFIX_RES)) WordUtils.uncapitalize(originalName.substring(Strings.PREFIX_RES.length)) else originalName, Types.STRING, annotations - Types.STRING_RES, javadoc, markers, defaultValue) { + defaultValue: AnnotationValue?, javadoc: String?) : + AnnotationField(if (originalName.startsWith(Strings.PREFIX_RES)) WordUtils.uncapitalize(originalName.substring(Strings.PREFIX_RES.length)) else originalName, + Types.STRING, annotations - Types.STRING_RES, javadoc, markers, defaultValue) { private val hasDefault: Boolean = defaultValue != null && defaultValue.accept(IsValidResourceVisitor(), null) public override fun addInitializer(constructor: CodeBlock.Builder) { - if(hasDefault) { + if (hasDefault) { constructor.addStatement("%L = %L.getString(%L?.%L.takeIf·{ it != 0 } ?: %L)", name, Strings.FIELD_CONTEXT, Strings.VAR_ANNOTATION, originalName, defaultValue) } else { constructor.addStatement("%L = %L?.%L.takeIf·{ it != 0 }?.let·{ %L.getString(it) } ?: \"\"", name, Strings.VAR_ANNOTATION, originalName, Strings.FIELD_CONTEXT) diff --git a/annotationprocessor/src/main/java/org/acra/processor/element/ElementFactory.kt b/annotationprocessor/src/main/java/org/acra/processor/element/ElementFactory.kt index f8b4377a40..d191091b10 100644 --- a/annotationprocessor/src/main/java/org/acra/processor/element/ElementFactory.kt +++ b/annotationprocessor/src/main/java/org/acra/processor/element/ElementFactory.kt @@ -47,6 +47,12 @@ class ElementFactory(private val elements: Elements) { elements.getDocComment(method)) } + fun fromArrayAnnotationMethod(method: ExecutableElement): Element { + val annotations = getAnnotations(method) + return AnnotationField.Array(method.simpleName.toString(), method.returnType.asTypeName().javaToKotlinType(), annotations.left, annotations.right, method.defaultValue, + elements.getDocComment(method)) + } + fun fromStringResourceAnnotationMethod(method: ExecutableElement): Element { val annotations = getAnnotations(method) return AnnotationField.StringResource(method.simpleName.toString(), annotations.left, annotations.right, From f97d77d6978e50423075c20a295828b13aca8ba3 Mon Sep 17 00:00:00 2001 From: f43nd1r Date: Wed, 25 Nov 2020 15:17:27 +0100 Subject: [PATCH 16/16] dokka replaces javadoc --- .github/workflows/upload-javadoc.yml | 6 ++-- .../DefaultAttachmentProviderTest.java | 2 +- .../src/main/java/org/acra/util/utils.kt | 2 +- .../main/java/org/acra/config/LimiterData.kt | 2 +- .../acra/processor/element/AbstractElement.kt | 3 +- .../processor/element/TransformedField.kt | 3 +- .../java/org/acra/processor/util/Strings.kt | 3 +- buildSrc/build.gradle.kts | 3 ++ .../kotlin/acra-android-library.gradle.kts | 26 ++++++++++------- .../src/main/kotlin/acra-ci-tasks.gradle.kts | 29 ++++--------------- .../main/kotlin/acra-java-library.gradle.kts | 3 +- gradle.properties | 5 ++-- 12 files changed, 39 insertions(+), 48 deletions(-) diff --git a/.github/workflows/upload-javadoc.yml b/.github/workflows/upload-javadoc.yml index 03206331f2..430afa893d 100644 --- a/.github/workflows/upload-javadoc.yml +++ b/.github/workflows/upload-javadoc.yml @@ -25,7 +25,7 @@ jobs: repository: 'ACRA/acra.github.com' path: 'web' - name: Generate Javadoc - run: ./gradlew joinedJavadoc --no-daemon + run: ./gradlew dokkaHtmlCollector --no-daemon working-directory: ./main - name: Extract version id: version @@ -34,8 +34,8 @@ jobs: - name: Update Javadoc run: | mkdir ./web/javadoc/${{ steps.version.outputs.value }} - cp -a ./main/build/javadoc/. ./web/javadoc/${{ steps.version.outputs.value }}/ - echo "- [${{ steps.version.outputs.value }}](${{ steps.version.outputs.value }})" >> ./web/javadoc/index.md + cp -a ./main/build/dokka/htmlCollector/. ./web/javadoc/${{ steps.version.outputs.value }}/ + echo "- [${{ steps.version.outputs.value }}](${{ steps.version.outputs.value }}/acra)" >> ./web/javadoc/index.md ln -sfn "${{ steps.version.outputs.value }}/" ./web/javadoc/latest - name: Commit files run: | diff --git a/acra-core/src/androidTest/java/org/acra/attachment/DefaultAttachmentProviderTest.java b/acra-core/src/androidTest/java/org/acra/attachment/DefaultAttachmentProviderTest.java index aac4d8ea64..a4d6b115f2 100644 --- a/acra-core/src/androidTest/java/org/acra/attachment/DefaultAttachmentProviderTest.java +++ b/acra-core/src/androidTest/java/org/acra/attachment/DefaultAttachmentProviderTest.java @@ -41,7 +41,7 @@ public class DefaultAttachmentProviderTest { @Test public void getAttachments() throws Exception { Uri uri = Uri.parse("content://not-a-valid-content-uri"); - List result = new DefaultAttachmentProvider().getAttachments(ApplicationProvider.getApplicationContext(), new CoreConfigurationBuilder(new Application()).setAttachmentUris(uri.toString()).build()); + List result = new DefaultAttachmentProvider().getAttachments(ApplicationProvider.getApplicationContext(), new CoreConfigurationBuilder(new Application()).withAttachmentUris(uri.toString()).build()); assertThat(result, hasSize(1)); assertEquals(uri, result.get(0)); } diff --git a/acra-core/src/main/java/org/acra/util/utils.kt b/acra-core/src/main/java/org/acra/util/utils.kt index 85c87812e2..14472242d3 100644 --- a/acra-core/src/main/java/org/acra/util/utils.kt +++ b/acra-core/src/main/java/org/acra/util/utils.kt @@ -2,7 +2,7 @@ package org.acra.util import android.util.SparseArray -public inline fun Iterable.mapNotNullToSparseArray(transform: (T) -> Pair?): SparseArray { +inline fun Iterable.mapNotNullToSparseArray(transform: (T) -> Pair?): SparseArray { val destination = SparseArray() forEach { element -> transform(element)?.let { (key, value) -> destination.put(key, value) } } return destination diff --git a/acra-limiter/src/main/java/org/acra/config/LimiterData.kt b/acra-limiter/src/main/java/org/acra/config/LimiterData.kt index 1df5fe1f16..f022a494f5 100644 --- a/acra-limiter/src/main/java/org/acra/config/LimiterData.kt +++ b/acra-limiter/src/main/java/org/acra/config/LimiterData.kt @@ -77,7 +77,7 @@ class LimiterData() { put(KEY_TIMESTAMP, crashReportData.getString(ReportField.USER_CRASH_DATE)) } - internal constructor(copyFrom: JSONObject) : super(copyFrom, jsonArrayToList(copyFrom.names())) {} + internal constructor(copyFrom: JSONObject) : super(copyFrom, jsonArrayToList(copyFrom.names())) val stacktrace: String get() = optString(KEY_STACK_TRACE) diff --git a/annotationprocessor/src/main/java/org/acra/processor/element/AbstractElement.kt b/annotationprocessor/src/main/java/org/acra/processor/element/AbstractElement.kt index 0afd7eaef3..9e22bf7f17 100644 --- a/annotationprocessor/src/main/java/org/acra/processor/element/AbstractElement.kt +++ b/annotationprocessor/src/main/java/org/acra/processor/element/AbstractElement.kt @@ -22,5 +22,4 @@ import com.squareup.kotlinpoet.TypeName * @author F43nd1r * @since 12.01.2018 */ -open class AbstractElement(override val name: String, override val type: TypeName, override val annotations: Collection) : Element { -} \ No newline at end of file +open class AbstractElement(override val name: String, override val type: TypeName, override val annotations: Collection) : Element \ No newline at end of file diff --git a/annotationprocessor/src/main/java/org/acra/processor/element/TransformedField.kt b/annotationprocessor/src/main/java/org/acra/processor/element/TransformedField.kt index 7ff2682856..5c40a0a3f8 100644 --- a/annotationprocessor/src/main/java/org/acra/processor/element/TransformedField.kt +++ b/annotationprocessor/src/main/java/org/acra/processor/element/TransformedField.kt @@ -48,6 +48,5 @@ class TransformedField(val transformName: String, override val type: TypeName, p override val annotations: Collection get() = transform.annotations - interface Transformable : ConfigElement, BuilderElement, ValidatedElement { - } + interface Transformable : ConfigElement, BuilderElement, ValidatedElement } \ No newline at end of file diff --git a/annotationprocessor/src/main/java/org/acra/processor/util/Strings.kt b/annotationprocessor/src/main/java/org/acra/processor/util/Strings.kt index fb56ca4999..05319a86d9 100644 --- a/annotationprocessor/src/main/java/org/acra/processor/util/Strings.kt +++ b/annotationprocessor/src/main/java/org/acra/processor/util/Strings.kt @@ -32,8 +32,7 @@ import javax.tools.Diagnostic */ object Strings { const val PREFIX_RES = "res" - const val PREFIX_SETTER = "set" - const val PREFIX_GETTER = "get" + const val PREFIX_SETTER = "with" const val PARAM_0 = "arg0" const val VAR_ANNOTATION = "annotation" const val FIELD_DELEGATE = "delegate" diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index f913f9fa1e..2f6040d3c0 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -35,6 +35,9 @@ dependencies { implementation("com.faendir.gradle:gradle-release:$releasePluginVersion") val kotlinVersion: String by project implementation(kotlin("gradle-plugin:$kotlinVersion")) + val dokkaVersion: String by project + implementation("org.jetbrains.dokka:dokka-gradle-plugin:$dokkaVersion") + implementation("org.jetbrains.dokka:dokka-core:$dokkaVersion") } val compileKotlin: KotlinCompile by tasks diff --git a/buildSrc/src/main/kotlin/acra-android-library.gradle.kts b/buildSrc/src/main/kotlin/acra-android-library.gradle.kts index 718d15896f..e8a10dd29c 100644 --- a/buildSrc/src/main/kotlin/acra-android-library.gradle.kts +++ b/buildSrc/src/main/kotlin/acra-android-library.gradle.kts @@ -1,3 +1,7 @@ +import org.jetbrains.dokka.Platform +import org.jetbrains.dokka.gradle.DokkaTask +import org.jetbrains.kotlin.gradle.internal.KaptTask +import org.jetbrains.kotlin.gradle.model.Kapt import org.jetbrains.kotlin.gradle.tasks.KotlinCompile /* @@ -21,6 +25,7 @@ plugins { apply(plugin = "kotlin-android") apply(plugin = "kotlin-kapt") apply(plugin = "acra-base") +apply(plugin = "org.jetbrains.dokka") android { val androidVersion: String by project @@ -89,22 +94,23 @@ tasks.register("sourcesJar") { archiveClassifier.set("sources") } -tasks.register("javadoc") { - group = "documentation" - source = (android.sourceSets["main"].java.srcDirs.map { fileTree(it) }.reduce(FileTree::plus) + - files("$buildDir/generated/source/buildConfig/release") + - files("$buildDir/generated/ap_generated_sources/release/out")).filter { it.extension != "kt" }.asFileTree - classpath += files(*android.bootClasspath.toTypedArray()) - android.libraryVariants.find { it.name == "release" }?.apply { - classpath += javaCompileProvider.get().classpath +tasks.withType { + useBuildCache = false +} + +tasks.withType { + dokkaSourceSets { + named("main") { + noAndroidSdkLink.set(false) + sourceRoot(File(buildDir,"generated/source/kaptKotlin/release")) + } } - linksOffline("http://d.android.com/reference", "${android.sdkDirectory.path}/docs/reference") dependsOn("assembleRelease") } tasks.register("javadocJar") { group = "documentation" - from(tasks["javadoc"]) + from(tasks["dokkaJavadoc"]) archiveClassifier.set("javadoc") } diff --git a/buildSrc/src/main/kotlin/acra-ci-tasks.gradle.kts b/buildSrc/src/main/kotlin/acra-ci-tasks.gradle.kts index ad41673caf..3751e69d19 100644 --- a/buildSrc/src/main/kotlin/acra-ci-tasks.gradle.kts +++ b/buildSrc/src/main/kotlin/acra-ci-tasks.gradle.kts @@ -13,33 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import com.android.build.gradle.LibraryPlugin -import com.android.build.gradle.LibraryExtension +plugins { + id("org.jetbrains.dokka") +} -tasks.register("joinedJavadoc") { - group = "documentation" - setDestinationDir(file("$buildDir/javadoc")) - subprojects { - val tasks = tasks.withType() - source += files(*tasks.map { it.source }.toTypedArray()).asFileTree - classpath += files(*tasks.map { it.classpath }.toTypedArray()) - val path = project.path.let { if (!it.endsWith(Project.PATH_SEPARATOR)) it + Project.PATH_SEPARATOR else it } - dependsOn(*tasks.flatMap { task -> task.dependsOn.map { "$path$it"} }.toTypedArray()) - plugins.withType { - linksOffline("http://d.android.com/reference", "${android.sdkDirectory.path}/docs/reference") - android.libraryVariants.find { it.name == "release" }?.apply { - classpath += javaCompileProvider.get().classpath - } - } - } +repositories { + jcenter() } tasks.register("printVersion") { doLast { println(version) } -} - -val Project.android: LibraryExtension get() = this.extensions.getByType(LibraryExtension::class.java) - -fun Javadoc.linksOffline(extDocUrl: String, packageListLoc: String) = (options as StandardJavadocDocletOptions).linksOffline(extDocUrl, packageListLoc) \ No newline at end of file +} \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/acra-java-library.gradle.kts b/buildSrc/src/main/kotlin/acra-java-library.gradle.kts index a37700e0d1..fbfae5ea8d 100644 --- a/buildSrc/src/main/kotlin/acra-java-library.gradle.kts +++ b/buildSrc/src/main/kotlin/acra-java-library.gradle.kts @@ -18,6 +18,7 @@ plugins { kotlin("jvm") id("kotlin-kapt") id("acra-base") + id("org.jetbrains.dokka") } tasks.register("sourcesJar") { @@ -28,7 +29,7 @@ tasks.register("sourcesJar") { tasks.register("javadocJar") { group = "documentation" - from(tasks["javadoc"]) + from(tasks["dokkaJavadoc"]) archiveClassifier.set("javadoc") } diff --git a/gradle.properties b/gradle.properties index 4e9436cb41..a182af440d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -23,9 +23,9 @@ org.gradle.configureondemand=false org.gradle.parallel=true org.gradle.caching=true android.useAndroidX=true -kapt.includeCompileClasspath=false +kapt.include.compile.classpath=false kapt.incremental.apt=false -kapt.generateStubs=true +kapt.use.worker.api=false # upload properties group=ch.acra @@ -41,6 +41,7 @@ junitVersion=4.12 androidxCoreVersion=1.1.0 androidxAnnotationVersion=1.1.0 kotlinVersion=1.4.10 +dokkaVersion=1.4.10.2 hamcrestVersion=2.2 androidXTestVersion=1.2.0 androidXJunitVersion=1.1.1