mirror of
https://gitlab.com/xynngh/YetAnotherCallBlocker.git
synced 2025-06-05 22:19:12 +02:00
Add crash handler and debugging options
This commit is contained in:
@@ -10,6 +10,7 @@
|
|||||||
<uses-permission android:name="android.permission.ANSWER_PHONE_CALLS" />
|
<uses-permission android:name="android.permission.ANSWER_PHONE_CALLS" />
|
||||||
<uses-permission android:name="android.permission.READ_CONTACTS" />
|
<uses-permission android:name="android.permission.READ_CONTACTS" />
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
|
<uses-permission android:name="android.permission.READ_LOG" android:maxSdkVersion="15" />
|
||||||
|
|
||||||
<!-- may be needed for call blocking on Android 9, also requires to be installed as a system app -->
|
<!-- may be needed for call blocking on Android 9, also requires to be installed as a system app -->
|
||||||
<uses-permission android:name="android.permission.MODIFY_PHONE_STATE"
|
<uses-permission android:name="android.permission.MODIFY_PHONE_STATE"
|
||||||
|
@@ -6,6 +6,7 @@ import android.content.Context;
|
|||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
|
||||||
import dummydomain.yetanothercallblocker.data.Config;
|
import dummydomain.yetanothercallblocker.data.Config;
|
||||||
|
import dummydomain.yetanothercallblocker.utils.DebuggingUtils;
|
||||||
|
|
||||||
public class App extends Application {
|
public class App extends Application {
|
||||||
|
|
||||||
@@ -28,6 +29,8 @@ public class App extends Application {
|
|||||||
|
|
||||||
instance = this;
|
instance = this;
|
||||||
|
|
||||||
|
DebuggingUtils.setUpCrashHandler();
|
||||||
|
|
||||||
new DeviceProtectedStorageMigrator().migrate(this);
|
new DeviceProtectedStorageMigrator().migrate(this);
|
||||||
|
|
||||||
settings = new Settings(getDeviceProtectedStorageContext());
|
settings = new Settings(getDeviceProtectedStorageContext());
|
||||||
|
@@ -19,6 +19,8 @@ public class Settings extends GenericSettings {
|
|||||||
public static final String PREF_LAST_UPDATE_CHECK_TIME = "lastUpdateCheckTime";
|
public static final String PREF_LAST_UPDATE_CHECK_TIME = "lastUpdateCheckTime";
|
||||||
public static final String PREF_COUNTRY_CODE_OVERRIDE = "countryCodeOverride";
|
public static final String PREF_COUNTRY_CODE_OVERRIDE = "countryCodeOverride";
|
||||||
public static final String PREF_COUNTRY_CODE_FOR_REVIEWS_OVERRIDE = "countryCodeForReviewsOverride";
|
public static final String PREF_COUNTRY_CODE_FOR_REVIEWS_OVERRIDE = "countryCodeForReviewsOverride";
|
||||||
|
public static final String PREF_SAVE_CRASHES_TO_EXTERNAL_STORAGE = "saveCrashesToExternalStorage";
|
||||||
|
public static final String PREF_SAVE_LOGCAT_ON_CRASH = "saveLogcatOnCrash";
|
||||||
|
|
||||||
static final String SYS_PREFERENCES_VERSION = "__preferencesVersion";
|
static final String SYS_PREFERENCES_VERSION = "__preferencesVersion";
|
||||||
|
|
||||||
@@ -166,4 +168,20 @@ public class Settings extends GenericSettings {
|
|||||||
return code;
|
return code;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean getSaveCrashesToExternalStorage() {
|
||||||
|
return getBoolean(PREF_SAVE_CRASHES_TO_EXTERNAL_STORAGE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSaveCrashesToExternalStorage(boolean flag) {
|
||||||
|
setBoolean(PREF_SAVE_CRASHES_TO_EXTERNAL_STORAGE, flag);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getSaveLogcatOnCrash() {
|
||||||
|
return getBoolean(PREF_SAVE_LOGCAT_ON_CRASH);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSaveLogcatOnCrash(boolean flag) {
|
||||||
|
setBoolean(PREF_SAVE_LOGCAT_ON_CRASH, flag);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -20,8 +20,13 @@ import androidx.preference.PreferenceGroup;
|
|||||||
import androidx.preference.PreferenceScreen;
|
import androidx.preference.PreferenceScreen;
|
||||||
import androidx.preference.SwitchPreferenceCompat;
|
import androidx.preference.SwitchPreferenceCompat;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import dummydomain.yetanothercallblocker.utils.DebuggingUtils;
|
||||||
import dummydomain.yetanothercallblocker.work.UpdateScheduler;
|
import dummydomain.yetanothercallblocker.work.UpdateScheduler;
|
||||||
|
|
||||||
import static java.util.Objects.requireNonNull;
|
import static java.util.Objects.requireNonNull;
|
||||||
@@ -94,6 +99,9 @@ public class SettingsActivity extends AppCompatActivity
|
|||||||
private static final String PREF_CATEGORY_NOTIFICATIONS = "categoryNotifications";
|
private static final String PREF_CATEGORY_NOTIFICATIONS = "categoryNotifications";
|
||||||
private static final String PREF_SCREEN_ADVANCED = "screenAdvanced";
|
private static final String PREF_SCREEN_ADVANCED = "screenAdvanced";
|
||||||
private static final String PREF_COUNTRY_CODES_INFO = "countryCodesInfo";
|
private static final String PREF_COUNTRY_CODES_INFO = "countryCodesInfo";
|
||||||
|
private static final String PREF_SAVE_LOGCAT_TO_FILE = "saveLogcatToFile";
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(SettingsFragment.class);
|
||||||
|
|
||||||
private final UpdateScheduler updateScheduler = UpdateScheduler.get(App.getInstance());
|
private final UpdateScheduler updateScheduler = UpdateScheduler.get(App.getInstance());
|
||||||
|
|
||||||
@@ -226,6 +234,12 @@ public class SettingsActivity extends AppCompatActivity
|
|||||||
EditTextPreference countryCodeForReviewsPreference
|
EditTextPreference countryCodeForReviewsPreference
|
||||||
= requireNonNull(findPreference(Settings.PREF_COUNTRY_CODE_FOR_REVIEWS_OVERRIDE));
|
= requireNonNull(findPreference(Settings.PREF_COUNTRY_CODE_FOR_REVIEWS_OVERRIDE));
|
||||||
countryCodeForReviewsPreference.setOnPreferenceChangeListener(countryCodeChangeListener);
|
countryCodeForReviewsPreference.setOnPreferenceChangeListener(countryCodeChangeListener);
|
||||||
|
|
||||||
|
requireNonNull((Preference) findPreference(PREF_SAVE_LOGCAT_TO_FILE))
|
||||||
|
.setOnPreferenceClickListener(preference -> {
|
||||||
|
saveLogcatToFile();
|
||||||
|
return true;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateCallScreeningPreference() {
|
public void updateCallScreeningPreference() {
|
||||||
@@ -233,5 +247,20 @@ public class SettingsActivity extends AppCompatActivity
|
|||||||
requireNonNull(findPreference(PREF_USE_CALL_SCREENING_SERVICE));
|
requireNonNull(findPreference(PREF_USE_CALL_SCREENING_SERVICE));
|
||||||
callScreeningPref.setChecked(PermissionHelper.isCallScreeningHeld(getActivity()));
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -99,4 +99,13 @@
|
|||||||
<string name="country_code_for_reviews_override">Код страны для отзывов</string>
|
<string name="country_code_for_reviews_override">Код страны для отзывов</string>
|
||||||
<string name="country_code_for_reviews_override_summary">Код страны, используемый для запроса онлайн-отзывов. Предназначен для представления страны звонящего. Оставьте пустым для автоопределения</string>
|
<string name="country_code_for_reviews_override_summary">Код страны, используемый для запроса онлайн-отзывов. Предназначен для представления страны звонящего. Оставьте пустым для автоопределения</string>
|
||||||
<string name="country_code_incorrect_format">Некорректный формат кода страны. Значение не обновлено</string>
|
<string name="country_code_incorrect_format">Некорректный формат кода страны. Значение не обновлено</string>
|
||||||
|
<string name="settings_category_debugging">Отладка</string>
|
||||||
|
<string name="save_crashes_to_external_storage">Сохранять отчёты в общее хранилище</string>
|
||||||
|
<string name="save_crashes_to_external_storage_summary">Сохранять отчёты об ошибках и логи в общее хранилище. В противном случае отчёты сохраняются в личную папку приложения. Отчёты могут содержать конфиденциальные данные (номера телефонов, имена контактов). Другие приложения с разрешением на доступ к хранилищу могут иметь доступ к этим данным в общем хранилище</string>
|
||||||
|
<string name="save_logcat_on_crash">Сохранять logcat при ошибках</string>
|
||||||
|
<string name="save_logcat_on_crash_summary">Сохранять содержимое logcat при ошибках (в дополнение к обычныму stacktrace)</string>
|
||||||
|
<string name="save_logcat_to_file">Сохранить logcat</string>
|
||||||
|
<string name="save_logcat_to_file_summary">Сохранить содержимое logcat в файл в общем хранилище (/sdcard/Android/data/dummydomain.yetanothercallblocker/files/). Отчёты могут содержать конфиденциальные данные (номера телефонов, имена контактов). Другие приложения с разрешением на доступ к хранилищу могут иметь доступ к этим данным в общем хранилище</string>
|
||||||
|
<string name="save_logcat_to_file_done">Выполнено</string>
|
||||||
|
<string name="save_logcat_to_file_error">Ошибка</string>
|
||||||
</resources>
|
</resources>
|
@@ -111,6 +111,15 @@
|
|||||||
<string name="country_code_for_reviews_override">Country code for reviews</string>
|
<string name="country_code_for_reviews_override">Country code for reviews</string>
|
||||||
<string name="country_code_for_reviews_override_summary">Country code used in requests for online reviews. Meant to represent the country of the caller. Leave empty for auto-detection</string>
|
<string name="country_code_for_reviews_override_summary">Country code used in requests for online reviews. Meant to represent the country of the caller. Leave empty for auto-detection</string>
|
||||||
<string name="country_code_incorrect_format">Incorrect country code format. Value is not updated</string>
|
<string name="country_code_incorrect_format">Incorrect country code format. Value is not updated</string>
|
||||||
|
<string name="settings_category_debugging">Debugging</string>
|
||||||
|
<string name="save_crashes_to_external_storage">Save reports to public storage</string>
|
||||||
|
<string name="save_crashes_to_external_storage_summary">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</string>
|
||||||
|
<string name="save_logcat_on_crash">Save logcat on crash</string>
|
||||||
|
<string name="save_logcat_on_crash_summary">Save logcat output on crash (in addition to a basic stacktrace)</string>
|
||||||
|
<string name="save_logcat_to_file">Save logcat</string>
|
||||||
|
<string name="save_logcat_to_file_summary">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</string>
|
||||||
|
<string name="save_logcat_to_file_done">Done</string>
|
||||||
|
<string name="save_logcat_to_file_error">Error</string>
|
||||||
|
|
||||||
<string name="open_debug_activity">Open debug screen</string>
|
<string name="open_debug_activity">Open debug screen</string>
|
||||||
<string name="debug_activity_label">Debug</string>
|
<string name="debug_activity_label">Debug</string>
|
||||||
|
@@ -68,6 +68,23 @@
|
|||||||
app:summary="@string/country_code_for_reviews_override_summary"
|
app:summary="@string/country_code_for_reviews_override_summary"
|
||||||
app:title="@string/country_code_for_reviews_override" />
|
app:title="@string/country_code_for_reviews_override" />
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
|
<PreferenceCategory
|
||||||
|
app:key="categoryDebugging"
|
||||||
|
app:title="@string/settings_category_debugging">
|
||||||
|
<SwitchPreferenceCompat
|
||||||
|
app:key="saveCrashesToExternalStorage"
|
||||||
|
app:summary="@string/save_crashes_to_external_storage_summary"
|
||||||
|
app:title="@string/save_crashes_to_external_storage" />
|
||||||
|
<SwitchPreferenceCompat
|
||||||
|
app:key="saveLogcatOnCrash"
|
||||||
|
app:summary="@string/save_logcat_on_crash_summary"
|
||||||
|
app:title="@string/save_logcat_on_crash" />
|
||||||
|
<Preference
|
||||||
|
app:key="saveLogcatToFile"
|
||||||
|
app:persistent="false"
|
||||||
|
app:summary="@string/save_logcat_to_file_summary"
|
||||||
|
app:title="@string/save_logcat_to_file" />
|
||||||
|
</PreferenceCategory>
|
||||||
</PreferenceScreen>
|
</PreferenceScreen>
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user