Add crash handler and debugging options

This commit is contained in:
xynngh 2020-07-04 15:51:26 +04:00
parent 461b4fc013
commit 2093fa1dee
8 changed files with 210 additions and 0 deletions

View File

@ -10,6 +10,7 @@
<uses-permission android:name="android.permission.ANSWER_PHONE_CALLS" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<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 -->
<uses-permission android:name="android.permission.MODIFY_PHONE_STATE"

View File

@ -6,6 +6,7 @@ import android.content.Context;
import android.os.Build;
import dummydomain.yetanothercallblocker.data.Config;
import dummydomain.yetanothercallblocker.utils.DebuggingUtils;
public class App extends Application {
@ -28,6 +29,8 @@ public class App extends Application {
instance = this;
DebuggingUtils.setUpCrashHandler();
new DeviceProtectedStorageMigrator().migrate(this);
settings = new Settings(getDeviceProtectedStorageContext());

View File

@ -19,6 +19,8 @@ public class Settings extends GenericSettings {
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_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";
@ -166,4 +168,20 @@ public class Settings extends GenericSettings {
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);
}
}

View File

@ -20,8 +20,13 @@ import androidx.preference.PreferenceGroup;
import androidx.preference.PreferenceScreen;
import androidx.preference.SwitchPreferenceCompat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.regex.Pattern;
import dummydomain.yetanothercallblocker.utils.DebuggingUtils;
import dummydomain.yetanothercallblocker.work.UpdateScheduler;
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_SCREEN_ADVANCED = "screenAdvanced";
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());
@ -226,6 +234,12 @@ public class SettingsActivity extends AppCompatActivity
EditTextPreference countryCodeForReviewsPreference
= requireNonNull(findPreference(Settings.PREF_COUNTRY_CODE_FOR_REVIEWS_OVERRIDE));
countryCodeForReviewsPreference.setOnPreferenceChangeListener(countryCodeChangeListener);
requireNonNull((Preference) findPreference(PREF_SAVE_LOGCAT_TO_FILE))
.setOnPreferenceClickListener(preference -> {
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();
}
}
}

View File

@ -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();
}
}

View File

@ -99,4 +99,13 @@
<string name="country_code_for_reviews_override">Код страны для отзывов</string>
<string name="country_code_for_reviews_override_summary">Код страны, используемый для запроса онлайн-отзывов. Предназначен для представления страны звонящего. Оставьте пустым для автоопределения</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>

View File

@ -111,6 +111,15 @@
<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_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="debug_activity_label">Debug</string>

View File

@ -68,6 +68,23 @@
app:summary="@string/country_code_for_reviews_override_summary"
app:title="@string/country_code_for_reviews_override" />
</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>
</PreferenceCategory>