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" /> + + + + +