diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 0d666e7..aa9b603 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -10,6 +10,7 @@
+
{
+ saveLogcatToFile();
+ return true;
+ });
}
public void updateCallScreeningPreference() {
@@ -233,5 +247,20 @@ public class SettingsActivity extends AppCompatActivity
requireNonNull(findPreference(PREF_USE_CALL_SCREENING_SERVICE));
callScreeningPref.setChecked(PermissionHelper.isCallScreeningHeld(getActivity()));
}
+
+ private void saveLogcatToFile() {
+ boolean success = false;
+ try {
+ DebuggingUtils.saveLogcatToFile(getActivity(), true);
+ success = true;
+ } catch (IOException e) {
+ LOG.warn("saveLogcatToFile()", e);
+ }
+
+ Toast.makeText(getActivity(), success
+ ? R.string.save_logcat_to_file_done
+ : R.string.save_logcat_to_file_error,
+ Toast.LENGTH_SHORT).show();
+ }
}
}
diff --git a/app/src/main/java/dummydomain/yetanothercallblocker/utils/DebuggingUtils.java b/app/src/main/java/dummydomain/yetanothercallblocker/utils/DebuggingUtils.java
new file mode 100644
index 0000000..0e3cd6b
--- /dev/null
+++ b/app/src/main/java/dummydomain/yetanothercallblocker/utils/DebuggingUtils.java
@@ -0,0 +1,124 @@
+package dummydomain.yetanothercallblocker.utils;
+
+import android.content.Context;
+import android.util.Log;
+
+import androidx.core.content.ContextCompat;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+
+import dummydomain.yetanothercallblocker.App;
+
+public class DebuggingUtils {
+
+ private static final String TAG = DebuggingUtils.class.getSimpleName();
+
+ public static void setUpCrashHandler() {
+ Thread.UncaughtExceptionHandler defaultHandler
+ = Thread.getDefaultUncaughtExceptionHandler();
+
+ Thread.UncaughtExceptionHandler customHandler = (t, e) -> {
+ try {
+ handleCrash(e);
+ } finally {
+ if (defaultHandler != null) {
+ defaultHandler.uncaughtException(t, e);
+ }
+ }
+ };
+
+ Thread.setDefaultUncaughtExceptionHandler(customHandler);
+ }
+
+ private static void handleCrash(Throwable e) {
+ boolean logToExternal;
+ try {
+ logToExternal = App.getSettings().getSaveCrashesToExternalStorage();
+ } catch (Exception ignored) {
+ logToExternal = false;
+ }
+
+ try {
+ saveCrashToFile(App.getInstance(), e, logToExternal);
+ } catch (IOException ex) {
+ ex.printStackTrace();
+ }
+
+ boolean saveLogcat;
+ try {
+ saveLogcat = App.getSettings().getSaveLogcatOnCrash();
+ } catch (Exception ignored) {
+ saveLogcat = false;
+ }
+
+ if (saveLogcat) {
+ try {
+ saveLogcatToFile(App.getInstance(), logToExternal);
+ } catch (IOException ex) {
+ ex.printStackTrace();
+ }
+ }
+ }
+
+ public static void saveCrashToFile(Context context, Throwable th, boolean external)
+ throws IOException {
+ saveCrashToFile(context, th, external, getDateString());
+ }
+
+ public static void saveLogcatToFile(Context context, boolean external) throws IOException {
+ saveLogcatToFile(context, external, getDateString());
+ }
+
+ private static void saveCrashToFile(Context context, Throwable th,
+ boolean external, String name)
+ throws IOException {
+ String fileName = getFilesDir(context, external).getAbsolutePath()
+ + "/crash_" + name + ".txt";
+
+ try (PrintWriter writer = new PrintWriter(new FileWriter(fileName))) {
+ th.printStackTrace(writer);
+ }
+ }
+
+ private static void saveLogcatToFile(Context context, boolean external, String name)
+ throws IOException {
+ String path = getFilesDir(context, external).getAbsolutePath()
+ + "/logcat_" + name + ".txt";
+
+ Log.d(TAG, "Saving logcat to " + path);
+ Runtime.getRuntime().exec("logcat -d -f " + path);
+ }
+
+ private static String getDateString() {
+ return new SimpleDateFormat("yyyyMMdd_HHmmssS", Locale.US).format(new Date());
+ }
+
+ private static File getFilesDir(Context context, boolean external) {
+ if (external) {
+ File[] dirs = ContextCompat.getExternalFilesDirs(context, null);
+ for (File dir : dirs) {
+ if (dir != null) return dir;
+ }
+
+ Log.d(TAG, "getFilesDir() no external dirs available");
+ }
+
+/*
+ not secure enough
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ if (!context.isDeviceProtectedStorage()) {
+ context = context.createDeviceProtectedStorageContext();
+ }
+ }
+*/
+
+ return context.getFilesDir();
+ }
+
+}
diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
index a92b5a1..8820b06 100644
--- a/app/src/main/res/values-ru/strings.xml
+++ b/app/src/main/res/values-ru/strings.xml
@@ -99,4 +99,13 @@
Код страны для отзывов
Код страны, используемый для запроса онлайн-отзывов. Предназначен для представления страны звонящего. Оставьте пустым для автоопределения
Некорректный формат кода страны. Значение не обновлено
+ Отладка
+ Сохранять отчёты в общее хранилище
+ Сохранять отчёты об ошибках и логи в общее хранилище. В противном случае отчёты сохраняются в личную папку приложения. Отчёты могут содержать конфиденциальные данные (номера телефонов, имена контактов). Другие приложения с разрешением на доступ к хранилищу могут иметь доступ к этим данным в общем хранилище
+ Сохранять logcat при ошибках
+ Сохранять содержимое logcat при ошибках (в дополнение к обычныму stacktrace)
+ Сохранить logcat
+ Сохранить содержимое logcat в файл в общем хранилище (/sdcard/Android/data/dummydomain.yetanothercallblocker/files/). Отчёты могут содержать конфиденциальные данные (номера телефонов, имена контактов). Другие приложения с разрешением на доступ к хранилищу могут иметь доступ к этим данным в общем хранилище
+ Выполнено
+ Ошибка
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 2ae54e7..5425362 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -111,6 +111,15 @@
Country code for reviews
Country code used in requests for online reviews. Meant to represent the country of the caller. Leave empty for auto-detection
Incorrect country code format. Value is not updated
+ Debugging
+ Save reports to public storage
+ Save crash reports and logs to public storage, otherwise crash reports are saved to a private app folder. The reports may contain sensitive data (phone numbers, contact names). Other apps with storage permission may have access to this data in public storage
+ Save logcat on crash
+ Save logcat output on crash (in addition to a basic stacktrace)
+ Save logcat
+ Save logcat output to a file in public storage (/sdcard/Android/data/dummydomain.yetanothercallblocker/files/). The reports may contain sensitive data (phone numbers, contact names). Other apps with storage permission may have access to this data in public storage
+ Done
+ Error
Open debug screen
Debug
diff --git a/app/src/main/res/xml/root_preferences.xml b/app/src/main/res/xml/root_preferences.xml
index cf9b259..3993b66 100644
--- a/app/src/main/res/xml/root_preferences.xml
+++ b/app/src/main/res/xml/root_preferences.xml
@@ -68,6 +68,23 @@
app:summary="@string/country_code_for_reviews_override_summary"
app:title="@string/country_code_for_reviews_override" />
+
+
+
+
+