diff --git a/Clover/app/build.gradle b/Clover/app/build.gradle index cf837499..708a8b34 100644 --- a/Clover/app/build.gradle +++ b/Clover/app/build.gradle @@ -78,6 +78,7 @@ android { resValue "string", "app_name", "Clover" resValue "string", "app_flavor_name", "" buildConfigField "String", "UPDATE_API_ENDPOINT", "\"https://floens.github.io/Clover/api/update\"" + buildConfigField "String", "CRASH_REPORT_ENDPOINT", "\"https://acra.floens.org/clover/report\"" } dev { @@ -86,6 +87,7 @@ android { resValue "string", "app_name", "Clover dev" resValue "string", "app_flavor_name", "" buildConfigField "String", "UPDATE_API_ENDPOINT", "\"\"" + buildConfigField "String", "CRASH_REPORT_ENDPOINT", "\"\"" } fdroid { @@ -94,6 +96,7 @@ android { resValue "string", "app_name", "Clover" resValue "string", "app_flavor_name", "F-Droid" buildConfigField "String", "UPDATE_API_ENDPOINT", "\"https://floens.github.io/Clover/api/update\"" + buildConfigField "String", "CRASH_REPORT_ENDPOINT", "\"https://acra.floens.org/clover/report\"" } } @@ -103,7 +106,7 @@ android { signingConfig signingConfigs.release } minifyEnabled true - proguardFiles 'proguard.cfg' + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard.cfg' } debug { @@ -141,4 +144,6 @@ dependencies { implementation 'com.google.code.gson:gson:2.8.1' implementation 'me.xdrop:fuzzywuzzy:1.1.9' implementation 'org.codejargon.feather:feather:1.0' + + releaseImplementation 'ch.acra:acra-http:5.0.1' } diff --git a/Clover/app/proguard.cfg b/Clover/app/proguard.cfg index 5bc091ac..eb1eabd6 100644 --- a/Clover/app/proguard.cfg +++ b/Clover/app/proguard.cfg @@ -110,4 +110,15 @@ -keep public class android.support.v7.widget.RecyclerView -keep public class android.support.v4.widget.SlidingPaneLayout --dontwarn dagger.internal.** +# Keep Feather inject working. +-keepclassmembers,allowobfuscation class * { + @javax.inject.* *; + (); +} + +# ACRA stuff +# ACRA loads Plugins using reflection, so we need to keep all Plugin classes +-keep class * extends @android.support.annotation.Keep org.acra.** {*;} + +# ACRA uses enum fields in annotations, so we have to keep those +-keep enum org.acra.** {*;} diff --git a/Clover/app/src/main/java/org/floens/chan/ChanApplication.java b/Clover/app/src/debug/java/org/floens/chan/ChanApplication.java similarity index 75% rename from Clover/app/src/main/java/org/floens/chan/ChanApplication.java rename to Clover/app/src/debug/java/org/floens/chan/ChanApplication.java index 4001cd19..ebad39ed 100644 --- a/Clover/app/src/main/java/org/floens/chan/ChanApplication.java +++ b/Clover/app/src/debug/java/org/floens/chan/ChanApplication.java @@ -17,5 +17,16 @@ */ package org.floens.chan; +/** + * The ChanApplication belonging to the debug configuration. + * + * It does not have acra enabled, unlike the release version, and immediately calls initialize. + */ public class ChanApplication extends Chan { + @Override + public void onCreate() { + super.onCreate(); + + initialize(); + } } diff --git a/Clover/app/src/main/java/org/floens/chan/Chan.java b/Clover/app/src/main/java/org/floens/chan/Chan.java index 8b4d4994..15d8d89f 100644 --- a/Clover/app/src/main/java/org/floens/chan/Chan.java +++ b/Clover/app/src/main/java/org/floens/chan/Chan.java @@ -20,6 +20,7 @@ package org.floens.chan; import android.annotation.SuppressLint; import android.app.Activity; import android.app.Application; +import android.content.Context; import android.content.pm.PackageManager; import android.os.Build; import android.os.Bundle; @@ -41,6 +42,7 @@ import javax.inject.Inject; import de.greenrobot.event.EventBus; +@SuppressLint("Registered") // extended by ChanApplication, which is registered in the manifest. public class Chan extends Application implements UserAgentProvider, Application.ActivityLifecycleCallbacks { private static final String TAG = "ChanApplication"; @@ -55,6 +57,7 @@ public class Chan extends Application implements UserAgentProvider, Application. @Inject DatabaseManager databaseManager; + private Feather feather; public Chan() { @@ -75,15 +78,17 @@ public class Chan extends Application implements UserAgentProvider, Application. } @Override - public void onCreate() { - super.onCreate(); + protected void attachBaseContext(Context base) { + super.attachBaseContext(base); + + AndroidUtils.init(this); + } + public void initialize() { final long startTime = Time.startTiming(); registerActivityLifecycleCallbacks(this); - AndroidUtils.init(this); - userAgent = createUserAgent(); initializeGraph(); diff --git a/Clover/app/src/main/java/org/floens/chan/core/settings/ChanSettings.java b/Clover/app/src/main/java/org/floens/chan/core/settings/ChanSettings.java index 79924098..c78d3f8c 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/settings/ChanSettings.java +++ b/Clover/app/src/main/java/org/floens/chan/core/settings/ChanSettings.java @@ -155,6 +155,8 @@ public class ChanSettings { public static final LongSetting updateCheckTime; public static final LongSetting updateCheckInterval; + public static final BooleanSetting crashReporting; + static { SharedPreferences p = AndroidUtils.getPreferences(); @@ -238,6 +240,8 @@ public class ChanSettings { updateCheckTime = new LongSetting(p, "update_check_time", 0L); updateCheckInterval = new LongSetting(p, "update_check_interval", UpdateManager.DEFAULT_UPDATE_CHECK_INTERVAL_MS); + crashReporting = new BooleanSetting(p, "preference_crash_reporting", true); + // Old (but possibly still in some users phone) // preference_board_view_mode default "list" // preference_board_editor_filler default false diff --git a/Clover/app/src/main/java/org/floens/chan/ui/controller/MainSettingsController.java b/Clover/app/src/main/java/org/floens/chan/ui/controller/MainSettingsController.java index e2bc0dda..65652bfa 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/controller/MainSettingsController.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/controller/MainSettingsController.java @@ -29,6 +29,7 @@ import org.floens.chan.R; import org.floens.chan.core.presenter.SettingsPresenter; import org.floens.chan.core.settings.ChanSettings; import org.floens.chan.ui.activity.StartActivity; +import org.floens.chan.ui.settings.BooleanSettingView; import org.floens.chan.ui.settings.LinkSettingView; import org.floens.chan.ui.settings.SettingView; import org.floens.chan.ui.settings.SettingsController; @@ -49,6 +50,7 @@ public class MainSettingsController extends SettingsController implements Settin private SettingView developerView; private LinkSettingView sitesSetting; private LinkSettingView filtersSetting; + private SettingView crashReportSetting; public MainSettingsController(Context context) { super(context); @@ -106,6 +108,15 @@ public class MainSettingsController extends SettingsController implements Settin R.string.setting_watch_summary_enabled : R.string.setting_watch_summary_disabled); } + @Override + public void onPreferenceChange(SettingView item) { + super.onPreferenceChange(item); + if (item == crashReportSetting) { + Toast.makeText(context, R.string.settings_crash_reporting_toggle_notice, + Toast.LENGTH_LONG).show(); + } + } + private void populatePreferences() { // General group { @@ -153,6 +164,11 @@ public class MainSettingsController extends SettingsController implements Settin setupUpdateSetting(about); + crashReportSetting = about.add(new BooleanSettingView(this, + ChanSettings.crashReporting, + R.string.settings_crash_reporting, + R.string.settings_crash_reporting_description)); + setupExtraAboutSettings(about, version); about.add(new LinkSettingView(this, diff --git a/Clover/app/src/main/java/org/floens/chan/utils/AndroidUtils.java b/Clover/app/src/main/java/org/floens/chan/utils/AndroidUtils.java index e72420a6..dad104d8 100644 --- a/Clover/app/src/main/java/org/floens/chan/utils/AndroidUtils.java +++ b/Clover/app/src/main/java/org/floens/chan/utils/AndroidUtils.java @@ -75,14 +75,16 @@ public class AndroidUtils { private static final Handler mainHandler = new Handler(Looper.getMainLooper()); public static void init(Application application) { - AndroidUtils.application = application; + if (AndroidUtils.application == null) { + AndroidUtils.application = application; - ROBOTO_MEDIUM = getTypeface("Roboto-Medium.ttf"); - ROBOTO_MEDIUM_ITALIC = getTypeface("Roboto-MediumItalic.ttf"); - ROBOTO_CONDENSED_REGULAR = getTypeface("RobotoCondensed-Regular.ttf"); + ROBOTO_MEDIUM = getTypeface("Roboto-Medium.ttf"); + ROBOTO_MEDIUM_ITALIC = getTypeface("Roboto-MediumItalic.ttf"); + ROBOTO_CONDENSED_REGULAR = getTypeface("RobotoCondensed-Regular.ttf"); - connectivityManager = (ConnectivityManager) - application.getSystemService(Context.CONNECTIVITY_SERVICE); + connectivityManager = (ConnectivityManager) + application.getSystemService(Context.CONNECTIVITY_SERVICE); + } } public static Resources getRes() { @@ -101,6 +103,10 @@ public class AndroidUtils { return PreferenceManager.getDefaultSharedPreferences(application); } + public static SharedPreferences getPreferences(Application application) { + return PreferenceManager.getDefaultSharedPreferences(application); + } + public static SharedPreferences getPreferences(String name) { return application.getSharedPreferences(name, Context.MODE_PRIVATE); } diff --git a/Clover/app/src/main/res/values/strings.xml b/Clover/app/src/main/res/values/strings.xml index a3b6c567..7adc1b35 100644 --- a/Clover/app/src/main/res/values/strings.xml +++ b/Clover/app/src/main/res/values/strings.xml @@ -421,6 +421,10 @@ Re-enable this permission in the app settings if you permanently disabled it." About Check for updates + Report crashes + Crash reporting creates reports of errors in Clover. + Crash reports do not collect any personally identifiable information. + Setting will be applied on next app start. Released under the GNU GPLv3 license Tap to see license Open Source Licenses diff --git a/Clover/app/src/release/java/org/floens/chan/ChanApplication.java b/Clover/app/src/release/java/org/floens/chan/ChanApplication.java new file mode 100644 index 00000000..97f675e5 --- /dev/null +++ b/Clover/app/src/release/java/org/floens/chan/ChanApplication.java @@ -0,0 +1,121 @@ +/* + * Clover - 4chan browser https://github.com/Floens/Clover/ + * Copyright (C) 2014 Floens + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.floens.chan; + +import android.content.Context; + +import org.acra.ACRA; +import org.acra.annotation.AcraCore; +import org.acra.annotation.AcraHttpSender; +import org.acra.data.StringFormat; +import org.acra.sender.HttpSender; +import org.floens.chan.core.settings.ChanSettings; + +import static org.acra.ReportField.ANDROID_VERSION; +import static org.acra.ReportField.APP_VERSION_CODE; +import static org.acra.ReportField.APP_VERSION_NAME; +import static org.acra.ReportField.AVAILABLE_MEM_SIZE; +import static org.acra.ReportField.BRAND; +import static org.acra.ReportField.BUILD; +import static org.acra.ReportField.BUILD_CONFIG; +import static org.acra.ReportField.LOGCAT; +import static org.acra.ReportField.MEDIA_CODEC_LIST; +import static org.acra.ReportField.PACKAGE_NAME; +import static org.acra.ReportField.PHONE_MODEL; +import static org.acra.ReportField.PRODUCT; +import static org.acra.ReportField.REPORT_ID; +import static org.acra.ReportField.STACK_TRACE; +import static org.acra.ReportField.TOTAL_MEM_SIZE; +import static org.acra.ReportField.USER_APP_START_DATE; +import static org.acra.ReportField.USER_CRASH_DATE; + +/** + * The ChanApplication belonging to the release configuration. + *

+ * It has acra enabled. Acra unfortunately believes it needs to have annotations on our + * application class, which I find really intrusive. We already had ChanApplication pointing to + * Chan, so we now have a debug and release variant of ChanApplication. The real initialization + * is done with the {@link Chan#initialize()} method, which does not get called from acra + * processes. + */ +@AcraCore( + sharedPreferencesName = "acra_preferences", + alsoReportToAndroidFramework = true, + buildConfigClass = BuildConfig.class, + reportFormat = StringFormat.JSON, + reportContent = { + // Required + REPORT_ID, + + // What app version + APP_VERSION_CODE, + APP_VERSION_NAME, + PACKAGE_NAME, + BUILD_CONFIG, + + // What phone + PHONE_MODEL, + BRAND, + PRODUCT, + + // What Android version + ANDROID_VERSION, + BUILD, + + // Memory details + TOTAL_MEM_SIZE, + AVAILABLE_MEM_SIZE, + + // Useful for webm debugging + MEDIA_CODEC_LIST, + + // The error + STACK_TRACE, + LOGCAT, + USER_APP_START_DATE, + USER_CRASH_DATE + } +) +@AcraHttpSender( + httpMethod = HttpSender.Method.PUT, + uri = BuildConfig.CRASH_REPORT_ENDPOINT +) +public class ChanApplication extends Chan { + @Override + public void onCreate() { + super.onCreate(); + + // Do not initialize again if running from the crash reporting process. + if (!ACRA.isACRASenderServiceProcess()) { + initialize(); + } + } + + @Override + protected void attachBaseContext(Context base) { + super.attachBaseContext(base); + + if (enableAcra()) { + ACRA.init(this); + } + } + + private boolean enableAcra() { + return !BuildConfig.CRASH_REPORT_ENDPOINT.isEmpty() && ChanSettings.crashReporting.get(); + } +}