diff --git a/app/src/main/java/com/hippo/ehviewer/AppConfig.java b/app/src/main/java/com/hippo/ehviewer/AppConfig.java index edd5ddf0c..03a9f30fe 100644 --- a/app/src/main/java/com/hippo/ehviewer/AppConfig.java +++ b/app/src/main/java/com/hippo/ehviewer/AppConfig.java @@ -38,6 +38,7 @@ public class AppConfig { private static final String PARSE_ERROR = "parse_error"; private static final String LOGCAT = "logcat"; private static final String DATA = "data"; + private static final String CRASH = "crash"; private static Context sContext; @@ -107,6 +108,11 @@ public class AppConfig { return getDirInExternalAppDir(DATA); } + @Nullable + public static File getExternalCrashDir() { + return getDirInExternalAppDir(CRASH); + } + @Nullable public static File createExternalTempFile() { return FileUtils.createTempFile(getExternalTempDir(), null); diff --git a/app/src/main/java/com/hippo/ehviewer/Crash.java b/app/src/main/java/com/hippo/ehviewer/Crash.java new file mode 100644 index 000000000..ea486294a --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/Crash.java @@ -0,0 +1,156 @@ +/* + * Copyright 2019 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer; + +import android.app.Activity; +import android.content.Context; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.os.Build; +import android.os.Debug; +import androidx.annotation.NonNull; +import com.hippo.scene.StageActivity; +import com.hippo.util.PackageUtils; +import com.hippo.util.ReadableTime; +import com.hippo.yorozuya.FileUtils; +import com.hippo.yorozuya.IOUtils; +import com.hippo.yorozuya.OSUtils; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; + +public final class Crash { + private Crash() {} + + @NonNull + private static String avoidNull(String str) { + return null != str ? str : "null"; + } + + private static void collectInfo(Context context, FileWriter fw) throws IOException { + try { + PackageManager pm = context.getPackageManager(); + PackageInfo pi = pm.getPackageInfo(context.getPackageName(), PackageManager.GET_ACTIVITIES); + if (pi != null) { + String versionName = pi.versionName == null ? "null" : pi.versionName; + String versionCode = String.valueOf(pi.versionCode); + fw.write("======== PackageInfo ========\r\n"); + fw.write("PackageName=");fw.write(pi.packageName);fw.write("\r\n"); + fw.write("VersionName=");fw.write(versionName);fw.write("\r\n"); + fw.write("VersionCode=");fw.write(versionCode);fw.write("\r\n"); + String signature = PackageUtils.getSignature(context, pi.packageName); + fw.write("Signature=");fw.write(null != signature ? signature : "null");fw.write("\r\n"); + fw.write("\r\n"); + } + } catch (PackageManager.NameNotFoundException e) { + fw.write("======== PackageInfo ========\r\n"); + fw.write("Can't get package information\r\n"); + fw.write("\r\n"); + } + + // Runtime + String topActivityClazzName = "null"; + String topSceneClazzName = "null"; + try { + Activity topActivity = ((EhApplication) context.getApplicationContext()).getTopActivity(); + if (null != topActivity) { + topActivityClazzName = topActivity.getClass().getName(); + if (topActivity instanceof StageActivity) { + Class clazz = ((StageActivity) topActivity).getTopSceneClass(); + if (clazz != null) { + topSceneClazzName = clazz.getName(); + } + } + } + } catch (Throwable e) { + // Ignore + } + fw.write("======== Runtime ========\r\n"); + fw.write("TopActivity=");fw.write(avoidNull(topActivityClazzName));fw.write("\r\n"); + fw.write("TopScene=");fw.write(avoidNull(topSceneClazzName));fw.write("\r\n"); + fw.write("\r\n"); + + // Device info + fw.write("======== DeviceInfo ========\r\n"); + fw.write("BOARD=");fw.write(Build.BOARD);fw.write("\r\n"); + fw.write("BOOTLOADER=");fw.write(Build.BOOTLOADER);fw.write("\r\n"); + fw.write("CPU_ABI=");fw.write(Build.CPU_ABI);fw.write("\r\n"); + fw.write("CPU_ABI2=");fw.write(Build.CPU_ABI2);fw.write("\r\n"); + fw.write("DEVICE=");fw.write(Build.DEVICE);fw.write("\r\n"); + fw.write("DISPLAY=");fw.write(Build.DISPLAY);fw.write("\r\n"); + fw.write("FINGERPRINT=");fw.write(Build.FINGERPRINT);fw.write("\r\n"); + fw.write("HARDWARE=");fw.write(Build.HARDWARE);fw.write("\r\n"); + fw.write("HOST=");fw.write(Build.HOST);fw.write("\r\n"); + fw.write("ID=");fw.write(Build.ID);fw.write("\r\n"); + fw.write("MANUFACTURER=");fw.write(Build.MANUFACTURER);fw.write("\r\n"); + fw.write("MODEL=");fw.write(Build.MODEL);fw.write("\r\n"); + fw.write("PRODUCT=");fw.write(Build.PRODUCT);fw.write("\r\n"); + fw.write("RADIO=");fw.write(Build.getRadioVersion());fw.write("\r\n"); + fw.write("SERIAL=");fw.write(Build.SERIAL);fw.write("\r\n"); + fw.write("TAGS=");fw.write(Build.TAGS);fw.write("\r\n"); + fw.write("TYPE=");fw.write(Build.TYPE);fw.write("\r\n"); + fw.write("USER=");fw.write(Build.USER);fw.write("\r\n"); + fw.write("CODENAME=");fw.write(Build.VERSION.CODENAME);fw.write("\r\n"); + fw.write("INCREMENTAL=");fw.write(Build.VERSION.INCREMENTAL);fw.write("\r\n"); + fw.write("RELEASE=");fw.write(Build.VERSION.RELEASE);fw.write("\r\n"); + fw.write("SDK=");fw.write(Integer.toString(Build.VERSION.SDK_INT));fw.write("\r\n"); + fw.write("MEMORY=");fw.write( + FileUtils.humanReadableByteCount(OSUtils.getAppAllocatedMemory(), false));fw.write("\r\n"); + fw.write("MEMORY_NATIVE=");fw.write(FileUtils.humanReadableByteCount(Debug.getNativeHeapAllocatedSize(), false));fw.write("\r\n"); + fw.write("MEMORY_MAX=");fw.write(FileUtils.humanReadableByteCount(OSUtils.getAppMaxMemory(), false));fw.write("\r\n"); + fw.write("MEMORY_TOTAL=");fw.write(FileUtils.humanReadableByteCount(OSUtils.getTotalMemory(), false));fw.write("\r\n"); + fw.write("\r\n"); + } + + private static void getThrowableInfo(Throwable t, FileWriter fw) { + PrintWriter printWriter = new PrintWriter(fw); + t.printStackTrace(printWriter); + Throwable cause = t.getCause(); + while (cause != null) { + cause.printStackTrace(printWriter); + cause = cause.getCause(); + } + } + + public static void saveCrashLog(Context context, Throwable t) { + File dir = AppConfig.getExternalCrashDir(); + if (dir == null) { + return; + } + + String nowString = ReadableTime.getFilenamableTime(System.currentTimeMillis()); + String fileName = "crash-" + nowString + ".log"; + File file = new File(dir, fileName); + + FileWriter fw = null; + try { + fw = new FileWriter(file); + fw.write("TIME=");fw.write(nowString);fw.write("\r\n"); + fw.write("\r\n"); + collectInfo(context, fw); + fw.write("======== CrashInfo ========\r\n"); + getThrowableInfo(t, fw); + fw.write("\r\n"); + fw.flush(); + } catch (Exception e) { + file.delete(); + } finally { + IOUtils.closeQuietly(fw); + } + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/EhApplication.java b/app/src/main/java/com/hippo/ehviewer/EhApplication.java index 70a3a9d89..799f43cc0 100644 --- a/app/src/main/java/com/hippo/ehviewer/EhApplication.java +++ b/app/src/main/java/com/hippo/ehviewer/EhApplication.java @@ -104,6 +104,19 @@ public class EhApplication extends RecordingApplication { public void onCreate() { instance = this; + Thread.UncaughtExceptionHandler handler = Thread.getDefaultUncaughtExceptionHandler(); + Thread.setDefaultUncaughtExceptionHandler((t, e) -> { + try { + if (Settings.getSaveCrashLog()) { + Crash.saveCrashLog(instance, e); + } + } catch (Throwable ignored) { } + + if (handler != null) { + handler.uncaughtException(t, e); + } + }); + super.onCreate(); GetText.initialize(this); diff --git a/app/src/main/java/com/hippo/ehviewer/Settings.java b/app/src/main/java/com/hippo/ehviewer/Settings.java index ddc97a981..ba9579689 100755 --- a/app/src/main/java/com/hippo/ehviewer/Settings.java +++ b/app/src/main/java/com/hippo/ehviewer/Settings.java @@ -950,6 +950,13 @@ public class Settings { putBoolean(KEY_SAVE_PARSE_ERROR_BODY, value); } + private static final String KEY_SAVE_CRASH_LOG = "save_crash_log"; + private static final boolean DEFAULT_SAVE_CRASH_LOG = false; + + public static boolean getSaveCrashLog() { + return getBoolean(KEY_SAVE_CRASH_LOG, DEFAULT_SAVE_CRASH_LOG); + } + public static final String KEY_SECURITY = "security"; public static final String DEFAULT_SECURITY = ""; diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index d1726b9df..f348f1f5b 100755 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -503,6 +503,8 @@ 高级 解析失败时保存页面内容 页面内容可能含有隐私敏感信息 + 应用崩溃时保存错误日志 + 错误日志可以帮助开发者修正问题 导出日志 保存日志至外置存储器 导出日志失败 diff --git a/app/src/main/res/values-zh-rHK/strings.xml b/app/src/main/res/values-zh-rHK/strings.xml index 2d99e782b..6a799c8c2 100755 --- a/app/src/main/res/values-zh-rHK/strings.xml +++ b/app/src/main/res/values-zh-rHK/strings.xml @@ -502,6 +502,8 @@ 高級 解析失敗時保存頁面內容 頁面內容可能含有隱私敏感信息 + 應用崩潰時保存錯誤日誌 + 錯誤日誌可以幫助開發者修正問題 導出日誌 保存日誌至外置存儲器 導出日誌失敗 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 772f877af..4cb64cb2e 100755 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -502,6 +502,8 @@ 進階 解析失敗時儲存網頁的內容 網頁內容可能含有敏感的隱私資料 + 應用崩潰時儲存錯誤日誌 + 錯誤日誌可以幫助開發者修正問題 傾印log 儲存log紀錄至外接儲存裝置中 無法傾印log紀錄 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a75696f01..3a198f7e0 100755 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -546,6 +546,8 @@ Advanced Save html content when parsing error Html content may be privacy-sensitive + Save crash log when app crashes + Crash logs help developers fix bugs Dump logcat Save logcat to external storage Dump logcat failed diff --git a/app/src/main/res/xml/advanced_settings.xml b/app/src/main/res/xml/advanced_settings.xml index 1d028d918..839337f5f 100644 --- a/app/src/main/res/xml/advanced_settings.xml +++ b/app/src/main/res/xml/advanced_settings.xml @@ -24,6 +24,12 @@ android:summary="@string/settings_advanced_save_parse_error_body_summary" android:defaultValue="@bool/beta"/> + +