190 Commits
1.1 ... master

Author SHA1 Message Date
c301d5aab7 chore: update target sdk to 36 2025-08-19 17:48:54 +08:00
2c673280cc chore: update jdk to 21 2025-08-19 17:48:32 +08:00
036a3f90e9 chore: bump gradle to 8.14.3 2025-08-19 17:48:24 +08:00
5b6caf4769 chore: bump dependencies 2025-08-19 17:48:15 +08:00
140b74fcab refactor: update KavaRef usage to 1.0.1 2025-07-06 21:42:17 +08:00
e59b7f501a refactor: migrate and update to YukiHookAPI 1.3.0 2025-06-25 23:22:59 +08:00
ab03c74659 docs: update README, README-zh-CN, README-ja-JP 2025-06-24 14:11:02 +08:00
a17e823a46 docs: update README-zh-CN 2025-06-20 12:52:05 +08:00
5a60ef47b5 chore: disable type auto conversion for sweet-property 2025-05-09 23:19:48 +08:00
Blue cat
9092e4d17a Support Android 15 (#370) 2025-05-08 00:22:46 +08:00
d70cf11a42 fix: removeExtra below onBackPressed in AppErrorsDetailActivity 2025-03-17 14:06:57 +08:00
8b73ad6610 chore: update project files 2025-03-17 14:05:23 +08:00
652c8383c1 chore: bump dependencies 2025-03-16 23:40:50 +08:00
fbabb41f33 chore: bump gradle to 8.13 2025-03-16 23:40:45 +08:00
f0b52d54c5 chore: update project files 2025-03-16 23:40:37 +08:00
40747a81ea feat: support share errors stacktrace with file 2025-03-16 02:30:46 +08:00
cabf025082 chore: bump ci to v4 2025-02-22 01:16:07 +08:00
248daa5c80 chore: update .gitignore 2025-02-22 01:15:58 +08:00
d253b26294 docs: update license 2025-01-13 11:19:00 +08:00
d7c26180ae refactor: support Android 15 edge-to-edge system bars 2024-11-25 18:55:00 +08:00
0facd0bcbf chore: update target sdk to 35 2024-11-10 21:42:06 +08:00
32855f4b61 chore: update project files 2024-11-10 21:39:25 +08:00
183999ca30 chore: bump dependencies 2024-11-10 21:39:15 +08:00
0ef91d8578 chore: bump gradle to 8.10.2 2024-11-10 21:19:17 +08:00
kitadai31
ff948fd1ae Update Japanese translations (#300) 2024-09-20 18:02:17 +08:00
Horange321
7c237dd024 <Add>monochrome 图标 (#288) 2024-08-09 11:17:40 +08:00
28749a9226 chore: update .editorconfig 2024-06-21 10:11:41 +08:00
46b810e409 chore: update project files 2024-06-21 10:11:36 +08:00
b497173e02 chore: bump dependencies 2024-06-20 11:32:44 +08:00
18c49405a8 chore: bump gradle to 8.7 2024-06-20 11:32:41 +08:00
7d96a5097e chore: update .editorconfig 2024-06-20 11:32:36 +08:00
Re*Index. (ot_inc)
773a5258df AppErrorDemo Japanese Update. (#239)
* Update strings.xml

* Update strings.xml

* Update strings.xml

* Update strings.xml

* Update strings.xml

* Update strings.xml

* Update strings.xml

* Update strings.xml

* Update strings.xml

* Add files via upload

* Update README-ja-JP.md

* Add Japanese README link

* Update README-zh-CN.md

* Add Japanese README link

* Add Prevent misoperation of error dialog text

text update.

* Update Japanese
2024-03-28 19:10:53 +08:00
Re*Index. (ot_inc)
59b835dd2e Update Japanese and Create Japanese README. (#237)
* Update strings.xml

* Update strings.xml

* Update strings.xml

* Update strings.xml

* Update strings.xml

* Update strings.xml

* Update strings.xml

* Update strings.xml

* Update strings.xml

* Add files via upload

* Update README-ja-JP.md

* Add Japanese README link

* Update README-zh-CN.md

* Add Japanese README link

* Add Prevent misoperation of error dialog text

text update.
2024-03-21 12:55:32 +08:00
Mufanc
69a96cb819 feat: prevent misoperation (#228)
* feat: prevent misoperation

* feat: add switch for misoperation prevention

* feat: add English and Chinese simplified translation
2024-03-05 14:55:03 +08:00
53c6f8a204 chore: bump dependencies 2024-02-20 11:43:39 +08:00
d1480ae8f3 chore: bump gradle to 8.6 2024-02-20 11:43:33 +08:00
Re*Index. (ot_inc)
91cfda35bc Major Fix in Japanese translation. (#231) 2024-02-20 09:23:05 +08:00
c774dbebf2 chore: update .editorconfig 2024-01-13 21:26:32 +08:00
e81ca4ec10 docs: update copyright date to 2024 for all existing files 2024-01-01 03:22:16 +08:00
cafb664c1a style: merge to new ktlint version & rules 2023-12-29 21:31:30 +08:00
7939d25bb6 chore: bump dependencies 2023-12-29 00:13:16 +08:00
1b6bc71e51 chore: some tweaks in build.gradle.kts 2023-12-29 00:13:09 +08:00
9483cd7940 chore: bump gradle to 8.5 2023-12-28 23:58:37 +08:00
d1bd76221d chore: update .gitignore 2023-12-28 23:58:25 +08:00
b13df7179a docs: use relative link 2023-12-11 02:37:13 +08:00
10a51c800b chore: update project files 2023-12-11 02:37:10 +08:00
308afa7d6a chore: bump dependency versions 2023-12-11 00:41:15 +08:00
7775c82caa docs: update piracy statement 2023-12-11 00:29:00 +08:00
60b3963df1 docs: update promotion 2023-11-18 18:10:28 +08:00
b7ffdf3231 chore: bump "com.highcapable.sweetdependency" version to 1.0.4 2023-11-14 01:01:48 +08:00
5f96817d8c chore: bump "com.highcapable.sweetproperty" version to 1.0.5 2023-11-08 15:12:33 +08:00
063b093eed chore: bump dependencies 2023-11-04 03:46:49 +08:00
fb388ee783 chore: bump plugin versions
- bump "com.highcapable.sweetdependency" version to 1.0.3
- bump "com.highcapable.sweetproperty" version to 1.0.4
2023-11-04 03:40:41 +08:00
389f3a69ee Bump version to 1.3 2023-11-03 20:05:58 +08:00
e0b8799a9b feat: ignore user-terminated crash 2023-11-03 19:51:44 +08:00
a64ad86e64 feat: add stack trace share optional dialog 2023-11-03 18:46:07 +08:00
2d42581e36 docs: update release channel 2023-10-26 21:40:50 +08:00
936d66a81e feat: add errors app's target and min sdk record 2023-10-22 23:13:26 +08:00
66b9407b34 fix: catch toast when no looper 2023-10-22 22:36:29 +08:00
c8a0631034 feat: add module app version in shared errors info data 2023-10-21 17:13:57 +08:00
32ca130da0 feat: use handleAppCrashLSPB method on system higher than Android 11 2023-10-21 02:30:11 +08:00
273dca6042 chore: update target sdk to 34 2023-10-21 01:27:10 +08:00
315dfd7e22 feat(docs): update YukiHookAPI owner link 2023-10-21 01:26:20 +08:00
b5baf8243e refactor: remove DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION 2023-10-21 01:25:04 +08:00
2064c01350 chore: bump "com.highcapable.flexilocale" version to 1.0.1 2023-10-13 20:02:06 +08:00
72d76a486c refactor: migrate i18ns generation to FlexiLocale plugin 2023-10-13 18:49:15 +08:00
d215440e83 refactor: migrate to YukiHookAPI new usage 2023-10-07 21:15:15 +08:00
52367b5c41 chore: bump dependency versions 2023-10-07 21:15:05 +08:00
061b73fef7 chore: bump plugin versions
- bump "com.highcapable.sweetdependency" version to 1.0.2
- bump "com.highcapable.sweetproperty" version to 1.0.3
2023-09-26 09:10:38 +08:00
13e63a8fb3 refactor: change "Url" to "URL" 2023-09-21 00:42:16 +08:00
7eacb56857 refactor: add new R8 rules to fix possible problems 2023-09-19 08:13:54 +08:00
684456bb5b style: rearrange imports 2023-09-19 07:41:23 +08:00
3b5aee9fb8 fix: data format and style problems 2023-09-19 07:30:58 +08:00
86ba9749be ci: use allowed symbols 2023-09-19 05:29:00 +08:00
4fff1d7c17 docs: optimize comments 2023-09-19 05:13:25 +08:00
c1e584d739 feat: lots of changes
- add BuildConfigWrapper
- add project promote
- add ci version tag support
- change app analytics config item show when available
- fix system api compat issues
2023-09-19 05:12:44 +08:00
ccc50d720e feat: add i18n strings 2023-09-19 05:10:35 +08:00
8df2fd5c14 docs: update README, README-zh-CN 2023-09-19 05:09:43 +08:00
99472dedc4 refactor: add package name 2023-09-19 05:07:19 +08:00
d22c5801b2 docs: add icon in img-src 2023-09-19 05:06:26 +08:00
0a87f13af7 ci: optimize and add artifacts post to Telegram 2023-09-19 05:02:57 +08:00
fd7bb9bf77 chore: add Android 14 option 2023-09-19 05:02:37 +08:00
b0a6c71300 chore: migrate build script from groovy to kts
- using SweetDependency, SweetProperty
- merge singing key file configs to properties
- update gradle and dependencies
2023-09-19 05:02:15 +08:00
00512d6f95 chore: clean up build step files 2023-09-19 05:00:58 +08:00
4bc2d84e7d [Change Commit Specification] Use the new commit spec from here on
child commits:
chore: add .editorconfig
2023-09-19 04:59:33 +08:00
Fankesyooni
75e6f1c16c Merge pull request #132 from ZQDesigned/master
[BUG FIX] Deprecated AppErrorInfo when dialog covers AppErrorsDetailActivity
2023-06-21 02:03:30 +08:00
886e8d1e37 Complete method annotation 2023-06-21 01:43:29 +08:00
a876854010 Revert some changes and Refactor code style again 2023-06-21 01:32:06 +08:00
9834b6c8dd Refactor code style 2023-06-21 01:14:17 +08:00
3a5a1df270 Fix:deprecated AppErrorInfo when dialog covers AppErrorsDetailActivity 2023-06-21 00:22:43 +08:00
4afe549a8d Modify refactor the locales config code 2023-05-09 11:51:11 +08:00
3a7c97a1ac Update Gradle dependencies 2023-05-09 11:50:06 +08:00
Fankesyooni
2b8b769aa9 Merge pull request #112 from huajijam/master
feat: Per-app language preferences
2023-05-04 22:34:22 +08:00
‭huajijam
fa793d764b feat: Per-app language preferences 2023-05-04 21:42:35 +08:00
4f9ef060ea Update Gradle dependencies 2023-04-25 07:30:55 +08:00
47be6b10b7 Modify merge to YukiHookAPI new usage 2023-04-25 06:29:55 +08:00
340034b531 Update YukiHookAPI 2023-04-25 06:29:35 +08:00
58aa7bc498 Update YukiHookAPI 2023-04-21 01:26:35 +08:00
06324d104a Update version to 1.25 2023-04-17 06:41:55 +08:00
0ed2cf4903 Modify merge to YukiHookAPI new usage 2023-04-17 06:38:05 +08:00
d278faf7c9 Modify merge to YukiHookAPI new usage 2023-04-17 06:06:35 +08:00
823c5c690f Update Gradle dependencies 2023-04-17 06:05:05 +08:00
a4e1c82ad0 Update YukiHookAPI 2023-04-17 06:03:55 +08:00
07e13ccf04 Modify merge contents of build.gradle into constant definitions 2023-04-15 23:01:55 +08:00
e19f8ea4af Update Gradle dependencies 2023-04-14 17:57:05 +08:00
45c337d475 Modify change uninstalled apps or unknown apps to show their package name for users 2023-04-09 20:52:55 +08:00
8690e8afdb Modify change apps related functions returned "" for default value in FunctionFactory 2023-04-09 20:51:55 +08:00
ec7dcabdb8 Added loading view and errors records count in AppErrorsRecordActivity, activity_app_errors_record 2023-04-09 20:31:55 +08:00
0db82b5630 Added i18n strings 2023-04-09 20:30:35 +08:00
1207dbb561 Modify replace YukiHookAPI to YukiReflection in demo-app 2023-04-08 00:06:05 +08:00
785208a5bb Update Gradle & Kotlin
- Update Kotlin version to 1.8.20
- Update Gradle version to 8.0.2
- Update Gradle dependencies
2023-04-08 00:03:55 +08:00
3dbd80e06e Added start history chart in README 2023-02-19 14:39:55 +08:00
f452bb2b32 Modify change ProgressBar to CircularProgressIndicator in activity_config 2023-02-08 13:42:55 +08:00
39b779eab9 Added new bug report issues template 2023-02-08 13:25:55 +08:00
948095c016 Fix CI compiler problem and add debug version on CI 2023-02-08 13:03:15 +08:00
1a7775d5cc Fix "GitHub" spelling in all files 2023-02-07 05:27:55 +08:00
00cfca12b0 Modify change ProgressBar to CircularProgressIndicator in DialogBuilderFactory 2023-02-03 23:17:15 +08:00
556951c27c Fix some problems in CompoundButtonFactory 2023-02-03 03:39:05 +08:00
b478654008 Modify merge to CompoundButtonDataBinder and optimize code in AppErrorsDetailActivity 2023-02-03 02:26:09 +08:00
b47543e71a Modify merge to CompoundButtonDataBinder and optimize code in MainActivity 2023-02-03 02:22:05 +08:00
6ffe4bc15d Update YukiHookAPI 2023-02-01 04:32:05 +08:00
bb94689baa Update .gitignore 2023-02-01 04:31:10 +08:00
6803b60dca Update Gradle dependencies 2023-01-31 23:50:05 +08:00
82946e55fe Update version to 1.2 2023-01-23 12:19:06 +08:00
910b3523a3 Update copyright date to 2023 for all existing files 2023-01-23 12:13:50 +08:00
a6fd0b699c Modify remove scrollbar and reset scroll view when view changed in AppErrorsDetailActivity, activity_app_errors_detail 2023-01-23 11:59:35 +08:00
6ddbfb5444 Added check for updates feature from GitHub Release 2023-01-23 11:43:55 +08:00
fe4f91291e Added i18n strings 2023-01-23 11:42:04 +08:00
5e0336a434 Added disable automatic wrapping error stack trace contents function in ConfigData, AppErrorsDetailActivity, activity_app_errors_detail 2023-01-22 15:51:31 +08:00
55a4d68a18 Added i18n strings 2023-01-22 15:49:31 +08:00
358ac848e2 Modify remove screenOrientation parameter for AppErrorsDetailActivity in AndroidManifest 2023-01-22 15:32:18 +08:00
567d0a7b6c Modify add cpuAbi, versionName, versionCode default value for legacy data transfer of AppErrorsInfoBean in AppErrorsRecordData 2023-01-22 14:55:16 +08:00
4d5dadae8b Modify make crashed apps info persistence and add more info to stackOutputShareContent, stackOutputFileContent in AppErrorsInfoBean, FrameworkHooker, AppErrorsDetailActivity 2023-01-22 14:51:15 +08:00
9dce54f326 Added appVersionNameOf, appVersionCodeOf functions and merge to appVersionBrandOf function in FunctionFactory 2023-01-22 14:23:41 +08:00
37af877a7d Modify change the judgment logic when fetched installed packages list is empty in FrameworkHooker 2023-01-22 14:11:05 +08:00
59f5b27d19 Modify change view id "app_api" to "app_cpu_abi" in AppErrorsDetailActivity, activity_app_errors_detail 2023-01-22 14:05:08 +08:00
1299779868 Modify change apps config filters function with 3 types such as user apps, system apps and all apps 2023-01-22 13:05:05 +08:00
27c5e92879 Added i18n strings and remove some unused translations 2023-01-22 13:03:02 +08:00
d22745f2ea Modify make radio button singleLine in dia_app_config 2023-01-22 12:59:38 +08:00
dfcee71168 Modify add @throws code note in putAppShowingType function in AppErrorsConfigData 2023-01-22 11:49:37 +08:00
5b30499cc3 Update YukiHookAPI 2023-01-21 00:53:45 +08:00
1889b848b0 Update Android Gradle Plugin to 7.4.0 2023-01-21 00:49:40 +08:00
c349ee5dc7 Added global app config template function and remove old implementations 2023-01-20 03:09:25 +08:00
72e99bd4f2 Added i18n strings and fix some translations 2023-01-20 02:59:15 +08:00
2d9550d5b9 Modify remove fetch installed packages list log and only print for empty list in FrameworkHooker 2023-01-20 02:33:25 +08:00
27e068898e Fix translations for i18n strings 2023-01-19 23:59:28 +08:00
92a0591d5b Update Gradle dependencies 2023-01-19 22:29:12 +08:00
2a97dcbc06 Added access root failed tips dialog in FrameworkTool 2023-01-19 21:53:58 +08:00
0dc12ae3e6 Added i18n strings 2023-01-19 21:53:51 +08:00
075850d239 Modify remove @Keep in all beans and add @SerializedName in AppErrorsInfoBean 2023-01-19 13:07:26 +08:00
c48a23f07d Modify change empty mark for AppErrorsInfoBean in AppErrorsInfoBean, FrameworkHooker 2023-01-19 12:57:02 +08:00
cf1796b7cb Modify optimize code in FrameworkHooker 2023-01-17 13:45:16 +08:00
5bb1f38146 Modify ignored "android" package name when getting app list data in FrameworkHooker 2023-01-17 13:13:21 +08:00
a055d7f53d Modify rename ui/view to ui/widget 2023-01-17 11:15:48 +08:00
2eae45a640 Modify change the way of getting app list data in FrameworkHooker 2023-01-17 05:12:23 +08:00
fd168f8810 Modify change multi-user app display name with suffix its user id in FrameworkHooker 2023-01-17 05:02:15 +08:00
37580519db Modify change appNameOf function returned default blank content to "unknown" in FunctionFactory 2023-01-17 04:56:37 +08:00
5144494f82 Modify make app errors record files sorted by last modified date in AppErrorsRecordData 2023-01-17 04:35:47 +08:00
01a6c1ffa5 Fix the previous fix caused the text could not be selected in AppErrorsDetailActivity, activity_app_errors_detail 2023-01-17 03:59:48 +08:00
2aaa422a56 Modify change AppErrorsData to AppErrorsProcessData in FrameworkHooker 2023-01-17 03:22:37 +08:00
7c0c1754e9 Modify merge to new way to save and read the app errors record data 2023-01-17 03:17:37 +08:00
587c718d0a Added List.toArrayList function in FunctionFactory 2023-01-17 03:03:27 +08:00
6b2b538047 Added Any?.toJsonOrNull, String.toEntityOrNull functions in GsonFormatFactory 2023-01-17 01:37:08 +08:00
6a5fee830c Fix code style in build.gradle 2023-01-16 23:36:20 +08:00
1d0cc1d24f Fix the central color problem of views such as CheckBox 2023-01-16 22:40:26 +08:00
eeaf386635 Fix the interface automatically slides up problem on Android versions lower than 10 in activity_app_errors_detail 2023-01-16 22:21:21 +08:00
a15b5b008d Added "Go It Now" button in app errors dialog for unable get app errors record in AppErrorsDetailActivity 2023-01-16 22:20:02 +08:00
37962fa12f Modify merge app errors functions implementation code to AppErrorsData in FrameworkHooker 2023-01-15 14:35:06 +08:00
3798479c23 Fix crashed apps user id mismatch problem in FrameworkHooker, AppErrorsInfoBean 2023-01-15 04:12:50 +08:00
a214f5773f Fix the app first crash report not responded problem in some customize ROMs in FrameworkHooker 2023-01-15 03:57:41 +08:00
aeda0f183e Modify support Android 7.0 2023-01-15 02:39:03 +08:00
c6d5f07b8c Fix PackageList class not exist problem in Android 8.1 and fix app errors dialog no show problem in FrameworkHooker 2023-01-15 02:37:16 +08:00
5991d976b9 Modify optimize code format in MainActivity 2023-01-15 01:35:02 +08:00
3fb4e4f375 Fix no onCreate method in AppErrorDialog class problem in Android 10 in FrameworkHooker 2023-01-14 01:55:52 +08:00
8c4a1ea5f0 Modify make HookEntry singleton 2023-01-14 01:55:29 +08:00
7749cb9aeb Modify merge to YukiHookAPI new usage 2023-01-14 01:55:22 +08:00
9d9cb473e8 Update Gradle & Kotlin
- Update Kotlin version to 1.7.22
- Update Gradle version to 7.6
- Update Gradle dependencies
2023-01-14 01:28:08 +08:00
afeb16e69d Update YukiHookAPI 2023-01-14 01:24:19 +08:00
211343a6e7 Modify remove "contains", "replace" method's param name statement 2023-01-14 01:22:16 +08:00
2c0cfb6863 Modify add release channel description, release status description in README 2022-11-26 00:12:48 +08:00
15293950a9 Modify change action file name for ci 2022-11-25 23:32:43 +08:00
88fe23ab9a Modify change action name for ci 2022-11-25 23:29:03 +08:00
Fankesyooni
150d2e8aa5 Merge pull request #24 from KitsunePie/ci
Upgrade ci deps
2022-11-14 13:13:57 +08:00
Howard Wu
dd6e971a34 gradlew chmod +x 2022-11-14 12:13:50 +08:00
Howard Wu
051da4df5f Update push_ci.yml 2022-11-14 12:10:41 +08:00
Howard Wu
368a4b347f Upgrade ci deps 2022-11-14 12:08:46 +08:00
30b92770e9 Update Gradle & PlatformSDK
- Update Android Gradle Plugin version to 7.3.1
- Update Kotlin version to 1.7.20
- Update YukiHookAPI version to 1.1.4 in demo-app
2022-10-20 00:18:46 +08:00
Fankesyooni
2491547e3e Merge pull request #10 from cracky5322/master
Update Traditional Chinese
2022-10-12 12:00:25 +08:00
Jia-Bin
182a65255f Update Traditional Chinese
Better quality and beautiful localization translation optimization
2022-10-12 11:42:17 +08:00
aef2e0814a Fix file naming bug 2022-10-05 09:57:39 +08:00
200 changed files with 4547 additions and 2649 deletions

33
.editorconfig Normal file
View File

@@ -0,0 +1,33 @@
# noinspection EditorConfigKeyCorrectness
[{*.kt,*.kts}]
ktlint_standard_annotation = disabled
ktlint_standard_filename = disabled
ktlint_standard_wrapping = disabled
ktlint_standard_import-ordering = enabled
ktlint_standard_max-line-length = disabled
ktlint_standard_multiline-if-else = disabled
ktlint_standard_argument-list-wrapping = disabled
ktlint_standard_parameter-list-wrapping = disabled
ktlint_standard_trailing-comma-on-declaration-site = disabled
ktlint_function_signature_body_expression_wrapping = multiline
ktlint_standard_string-template-indent = disabled
ktlint_standard_function-signature = disabled
ktlint_standard_trailing-comma-on-call-site = disabled
ktlint_standard_multiline-expression-wrapping = disabled
ktlint_standard_no-empty-first-line-in-class-body = disabled
ktlint_standard_if-else-wrapping = disabled
ktlint_standard_if-else-bracing = disabled
ktlint_standard_statement-wrapping = disabled
ktlint_standard_blank-line-before-declaration = disabled
ktlint_standard_no-empty-file = disabled
ktlint_standard_property-naming = disabled
ktlint_standard_function-naming = disabled
ktlint_standard_chain-method-continuation = disabled
ktlint_standard_class-signature = disabled
ktlint_standard_condition-wrapping = disabled
ktlint_standard_class-signature = disabled
ij_continuation_indent_size = 2
indent_size = 4
indent_style = space
insert_final_newline = false
max_line_length = 150

101
.github/ISSUE_TEMPLATE/bug_report.yml vendored Normal file
View File

@@ -0,0 +1,101 @@
name: Issues and Bugs Report / 问题与 BUG 反馈
description: 问题反馈必须使用此模板进行提交 / Issues and bugs report must be submitted using this template
labels: [ bug ]
title: "[Issues and Bugs Report] (Briefly describe the cause of the problem)"
body:
- type: markdown
attributes:
value: |
### Please fill in the specific reason and steps to reproduce the problem below.
In the event of an exception, crash functional problem, you must submit a problem log, if not, your issues will be closed directly.
### 请在下方填写问题发生的具体原因和复现步骤。
发生异常、崩溃、闪退或功能性问题,必须提交问题 Log (日志),没有 Log 的 issues 将直接被关闭。
- type: input
attributes:
label: Module App version / 模块版本
description: |
Please fill in the complete version of the Module App currently in use, for example: **1.0**
请填写当前使用的模块完整版本号,例如:**1.0**
validations:
required: true
- type: input
attributes:
label: Device model and system in used / 正在使用的设备型号以及使用的系统
description: |
Fill in the current device model and system here, the system such as (MIUI, ColorOS, OxygenOS, PE/Native)
这里填写当前使用的设备型号以及使用的系统,系统例如 (MIUI、ColorOS、OxygenOS、PE/原生)
validations:
required: true
- type: dropdown
attributes:
label: Android version / Android 版本
options:
- 14
- 13
- 12L/12.1
- 12
- 11
- 10
- 9
- 8.1
- 8.0.0
- 7.1.2
- 7.1.1
- 7.0
validations:
required: true
- type: input
attributes:
label: Xposed Framework name and version / Xposed 框架名称与版本号
description: |
Please fill in the currently used Xposed Framework, for example: **LSPosed 1.8.4 (version code)**
请填写当前使用的 Xposed 框架,例如:**LSPosed 1.8.4 (次版本号)**
validations:
required: true
- type: input
attributes:
label: Xposed Modules with the same scope / 与当前同作用域的 Xposed 模块
description: |
The scope of this module is the System Framework (Android System).
To ensure that the problem is not caused by conflicts with other modules, please be sure to fill in the relevant modules that you are currently activating at the same time.
If not, please fill in "none" directly below.
此模块的作用域为系统框架 (Android 系统),为确保非其它模块冲突造成的问题,请一定要填写当前你同时激活的相关模块。
若没有,请直接在下方填写“无”。
validations:
required: true
- type: textarea
attributes:
label: Describe in detail why the problem occurred / 详细描述问题发生的具体原因
description: 请在下方详细描述问题发生的具体场景、复现步骤和经过,以便我们能够按照你所描述的步骤复现这个问题。
validations:
required: true
- type: textarea
attributes:
label: Provide module problem logs or necessary logs / 提供模块问题 Log 或必要 Log
description: |
If you are using LSPosed, you can view and filter the logs containing `AppErrorsTracking` in the log management.
LSPosed 可在日志管理中查看并筛选包含 `AppErrorsTracking` 的日志。
value: |
<details><summary>Click to expand / 展开查看</summary><pre><code>
(Paste problem log here / 此处粘贴问题 Log)
</code></pre></details>
<!-- When submitting, please delete the parentheses including the parentheses, paste the logs you copied, and do not break the code format -->
<!-- 提交时请将括号内容包括括号全部删除,粘贴你复制的日志,不要破坏代码格式 -->
validations:
required: true
- type: checkboxes
attributes:
label: Confirm the contents you submitted / 确认一下你提交的信息
description: |
In order to ensure the quality of issues and avoid wasting unnecessary time, issues that do not check the options below will be closed directly.
Please make sure you have **checked the option below** before submitting.
为了确保 issues 的质量和避免浪费不必要的时间,未勾选下方选项的 issues 将直接被关闭。
请一定确保你已经**勾选下方的选项**后再提交。
options:
- label: I certify that the above contents is correct / 我确保上述信息准确无误
required: false

108
.github/workflows/commit_ci.yml vendored Normal file
View File

@@ -0,0 +1,108 @@
name: Automatic Build on Commit
on:
workflow_dispatch:
push:
branches: [ master ]
paths-ignore:
- '**.md'
- '**.txt'
- '.github/**'
- '!.github/workflows/**'
jobs:
build:
name: Build CI
if: ${{ success() }}
runs-on: ubuntu-latest
env:
MODULE_APK_OUTPUT_PATH: 'module-app/build/outputs/apk'
DEMO_APK_OUTPUT_PATH: 'demo-app/build/outputs/apk'
APP_CENTER_SECRET: ${{ secrets.APP_CENTER_SECRET }}
TG_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
TG_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }}
COMMIT_MESSAGE: |+
New push to GitHub\!
```
${{ github.event.head_commit.message }}
```by `${{ github.event.head_commit.author.name }}`
See commit detail [here](${{ github.event.head_commit.url }})
COMMIT_URL: ${{ github.event.head_commit.url }}
steps:
- uses: actions/checkout@v4
- name: Prepare GitHub Env
run: |
GITHUB_SHA=${{ github.sha }}
GITHUB_CI_COMMIT_ID=${GITHUB_SHA:0:7}
echo "GITHUB_CI_COMMIT_ID=$GITHUB_CI_COMMIT_ID" >> $GITHUB_ENV
- name: Setup cmake
uses: jwlawson/actions-setup-cmake@v1
with:
cmake-version: '3.22.1'
- name: Prepare Java 21
uses: actions/setup-java@v4
with:
java-version: 21
java-package: jdk
distribution: 'temurin'
cache: 'gradle'
- name: Cache Gradle Dependencies
uses: actions/cache@v3
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
!~/.gradle/caches/build-cache-*
key: gradle-deps-core-${{ hashFiles('**/build.gradle.kts') }}
restore-keys: |
gradle-deps
- name: Cache Gradle Build
uses: actions/cache@v3
with:
path: |
~/.gradle/caches/build-cache-*
key: gradle-builds-core-${{ github.sha }}
restore-keys: |
gradle-builds
- name: Build with Gradle
run: |
./gradlew :module-app:assembleDebug
./gradlew :module-app:assembleRelease
./gradlew :demo-app:assembleDebug
./gradlew :demo-app:assembleRelease
echo "MODULE_DEBUG_APK_PATH=$(find ${{ env.MODULE_APK_OUTPUT_PATH }}/debug -name '*.apk')" >> $GITHUB_ENV
echo "MODULE_RELEASE_APK_PATH=$(find ${{ env.MODULE_APK_OUTPUT_PATH }}/release -name '*.apk')" >> $GITHUB_ENV
echo "DEMO_DEBUG_APK_PATH=$(find ${{ env.DEMO_APK_OUTPUT_PATH }}/debug -name '*.apk')" >> $GITHUB_ENV
echo "DEMO_RELEASE_APK_PATH=$(find ${{ env.DEMO_APK_OUTPUT_PATH }}/release -name '*.apk')" >> $GITHUB_ENV
- name: Upload Artifacts (Module-Debug)
uses: actions/upload-artifact@v4
with:
path: ${{ env.MODULE_DEBUG_APK_PATH }}
name: AppErrorsTracking-module-debug-${{ github.event.head_commit.id }}
- name: Upload Artifacts (Module-Release)
uses: actions/upload-artifact@v4
with:
path: ${{ env.MODULE_RELEASE_APK_PATH }}
name: AppErrorsTracking-module-release-${{ github.event.head_commit.id }}
- name: Upload Artifacts (Demo-Debug)
uses: actions/upload-artifact@v4
with:
path: ${{ env.DEMO_DEBUG_APK_PATH }}
name: AppErrorsTracking-demo-debug-${{ github.event.head_commit.id }}
- name: Upload Artifacts (Demo-Release)
uses: actions/upload-artifact@v4
with:
path: ${{ env.DEMO_RELEASE_APK_PATH }}
name: AppErrorsTracking-demo-release-${{ github.event.head_commit.id }}
- name: Post Artifacts to Telegram
run: |
export module_debug=$(find ${{ env.MODULE_APK_OUTPUT_PATH }}/debug -name "*.apk")
export module_release=$(find ${{ env.MODULE_APK_OUTPUT_PATH }}/release -name "*.apk")
export demo_debug=$(find ${{ env.DEMO_APK_OUTPUT_PATH }}/debug -name "*.apk")
export demo_release=$(find ${{ env.DEMO_APK_OUTPUT_PATH }}/release -name "*.apk")
ESCAPED=`python3 -c 'import json,os,urllib.parse; msg = json.dumps(os.environ["COMMIT_MESSAGE"]); print(urllib.parse.quote(msg if len(msg) <= 1024 else json.dumps(os.environ["COMMIT_URL"])))'`
curl -v "https://api.telegram.org/bot${TG_BOT_TOKEN}/sendMediaGroup?chat_id=${TG_CHAT_ID}&media=%5B%7B%22type%22%3A%22document%22%2C%20%22media%22%3A%22attach%3A%2F%2Fmodule_debug%22%7D%2C%7B%22type%22%3A%22document%22%2C%20%22media%22%3A%22attach%3A%2F%2Fmodule_release%22%7D%2C%7B%22type%22%3A%22document%22%2C%20%22media%22%3A%22attach%3A%2F%2Fdemo_debug%22%7D%2C%7B%22type%22%3A%22document%22%2C%20%22media%22%3A%22attach%3A%2F%2Fdemo_release%22%2C%22parse_mode%22%3A%22MarkdownV2%22%2C%22caption%22:${ESCAPED}%7D%5D" \
-F module_debug="@$module_debug" \
-F module_release="@$module_release" \
-F demo_debug="@$demo_debug" \
-F demo_release="@$demo_release"

View File

@@ -1,62 +0,0 @@
name: main
on:
pull_request:
branches: [ master ]
paths-ignore:
- '**.md'
- '**.txt'
- '.github/**'
- '!.github/workflows/**'
jobs:
build:
name: Pull request check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup cmake
uses: jwlawson/actions-setup-cmake@v1.12
with:
cmake-version: '3.22.1'
- name: Prepare Java 11
uses: actions/setup-java@v1
with:
java-version: 11
java-package: jdk
- name: Cache Gradle Dependencies
uses: actions/cache@v2
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
!~/.gradle/caches/build-cache-*
key: gradle-deps-core-${{ hashFiles('**/build.gradle') }}
restore-keys: |
gradle-deps
- name: Cache Gradle Build
uses: actions/cache@v2
with:
path: |
~/.gradle/caches/build-cache-*
key: gradle-builds-core-${{ github.sha }}
restore-keys: |
gradle-builds
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Build with Gradle
run: |
./gradlew :app:assembleRelease
./gradlew :demo-app:assembleRelease
echo "APK_FILE=$(find app/build/outputs/apk/release -name '*.apk')" >> $GITHUB_ENV
echo "DEMO_APK_FILE=$(find demo-app/build/outputs/apk/release -name '*.apk')" >> $GITHUB_ENV
- name: Upload Artifacts(module)
uses: actions/upload-artifact@v2
with:
path: ${{ env.APK_FILE }}
name: module-release
- name: Upload Artifacts(demo-app)
uses: actions/upload-artifact@v2
with:
path: ${{ env.DEMO_APK_FILE }}
name: demo-release

85
.github/workflows/pull_request_ci.yml vendored Normal file
View File

@@ -0,0 +1,85 @@
name: Pull Request Checker
on:
pull_request:
branches: [ master ]
paths-ignore:
- '**.md'
- '**.txt'
- '.github/**'
- '!.github/workflows/**'
jobs:
build:
name: Pull Request Check
if: ${{ success() }}
runs-on: ubuntu-latest
env:
MODULE_APK_OUTPUT_PATH: 'module-app/build/outputs/apk'
DEMO_APK_OUTPUT_PATH: 'demo-app/build/outputs/apk'
steps:
- uses: actions/checkout@v4
- name: Prepare GitHub Env
run: |
GITHUB_SHA=${{ github.sha }}
GITHUB_CI_COMMIT_ID=${GITHUB_SHA:0:7}
echo "GITHUB_CI_COMMIT_ID=$GITHUB_CI_COMMIT_ID" >> $GITHUB_ENV
- name: Setup cmake
uses: jwlawson/actions-setup-cmake@v1
with:
cmake-version: '3.22.1'
- name: Prepare Java 21
uses: actions/setup-java@v4
with:
java-version: 21
java-package: jdk
distribution: 'temurin'
cache: 'gradle'
- name: Cache Gradle Dependencies
uses: actions/cache@v3
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
!~/.gradle/caches/build-cache-*
key: gradle-deps-core-${{ hashFiles('**/build.gradle.kts') }}
restore-keys: |
gradle-deps
- name: Cache Gradle Build
uses: actions/cache@v3
with:
path: |
~/.gradle/caches/build-cache-*
key: gradle-builds-core-${{ github.sha }}
restore-keys: |
gradle-builds
- name: Build with Gradle
run: |
./gradlew :module-app:assembleDebug
./gradlew :module-app:assembleRelease
./gradlew :demo-app:assembleDebug
./gradlew :demo-app:assembleRelease
echo "MODULE_DEBUG_APK_PATH=$(find ${{ env.MODULE_APK_OUTPUT_PATH }}/debug -name '*.apk')" >> $GITHUB_ENV
echo "MODULE_RELEASE_APK_PATH=$(find ${{ env.MODULE_APK_OUTPUT_PATH }}/release -name '*.apk')" >> $GITHUB_ENV
echo "DEMO_DEBUG_APK_PATH=$(find ${{ env.DEMO_APK_OUTPUT_PATH }}/debug -name '*.apk')" >> $GITHUB_ENV
echo "DEMO_RELEASE_APK_PATH=$(find ${{ env.DEMO_APK_OUTPUT_PATH }}/release -name '*.apk')" >> $GITHUB_ENV
- name: Upload Artifacts (Module-Debug)
uses: actions/upload-artifact@v4
with:
path: ${{ env.MODULE_DEBUG_APK_PATH }}
name: AppErrorsTracking-module-debug-${{ github.event.head_commit.id }}
- name: Upload Artifacts (Module-Release)
uses: actions/upload-artifact@v4
with:
path: ${{ env.MODULE_RELEASE_APK_PATH }}
name: AppErrorsTracking-module-release-${{ github.event.head_commit.id }}
- name: Upload Artifacts (Demo-Debug)
uses: actions/upload-artifact@v4
with:
path: ${{ env.DEMO_DEBUG_APK_PATH }}
name: AppErrorsTracking-demo-debug-${{ github.event.head_commit.id }}
- name: Upload Artifacts (Demo-Release)
uses: actions/upload-artifact@v4
with:
path: ${{ env.DEMO_RELEASE_APK_PATH }}
name: AppErrorsTracking-demo-release-${{ github.event.head_commit.id }}

View File

@@ -1,63 +0,0 @@
name: main
on:
workflow_dispatch:
push:
branches: [ master ]
paths-ignore:
- '**.md'
- '**.txt'
- '.github/**'
- '!.github/workflows/**'
jobs:
build:
name: Build CI
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup cmake
uses: jwlawson/actions-setup-cmake@v1.12
with:
cmake-version: '3.22.1'
- name: Prepare Java 11
uses: actions/setup-java@v1
with:
java-version: 11
java-package: jdk
- name: Cache Gradle Dependencies
uses: actions/cache@v2
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
!~/.gradle/caches/build-cache-*
key: gradle-deps-core-${{ hashFiles('**/build.gradle') }}
restore-keys: |
gradle-deps
- name: Cache Gradle Build
uses: actions/cache@v2
with:
path: |
~/.gradle/caches/build-cache-*
key: gradle-builds-core-${{ github.sha }}
restore-keys: |
gradle-builds
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Build with Gradle
run: |
./gradlew :app:assembleRelease
./gradlew :demo-app:assembleRelease
echo "APK_FILE=$(find app/build/outputs/apk/release -name '*.apk')" >> $GITHUB_ENV
echo "DEMO_APK_FILE=$(find demo-app/build/outputs/apk/release -name '*.apk')" >> $GITHUB_ENV
- name: Upload Artifacts(module)
uses: actions/upload-artifact@v2
with:
path: ${{ env.APK_FILE }}
name: module-release
- name: Upload Artifacts(demo-app)
uses: actions/upload-artifact@v2
with:
path: ${{ env.DEMO_APK_FILE }}
name: demo-release

122
.gitignore vendored
View File

@@ -1,20 +1,110 @@
# Project exclude paths
## Fully .gtignore for IntelliJ, Android Studio and Gradle based Java projects
## References:
## - https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
## - https://github.com/android/platform-samples/blob/main/.gitignore
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# AWS User-specific
.idea/**/aws.xml
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
.idea/.name
.idea/artifacts
.idea/compiler.xml
.idea/jarRepositories.xml
.idea/modules.xml
.idea/*.iml
.idea/modules
.idea/caches
.idea/material_theme**
.idea/other.xml
*.iml
.gradle
.secret
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
/build
*.ipr
# Kotlin
.kotlin
# Misc
.idea/misc.xml
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# SonarLint plugin
.idea/sonarlint/
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
# Android studio 3.1+ additional
.idea/deployment*.xml
.idea/assetWizardSettings.xml
.idea/androidTestResultsUserPreferences.xml
# Android projects
**/local.properties
/captures
.externalNativeBuild
.cxx
local.properties
/app/releaseHasController/
/app/debug/
/app/release/
# Gradle projects
.gradle
build/
# Mkdocs temporary serving folder
docs-gen
site
*.bak
.idea/appInsightsSettings.xml
# Mac OS
.DS_Store

3
.idea/.gitignore generated vendored
View File

@@ -1,3 +0,0 @@
# Default ignored files
/shelf/
/workspace.xml

6
.idea/AndroidProjectSystem.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AndroidProjectSystem">
<option name="providerId" value="com.android.tools.idea.GradleProjectSystem" />
</component>
</project>

6
.idea/compiler.xml generated
View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="11" />
</component>
</project>

View File

@@ -1,17 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="deploymentTargetDropDown">
<targetSelectedWithDropDown>
<Target>
<type value="QUICK_BOOT_TARGET" />
<deviceKey>
<Key>
<type value="VIRTUAL_DEVICE_PATH" />
<value value="$USER_HOME$/.android/avd/Pixel_2_API_29.avd" />
</Key>
</deviceKey>
</Target>
</targetSelectedWithDropDown>
<timeTargetWasSelectedWithDropDown value="2022-05-19T02:00:42.222889Z" />
</component>
</project>

21
.idea/gradle.xml generated
View File

@@ -1,21 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="testRunner" value="GRADLE" />
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="Embedded JDK" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
<option value="$PROJECT_DIR$/demo-app" />
</set>
</option>
</GradleProjectSettings>
</option>
</component>
</project>

View File

@@ -1,10 +1,12 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="CheckImageSize" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
<option name="processCode" value="true" />
<option name="processLiterals" value="true" />
<option name="processComments" value="true" />
</inspection_tool>
<inspection_tool class="YAMLSchemaValidation" enabled="false" level="WARNING" enabled_by_default="false" />
</profile>
</component>

6
.idea/kotlinc.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="KotlinJpsPluginSettings">
<option name="version" value="2.1.10" />
</component>
</project>

7
.idea/ktlint-plugin.xml generated Normal file
View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="KtLint plugin">
<ktlintMode>MANUAL</ktlintMode>
<formatOnSave>false</formatOnSave>
</component>
</project>

6
.idea/ktlint.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="KtlintProjectConfiguration">
<treatAsErrors>false</treatAsErrors>
</component>
</project>

10
.idea/migrations.xml generated Normal file
View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectMigrations">
<option name="MigrateToGradleLocalJavaHome">
<set>
<option value="$PROJECT_DIR$" />
</set>
</option>
</component>
</project>

48
.idea/misc.xml generated
View File

@@ -1,48 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DesignSurface">
<option name="filePathToZoomLevelMap">
<map>
<entry key="app/src/main/res/drawable-night/bg_black_round.xml" value="0.2425" />
<entry key="app/src/main/res/drawable-night/bg_dark_round.xml" value="0.256" />
<entry key="app/src/main/res/drawable-night/bg_green_round.xml" value="0.2425" />
<entry key="app/src/main/res/drawable-night/bg_orange_round.xml" value="0.2425" />
<entry key="app/src/main/res/drawable-night/bg_yellow_round.xml" value="0.2425" />
<entry key="app/src/main/res/drawable/bg_black_round.xml" value="0.2425" />
<entry key="app/src/main/res/drawable/bg_dark_round.xml" value="0.2425" />
<entry key="app/src/main/res/drawable/bg_green_round.xml" value="0.2425" />
<entry key="app/src/main/res/drawable/bg_orange_round.xml" value="0.2425" />
<entry key="app/src/main/res/drawable/ic_debug.xml" value="0.2555" />
<entry key="app/src/main/res/drawable/ic_exception.xml" value="0.256" />
<entry key="app/src/main/res/drawable/ic_filter.xml" value="0.256" />
<entry key="app/src/main/res/drawable/ic_share.xml" value="0.241" />
<entry key="app/src/main/res/drawable/ic_statistics.xml" value="0.256" />
<entry key="app/src/main/res/layout/activity_app_errors_detail.xml" value="0.43697916666666664" />
<entry key="app/src/main/res/layout/activity_app_errors_display.xml" value="0.4359375" />
<entry key="app/src/main/res/layout/activity_app_errors_ignored.xml" value="0.43697916666666664" />
<entry key="app/src/main/res/layout/activity_app_errors_muted.xml" value="0.43697916666666664" />
<entry key="app/src/main/res/layout/activity_app_errors_record.xml" value="0.43697916666666664" />
<entry key="app/src/main/res/layout/activity_config.xml" value="0.4359375" />
<entry key="app/src/main/res/layout/activity_main.xml" value="0.4359375" />
<entry key="app/src/main/res/layout/adapter_app_errors_ignored.xml" value="0.43697916666666664" />
<entry key="app/src/main/res/layout/adapter_app_errors_muted.xml" value="0.43697916666666664" />
<entry key="app/src/main/res/layout/adapter_app_errors_record.xml" value="0.43697916666666664" />
<entry key="app/src/main/res/layout/adapter_app_info.xml" value="0.43697916666666664" />
<entry key="app/src/main/res/layout/dia_app_config.xml" value="0.43697916666666664" />
<entry key="app/src/main/res/layout/dia_app_errors_display.xml" value="0.4359375" />
<entry key="app/src/main/res/layout/dia_app_errors_statistics.xml" value="0.43697916666666664" />
<entry key="app/src/main/res/layout/dia_apps_filter.xml" value="0.43697916666666664" />
<entry key="app/src/main/res/menu/menu_list_detail_action.xml" value="0.43697916666666664" />
<entry key="demo-app/src/main/res/layout/activity_main.xml" value="0.4083333333333333" />
<entry key="demo-app/src/main/res/layout/activity_multi_process.xml" value="0.4359375" />
</map>
</option>
</component>
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="JDK" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
</project>

17
.idea/runConfigurations.xml generated Normal file
View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RunConfigurationProducerService">
<option name="ignoredProducers">
<set>
<option value="com.intellij.execution.junit.AbstractAllInDirectoryConfigurationProducer" />
<option value="com.intellij.execution.junit.AllInPackageConfigurationProducer" />
<option value="com.intellij.execution.junit.PatternConfigurationProducer" />
<option value="com.intellij.execution.junit.TestInClassConfigurationProducer" />
<option value="com.intellij.execution.junit.UniqueIdConfigurationProducer" />
<option value="com.intellij.execution.junit.testDiscovery.JUnitTestDiscoveryConfigurationProducer" />
<option value="org.jetbrains.kotlin.idea.junit.KotlinJUnitRunConfigurationProducer" />
<option value="org.jetbrains.kotlin.idea.junit.KotlinPatternConfigurationProducer" />
</set>
</option>
</component>
</project>

1
.secret/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/secret.properties

121
README-ja-JP.md Normal file
View File

@@ -0,0 +1,121 @@
# AppErrorsTracking
[![GitHub license](https://img.shields.io/github/license/KitsunePie/AppErrorsTracking?color=blue&style=flat-square)](https://github.com/KitsunePie/AppErrorsTracking/blob/master/LICENSE)
[![GitHub CI](https://img.shields.io/github/actions/workflow/status/KitsunePie/AppErrorsTracking/commit_ci.yml?label=CI%20builds&style=flat-square)](https://github.com/KitsunePie/AppErrorsTracking/actions/workflows/commit_ci.yml)
[![GitHub release](https://img.shields.io/github/v/release/KitsunePie/AppErrorsTracking?display_name=release&logo=github&color=green&style=flat-square)](https://github.com/KitsunePie/AppErrorsTracking/releases)
![GitHub all releases](https://img.shields.io/github/downloads/KitsunePie/AppErrorsTracking/total?label=downloads&style=flat-square)
![GitHub all releases](https://img.shields.io/github/downloads/Xposed-Modules-Repo/com.fankes.apperrorstracking/total?label=LSPosed%20downloads&labelColor=F48FB1&style=flat-square)
[![Telegram CI](https://img.shields.io/badge/CI%20builds-Telegram-blue.svg?logo=telegram&style=flat-square)](https://t.me/AppErrorsTracking_CI)
[![Telegram](https://img.shields.io/badge/discussion-Telegram-blue.svg?logo=telegram&style=flat-square)](https://t.me/XiaofangInternet)
[![QQ](https://img.shields.io/badge/discussion-QQ-blue.svg?logo=tencent-qq&logoColor=red&style=flat-square)](https://qm.qq.com/cgi-bin/qm/qr?k=dp2h5YhWiga9WWb_Oh7kSHmx01X8I8ii&jump_from=webapi&authKey=Za5CaFP0lk7+Zgsk2KpoBD7sSaYbeXbsDgFjiWelOeH4VSionpxFJ7V0qQBSqvFM)
[![QQ 频道](https://img.shields.io/badge/discussion-QQ%20频道-blue.svg?logo=tencent-qq&logoColor=red&style=flat-square)](https://pd.qq.com/s/44gcy28h)
<img src="img-src/icon.png" width = "100" height = "100" alt="LOGO"/>
[English](README.md) | [简体中文](README-zh-CN.md) | 日本語
アプリのエラーダイアログに機能を追加し、カスタムROMによって削除されたダイアログを修正することで、Android開発者に最高の体験を提供します。
このプロジェクトは、どのAndroidシステム上でも使用できるXposedモジュールであり、現在は**LSPosed**でのみテストされています。
このXposedモジュールは、Android開発者のために特別に設計されています。
PCに接続できない、ADBが実行できない状態である場合にこのモジュールを使用して、インストールされているアプリのエラーをキャプチャする事で問題を迅速に特定することができます。
アプリがクラッシュしたときのエラーログは、開発者にとって貴重な財産です。もしあなたが開発者でなくても、このモジュールをインストールする事で開発者への貢献に繋がるでしょう。
> 最小サポート Android 7.0
## プロジェクトの理由
本当に理解不能ですが、中国本土のAndroid ROMは、MIUI(安定版を除く)を除いて、アプリのクラッシュ時のダイアログボックス(強制終了ダイアログ)を削除しています。私はシステムフレームワークを逆コンパイルして本当に削除されていることを確認するまで、これは当たり前の機能だと思っていました。
プロダクトマネージャーは、ユーザーにエラーを表示させずにアプリをクラッシュさせて直接終了する事が最善の解決策と考えているのでしょうか?
それとも **隠された秘密** があるのでしょうか?
## 動作の原理
`Thread.UncaughtExceptionHandler`とは異なり、システムフレームワークをインジェクトする事でアプリのエラーを全方向からキャプチャするネイティブメソッドを使用します。これは、元の例外監視よりもパフォーマンスに優れています。
同時に、システムレベルの例外のキャプチャは、ネイティブプラットフォームの`スタックトレース`もキャプチャ可能です。
## 注意事項
システムによってネイティブにキャプチャされるエラーは、アプリ自体によって処理されないエラーのみです。アプリ自体に**Bugly**のような、エラーを自動的に収集するためのカスタムの `Thread.UncaughtExceptionHandler` がある場合、システムはアプリが実際にクラッシュ **(強制終了)** したかどうかを取得できません。
## 機能のリスト
- [x] システムのアプリエラーダイアログを完全に置き換え
- [x] 各アプリの例外をログに記録して再起動まで保持
- [x] エラーのスタックトレース関数のコピー、共有、エクスポート
- [x] エラー履歴記録機能、これは通知バータイルの「エラー履歴の記録」およびモジュールのメインインターフェースから入力
- [x] アプリのエラー統計機能
- [x] マルチプロセスアプリのエラー表示機能
## 翻訳の貢献
このプロジェクトは、あなたの国の言語に翻訳する事を歓迎します。
## リリースチャンネル
| <img src="https://avatars.githubusercontent.com/in/15368?s=64&v=4" width = "30" height = "30" alt="LOGO"/> | [GitHub CI](https://github.com/KitsunePie/AppErrorsTracking/actions/workflows/commit_ci.yml) | CI 自動ビルド (テスト版) |
|------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------|-----------------------------------|
| <img src="https://github.com/peter-iakovlev/Telegram/blob/public/Icon.png?raw=true" width = "30" height = "30" alt="LOGO"/> | [Telegram CI チャンネル](https://t.me/AppErrorsTracking_CI) | CI 自動ビルド (テスト版) |
|-----------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------|-----------------------------------|
| <img src="https://avatars.githubusercontent.com/in/15368?s=64&v=4" width = "30" height = "30" alt="LOGO"/> | [GitHub リリース](https://github.com/KitsunePie/AppErrorsTracking/releases) | 正式版 (安定版) |
|------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------|---------------------------------|
| <img src="https://avatars.githubusercontent.com/u/78217009?s=200&v=4?raw=true" width = "30" height = "30" alt="LOGO"/> | [Xposed モジュールのリポジトリ](https://github.com/Xposed-Modules-Repo/com.fankes.apperrorstracking/releases) | 正式版 (安定版) |
|------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------|---------------------------------|
このXposedモジュールのリリースは上記のURLに限定されています。
他の非公式チャンネルからダウンロードされたバージョンで及ぼした問題は一切関係はありません。
## プロモーション
<!--suppress HtmlDeprecatedAttribute -->
<div align="center">
<h2>ねぇ、きいて! 👋</h2>
<h3>ここでは、Androidの開発ツールやUIデザイン、Gradleプラグイン、Xposedモジュール、実用的なソフトウェアなどの関連プロジェクトを紹介します。</h3>
<h3>もしも以下のプロジェクトであなたのお役に立てたのであれば、私にStarを付けてください!</h3>
<h3>すべてのプロジェクトは無料でオープンソースであり、対応するオープンソースライセンス契約に基づいています。</h3>
<h1><a href="https://github.com/fankes/fankes/blob/main/project-promote/README.md">→ 私のプロジェクトについてはこちらをクリック ←</a></h1>
</div>
## Starの推移
![Star History Chart](https://api.star-history.com/svg?repos=KitsunePie/AppErrorsTracking&type=Date)
## ライセンス
- [AGPL-3.0](https://www.gnu.org/licenses/agpl-3.0.html)
```
Copyright (C) 2017 Fankes Studio(qzmmcn@163.com)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
```
Powered by [YukiHookAPI](https://github.com/HighCapable/YukiHookAPI)
Copyright © 2017 Fankes Studio(qzmmcn@163.com)

View File

@@ -1,12 +1,19 @@
# AppErrorsTracking
[![Blank](https://img.shields.io/badge/build-passing-brightgreen)](https://github.com/KitsunePie/AppErrorsTracking)
[![Blank](https://img.shields.io/badge/license-AGPL3.0-blue)](https://github.com/KitsunePie/AppErrorsTracking/blob/master/LICENSE)
[![Blank](https://img.shields.io/badge/version-v1.1-green)](https://github.com/KitsunePie/AppErrorsTracking/releases)
[![Blank](https://img.shields.io/github/downloads/KitsunePie/AppErrorsTracking/total?label=Release)](https://github.com/KitsunePie/AppErrorsTracking/releases)
[![Blank](https://img.shields.io/github/downloads/Xposed-Modules-Repo/com.fankes.apperrorstracking/total?label=LSPosed%20Repo&logo=Android&style=flat&labelColor=F48FB1&logoColor=ffffff)](https://github.com/Xposed-Modules-Repo/com.fankes.apperrorstracking/releases)
<br/><br/>
[English](https://github.com/KitsunePie/AppErrorsTracking/blob/master/README.md) | 简体中文
[![GitHub license](https://img.shields.io/github/license/KitsunePie/AppErrorsTracking?color=blue&style=flat-square)](https://github.com/KitsunePie/AppErrorsTracking/blob/master/LICENSE)
[![GitHub CI](https://img.shields.io/github/actions/workflow/status/KitsunePie/AppErrorsTracking/commit_ci.yml?label=CI%20builds&style=flat-square)](https://github.com/KitsunePie/AppErrorsTracking/actions/workflows/commit_ci.yml)
[![GitHub release](https://img.shields.io/github/v/release/KitsunePie/AppErrorsTracking?display_name=release&logo=github&color=green&style=flat-square)](https://github.com/KitsunePie/AppErrorsTracking/releases)
![GitHub all releases](https://img.shields.io/github/downloads/KitsunePie/AppErrorsTracking/total?label=downloads&style=flat-square)
![GitHub all releases](https://img.shields.io/github/downloads/Xposed-Modules-Repo/com.fankes.apperrorstracking/total?label=LSPosed%20downloads&labelColor=F48FB1&style=flat-square)
[![Telegram CI](https://img.shields.io/badge/CI%20builds-Telegram-blue.svg?logo=telegram&style=flat-square)](https://t.me/AppErrorsTracking_CI)
[![Telegram](https://img.shields.io/badge/discussion-Telegram-blue.svg?logo=telegram&style=flat-square)](https://t.me/XiaofangInternet)
[![QQ](https://img.shields.io/badge/discussion-QQ-blue.svg?logo=tencent-qq&logoColor=red&style=flat-square)](https://qm.qq.com/cgi-bin/qm/qr?k=dp2h5YhWiga9WWb_Oh7kSHmx01X8I8ii&jump_from=webapi&authKey=Za5CaFP0lk7+Zgsk2KpoBD7sSaYbeXbsDgFjiWelOeH4VSionpxFJ7V0qQBSqvFM)
[![QQ 频道](https://img.shields.io/badge/discussion-QQ%20频道-blue.svg?logo=tencent-qq&logoColor=red&style=flat-square)](https://pd.qq.com/s/44gcy28h)
<img src="img-src/icon.png" width = "100" height = "100" alt="LOGO"/>
[English](README.md) | 简体中文 | [日本語](README-ja-JP.md)
为原生 FC 对话框增加更多功能并修复国内定制 ROM 删除 FC 对话框的问题,给 Android 开发者带来更好的体验。
@@ -18,7 +25,7 @@
应用发生崩溃的错误日志对开发者来说是无价的财富,若你不是开发者,你依然可以安装此模块,以便给开发者提供更多异常信息快速解决问题。
> 最低支持 Android 8.1
> 最低支持 Android 7.0
## 项目缘由
@@ -39,28 +46,72 @@
## 功能列表
- 完全取代系统的应用错误对话框
- [x] 完全取代系统的应用错误对话框
- 记录每个应用的异常,直到重新启动前持续保留
- [x] 记录每个应用的异常,直到重新启动前持续保留
- 复制、分享、导出异常堆栈功能
- [x] 复制、分享、导出异常堆栈功能
- 异常历史记录功能,可通过通知栏磁贴“异常历史记录”进入和模块主界面进入
- [x] 异常历史记录功能,可通过通知栏磁贴“异常历史记录”进入和模块主界面进入
- 应用异常统计功能
- [x] 应用异常统计功能
- 多进程应用的异常显示功能
- [x] 多进程应用的异常显示功能
## 翻译贡献
欢迎为此项目做出贡献,将其翻译为您国家的语言。
## 发行渠道
| <img src="https://avatars.githubusercontent.com/in/15368?s=64&v=4" width = "30" height = "30" alt="LOGO"/> | [GitHub CI](https://github.com/KitsunePie/AppErrorsTracking/actions/workflows/commit_ci.yml) | CI 自动构建 (测试版) |
|------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------|---------------|
| <img src="https://github.com/peter-iakovlev/Telegram/blob/public/Icon.png?raw=true" width = "30" height = "30" alt="LOGO"/> | [Telegram CI 频道](https://t.me/AppErrorsTracking_CI) | CI 自动构建 (测试版) |
|-----------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------|---------------|
| <img src="https://avatars.githubusercontent.com/in/15368?s=64&v=4" width = "30" height = "30" alt="LOGO"/> | [GitHub Releases](https://github.com/KitsunePie/AppErrorsTracking/releases) | 正式版 (稳定版) |
|------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------|-----------|
| <img src="https://avatars.githubusercontent.com/u/78217009?s=200&v=4?raw=true" width = "30" height = "30" alt="LOGO"/> | [Xposed-Modules-Repo](https://github.com/Xposed-Modules-Repo/com.fankes.apperrorstracking/releases) | 正式版 (稳定版) |
|------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------|-----------|
本模块发布地址仅限于上述所列出的地址,从其他非正规渠道下载到的版本或对您造成任何影响均与我们无关。
## 注意事项
<h3>1.&nbsp;本软件免费、由兴趣驱动开发,仅供学习交流使用。如果你是从其他非官方渠道付费获得本软件,可能已遭遇欺诈,欢迎向我们举报可疑行为。</h3>
<h3>2.&nbsp;本软件采用 <strong>GNU Affero General Public License (AGPL 3.0)</strong> 许可证。根据该许可证的要求:</h3>
- 任何衍生作品必须采用相同的 AGPL 许可证
- 分发本软件或其修改版本时,必须提供完整的源代码
- 必须保留原始的版权声明及许可证信息
- 不得额外施加限制来限制他人对本软件的自由使用
<h3>3.&nbsp;我们鼓励在遵守 AGPL 3.0 条款的前提下进行自由传播和改进,但请尊重作者署名权,勿冒用原作者名义。</h3>
## 项目推广
<!--suppress HtmlDeprecatedAttribute -->
<div align="center">
<h2>嘿,还请君留步!👋</h2>
<h3>这里有 Android 开发工具、UI 设计、Gradle 插件、Xposed 模块和实用软件等相关项目。</h3>
<h3>如果下方的项目能为你提供帮助,不妨为我点个 star 吧!</h3>
<h3>所有项目免费、开源,遵循对应开源许可协议。</h3>
<h1><a href="https://github.com/fankes/fankes/blob/main/project-promote/README-zh-CN.md">→ 查看更多关于我的项目,请点击这里 ←</a></h1>
</div>
## Star History
![Star History Chart](https://api.star-history.com/svg?repos=KitsunePie/AppErrorsTracking&type=Date)
## 许可证
- [AGPL-3.0](https://www.gnu.org/licenses/agpl-3.0.html)
```
Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
Copyright (C) 2017 Fankes Studio(qzmmcn@163.com)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
@@ -73,9 +124,9 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
along with this program. If not, see <https://www.gnu.org/licenses/>.
```
Powered by [YukiHookAPI](https://github.com/fankes/YukiHookAPI)
Powered by [YukiHookAPI](https://github.com/HighCapable/YukiHookAPI)
版权所有 © 2019-2022 Fankes Studio(qzmmcn@163.com)
版权所有 © 2017 Fankes Studio(qzmmcn@163.com)

View File

@@ -1,31 +1,38 @@
# AppErrorsTracking
[![Blank](https://img.shields.io/badge/build-passing-brightgreen)](https://github.com/KitsunePie/AppErrorsTracking)
[![Blank](https://img.shields.io/badge/license-AGPL3.0-blue)](https://github.com/KitsunePie/AppErrorsTracking/blob/master/LICENSE)
[![Blank](https://img.shields.io/badge/version-v1.1-green)](https://github.com/KitsunePie/AppErrorsTracking/releases)
[![Blank](https://img.shields.io/github/downloads/KitsunePie/AppErrorsTracking/total?label=Release)](https://github.com/KitsunePie/AppErrorsTracking/releases)
[![Blank](https://img.shields.io/github/downloads/Xposed-Modules-Repo/com.fankes.apperrorstracking/total?label=LSPosed%20Repo&logo=Android&style=flat&labelColor=F48FB1&logoColor=ffffff)](https://github.com/Xposed-Modules-Repo/com.fankes.apperrorstracking/releases)
<br/><br/>
English | [简体中文](https://github.com/KitsunePie/AppErrorsTracking/blob/master/README-zh-CN.md)
[![GitHub license](https://img.shields.io/github/license/KitsunePie/AppErrorsTracking?color=blue&style=flat-square)](https://github.com/KitsunePie/AppErrorsTracking/blob/master/LICENSE)
[![GitHub CI](https://img.shields.io/github/actions/workflow/status/KitsunePie/AppErrorsTracking/commit_ci.yml?label=CI%20builds&style=flat-square)](https://github.com/KitsunePie/AppErrorsTracking/actions/workflows/commit_ci.yml)
[![GitHub release](https://img.shields.io/github/v/release/KitsunePie/AppErrorsTracking?display_name=release&logo=github&color=green&style=flat-square)](https://github.com/KitsunePie/AppErrorsTracking/releases)
![GitHub all releases](https://img.shields.io/github/downloads/KitsunePie/AppErrorsTracking/total?label=downloads&style=flat-square)
![GitHub all releases](https://img.shields.io/github/downloads/Xposed-Modules-Repo/com.fankes.apperrorstracking/total?label=LSPosed%20downloads&labelColor=F48FB1&style=flat-square)
[![Telegram CI](https://img.shields.io/badge/CI%20builds-Telegram-blue.svg?logo=telegram&style=flat-square)](https://t.me/AppErrorsTracking_CI)
[![Telegram](https://img.shields.io/badge/discussion-Telegram-blue.svg?logo=telegram&style=flat-square)](https://t.me/XiaofangInternet)
[![QQ](https://img.shields.io/badge/discussion-QQ-blue.svg?logo=tencent-qq&logoColor=red&style=flat-square)](https://qm.qq.com/cgi-bin/qm/qr?k=dp2h5YhWiga9WWb_Oh7kSHmx01X8I8ii&jump_from=webapi&authKey=Za5CaFP0lk7+Zgsk2KpoBD7sSaYbeXbsDgFjiWelOeH4VSionpxFJ7V0qQBSqvFM)
[![QQ 频道](https://img.shields.io/badge/discussion-QQ%20频道-blue.svg?logo=tencent-qq&logoColor=red&style=flat-square)](https://pd.qq.com/s/44gcy28h)
<img src="img-src/icon.png" width = "100" height = "100" alt="LOGO"/>
English | [简体中文](README-zh-CN.md) | [日本語](README-ja-JP.md)
Added more features to app's errors dialog, fixed custom rom deleted dialog, the best experience to Android developer.
This project is an Xposed module that can be used in any Android system, currently only tested in **LSPosed**.
This project is a Xposed Module that can be used in any Android system, currently only tested in **LSPosed**.
This module is specially designed for Android developers.
This Xposed Module is specially designed for Android developers.
When it is possible that the computer cannot be connected and ADB cannot be performed, this module can be used to quickly capture any errors of
any installed apps, so as to quickly locate the problem.
any installed apps, to quickly locate the problem.
The error log of apps crashing is an invaluable asset for developers. If you are not a developer, you can still install this module to provide
developers with more exception information to quickly solve problems.
> Minimum support Android 8.1
> Minimum support Android 7.0
## Project Reason
I really can't understand, except for MIUI (except stable version), Android ROMs in mainland China have chosen to delete the dialog box (FC
dialog) of apps crashes. I thought this was always a feature until I decompiled the system. frame, only to confirm that it was indeed deleted.
dialog) of apps crashes. I thought this was always a feature until I decompiled the system framework, only to confirm that it was indeed deleted.
Does the product manager think that it is the best solution to let the user not see the error, and the apps will crash and exit directly, or is
there another **hidden secret**?
@@ -45,29 +52,62 @@ Similar to **Bugly** to automatically collect errors, the system cannot obtain w
## Features List
- Completely replaces the system's apps errors dialog
- [x] Completely replaces the system's apps errors dialog
- Logs exceptions for each apps and persists until restart
- [x] Logs exceptions for each app and persists until restart
- Copy, share, export errors stack trace functions
- [x] Copy, share, export errors stack trace functions
- Errors history record function, which can be entered through the notification bar tile "errors history record" and the main interface of the
module
- [x] Errors history record function,
which can be entered through the notification bar tile "errors history record" and the main interface of the module
- Apps errors statistics function
- [x] Apps errors statistics function
- Errors display function for multi-process apps
- [x] Errors display function for multi-process apps
## Translation contribution
## Translation Contribution
Contributions to this project are welcome to translate it into your country's language.
## Release Channel
| <img src="https://avatars.githubusercontent.com/in/15368?s=64&v=4" width = "30" height = "30" alt="LOGO"/> | [GitHub CI](https://github.com/KitsunePie/AppErrorsTracking/actions/workflows/commit_ci.yml) | CI automatic build (test version) |
|------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------|-----------------------------------|
| <img src="https://github.com/peter-iakovlev/Telegram/blob/public/Icon.png?raw=true" width = "30" height = "30" alt="LOGO"/> | [Telegram CI Channel](https://t.me/AppErrorsTracking_CI) | CI automatic build (test version) |
|-----------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------|-----------------------------------|
| <img src="https://avatars.githubusercontent.com/in/15368?s=64&v=4" width = "30" height = "30" alt="LOGO"/> | [GitHub Releases](https://github.com/KitsunePie/AppErrorsTracking/releases) | Formal edition (stable version) |
|------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------|---------------------------------|
| <img src="https://avatars.githubusercontent.com/u/78217009?s=200&v=4?raw=true" width = "30" height = "30" alt="LOGO"/> | [Xposed-Modules-Repo](https://github.com/Xposed-Modules-Repo/com.fankes.apperrorstracking/releases) | Formal edition (stable version) |
|------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------|---------------------------------|
The releases of this Xposed Module is limited to the urls listed above.
We have nothing to do with versions downloaded from other informal channels or any impact on you.
## Promotion
<!--suppress HtmlDeprecatedAttribute -->
<div align="center">
<h2>Hey, please stay! 👋</h2>
<h3>Here are related projects such as Android development tools, UI design, Gradle plugins, Xposed Modules and practical software. </h3>
<h3>If the project below can help you, please give me a star! </h3>
<h3>All projects are free, open source, and follow the corresponding open source license agreement. </h3>
<h1><a href="https://github.com/fankes/fankes/blob/main/project-promote/README.md">→ To see more about my projects, please click here ←</a></h1>
</div>
## Star History
![Star History Chart](https://api.star-history.com/svg?repos=KitsunePie/AppErrorsTracking&type=Date)
## License
- [AGPL-3.0](https://www.gnu.org/licenses/agpl-3.0.html)
```
Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
Copyright (C) 2017 Fankes Studio(qzmmcn@163.com)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
@@ -80,9 +120,9 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
along with this program. If not, see <https://www.gnu.org/licenses/>.
```
Powered by [YukiHookAPI](https://github.com/fankes/YukiHookAPI)
Powered by [YukiHookAPI](https://github.com/HighCapable/YukiHookAPI)
Copyright © 2019-2022 Fankes Studio(qzmmcn@163.com)
Copyright © 2017 Fankes Studio(qzmmcn@163.com)

15
app/.gitignore vendored
View File

@@ -1,15 +0,0 @@
*.iml
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
local.properties

View File

@@ -1,90 +0,0 @@
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id 'com.google.devtools.ksp' version '1.7.10-1.0.6'
}
android {
namespace 'com.fankes.apperrorstracking'
compileSdk 33
signingConfigs {
debug {
storeFile file('../keystore/public')
storePassword '123456'
keyAlias 'public'
keyPassword '123456'
v1SigningEnabled true
v2SigningEnabled true
}
}
defaultConfig {
applicationId "com.fankes.apperrorstracking"
minSdk 27
targetSdk 33
versionCode rootProject.ext.appVersionCode
versionName rootProject.ext.appVersionName
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
/** 添加 App Center Secret 到 BuildConfig */
buildConfigField("String", "APP_CENTER_SECRET", "\"${getAppCenterSecret()}\"")
}
buildTypes {
release {
minifyEnabled rootProject.ext.enableR8
shrinkResources rootProject.ext.enableR8
zipAlignEnabled rootProject.ext.enableR8
signingConfig signingConfigs.debug
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = '11'
freeCompilerArgs = [
'-Xno-param-assertions',
'-Xno-call-assertions',
'-Xno-receiver-assertions'
]
}
buildFeatures {
viewBinding true
}
lintOptions {
checkReleaseBuilds false
}
}
/**
* 获取 App Center Secret
* @return [String]
*/
String getAppCenterSecret() {
def fileName = '../.secret/APP_CENTER_SECRET'
def content = ''
if (file(fileName).exists()) file(fileName).eachLine { content = it }
return content
}
dependencies {
compileOnly 'de.robv.android.xposed:api:82'
implementation 'com.highcapable.yukihookapi:api:1.1.4'
ksp 'com.highcapable.yukihookapi:ksp-xposed:1.1.4'
implementation "com.microsoft.appcenter:appcenter-analytics:4.4.5"
implementation "com.microsoft.appcenter:appcenter-crashes:4.4.5"
implementation 'com.google.code.gson:gson:2.9.0'
implementation 'com.github.duanhong169:drawabletoolbox:1.0.7'
implementation "com.github.topjohnwu.libsu:core:3.1.2"
implementation 'androidx.core:core-ktx:1.9.0'
implementation 'androidx.appcompat:appcompat:1.5.1'
implementation 'com.google.android.material:material:1.6.1'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}

View File

@@ -1 +0,0 @@
com.fankes.apperrorstracking.hook.AppErrorsTracking

View File

@@ -1 +0,0 @@
com.fankes.apperrorstracking.hook.HookEntry

View File

@@ -1,174 +0,0 @@
/*
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* https://github.com/KitsunePie/AppErrorsTracking
*
* This software is non-free but opensource software: you can redistribute it
* and/or modify it under the terms of the GNU Affero General Public License
* as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* and eula along with this software. If not, see
* <https://www.gnu.org/licenses/>
*
* This file is Created by fankes on 2022/5/10.
*/
package com.fankes.apperrorstracking.bean
import android.app.ApplicationErrorReport
import android.os.Build
import androidx.annotation.Keep
import com.fankes.apperrorstracking.locale.LocaleString
import com.fankes.apperrorstracking.utils.factory.difference
import com.fankes.apperrorstracking.utils.factory.toUtcTime
import java.io.Serializable
import java.text.SimpleDateFormat
import java.util.*
/**
* 应用异常信息 bean
* @param pid 进程 ID
* @param userId 用户 ID
* @param packageName 包名
* @param isNativeCrash 是否为原生层异常
* @param exceptionClassName 异常类名
* @param exceptionMessage 异常信息
* @param throwClassName 抛出异常的类名
* @param throwFileName 抛出异常的文件名
* @param throwMethodName 抛出异常的方法名
* @param throwLineNumber 抛出异常的行号
* @param stackTrace 异常堆栈
* @param timestamp 记录时间戳
*/
data class AppErrorsInfoBean(
@Keep var pid: Int = -1,
@Keep var userId: Int = -1,
@Keep var packageName: String = "",
@Keep var isNativeCrash: Boolean = false,
@Keep var exceptionClassName: String = "",
@Keep var exceptionMessage: String = "",
@Keep var throwFileName: String = "",
@Keep var throwClassName: String = "",
@Keep var throwMethodName: String = "",
@Keep var throwLineNumber: Int = -1,
@Keep var stackTrace: String = "",
@Keep var timestamp: Long = -1L
) : Serializable {
companion object {
/**
* 创建一个空的 [AppErrorsInfoBean]
* @return [AppErrorsInfoBean]
*/
fun createEmpty() = AppErrorsInfoBean().apply { isEmpty = true }
/**
* 从 [ApplicationErrorReport.CrashInfo] 克隆
* @param pid APP 进程 ID
* @param packageName APP 包名
* @param userId APP 用户 ID
* @param crashInfo [ApplicationErrorReport.CrashInfo]
* @return [AppErrorsInfoBean]
*/
fun clone(pid: Int, packageName: String?, userId: Int?, crashInfo: ApplicationErrorReport.CrashInfo?) =
(crashInfo?.exceptionClassName?.lowercase() == "native crash").let { isNativeCrash ->
AppErrorsInfoBean(
pid = pid,
userId = userId ?: 0,
packageName = packageName ?: "unknown",
isNativeCrash = isNativeCrash,
exceptionClassName = crashInfo?.exceptionClassName ?: "unknown",
exceptionMessage = if (isNativeCrash) crashInfo?.stackTrace.let {
if (it?.contains(other = "Abort message: '") == true)
runCatching { it.split("Abort message: '")[1].split("'")[0] }.getOrNull()
?: crashInfo?.exceptionMessage ?: "unknown"
else crashInfo?.exceptionMessage ?: "unknown"
} else crashInfo?.exceptionMessage ?: "unknown",
throwFileName = crashInfo?.throwFileName ?: "unknown",
throwClassName = crashInfo?.throwClassName ?: "unknown",
throwMethodName = crashInfo?.throwMethodName ?: "unknown",
throwLineNumber = crashInfo?.throwLineNumber ?: -1,
stackTrace = crashInfo?.stackTrace?.trim() ?: "unknown",
timestamp = System.currentTimeMillis()
)
}
}
/** 标识当前内容是否为空 */
var isEmpty = false
/**
* 获取异常本地化 UTC 时间
* @return [String]
*/
val utcTime get() = timestamp.toUtcTime()
/**
* 获取异常本地化经过时间
* @return [String]
*/
val crossTime
get() = timestamp.difference(
now = LocaleString.momentAgo,
second = LocaleString.secondAgo,
minute = LocaleString.minuteAgo,
hour = LocaleString.hourAgo,
day = LocaleString.dayAgo,
month = LocaleString.monthAgo,
year = LocaleString.yearAgo
)
/**
* 获取异常本地化时间
* @return [String]
*/
val dateTime get() = SimpleDateFormat.getDateTimeInstance().format(Date(timestamp)) ?: utcTime
/**
* 获取异常堆栈分享模板
* @return [String]
*/
val stackOutputShareContent
get() = "Generated by AppErrorsTracking\n" +
"Project Url: https://github.com/KitsunePie/AppErrorsTracking\n" +
"===============\n" +
"[Device Brand]: ${Build.BRAND}\n" +
"[Device Model]: ${Build.MODEL}\n" +
"[Display]: ${Build.DISPLAY}\n" +
"[Android Version]: ${Build.VERSION.RELEASE}\n" +
"[API Version]: ${Build.VERSION.SDK_INT}\n" +
"[System Locale]: ${Locale.getDefault()}\n" +
"[Package Name]: $packageName\n" +
(if (userId > 0) "[User Id]: $userId\n" else "") +
"[Error Type]: ${if (isNativeCrash) "Native" else "Jvm"}\n" +
"[Crash Time]: $utcTime\n" +
"[Stack Trace]:\n" + stackTrace
/**
* 获取异常堆栈文件模板
* @return [String]
*/
val stackOutputFileContent
get() = "================================================================\n" +
" Generated by AppErrorsTracking\n" +
" Project Url: https://github.com/KitsunePie/AppErrorsTracking\n" +
"================================================================\n" +
"[Device Brand]: ${Build.BRAND}\n" +
"[Device Model]: ${Build.MODEL}\n" +
"[Display]: ${Build.DISPLAY}\n" +
"[Android Version]: ${Build.VERSION.RELEASE}\n" +
"[API Version]: ${Build.VERSION.SDK_INT}\n" +
"[System Locale]: ${Locale.getDefault()}\n" +
"[Package Name]: $packageName\n" +
(if (userId > 0) "[User Id]: $userId\n" else "") +
"[Error Type]: ${if (isNativeCrash) "Native" else "Jvm"}\n" +
"[Crash Time]: $utcTime\n" +
"[Stack Trace]:\n" + stackTrace
}

View File

@@ -1,104 +0,0 @@
/*
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* https://github.com/KitsunePie/AppErrorsTracking
*
* This software is non-free but opensource software: you can redistribute it
* and/or modify it under the terms of the GNU Affero General Public License
* as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* and eula along with this software. If not, see
* <https://www.gnu.org/licenses/>
*
* This file is Created by fankes on 2022/6/8.
*/
@file:Suppress("unused")
package com.fankes.apperrorstracking.data.factory
import android.content.Context
import com.highcapable.yukihookapi.hook.factory.modulePrefs
import com.highcapable.yukihookapi.hook.param.PackageParam
/**
* 获取此 APP 是否配置显示错误对话框
* @param packageName APP 包名
*/
fun PackageParam.isAppShowErrorsDialog(packageName: String) = prefs.getBoolean("${packageName}_show_errors_dialog", true)
/**
* 获取此 APP 是否配置显示错误通知推送
* @param packageName APP 包名
*/
fun PackageParam.isAppShowErrorsNotify(packageName: String) = prefs.getBoolean("${packageName}_show_errors_notify", false)
/**
* 获取此 APP 是否配置显示错误 Toast 提示
* @param packageName APP 包名
*/
fun PackageParam.isAppShowErrorsToast(packageName: String) = prefs.getBoolean("${packageName}_show_errors_toast", false)
/**
* 获取此 APP 是否配置不显示任何提示
* @param packageName APP 包名
*/
fun PackageParam.isAppShowNothing(packageName: String) = prefs.getBoolean("${packageName}_show_nothing", false)
/**
* 获取此 APP 是否配置显示错误对话框
* @param packageName APP 包名
*/
fun Context.isAppShowErrorsDialog(packageName: String) = modulePrefs.getBoolean("${packageName}_show_errors_dialog", true)
/**
* 获取此 APP 是否配置显示错误通知推送
* @param packageName APP 包名
*/
fun Context.isAppShowErrorsNotify(packageName: String) = modulePrefs.getBoolean("${packageName}_show_errors_notify", false)
/**
* 获取此 APP 是否配置显示错误 Toast 提示
* @param packageName APP 包名
*/
fun Context.isAppShowErrorsToast(packageName: String) = modulePrefs.getBoolean("${packageName}_show_errors_toast", false)
/**
* 获取此 APP 是否配置不显示任何提示
* @param packageName APP 包名
*/
fun Context.isAppShowNothing(packageName: String) = modulePrefs.getBoolean("${packageName}_show_nothing", false)
/**
* 设置此 APP 是否配置显示错误对话框
* @param packageName APP 包名
* @param isApply 是否设置
*/
fun Context.putAppShowErrorsDialog(packageName: String, isApply: Boolean) = modulePrefs.putBoolean("${packageName}_show_errors_dialog", isApply)
/**
* 设置此 APP 是否配置显示错误通知推送
* @param packageName APP 包名
* @param isApply 是否设置
*/
fun Context.putAppShowErrorsNotify(packageName: String, isApply: Boolean) = modulePrefs.putBoolean("${packageName}_show_errors_notify", isApply)
/**
* 设置此 APP 是否配置显示错误 Toast 提示
* @param packageName APP 包名
* @param isApply 是否设置
*/
fun Context.putAppShowErrorsToast(packageName: String, isApply: Boolean) = modulePrefs.putBoolean("${packageName}_show_errors_toast", isApply)
/**
* 设置此 APP 是否配置不显示任何提示
* @param packageName APP 包名
* @param isApply 是否设置
*/
fun Context.putAppShowNothing(packageName: String, isApply: Boolean) = modulePrefs.putBoolean("${packageName}_show_nothing", isApply)

View File

@@ -1,375 +0,0 @@
/*
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* https://github.com/KitsunePie/AppErrorsTracking
*
* This software is non-free but opensource software: you can redistribute it
* and/or modify it under the terms of the GNU Affero General Public License
* as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* and eula along with this software. If not, see
* <https://www.gnu.org/licenses/>
*
* This file is Created by fankes on 2022/5/7.
*/
@file:Suppress("UseCompatLoadingForDrawables")
package com.fankes.apperrorstracking.hook.entity
import android.app.Dialog
import android.content.Context
import android.content.Intent
import android.content.pm.ApplicationInfo
import android.os.Build
import android.os.Message
import androidx.core.graphics.drawable.IconCompat
import androidx.core.graphics.drawable.toBitmap
import com.fankes.apperrorstracking.BuildConfig
import com.fankes.apperrorstracking.R
import com.fankes.apperrorstracking.bean.AppErrorsDisplayBean
import com.fankes.apperrorstracking.bean.AppErrorsInfoBean
import com.fankes.apperrorstracking.bean.AppInfoBean
import com.fankes.apperrorstracking.bean.MutedErrorsAppBean
import com.fankes.apperrorstracking.data.ConfigData
import com.fankes.apperrorstracking.data.factory.isAppShowErrorsNotify
import com.fankes.apperrorstracking.data.factory.isAppShowErrorsToast
import com.fankes.apperrorstracking.data.factory.isAppShowNothing
import com.fankes.apperrorstracking.locale.LocaleString
import com.fankes.apperrorstracking.ui.activity.errors.AppErrorsDisplayActivity
import com.fankes.apperrorstracking.ui.activity.errors.AppErrorsRecordActivity
import com.fankes.apperrorstracking.utils.factory.*
import com.fankes.apperrorstracking.utils.tool.FrameworkTool
import com.highcapable.yukihookapi.hook.bean.VariousClass
import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker
import com.highcapable.yukihookapi.hook.factory.field
import com.highcapable.yukihookapi.hook.factory.hasMethod
import com.highcapable.yukihookapi.hook.factory.method
import com.highcapable.yukihookapi.hook.log.loggerD
import com.highcapable.yukihookapi.hook.log.loggerE
import com.highcapable.yukihookapi.hook.log.loggerI
import com.highcapable.yukihookapi.hook.log.loggerW
import com.highcapable.yukihookapi.hook.type.android.BundleClass
import com.highcapable.yukihookapi.hook.type.android.MessageClass
object FrameworkHooker : YukiBaseHooker() {
private const val ActivityManagerServiceClass = "com.android.server.am.ActivityManagerService"
private const val UserControllerClass = "com.android.server.am.UserController"
private const val AppErrorsClass = "com.android.server.am.AppErrors"
private const val AppErrorDialogClass = "com.android.server.am.AppErrorDialog"
private const val AppErrorDialog_DataClass = "com.android.server.am.AppErrorDialog\$Data"
private const val ProcessRecordClass = "com.android.server.am.ProcessRecord"
private const val ActivityTaskManagerService_LocalServiceClass = "com.android.server.wm.ActivityTaskManagerService\$LocalService"
private val PackageListClass = VariousClass(
"com.android.server.am.ProcessRecord\$PackageList",
"com.android.server.am.PackageList"
)
private val ErrorDialogControllerClass = VariousClass(
"com.android.server.am.ProcessRecord\$ErrorDialogController",
"com.android.server.am.ErrorDialogController"
)
/** 已记录的 APP 用户 ID */
private var appUserIdRecords = HashMap<Int, Int>()
/** 已忽略错误的 APP 数组 - 直到重新解锁 */
private var mutedErrorsIfUnlockApps = HashSet<String>()
/** 已忽略错误的 APP 数组 - 直到重新启动 */
private var mutedErrorsIfRestartApps = HashSet<String>()
/** 已记录的 APP 异常信息数组 */
private var appErrorsRecords = ArrayList<AppErrorsInfoBean>()
/** 注册生命周期 */
private fun registerLifecycle() {
onAppLifecycle {
/** 解锁后清空已记录的忽略错误 APP */
registerReceiver(Intent.ACTION_USER_PRESENT) { _, _ -> mutedErrorsIfUnlockApps.clear() }
/** 刷新模块 Resources 缓存 */
registerReceiver(Intent.ACTION_LOCALE_CHANGED) { _, _ -> refreshModuleAppResources() }
/** 启动时从本地获取异常记录 */
onCreate { appErrorsRecords = ConfigData.getResolverString(ConfigData.APP_ERRORS_DATA).toEntity() ?: arrayListOf() }
}
FrameworkTool.Host.with(instance = this) {
onOpenAppUsedFramework {
appContext?.openApp(it.first, it.second)
loggerI(msg = "Opened \"${it.first}\"${it.second.takeIf { e -> e > 0 }?.let { e -> " --user $e" } ?: ""}")
}
onPushAppErrorInfoData {
appErrorsRecords.firstOrNull { e -> e.pid == it } ?: run {
loggerW(msg = "Cannot received crash application data --pid $it")
AppErrorsInfoBean.createEmpty()
}
}
onPushAppErrorsInfoData { appErrorsRecords }
onRemoveAppErrorsInfoData {
loggerI(msg = "Removed app errors info data for package \"${it.packageName}\"")
appErrorsRecords.remove(it)
saveAllAppErrorsRecords()
}
onClearAppErrorsInfoData {
loggerI(msg = "Cleared all app errors info data, size ${appErrorsRecords.size}")
appErrorsRecords.clear()
saveAllAppErrorsRecords()
}
onMutedErrorsIfUnlock {
mutedErrorsIfUnlockApps.add(it)
loggerI(msg = "Muted \"$it\" until unlocks")
}
onMutedErrorsIfRestart {
mutedErrorsIfRestartApps.add(it)
loggerI(msg = "Muted \"$it\" until restarts")
}
onPushMutedErrorsAppsData {
arrayListOf<MutedErrorsAppBean>().apply {
mutedErrorsIfUnlockApps.takeIf { it.isNotEmpty() }
?.forEach { add(MutedErrorsAppBean(MutedErrorsAppBean.MuteType.UNTIL_UNLOCKS, it)) }
mutedErrorsIfRestartApps.takeIf { it.isNotEmpty() }
?.forEach { add(MutedErrorsAppBean(MutedErrorsAppBean.MuteType.UNTIL_REBOOTS, it)) }
}
}
onUnmuteErrorsApp {
when (it.type) {
MutedErrorsAppBean.MuteType.UNTIL_UNLOCKS -> {
loggerI(msg = "Unmuted if unlocks errors app \"${it.packageName}\"")
mutedErrorsIfUnlockApps.remove(it.packageName)
}
MutedErrorsAppBean.MuteType.UNTIL_REBOOTS -> {
loggerI(msg = "Unmuted if restarts errors app \"${it.packageName}\"")
mutedErrorsIfRestartApps.remove(it.packageName)
}
}
}
onUnmuteAllErrorsApps {
loggerI(msg = "Unmute all errors apps --unlocks ${mutedErrorsIfUnlockApps.size} --restarts ${mutedErrorsIfRestartApps.size}")
mutedErrorsIfUnlockApps.clear()
mutedErrorsIfRestartApps.clear()
}
onPushAppListData { filters ->
appContext?.let { context ->
arrayListOf<AppInfoBean>().apply {
context.listOfPackages().also { info ->
(if (filters.name.isNotBlank())
info.filter { it.packageName.contains(filters.name) || context.appNameOf(it.packageName).contains(filters.name) }
else info).let { result ->
if (filters.isContainsSystem.not())
result.filter { (it.applicationInfo.flags and ApplicationInfo.FLAG_SYSTEM) == 0 }
else result
}.sortedByDescending { it.lastUpdateTime }
.forEach { add(AppInfoBean(name = context.appNameOf(it.packageName), packageName = it.packageName)) }
/** 移除模块自身 */
removeIf { it.packageName == BuildConfig.APPLICATION_ID }
}
loggerD(msg = "Fetched installed packages list, size $size")
}
} ?: arrayListOf()
}
}
}
/** 保存异常记录到本地 */
private fun saveAllAppErrorsRecords() = newThread { ConfigData.putResolverString(ConfigData.APP_ERRORS_DATA, appErrorsRecords.toJson()) }
override fun onHook() {
/** 注册生命周期 */
registerLifecycle()
/** 干掉原生错误对话框 - 如果有 */
ErrorDialogControllerClass.hook {
injectMember {
method {
name = "hasCrashDialogs"
emptyParam()
}
replaceToTrue()
}
injectMember {
method {
name = "showCrashDialogs"
paramCount = 1
}
intercept()
}
}.ignoredHookClassNotFoundFailure()
/** 干掉原生错误对话框 - API 30 以下 */
ActivityTaskManagerService_LocalServiceClass.hook {
injectMember {
method {
name = "canShowErrorDialogs"
emptyParam()
}
replaceToFalse()
}
}.by { Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q }
/** 干掉原生错误对话框 - 如果上述方法全部失效则直接结束对话框 */
AppErrorDialogClass.hook {
injectMember {
method {
name = "onCreate"
param(BundleClass)
}
afterHook { instance<Dialog>().cancel() }
}
}
/** 注入自定义错误对话框 */
AppErrorsClass.hook {
injectMember {
method {
name = "handleShowAppErrorUi"
param(MessageClass)
}
afterHook {
/** 当前实例 */
val context = appContext ?: field { name = "mContext" }.get(instance).cast<Context>() ?: return@afterHook
/** 错误数据 */
val errData = args().first().cast<Message>()?.obj
/** 当前进程信息 */
val proc = AppErrorDialog_DataClass.toClass().field { name = "proc" }.get(errData).any()
/** 当前 pid 信息 */
val pid = ProcessRecordClass.toClass().field { name { it == "mPid" || it == "pid" } }.get(proc).int()
/** 当前用户 ID 信息 */
val userId = ProcessRecordClass.toClass().field { name = "userId" }.get(proc).int()
/** 当前 APP 信息 */
val appInfo = ProcessRecordClass.toClass().field { name = "info" }.get(proc).cast<ApplicationInfo>()
/** 当前进程名称 */
val processName = ProcessRecordClass.toClass().field { name = "processName" }.get(proc).string()
/** 当前 APP、进程 包名 */
val packageName = appInfo?.packageName ?: processName
/** 当前 APP 名称 */
val appName = appInfo?.let { context.appNameOf(it.packageName) } ?: packageName
/** 是否为 APP */
val isApp = (PackageListClass.toClass().method {
name = "size"
emptyParam()
}.get(if (ProcessRecordClass.toClass().hasMethod {
name = "getPkgList"
emptyParam()
}) ProcessRecordClass.toClass().method {
name = "getPkgList"
emptyParam()
}.get(proc).call() else ProcessRecordClass.toClass().field {
name = "pkgList"
}.get(proc).any()).int() == 1 && appInfo != null)
/** 是否为主进程 */
val isMainProcess = packageName == processName
/** 是否为后台进程 */
val isBackgroundProcess = UserControllerClass.toClass()
.method { name = "getCurrentProfileIds" }
.get(ActivityManagerServiceClass.toClass().field { name = "mUserController" }
.get(field { name = "mService" }.get(instance).any()).any())
.invoke<IntArray>()?.takeIf { it.isNotEmpty() }?.any { it != userId } ?: false
/** 是否短时内重复错误 */
val isRepeating = AppErrorDialog_DataClass.toClass().field { name = "repeating" }.get(errData).boolean()
/** 崩溃标题 */
val errorTitle = if (isRepeating) LocaleString.aerrRepeatedTitle(appName) else LocaleString.aerrTitle(appName)
/** 写入到用户 ID 记录 */
appUserIdRecords[pid] = userId
/** 打印错误日志 */
if (isApp) loggerE(
msg = "Application \"$packageName\" ${if (isRepeating) "keeps stopping" else "has stopped"}" +
(if (packageName != processName) " --process \"$processName\"" else "") +
"${if (userId != 0) " --user $userId" else ""} --pid $pid"
) else loggerE(msg = "Process \"$processName\" ${if (isRepeating) "keeps stopping" else "has stopped"} --pid $pid")
/** 判断是否为模块自身崩溃 */
if (packageName == BuildConfig.APPLICATION_ID) {
context.toast(msg = "AppErrorsTracking has crashed, please see the log in console")
loggerE(msg = "AppErrorsTracking has crashed itself, please see the Android Runtime Exception in console")
return@afterHook
}
/** 判断是否为已忽略的 APP */
if (mutedErrorsIfUnlockApps.contains(packageName) || mutedErrorsIfRestartApps.contains(packageName)) return@afterHook
/** 判断配置模块启用状态 */
if (ConfigData.isEnableAppConfigTemplate) {
if (isAppShowNothing(packageName)) return@afterHook
if (isAppShowErrorsNotify(packageName)) {
context.pushNotify(
channelId = "APPS_ERRORS",
channelName = LocaleString.appName,
title = errorTitle,
content = LocaleString.appErrorsTip,
icon = IconCompat.createWithBitmap(moduleAppResources.drawableOf(R.drawable.ic_notify).toBitmap()),
color = 0xFFFF6200.toInt(),
intent = AppErrorsRecordActivity.intent()
)
return@afterHook
}
if (isAppShowErrorsToast(packageName)) {
context.toast(errorTitle)
return@afterHook
}
}
/** 判断是否为后台进程 */
if ((isBackgroundProcess || context.isAppCanOpened(packageName).not()) && ConfigData.isEnableOnlyShowErrorsInFront)
return@afterHook
/** 判断是否为主进程 */
if (isMainProcess.not() && ConfigData.isEnableOnlyShowErrorsInMain) return@afterHook
/** 启动错误对话框显示窗口 */
AppErrorsDisplayActivity.start(
context, AppErrorsDisplayBean(
pid = pid,
userId = userId,
packageName = packageName,
processName = processName,
appName = appName,
title = errorTitle,
isShowAppInfoButton = isApp,
isShowReopenButton = isApp && (isRepeating.not() || ConfigData.isEnableAlwaysShowsReopenAppOptions)
&& context.isAppCanOpened(packageName) && isMainProcess,
isShowCloseAppButton = isApp
)
)
}
}
injectMember {
method {
name = "crashApplication"
paramCount = 2
}
afterHook {
/** 当前进程信息 */
val proc = args().first().any() ?: return@afterHook loggerW(msg = "Received but got null ProcessRecord")
/** 当前 pid 信息 */
val pid = ProcessRecordClass.toClass().field { name { it == "mPid" || it == "pid" } }.get(proc).int()
/** 当前 APP 信息 */
val appInfo = ProcessRecordClass.toClass().field { name = "info" }.get(proc).cast<ApplicationInfo>()
/** 启动新线程延迟防止方法执行顺序在前导致无法正确获取数据 */
newThread {
/** 延迟 50ms */
Thread.sleep(50)
/** 添加当前异常信息到第一位 */
appErrorsRecords.add(
0, AppErrorsInfoBean.clone(pid, appInfo?.packageName, appUserIdRecords[pid], args().last().cast())
)
loggerI(msg = "Received crash application data --pid $pid")
}
/** 保存异常记录到本地 */
saveAllAppErrorsRecords()
}
}
}
}
}

View File

@@ -1,505 +0,0 @@
/*
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* https://github.com/KitsunePie/AppErrorsTracking
*
* This software is non-free but opensource software: you can redistribute it
* and/or modify it under the terms of the GNU Affero General Public License
* as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* and eula along with this software. If not, see
* <https://www.gnu.org/licenses/>
*
* This file is Created by fankes on 2022/5/11.
*/
@file:Suppress("MemberVisibilityCanBePrivate", "StaticFieldLeak", "unused")
package com.fankes.apperrorstracking.locale
import android.content.Context
import android.content.res.Resources
import com.fankes.apperrorstracking.R
import com.highcapable.yukihookapi.hook.param.PackageParam
/**
* I18n 字符串实例
*/
object LocaleString {
/** 当前的 [Context] */
private var baseContext: Context? = null
/** 当前的 [PackageParam] */
private var basePackageParam: PackageParam? = null
/** 当前的 [Resources] */
private var baseResources: Resources? = null
/**
* 当前的 [Resources]
* @return [Resources]
* @throws IllegalStateException 如果 [LocaleString] 没有被绑定
*/
private val resources
get() = baseContext?.resources ?: basePackageParam?.moduleAppResources ?: baseResources
?: error("LocaleString must bind an instance first")
/**
* 绑定并初始化
* @param instance 可以是 [Context]、[PackageParam]、[Resources]
*/
fun bind(instance: Any) {
when (instance) {
is Context -> baseContext = instance
is PackageParam -> basePackageParam = instance
is Resources -> baseResources = instance
else -> error("LocaleString bind an unknown instance")
}
}
/**
* 根据资源 Id 获取字符串
* @param objArrs 格式化实例
* @return [String]
*/
private fun Int.bind(vararg objArrs: Any) = resources.getString(this, *objArrs)
/** @string Automatic generated */
val appName get() = appName()
/** @string Automatic generated */
fun appName(vararg objArrs: Any) = R.string.app_name.bind(*objArrs)
/** @string Automatic generated */
val copied get() = copied()
/** @string Automatic generated */
fun copied(vararg objArrs: Any) = R.string.copied.bind(*objArrs)
/** @string Automatic generated */
val copyFail get() = copyFail()
/** @string Automatic generated */
fun copyFail(vararg objArrs: Any) = R.string.copy_fail.bind(*objArrs)
/** @string Automatic generated */
val printToLogcatSuccess get() = printToLogcatSuccess()
/** @string Automatic generated */
fun printToLogcatSuccess(vararg objArrs: Any) = R.string.print_to_logcat_success.bind(*objArrs)
/** @string Automatic generated */
val outputStackSuccess get() = outputStackSuccess()
/** @string Automatic generated */
fun outputStackSuccess(vararg objArrs: Any) = R.string.output_stack_success.bind(*objArrs)
/** @string Automatic generated */
val outputStackFail get() = outputStackFail()
/** @string Automatic generated */
fun outputStackFail(vararg objArrs: Any) = R.string.output_stack_fail.bind(*objArrs)
/** @string Automatic generated */
val aerrTitle get() = aerrTitle()
/** @string Automatic generated */
fun aerrTitle(vararg objArrs: Any) = R.string.aerr_title.bind(*objArrs)
/** @string Automatic generated */
val aerrRepeatedTitle get() = aerrRepeatedTitle()
/** @string Automatic generated */
fun aerrRepeatedTitle(vararg objArrs: Any) = R.string.aerr_repeated_title.bind(*objArrs)
/** @string Automatic generated */
val appInfo get() = appInfo()
/** @string Automatic generated */
fun appInfo(vararg objArrs: Any) = R.string.app_info.bind(*objArrs)
/** @string Automatic generated */
val closeApp get() = closeApp()
/** @string Automatic generated */
fun closeApp(vararg objArrs: Any) = R.string.close_app.bind(*objArrs)
/** @string Automatic generated */
val reopenApp get() = reopenApp()
/** @string Automatic generated */
fun reopenApp(vararg objArrs: Any) = R.string.reopen_app.bind(*objArrs)
/** @string Automatic generated */
val errorDetail get() = errorDetail()
/** @string Automatic generated */
fun errorDetail(vararg objArrs: Any) = R.string.error_detail.bind(*objArrs)
/** @string Automatic generated */
val muteIfUnlock get() = muteIfUnlock()
/** @string Automatic generated */
fun muteIfUnlock(vararg objArrs: Any) = R.string.mute_if_unlock.bind(*objArrs)
/** @string Automatic generated */
val muteIfRestart get() = muteIfRestart()
/** @string Automatic generated */
fun muteIfRestart(vararg objArrs: Any) = R.string.mute_if_restart.bind(*objArrs)
/** @string Automatic generated */
val muteIfUnlockTip get() = muteIfUnlockTip()
/** @string Automatic generated */
fun muteIfUnlockTip(vararg objArrs: Any) = R.string.mute_if_unlock_tip.bind(*objArrs)
/** @string Automatic generated */
val muteIfRestartTip get() = muteIfRestartTip()
/** @string Automatic generated */
fun muteIfRestartTip(vararg objArrs: Any) = R.string.mute_if_restart_tip.bind(*objArrs)
/** @string Automatic generated */
val noListData get() = noListData()
/** @string Automatic generated */
fun noListData(vararg objArrs: Any) = R.string.no_list_data.bind(*objArrs)
/** @string Automatic generated */
val confirm get() = confirm()
/** @string Automatic generated */
fun confirm(vararg objArrs: Any) = R.string.confirm.bind(*objArrs)
/** @string Automatic generated */
val cancel get() = cancel()
/** @string Automatic generated */
fun cancel(vararg objArrs: Any) = R.string.cancel.bind(*objArrs)
/** @string Automatic generated */
val more get() = more()
/** @string Automatic generated */
fun more(vararg objArrs: Any) = R.string.more.bind(*objArrs)
/** @string Automatic generated */
val notice get() = notice()
/** @string Automatic generated */
fun notice(vararg objArrs: Any) = R.string.notice.bind(*objArrs)
/** @string Automatic generated */
val warning get() = warning()
/** @string Automatic generated */
fun warning(vararg objArrs: Any) = R.string.warning.bind(*objArrs)
/** @string Automatic generated */
val areYouSureClearErrors get() = areYouSureClearErrors()
/** @string Automatic generated */
fun areYouSureClearErrors(vararg objArrs: Any) = R.string.are_you_sure_clear_errors.bind(*objArrs)
/** @string Automatic generated */
val allErrorsClearSuccess get() = allErrorsClearSuccess()
/** @string Automatic generated */
fun allErrorsClearSuccess(vararg objArrs: Any) = R.string.all_errors_clear_success.bind(*objArrs)
/** @string Automatic generated */
val areYouSureExportAllErrors get() = areYouSureExportAllErrors()
/** @string Automatic generated */
fun areYouSureExportAllErrors(vararg objArrs: Any) = R.string.are_you_sure_export_all_errors.bind(*objArrs)
/** @string Automatic generated */
val exportAllErrorsSuccess get() = exportAllErrorsSuccess()
/** @string Automatic generated */
fun exportAllErrorsSuccess(vararg objArrs: Any) = R.string.export_all_errors_success.bind(*objArrs)
/** @string Automatic generated */
val exportAllErrorsFail get() = exportAllErrorsFail()
/** @string Automatic generated */
fun exportAllErrorsFail(vararg objArrs: Any) = R.string.export_all_errors_fail.bind(*objArrs)
/** @string Automatic generated */
val noCpuAbi get() = noCpuAbi()
/** @string Automatic generated */
fun noCpuAbi(vararg objArrs: Any) = R.string.no_cpu_abi.bind(*objArrs)
/** @string Automatic generated */
val areYouSureRemoveRecord get() = areYouSureRemoveRecord()
/** @string Automatic generated */
fun areYouSureRemoveRecord(vararg objArrs: Any) = R.string.are_you_sure_remove_record.bind(*objArrs)
/** @string Automatic generated */
val gotIt get() = gotIt()
/** @string Automatic generated */
fun gotIt(vararg objArrs: Any) = R.string.got_it.bind(*objArrs)
/** @string Automatic generated */
val moduleVersion get() = moduleVersion()
/** @string Automatic generated */
fun moduleVersion(vararg objArrs: Any) = R.string.module_version.bind(*objArrs)
/** @string Automatic generated */
val systemVersion get() = systemVersion()
/** @string Automatic generated */
fun systemVersion(vararg objArrs: Any) = R.string.system_version.bind(*objArrs)
/** @string Automatic generated */
val areYouSureRestartSystem get() = areYouSureRestartSystem()
/** @string Automatic generated */
fun areYouSureRestartSystem(vararg objArrs: Any) = R.string.are_your_sure_restart_system.bind(*objArrs)
/** @string Automatic generated */
val fastRestart get() = fastRestart()
/** @string Automatic generated */
fun fastRestart(vararg objArrs: Any) = R.string.fast_restart.bind(*objArrs)
/** @string Automatic generated */
val accessRootFail get() = accessRootFail()
/** @string Automatic generated */
fun accessRootFail(vararg objArrs: Any) = R.string.access_root_fail.bind(*objArrs)
/** @string Automatic generated */
val moduleNotActivated get() = moduleNotActivated()
/** @string Automatic generated */
fun moduleNotActivated(vararg objArrs: Any) = R.string.module_not_activated.bind(*objArrs)
/** @string Automatic generated */
val moduleIsActivated get() = moduleIsActivated()
/** @string Automatic generated */
fun moduleIsActivated(vararg objArrs: Any) = R.string.module_is_activated.bind(*objArrs)
/** @string Automatic generated */
val moduleNotFullyActivated get() = moduleNotFullyActivated()
/** @string Automatic generated */
fun moduleNotFullyActivated(vararg objArrs: Any) = R.string.module_not_fully_activated.bind(*objArrs)
/** @string Automatic generated */
val momentAgo get() = momentAgo()
/** @string Automatic generated */
fun momentAgo(vararg objArrs: Any) = R.string.moment_ago.bind(*objArrs)
/** @string Automatic generated */
val secondAgo get() = secondAgo()
/** @string Automatic generated */
fun secondAgo(vararg objArrs: Any) = R.string.second_ago.bind(*objArrs)
/** @string Automatic generated */
val minuteAgo get() = minuteAgo()
/** @string Automatic generated */
fun minuteAgo(vararg objArrs: Any) = R.string.minute_ago.bind(*objArrs)
/** @string Automatic generated */
val hourAgo get() = hourAgo()
/** @string Automatic generated */
fun hourAgo(vararg objArrs: Any) = R.string.hour_ago.bind(*objArrs)
/** @string Automatic generated */
val dayAgo get() = dayAgo()
/** @string Automatic generated */
fun dayAgo(vararg objArrs: Any) = R.string.day_ago.bind(*objArrs)
/** @string Automatic generated */
val monthAgo get() = monthAgo()
/** @string Automatic generated */
fun monthAgo(vararg objArrs: Any) = R.string.month_ago.bind(*objArrs)
/** @string Automatic generated */
val yearAgo get() = yearAgo()
/** @string Automatic generated */
fun yearAgo(vararg objArrs: Any) = R.string.year_ago.bind(*objArrs)
/** @string Automatic generated */
val crashProcess get() = crashProcess()
/** @string Automatic generated */
fun crashProcess(vararg objArrs: Any) = R.string.crash_process.bind(*objArrs)
/** @string Automatic generated */
val shareErrorStack get() = shareErrorStack()
/** @string Automatic generated */
fun shareErrorStack(vararg objArrs: Any) = R.string.share_error_stack.bind(*objArrs)
/** @string Automatic generated */
val areYouSureUnmuteAll get() = areYouSureUnmuteAll()
/** @string Automatic generated */
fun areYouSureUnmuteAll(vararg objArrs: Any) = R.string.are_you_sure_unmute_all.bind(*objArrs)
/** @string Automatic generated */
val noListResult get() = noListResult()
/** @string Automatic generated */
fun noListResult(vararg objArrs: Any) = R.string.no_list_result.bind(*objArrs)
/** @string Automatic generated */
val filterByCondition get() = filterByCondition()
/** @string Automatic generated */
fun filterByCondition(vararg objArrs: Any) = R.string.filter_by_condition.bind(*objArrs)
/** @string Automatic generated */
val clearFilters get() = clearFilters()
/** @string Automatic generated */
fun clearFilters(vararg objArrs: Any) = R.string.clear_filters.bind(*objArrs)
/** @string Automatic generated */
val resultCount get() = resultCount()
/** @string Automatic generated */
fun resultCount(vararg objArrs: Any) = R.string.result_count.bind(*objArrs)
/** @string Automatic generated */
val loading get() = loading()
/** @string Automatic generated */
fun loading(vararg objArrs: Any) = R.string.loading.bind(*objArrs)
/** @string Automatic generated */
val showErrorsDialog get() = showErrorsDialog()
/** @string Automatic generated */
fun showErrorsDialog(vararg objArrs: Any) = R.string.show_errors_dialog.bind(*objArrs)
/** @string Automatic generated */
val showErrorsToast get() = showErrorsToast()
/** @string Automatic generated */
fun showErrorsToast(vararg objArrs: Any) = R.string.show_errors_toast.bind(*objArrs)
/** @string Automatic generated */
val showNothing get() = showNothing()
/** @string Automatic generated */
fun showNothing(vararg objArrs: Any) = R.string.show_nothing.bind(*objArrs)
/** @string Automatic generated */
val appErrorsStatistics get() = appErrorsStatistics()
/** @string Automatic generated */
fun appErrorsStatistics(vararg objArrs: Any) = R.string.app_errors_statistics.bind(*objArrs)
/** @string Automatic generated */
val totalErrorsUnit get() = totalErrorsUnit()
/** @string Automatic generated */
fun totalErrorsUnit(vararg objArrs: Any) = R.string.total_errors_unit.bind(*objArrs)
/** @string Automatic generated */
val totalAppsUnit get() = totalAppsUnit()
/** @string Automatic generated */
fun totalAppsUnit(vararg objArrs: Any) = R.string.total_apps_unit.bind(*objArrs)
/** @string Automatic generated */
val generatingStatistics get() = generatingStatistics()
/** @string Automatic generated */
fun generatingStatistics(vararg objArrs: Any) = R.string.generating_statistics.bind(*objArrs)
/** @string Automatic generated */
val moduleNotFullyActivatedTip get() = moduleNotFullyActivatedTip()
/** @string Automatic generated */
fun moduleNotFullyActivatedTip(vararg objArrs: Any) = R.string.module_not_fully_activated_tip.bind(*objArrs)
/** @string Automatic generated */
val showErrorsNotify get() = showErrorsNotify()
/** @string Automatic generated */
fun showErrorsNotify(vararg objArrs: Any) = R.string.show_errors_notify.bind(*objArrs)
/** @string Automatic generated */
val appErrorsTip get() = appErrorsTip()
/** @string Automatic generated */
fun appErrorsTip(vararg objArrs: Any) = R.string.app_errors_tip.bind(*objArrs)
/** @string Automatic generated */
val batchOperations get() = batchOperations()
/** @string Automatic generated */
fun batchOperations(vararg objArrs: Any) = R.string.batch_operations.bind(*objArrs)
/** @string Automatic generated */
val areYouSureApplySiteApps get() = areYouSureApplySiteApps()
/** @string Automatic generated */
fun areYouSureApplySiteApps(vararg objArrs: Any) = R.string.are_you_sure_apply_site_apps.bind(*objArrs)
/** @string Automatic generated */
val developerNoticeTip get() = developerNoticeTip()
/** @string Automatic generated */
fun developerNoticeTip(vararg objArrs: Any) = R.string.developer_notice_tip.bind(*objArrs)
/** @string Automatic generated */
val developerNotice get() = developerNotice()
/** @string Automatic generated */
fun developerNotice(vararg objArrs: Any) = R.string.developer_notice.bind(*objArrs)
/** @string Automatic generated */
val fastRestartProblem get() = fastRestartProblem()
/** @string Automatic generated */
fun fastRestartProblem(vararg objArrs: Any) = R.string.fast_restart_problem.bind(*objArrs)
/** @string Automatic generated */
val userId get() = userId()
/** @string Automatic generated */
fun userId(vararg objArrs: Any) = R.string.user_id.bind(*objArrs)
/** @string Automatic generated */
val unableGetAppErrorsRecordTip get() = unableGetAppErrorsRecordTip()
/** @string Automatic generated */
fun unableGetAppErrorsRecordTip(vararg objArrs: Any) = R.string.unable_get_app_errors_record_tip.bind(*objArrs)
/** @string Automatic generated */
val exportAllLogsSuccess get() = exportAllLogsSuccess()
/** @string Automatic generated */
fun exportAllLogsSuccess(vararg objArrs: Any) = R.string.export_all_logs_success.bind(*objArrs)
/** @string Automatic generated */
val exportAllLogsFail get() = exportAllLogsFail()
/** @string Automatic generated */
fun exportAllLogsFail(vararg objArrs: Any) = R.string.export_all_logs_fail.bind(*objArrs)
}

View File

@@ -1,136 +0,0 @@
/*
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* https://github.com/KitsunePie/AppErrorsTracking
*
* This software is non-free but opensource software: you can redistribute it
* and/or modify it under the terms of the GNU Affero General Public License
* as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* and eula along with this software. If not, see
* <https://www.gnu.org/licenses/>
*
* This file is Created by fankes on 2022/5/7.
*/
@file:Suppress("DEPRECATION", "OVERRIDE_DEPRECATION")
package com.fankes.apperrorstracking.ui.activity.errors
import android.app.Activity
import android.content.Context
import android.content.Intent
import androidx.core.view.isGone
import androidx.core.view.isVisible
import com.fankes.apperrorstracking.R
import com.fankes.apperrorstracking.bean.AppErrorsInfoBean
import com.fankes.apperrorstracking.databinding.ActivityAppErrorsDetailBinding
import com.fankes.apperrorstracking.locale.LocaleString
import com.fankes.apperrorstracking.ui.activity.base.BaseActivity
import com.fankes.apperrorstracking.utils.factory.*
import com.highcapable.yukihookapi.hook.log.loggerE
class AppErrorsDetailActivity : BaseActivity<ActivityAppErrorsDetailBinding>() {
companion object {
/** 请求保存文件回调标识 */
private const val WRITE_REQUEST_CODE = 0
/** [AppErrorsInfoBean] 传值 */
private const val EXTRA_APP_ERRORS_INFO = "app_errors_info_extra"
/**
* 启动 [AppErrorsDetailActivity]
* @param context 实例
* @param appErrorsInfo 应用异常信息
*/
fun start(context: Context, appErrorsInfo: AppErrorsInfoBean) =
context.navigate<AppErrorsDetailActivity> { putExtra(EXTRA_APP_ERRORS_INFO, appErrorsInfo) }
}
/** 预导出的异常堆栈 */
private var stackTrace = ""
override fun onCreate() {
val appErrorsInfo = runCatching { intent?.getSerializableExtraCompat<AppErrorsInfoBean>(EXTRA_APP_ERRORS_INFO) }.getOrNull()
?: return toastAndFinish(name = "AppErrorsInfo")
if (appErrorsInfo.isEmpty) {
binding.appPanelScrollView.isVisible = false
showDialog {
title = LocaleString.notice
msg = LocaleString.unableGetAppErrorsRecordTip
confirmButton(LocaleString.gotIt) {
cancel()
finish()
}
noCancelable()
}
return
}
binding.appInfoItem.setOnClickListener { openSelfSetting(appErrorsInfo.packageName) }
binding.titleBackIcon.setOnClickListener { onBackPressed() }
binding.printIcon.setOnClickListener {
loggerE(msg = appErrorsInfo.stackTrace)
toast(LocaleString.printToLogcatSuccess)
}
binding.copyIcon.setOnClickListener { copyToClipboard(appErrorsInfo.stackOutputShareContent) }
binding.exportIcon.setOnClickListener {
stackTrace = appErrorsInfo.stackOutputFileContent
runCatching {
startActivityForResult(Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "*/application"
putExtra(Intent.EXTRA_TITLE, "${appErrorsInfo.packageName}_${appErrorsInfo.utcTime}.log")
}, WRITE_REQUEST_CODE)
}.onFailure { toast(msg = "Start Android SAF failed") }
}
binding.shareIcon.setOnClickListener {
startActivity(Intent.createChooser(Intent(Intent.ACTION_SEND).apply {
type = "text/plain"
putExtra(Intent.EXTRA_TEXT, appErrorsInfo.stackOutputShareContent)
}, LocaleString.shareErrorStack))
}
binding.appIcon.setImageDrawable(appIconOf(appErrorsInfo.packageName))
binding.appNameText.text = appNameOf(appErrorsInfo.packageName)
binding.appVersionText.text = appVersionBrandOf(appErrorsInfo.packageName)
binding.appUserIdText.isVisible = appErrorsInfo.userId > 0
binding.appUserIdText.text = LocaleString.userId(appErrorsInfo.userId)
binding.appAbiText.text = appCpuAbiOf(appErrorsInfo.packageName).ifBlank { LocaleString.noCpuAbi }
binding.jvmErrorPanel.isGone = appErrorsInfo.isNativeCrash
binding.errorTypeIcon.setImageResource(if (appErrorsInfo.isNativeCrash) R.drawable.ic_cpp else R.drawable.ic_java)
binding.errorInfoText.text = appErrorsInfo.exceptionMessage
binding.errorTypeText.text = appErrorsInfo.exceptionClassName
binding.errorFileNameText.text = appErrorsInfo.throwFileName
binding.errorThrowClassText.text = appErrorsInfo.throwClassName
binding.errorThrowMethodText.text = appErrorsInfo.throwMethodName
binding.errorLineNumberText.text = appErrorsInfo.throwLineNumber.toString()
binding.errorRecordTimeText.text = appErrorsInfo.dateTime
binding.errorStackText.text = appErrorsInfo.stackTrace
binding.appPanelScrollView.setOnScrollChangeListener { _, _, y, _, _ ->
binding.detailTitleText.text = if (y >= 30.dp(context = this)) appNameOf(appErrorsInfo.packageName) else LocaleString.appName
}
binding.detailTitleText.setOnClickListener { binding.appPanelScrollView.smoothScrollTo(0, 0) }
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == WRITE_REQUEST_CODE && resultCode == Activity.RESULT_OK) runCatching {
data?.data?.let {
contentResolver?.openOutputStream(it)?.apply { write(stackTrace.toByteArray()) }?.close()
toast(LocaleString.outputStackSuccess)
} ?: toast(LocaleString.outputStackFail)
}.onFailure { toast(LocaleString.outputStackFail) }
}
override fun onBackPressed() {
intent?.removeExtra(EXTRA_APP_ERRORS_INFO)
finish()
}
}

View File

@@ -1,31 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingLeft="15dp"
android:paddingTop="15dp"
android:paddingRight="15dp">
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/app_filters_edit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:hint="@string/typo_app_name_pkg_name"
android:singleLine="true" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/contains_system_switch"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="10dp"
android:text="@string/result_contains_system_apps"
app:buttonTint="@color/colorPrimaryAccent" />
</LinearLayout>

View File

@@ -1,136 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">エラー追跡</string>
<string name="xposed_desc">ネイティブFCダイアログに機能を追加し、中国の国内カスタムROMがFCダイアログを削除する問題を修正して、Android開発者のエクスペリエンスを向上させます。</string>
<string name="app_info">アプリ情報</string>
<string name="reopen_app">アプリをリスタート</string>
<string name="error_detail">エラーの詳細</string>
<string name="mute_if_unlock">無視(ロックが解除されるまで)</string>
<string name="mute_if_restart">無視(システイムがリスタートまで)</string>
<string name="close_app">アプリを閉じる</string>
<string name="aerr_title">%1$s がエラー</string>
<string name="aerr_repeated_title">%1$s が再びエラー</string>
<string name="mute_if_restart_tip">システイムがリスタートするまで、「%1$s」のエラーを無視します</string>
<string name="mute_if_unlock_tip">ロックが解除されるまで、「%1$s」のエラーを無視します</string>
<string name="back">戻る</string>
<string name="copy_error_stack">エラースタックをコピーする</string>
<string name="export_to_file">ファイルにエクスポート</string>
<string name="error_info">エラー情報</string>
<string name="error_type">エラータイプ</string>
<string name="error_file_name">ファイル名</string>
<string name="error_throw_class">投擲クラス</string>
<string name="error_throw_method">投擲メソッド</string>
<string name="error_line_number">行番号</string>
<string name="error_record_time">記録タイム</string>
<string name="copied">コピーしました</string>
<string name="copy_fail">コピーに失敗しました</string>
<string name="print_to_logcat">コンソールに印刷</string>
<string name="print_to_logcat_success">コンソールに印刷されていました</string>
<string name="output_stack_success">エクスポートされたエラースタックされていました</string>
<string name="output_stack_fail">エラースタックのエクスポートに失敗しました</string>
<string name="export_all">すべてエクスポート</string>
<string name="clear_all">すべてクリア</string>
<string name="errors_record">エラー履歴記錄</string>
<string name="no_list_data">一時的にデータレコードはありません</string>
<string name="confirm">確認</string>
<string name="cancel">キャンセル</string>
<string name="more">モア</string>
<string name="notice">ヒント</string>
<string name="are_you_sure_clear_errors">すべてのエラーレコードをクリアしてもよろしいですか</string>
<string name="all_errors_clear_success">すべてのアラーレコードがクリアされました</string>
<string name="are_you_sure_export_all_errors">すべてのログファイルをエクスポートしてもよろしいですか。梱包プロセスには時間がかかる場合があります。</string>
<string name="view_detail">詳細を見る</string>
<string name="export_all_errors_success">すべてのエラーレコードがエクスポートされました</string>
<string name="export_all_errors_fail">すべてのエラーレコードのエクスポートに失敗しました</string>
<string name="no_cpu_abi">アビなし</string>
<string name="remove_record">レコードを削除</string>
<string name="are_you_sure_remove_record">このレコードを削除してもよろしいですか</string>
<string name="got_it">分かります</string>
<string name="restart_system">リスタートシステム</string>
<string name="project_address">プロジェクトアドレス</string>
<string name="module_not_activated">モジュールが無効化でした</string>
<string name="module_version">モジュールバージョン: %1$s</string>
<string name="system_version">システムバージョン: %1$s</string>
<string name="module_is_activated">モジュールが有効化でした</string>
<string name="display_settings">表示設定</string>
<string name="hide_app_icon_on_launcher">デスクトップ上のアプリアイコンを非表示</string>
<string name="hide_app_icon_on_launcher_tip">モジュールアイコンを非表示にすると、インターフェイスが閉じてデスクトップに表示されなくなります。モジュール設定は、EdXposedまたはLSPosedで検索して開くことができます。</string>
<string name="hide_app_icon_on_launcher_notice">LSPosedの「Force apps to show launcher icons」機能を必ずオフにしてください</string>
<string name="about_module">このモジュールは、YukiHookAPIを使用して構築できます。 \n詳細 https://github.com/fankes/YukiHookAPI</string>
<string name="module_not_fully_activated">モジュールが不完全に有効化でした</string>
<string name="are_your_sure_restart_system">リスタートシステムしてもよろしいですか</string>
<string name="fast_restart">高速リスタート</string>
<string name="access_root_fail">ルート権限を取得できませんでした</string>
<string name="moment_ago">現在</string>
<string name="second_ago">秒前</string>
<string name="minute_ago">分前</string>
<string name="hour_ago">時間前</string>
<string name="day_ago">日前</string>
<string name="month_ago">月前</string>
<string name="year_ago">年前</string>
<string name="crash_process">エラープロセス「%1$s」</string>
<string name="share_error_stack">エラースタックをシェアする</string>
<string name="preference_settings">好み設定</string>
<string name="function_mgr">機能管理</string>
<string name="only_show_errors_in_front">フロントアプリのエラーダイアログを表示</string>
<string name="only_show_errors_in_main_process">メインプロセスのエラーダイアログを表示</string>
<string name="enable_apps_config_template">アプリ設定テンプレートを有効</string>
<string name="mgr_apps_config_template">アプリ設定テンプレートを管理する</string>
<string name="view_errors_record">エラー履歴を表示する</string>
<string name="view_muted_errors_apps">エラーを無視したアプリを表示する</string>
<string name="only_show_errors_in_front_tip">有効にすると、エラーが発生したアプリがフォアグラウンド(使用中)の場合にのみエラーダイアログが表示されます。エラーが発生したバックグラウンドアプリではエラーダイアログは表示されませんが、すべてがに記録されます。エラー履歴。</string>
<string name="only_show_errors_in_main_process_tip">有効にすると、エラーダイアログは、アプリで発生したエラーがメインプロセス(最初のアプリケーションインスタンスオブジェクト)にある場合にのみ表示されます。</string>
<string name="apps_config_template_tip">ここでは、アプリごとに個別にエラーが発生したときにエラーダイアログおよびその他のエラーメッセージを表示するかどうかを設定できます。</string>
<string name="view_muted_errors_apps_tip">ここでは、さまざまな形式のエラーを手動で無視したアプリを見つけることができます。このリストは、リスタート後に自動的にクリアされます。これらの無視されたアプリを管理し、無視リストから削除できます。</string>
<string name="muted_errors_apps">エラーを無視したアプリ</string>
<string name="unmute">無視を解除</string>
<string name="unmute_all">無視を全部解除</string>
<string name="are_you_sure_unmute_all">エラーを無視したすべてのアプリを解除してもよろしいですか</string>
<string name="apps_config_template">アプリ設定テンプレート</string>
<string name="filter_by_condition">条件でフィルタリング</string>
<string name="no_list_result">表示結果はありません</string>
<string name="typo_app_name_pkg_name">アプリ名とパッケージ名を入力できます</string>
<string name="result_contains_system_apps">結果にはシステムアプリが含まれます</string>
<string name="clear_filters">条件をクリア</string>
<string name="result_count">%1$s の合計結果</string>
<string name="loading">ロード中</string>
<string name="when_errors_how_to_show_tip">エラーが適用されているときにシステムがエラーを表示する方法を構成できます。</string>
<string name="show_errors_dialog">エラーダイアログを表示</string>
<string name="show_errors_toast">エラー Toast を表示</string>
<string name="show_nothing">何も表示されません</string>
<string name="app_errors_statistics">アプリのエラー統計</string>
<string name="total_errors">エラーの総数</string>
<string name="total_apps">アプリの総数</string>
<string name="most_errors_app">エラーが最も多いアプリ</string>
<string name="most_errors_type">エラーが最も多いタイプ</string>
<string name="total_proportion_of_errors">エラーの合計割合</string>
<string name="total_errors_unit">%1$s 個</string>
<string name="total_apps_unit">%1$s 個 (システムアプリを含む)</string>
<string name="generating_statistics">統計が生成されています</string>
<string name="module_not_fully_activated_tip">モジュールは完全にアクティブ化されておらず、現在の設定アイテムをロードできない場合があります。システムをリスタートして試してみることをお勧めします。</string>
<string name="show_errors_notify">エラー通知プッシュを表示</string>
<string name="app_errors_tip">アプリは未処理のエラーで崩壊しました。クリックして、アプリ開発者への詳細またはフィードバックを表示します。</string>
<string name="batch_operations">一括操作</string>
<string name="are_you_sure_apply_site_apps">一度に %1$s 個のアプリに設定を適用してもよろしいですか</string>
<string name="errors_dialog_always_show_reopen">エラーダイアログには常にリスタートが表示</string>
<string name="errors_dialog_always_show_reopen_tip">有効にした後、アプリが最初のエラーでない場合にも「アプリをリスタート」オプションが表示されます。現在のエラーがメインプロセスでない場合、またはアプリを開くことができない場合でも、このオプションは表示されません。</string>
<string name="developer_notice_tip">このモジュールは、Android開発者向けに作成されています。\n\nコンピューターに接続できず、ADBデバッグを実行できない可能性がある場合、このモジュールを使用して、インストールされているアプリのエラーをすばやくキャプチャし、問題をすばやく特定できます。\n\nアプリのクラッシュのエラーログは開発者にとって非常に貴重です。開発者でない場合でも、このモジュールをインストールして、問題をすばやく解決するためのより多くの例外情報を開発者に提供できます。</string>
<string name="developer_notice">使用説明書</string>
<string name="warning">警告</string>
<string name="fast_restart_problem">一部のカスタム システムでは、高速リスタートを使用した後にエラーが発生する場合があります。\\n続行しますか?</string>
<string name="view_errors_record_tip">ここでは、モジュールが記録を開始してからのすべてのアプリ エラー レコードを見つけることができます。エラー履歴は、手動で消去するか工場出荷時の設定に戻すまで保持され続けます。記録を表示、エクスポート、共有、消去できます。</string>
<string name="theme_settings">テーマ設定</string>
<string name="enable_md3_app_errors_dialog">MD3 スタイルのエラーダイアログを有効</string>
<string name="enable_md3_app_errors_dialog_tip">この機能は、Android のターゲット バージョンが 12 以降の場合にデフォルトで有効になり、動的テーマ カラー機能は Android 12 以降でのみ有効になります。</string>
<string name="user_id">ユーザー %1$s</string>
<string name="unable_get_app_errors_record_tip">現在、エラー レコードを取得できません。後でエラー履歴で確認する必要があるかもしれません。問題が解決しない場合は、システムがエラー レポート収集機能をオフにしている可能性があります。</string>
<string name="debug_log">デバッグ ログ</string>
<string name="refresh">リフレッシュ</string>
<string name="copy">コピー</string>
<string name="export_all_logs_success">エクスポートされたすべてのデバッグ ログ</string>
<string name="export_all_logs_fail">すべてのデバッグ ログのエクスポートに失敗しました</string>
<string name="this_contents_clear_when_restarts_tip">ここのコンテンツは、デバイスのリスタート後に自動的に消去されます</string>
<string name="when_logger_how_to_show_tip">現在のデバッグ ログ表示の優先度フィルタ条件を設定できます。</string>
<string name="enable_anonymous_statistics">匿名統計を有効</string>
<string name="enable_anonymous_statistics_tip">有効にすると、Microsoft App Center を使用してアプリ関連の診断データを匿名で送信し、アプリの機能を向上させます。\n収集される情報は匿名であり、独立したデバイスまたはユーザーまで追跡することはできません。\nこの機能を有効または無効にするには、リスタートアプリが有効になります。</string>
</resources>

View File

@@ -1,136 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">異常調查</string>
<string name="xposed_desc">為原生 FC 對話框增加更多功能並修復中國大陸定制 ROM 刪除 FC 對話框的問題,給 Android 開發者帶來更好的體驗。 \n開發者酷安 @星夜不薈</string>
<string name="app_info">程式情報</string>
<string name="reopen_app">重新開啟</string>
<string name="error_detail">異常詳解</string>
<string name="mute_if_unlock">忽略(直到設備重新開屏)</string>
<string name="mute_if_restart">忽略(直到設備重新開機)</string>
<string name="close_app">結束程式</string>
<string name="aerr_title">%1$s 已停止運作</string>
<string name="aerr_repeated_title">%1$s 屢次停止運作</string>
<string name="mute_if_unlock_tip">忽略“%1$s”的錯誤直到設備重新開屏</string>
<string name="mute_if_restart_tip">忽略“%1$s”的錯誤直到設備重新開機</string>
<string name="back">回退</string>
<string name="copy_error_stack">複製異常堆棧</string>
<string name="export_to_file">導出到副案</string>
<string name="error_info">異常訊息</string>
<string name="error_type">異常類型</string>
<string name="error_file_name">文件名</string>
<string name="error_throw_class">拋出 Class</string>
<string name="error_throw_method">抛出 Method</string>
<string name="error_line_number">行號</string>
<string name="error_record_time">記錄時間</string>
<string name="copied">已復制</string>
<string name="copy_fail">複製失敗</string>
<string name="print_to_logcat">打印到控制台</string>
<string name="print_to_logcat_success">已打印到控制台</string>
<string name="output_stack_success">已導出異常堆棧</string>
<string name="output_stack_fail">導出異常堆棧失敗</string>
<string name="export_all">導出全部</string>
<string name="clear_all">清空全部</string>
<string name="errors_record">異常歷史記錄</string>
<string name="no_list_data">暫時沒有紀錄</string>
<string name="confirm">確認</string>
<string name="cancel">取消</string>
<string name="more">更多</string>
<string name="notice">提醒</string>
<string name="are_you_sure_clear_errors">你確認要清空全部異常紀錄嗎?</string>
<string name="all_errors_clear_success">全部異常紀錄已清空</string>
<string name="are_you_sure_export_all_errors">你確認要導出全部日誌文件嗎?打包過程可能會花費一點時間。</string>
<string name="view_detail">查看詳情</string>
<string name="export_all_errors_success">已導出全部異常紀錄</string>
<string name="export_all_errors_fail">導出全部異常紀錄失敗</string>
<string name="no_cpu_abi">無原生庫</string>
<string name="remove_record">移除紀錄</string>
<string name="are_you_sure_remove_record">你確認要移除這條紀錄嗎?</string>
<string name="got_it">我懂了</string>
<string name="restart_system">重新開機</string>
<string name="project_address">項目地址</string>
<string name="module_not_activated">模組未激活</string>
<string name="module_version">模組版本:%1$s</string>
<string name="system_version">系統版本:%1$s</string>
<string name="module_is_activated">模組已激活</string>
<string name="display_settings">顯示設置</string>
<string name="hide_app_icon_on_launcher">在桌面隱藏模組圖標</string>
<string name="hide_app_icon_on_launcher_tip">隱藏模組圖標後界面可能會被關閉,將不會再在桌面顯示,你可以在 EdXposed、LSPosed 中找到模組設置並打開。</string>
<string name="hide_app_icon_on_launcher_notice">注意:請務必在 LSPosed 中關閉“強制顯示桌面圖標”功能</string>
<string name="about_module">此模組使用 YukiHookAPI 構建。 \n了解更多 https://github.com/fankes/YukiHookAPI</string>
<string name="module_not_fully_activated">模組未完全激活,請重新開機</string>
<string name="are_your_sure_restart_system">你確定要重新開機嗎?</string>
<string name="fast_restart">急速重開</string>
<string name="access_root_fail">取得 Root 权利失败</string>
<string name="moment_ago">剛剛</string>
<string name="second_ago">秒前</string>
<string name="minute_ago">分鐘前</string>
<string name="hour_ago">小時前</string>
<string name="day_ago">天前</string>
<string name="month_ago">月前</string>
<string name="year_ago">年前</string>
<string name="crash_process">異常進程 \"%1$s\"</string>
<string name="share_error_stack">分享異常堆棧</string>
<string name="preference_settings">偏好設置</string>
<string name="function_mgr">功能管理</string>
<string name="only_show_errors_in_front">僅為前台程式展示錯誤對話框</string>
<string name="only_show_errors_in_main_process">僅為程式主進程展示錯誤對話框</string>
<string name="enable_apps_config_template">啟用程式配置模板</string>
<string name="mgr_apps_config_template">管理程式配置模板</string>
<string name="view_errors_record">查看異常歷史記錄</string>
<string name="view_muted_errors_apps">查看已忽略異常的程式</string>
<string name="only_show_errors_in_front_tip">啟用後,只有發生異常的程式處於前台(正在使用中)時才會展示錯誤對話框,發生異常的後台程式雖然不會展示錯誤對話框,但是它們都會被記錄到異常歷史記錄中。</string>
<string name="only_show_errors_in_main_process_tip">啟用後,只有程式發生的異常位於主進程(第一個 Application 實例對象)時才會展示錯誤對話框。</string>
<string name="apps_config_template_tip">你可以在這裡對每個程式發生異常時,單獨配置其在發生異常時是否展示錯誤對話框以及其它錯誤提示。</string>
<string name="view_errors_record_tip">在這裡,你可以找到從模組開始記錄以來的全部程式異常記錄,異常歷史記錄會持續保留直到你手動清空或恢復出廠設置,你可以對記錄進行查看、導出和分享以及清空。</string>
<string name="view_muted_errors_apps_tip">在這裡,你可以找到已被你以不同形式手動忽略異常的程式,這個列表將會在重新啟動後自動清空,你可以對這些已忽略的程式進行管理以及從忽略列表中移除它們。</string>
<string name="muted_errors_apps">已忽略異常的程式</string>
<string name="unmute">取消忽略</string>
<string name="unmute_all">取消全部忽略</string>
<string name="are_you_sure_unmute_all">你確認要取消全部已忽略異常的程式嗎?</string>
<string name="apps_config_template">程式配置模板</string>
<string name="filter_by_condition">按條件過濾</string>
<string name="no_list_result">沒有結果可以展示</string>
<string name="typo_app_name_pkg_name">可輸入 APP 名稱、包名</string>
<string name="result_contains_system_apps">結果包含系統程式</string>
<string name="clear_filters">清除條件</string>
<string name="result_count">共 %1$s 個結果</string>
<string name="loading">加載中</string>
<string name="when_errors_how_to_show_tip">你可以配置當程式崩潰時系統將如何向你展示錯誤提醒副案。</string>
<string name="show_errors_dialog">顯示錯誤對話框</string>
<string name="show_errors_toast">顯示錯誤 Toast</string>
<string name="show_nothing">什麼也不顯示</string>
<string name="app_errors_statistics">程式異常統計</string>
<string name="total_errors">異常總數</string>
<string name="total_apps">程式總數</string>
<string name="most_errors_app">最多異常的程式</string>
<string name="most_errors_type">最多異常的類型</string>
<string name="total_proportion_of_errors">異常總佔比</string>
<string name="total_errors_unit">%1$s 條</string>
<string name="total_apps_unit">%1$s 個 (含系統程式)</string>
<string name="generating_statistics">統計數據生成中</string>
<string name="module_not_fully_activated_tip">模組未完全激活,可能無法加載當前設定項,建議重新開機後重試。</string>
<string name="show_errors_notify">推送錯誤通知</string>
<string name="app_errors_tip">程式發生了未處理的異常而崩潰,點擊查看詳情或反饋給程式開發者。</string>
<string name="batch_operations">批量操作</string>
<string name="are_you_sure_apply_site_apps">你確認要一次性應用設置給 %1$s 個程式嗎?</string>
<string name="errors_dialog_always_show_reopen">錯誤對話框始終顯示“重新開啟”選項</string>
<string name="errors_dialog_always_show_reopen_tip">啟用後,在程式非首次異常時也將顯示“重新開啟”選項,若當前異常非主進程或程式無法打開則依然不會顯示此選項。</string>
<string name="developer_notice">使用說明</string>
<string name="developer_notice_tip">此模組專為 Android 開發者而打造。 \n\n在可能的無法連接電腦不能進行 ADB 調試的時候,可通過此模組來快速捕獲任意已安裝程式的任意異常,以便快速定位問題。 \n\n程式發生崩潰的錯誤日誌對開發者來說是無價的財富若你不是開發者你依然可以安裝此模組以便給開發者提供更多異常信息快速解決問題。</string>
<string name="warning">警告</string>
<string name="fast_restart_problem">部分定制系統使用急速重開後可能會發生錯誤,仍然要繼續嗎?</string>
<string name="theme_settings">主題設置</string>
<string name="enable_md3_app_errors_dialog">啟用 Material 3 風格錯誤對話框</string>
<string name="enable_md3_app_errors_dialog_tip">此功能在 Android 目標版本為 12 及以上時默認啟用,動態主題色功能僅會在 Android 12 及以上版本生效。</string>
<string name="user_id">用户 %1$s</string>
<string name="unable_get_app_errors_record_tip">現在無法獲取異常記錄,你可能需要稍後在異常歷史記錄中查看,若問題持續出現,可能是你的系統關閉了錯誤報告收集功能。</string>
<string name="debug_log">調試日誌</string>
<string name="refresh">刷新</string>
<string name="copy">複製</string>
<string name="export_all_logs_success">已導出全部調試日誌</string>
<string name="export_all_logs_fail">導出全部調試日誌失敗</string>
<string name="this_contents_clear_when_restarts_tip">這裡的內容會在設備重開後自動清空</string>
<string name="when_logger_how_to_show_tip">你可以配置當前調試日誌顯示的優先級過濾條件。</string>
<string name="enable_anonymous_statistics">啟用匿名統計</string>
<string name="enable_anonymous_statistics_tip">啟用後,我們將會使用 Microsoft App Center 來匿名發送程式相關的診斷數據以幫助程式的功能更加完善,收集的信息是匿名的,我們無法追溯到獨立的裝置或用戶,啟用或關閉此功能需要重啟程式方可生效。</string>
</resources>

View File

@@ -1,11 +0,0 @@
plugins {
id 'com.android.application' version '7.3.0' apply false
id 'com.android.library' version '7.3.0' apply false
id 'org.jetbrains.kotlin.android' version '1.7.10' apply false
}
ext {
appVersionName = "1.1"
appVersionCode = 3
enableR8 = true
}

21
build.gradle.kts Normal file
View File

@@ -0,0 +1,21 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile
plugins {
autowire(libs.plugins.android.application) apply false
autowire(libs.plugins.kotlin.android) apply false
autowire(libs.plugins.kotlin.ksp) apply false
}
allprojects {
tasks.withType<KotlinJvmCompile>().configureEach {
compilerOptions {
jvmTarget = JvmTarget.JVM_17
freeCompilerArgs.addAll(
"-Xno-param-assertions",
"-Xno-call-assertions",
"-Xno-receiver-assertions"
)
}
}
}

1
demo-app/.gitignore vendored
View File

@@ -1 +0,0 @@
/build

View File

@@ -1,78 +0,0 @@
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
}
android {
namespace 'com.fankes.apperrorsdemo'
compileSdk 33
ndkVersion '24.0.8215888'
signingConfigs {
debug {
storeFile file('../keystore/public')
storePassword '123456'
keyAlias 'public'
keyPassword '123456'
v1SigningEnabled true
v2SigningEnabled true
}
}
defaultConfig {
minSdk 27
targetSdk 33
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles "consumer-rules.pro"
externalNativeBuild {
cmake {
cppFlags ""
}
}
}
buildTypes {
release {
minifyEnabled false
signingConfig signingConfigs.debug
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
externalNativeBuild {
cmake {
path "src/main/cpp/CMakeLists.txt"
version "3.22.1"
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = '11'
freeCompilerArgs = [
'-Xno-param-assertions',
'-Xno-call-assertions',
'-Xno-receiver-assertions'
]
}
buildFeatures {
viewBinding true
}
lintOptions {
checkReleaseBuilds false
}
}
dependencies {
implementation 'com.highcapable.yukihookapi:api:1.1.3'
implementation 'androidx.core:core-ktx:1.9.0'
implementation 'androidx.appcompat:appcompat:1.5.1'
implementation 'com.google.android.material:material:1.6.1'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}

87
demo-app/build.gradle.kts Normal file
View File

@@ -0,0 +1,87 @@
plugins {
autowire(libs.plugins.android.application)
autowire(libs.plugins.kotlin.android)
}
android {
namespace = property.project.demo.app.packageName
compileSdk = property.project.android.compileSdk
ndkVersion = property.project.android.ndk.version
signingConfigs {
create("universal") {
keyAlias = property.project.demo.app.signing.keyAlias
keyPassword = property.project.demo.app.signing.keyPassword
storeFile = rootProject.file(property.project.demo.app.signing.storeFilePath)
storePassword = property.project.demo.app.signing.storePassword
enableV1Signing = true
enableV2Signing = true
}
}
defaultConfig {
applicationId = property.project.demo.app.packageName
minSdk = property.project.android.minSdk
targetSdk = property.project.android.targetSdk
versionName = property.project.demo.app.versionName
versionCode = property.project.demo.app.versionCode
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
all { signingConfig = signingConfigs.getByName("universal") }
release {
isMinifyEnabled = false
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
}
}
externalNativeBuild {
cmake {
path("src/main/cpp/CMakeLists.txt")
version = property.project.android.cmake.version
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
buildFeatures {
buildConfig = true
viewBinding = true
}
lint { checkReleaseBuilds = false }
androidResources.additionalParameters += listOf("--allow-reserved-package-id", "--package-id", "0x37")
}
androidComponents {
onVariants(selector().all()) {
it.outputs.forEach { output ->
val currentType = it.buildType
// Workaround for GitHub Actions.
// Why? I don't know, but it works.
// Unresolved reference. None of the following candidates is applicable because of receiver type mismatch:
// public inline fun CharSequence.isNotBlank(): Boolean defined in kotlin.text.
@Suppress("UNNECESSARY_SAFE_CALL", "RemoveRedundantCallsOfConversionMethods")
val currentSuffix = property.github.ci.commit.id?.let { suffix ->
// Workaround for GitHub Actions.
// Strongly transfer type to [String].
val sSuffix = suffix.toString()
if (sSuffix.isNotBlank()) "-$sSuffix" else ""
}
val currentVersion = "${output.versionName.get()}$currentSuffix(${output.versionCode.get()})"
if (output is com.android.build.api.variant.impl.VariantOutputImpl)
output.outputFileName.set("${property.project.name}-demo-v$currentVersion-$currentType.apk")
}
}
}
dependencies {
implementation(com.fankes.projectpromote.project.promote)
implementation(com.highcapable.kavaref.kavaref.core)
implementation(com.highcapable.kavaref.kavaref.extension)
implementation(androidx.core.core.ktx)
implementation(androidx.appcompat.appcompat)
implementation(com.google.android.material.material)
testImplementation(junit.junit)
androidTestImplementation(androidx.test.ext.junit)
androidTestImplementation(androidx.test.espresso.espresso.core)
}

View File

@@ -1,14 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission
android:name="${applicationId}.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION"
tools:node="remove" />
<permission
android:name="${applicationId}.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION"
tools:node="remove" />
<application
android:name=".application.DemoApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:localeConfig="@xml/locales_config"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.AppErrorsDemo">
android:theme="@style/Theme.AppErrorsDemo"
tools:targetApi="tiramisu">
<activity
android:name=".ui.activity.MainActivity"

View File

@@ -1,6 +1,6 @@
/*
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* Copyright (C) 2017 Fankes Studio(qzmmcn@163.com)
* https://github.com/KitsunePie/AppErrorsTracking
*
* This software is non-free but opensource software: you can redistribute it
@@ -17,7 +17,7 @@
* and eula along with this software. If not, see
* <https://www.gnu.org/licenses/>
*
* This file is Created by fankes on 2022/5/10.
* This file is created by fankes on 2022/5/10.
*/
package com.fankes.apperrorsdemo.application

View File

@@ -1,6 +1,6 @@
/*
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* Copyright (C) 2017 Fankes Studio(qzmmcn@163.com)
* https://github.com/KitsunePie/AppErrorsTracking
*
* This software is non-free but opensource software: you can redistribute it
@@ -17,7 +17,7 @@
* and eula along with this software. If not, see
* <https://www.gnu.org/licenses/>
*
* This file is Created by fankes on 2022/5/10.
* This file is created by fankes on 2022/5/10.
*/
package com.fankes.apperrorsdemo.native

View File

@@ -1,6 +1,6 @@
/*
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* Copyright (C) 2017 Fankes Studio(qzmmcn@163.com)
* https://github.com/KitsunePie/AppErrorsTracking
*
* This software is non-free but opensource software: you can redistribute it
@@ -17,21 +17,27 @@
* and eula along with this software. If not, see
* <https://www.gnu.org/licenses/>
*
* This file is Created by fankes on 2022/5/10.
* This file is created by fankes on 2022/5/10.
*/
@file:Suppress("SetTextI18n")
package com.fankes.apperrorsdemo.ui.activity
import android.content.Intent
import android.os.SystemClock
import com.fankes.apperrorsdemo.R
import com.fankes.apperrorsdemo.databinding.ActivityMainBinding
import com.fankes.apperrorsdemo.databinding.ActivityMultiProcessBinding
import com.fankes.apperrorsdemo.generated.DemoAppProperties
import com.fankes.apperrorsdemo.native.Channel
import com.fankes.apperrorsdemo.ui.activity.base.BaseActivity
class MainActivity : BaseActivity<ActivityMainBinding>() {
override fun onCreate() {
binding.titleBackIcon.setOnClickListener { finish() }
DemoAppProperties.GITHUB_CI_COMMIT_ID.takeIf(String::isNotBlank)?.also {
binding.titleText.text = "${getString(R.string.app_name)} ($it)"
}; binding.titleBackIcon.setOnClickListener { finish() }
binding.throwRuntimeButton.setOnClickListener { Channel.throwRuntimeException() }
binding.throwIllegalStateButton.setOnClickListener { Channel.throwIllegalStateException() }
binding.throwNullPointerButton.setOnClickListener { Channel.throwNullPointerException() }

View File

@@ -1,6 +1,6 @@
/*
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* Copyright (C) 2017 Fankes Studio(qzmmcn@163.com)
* https://github.com/KitsunePie/AppErrorsTracking
*
* This software is non-free but opensource software: you can redistribute it
@@ -17,23 +17,22 @@
* and eula along with this software. If not, see
* <https://www.gnu.org/licenses/>
*
* This file is Created by fankes on 2022/5/10.
* This file is created by fankes on 2022/5/10.
*/
@file:Suppress("UNCHECKED_CAST")
package com.fankes.apperrorsdemo.ui.activity.base
import android.os.Build
import android.os.Bundle
import android.view.LayoutInflater
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.res.ResourcesCompat
import androidx.core.view.WindowCompat
import androidx.viewbinding.ViewBinding
import com.fankes.apperrorsdemo.R
import com.fankes.apperrorsdemo.utils.factory.isNotSystemInDarkMode
import com.highcapable.yukihookapi.hook.factory.current
import com.highcapable.yukihookapi.hook.factory.method
import com.highcapable.yukihookapi.hook.type.android.LayoutInflaterClass
import com.highcapable.kavaref.KavaRef.Companion.resolve
import com.highcapable.kavaref.extension.genericSuperclassTypeArguments
import com.highcapable.kavaref.extension.toClassOrNull
abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
@@ -42,10 +41,12 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = current().generic()?.argument()?.method {
val bindingClass = javaClass.genericSuperclassTypeArguments().firstOrNull()?.toClassOrNull()
binding = bindingClass?.resolve()?.optional()?.firstMethodOrNull {
name = "inflate"
param(LayoutInflaterClass)
}?.get()?.invoke<VB>(layoutInflater) ?: error("binding failed")
parameters(LayoutInflater::class)
}?.invoke<VB>(layoutInflater) ?: error("binding failed")
if (Build.VERSION.SDK_INT >= 35) binding.root.fitsSystemWindows = true
setContentView(binding.root)
/** 隐藏系统的标题栏 */
supportActionBar?.hide()
@@ -54,6 +55,7 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
isAppearanceLightStatusBars = isNotSystemInDarkMode
isAppearanceLightNavigationBars = isNotSystemInDarkMode
}
@Suppress("DEPRECATION")
ResourcesCompat.getColor(resources, R.color.colorThemeBackground, null).also {
window?.statusBarColor = it
window?.navigationBarColor = it

View File

@@ -1,6 +1,6 @@
/*
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* Copyright (C) 2017 Fankes Studio(qzmmcn@163.com)
* https://github.com/KitsunePie/AppErrorsTracking
*
* This software is non-free but opensource software: you can redistribute it
@@ -17,7 +17,7 @@
* and eula along with this software. If not, see
* <https://www.gnu.org/licenses/>
*
* This file is Created by fankes on 2022/5/10.
* This file is created by fankes on 2022/5/10.
*/
package com.fankes.apperrorsdemo.utils.factory

View File

@@ -30,6 +30,7 @@
android:tooltipText="@string/back" />
<TextView
android:id="@+id/title_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="2.5dp"

View File

@@ -1,13 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">エラーの例</string>
<string name="back">戻る</string>
<string name="function_desc">一般的なシナリオでのビジネスエラーロジックをここに示します。Xposedモジュールをアクティブ化した後、ここでエラーをキャッチできるかどうかをテストできます。Xposedモジュールの有効性を確認するため、現在のアプリには手動用のロジックコードありません。エラーの処理。</string>
<string name="throw_native_error">ネイティブレイヤーのエラーをスロー</string>
<string name="throw_exception">Exception をスロー</string>
<string name="throw_nullpointer">NullPointerException をスロー</string>
<string name="throw_illegalstate">IllegalStateException をスロー</string>
<string name="throw_runtime">RuntimeException をスロー</string>
<string name="throw_multi_process_error">マルチプロセスエラーをスロー</string>
<string name="suicide_in_progress">自殺</string>
</resources>
<string name="app_name">AppErrorsDemo</string>
<string name="back">戻る</string>
<string name="function_desc">一般的なシナリオにおけるビジネス例外のロジックを提供します。\nXposed モジュールを有効化後に例外を取得できるかどうかをテストする事ができます。\nXposed モジュールの有効性を検証するため、現在のアプリには手動で例外を処理するロジックコードありません。</string>
<string name="throw_runtime">RuntimeException をスロー</string>
<string name="throw_illegalstate">IllegalStateException をスロー</string>
<string name="throw_nullpointer">NullPointerException をスロー</string>
<string name="throw_exception">Exception をスロー</string>
<string name="throw_native_error">ネイティブエラーをスロー</string>
<string name="throw_multi_process_error">マルチプロセスの例外をスロー</string>
<string name="suicide_in_progress">プロセスを強制停止</string>
</resources>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<locale-config xmlns:android="http://schemas.android.com/apk/res/android">
<locale android:name="en" />
<locale android:name="ja" />
<locale android:name="zh-Hans-CN" />
<locale android:name="zh-Hant-HK" />
<locale android:name="zh-Hant-MO" />
<locale android:name="zh-Hant-TW" />
</locale-config>

View File

@@ -1,25 +1,26 @@
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-XX:+UseParallelGC
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app"s APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
# Compiler Configuration
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
android.useAndroidX=true
# Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official
# Enables namespacing of each library's R class so that its R class includes only the
# resources declared in the library itself and none from the library's dependencies,
# thereby reducing the size of the R class for that library
android.nonTransitiveRClass=true
# Incremental
kotlin.incremental.useClasspathSnapshot=true
kotlin.code.style=official
# Project Configuration
project.name=AppErrorsTracking
project.android.compileSdk=36
project.android.minSdk=24
project.android.targetSdk=36
project.android.ndk.version="24.0.8215888"
project.android.cmake.version="3.22.1"
project.module-app.packageName=com.fankes.apperrorstracking
project.module-app.versionName="1.3"
project.module-app.versionCode=6
project.module-app.signing.keyAlias=public
project.module-app.signing.keyPassword="123456"
project.module-app.signing.storePassword="123456"
project.module-app.signing.storeFilePath=.secret/universal.p12
project.demo-app.packageName=com.fankes.apperrorsdemo
project.demo-app.versionName="${project.module-app.versionName}"
project.demo-app.versionCode=${project.module-app.versionCode}
project.demo-app.signing.keyAlias="${project.module-app.signing.keyAlias}"
project.demo-app.signing.keyPassword="${project.module-app.signing.keyPassword}"
project.demo-app.signing.storePassword="${project.module-app.signing.storePassword}"
project.demo-app.signing.storeFilePath="${project.module-app.signing.storeFilePath}"

View File

@@ -0,0 +1,95 @@
preferences:
autowire-on-sync-mode: UPDATE_OPTIONAL_DEPENDENCIES
repositories-mode: FAIL_ON_PROJECT_REPOS
repositories:
gradle-plugin-portal:
scope: PLUGINS
google:
maven-central:
jit-pack:
sonatype-oss-releases:
rovo89-xposed-api:
scope: LIBRARIES
url: https://api.xposed.info/
content:
include:
group:
de.robv.android.xposed
fankes-maven-releases:
url: https://raw.githubusercontent.com/fankes/maven-repository/main/repository/releases
plugins:
com.android.application:
alias: android-application
version: 8.12.1
org.jetbrains.kotlin.android:
alias: kotlin-android
version: 2.2.10
com.highcapable.flexilocale:
alias: flexi-locale
version: 1.0.2
com.google.devtools.ksp:
alias: kotlin-ksp
version: 2.2.10-2.0.2
libraries:
com.fankes.projectpromote:
project-promote:
version: 1.0.0
repositories:
fankes-maven-releases
de.robv.android.xposed:
api:
version: 82
repositories:
rovo89-xposed-api
com.highcapable.yukihookapi:
api:
version: 1.3.0
ksp-xposed:
version-ref: <this>::api
com.highcapable.kavaref:
kavaref-core:
version: 1.0.1
kavaref-extension:
version: 1.0.1
com.microsoft.appcenter:
appcenter-analytics:
version: 5.0.6
appcenter-crashes:
version-ref: <this>::appcenter-analytics
com.github.topjohnwu.libsu:
core:
version: 5.2.2
auto-update: false
com.github.duanhong169:
drawabletoolbox:
version: 1.0.7
com.google.code.gson:
gson:
version: 2.13.1
com.squareup.okhttp3:
okhttp:
version: 5.1.0
androidx.core:
core-ktx:
version: 1.17.0
androidx.appcompat:
appcompat:
version: 1.7.1
com.google.android.material:
material:
version: 1.12.0
androidx.constraintlayout:
constraintlayout:
version: 2.2.1
androidx.test.ext:
junit:
version: 1.3.0
androidx.test.espresso:
espresso-core:
version: 3.7.0
junit:
junit:
version: 4.13.2

View File

@@ -1,6 +1,5 @@
#Wed May 04 08:35:13 CST 2022
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStoreBase=GRADLE_USER_HOME

View File

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

2
module-app/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
/src/main/assets/xposed_init
/src/main/resources/META-INF/yukihookapi_init

View File

@@ -0,0 +1,93 @@
plugins {
autowire(libs.plugins.android.application)
autowire(libs.plugins.kotlin.android)
autowire(libs.plugins.kotlin.ksp)
autowire(libs.plugins.flexi.locale)
}
android {
namespace = property.project.module.app.packageName
compileSdk = property.project.android.compileSdk
signingConfigs {
create("universal") {
keyAlias = property.project.module.app.signing.keyAlias
keyPassword = property.project.module.app.signing.keyPassword
storeFile = rootProject.file(property.project.module.app.signing.storeFilePath)
storePassword = property.project.module.app.signing.storePassword
enableV1Signing = true
enableV2Signing = true
}
}
defaultConfig {
applicationId = property.project.module.app.packageName
minSdk = property.project.android.minSdk
targetSdk = property.project.android.targetSdk
versionName = property.project.module.app.versionName
versionCode = property.project.module.app.versionCode
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
all { signingConfig = signingConfigs.getByName("universal") }
release {
isMinifyEnabled = true
isShrinkResources = true
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
buildFeatures {
buildConfig = true
viewBinding = true
}
lint { checkReleaseBuilds = false }
androidResources.additionalParameters += listOf("--allow-reserved-package-id", "--package-id", "0x37")
}
androidComponents {
onVariants(selector().all()) {
it.outputs.forEach { output ->
val currentType = it.buildType
// Workaround for GitHub Actions.
// Why? I don't know, but it works.
// Unresolved reference. None of the following candidates is applicable because of receiver type mismatch:
// public inline fun CharSequence.isNotBlank(): Boolean defined in kotlin.text.
@Suppress("UNNECESSARY_SAFE_CALL", "RemoveRedundantCallsOfConversionMethods")
val currentSuffix = property.github.ci.commit.id?.let { suffix ->
// Workaround for GitHub Actions.
// Strongly transfer type to [String].
val sSuffix = suffix.toString()
if (sSuffix.isNotBlank()) "-$sSuffix" else ""
}
val currentVersion = "${output.versionName.get()}$currentSuffix(${output.versionCode.get()})"
if (output is com.android.build.api.variant.impl.VariantOutputImpl)
output.outputFileName.set("${property.project.name}-module-v$currentVersion-$currentType.apk")
}
}
}
dependencies {
compileOnly(de.robv.android.xposed.api)
implementation(com.highcapable.yukihookapi.api)
ksp(com.highcapable.yukihookapi.ksp.xposed)
implementation(com.highcapable.kavaref.kavaref.core)
implementation(com.highcapable.kavaref.kavaref.extension)
implementation(com.fankes.projectpromote.project.promote)
implementation(com.microsoft.appcenter.appcenter.analytics)
implementation(com.microsoft.appcenter.appcenter.crashes)
implementation(com.github.topjohnwu.libsu.core)
implementation(com.github.duanhong169.drawabletoolbox)
implementation(com.google.code.gson.gson)
implementation(com.squareup.okhttp3.okhttp)
implementation(androidx.core.core.ktx)
implementation(androidx.appcompat.appcompat)
implementation(com.google.android.material.material)
implementation(androidx.constraintlayout.constraintlayout)
testImplementation(junit.junit)
androidTestImplementation(androidx.test.ext.junit)
androidTestImplementation(androidx.test.espresso.espresso.core)
}

View File

@@ -32,31 +32,36 @@
-adaptresourcefilecontents
-renamesourcefileattribute P
-keepattributes SourceFile,LineNumberTable
-keepattributes SourceFile,Signature,LineNumberTable
## ---------------Begin: proguard configuration for Gson ----------
# Gson uses generic type information stored in a class file when working with fields. Proguard
# removes such information by default, so configure it to keep all of it.
# Explicitly preserve all serialization members. The Serializable interface
# is only a marker interface, so it wouldn't save them.
-keepclassmembers class * implements java.io.Serializable {
static final long serialVersionUID;
private static final java.io.ObjectStreamField[] serialPersistentFields;
private void writeObject(java.io.ObjectOutputStream);
private void readObject(java.io.ObjectInputStream);
java.lang.Object writeReplace();
java.lang.Object readResolve();
}
# Gson specific classes
-dontwarn sun.misc**
-keep class com.google.gson.stream**{*;}
# Application classes that will be serialized/deserialized over Gson
-keep class com.google.gson.examples.android.model** { <fields>; }
# Prevent proguard from stripping interface information from TypeAdapter, TypeAdapterFactory,
# JsonSerializer, JsonDeserializer instances (so they can be used in @JsonAdapter)
-keep class * implements com.google.gson.TypeAdapter
-keep class * implements com.google.gson.TypeAdapterFactory
-keep class * implements com.google.gson.JsonSerializer
-keep class * implements com.google.gson.JsonDeserializer
-dontwarn sun.misc.**
-keep class com.google.gson.stream.** { *; }
# Prevent R8 from leaving Data object members always null
-keepclassmembers,allowobfuscation class * {
@com.google.gson.annotations.SerializedName <fields>;
}
# Retain generic signatures of TypeToken and its subclasses with R8 version 3.0 and higher.
-keep,allowobfuscation,allowshrinking class com.google.gson.reflect.TypeToken
-keep,allowobfuscation,allowshrinking class * extends com.google.gson.reflect.TypeToken
## ---------------End: proguard configuration for Gson ----------
-assumenosideeffects class kotlin.jvm.internal.Intrinsics {
@@ -64,4 +69,8 @@
public static *** throwUninitializedPropertyAccessException(...);
}
-keep class com.fankes.apperrorstracking.databinding**{*;}
-keep class * extends android.app.Activity
-keep class * implements androidx.viewbinding.ViewBinding {
<init>();
*** inflate(android.view.LayoutInflater);
}

View File

@@ -10,14 +10,23 @@
android:name="android.permission.INTERACT_ACROSS_USERS"
tools:ignore="ProtectedPermissions" />
<uses-permission
android:name="${applicationId}.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION"
tools:node="remove" />
<permission
android:name="${applicationId}.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION"
tools:node="remove" />
<application
android:name=".application.AppErrorsApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:localeConfig="@xml/locales_config"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.AppErrorsTracking">
android:theme="@style/Theme.AppErrorsTracking"
tools:targetApi="tiramisu">
<meta-data
android:name="xposedmodule"
@@ -27,7 +36,10 @@
android:value="@string/xposed_desc" />
<meta-data
android:name="xposedminversion"
android:value="93" />
android:value="89" />
<meta-data
android:name="xposedsharedprefs"
android:value="true" />
<meta-data
android:name="xposedscope"
android:resource="@array/module_scope" />
@@ -92,7 +104,6 @@
android:name=".ui.activity.errors.AppErrorsDetailActivity"
android:exported="true"
android:launchMode="singleTask"
android:screenOrientation="behind"
android:taskAffinity=":detail" />
<service
@@ -106,5 +117,15 @@
<action android:name="android.service.quicksettings.action.QS_TILE" />
</intent-filter>
</service>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
</application>
</manifest>

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -1,6 +1,6 @@
/*
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* Copyright (C) 2017 Fankes Studio(qzmmcn@163.com)
* https://github.com/KitsunePie/AppErrorsTracking
*
* This software is non-free but opensource software: you can redistribute it
@@ -17,13 +17,14 @@
* and eula along with this software. If not, see
* <https://www.gnu.org/licenses/>
*
* This file is Created by fankes on 2022/5/10.
* This file is created by fankes on 2022/5/10.
*/
package com.fankes.apperrorstracking.application
import androidx.appcompat.app.AppCompatDelegate
import com.fankes.apperrorstracking.data.ConfigData
import com.fankes.apperrorstracking.locale.LocaleString
import com.fankes.apperrorstracking.generated.locale.ModuleAppLocale
import com.fankes.apperrorstracking.locale.locale
import com.fankes.apperrorstracking.utils.tool.AppAnalyticsTool
import com.highcapable.yukihookapi.hook.xposed.application.ModuleApplication
@@ -31,13 +32,13 @@ class AppErrorsApplication : ModuleApplication() {
override fun onCreate() {
super.onCreate()
/** 绑定 I18n */
locale = ModuleAppLocale.attach(this)
/** 跟随系统夜间模式 */
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
/** 绑定 I18n */
LocaleString.bind(instance = this)
/** 装载存储控制类 */
ConfigData.init(instance = this)
ConfigData.init(this)
/** 装载 App Center */
AppAnalyticsTool.init(instance = this)
AppAnalyticsTool.init(this)
}
}

View File

@@ -1,6 +1,6 @@
/*
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* Copyright (C) 2017 Fankes Studio(qzmmcn@163.com)
* https://github.com/KitsunePie/AppErrorsTracking
*
* This software is non-free but opensource software: you can redistribute it
@@ -17,11 +17,10 @@
* and eula along with this software. If not, see
* <https://www.gnu.org/licenses/>
*
* This file is Created by fankes on 2022/6/1.
* This file is created by fankes on 2022/6/1.
*/
package com.fankes.apperrorstracking.bean
import androidx.annotation.Keep
import java.io.Serializable
/**
@@ -37,13 +36,13 @@ import java.io.Serializable
* @param isShowReopenButton 是否显示重新打开按钮
*/
data class AppErrorsDisplayBean(
@Keep var pid: Int,
@Keep var userId: Int,
@Keep var packageName: String,
@Keep var processName: String,
@Keep var appName: String,
@Keep var title: String,
@Keep var isShowAppInfoButton: Boolean,
@Keep var isShowCloseAppButton: Boolean,
@Keep var isShowReopenButton: Boolean
var pid: Int,
var userId: Int,
var packageName: String,
var processName: String,
var appName: String,
var title: String,
var isShowAppInfoButton: Boolean,
var isShowCloseAppButton: Boolean,
var isShowReopenButton: Boolean
) : Serializable

View File

@@ -0,0 +1,262 @@
/*
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
* Copyright (C) 2017 Fankes Studio(qzmmcn@163.com)
* https://github.com/KitsunePie/AppErrorsTracking
*
* This software is non-free but opensource software: you can redistribute it
* and/or modify it under the terms of the GNU Affero General Public License
* as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* and eula along with this software. If not, see
* <https://www.gnu.org/licenses/>
*
* This file is created by fankes on 2022/5/10.
*/
package com.fankes.apperrorstracking.bean
import android.app.ApplicationErrorReport
import android.content.Context
import android.os.Build
import com.fankes.apperrorstracking.const.ModuleVersion
import com.fankes.apperrorstracking.locale.locale
import com.fankes.apperrorstracking.utils.factory.appCpuAbiOf
import com.fankes.apperrorstracking.utils.factory.appMinSdkOf
import com.fankes.apperrorstracking.utils.factory.appTargetSdkOf
import com.fankes.apperrorstracking.utils.factory.appVersionCodeOf
import com.fankes.apperrorstracking.utils.factory.appVersionNameOf
import com.fankes.apperrorstracking.utils.factory.difference
import com.fankes.apperrorstracking.utils.factory.toUtcTime
import com.google.gson.annotations.SerializedName
import java.io.Serializable
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
/**
* 应用异常信息 bean
* @param pid 进程 ID
* @param userId 用户 ID
* @param cpuAbi CPU 架构类型
* @param packageName 包名
* @param versionName 版本名称
* @param versionCode 版本号
* @param targetSdk 目标 SDK 版本
* @param minSdk 最低 SDK 版本
* @param isNativeCrash 是否为原生层异常
* @param exceptionClassName 异常类名
* @param exceptionMessage 异常信息
* @param throwClassName 抛出异常的类名
* @param throwFileName 抛出异常的文件名
* @param throwMethodName 抛出异常的方法名
* @param throwLineNumber 抛出异常的行号
* @param stackTrace 异常堆栈
* @param timestamp 记录时间戳
*/
data class AppErrorsInfoBean(
@SerializedName("pid")
var pid: Int = -1,
@SerializedName("userId")
var userId: Int = -1,
@SerializedName("cpuAbi")
var cpuAbi: String = "",
@SerializedName("packageName")
var packageName: String = "",
@SerializedName("versionName")
var versionName: String = "",
@SerializedName("versionCode")
var versionCode: Long = -1L,
@SerializedName("targetSdk")
var targetSdk: Int = -1,
@SerializedName("minSdk")
var minSdk: Int = -1,
@SerializedName("isNativeCrash")
var isNativeCrash: Boolean = false,
@SerializedName("exceptionClassName")
var exceptionClassName: String = "",
@SerializedName("exceptionMessage")
var exceptionMessage: String = "",
@SerializedName("throwFileName")
var throwFileName: String = "",
@SerializedName("throwClassName")
var throwClassName: String = "",
@SerializedName("throwMethodName")
var throwMethodName: String = "",
@SerializedName("throwLineNumber")
var throwLineNumber: Int = -1,
@SerializedName("stackTrace")
var stackTrace: String = "",
@SerializedName("timestamp")
var timestamp: Long = -1L
) : Serializable {
companion object {
/**
* 从 [ApplicationErrorReport.CrashInfo] 克隆
* @param context 当前实例
* @param pid APP 进程 ID
* @param userId APP 用户 ID
* @param packageName APP 包名
* @param crashInfo [ApplicationErrorReport.CrashInfo]
* @return [AppErrorsInfoBean]
*/
fun clone(context: Context, pid: Int, userId: Int, packageName: String?, crashInfo: ApplicationErrorReport.CrashInfo?) =
(crashInfo?.exceptionClassName?.lowercase() == "native crash").let { isNativeCrash ->
AppErrorsInfoBean(
pid = pid,
userId = userId,
cpuAbi = packageName?.let { context.appCpuAbiOf(it) } ?: "",
packageName = packageName ?: "unknown",
versionName = packageName?.let { context.appVersionNameOf(it).ifBlank { "unknown" } } ?: "",
versionCode = packageName?.let { context.appVersionCodeOf(it) } ?: -1L,
targetSdk = packageName?.let { context.appTargetSdkOf(it) } ?: -1,
minSdk = packageName?.let { context.appMinSdkOf(it) } ?: -1,
isNativeCrash = isNativeCrash,
exceptionClassName = crashInfo?.exceptionClassName ?: "unknown",
exceptionMessage = if (isNativeCrash) crashInfo?.stackTrace.let {
if (it?.contains("Abort message: '") == true)
runCatching { it.split("Abort message: '")[1].split("'")[0] }.getOrNull()
?: crashInfo?.exceptionMessage ?: "unknown"
else crashInfo?.exceptionMessage ?: "unknown"
} else crashInfo?.exceptionMessage ?: "unknown",
throwFileName = crashInfo?.throwFileName ?: "unknown",
throwClassName = crashInfo?.throwClassName ?: "unknown",
throwMethodName = crashInfo?.throwMethodName ?: "unknown",
throwLineNumber = crashInfo?.throwLineNumber ?: -1,
stackTrace = crashInfo?.stackTrace?.trim() ?: "unknown",
timestamp = System.currentTimeMillis()
)
}
}
/**
* 获取当前内容是否为空
* @return [Boolean]
*/
val isEmpty get() = pid == -1 && userId == -1 && timestamp == -1L
/**
* 获取生成的 Json 文件名
* @return [String]
*/
val jsonFileName get() = "${packageName}_${pid}_$timestamp.json"
/**
* 获取 APP 版本信息与版本号
* @return [String]
*/
val versionBrand get() = if (versionName.isBlank()) "unknown" else "$versionName($versionCode)"
/**
* 获取异常本地化 UTC 时间
* @return [String]
*/
val utcTime get() = timestamp.toUtcTime()
/**
* 获取异常本地化经过时间
* @return [String]
*/
val crossTime
get() = timestamp.difference(
now = locale.momentAgo,
second = locale.secondAgo,
minute = locale.minuteAgo,
hour = locale.hourAgo,
day = locale.dayAgo,
month = locale.monthAgo,
year = locale.yearAgo
)
/**
* 获取异常本地化时间
* @return [String]
*/
val dateTime get() = SimpleDateFormat.getDateTimeInstance().format(Date(timestamp)) ?: utcTime
/**
* 获取异常堆栈分享模板
* @param sDeviceBrand
* @param sDeviceModel
* @param sDisplay
* @param sPackageName
* @return [String]
*/
fun stackOutputShareContent(
sDeviceBrand: Boolean = true,
sDeviceModel: Boolean = true,
sDisplay: Boolean = true,
sPackageName: Boolean = true
) = """
Generated by AppErrorsTracking $ModuleVersion
Project URL: https://github.com/KitsunePie/AppErrorsTracking
===============
""".trimIndent() + "\n${environmentInfo(sDeviceBrand, sDeviceModel, sDisplay, sPackageName)}"
/**
* 获取异常堆栈文件模板
* @param sDeviceBrand
* @param sDeviceModel
* @param sDisplay
* @param sPackageName
* @return [String]
*/
fun stackOutputFileContent(
sDeviceBrand: Boolean = true,
sDeviceModel: Boolean = true,
sDisplay: Boolean = true,
sPackageName: Boolean = true
) = """
================================================================
Generated by AppErrorsTracking $ModuleVersion
Project URL: https://github.com/KitsunePie/AppErrorsTracking
================================================================
""".trimIndent() + "\n${environmentInfo(sDeviceBrand, sDeviceModel, sDisplay, sPackageName)}"
/**
* 获取运行环境信息
* @param sDeviceBrand
* @param sDeviceModel
* @param sDisplay
* @param sPackageName
* @return [String]
*/
private fun environmentInfo(
sDeviceBrand: Boolean,
sDeviceModel: Boolean,
sDisplay: Boolean,
sPackageName: Boolean
) = """
[Device Brand]: ${Build.BRAND.by(sDeviceBrand)}
[Device Model]: ${Build.MODEL.by(sDeviceModel)}
[Display]: ${Build.DISPLAY.by(sDisplay)}
[Android Version]: ${Build.VERSION.RELEASE}
[Android API Level]: ${Build.VERSION.SDK_INT}
[System Locale]: ${Locale.getDefault()}
[Process ID]: $pid
[User ID]: $userId
[CPU ABI]: ${cpuAbi.ifBlank { "none" }}
[Package Name]: ${packageName.by(sPackageName)}
[Version Name]: ${versionName.ifBlank { "unknown" }}
[Version Code]: ${versionCode.takeIf { it != -1L } ?: "unknown"}
[Target SDK]: ${targetSdk.takeIf { it != -1 } ?: "unknown"}
[Min SDK]: ${minSdk.takeIf { it != -1 } ?: "unknown"}
[Error Type]: ${if (isNativeCrash) "Native" else "JVM"}
[Crash Time]: $utcTime
[Stack Trace]:
""".trimIndent() + "\n$stackTrace"
/**
* 判断字符串是否需要显示
* @param isDisplay 是否需要显示
* @return [String]
*/
private fun String.by(isDisplay: Boolean) = if (isDisplay) this else "***"
}

View File

@@ -1,6 +1,6 @@
/*
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* Copyright (C) 2017 Fankes Studio(qzmmcn@163.com)
* https://github.com/KitsunePie/AppErrorsTracking
*
* This software is non-free but opensource software: you can redistribute it
@@ -17,19 +17,19 @@
* and eula along with this software. If not, see
* <https://www.gnu.org/licenses/>
*
* This file is Created by fankes on 2022/6/4.
* This file is created by fankes on 2022/6/4.
*/
package com.fankes.apperrorstracking.bean
import androidx.annotation.Keep
import com.fankes.apperrorstracking.bean.enum.AppFiltersType
import java.io.Serializable
/**
* 应用过滤条件 bean
* @param name 名称或包名
* @param isContainsSystem 是否包含系统应用
* @param type 过滤条件类型
*/
data class AppFiltersBean(
@Keep var name: String = "",
@Keep var isContainsSystem: Boolean = false
var name: String = "",
var type: AppFiltersType = AppFiltersType.USER
) : Serializable

View File

@@ -1,6 +1,6 @@
/*
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* Copyright (C) 2017 Fankes Studio(qzmmcn@163.com)
* https://github.com/KitsunePie/AppErrorsTracking
*
* This software is non-free but opensource software: you can redistribute it
@@ -17,12 +17,11 @@
* and eula along with this software. If not, see
* <https://www.gnu.org/licenses/>
*
* This file is Created by fankes on 2022/6/8.
* This file is created by fankes on 2022/6/8.
*/
package com.fankes.apperrorstracking.bean
import android.graphics.drawable.Drawable
import androidx.annotation.Keep
import java.io.Serializable
/**
@@ -32,7 +31,7 @@ import java.io.Serializable
* @param packageName APP 包名
*/
data class AppInfoBean(
@Keep var icon: Drawable? = null,
@Keep var name: String,
@Keep var packageName: String
var icon: Drawable? = null,
var name: String,
var packageName: String
) : Serializable

View File

@@ -1,6 +1,6 @@
/*
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* Copyright (C) 2017 Fankes Studio(qzmmcn@163.com)
* https://github.com/KitsunePie/AppErrorsTracking
*
* This software is non-free but opensource software: you can redistribute it
@@ -17,11 +17,10 @@
* and eula along with this software. If not, see
* <https://www.gnu.org/licenses/>
*
* This file is Created by fankes on 2022/6/3.
* This file is created by fankes on 2022/6/3.
*/
package com.fankes.apperrorstracking.bean
import androidx.annotation.Keep
import java.io.Serializable
/**
@@ -30,8 +29,8 @@ import java.io.Serializable
* @param packageName 包名
*/
data class MutedErrorsAppBean(
@Keep var type: MuteType,
@Keep var packageName: String
var type: MuteType,
var packageName: String
) : Serializable {
/**

View File

@@ -0,0 +1,36 @@
/*
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
* Copyright (C) 2017 Fankes Studio(qzmmcn@163.com)
* https://github.com/KitsunePie/AppErrorsTracking
*
* This software is non-free but opensource software: you can redistribute it
* and/or modify it under the terms of the GNU Affero General Public License
* as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* and eula along with this software. If not, see
* <https://www.gnu.org/licenses/>
*
* This file is created by fankes on 2023/1/22.
*/
package com.fankes.apperrorstracking.bean.enum
/**
* 应用过滤条件类型定义类
*/
enum class AppFiltersType {
/** 用户 */
USER,
/** 系统 */
SYSTEM,
/** 全部 */
ALL
}

View File

@@ -0,0 +1,59 @@
/*
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
* Copyright (C) 2017 Fankes Studio(qzmmcn@163.com)
* https://github.com/KitsunePie/AppErrorsTracking
*
* This software is non-free but opensource software: you can redistribute it
* and/or modify it under the terms of the GNU Affero General Public License
* as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* and eula along with this software. If not, see
* <https://www.gnu.org/licenses/>
*
* This file is created by fankes on 2023/9/19.
*/
@file:Suppress("MemberVisibilityCanBePrivate")
package com.fankes.apperrorstracking.const
import com.fankes.apperrorstracking.generated.ModuleAppProperties
import com.fankes.apperrorstracking.wrapper.BuildConfigWrapper
/**
* 包名常量定义类
*/
object PackageName {
/** 系统框架 */
const val SYSTEM_FRAMEWORK = "android"
}
/**
* 模块版本常量定义类
*/
object ModuleVersion {
/** 当前 GitHub 提交的 ID (CI 自动构建) */
const val GITHUB_COMMIT_ID = ModuleAppProperties.GITHUB_CI_COMMIT_ID
/** 版本名称 */
const val NAME = BuildConfigWrapper.VERSION_NAME
/** 版本号 */
const val CODE = BuildConfigWrapper.VERSION_CODE
/** 是否为 CI 自动构建版本 */
val isCiMode = GITHUB_COMMIT_ID.isNotBlank()
/** 当前版本名称后缀 */
val suffix = GITHUB_COMMIT_ID.let { if (it.isNotBlank()) "-$it" else "" }
override fun toString() = "$NAME$suffix($CODE)"
}

View File

@@ -0,0 +1,139 @@
/*
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
* Copyright (C) 2017 Fankes Studio(qzmmcn@163.com)
* https://github.com/KitsunePie/AppErrorsTracking
*
* This software is non-free but opensource software: you can redistribute it
* and/or modify it under the terms of the GNU Affero General Public License
* as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* and eula along with this software. If not, see
* <https://www.gnu.org/licenses/>
*
* This file is created by fankes on 2023/1/20.
*/
package com.fankes.apperrorstracking.data
import com.fankes.apperrorstracking.data.enum.AppErrorsConfigType
import com.highcapable.yukihookapi.hook.xposed.prefs.data.PrefsData
/**
* 应用配置模版存储控制类
*/
object AppErrorsConfigData {
/** 显示错误对话框键值名称 */
private const val SHOW_ERRORS_DIALOG_APPS = "_show_errors_dialog_apps"
/** 推送错误通知键值名称 */
private const val SHOW_ERRORS_NOTIFY_APPS = "_show_errors_notify_apps"
/** 显示错误 Toast 键值名称 */
private const val SHOW_ERRORS_TOAST_APPS = "_show_errors_toast_apps"
/** 什么也不显示键值名称 */
private const val SHOW_ERRORS_NOTHING_APPS = "_show_errors_nothing_apps"
/** 全局错误显示类型 */
private val GLOBAL_SHOW_ERRORS_TYPE = PrefsData("_global_show_errors_type", AppErrorsConfigType.DIALOG.ordinal)
/** 显示错误对话框的 APP 包名数组 */
private var showDialogApps = HashSet<String>()
/** 推送错误通知的 APP 包名数组 */
private var showNotifyApps = HashSet<String>()
/** 显示错误 Toast 的 APP 包名数组 */
private var showToastApps = HashSet<String>()
/** 什么也不显示的 APP 包名数组 */
private var showNothingApps = HashSet<String>()
/** 刷新存储控制类 */
fun refresh() {
showDialogApps = ConfigData.getStringSet(SHOW_ERRORS_DIALOG_APPS).toHashSet()
showNotifyApps = ConfigData.getStringSet(SHOW_ERRORS_NOTIFY_APPS).toHashSet()
showToastApps = ConfigData.getStringSet(SHOW_ERRORS_TOAST_APPS).toHashSet()
showNothingApps = ConfigData.getStringSet(SHOW_ERRORS_NOTHING_APPS).toHashSet()
}
/**
* 获取当前 APP 显示错误的类型是否为 [type]
* @param type 当前类型
* @param packageName 当前 APP 包名 - 不填为全局配置
* @return [Boolean]
*/
fun isAppShowingType(type: AppErrorsConfigType, packageName: String = "") =
if (packageName.isNotBlank()) when (type) {
AppErrorsConfigType.GLOBAL ->
showDialogApps.contains(packageName).not() &&
showNotifyApps.contains(packageName).not() &&
showToastApps.contains(packageName).not() &&
showNothingApps.contains(packageName).not()
AppErrorsConfigType.DIALOG -> showDialogApps.contains(packageName)
AppErrorsConfigType.NOTIFY -> showNotifyApps.contains(packageName)
AppErrorsConfigType.TOAST -> showToastApps.contains(packageName)
AppErrorsConfigType.NOTHING -> showNothingApps.contains(packageName)
} else ConfigData.getInt(GLOBAL_SHOW_ERRORS_TYPE) == type.ordinal
/**
* 写入当前 APP 显示错误的类型
* @param type 当前类型
* @param packageName 当前 APP 包名 - 不填为全局配置
* @throws IllegalStateException 如果 [packageName] 为空 [type] 为 [AppErrorsConfigType.GLOBAL]
*/
fun putAppShowingType(type: AppErrorsConfigType, packageName: String = "") {
if (packageName.isBlank() && type == AppErrorsConfigType.GLOBAL)
error("You can't still specify the \"follow global config\" type when saving the global config")
fun saveAllData() {
ConfigData.putStringSet(SHOW_ERRORS_DIALOG_APPS, showDialogApps)
ConfigData.putStringSet(SHOW_ERRORS_NOTIFY_APPS, showNotifyApps)
ConfigData.putStringSet(SHOW_ERRORS_TOAST_APPS, showToastApps)
ConfigData.putStringSet(SHOW_ERRORS_NOTHING_APPS, showNothingApps)
}
if (packageName.isNotBlank()) when (type) {
AppErrorsConfigType.GLOBAL -> {
showDialogApps.remove(packageName)
showNotifyApps.remove(packageName)
showToastApps.remove(packageName)
showNothingApps.remove(packageName)
saveAllData()
}
AppErrorsConfigType.DIALOG -> {
showDialogApps.add(packageName)
showNotifyApps.remove(packageName)
showToastApps.remove(packageName)
showNothingApps.remove(packageName)
saveAllData()
}
AppErrorsConfigType.NOTIFY -> {
showDialogApps.remove(packageName)
showNotifyApps.add(packageName)
showToastApps.remove(packageName)
showNothingApps.remove(packageName)
saveAllData()
}
AppErrorsConfigType.TOAST -> {
showDialogApps.remove(packageName)
showNotifyApps.remove(packageName)
showToastApps.add(packageName)
showNothingApps.remove(packageName)
saveAllData()
}
AppErrorsConfigType.NOTHING -> {
showDialogApps.remove(packageName)
showNotifyApps.remove(packageName)
showToastApps.remove(packageName)
showNothingApps.add(packageName)
saveAllData()
}
} else ConfigData.putInt(GLOBAL_SHOW_ERRORS_TYPE, type.ordinal)
}
}

View File

@@ -0,0 +1,138 @@
/*
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
* Copyright (C) 2017 Fankes Studio(qzmmcn@163.com)
* https://github.com/KitsunePie/AppErrorsTracking
*
* This software is non-free but opensource software: you can redistribute it
* and/or modify it under the terms of the GNU Affero General Public License
* as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* and eula along with this software. If not, see
* <https://www.gnu.org/licenses/>
*
* This file is created by fankes on 2023/1/17.
*/
@file:Suppress("StaticFieldLeak")
package com.fankes.apperrorstracking.data
import android.content.Context
import android.provider.Settings
import com.fankes.apperrorstracking.bean.AppErrorsInfoBean
import com.fankes.apperrorstracking.utils.factory.appCpuAbiOf
import com.fankes.apperrorstracking.utils.factory.appVersionCodeOf
import com.fankes.apperrorstracking.utils.factory.appVersionNameOf
import com.fankes.apperrorstracking.utils.factory.toEntityOrNull
import com.fankes.apperrorstracking.utils.factory.toJsonOrNull
import com.highcapable.yukihookapi.hook.log.YLog
import java.io.File
import java.util.concurrent.CopyOnWriteArrayList
/**
* [AppErrorsInfoBean] 存储控制类
*/
object AppErrorsRecordData {
/** 异常记录数据文件目录路径 */
private const val FOLDER_PATH = "/data/misc/app_errors_records/"
/** 当前实例 */
private var context: Context? = null
/**
* 获取当前异常记录数据目录
* @return [File]
*/
private val errorsInfoDataFolder by lazy { File(FOLDER_PATH) }
/**
* 获取当前全部异常记录数据文件
* @return [List]<[File]>
*/
private val errorsInfoDataFiles get() = errorsInfoDataFolder.listFiles()?.sortedByDescending { it.lastModified() } ?: emptyList()
/** 已记录的全部 APP 异常信息数组 */
var allData = CopyOnWriteArrayList<AppErrorsInfoBean>()
/**
* 初始化存储控制类
* @param context 实例
*/
fun init(context: Context) {
this.context = context
initializeDataDirectory()
allData = readAllDataFromFiles()
}
/** 初始化异常记录数据目录 */
private fun initializeDataDirectory() {
runCatching {
errorsInfoDataFolder.also { if (it.exists().not() || it.isFile) it.apply { delete(); mkdirs() } }
}.onFailure {
YLog.error("Can't create directory \"$FOLDER_PATH\", there will be problems with the app errors records function", it)
}
}
/**
* 获取旧版异常记录数据并自动转换到新版
* @return [ArrayList]<[AppErrorsInfoBean]> or null
*/
private fun copyOldDataFromResolverString() = context?.let {
val keyName = "app_errors_data"
runCatching {
Settings.Secure.getString(it.contentResolver, keyName)
?.toEntityOrNull<CopyOnWriteArrayList<AppErrorsInfoBean>>()
?.onEach { e ->
e.cpuAbi = it.appCpuAbiOf(e.packageName)
e.versionName = it.appVersionNameOf(e.packageName).ifBlank { "unknown" }
e.versionCode = it.appVersionCodeOf(e.packageName)
e.toJsonOrNull()?.also { json -> File(errorsInfoDataFolder.absolutePath, e.jsonFileName).writeText(json) }
}.let { result ->
if (result != null) {
Settings.Secure.putString(it.contentResolver, keyName, "")
result
} else null
}
}.getOrNull()
}
/**
* 从文件获取全部异常记录数据
* @return [ArrayList]<[AppErrorsInfoBean]>
*/
private fun readAllDataFromFiles() = copyOldDataFromResolverString() ?: CopyOnWriteArrayList<AppErrorsInfoBean>().apply {
errorsInfoDataFiles.takeIf { it.isNotEmpty() }?.forEach { it.readText().toEntityOrNull<AppErrorsInfoBean>()?.let { e -> add(e) } }
}
/**
* 添加新的异常记录数据
* @param bean [AppErrorsInfoBean] 实例
*/
fun add(bean: AppErrorsInfoBean) {
allData.add(0, bean)
bean.toJsonOrNull()?.runCatching { File(errorsInfoDataFolder.absolutePath, bean.jsonFileName).writeText(this) }
}
/**
* 移除指定的异常记录数据
* @param bean [AppErrorsInfoBean] 实例
*/
fun remove(bean: AppErrorsInfoBean) {
allData.remove(bean)
runCatching { File(errorsInfoDataFolder.absolutePath, bean.jsonFileName).delete() }
}
/** 清除全部异常记录数据 */
fun clearAll() {
allData.clear()
runCatching { errorsInfoDataFolder.deleteRecursively() }
initializeDataDirectory()
}
}

View File

@@ -1,6 +1,6 @@
/*
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* Copyright (C) 2017 Fankes Studio(qzmmcn@163.com)
* https://github.com/KitsunePie/AppErrorsTracking
*
* This software is non-free but opensource software: you can redistribute it
@@ -17,20 +17,16 @@
* and eula along with this software. If not, see
* <https://www.gnu.org/licenses/>
*
* This file is Created by fankes on 2022/10/1.
* This file is created by fankes on 2022/10/1.
*/
@file:Suppress("MemberVisibilityCanBePrivate")
package com.fankes.apperrorstracking.data
import android.content.ContentResolver
import android.content.Context
import android.os.Build
import android.provider.Settings
import android.widget.CompoundButton
import com.highcapable.yukihookapi.hook.factory.modulePrefs
import com.highcapable.yukihookapi.hook.log.loggerE
import com.highcapable.yukihookapi.hook.log.loggerW
import com.highcapable.yukihookapi.hook.factory.prefs
import com.highcapable.yukihookapi.hook.log.YLog
import com.highcapable.yukihookapi.hook.param.PackageParam
import com.highcapable.yukihookapi.hook.xposed.prefs.data.PrefsData
@@ -39,9 +35,6 @@ import com.highcapable.yukihookapi.hook.xposed.prefs.data.PrefsData
*/
object ConfigData {
/** 存取全部应用异常数据的键值名称 */
const val APP_ERRORS_DATA = "app_errors_data"
/** 显示开发者提示 */
val SHOW_DEVELOPER_NOTICE = PrefsData("_show_developer_notice", true)
@@ -60,6 +53,15 @@ object ConfigData {
/** 启用应用配置模板 */
val ENABLE_APP_CONFIG_TEMPLATE = PrefsData("_enable_app_config_template", false)
/** 启用对话框防误触 */
val ENABLE_PREVENT_MISOPERATION_FOR_DIALOG = PrefsData("_enable_prevent_misoperation_for_dialog", false)
/** 禁止异常堆栈内容自动换行 */
val DISABLE_AUTO_WRAP_ERROR_STACK_TRACE = PrefsData("_disable_auto_wrap_error_stack_trace", false)
/** 分享时使用文件 */
val SHARE_WITH_FILE = PrefsData("_share_with_file", false)
/** 当前实例 - [Context] or [PackageParam] */
private var instance: Any? = null
@@ -73,6 +75,55 @@ object ConfigData {
is Context, is PackageParam -> this.instance = instance
else -> error("Unknown type for init ConfigData")
}
AppErrorsConfigData.refresh()
}
/**
* 读取 [Set]<[String]> 数据
* @param key 键值名称
* @return [Set]<[String]>
*/
internal fun getStringSet(key: String) = when (instance) {
is Context -> (instance as Context).prefs().getStringSet(key, setOf())
is PackageParam -> (instance as PackageParam).prefs.getStringSet(key, setOf())
else -> error("Unknown type for get prefs data")
}
/**
* 存入 [Set]<[String]> 数据
* @param key 键值名称
* @param value 键值内容
*/
internal fun putStringSet(key: String, value: Set<String>) {
when (instance) {
is Context -> (instance as Context).prefs().edit { putStringSet(key, value) }
is PackageParam -> YLog.warn("Not support for this method")
else -> error("Unknown type for put prefs data")
}
}
/**
* 读取 [Int] 数据
* @param data 键值数据模板
* @return [Int]
*/
internal fun getInt(data: PrefsData<Int>) = when (instance) {
is Context -> (instance as Context).prefs().get(data)
is PackageParam -> (instance as PackageParam).prefs.get(data)
else -> error("Unknown type for get prefs data")
}
/**
* 存入 [Int] 数据
* @param data 键值数据模板
* @param value 键值内容
*/
internal fun putInt(data: PrefsData<Int>, value: Int) {
when (instance) {
is Context -> (instance as Context).prefs().edit { put(data, value) }
is PackageParam -> YLog.warn("Not support for this method")
else -> error("Unknown type for put prefs data")
}
}
/**
@@ -80,8 +131,8 @@ object ConfigData {
* @param data 键值数据模板
* @return [Boolean]
*/
private fun getBoolean(data: PrefsData<Boolean>) = when (instance) {
is Context -> (instance as Context).modulePrefs.get(data)
internal fun getBoolean(data: PrefsData<Boolean>) = when (instance) {
is Context -> (instance as Context).prefs().get(data)
is PackageParam -> (instance as PackageParam).prefs.get(data)
else -> error("Unknown type for get prefs data")
}
@@ -91,50 +142,14 @@ object ConfigData {
* @param data 键值数据模板
* @param value 键值内容
*/
private fun putBoolean(data: PrefsData<Boolean>, value: Boolean) {
internal fun putBoolean(data: PrefsData<Boolean>, value: Boolean) {
when (instance) {
is Context -> (instance as Context).modulePrefs.put(data, value)
is PackageParam -> loggerW(msg = "Not support for this method")
is Context -> (instance as Context).prefs().edit { put(data, value) }
is PackageParam -> YLog.warn("Not support for this method")
else -> error("Unknown type for put prefs data")
}
}
/**
* 绑定到 [CompoundButton] 自动设置选中状态
* @param data 键值数据模板
* @param onChange 当改变时回调
*/
fun CompoundButton.bind(data: PrefsData<Boolean>, onChange: (Boolean) -> Unit = {}) {
isChecked = getBoolean(data).also(onChange)
setOnCheckedChangeListener { button, isChecked ->
if (button.isPressed) {
putBoolean(data, isChecked)
onChange(isChecked)
}
}
}
/**
* 获取 [ContentResolver] 字符串数据 (仅限 Hook 进程)
* @param key 键值名称
* @return [String]
*/
fun getResolverString(key: String) =
runCatching { (instance as? PackageParam)?.appContext?.let { Settings.Secure.getString(it.contentResolver, key) } }.getOrNull() ?: ""
/**
* 存入 [ContentResolver] 字符串数据 (仅限 Hook 进程)
* @param key 键值名称
* @param value 键值数据
*/
fun putResolverString(key: String, value: String) {
runCatching {
(instance as? PackageParam)?.appContext?.also { Settings.Secure.putString(it.contentResolver, key, value) }
}.onFailure {
loggerE(msg = "Write secure settings failed", e = it)
}
}
/**
* 是否显示开发者提示
* @return [Boolean]
@@ -185,6 +200,16 @@ object ConfigData {
putBoolean(ENABLE_APP_CONFIG_TEMPLATE, value)
}
/**
* 是否启用对话框防误触
* @return [Boolean]
*/
var isEnablePreventMisoperation
get() = getBoolean(ENABLE_PREVENT_MISOPERATION_FOR_DIALOG)
set(value) {
putBoolean(ENABLE_PREVENT_MISOPERATION_FOR_DIALOG, value)
}
/**
* 是否启用 Material 3 风格的错误对话框
* @return [Boolean]
@@ -194,4 +219,13 @@ object ConfigData {
set(value) {
putBoolean(ENABLE_MATERIAL3_STYLE_APP_ERRORS_DIALOG, value)
}
/**
* 是否以文件方式分享
*/
var isShareWithFile
get() = getBoolean(SHARE_WITH_FILE)
set(value) {
putBoolean(SHARE_WITH_FILE, value)
}
}

View File

@@ -0,0 +1,42 @@
/*
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
* Copyright (C) 2017 Fankes Studio(qzmmcn@163.com)
* https://github.com/KitsunePie/AppErrorsTracking
*
* This software is non-free but opensource software: you can redistribute it
* and/or modify it under the terms of the GNU Affero General Public License
* as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* and eula along with this software. If not, see
* <https://www.gnu.org/licenses/>
*
* This file is created by fankes on 2023/1/20.
*/
package com.fankes.apperrorstracking.data.enum
/**
* 应用配置模版类型定义类
*/
enum class AppErrorsConfigType {
/** 跟随全局配置 */
GLOBAL,
/** 对话框 */
DIALOG,
/** 通知 */
NOTIFY,
/** Toast */
TOAST,
/** 什么也不显示 */
NOTHING
}

View File

@@ -0,0 +1,101 @@
/*
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
* Copyright (C) 2017 Fankes Studio(qzmmcn@163.com)
* https://github.com/KitsunePie/AppErrorsTracking
*
* This software is non-free but opensource software: you can redistribute it
* and/or modify it under the terms of the GNU Affero General Public License
* as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* and eula along with this software. If not, see
* <https://www.gnu.org/licenses/>
*
* This file is created by fankes on 2023/2/3.
*/
@file:Suppress("unused", "MemberVisibilityCanBePrivate")
package com.fankes.apperrorstracking.data.factory
import android.widget.CompoundButton
import com.fankes.apperrorstracking.data.ConfigData
import com.highcapable.yukihookapi.hook.xposed.prefs.data.PrefsData
/**
* 绑定到 [CompoundButton] 自动设置选中状态
* @param data 键值数据模板
* @param initiate 方法体
*/
fun CompoundButton.bind(data: PrefsData<Boolean>, initiate: CompoundButtonDataBinder.(CompoundButton) -> Unit = {}) {
val binder = CompoundButtonDataBinder(button = this).also { initiate(it, this) }
isChecked = ConfigData.getBoolean(data).also { binder.initializeCallback?.invoke(it) }
binder.applyChangesCallback = { ConfigData.putBoolean(data, it) }
setOnCheckedChangeListener { button, isChecked ->
if (button.isPressed) {
if (binder.isAutoApplyChanges) binder.applyChangesCallback?.invoke(isChecked)
binder.changedCallback?.invoke(isChecked)
}
}
}
/**
* [CompoundButton] 数据绑定管理器实例
* @param button 当前实例
*/
class CompoundButtonDataBinder(private val button: CompoundButton) {
/** 状态初始化回调事件 */
internal var initializeCallback: ((Boolean) -> Unit)? = null
/** 状态改变回调事件 */
internal var changedCallback: ((Boolean) -> Unit)? = null
/** 应用更改回调事件 */
internal var applyChangesCallback: ((Boolean) -> Unit)? = null
/** 是否启用自动应用更改 */
var isAutoApplyChanges = true
/**
* 监听状态初始化
* @param result 回调结果
*/
fun onInitialize(result: (Boolean) -> Unit) {
initializeCallback = result
}
/**
* 监听状态改变
* @param result 回调结果
*/
fun onChanged(result: (Boolean) -> Unit) {
changedCallback = result
}
/** 重新初始化 */
fun reinitialize() {
initializeCallback?.invoke(button.isChecked)
}
/** 应用更改并重新初始化 */
fun applyChangesAndReinitialize() {
applyChanges()
reinitialize()
}
/** 应用更改 */
fun applyChanges() {
applyChangesCallback?.invoke(button.isChecked)
}
/** 取消更改 */
fun cancelChanges() {
button.isChecked = button.isChecked.not()
}
}

View File

@@ -1,6 +1,6 @@
/*
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* Copyright (C) 2017 Fankes Studio(qzmmcn@163.com)
* https://github.com/KitsunePie/AppErrorsTracking
*
* This software is non-free but opensource software: you can redistribute it
@@ -17,20 +17,21 @@
* and eula along with this software. If not, see
* <https://www.gnu.org/licenses/>
*
* This file is Created by fankes on 2022/5/7.
* This file is created by fankes on 2022/5/7.
*/
package com.fankes.apperrorstracking.hook
import com.fankes.apperrorstracking.data.ConfigData
import com.fankes.apperrorstracking.generated.locale.ModuleAppLocale
import com.fankes.apperrorstracking.hook.entity.FrameworkHooker
import com.fankes.apperrorstracking.locale.LocaleString
import com.fankes.apperrorstracking.locale.locale
import com.highcapable.yukihookapi.annotation.xposed.InjectYukiHookWithXposed
import com.highcapable.yukihookapi.hook.factory.configs
import com.highcapable.yukihookapi.hook.factory.encase
import com.highcapable.yukihookapi.hook.xposed.proxy.IYukiHookXposedInit
@InjectYukiHookWithXposed(entryClassName = "AppErrorsTracking", isUsingResourcesHook = false)
class HookEntry : IYukiHookXposedInit {
object HookEntry : IYukiHookXposedInit {
override fun onInit() = configs {
debugLog {
@@ -38,13 +39,12 @@ class HookEntry : IYukiHookXposedInit {
isRecord = true
}
isDebug = false
isEnableModulePrefsCache = false
}
override fun onHook() = encase {
loadSystem {
LocaleString.bind(instance = this)
ConfigData.init(instance = this)
locale = ModuleAppLocale.attach { moduleAppResources }
ConfigData.init(this)
loadHooker(FrameworkHooker)
}
}

View File

@@ -0,0 +1,507 @@
/*
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
* Copyright (C) 2017 Fankes Studio(qzmmcn@163.com)
* https://github.com/KitsunePie/AppErrorsTracking
*
* This software is non-free but opensource software: you can redistribute it
* and/or modify it under the terms of the GNU Affero General Public License
* as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* and eula along with this software. If not, see
* <https://www.gnu.org/licenses/>
*
* This file is created by fankes on 2022/5/7.
*/
@file:Suppress("ConstPropertyName")
package com.fankes.apperrorstracking.hook.entity
import android.app.ApplicationErrorReport
import android.app.Dialog
import android.content.Context
import android.content.Intent
import android.content.pm.ApplicationInfo
import android.content.pm.PackageInfo
import android.os.Build
import android.os.Bundle
import android.os.Message
import android.os.SystemClock
import android.util.ArrayMap
import androidx.core.graphics.drawable.IconCompat
import androidx.core.graphics.drawable.toBitmap
import com.fankes.apperrorstracking.R
import com.fankes.apperrorstracking.bean.AppErrorsDisplayBean
import com.fankes.apperrorstracking.bean.AppErrorsInfoBean
import com.fankes.apperrorstracking.bean.AppInfoBean
import com.fankes.apperrorstracking.bean.MutedErrorsAppBean
import com.fankes.apperrorstracking.bean.enum.AppFiltersType
import com.fankes.apperrorstracking.data.AppErrorsConfigData
import com.fankes.apperrorstracking.data.AppErrorsRecordData
import com.fankes.apperrorstracking.data.ConfigData
import com.fankes.apperrorstracking.data.enum.AppErrorsConfigType
import com.fankes.apperrorstracking.locale.locale
import com.fankes.apperrorstracking.ui.activity.errors.AppErrorsDisplayActivity
import com.fankes.apperrorstracking.ui.activity.errors.AppErrorsRecordActivity
import com.fankes.apperrorstracking.utils.factory.appNameOf
import com.fankes.apperrorstracking.utils.factory.drawableOf
import com.fankes.apperrorstracking.utils.factory.isAppCanOpened
import com.fankes.apperrorstracking.utils.factory.listOfPackages
import com.fankes.apperrorstracking.utils.factory.openApp
import com.fankes.apperrorstracking.utils.factory.pushNotify
import com.fankes.apperrorstracking.utils.factory.toArrayList
import com.fankes.apperrorstracking.utils.factory.toast
import com.fankes.apperrorstracking.utils.tool.FrameworkTool
import com.fankes.apperrorstracking.wrapper.BuildConfigWrapper
import com.highcapable.kavaref.KavaRef.Companion.resolve
import com.highcapable.kavaref.extension.VariousClass
import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker
import com.highcapable.yukihookapi.hook.log.YLog
object FrameworkHooker : YukiBaseHooker() {
private val UserControllerClass by lazyClass("com.android.server.am.UserController")
private val AppErrorsClass by lazyClass("com.android.server.am.AppErrors")
private val AppErrorDialogClass by lazyClass("com.android.server.am.AppErrorDialog")
private val AppErrorDialog_DataClass by lazyClass("com.android.server.am.AppErrorDialog\$Data")
private val ProcessRecordClass by lazyClass("com.android.server.am.ProcessRecord")
private val ActivityManagerServiceClass by lazyClassOrNull("com.android.server.am.ActivityManagerService")
private val ActivityTaskManagerService_LocalServiceClass by lazyClassOrNull("com.android.server.wm.ActivityTaskManagerService\$LocalService")
private val PackageListClass by lazyClassOrNull(
VariousClass(
"com.android.server.am.ProcessRecord\$PackageList",
"com.android.server.am.PackageList"
)
)
private val ErrorDialogControllerClass by lazyClassOrNull(
VariousClass(
"com.android.server.am.ProcessRecord\$ErrorDialogController",
"com.android.server.am.ErrorDialogController"
)
)
/** 已忽略错误的 APP 数组 - 直到重新解锁 */
private var mutedErrorsIfUnlockApps = mutableSetOf<String>()
/** 已忽略错误的 APP 数组 - 直到重新启动 */
private var mutedErrorsIfRestartApps = mutableSetOf<String>()
/**
* APP 进程异常数据定义类
* @param errors [AppErrorsClass] 实例
* @param proc [ProcessRecordClass] 实例
* @param resultData [AppErrorDialog_DataClass] 实例 - 默认空
*/
private class AppErrorsProcessData(errors: Any?, proc: Any?, resultData: Any? = null) {
/**
* 获取当前包列表实例
* @return [Any] or null
*/
private val pkgList by lazy {
ProcessRecordClass.resolve().optional(silent = true)
.firstMethodOrNull {
name = "getPkgList"
emptyParameters()
}?.of(proc)?.invoke()
?: ProcessRecordClass.resolve().optional(silent = true)
.firstFieldOrNull {
name { it.endsWith("pkgList", true) }
}?.of(proc)?.get()
}
/**
* 获取当前包列表数组大小
* @return [Int]
*/
private val pkgListSize by lazy {
PackageListClass?.resolve()?.optional(silent = true)
?.firstMethodOrNull {
name = "size"
emptyParameters()
}?.of(pkgList)?.invoke()
?: ProcessRecordClass.resolve().optional(silent = true)
.firstFieldOrNull { name = "pkgList" }
?.of(proc)?.get<ArrayMap<*, *>>()?.size ?: -1
}
/**
* 获取当前 pid 信息
* @return [Int]
*/
val pid by lazy {
ProcessRecordClass.resolve().optional()
.firstFieldOrNull {
name { it == "mPid" || it == "pid" }
}?.of(proc)?.get<Int>() ?: 0
}
/**
* 获取当前用户 ID 信息
* @return [Int]
*/
val userId by lazy {
ProcessRecordClass.resolve().optional()
.firstFieldOrNull { name = "userId" }
?.of(proc)?.get<Int>() ?: 0
}
/**
* 获取当前 APP 信息
* @return [ApplicationInfo] or null
*/
val appInfo by lazy {
ProcessRecordClass.resolve().optional()
.firstFieldOrNull { name = "info" }
?.of(proc)?.get<ApplicationInfo>()
}
/**
* 获取当前进程名称
* @return [String]
*/
val processName by lazy {
ProcessRecordClass.resolve().optional()
.firstFieldOrNull { name = "processName" }
?.of(proc)?.get<String>() ?: ""
}
/**
* 获取当前 APP、进程 包名
* @return [String]
*/
val packageName = appInfo?.packageName ?: processName
/**
* 获取当前进程是否为可被启动的 APP - 非框架 APP
* @return [Boolean]
*/
val isActualApp = pkgListSize == 1 && appInfo != null
/**
* 获取当前进程是否为主进程
* @return [Boolean]
*/
val isMainProcess = packageName == processName
/**
* 获取当前进程是否为后台进程
* @return [Boolean]
*/
val isBackgroundProcess by lazy {
UserControllerClass.resolve().optional()
.firstMethodOrNull { name { it == "getCurrentProfileIds" || it == "getCurrentProfileIdsLocked" } }
?.of(ActivityManagerServiceClass?.resolve()?.optional()?.firstFieldOrNull { name = "mUserController" }
?.of(AppErrorsClass.resolve().optional().firstFieldOrNull { name = "mService" }?.of(errors)?.get())?.getQuietly())
?.invokeQuietly<IntArray>()?.takeIf { it.isNotEmpty() }?.any { it != userId } ?: false
}
/**
* 获取当前进程是否短时内重复崩溃
* @return [Boolean]
*/
val isRepeatingCrash by lazy {
resultData?.let {
AppErrorDialog_DataClass.resolve().optional()
.firstFieldOrNull { name = "repeating" }
?.of(it)?.get<Boolean>() == true
} ?: false
}
}
/** 注册生命周期 */
private fun registerLifecycle() {
onAppLifecycle {
/** 解锁后清空已记录的忽略错误 APP */
registerReceiver(Intent.ACTION_USER_PRESENT) { _, _ -> mutedErrorsIfUnlockApps.clear() }
/** 刷新模块 Resources 缓存 */
registerReceiver(Intent.ACTION_LOCALE_CHANGED) { _, _ -> refreshModuleAppResources() }
/** 启动时从本地获取异常记录数据 */
onCreate { AppErrorsRecordData.init(context = this) }
}
FrameworkTool.Host.with(instance = this) {
onRefreshFrameworkPrefsData {
/** 必要的延迟防止 Sp 存储不刷新 */
SystemClock.sleep(100)
/** 刷新存储类 */
AppErrorsConfigData.refresh()
if (prefs.isPreferencesAvailable.not()) YLog.warn("Cannot refreshing app errors config data, preferences is not available")
}
onOpenAppUsedFramework {
appContext?.openApp(it.first, it.second)
YLog.info("Opened \"${it.first}\"${it.second.takeIf { e -> e > 0 }?.let { e -> " --user $e" } ?: ""}")
}
onPushAppErrorInfoData {
AppErrorsRecordData.allData.firstOrNull { e -> e.pid == it } ?: run {
YLog.warn("Cannot received crash application data --pid $it")
AppErrorsInfoBean()
}
}
onPushAppErrorsInfoData { AppErrorsRecordData.allData.toArrayList() }
onRemoveAppErrorsInfoData {
YLog.info("Removed app errors info data for package \"${it.packageName}\"")
AppErrorsRecordData.remove(it)
}
onClearAppErrorsInfoData {
YLog.info("Cleared all app errors info data, size ${AppErrorsRecordData.allData.size}")
AppErrorsRecordData.clearAll()
}
onMutedErrorsIfUnlock {
mutedErrorsIfUnlockApps.add(it)
YLog.info("Muted \"$it\" until unlocks")
}
onMutedErrorsIfRestart {
mutedErrorsIfRestartApps.add(it)
YLog.info("Muted \"$it\" until restarts")
}
onPushMutedErrorsAppsData {
arrayListOf<MutedErrorsAppBean>().apply {
mutedErrorsIfUnlockApps.takeIf { it.isNotEmpty() }
?.forEach { add(MutedErrorsAppBean(MutedErrorsAppBean.MuteType.UNTIL_UNLOCKS, it)) }
mutedErrorsIfRestartApps.takeIf { it.isNotEmpty() }
?.forEach { add(MutedErrorsAppBean(MutedErrorsAppBean.MuteType.UNTIL_REBOOTS, it)) }
}
}
onUnmuteErrorsApp {
when (it.type) {
MutedErrorsAppBean.MuteType.UNTIL_UNLOCKS -> {
YLog.info("Unmuted if unlocks errors app \"${it.packageName}\"")
mutedErrorsIfUnlockApps.remove(it.packageName)
}
MutedErrorsAppBean.MuteType.UNTIL_REBOOTS -> {
YLog.info("Unmuted if restarts errors app \"${it.packageName}\"")
mutedErrorsIfRestartApps.remove(it.packageName)
}
}
}
onUnmuteAllErrorsApps {
YLog.info("Unmute all errors apps --unlocks ${mutedErrorsIfUnlockApps.size} --restarts ${mutedErrorsIfRestartApps.size}")
mutedErrorsIfUnlockApps.clear()
mutedErrorsIfRestartApps.clear()
}
onPushAppListData { filters ->
appContext?.let { context ->
context.listOfPackages()
.filter { it.packageName.let { e -> e != "android" && e != BuildConfigWrapper.APPLICATION_ID } }
.let { info ->
arrayListOf<AppInfoBean>().apply {
if (info.isNotEmpty())
(if (filters.name.isNotBlank()) info.filter {
it.packageName.contains(filters.name) || context.appNameOf(it.packageName).contains(filters.name)
} else info).let { result ->
/**
* 是否为系统应用
* @return [Boolean]
*/
fun PackageInfo.isSystemApp() = applicationInfo?.let {
(it.flags and ApplicationInfo.FLAG_SYSTEM) != 0
} ?: false
when (filters.type) {
AppFiltersType.USER -> result.filter { it.isSystemApp().not() }
AppFiltersType.SYSTEM -> result.filter { it.isSystemApp() }
AppFiltersType.ALL -> result
}
}.sortedByDescending { it.lastUpdateTime }
.forEach { add(AppInfoBean(name = context.appNameOf(it.packageName), packageName = it.packageName)) }
else YLog.warn("Fetched installed packages but got empty list")
}
}
} ?: arrayListOf()
}
}
}
/**
* 处理 APP 进程异常信息展示
* @param context 当前实例
*/
private fun AppErrorsProcessData.handleShowAppErrorUi(context: Context) {
/** 当前 APP 名称 */
val appName = appInfo?.let { context.appNameOf(it.packageName).ifBlank { it.packageName } } ?: packageName
/** 当前 APP 名称 (包含用户 ID) */
val appNameWithUserId = if (userId != 0) "$appName (${locale.userId(userId)})" else appName
/** 崩溃标题 */
val errorTitle = if (isRepeatingCrash) locale.aerrRepeatedTitle(appNameWithUserId) else locale.aerrTitle(appNameWithUserId)
/** 使用通知推送异常信息 */
fun showAppErrorsWithNotify() =
context.pushNotify(
channelId = "APPS_ERRORS",
channelName = locale.appName,
title = errorTitle,
content = locale.appErrorsTip,
icon = IconCompat.createWithBitmap(moduleAppResources.drawableOf(R.drawable.ic_notify).toBitmap()),
color = 0xFFFF6200.toInt(),
intent = AppErrorsRecordActivity.intent()
)
/** 使用 Toast 展示异常信息 */
fun showAppErrorsWithToast() = context.toast(errorTitle)
/** 使用对话框展示异常信息 */
fun showAppErrorsWithDialog() =
AppErrorsDisplayActivity.start(
context, AppErrorsDisplayBean(
pid = pid,
userId = userId,
packageName = packageName,
processName = processName,
appName = appName,
title = errorTitle,
isShowAppInfoButton = isActualApp,
isShowReopenButton = isActualApp &&
(isRepeatingCrash.not() || ConfigData.isEnableAlwaysShowsReopenAppOptions) &&
context.isAppCanOpened(packageName) &&
isMainProcess,
isShowCloseAppButton = isActualApp
)
)
/** 判断是否为已忽略的 APP */
if (mutedErrorsIfUnlockApps.contains(packageName) || mutedErrorsIfRestartApps.contains(packageName)) return
/** 判断是否为后台进程 */
if ((isBackgroundProcess || context.isAppCanOpened(packageName).not()) && ConfigData.isEnableOnlyShowErrorsInFront) return
/** 判断是否为主进程 */
if (isMainProcess.not() && ConfigData.isEnableOnlyShowErrorsInMain) return
when {
packageName == BuildConfigWrapper.APPLICATION_ID -> {
context.toast(msg = "AppErrorsTracking has crashed, please see the log in console")
YLog.error("AppErrorsTracking has crashed itself, please see the Android Runtime Exception in console")
}
ConfigData.isEnableAppConfigTemplate -> when {
AppErrorsConfigData.isAppShowingType(AppErrorsConfigType.GLOBAL, packageName) -> when {
AppErrorsConfigData.isAppShowingType(AppErrorsConfigType.DIALOG) -> showAppErrorsWithDialog()
AppErrorsConfigData.isAppShowingType(AppErrorsConfigType.NOTIFY) -> showAppErrorsWithNotify()
AppErrorsConfigData.isAppShowingType(AppErrorsConfigType.TOAST) -> showAppErrorsWithToast()
AppErrorsConfigData.isAppShowingType(AppErrorsConfigType.NOTHING) -> {}
}
AppErrorsConfigData.isAppShowingType(AppErrorsConfigType.DIALOG, packageName) -> showAppErrorsWithDialog()
AppErrorsConfigData.isAppShowingType(AppErrorsConfigType.NOTIFY, packageName) -> showAppErrorsWithNotify()
AppErrorsConfigData.isAppShowingType(AppErrorsConfigType.TOAST, packageName) -> showAppErrorsWithToast()
AppErrorsConfigData.isAppShowingType(AppErrorsConfigType.NOTHING, packageName) -> {}
}
else -> showAppErrorsWithDialog()
}
/** 打印错误日志 */
if (isActualApp) YLog.error(
msg = "Application \"$packageName\" ${if (isRepeatingCrash) "keeps stopping" else "has stopped"}" +
(if (packageName != processName) " --process \"$processName\"" else "") +
"${if (userId != 0) " --user $userId" else ""} --pid $pid"
) else YLog.error("Process \"$processName\" ${if (isRepeatingCrash) "keeps stopping" else "has stopped"} --pid $pid")
}
/**
* 处理 APP 进程异常数据
* @param context 当前实例
* @param info 系统错误报告数据实例
*/
private fun AppErrorsProcessData.handleAppErrorsInfo(context: Context, info: ApplicationErrorReport.CrashInfo?) {
AppErrorsRecordData.add(AppErrorsInfoBean.clone(context, pid, userId, appInfo?.packageName, info))
YLog.info("Received crash application data${if (userId != 0) " --user $userId" else ""} --pid $pid")
}
override fun onHook() {
/** 注册生命周期 */
registerLifecycle()
/** 干掉原生错误对话框 - 如果有 */
ErrorDialogControllerClass?.resolve()?.optional(silent = true)?.apply {
val hasCrashDialogs = firstMethodOrNull {
name = "hasCrashDialogs"
emptyParameters()
}?.hook()?.replaceToTrue() != null
if (!hasCrashDialogs)
firstConstructorOrNull {
parameterCount = 1
}?.hook()?.after {
firstFieldOrNull { name = "mCrashDialogs" }?.of(instance)?.set(emptyList<Any>())
}
firstMethodOrNull {
name = "showCrashDialogs"
parameterCount = 1
}?.hook()?.intercept()
}
/** 干掉原生错误对话框 - API 30 以下 */
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) {
ActivityTaskManagerService_LocalServiceClass?.resolve()?.optional()?.firstMethodOrNull {
name = "canShowErrorDialogs"
emptyParameters()
}?.hook()?.replaceToFalse()
ActivityManagerServiceClass?.resolve()?.optional()?.firstMethodOrNull {
name = "canShowErrorDialogs"
emptyParameters()
}?.hook()?.replaceToFalse()
}
/** 干掉原生错误对话框 - 如果上述方法全部失效则直接结束对话框 */
AppErrorDialogClass.resolve().optional(silent = true).apply {
firstMethodOrNull {
name = "onCreate"
parameters(Bundle::class)
}?.hook()?.after { instance<Dialog>().cancel() }
firstMethodOrNull {
name = "onStart"
emptyParameters()
}?.hook()?.after { instance<Dialog>().cancel() }
}
/** 注入自定义错误对话框 */
AppErrorsClass.resolve().optional().apply {
when {
Build.VERSION.SDK_INT > Build.VERSION_CODES.R -> {
firstMethodOrNull {
name = "handleAppCrashLSPB"
parameterCount = 6
}?.hook()?.after {
/** 如果为用户终止则不展示异常 */
if (args(index = 1).string() == "user-terminated") return@after
/** 当前实例 */
val context = appContext ?: firstFieldOrNull { name = "mContext" }?.of(instance)?.get<Context>() ?: return@after
/** 当前进程信息 */
val proc = args().first().any() ?: return@after YLog.warn("Received but got null ProcessRecord (Show UI failed)")
/** 当前错误数据 */
val resultData = args().last().any()
/** 创建 APP 进程异常数据类 */
AppErrorsProcessData(instance, proc, resultData).handleShowAppErrorUi(context)
}
}
else -> {
firstMethodOrNull {
name = "handleShowAppErrorUi"
parameters(Message::class)
}?.hook()?.after {
/** 当前实例 */
val context = appContext ?: firstFieldOrNull { name = "mContext" }?.of(instance)?.get<Context>() ?: return@after
/** 当前错误数据 */
val resultData = args().first().cast<Message>()?.obj
/** 当前进程信息 */
val proc = AppErrorDialog_DataClass.resolve().optional().firstFieldOrNull { name = "proc" }?.of(resultData)?.get()
/** 创建 APP 进程异常数据类 */
AppErrorsProcessData(instance, proc, resultData).handleShowAppErrorUi(context)
}
}
}
firstMethodOrNull {
name = "handleAppCrashInActivityController"
returnType = Boolean::class
}?.hook()?.after {
/** 当前实例 */
val context = appContext ?: firstFieldOrNull { name = "mContext" }?.of(instance)?.get<Context>() ?: return@after
/** 当前进程信息 */
val proc = args().first().any() ?: return@after YLog.warn("Received but got null ProcessRecord")
/** 创建 APP 进程异常数据类 */
AppErrorsProcessData(instance, proc).handleAppErrorsInfo(context, args(index = 1).cast())
}
}
}
}

View File

@@ -0,0 +1,29 @@
/*
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
* Copyright (C) 2017 Fankes Studio(qzmmcn@163.com)
* https://github.com/KitsunePie/AppErrorsTracking
*
* This software is non-free but opensource software: you can redistribute it
* and/or modify it under the terms of the GNU Affero General Public License
* as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* and eula along with this software. If not, see
* <https://www.gnu.org/licenses/>
*
* This file is created by fankes on 2023/10/13.
*/
@file:Suppress("StaticFieldLeak")
package com.fankes.apperrorstracking.locale
import com.fankes.apperrorstracking.generated.locale.ModuleAppLocale
/** I18ns 实例 */
lateinit var locale: ModuleAppLocale

View File

@@ -1,6 +1,6 @@
/*
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* Copyright (C) 2017 Fankes Studio(qzmmcn@163.com)
* https://github.com/KitsunePie/AppErrorsTracking
*
* This software is non-free but opensource software: you can redistribute it
@@ -17,7 +17,7 @@
* and eula along with this software. If not, see
* <https://www.gnu.org/licenses/>
*
* This file is Created by fankes on 2022/5/11.
* This file is created by fankes on 2022/5/11.
*/
package com.fankes.apperrorstracking.service

View File

@@ -1,6 +1,6 @@
/*
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* Copyright (C) 2017 Fankes Studio(qzmmcn@163.com)
* https://github.com/KitsunePie/AppErrorsTracking
*
* This software is non-free but opensource software: you can redistribute it
@@ -17,14 +17,15 @@
* and eula along with this software. If not, see
* <https://www.gnu.org/licenses/>
*
* This file is Created by fankes on 2022/5/7.
* This file is created by fankes on 2022/5/7.
*/
@file:Suppress("UNCHECKED_CAST")
package com.fankes.apperrorstracking.ui.activity.base
import android.app.ActivityManager
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.view.LayoutInflater
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.res.ResourcesCompat
import androidx.core.view.WindowCompat
@@ -32,9 +33,9 @@ import androidx.viewbinding.ViewBinding
import com.fankes.apperrorstracking.R
import com.fankes.apperrorstracking.utils.factory.isNotSystemInDarkMode
import com.fankes.apperrorstracking.utils.factory.toast
import com.highcapable.yukihookapi.hook.factory.current
import com.highcapable.yukihookapi.hook.factory.method
import com.highcapable.yukihookapi.hook.type.android.LayoutInflaterClass
import com.highcapable.kavaref.KavaRef.Companion.resolve
import com.highcapable.kavaref.extension.genericSuperclassTypeArguments
import com.highcapable.kavaref.extension.toClassOrNull
abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
@@ -43,10 +44,12 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = current().generic()?.argument()?.method {
val bindingClass = javaClass.genericSuperclassTypeArguments().firstOrNull()?.toClassOrNull()
binding = bindingClass?.resolve()?.optional()?.firstMethodOrNull {
name = "inflate"
param(LayoutInflaterClass)
}?.get()?.invoke<VB>(layoutInflater) ?: error("binding failed")
parameters(LayoutInflater::class)
}?.invoke<VB>(layoutInflater) ?: error("binding failed")
if (Build.VERSION.SDK_INT >= 35) binding.root.fitsSystemWindows = true
setContentView(binding.root)
/** 隐藏系统的标题栏 */
supportActionBar?.hide()
@@ -55,6 +58,7 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
isAppearanceLightStatusBars = isNotSystemInDarkMode
isAppearanceLightNavigationBars = isNotSystemInDarkMode
}
@Suppress("DEPRECATION")
ResourcesCompat.getColor(resources, R.color.colorThemeBackground, null).also {
window?.statusBarColor = it
window?.navigationBarColor = it
@@ -67,6 +71,24 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
/** 回调 [onCreate] 方法 */
abstract fun onCreate()
/**
* 在旧版的 Android 系统中使用了 activity-alias 标签从启动器启动 Activity 会造成其组件名称 (完整类名) 为代理名称
*
* 为了获取真实的顶层 Activity 组件名称 (完整类名) - 如果名称不正确将自动执行一次结束并重新打开当前 Activity
*/
fun checkingTopComponentName() {
/** 当前顶层的 Activity 组件名称 (完整类名) */
val topComponentName = runCatching {
@Suppress("DEPRECATION")
(getSystemService(ACTIVITY_SERVICE) as? ActivityManager?)
?.getRunningTasks(9999)?.firstOrNull()?.topActivity?.className ?: ""
}.getOrNull() ?: ""
if (topComponentName.isNotBlank() && topComponentName != javaClass.name) {
finish()
startActivity(Intent(this, javaClass))
}
}
/**
* 弹出提示并退出
* @param name 名称

View File

@@ -1,6 +1,6 @@
/*
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* Copyright (C) 2017 Fankes Studio(qzmmcn@163.com)
* https://github.com/KitsunePie/AppErrorsTracking
*
* This software is non-free but opensource software: you can redistribute it
@@ -17,7 +17,7 @@
* and eula along with this software. If not, see
* <https://www.gnu.org/licenses/>
*
* This file is Created by fankes on 2022/10/4.
* This file is created by fankes on 2022/10/4.
*/
@file:Suppress("DEPRECATION", "OVERRIDE_DEPRECATION")
@@ -31,21 +31,24 @@ import android.view.View
import android.widget.AdapterView
import androidx.core.view.isVisible
import com.fankes.apperrorstracking.R
import com.fankes.apperrorstracking.const.PackageName
import com.fankes.apperrorstracking.databinding.ActivitiyLoggerBinding
import com.fankes.apperrorstracking.databinding.AdapterLoggerBinding
import com.fankes.apperrorstracking.databinding.DiaLoggerFilterBinding
import com.fankes.apperrorstracking.locale.LocaleString
import com.fankes.apperrorstracking.locale.locale
import com.fankes.apperrorstracking.ui.activity.base.BaseActivity
import com.fankes.apperrorstracking.utils.factory.*
import com.fankes.apperrorstracking.utils.tool.FrameworkTool
import com.highcapable.yukihookapi.hook.factory.current
import com.fankes.apperrorstracking.utils.factory.bindAdapter
import com.fankes.apperrorstracking.utils.factory.copyToClipboard
import com.fankes.apperrorstracking.utils.factory.showDialog
import com.fankes.apperrorstracking.utils.factory.toUtcTime
import com.fankes.apperrorstracking.utils.factory.toast
import com.highcapable.yukihookapi.hook.factory.dataChannel
import com.highcapable.yukihookapi.hook.log.YukiHookLogger
import com.highcapable.yukihookapi.hook.log.YukiLoggerData
import com.highcapable.yukihookapi.hook.log.YLog
import com.highcapable.yukihookapi.hook.log.data.YLogData
import java.io.ByteArrayOutputStream
import java.io.PrintStream
import java.text.SimpleDateFormat
import java.util.*
import java.util.Date
class LoggerActivity : BaseActivity<ActivitiyLoggerBinding>() {
@@ -62,14 +65,14 @@ class LoggerActivity : BaseActivity<ActivitiyLoggerBinding>() {
private var filters = arrayListOf("D", "I", "W", "E")
/** 全部的调试日志数据 */
private val listData = ArrayList<YukiLoggerData>()
private val listData = mutableListOf<YLogData>()
override fun onCreate() {
binding.titleBackIcon.setOnClickListener { finish() }
binding.refreshIcon.setOnClickListener { refreshData() }
binding.filterIcon.setOnClickListener {
showDialog<DiaLoggerFilterBinding> {
title = LocaleString.filterByCondition
title = locale.filterByCondition
binding.configCheck0.isChecked = filters.any { it == "D" }
binding.configCheck1.isChecked = filters.any { it == "I" }
binding.configCheck2.isChecked = filters.any { it == "W" }
@@ -125,7 +128,7 @@ class LoggerActivity : BaseActivity<ActivitiyLoggerBinding>() {
/** 更新列表数据 */
private fun refreshData() {
dataChannel(FrameworkTool.SYSTEM_FRAMEWORK_NAME).obtainLoggerInMemoryData {
dataChannel(PackageName.SYSTEM_FRAMEWORK).obtainLoggerInMemoryData {
listData.clear()
it.takeIf { e -> e.isNotEmpty() }?.reversed()?.filter { filters.any { e -> it.priority == e } }?.forEach { e -> listData.add(e) }
onChanged?.invoke()
@@ -133,30 +136,10 @@ class LoggerActivity : BaseActivity<ActivitiyLoggerBinding>() {
binding.exportAllIcon.isVisible = listData.isNotEmpty()
binding.listView.isVisible = listData.isNotEmpty()
binding.listNoDataView.isVisible = listData.isEmpty()
binding.listNoDataView.text = if (filters.size < 4) LocaleString.noListResult else LocaleString.noListData
binding.listNoDataView.text = if (filters.size < 4) locale.noListResult else locale.noListData
}
}
/**
* 获取当前所有日志写出文件的格式
*
* 复制自 [YukiHookLogger.contents]
* @return [String]
*/
private val loggerContents: String
get() {
var content = ""
listData.takeIf { it.isNotEmpty() }?.forEach {
val head = "${it.time} ------ "
content += "$head$it\n"
it.throwable?.also { e ->
content += "${head}Dump stack trace for \"${e.current().name}\":\n"
content += e.toStackTrace()
}
}
return content
}
/**
* 格式化为本地时间格式
* @return [String]
@@ -167,7 +150,7 @@ class LoggerActivity : BaseActivity<ActivitiyLoggerBinding>() {
* 格式化消息字符串样式
* @return [String]
*/
private fun String.format() = replace(oldValue = "--", newValue = "\n--")
private fun String.format() = replace("--", "\n--")
/**
* 获取完整的异常堆栈内容
@@ -193,10 +176,10 @@ class LoggerActivity : BaseActivity<ActivitiyLoggerBinding>() {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == WRITE_REQUEST_CODE && resultCode == Activity.RESULT_OK) runCatching {
data?.data?.let {
contentResolver?.openOutputStream(it)?.apply { write(loggerContents.toByteArray()) }?.close()
toast(LocaleString.exportAllLogsSuccess)
} ?: toast(LocaleString.exportAllLogsFail)
}.onFailure { toast(LocaleString.exportAllLogsFail) }
contentResolver?.openOutputStream(it)?.apply { write(YLog.contents(listData).toByteArray()) }?.close()
toast(locale.exportAllLogsSuccess)
} ?: toast(locale.exportAllLogsFail)
}.onFailure { toast(locale.exportAllLogsFail) }
}
override fun onResume() {

View File

@@ -0,0 +1,220 @@
/*
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
* Copyright (C) 2017 Fankes Studio(qzmmcn@163.com)
* https://github.com/KitsunePie/AppErrorsTracking
*
* This software is non-free but opensource software: you can redistribute it
* and/or modify it under the terms of the GNU Affero General Public License
* as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* and eula along with this software. If not, see
* <https://www.gnu.org/licenses/>
*
* This file is created by fankes on 2022/5/7.
*/
@file:Suppress("DEPRECATION", "OVERRIDE_DEPRECATION")
package com.fankes.apperrorstracking.ui.activity.errors
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.widget.TextView
import androidx.core.content.FileProvider
import androidx.core.view.isGone
import androidx.core.view.isVisible
import com.fankes.apperrorstracking.R
import com.fankes.apperrorstracking.bean.AppErrorsInfoBean
import com.fankes.apperrorstracking.data.ConfigData
import com.fankes.apperrorstracking.data.factory.bind
import com.fankes.apperrorstracking.databinding.ActivityAppErrorsDetailBinding
import com.fankes.apperrorstracking.locale.locale
import com.fankes.apperrorstracking.ui.activity.base.BaseActivity
import com.fankes.apperrorstracking.utils.factory.appIconOf
import com.fankes.apperrorstracking.utils.factory.appNameOf
import com.fankes.apperrorstracking.utils.factory.copyToClipboard
import com.fankes.apperrorstracking.utils.factory.dp
import com.fankes.apperrorstracking.utils.factory.getSerializableExtraCompat
import com.fankes.apperrorstracking.utils.factory.navigate
import com.fankes.apperrorstracking.utils.factory.openSelfSetting
import com.fankes.apperrorstracking.utils.factory.showDialog
import com.fankes.apperrorstracking.utils.factory.toast
import com.fankes.apperrorstracking.utils.tool.StackTraceShareHelper
import com.highcapable.yukihookapi.hook.log.loggerE
import java.io.File
class AppErrorsDetailActivity : BaseActivity<ActivityAppErrorsDetailBinding>() {
companion object {
/** 请求保存文件回调标识 */
private const val WRITE_REQUEST_CODE = 0
/** [AppErrorsInfoBean] 传值 */
private const val EXTRA_APP_ERRORS_INFO = "app_errors_info_extra"
/**
* 启动 [AppErrorsDetailActivity]
* @param context 实例
* @param appErrorsInfo 应用异常信息
*/
fun start(context: Context, appErrorsInfo: AppErrorsInfoBean) =
context.navigate<AppErrorsDetailActivity> { putExtra(EXTRA_APP_ERRORS_INFO, appErrorsInfo) }
}
/** 预导出的异常堆栈 */
private var stackTrace = ""
override fun onCreate() {
if (initUi(intent).not()) return
binding.titleBackIcon.setOnClickListener { onBackPressed() }
binding.disableAutoWrapErrorStackTraceSwitch.bind(ConfigData.DISABLE_AUTO_WRAP_ERROR_STACK_TRACE) {
onInitialize {
binding.errorStackTraceScrollView.isVisible = it
binding.errorStackTraceFixedText.isGone = it
}
onChanged {
reinitialize()
resetScrollView()
}
}
binding.detailTitleText.setOnClickListener { binding.appPanelScrollView.smoothScrollTo(0, 0) }
resetScrollView()
}
/**
* 从 [Intent] 中解析 [AppErrorsInfoBean] 并加载至界面
*
* @param intent 用于获取并解析 [AppErrorsInfoBean] 的 [Intent] 实例
* @return [Boolean] 是否解析成功true 为成功false 为失败,可能是 [Intent] 为空或者 [AppErrorsInfoBean] 为空
*/
private fun initUi(intent: Intent?): Boolean {
val appErrorsInfo = runCatching { intent?.getSerializableExtraCompat<AppErrorsInfoBean>(EXTRA_APP_ERRORS_INFO) }.getOrNull()
if (appErrorsInfo == null) {
toastAndFinish(name = "AppErrorsInfo")
return false
}
if (appErrorsInfo.isEmpty) {
binding.appPanelScrollView.isVisible = false
showDialog {
title = locale.notice
msg = locale.unableGetAppErrorsRecordTip
confirmButton(locale.gotIt) {
cancel()
finish()
}
cancelButton(locale.goItNow) {
cancel()
finish()
navigate<AppErrorsRecordActivity>()
}
noCancelable()
}
return false
}
binding.appInfoItem.setOnClickListener { openSelfSetting(appErrorsInfo.packageName) }
binding.printIcon.setOnClickListener {
loggerE(msg = appErrorsInfo.stackTrace)
toast(locale.printToLogcatSuccess)
}
binding.copyIcon.setOnClickListener {
StackTraceShareHelper.showChoose(context = this, locale.copyErrorStack) { sDeviceBrand, sDeviceModel, sDisplay, sPackageName ->
copyToClipboard(appErrorsInfo.stackOutputShareContent(sDeviceBrand, sDeviceModel, sDisplay, sPackageName))
}
}
binding.exportIcon.setOnClickListener {
StackTraceShareHelper.showChoose(context = this, locale.exportToFile) { sDeviceBrand, sDeviceModel, sDisplay, sPackageName ->
stackTrace = appErrorsInfo.stackOutputFileContent(sDeviceBrand, sDeviceModel, sDisplay, sPackageName)
runCatching {
startActivityForResult(Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "*/application"
val packageName = if (sPackageName) appErrorsInfo.packageName else "anonymous"
putExtra(Intent.EXTRA_TITLE, "${packageName}_${appErrorsInfo.utcTime}.log")
}, WRITE_REQUEST_CODE)
}.onFailure { toast(msg = "Start Android SAF failed") }
}
}
binding.shareIcon.setOnClickListener {
StackTraceShareHelper.showChoose(context = this, locale.shareErrorStack) { sDeviceBrand, sDeviceModel, sDisplay, sPackageName ->
startActivity(Intent.createChooser(Intent(Intent.ACTION_SEND).apply {
val content = appErrorsInfo.stackOutputShareContent(sDeviceBrand, sDeviceModel, sDisplay, sPackageName)
if (ConfigData.isShareWithFile) {
type = "application/octet-stream"
runCatching {
val file = File.createTempFile("app_errors_stacktrace_", ".log", cacheDir)
file.deleteOnExit()
file.writeText(content)
putExtra(Intent.EXTRA_STREAM, FileProvider.getUriForFile(this@AppErrorsDetailActivity, "$packageName.provider", file))
}.onFailure {
toast(msg = "Create temp file failed")
}
} else {
type = "text/plain"
putExtra(Intent.EXTRA_TEXT, content)
}
}, locale.shareErrorStack))
}
}
binding.appIcon.setImageDrawable(appIconOf(appErrorsInfo.packageName))
binding.appNameText.text = appNameOf(appErrorsInfo.packageName).ifBlank { appErrorsInfo.packageName }
binding.appVersionText.text = appErrorsInfo.versionBrand
binding.appUserIdText.isVisible = appErrorsInfo.userId > 0
binding.appUserIdText.text = locale.userId(appErrorsInfo.userId)
binding.appCpuAbiText.text = appErrorsInfo.cpuAbi.ifBlank { locale.noCpuAbi }
binding.appTargetSdkText.text = locale.appTargetSdk(appErrorsInfo.targetSdk)
binding.appMinSdkText.text = locale.appMinSdk(appErrorsInfo.minSdk)
binding.jvmErrorPanel.isGone = appErrorsInfo.isNativeCrash
binding.errorTypeIcon.setImageResource(if (appErrorsInfo.isNativeCrash) R.drawable.ic_cpp else R.drawable.ic_java)
binding.errorInfoText.text = appErrorsInfo.exceptionMessage
binding.errorTypeText.text = appErrorsInfo.exceptionClassName
binding.errorFileNameText.text = appErrorsInfo.throwFileName
binding.errorThrowClassText.text = appErrorsInfo.throwClassName
binding.errorThrowMethodText.text = appErrorsInfo.throwMethodName
binding.errorLineNumberText.text = appErrorsInfo.throwLineNumber.toString()
binding.errorRecordTimeText.text = appErrorsInfo.dateTime
binding.errorStackTraceMovableText.text = appErrorsInfo.stackTrace
binding.errorStackTraceFixedText.text = appErrorsInfo.stackTrace
binding.appPanelScrollView.setOnScrollChangeListener { _, _, y, _, _ ->
binding.detailTitleText.text = if (y >= 30.dp(context = this@AppErrorsDetailActivity))
appNameOf(appErrorsInfo.packageName).ifBlank { appErrorsInfo.packageName }
else locale.appName
}
return true
}
/** 修复在一些小屏设备上设置了 [TextView.setTextIsSelectable] 后布局自动上滑问题 */
private fun resetScrollView() {
binding.rootView.post {
binding.appPanelScrollView.scrollTo(0, 0)
binding.errorStackTraceScrollView.scrollTo(0, 0)
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == WRITE_REQUEST_CODE && resultCode == Activity.RESULT_OK) runCatching {
data?.data?.let {
contentResolver?.openOutputStream(it)?.apply { write(stackTrace.toByteArray()) }?.close()
toast(locale.outputStackSuccess)
} ?: toast(locale.outputStackFail)
}.onFailure { toast(locale.outputStackFail) }
}
override fun onBackPressed() {
intent?.removeExtra(EXTRA_APP_ERRORS_INFO)
super.onBackPressed()
}
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
if (initUi(intent)) binding.appPanelScrollView.scrollTo(0, 0)
}
}

View File

@@ -1,6 +1,6 @@
/*
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* Copyright (C) 2017 Fankes Studio(qzmmcn@163.com)
* https://github.com/KitsunePie/AppErrorsTracking
*
* This software is non-free but opensource software: you can redistribute it
@@ -17,7 +17,7 @@
* and eula along with this software. If not, see
* <https://www.gnu.org/licenses/>
*
* This file is Created by fankes on 2022/6/1.
* This file is created by fankes on 2022/6/1.
*/
package com.fankes.apperrorstracking.ui.activity.errors
@@ -29,9 +29,14 @@ import com.fankes.apperrorstracking.bean.AppErrorsDisplayBean
import com.fankes.apperrorstracking.data.ConfigData
import com.fankes.apperrorstracking.databinding.ActivityAppErrorsDisplayBinding
import com.fankes.apperrorstracking.databinding.DiaAppErrorsDisplayBinding
import com.fankes.apperrorstracking.locale.LocaleString
import com.fankes.apperrorstracking.locale.locale
import com.fankes.apperrorstracking.ui.activity.base.BaseActivity
import com.fankes.apperrorstracking.utils.factory.*
import com.fankes.apperrorstracking.utils.factory.colorOf
import com.fankes.apperrorstracking.utils.factory.getSerializableExtraCompat
import com.fankes.apperrorstracking.utils.factory.navigate
import com.fankes.apperrorstracking.utils.factory.openSelfSetting
import com.fankes.apperrorstracking.utils.factory.showDialog
import com.fankes.apperrorstracking.utils.factory.toast
import com.fankes.apperrorstracking.utils.tool.FrameworkTool
class AppErrorsDisplayActivity : BaseActivity<ActivityAppErrorsDisplayBinding>() {
@@ -73,7 +78,7 @@ class AppErrorsDisplayActivity : BaseActivity<ActivityAppErrorsDisplayBinding>()
binding.appInfoItem.isVisible = appErrorsDisplay.isShowAppInfoButton
binding.closeAppItem.isVisible = appErrorsDisplay.isShowReopenButton.not() && appErrorsDisplay.isShowCloseAppButton
binding.reopenAppItem.isVisible = appErrorsDisplay.isShowReopenButton
binding.processNameText.text = LocaleString.crashProcess(appErrorsDisplay.processName)
binding.processNameText.text = locale.crashProcess(appErrorsDisplay.processName)
binding.appInfoItem.setOnClickListener {
cancel()
openSelfSetting(appErrorsDisplay.packageName)
@@ -91,13 +96,13 @@ class AppErrorsDisplayActivity : BaseActivity<ActivityAppErrorsDisplayBinding>()
}
binding.mutedIfUnlockItem.setOnClickListener {
FrameworkTool.mutedErrorsIfUnlock(context, appErrorsDisplay.packageName) {
toast(LocaleString.muteIfUnlockTip(appErrorsDisplay.appName))
toast(locale.muteIfUnlockTip(appErrorsDisplay.appName))
cancel()
}
}
binding.mutedIfRestartItem.setOnClickListener {
FrameworkTool.mutedErrorsIfRestart(context, appErrorsDisplay.packageName) {
toast(LocaleString.muteIfRestartTip(appErrorsDisplay.appName))
toast(locale.muteIfRestartTip(appErrorsDisplay.appName))
cancel()
}
}

View File

@@ -1,6 +1,6 @@
/*
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* Copyright (C) 2017 Fankes Studio(qzmmcn@163.com)
* https://github.com/KitsunePie/AppErrorsTracking
*
* This software is non-free but opensource software: you can redistribute it
@@ -17,17 +17,15 @@
* and eula along with this software. If not, see
* <https://www.gnu.org/licenses/>
*
* This file is Created by fankes on 2022/6/3.
* This file is created by fankes on 2022/6/3.
*/
@file:Suppress("DEPRECATION", "OVERRIDE_DEPRECATION")
package com.fankes.apperrorstracking.ui.activity.errors
import androidx.core.view.isVisible
import com.fankes.apperrorstracking.bean.MutedErrorsAppBean
import com.fankes.apperrorstracking.databinding.ActivityAppErrorsMutedBinding
import com.fankes.apperrorstracking.databinding.AdapterAppErrorsMutedBinding
import com.fankes.apperrorstracking.locale.LocaleString
import com.fankes.apperrorstracking.locale.locale
import com.fankes.apperrorstracking.ui.activity.base.BaseActivity
import com.fankes.apperrorstracking.utils.factory.appIconOf
import com.fankes.apperrorstracking.utils.factory.appNameOf
@@ -47,8 +45,8 @@ class AppErrorsMutedActivity : BaseActivity<ActivityAppErrorsMutedBinding>() {
binding.titleBackIcon.setOnClickListener { onBackPressed() }
binding.unmuteAllIcon.setOnClickListener {
showDialog {
title = LocaleString.notice
msg = LocaleString.areYouSureUnmuteAll
title = locale.notice
msg = locale.areYouSureUnmuteAll
confirmButton { FrameworkTool.unmuteAllErrorsApps(context) { refreshData() } }
cancelButton()
}
@@ -59,10 +57,10 @@ class AppErrorsMutedActivity : BaseActivity<ActivityAppErrorsMutedBinding>() {
onBindViews<AdapterAppErrorsMutedBinding> { binding, position ->
listData[position].also { bean ->
binding.appIcon.setImageDrawable(appIconOf(bean.packageName))
binding.appNameText.text = appNameOf(bean.packageName)
binding.appNameText.text = appNameOf(bean.packageName).ifBlank { bean.packageName }
binding.muteTypeText.text = when (bean.type) {
MutedErrorsAppBean.MuteType.UNTIL_UNLOCKS -> LocaleString.muteIfUnlock
MutedErrorsAppBean.MuteType.UNTIL_REBOOTS -> LocaleString.muteIfRestart
MutedErrorsAppBean.MuteType.UNTIL_UNLOCKS -> locale.muteIfUnlock
MutedErrorsAppBean.MuteType.UNTIL_REBOOTS -> locale.muteIfRestart
}
binding.unmuteButton.setOnClickListener { FrameworkTool.unmuteErrorsApp(context, bean) { refreshData() } }
}

View File

@@ -1,6 +1,6 @@
/*
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* Copyright (C) 2017 Fankes Studio(qzmmcn@163.com)
* https://github.com/KitsunePie/AppErrorsTracking
*
* This software is non-free but opensource software: you can redistribute it
@@ -17,7 +17,7 @@
* and eula along with this software. If not, see
* <https://www.gnu.org/licenses/>
*
* This file is Created by fankes on 2022/5/11.
* This file is created by fankes on 2022/5/11.
*/
@file:Suppress("DEPRECATION", "OVERRIDE_DEPRECATION", "SetTextI18n")
@@ -31,18 +31,28 @@ import android.view.MenuItem
import android.view.View
import android.widget.AdapterView.AdapterContextMenuInfo
import androidx.core.view.isVisible
import com.fankes.apperrorstracking.BuildConfig
import com.fankes.apperrorstracking.R
import com.fankes.apperrorstracking.bean.AppErrorsInfoBean
import com.fankes.apperrorstracking.bean.AppFiltersBean
import com.fankes.apperrorstracking.bean.enum.AppFiltersType
import com.fankes.apperrorstracking.databinding.ActivityAppErrorsRecordBinding
import com.fankes.apperrorstracking.databinding.AdapterAppErrorsRecordBinding
import com.fankes.apperrorstracking.databinding.DiaAppErrorsStatisticsBinding
import com.fankes.apperrorstracking.locale.LocaleString
import com.fankes.apperrorstracking.locale.locale
import com.fankes.apperrorstracking.ui.activity.base.BaseActivity
import com.fankes.apperrorstracking.utils.factory.*
import com.fankes.apperrorstracking.utils.factory.appIconOf
import com.fankes.apperrorstracking.utils.factory.appNameOf
import com.fankes.apperrorstracking.utils.factory.bindAdapter
import com.fankes.apperrorstracking.utils.factory.decimal
import com.fankes.apperrorstracking.utils.factory.newThread
import com.fankes.apperrorstracking.utils.factory.openSelfSetting
import com.fankes.apperrorstracking.utils.factory.showDialog
import com.fankes.apperrorstracking.utils.factory.toUtcTime
import com.fankes.apperrorstracking.utils.factory.toast
import com.fankes.apperrorstracking.utils.tool.FrameworkTool
import com.fankes.apperrorstracking.utils.tool.StackTraceShareHelper
import com.fankes.apperrorstracking.utils.tool.ZipFileTool
import com.fankes.apperrorstracking.wrapper.BuildConfigWrapper
import java.io.File
import java.io.FileInputStream
@@ -57,7 +67,7 @@ class AppErrorsRecordActivity : BaseActivity<ActivityAppErrorsRecordBinding>() {
* 获取 [Intent]
* @return [Intent]
*/
fun intent() = Intent().apply { component = ComponentName(BuildConfig.APPLICATION_ID, AppErrorsRecordActivity::class.java.name) }
fun intent() = Intent().apply { component = ComponentName(BuildConfigWrapper.APPLICATION_ID, AppErrorsRecordActivity::class.java.name) }
}
/** 当前导出文件的路径 */
@@ -73,10 +83,10 @@ class AppErrorsRecordActivity : BaseActivity<ActivityAppErrorsRecordBinding>() {
binding.titleBackIcon.setOnClickListener { onBackPressed() }
binding.appErrorSisIcon.setOnClickListener {
showDialog {
title = LocaleString.notice
progressContent = LocaleString.generatingStatistics
title = locale.notice
progressContent = locale.generatingStatistics
noCancelable()
FrameworkTool.fetchAppListData(context, AppFiltersBean(isContainsSystem = true)) {
FrameworkTool.fetchAppListData(context, AppFiltersBean(type = AppFiltersType.ALL)) {
newThread {
val errorsApps = listData.groupBy { it.packageName }
.map { it.key to it.value.size }
@@ -91,14 +101,14 @@ class AppErrorsRecordActivity : BaseActivity<ActivityAppErrorsRecordBinding>() {
runOnUiThread {
cancel()
showDialog<DiaAppErrorsStatisticsBinding> {
title = LocaleString.appErrorsStatistics
binding.totalErrorsUnitText.text = LocaleString.totalErrorsUnit(listData.size)
binding.totalAppsUnitText.text = LocaleString.totalAppsUnit(it.size)
title = locale.appErrorsStatistics
binding.totalErrorsUnitText.text = locale.totalErrorsUnit(listData.size)
binding.totalAppsUnitText.text = locale.totalAppsUnit(it.size)
binding.mostErrorsAppIcon.setImageDrawable(appIconOf(mostAppPackageName))
binding.mostErrorsAppText.text = appNameOf(mostAppPackageName)
binding.mostErrorsAppText.text = appNameOf(mostAppPackageName).ifBlank { mostAppPackageName }
binding.mostErrorsTypeText.text = mostErrorsType
binding.totalPptOfErrorsText.text = "$pptCount%"
confirmButton(LocaleString.gotIt)
confirmButton(locale.gotIt)
}
}
}
@@ -107,12 +117,12 @@ class AppErrorsRecordActivity : BaseActivity<ActivityAppErrorsRecordBinding>() {
}
binding.clearAllIcon.setOnClickListener {
showDialog {
title = LocaleString.notice
msg = LocaleString.areYouSureClearErrors
title = locale.notice
msg = locale.areYouSureClearErrors
confirmButton {
FrameworkTool.clearAppErrorsInfoData(context) {
refreshData()
toast(LocaleString.allErrorsClearSuccess)
toast(locale.allErrorsClearSuccess)
}
}
cancelButton()
@@ -120,8 +130,8 @@ class AppErrorsRecordActivity : BaseActivity<ActivityAppErrorsRecordBinding>() {
}
binding.exportAllIcon.setOnClickListener {
showDialog {
title = LocaleString.notice
msg = LocaleString.areYouSureExportAllErrors
title = locale.notice
msg = locale.areYouSureExportAllErrors
confirmButton { exportAll() }
cancelButton()
}
@@ -133,9 +143,9 @@ class AppErrorsRecordActivity : BaseActivity<ActivityAppErrorsRecordBinding>() {
onBindViews<AdapterAppErrorsRecordBinding> { binding, position ->
listData[position].also { bean ->
binding.appIcon.setImageDrawable(appIconOf(bean.packageName))
binding.appNameText.text = appNameOf(bean.packageName)
binding.appNameText.text = appNameOf(bean.packageName).ifBlank { bean.packageName }
binding.appUserIdText.isVisible = bean.userId > 0
binding.appUserIdText.text = LocaleString.userId(bean.userId)
binding.appUserIdText.text = locale.userId(bean.userId)
binding.errorsTimeText.text = bean.crossTime
binding.errorTypeIcon.setImageResource(if (bean.isNativeCrash) R.drawable.ic_cpp else R.drawable.ic_java)
binding.errorTypeText.text = if (bean.isNativeCrash) "Native crash" else bean.exceptionClassName.simpleThwName()
@@ -151,34 +161,40 @@ class AppErrorsRecordActivity : BaseActivity<ActivityAppErrorsRecordBinding>() {
/** 更新列表数据 */
private fun refreshData() {
FrameworkTool.fetchAppErrorsInfoData(context = this) {
binding.titleCountText.text = locale.recordCount(it.size)
binding.listProgressView.isVisible = false
binding.appErrorSisIcon.isVisible = it.size >= 5
binding.clearAllIcon.isVisible = it.isNotEmpty()
binding.exportAllIcon.isVisible = it.isNotEmpty()
binding.listView.isVisible = it.isNotEmpty()
binding.listNoDataView.isVisible = it.isEmpty()
listData.clear()
it.takeIf { e -> e.isNotEmpty() }?.forEach { e -> listData.add(e) }
onChanged?.invoke()
binding.appErrorSisIcon.isVisible = listData.size >= 5
binding.clearAllIcon.isVisible = listData.isNotEmpty()
binding.exportAllIcon.isVisible = listData.isNotEmpty()
binding.listView.isVisible = listData.isNotEmpty()
binding.listNoDataView.isVisible = listData.isEmpty()
}
}
/** 打包导出全部 */
private fun exportAll() {
clearAllExportTemp()
("${cacheDir.absolutePath}/temp").also { path ->
File(path).mkdirs()
listData.takeIf { it.isNotEmpty() }?.forEach {
File("$path/${it.packageName}_${it.utcTime}.log").writeText(it.stackOutputFileContent)
StackTraceShareHelper.showChoose(context = this, locale.exportAll) { sDeviceBrand, sDeviceModel, sDisplay, sPackageName ->
("${cacheDir.absolutePath}/temp").also { path ->
File(path).mkdirs()
listData.takeIf { it.isNotEmpty() }?.forEachIndexed { index, bean ->
val packageName = if (sPackageName) bean.packageName else "anonymous_$index"
File("$path/${packageName}_${bean.utcTime}.log")
.writeText(bean.stackOutputFileContent(sDeviceBrand, sDeviceModel, sDisplay, sPackageName))
}
outPutFilePath = "${cacheDir.absolutePath}/temp_${System.currentTimeMillis()}.zip"
ZipFileTool.zipMultiFile(path, outPutFilePath)
runCatching {
startActivityForResult(Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "*/application"
putExtra(Intent.EXTRA_TITLE, "app_errors_info_${System.currentTimeMillis().toUtcTime()}.zip")
}, WRITE_REQUEST_CODE)
}.onFailure { toast(msg = "Start Android SAF failed") }
}
outPutFilePath = "${cacheDir.absolutePath}/temp_${System.currentTimeMillis()}.zip"
ZipFileTool.zipMultiFile(path, outPutFilePath)
runCatching {
startActivityForResult(Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "*/application"
putExtra(Intent.EXTRA_TITLE, "app_errors_info_${System.currentTimeMillis().toUtcTime()}.zip")
}, WRITE_REQUEST_CODE)
}.onFailure { toast(msg = "Start Android SAF failed") }
}
}
@@ -193,7 +209,7 @@ class AppErrorsRecordActivity : BaseActivity<ActivityAppErrorsRecordBinding>() {
* @return [String]
*/
private fun String.simpleThwName() =
let { text -> if (text.contains(other = ".")) text.split(".").let { e -> e[e.lastIndex] } else text }
let { text -> if (text.contains(".")) text.split(".").let { e -> e[e.lastIndex] } else text }
override fun onCreateContextMenu(menu: ContextMenu?, v: View?, menuInfo: ContextMenu.ContextMenuInfo?) {
menuInflater.inflate(R.menu.menu_list_detail_action, menu)
@@ -208,8 +224,8 @@ class AppErrorsRecordActivity : BaseActivity<ActivityAppErrorsRecordBinding>() {
R.id.aerrors_app_info -> openSelfSetting(listData[it.position].packageName)
R.id.aerrors_remove_record ->
showDialog {
title = LocaleString.notice
msg = LocaleString.areYouSureRemoveRecord
title = locale.notice
msg = locale.areYouSureRemoveRecord
confirmButton { FrameworkTool.removeAppErrorsInfoData(context, listData[it.position]) { refreshData() } }
cancelButton()
}
@@ -224,9 +240,9 @@ class AppErrorsRecordActivity : BaseActivity<ActivityAppErrorsRecordBinding>() {
data?.data?.let {
contentResolver?.openOutputStream(it)?.apply { write(FileInputStream(outPutFilePath).readBytes()) }?.close()
clearAllExportTemp()
toast(LocaleString.exportAllErrorsSuccess)
} ?: toast(LocaleString.exportAllErrorsFail)
}.onFailure { toast(LocaleString.exportAllErrorsFail) }
toast(locale.exportAllErrorsSuccess)
} ?: toast(locale.exportAllErrorsFail)
}.onFailure { toast(locale.exportAllErrorsFail) }
}
override fun onResume() {

View File

@@ -1,6 +1,6 @@
/*
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* Copyright (C) 2017 Fankes Studio(qzmmcn@163.com)
* https://github.com/KitsunePie/AppErrorsTracking
*
* This software is non-free but opensource software: you can redistribute it
@@ -17,19 +17,21 @@
* and eula along with this software. If not, see
* <https://www.gnu.org/licenses/>
*
* This file is Created by fankes on 2022/6/4.
* This file is created by fankes on 2022/6/4.
*/
package com.fankes.apperrorstracking.ui.activity.main
import androidx.core.view.isVisible
import com.fankes.apperrorstracking.bean.AppFiltersBean
import com.fankes.apperrorstracking.bean.AppInfoBean
import com.fankes.apperrorstracking.data.factory.*
import com.fankes.apperrorstracking.bean.enum.AppFiltersType
import com.fankes.apperrorstracking.data.AppErrorsConfigData
import com.fankes.apperrorstracking.data.enum.AppErrorsConfigType
import com.fankes.apperrorstracking.databinding.ActivityConfigBinding
import com.fankes.apperrorstracking.databinding.AdapterAppInfoBinding
import com.fankes.apperrorstracking.databinding.DiaAppConfigBinding
import com.fankes.apperrorstracking.databinding.DiaAppsFilterBinding
import com.fankes.apperrorstracking.locale.LocaleString
import com.fankes.apperrorstracking.locale.locale
import com.fankes.apperrorstracking.ui.activity.base.BaseActivity
import com.fankes.apperrorstracking.utils.factory.appIconOf
import com.fankes.apperrorstracking.utils.factory.bindAdapter
@@ -50,36 +52,31 @@ class ConfigureActivity : BaseActivity<ActivityConfigBinding>() {
override fun onCreate() {
binding.titleBackIcon.setOnClickListener { finish() }
binding.globalIcon.setOnClickListener {
showAppConfigDialog(locale.globalConfig, isShowGlobalConfig = false) { type ->
AppErrorsConfigData.putAppShowingType(type)
onChanged?.invoke()
}
}
binding.batchIcon.setOnClickListener {
showDialog<DiaAppConfigBinding> {
title = LocaleString.batchOperations
confirmButton {
val config0 = binding.configRadio0.isChecked
val config1 = binding.configRadio1.isChecked
val config2 = binding.configRadio2.isChecked
val config3 = binding.configRadio3.isChecked
showDialog {
title = LocaleString.notice
msg = LocaleString.areYouSureApplySiteApps(listData.size)
confirmButton {
listData.takeIf { it.isNotEmpty() }?.forEach {
putAppShowErrorsDialog(it.packageName, config0)
putAppShowErrorsNotify(it.packageName, config1)
putAppShowErrorsToast(it.packageName, config2)
putAppShowNothing(it.packageName, config3)
}
onChanged?.invoke()
}
cancelButton()
showAppConfigDialog(locale.batchOperationsNumber(listData.size), isNotSetDefaultValue = true) { type ->
showDialog {
title = locale.notice
msg = locale.areYouSureApplySiteApps(listData.size)
confirmButton {
listData.takeIf { it.isNotEmpty() }?.forEach { AppErrorsConfigData.putAppShowingType(type, it.packageName) }
onChanged?.invoke()
}
cancelButton()
}
cancelButton()
}
}
binding.filterIcon.setOnClickListener {
showDialog<DiaAppsFilterBinding> {
title = LocaleString.filterByCondition
binding.containsSystemSwitch.isChecked = appFilters.isContainsSystem
title = locale.filterByCondition
binding.filtersRadioUser.isChecked = appFilters.type == AppFiltersType.USER
binding.filtersRadioSystem.isChecked = appFilters.type == AppFiltersType.SYSTEM
binding.filtersRadioAll.isChecked = appFilters.type == AppFiltersType.ALL
binding.appFiltersEdit.apply {
requestFocus()
invalidate()
@@ -88,15 +85,24 @@ class ConfigureActivity : BaseActivity<ActivityConfigBinding>() {
setSelection(appFilters.name.length)
}
}
/** 设置 [AppFiltersBean.type] */
fun setAppFiltersType() {
appFilters.type = when {
binding.filtersRadioUser.isChecked -> AppFiltersType.USER
binding.filtersRadioSystem.isChecked -> AppFiltersType.SYSTEM
binding.filtersRadioAll.isChecked -> AppFiltersType.ALL
else -> error("Invalid app filters type")
}
}
confirmButton {
appFilters.isContainsSystem = binding.containsSystemSwitch.isChecked
setAppFiltersType()
appFilters.name = binding.appFiltersEdit.text.toString().trim()
refreshData()
}
cancelButton()
if (appFilters.name.isNotBlank())
neutralButton(LocaleString.clearFilters) {
appFilters.isContainsSystem = binding.containsSystemSwitch.isChecked
neutralButton(locale.clearFilters) {
setAppFiltersType()
appFilters.name = ""
refreshData()
}
@@ -110,10 +116,11 @@ class ConfigureActivity : BaseActivity<ActivityConfigBinding>() {
binding.appIcon.setImageDrawable(bean.icon)
binding.appNameText.text = bean.name
binding.configTypeText.text = when {
isAppShowErrorsDialog(bean.packageName) -> LocaleString.showErrorsDialog
isAppShowErrorsNotify(bean.packageName) -> LocaleString.showErrorsNotify
isAppShowErrorsToast(bean.packageName) -> LocaleString.showErrorsToast
isAppShowNothing(bean.packageName) -> LocaleString.showNothing
AppErrorsConfigData.isAppShowingType(AppErrorsConfigType.GLOBAL, bean.packageName) -> locale.followGlobalConfig
AppErrorsConfigData.isAppShowingType(AppErrorsConfigType.DIALOG, bean.packageName) -> locale.showErrorsDialog
AppErrorsConfigData.isAppShowingType(AppErrorsConfigType.NOTIFY, bean.packageName) -> locale.showErrorsNotify
AppErrorsConfigData.isAppShowingType(AppErrorsConfigType.TOAST, bean.packageName) -> locale.showErrorsToast
AppErrorsConfigData.isAppShowingType(AppErrorsConfigType.NOTHING, bean.packageName) -> locale.showNothing
else -> "Unknown type"
}
}
@@ -121,20 +128,9 @@ class ConfigureActivity : BaseActivity<ActivityConfigBinding>() {
}.apply { onChanged = { notifyDataSetChanged() } }
setOnItemClickListener { _, _, p, _ ->
listData[p].also { bean ->
showDialog<DiaAppConfigBinding> {
title = bean.name
binding.configRadio0.isChecked = isAppShowErrorsDialog(bean.packageName)
binding.configRadio1.isChecked = isAppShowErrorsNotify(bean.packageName)
binding.configRadio2.isChecked = isAppShowErrorsToast(bean.packageName)
binding.configRadio3.isChecked = isAppShowNothing(bean.packageName)
confirmButton {
putAppShowErrorsDialog(bean.packageName, binding.configRadio0.isChecked)
putAppShowErrorsNotify(bean.packageName, binding.configRadio1.isChecked)
putAppShowErrorsToast(bean.packageName, binding.configRadio2.isChecked)
putAppShowNothing(bean.packageName, binding.configRadio3.isChecked)
onChanged?.invoke()
}
cancelButton()
showAppConfigDialog(bean.name, bean.packageName) { type ->
AppErrorsConfigData.putAppShowingType(type, bean.packageName)
onChanged?.invoke()
}
}
}
@@ -142,8 +138,8 @@ class ConfigureActivity : BaseActivity<ActivityConfigBinding>() {
/** 模块未完全激活将显示警告 */
if (MainActivity.isModuleValied.not())
showDialog {
title = LocaleString.notice
msg = LocaleString.moduleNotFullyActivatedTip
title = locale.notice
msg = locale.moduleNotFullyActivatedTip
confirmButton { FrameworkTool.restartSystem(context) }
cancelButton()
noCancelable()
@@ -152,14 +148,58 @@ class ConfigureActivity : BaseActivity<ActivityConfigBinding>() {
refreshData()
}
/**
* 显示应用配置对话框
* @param title 对话框标题
* @param packageName APP 包名 - 默认空 (空时使用全局配置的默认值)
* @param isNotSetDefaultValue 是否不设置选项的默认值 - 默认否
* @param isShowGlobalConfig 是否显示跟随全局配置选项 - 默认是
* @param result 回调类型结果
*/
private fun showAppConfigDialog(
title: String,
packageName: String = "",
isNotSetDefaultValue: Boolean = false,
isShowGlobalConfig: Boolean = true,
result: (AppErrorsConfigType) -> Unit
) {
showDialog<DiaAppConfigBinding> {
this.title = title
binding.configRadio0.isVisible = isShowGlobalConfig
if (isNotSetDefaultValue.not()) {
if (isShowGlobalConfig) binding.configRadio0.isChecked =
AppErrorsConfigData.isAppShowingType(AppErrorsConfigType.GLOBAL, packageName)
binding.configRadio1.isChecked = AppErrorsConfigData.isAppShowingType(AppErrorsConfigType.DIALOG, packageName)
binding.configRadio2.isChecked = AppErrorsConfigData.isAppShowingType(AppErrorsConfigType.NOTIFY, packageName)
binding.configRadio3.isChecked = AppErrorsConfigData.isAppShowingType(AppErrorsConfigType.TOAST, packageName)
binding.configRadio4.isChecked = AppErrorsConfigData.isAppShowingType(AppErrorsConfigType.NOTHING, packageName)
}
confirmButton {
result(
when {
binding.configRadio0.isChecked -> AppErrorsConfigType.GLOBAL
binding.configRadio1.isChecked -> AppErrorsConfigType.DIALOG
binding.configRadio2.isChecked -> AppErrorsConfigType.NOTIFY
binding.configRadio3.isChecked -> AppErrorsConfigType.TOAST
binding.configRadio4.isChecked -> AppErrorsConfigType.NOTHING
else -> error("Invalid config type")
}
)
FrameworkTool.refreshFrameworkPrefsData(context)
}
cancelButton()
}
}
/** 刷新列表数据 */
private fun refreshData() {
binding.listProgressView.isVisible = true
binding.globalIcon.isVisible = false
binding.batchIcon.isVisible = false
binding.filterIcon.isVisible = false
binding.listView.isVisible = false
binding.listNoDataView.isVisible = false
binding.titleCountText.text = LocaleString.loading
binding.titleCountText.text = locale.loading
FrameworkTool.fetchAppListData(context = this, appFilters) {
/** 设置一个临时变量用于更新列表数据 */
val tempsData = ArrayList<AppInfoBean>()
@@ -176,11 +216,12 @@ class ConfigureActivity : BaseActivity<ActivityConfigBinding>() {
onChanged?.invoke()
binding.listView.post { binding.listView.setSelection(0) }
binding.listProgressView.isVisible = false
binding.globalIcon.isVisible = true
binding.batchIcon.isVisible = listData.isNotEmpty()
binding.filterIcon.isVisible = true
binding.listView.isVisible = listData.isNotEmpty()
binding.listNoDataView.isVisible = listData.isEmpty()
binding.titleCountText.text = LocaleString.resultCount(listData.size)
binding.titleCountText.text = locale.resultCount(listData.size)
} else tempsData.clear()
}
}

View File

@@ -1,6 +1,6 @@
/*
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* Copyright (C) 2017 Fankes Studio(qzmmcn@163.com)
* https://github.com/KitsunePie/AppErrorsTracking
*
* This software is non-free but opensource software: you can redistribute it
@@ -17,7 +17,7 @@
* and eula along with this software. If not, see
* <https://www.gnu.org/licenses/>
*
* This file is Created by fankes on 2022/5/14.
* This file is created by fankes on 2022/5/14.
*/
@file:Suppress("SetTextI18n")
@@ -25,19 +25,28 @@ package com.fankes.apperrorstracking.ui.activity.main
import android.os.Build
import androidx.core.view.isVisible
import com.fankes.apperrorstracking.BuildConfig
import com.fankes.apperrorstracking.R
import com.fankes.apperrorstracking.const.ModuleVersion
import com.fankes.apperrorstracking.data.ConfigData
import com.fankes.apperrorstracking.data.ConfigData.bind
import com.fankes.apperrorstracking.data.factory.bind
import com.fankes.apperrorstracking.databinding.ActivityMainBinding
import com.fankes.apperrorstracking.locale.LocaleString
import com.fankes.apperrorstracking.locale.locale
import com.fankes.apperrorstracking.ui.activity.base.BaseActivity
import com.fankes.apperrorstracking.ui.activity.debug.LoggerActivity
import com.fankes.apperrorstracking.ui.activity.errors.AppErrorsMutedActivity
import com.fankes.apperrorstracking.ui.activity.errors.AppErrorsRecordActivity
import com.fankes.apperrorstracking.utils.factory.*
import com.fankes.apperrorstracking.utils.factory.hideOrShowLauncherIcon
import com.fankes.apperrorstracking.utils.factory.isLauncherIconShowing
import com.fankes.apperrorstracking.utils.factory.isSystemLanguageSimplifiedChinese
import com.fankes.apperrorstracking.utils.factory.navigate
import com.fankes.apperrorstracking.utils.factory.openBrowser
import com.fankes.apperrorstracking.utils.factory.showDialog
import com.fankes.apperrorstracking.utils.factory.toast
import com.fankes.apperrorstracking.utils.tool.AppAnalyticsTool
import com.fankes.apperrorstracking.utils.tool.AppAnalyticsTool.bindAppAnalytics
import com.fankes.apperrorstracking.utils.tool.FrameworkTool
import com.fankes.apperrorstracking.utils.tool.GithubReleaseTool
import com.fankes.projectpromote.ProjectPromote
import com.highcapable.yukihookapi.YukiHookAPI
class MainActivity : BaseActivity<ActivityMainBinding>() {
@@ -52,29 +61,60 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
}
override fun onCreate() {
binding.mainTextVersion.text = LocaleString.moduleVersion(BuildConfig.VERSION_NAME)
binding.mainTextSystemVersion.text = LocaleString.systemVersion(systemVersion)
checkingTopComponentName()
/** 检查更新 */
GithubReleaseTool.checkingForUpdate(context = this, ModuleVersion.NAME) { version, function ->
binding.mainTextReleaseVersion.apply {
text = locale.clickToUpdate(version)
isVisible = true
setOnClickListener { function() }
}
}
/** 推广、恰饭 */
if (YukiHookAPI.Status.isXposedModuleActive) ProjectPromote.show(activity = this, ModuleVersion.toString())
/** 显示开发者提示 */
if (ConfigData.isShowDeveloperNotice)
showDialog {
title = locale.developerNotice
msg = locale.developerNoticeTip
confirmButton(locale.gotIt) { ConfigData.isShowDeveloperNotice = false }
noCancelable()
}
/** 设置 CI 自动构建标识 */
if (ModuleVersion.isCiMode)
binding.mainTextReleaseVersion.apply {
text = "CI ${ModuleVersion.GITHUB_COMMIT_ID}"
isVisible = true
setOnClickListener {
showDialog {
title = locale.ciNoticeDialogTitle
msg = locale.ciNoticeDialogContent(ModuleVersion.GITHUB_COMMIT_ID)
confirmButton(locale.gotIt)
noCancelable()
}
}
}
binding.mainTextVersion.text = locale.moduleVersion(ModuleVersion.NAME)
binding.mainTextSystemVersion.text = locale.systemVersion(systemVersion)
binding.onlyShowErrorsInFrontSwitch.bind(ConfigData.ENABLE_ONLY_SHOW_ERRORS_IN_FRONT)
binding.onlyShowErrorsInMainProcessSwitch.bind(ConfigData.ENABLE_ONLY_SHOW_ERRORS_IN_MAIN)
binding.alwaysShowsReopenAppOptionsSwitch.bind(ConfigData.ENABLE_ALWAYS_SHOWS_REOPEN_APP_OPTIONS)
binding.shareWithFile.bind(ConfigData.SHARE_WITH_FILE)
binding.enableAppsConfigsTemplateSwitch.bind(ConfigData.ENABLE_APP_CONFIG_TEMPLATE) {
binding.mgrAppsConfigsTemplateButton.isVisible = it
onInitialize { binding.mgrAppsConfigsTemplateButton.isVisible = it }
onChanged { reinitialize() }
}
binding.errorsDialogPreventMisoperationSwitch.bind(ConfigData.ENABLE_PREVENT_MISOPERATION_FOR_DIALOG)
binding.enableMaterial3AppErrorsDialogSwitch.bind(ConfigData.ENABLE_MATERIAL3_STYLE_APP_ERRORS_DIALOG)
/** 设置桌面图标显示隐藏 */
binding.hideIconInLauncherSwitch.isChecked = isLauncherIconShowing.not()
binding.hideIconInLauncherSwitch.setOnCheckedChangeListener { btn, b ->
if (btn.isPressed.not()) return@setOnCheckedChangeListener
hideOrShowLauncherIcon(b)
}
/** 设置匿名统计 */
binding.appAnalyticsConfigItem.isVisible = AppAnalyticsTool.isAvailable
binding.enableAnonymousStatisticsSwitch.bindAppAnalytics()
/** 系统版本点击事件 */
binding.mainTextSystemVersion.setOnClickListener {
showDialog {
title = LocaleString.notice
title = locale.notice
msg = systemVersion
confirmButton(LocaleString.gotIt)
confirmButton(locale.gotIt)
}
}
/** 管理应用配置模板按钮点击事件 */
@@ -88,14 +128,17 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
binding.titleRestartIcon.setOnClickListener { FrameworkTool.restartSystem(context = this) }
/** 项目地址按钮点击事件 */
binding.titleGithubIcon.setOnClickListener { openBrowser(url = "https://github.com/KitsunePie/AppErrorsTracking") }
/** 显示开发者提示 */
if (ConfigData.isShowDeveloperNotice)
showDialog {
title = LocaleString.developerNotice
msg = LocaleString.developerNoticeTip
confirmButton(LocaleString.gotIt) { ConfigData.isShowDeveloperNotice = false }
noCancelable()
}
/** 恰饭! */
binding.paymentFollowingZhCnItem.isVisible = isSystemLanguageSimplifiedChinese
binding.linkWithFollowMe.setOnClickListener {
openBrowser(url = "https://www.coolapk.com/u/876977", packageName = "com.coolapk.market")
}
/** 设置桌面图标显示隐藏 */
binding.hideIconInLauncherSwitch.isChecked = isLauncherIconShowing.not()
binding.hideIconInLauncherSwitch.setOnCheckedChangeListener { btn, b ->
if (btn.isPressed.not()) return@setOnCheckedChangeListener
hideOrShowLauncherIcon(b)
}
}
/** 刷新模块状态 */
@@ -113,14 +156,13 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
else -> R.drawable.ic_warn
}
)
binding.mainTextStatus.text =
when {
YukiHookAPI.Status.isXposedModuleActive && isModuleValied.not() -> LocaleString.moduleNotFullyActivated
YukiHookAPI.Status.isXposedModuleActive -> LocaleString.moduleIsActivated
else -> LocaleString.moduleNotActivated
}
binding.mainTextStatus.text = when {
YukiHookAPI.Status.isXposedModuleActive && isModuleValied.not() -> locale.moduleNotFullyActivated
YukiHookAPI.Status.isXposedModuleActive -> locale.moduleIsActivated
else -> locale.moduleNotActivated
}
binding.mainTextApiWay.isVisible = YukiHookAPI.Status.isXposedModuleActive
binding.mainTextApiWay.text = "Activated by ${YukiHookAPI.Status.executorName} API ${YukiHookAPI.Status.executorVersion}"
binding.mainTextApiWay.text = "Activated by ${YukiHookAPI.Status.Executor.name} API ${YukiHookAPI.Status.Executor.apiLevel}"
}
/**
@@ -128,7 +170,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
* @param callback 激活后回调
*/
private inline fun whenActivated(callback: () -> Unit) {
if (YukiHookAPI.Status.isXposedModuleActive) callback() else toast(LocaleString.moduleNotActivated)
if (YukiHookAPI.Status.isXposedModuleActive) callback() else toast(locale.moduleNotActivated)
}
override fun onResume() {

View File

@@ -1,6 +1,6 @@
/*
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* Copyright (C) 2017 Fankes Studio(qzmmcn@163.com)
* https://github.com/KitsunePie/AppErrorsTracking
*
* This software is non-free but opensource software: you can redistribute it
@@ -17,9 +17,9 @@
* and eula along with this software. If not, see
* <https://www.gnu.org/licenses/>
*
* This file is Created by fankes on 2022/6/1.
* This file is created by fankes on 2022/6/1.
*/
package com.fankes.apperrorstracking.ui.view
package com.fankes.apperrorstracking.ui.widget
import android.content.Context
import android.util.AttributeSet

View File

@@ -1,6 +1,6 @@
/*
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* Copyright (C) 2017 Fankes Studio(qzmmcn@163.com)
* https://github.com/KitsunePie/AppErrorsTracking
*
* This software is non-free but opensource software: you can redistribute it
@@ -17,11 +17,11 @@
* and eula along with this software. If not, see
* <https://www.gnu.org/licenses/>
*
* This file is Created by fankes on 2022/5/14.
* This file is created by fankes on 2022/5/14.
*/
@file:Suppress("SameParameterValue")
package com.fankes.apperrorstracking.ui.view
package com.fankes.apperrorstracking.ui.widget
import android.content.Context
import android.content.res.ColorStateList

View File

@@ -1,6 +1,6 @@
/*
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* Copyright (C) 2017 Fankes Studio(qzmmcn@163.com)
* https://github.com/KitsunePie/AppErrorsTracking
*
* This software is non-free but opensource software: you can redistribute it
@@ -17,8 +17,10 @@
* and eula along with this software. If not, see
* <https://www.gnu.org/licenses/>
*
* This file is Created by fankes on 2022/6/3.
* This file is created by fankes on 2022/6/3.
*/
@file:Suppress("DEPRECATION")
package com.fankes.apperrorstracking.utils.factory
import android.content.Context

View File

@@ -1,6 +1,6 @@
/*
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* Copyright (C) 2017 Fankes Studio(qzmmcn@163.com)
* https://github.com/KitsunePie/AppErrorsTracking
*
* This software is non-free but opensource software: you can redistribute it
@@ -17,28 +17,32 @@
* and eula along with this software. If not, see
* <https://www.gnu.org/licenses/>
*
* This file is Created by fankes on 2022/5/12.
* This file is created by fankes on 2022/5/12.
*/
@file:Suppress("unused", "DEPRECATION", "OPT_IN_USAGE", "EXPERIMENTAL_API_USAGE")
@file:Suppress("unused", "DEPRECATION")
package com.fankes.apperrorstracking.utils.factory
import android.app.Dialog
import android.content.Context
import android.os.Handler
import android.os.Looper
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import android.widget.LinearLayout
import android.widget.ProgressBar
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import androidx.core.os.postDelayed
import androidx.viewbinding.ViewBinding
import com.fankes.apperrorstracking.locale.LocaleString
import com.fankes.apperrorstracking.data.ConfigData
import com.fankes.apperrorstracking.locale.locale
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.progressindicator.CircularProgressIndicator
import com.google.android.material.shape.MaterialShapeDrawable
import com.highcapable.yukihookapi.YukiHookAPI
import com.highcapable.yukihookapi.annotation.CauseProblemsApi
import com.highcapable.yukihookapi.hook.factory.method
import com.highcapable.yukihookapi.hook.type.android.LayoutInflaterClass
@@ -83,6 +87,8 @@ class DialogBuilder<VB : ViewBinding>(
/** 自定义布局 */
private var customLayoutView: View? = null
private val mainHandler = Handler(Looper.getMainLooper())
/**
* 获取 [DialogBuilder] 绑定布局对象
* @return [VB]
@@ -131,7 +137,10 @@ class DialogBuilder<VB : ViewBinding>(
customLayoutView = LinearLayout(context).apply {
orientation = LinearLayout.HORIZONTAL
gravity = Gravity.CENTER or Gravity.START
addView(ProgressBar(context))
addView(CircularProgressIndicator(context).apply {
isIndeterminate = true
trackCornerRadius = 10.dp(context)
})
addView(View(context).apply { layoutParams = ViewGroup.LayoutParams(20.dp(context), 5) })
addView(TextView(context).apply {
tag = "progressContent"
@@ -147,7 +156,7 @@ class DialogBuilder<VB : ViewBinding>(
* @param text 按钮文本内容
* @param callback 点击事件
*/
fun confirmButton(text: String = LocaleString.confirm, callback: () -> Unit = {}) {
fun confirmButton(text: String = locale.confirm, callback: () -> Unit = {}) {
instance?.setPositiveButton(text) { _, _ -> callback() }
}
@@ -156,7 +165,7 @@ class DialogBuilder<VB : ViewBinding>(
* @param text 按钮文本内容
* @param callback 点击事件
*/
fun cancelButton(text: String = LocaleString.cancel, callback: () -> Unit = {}) {
fun cancelButton(text: String = locale.cancel, callback: () -> Unit = {}) {
instance?.setNegativeButton(text) { _, _ -> callback() }
}
@@ -165,7 +174,7 @@ class DialogBuilder<VB : ViewBinding>(
* @param text 按钮文本内容
* @param callback 点击事件
*/
fun neutralButton(text: String = LocaleString.more, callback: () -> Unit = {}) {
fun neutralButton(text: String = locale.more, callback: () -> Unit = {}) {
instance?.setNeutralButton(text) { _, _ -> callback() }
}
@@ -181,7 +190,6 @@ class DialogBuilder<VB : ViewBinding>(
fun cancel() = dialogInstance?.cancel()
/** 显示对话框 */
@CauseProblemsApi
fun show() {
/** 若当前自定义 View 的对话框没有调用 [binding] 将会对其手动调用一次以确保显示布局 */
if (bindingClass != null) binding
@@ -190,6 +198,16 @@ class DialogBuilder<VB : ViewBinding>(
customLayoutView?.let { setView(it) }
dialogInstance = this
setOnCancelListener { onCancel?.invoke() }
if (ConfigData.isEnablePreventMisoperation) {
setOnShowListener {
window?.run {
addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE)
mainHandler.postDelayed(1000) {
clearFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE)
}
}
}
}
}?.show()
}
}

View File

@@ -1,6 +1,6 @@
/*
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* Copyright (C) 2017 Fankes Studio(qzmmcn@163.com)
* https://github.com/KitsunePie/AppErrorsTracking
*
* This software is non-free but opensource software: you can redistribute it
@@ -17,14 +17,22 @@
* and eula along with this software. If not, see
* <https://www.gnu.org/licenses/>
*
* This file is Created by fankes on 2022/5/7.
* This file is created by fankes on 2022/5/7.
*/
@file:Suppress("unused")
@file:Suppress("unused", "NotificationPermission", "DEPRECATION")
package com.fankes.apperrorstracking.utils.factory
import android.app.*
import android.content.*
import android.app.Activity
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.app.Service
import android.content.ClipData
import android.content.ClipboardManager
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.content.pm.PackageManager.PackageInfoFlags
@@ -43,12 +51,13 @@ import androidx.core.content.getSystemService
import androidx.core.content.pm.PackageInfoCompat
import androidx.core.content.res.ResourcesCompat
import androidx.core.graphics.drawable.IconCompat
import com.fankes.apperrorstracking.BuildConfig
import com.fankes.apperrorstracking.R
import com.fankes.apperrorstracking.locale.LocaleString
import com.fankes.apperrorstracking.locale.locale
import com.fankes.apperrorstracking.wrapper.BuildConfigWrapper
import com.google.android.material.snackbar.Snackbar
import com.highcapable.yukihookapi.hook.factory.field
import com.highcapable.yukihookapi.hook.factory.method
import com.highcapable.yukihookapi.hook.log.YLog
import com.highcapable.yukihookapi.hook.type.android.ApplicationInfoClass
import com.highcapable.yukihookapi.hook.type.android.ContextClass
import com.highcapable.yukihookapi.hook.type.android.IntentClass
@@ -58,7 +67,18 @@ import java.io.Serializable
import java.math.RoundingMode
import java.text.DecimalFormat
import java.text.SimpleDateFormat
import java.util.*
import java.util.Date
import java.util.Locale
/**
* 当前系统环境是否为简体中文
* @return [Boolean]
*/
val isSystemLanguageSimplifiedChinese
get(): Boolean {
val locale = Locale.getDefault()
return locale.language == "zh" && locale.country == "CN"
}
/**
* 系统深色模式是否开启
@@ -107,7 +127,7 @@ fun Resources.colorOf(@ColorRes resId: Int) = ResourcesCompat.getColor(this, res
* @return [PackageInfo] or null
*/
private fun Context.getPackageInfoCompat(packageName: String, flag: Number = 0) = runCatching {
@Suppress("DEPRECATION")
@Suppress("DEPRECATION", "KotlinRedundantDiagnosticSuppress")
if (Build.VERSION.SDK_INT >= 33)
packageManager?.getPackageInfo(packageName, PackageInfoFlags.of(flag.toLong()))
else packageManager?.getPackageInfo(packageName, flag.toInt())
@@ -124,7 +144,7 @@ private val PackageInfo.versionCodeCompat get() = PackageInfoCompat.getLongVersi
* @return [List]<[PackageInfo]>
*/
fun Context.listOfPackages() = runCatching {
@Suppress("DEPRECATION")
@Suppress("DEPRECATION", "KotlinRedundantDiagnosticSuppress")
if (Build.VERSION.SDK_INT >= 33)
packageManager?.getInstalledPackages(PackageInfoFlags.of(PackageManager.GET_CONFIGURATIONS.toLong()))
else packageManager?.getInstalledPackages(PackageManager.GET_CONFIGURATIONS)
@@ -133,7 +153,7 @@ fun Context.listOfPackages() = runCatching {
/**
* 得到 APP 名称
* @param packageName APP 包名 - 默认为当前 APP
* @return [String]
* @return [String] 无法获取时返回 ""
*/
fun Context.appNameOf(packageName: String = getPackageName()) =
getPackageInfoCompat(packageName)?.applicationInfo?.loadLabel(packageManager)?.toString() ?: ""
@@ -141,15 +161,43 @@ fun Context.appNameOf(packageName: String = getPackageName()) =
/**
* 得到 APP 版本信息与版本号
* @param packageName APP 包名 - 默认为当前 APP
* @return [String] 无法获取时返回 "unknown"
* @return [String] 无法获取时返回 ""
*/
fun Context.appVersionBrandOf(packageName: String = getPackageName()) =
getPackageInfoCompat(packageName)?.let { "${it.versionName}(${it.versionCodeCompat})" } ?: "unknown"
if (appVersionNameOf(packageName).isNotBlank()) "${appVersionNameOf(packageName)}(${appVersionCodeOf(packageName)})" else ""
/**
* 得到 APP 版本名称
* @param packageName APP 包名 - 默认为当前 APP
* @return [String] 无法获取时返回 ""
*/
fun Context.appVersionNameOf(packageName: String = getPackageName()) = getPackageInfoCompat(packageName)?.versionName ?: ""
/**
* 得到 APP 版本号
* @param packageName APP 包名 - 默认为当前 APP
* @return [Long] 无法获取时返回 -1
*/
fun Context.appVersionCodeOf(packageName: String = getPackageName()) = getPackageInfoCompat(packageName)?.versionCodeCompat ?: -1L
/**
* 得到 APP 目标 SDK 版本
* @param packageName APP 包名 - 默认为当前 APP
* @return [Int] 无法获取时返回 -1
*/
fun Context.appTargetSdkOf(packageName: String = getPackageName()) = getPackageInfoCompat(packageName)?.applicationInfo?.targetSdkVersion ?: -1
/**
* 得到 APP 最低 SDK 版本
* @param packageName APP 包名 - 默认为当前 APP
* @return [Int] 无法获取时返回 -1
*/
fun Context.appMinSdkOf(packageName: String = getPackageName()) = getPackageInfoCompat(packageName)?.applicationInfo?.minSdkVersion ?: -1
/**
* 获取 APP CPU ABI 名称
* @param packageName APP 包名 - 默认为当前 APP
* @return [String]
* @return [String] 无法获取时返回 ""
*/
fun Context.appCpuAbiOf(packageName: String = getPackageName()) = runCatching {
ApplicationInfoClass.field { name = "primaryCpuAbi" }.get(getPackageInfoCompat(packageName)?.applicationInfo).string()
@@ -175,6 +223,12 @@ inline fun <reified T : Serializable> Intent.getSerializableExtraCompat(key: Str
else getSerializableExtra(key) as? T?
}
/**
* [List]<[T]> 转换为 [ArrayList]<[T]>
* @return [ArrayList]<[T]>
*/
fun <T> List<T>.toArrayList() = toMutableList() as ArrayList<T>
/**
* 计算与当前时间戳相差的友好时间
* @param now 刚刚
@@ -234,7 +288,11 @@ fun Long.toUtcTime() = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS", Locale.ROOT
* 弹出 [Toast]
* @param msg 提示内容
*/
fun Context.toast(msg: String) = Toast.makeText(this, msg, Toast.LENGTH_SHORT).show()
fun Context.toast(msg: String) {
runCatching {
Toast.makeText(this, msg, Toast.LENGTH_SHORT).show()
}.onFailure { YLog.warn(msg) }
}
/**
* 弹出 [Snackbar]
@@ -261,7 +319,8 @@ fun Context.snake(msg: String, actionText: String = "", callback: () -> Unit = {
*/
fun Context.pushNotify(channelId: String, channelName: String, title: String, content: String, icon: IconCompat, color: Int, intent: Intent) {
getSystemService<NotificationManager>()?.apply {
createNotificationChannel(NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_HIGH))
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
createNotificationChannel(NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_HIGH))
notify((0..999).random(), NotificationCompat.Builder(this@pushNotify, channelId).apply {
this.color = color
setAutoCancel(true)
@@ -285,7 +344,7 @@ inline fun <reified T : Activity> Context.navigate(isOutSide: Boolean = false, i
startActivity((if (isOutSide) Intent() else Intent(if (this is Service) applicationContext else this, T::class.java)).apply {
flags = if (this@navigate !is Activity) Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
else Intent.FLAG_ACTIVITY_NEW_TASK
if (isOutSide) component = ComponentName(BuildConfig.APPLICATION_ID, T::class.java.name)
if (isOutSide) component = ComponentName(BuildConfigWrapper.APPLICATION_ID, T::class.java.name)
initiate(this)
})
}.onFailure { toast(msg = "Start ${T::class.java.name} failed") }
@@ -298,7 +357,7 @@ fun Context.copyToClipboard(content: String) = runCatching {
(getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager).apply {
setPrimaryClip(ClipData.newPlainText(null, content))
(primaryClip?.getItemAt(0)?.text ?: "").also {
if (it != content) toast(LocaleString.copyFail) else toast(LocaleString.copied)
if (it != content) toast(locale.copyFail) else toast(locale.copied)
}
}
}
@@ -356,7 +415,10 @@ fun Context.openApp(packageName: String = getPackageName(), userId: Int = 0) = r
* 是否有 Root 权限
* @return [Boolean]
*/
val isRootAccess get() = runCatching { Shell.rootAccess() }.getOrNull() ?: false
val isRootAccess get() = runCatching {
@Suppress("DEPRECATION")
Shell.rootAccess()
}.getOrNull() ?: false
/**
* 执行命令
@@ -365,6 +427,7 @@ val isRootAccess get() = runCatching { Shell.rootAccess() }.getOrNull() ?: false
* @return [String] 执行结果
*/
fun execShell(cmd: String, isSu: Boolean = true) = runCatching {
@Suppress("DEPRECATION")
(if (isSu) Shell.su(cmd) else Shell.sh(cmd)).exec().out.let {
if (it.isNotEmpty()) it[0].trim() else ""
}
@@ -378,7 +441,7 @@ fun execShell(cmd: String, isSu: Boolean = true) = runCatching {
*/
fun Context.hideOrShowLauncherIcon(isShow: Boolean) {
packageManager?.setComponentEnabledSetting(
ComponentName(packageName, "${BuildConfig.APPLICATION_ID}.Home"),
ComponentName(packageName, "${BuildConfigWrapper.APPLICATION_ID}.Home"),
if (isShow) PackageManager.COMPONENT_ENABLED_STATE_DISABLED else PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
PackageManager.DONT_KILL_APP
)
@@ -390,5 +453,5 @@ fun Context.hideOrShowLauncherIcon(isShow: Boolean) {
*/
val Context.isLauncherIconShowing
get() = packageManager?.getComponentEnabledSetting(
ComponentName(packageName, "${BuildConfig.APPLICATION_ID}.Home")
ComponentName(packageName, "${BuildConfigWrapper.APPLICATION_ID}.Home")
) != PackageManager.COMPONENT_ENABLED_STATE_DISABLED

View File

@@ -1,6 +1,6 @@
/*
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* Copyright (C) 2017 Fankes Studio(qzmmcn@163.com)
* https://github.com/KitsunePie/AppErrorsTracking
*
* This software is non-free but opensource software: you can redistribute it
@@ -17,12 +17,14 @@
* and eula along with this software. If not, see
* <https://www.gnu.org/licenses/>
*
* This file is Created by fankes on 2022/10/3.
* This file is created by fankes on 2022/10/3.
*/
package com.fankes.apperrorstracking.utils.factory
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.JsonIOException
import com.google.gson.JsonSyntaxException
import com.google.gson.reflect.TypeToken
/**
@@ -34,11 +36,25 @@ val GSON by lazy { GsonBuilder().setLenient().create() ?: error("Gson create fai
/**
* 实体类转 Json 字符串
* @return [String]
* @throws [JsonIOException] 如果不是有效的 Json 数据
*/
fun Any?.toJson() = GSON.toJson(this) ?: ""
/**
* 实体类转 Json 字符串
* @return [String] or null
*/
fun Any?.toJsonOrNull() = runCatching { toJson() }.getOrNull()
/**
* Json 字符串转实体类
* @return [T] or null
* @throws [JsonSyntaxException] 如果 Json 格式不正确
*/
inline fun <reified T> String.toEntity(): T? = takeIf { it.isNotBlank() }.let { GSON.fromJson(this, object : TypeToken<T>() {}.type) }
/**
* Json 字符串转实体类
* @return [T] or null
*/
inline fun <reified T> String.toEntity(): T? = takeIf { it.isNotBlank() }.let { GSON.fromJson(this, object : TypeToken<T>() {}.type) }
inline fun <reified T> String.toEntityOrNull(): T? = runCatching { toEntity<T?>() }.getOrNull()

View File

@@ -1,6 +1,6 @@
/*
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* Copyright (C) 2017 Fankes Studio(qzmmcn@163.com)
* https://github.com/KitsunePie/AppErrorsTracking
*
* This software is non-free but opensource software: you can redistribute it
@@ -17,7 +17,7 @@
* and eula along with this software. If not, see
* <https://www.gnu.org/licenses/>
*
* This file is Created by fankes on 2022/10/3.
* This file is created by fankes on 2022/10/3.
*/
package com.fankes.apperrorstracking.utils.factory

View File

@@ -1,6 +1,6 @@
/*
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* Copyright (C) 2017 Fankes Studio(qzmmcn@163.com)
* https://github.com/KitsunePie/AppErrorsTracking
*
* This software is non-free but opensource software: you can redistribute it
@@ -17,7 +17,7 @@
* and eula along with this software. If not, see
* <https://www.gnu.org/licenses/>
*
* This file is Created by fankes on 2022/10/5.
* This file is created by fankes on 2022/10/5.
*/
@file:Suppress("unused")
@@ -25,8 +25,8 @@ package com.fankes.apperrorstracking.utils.tool
import android.app.Application
import android.widget.CompoundButton
import com.fankes.apperrorstracking.BuildConfig
import com.highcapable.yukihookapi.hook.factory.modulePrefs
import com.fankes.apperrorstracking.generated.ModuleAppProperties
import com.highcapable.yukihookapi.hook.factory.prefs
import com.highcapable.yukihookapi.hook.xposed.prefs.data.PrefsData
import com.microsoft.appcenter.AppCenter
import com.microsoft.appcenter.analytics.Analytics
@@ -38,7 +38,7 @@ import com.microsoft.appcenter.crashes.Crashes
object AppAnalyticsTool {
/** App Secret */
private const val APP_CENTER_SECRET = BuildConfig.APP_CENTER_SECRET
private const val APP_CENTER_SECRET = ModuleAppProperties.APP_CENTER_SECRET
/** 启用匿名统计收集使用情况功能 */
private val ENABLE_APP_CENTER_ANALYTICS = PrefsData("_enable_app_center_analytics", true)
@@ -51,11 +51,14 @@ object AppAnalyticsTool {
* @return [Boolean]
*/
private var isEnableAppCenterAnalytics
get() = instance?.modulePrefs?.get(ENABLE_APP_CENTER_ANALYTICS) ?: true
get() = instance?.prefs()?.get(ENABLE_APP_CENTER_ANALYTICS) ?: true
set(value) {
instance?.modulePrefs?.put(ENABLE_APP_CENTER_ANALYTICS, value)
instance?.prefs()?.edit { put(ENABLE_APP_CENTER_ANALYTICS, value) }
}
/** 是否可用 */
val isAvailable = APP_CENTER_SECRET.isNotBlank()
/** 绑定到 [CompoundButton] 自动设置选中状态 */
fun CompoundButton.bindAppAnalytics() {
isChecked = isEnableAppCenterAnalytics
@@ -81,7 +84,7 @@ object AppAnalyticsTool {
*/
fun init(instance: Application) {
this.instance = instance
if (isEnableAppCenterAnalytics && APP_CENTER_SECRET.isNotBlank())
if (isEnableAppCenterAnalytics && isAvailable)
AppCenter.start(instance, APP_CENTER_SECRET, Analytics::class.java, Crashes::class.java)
}
}

View File

@@ -1,6 +1,6 @@
/*
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* Copyright (C) 2017 Fankes Studio(qzmmcn@163.com)
* https://github.com/KitsunePie/AppErrorsTracking
*
* This software is non-free but opensource software: you can redistribute it
@@ -17,10 +17,8 @@
* and eula along with this software. If not, see
* <https://www.gnu.org/licenses/>
*
* This file is Created by fankes on 2022/5/12.
* This file is created by fankes on 2022/5/12.
*/
@file:Suppress("UNCHECKED_CAST")
package com.fankes.apperrorstracking.utils.tool
import android.content.Context
@@ -28,11 +26,11 @@ import com.fankes.apperrorstracking.bean.AppErrorsInfoBean
import com.fankes.apperrorstracking.bean.AppFiltersBean
import com.fankes.apperrorstracking.bean.AppInfoBean
import com.fankes.apperrorstracking.bean.MutedErrorsAppBean
import com.fankes.apperrorstracking.locale.LocaleString
import com.fankes.apperrorstracking.const.PackageName
import com.fankes.apperrorstracking.locale.locale
import com.fankes.apperrorstracking.utils.factory.execShell
import com.fankes.apperrorstracking.utils.factory.isRootAccess
import com.fankes.apperrorstracking.utils.factory.showDialog
import com.fankes.apperrorstracking.utils.factory.snake
import com.highcapable.yukihookapi.hook.factory.dataChannel
import com.highcapable.yukihookapi.hook.param.PackageParam
import com.highcapable.yukihookapi.hook.xposed.channel.data.ChannelData
@@ -42,9 +40,7 @@ import com.highcapable.yukihookapi.hook.xposed.channel.data.ChannelData
*/
object FrameworkTool {
/** 系统框架包名 */
const val SYSTEM_FRAMEWORK_NAME = "android"
private const val CALL_REFRESH_HOST_PREFS_DATA = "call_refresh_host_prefs_data"
private const val CALL_APP_ERRORS_DATA_GET = "call_app_errors_data_get"
private const val CALL_MUTED_ERRORS_APP_DATA_GET = "call_muted_app_errors_data_get"
private const val CALL_APP_ERRORS_DATA_CLEAR = "call_app_errors_data_clear"
@@ -84,6 +80,12 @@ object FrameworkTool {
*/
fun with(instance: PackageParam, initiate: Host.() -> Unit) = apply { this.instance = instance }.apply(initiate)
/**
* 通知系统框架刷新存储的数据
* @param callback 回调
*/
fun onRefreshFrameworkPrefsData(callback: () -> Unit) = instance?.dataChannel?.wait(CALL_REFRESH_HOST_PREFS_DATA) { callback() }
/**
* 监听使用系统框架打开 APP
* @param result 回调包名和用户 ID
@@ -205,36 +207,51 @@ object FrameworkTool {
* 重启系统
* @param context 实例
*/
fun restartSystem(context: Context) =
fun restartSystem(context: Context) {
/** 当 Root 权限获取失败时显示对话框 */
fun showWhenAccessRootFail() =
context.showDialog {
title = locale.accessRootFail
msg = locale.accessRootFailTip
confirmButton(locale.gotIt)
}
context.showDialog {
title = LocaleString.notice
msg = LocaleString.areYouSureRestartSystem
title = locale.notice
msg = locale.areYourSureRestartSystem
confirmButton {
if (isRootAccess)
execShell(cmd = "reboot")
else context.snake(LocaleString.accessRootFail)
else showWhenAccessRootFail()
}
neutralButton(LocaleString.fastRestart) {
neutralButton(locale.fastRestart) {
context.showDialog {
title = LocaleString.warning
msg = LocaleString.fastRestartProblem
title = locale.warning
msg = locale.fastRestartProblem
confirmButton {
if (isRootAccess)
execShell(cmd = "killall zygote")
else context.snake(LocaleString.accessRootFail)
else showWhenAccessRootFail()
}
cancelButton()
}
}
cancelButton()
}
}
/**
* 检查模块是否激活
* @param context 实例
* @param result 成功后回调
*/
fun checkingActivated(context: Context, result: (Boolean) -> Unit) = context.dataChannel(SYSTEM_FRAMEWORK_NAME).checkingVersionEquals(result)
fun checkingActivated(context: Context, result: (Boolean) -> Unit) =
context.dataChannel(PackageName.SYSTEM_FRAMEWORK).checkingVersionEquals(result = result)
/**
* 通知系统框架刷新存储的数据
* @param context 实例
*/
fun refreshFrameworkPrefsData(context: Context) = context.dataChannel(PackageName.SYSTEM_FRAMEWORK).put(CALL_REFRESH_HOST_PREFS_DATA)
/**
* 使用系统框架打开 [packageName]
@@ -243,7 +260,7 @@ object FrameworkTool {
* @param userId APP 用户 ID
*/
fun openAppUsedFramework(context: Context, packageName: String, userId: Int) =
context.dataChannel(SYSTEM_FRAMEWORK_NAME).put(CALL_OPEN_SPECIFY_APP, Pair(packageName, userId))
context.dataChannel(PackageName.SYSTEM_FRAMEWORK).put(CALL_OPEN_SPECIFY_APP, Pair(packageName, userId))
/**
* 获取指定 APP 异常信息
@@ -252,7 +269,7 @@ object FrameworkTool {
* @param result 回调数据
*/
fun fetchAppErrorInfoData(context: Context, pid: Int, result: (AppErrorsInfoBean) -> Unit) {
context.dataChannel(SYSTEM_FRAMEWORK_NAME).with {
context.dataChannel(PackageName.SYSTEM_FRAMEWORK).with {
wait(CALL_APP_ERROR_DATA_GET_RESULT) { result(it) }
put(CALL_APP_ERROR_DATA_GET, pid)
}
@@ -264,7 +281,7 @@ object FrameworkTool {
* @param result 回调数据
*/
fun fetchAppErrorsInfoData(context: Context, result: (ArrayList<AppErrorsInfoBean>) -> Unit) {
context.dataChannel(SYSTEM_FRAMEWORK_NAME).with {
context.dataChannel(PackageName.SYSTEM_FRAMEWORK).with {
wait(CALL_APP_ERRORS_DATA_GET_RESULT) { result(it) }
put(CALL_APP_ERRORS_DATA_GET)
}
@@ -277,7 +294,7 @@ object FrameworkTool {
* @param callback 成功后回调
*/
fun removeAppErrorsInfoData(context: Context, appErrorsInfo: AppErrorsInfoBean, callback: () -> Unit) {
context.dataChannel(SYSTEM_FRAMEWORK_NAME).with {
context.dataChannel(PackageName.SYSTEM_FRAMEWORK).with {
wait(CALL_APP_ERRORS_DATA_REMOVE_RESULT) { callback() }
put(CALL_APP_ERRORS_DATA_REMOVE, appErrorsInfo)
}
@@ -289,7 +306,7 @@ object FrameworkTool {
* @param callback 成功后回调
*/
fun clearAppErrorsInfoData(context: Context, callback: () -> Unit) {
context.dataChannel(SYSTEM_FRAMEWORK_NAME).with {
context.dataChannel(PackageName.SYSTEM_FRAMEWORK).with {
wait(CALL_APP_ERRORS_DATA_CLEAR_RESULT) { callback() }
put(CALL_APP_ERRORS_DATA_CLEAR)
}
@@ -302,7 +319,7 @@ object FrameworkTool {
* @param callback 成功后回调
*/
fun mutedErrorsIfUnlock(context: Context, packageName: String, callback: () -> Unit) {
context.dataChannel(SYSTEM_FRAMEWORK_NAME).with {
context.dataChannel(PackageName.SYSTEM_FRAMEWORK).with {
wait(CALL_MUTED_ERRORS_IF_UNLOCK_RESULT) { callback() }
put(CALL_MUTED_ERRORS_IF_UNLOCK, packageName)
}
@@ -315,7 +332,7 @@ object FrameworkTool {
* @param callback 成功后回调
*/
fun mutedErrorsIfRestart(context: Context, packageName: String, callback: () -> Unit) {
context.dataChannel(SYSTEM_FRAMEWORK_NAME).with {
context.dataChannel(PackageName.SYSTEM_FRAMEWORK).with {
wait(CALL_MUTED_ERRORS_IF_RESTART_RESULT) { callback() }
put(CALL_MUTED_ERRORS_IF_RESTART, packageName)
}
@@ -327,7 +344,7 @@ object FrameworkTool {
* @param result 回调数据
*/
fun fetchMutedErrorsAppsData(context: Context, result: (ArrayList<MutedErrorsAppBean>) -> Unit) {
context.dataChannel(SYSTEM_FRAMEWORK_NAME).with {
context.dataChannel(PackageName.SYSTEM_FRAMEWORK).with {
wait(CALL_MUTED_ERRORS_APP_DATA_GET_RESULT) { result(it) }
put(CALL_MUTED_ERRORS_APP_DATA_GET)
}
@@ -340,7 +357,7 @@ object FrameworkTool {
* @param callback 成功后回调
*/
fun unmuteErrorsApp(context: Context, mutedErrorsApp: MutedErrorsAppBean, callback: () -> Unit) {
context.dataChannel(SYSTEM_FRAMEWORK_NAME).with {
context.dataChannel(PackageName.SYSTEM_FRAMEWORK).with {
wait(CALL_UNMUTE_ERRORS_APP_DATA_RESULT) { callback() }
put(CALL_UNMUTE_ERRORS_APP_DATA, mutedErrorsApp)
}
@@ -352,7 +369,7 @@ object FrameworkTool {
* @param callback 成功后回调
*/
fun unmuteAllErrorsApps(context: Context, callback: () -> Unit) {
context.dataChannel(SYSTEM_FRAMEWORK_NAME).with {
context.dataChannel(PackageName.SYSTEM_FRAMEWORK).with {
wait(CALL_UNMUTE_ALL_ERRORS_APPS_DATA_RESULT) { callback() }
put(CALL_UNMUTE_ALL_ERRORS_APPS_DATA)
}
@@ -365,7 +382,7 @@ object FrameworkTool {
* @param result 回调数据
*/
fun fetchAppListData(context: Context, appFilters: AppFiltersBean, result: (ArrayList<AppInfoBean>) -> Unit) {
context.dataChannel(SYSTEM_FRAMEWORK_NAME).with {
context.dataChannel(PackageName.SYSTEM_FRAMEWORK).with {
wait(CALL_APP_LIST_DATA_GET_RESULT) { result(it) }
put(CALL_APP_LIST_DATA_GET, appFilters)
}

View File

@@ -0,0 +1,117 @@
/*
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
* Copyright (C) 2017 Fankes Studio(qzmmcn@163.com)
* https://github.com/KitsunePie/AppErrorsTracking
*
* This software is non-free but opensource software: you can redistribute it
* and/or modify it under the terms of the GNU Affero General Public License
* as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* and eula along with this software. If not, see
* <https://www.gnu.org/licenses/>
*
* This file is created by fankes on 2023/1/23.
*/
package com.fankes.apperrorstracking.utils.tool
import android.app.Activity
import android.content.Context
import android.icu.text.SimpleDateFormat
import android.icu.util.Calendar
import android.icu.util.TimeZone
import com.fankes.apperrorstracking.locale.locale
import com.fankes.apperrorstracking.utils.factory.openBrowser
import com.fankes.apperrorstracking.utils.factory.showDialog
import okhttp3.Call
import okhttp3.Callback
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import org.json.JSONObject
import java.io.IOException
import java.io.Serializable
import java.util.Locale
/**
* 获取 GitHub Release 最新版本工具类
*/
object GithubReleaseTool {
/** 仓库作者 */
private const val REPO_AUTHOR = "KitsunePie"
/** 仓库名称 */
private const val REPO_NAME = "AppErrorsTracking"
/**
* 获取最新版本信息
* @param context 实例
* @param version 当前版本
* @param result 成功后回调 - ([String] 最新版本,[Function] 更新对话框方法体)
*/
fun checkingForUpdate(context: Context, version: String, result: (String, () -> Unit) -> Unit) = runCatching {
OkHttpClient().newBuilder().build().newCall(
Request.Builder()
.url("https://api.github.com/repos/$REPO_AUTHOR/$REPO_NAME/releases/latest")
.get()
.build()
).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {}
override fun onResponse(call: Call, response: Response) = runCatching {
JSONObject(response.body.string()).apply {
GithubReleaseBean(
name = getString("name"),
htmlUrl = getString("html_url"),
content = getString("body"),
date = getString("published_at").localTime()
).apply {
fun showUpdate() = context.showDialog {
title = locale.latestVersion(name)
msg = locale.latestVersionTip(date, content)
confirmButton(locale.updateNow) { context.openBrowser(htmlUrl) }
cancelButton()
}
if (name != version) (context as? Activity?)?.runOnUiThread {
showUpdate()
result(name) { showUpdate() }
}
}
}
}.getOrNull().let {}
})
}.getOrNull().let {}
/**
* 格式化时间为本地时区
* @return [String] 本地时区时间
*/
private fun String.localTime() = replace("T", " ").replace("Z", "").let {
runCatching {
val local = SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.ROOT).apply { timeZone = Calendar.getInstance().timeZone }
val current = SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.ROOT).apply { timeZone = TimeZone.getTimeZone("GMT") }
local.format(current.parse(it))
}.getOrNull() ?: it
}
/**
* GitHub Release bean
* @param name 版本名称
* @param htmlUrl 网页地址
* @param content 更新日志
* @param date 发布时间
*/
private data class GithubReleaseBean(
var name: String,
var htmlUrl: String,
var content: String,
var date: String
) : Serializable
}

View File

@@ -0,0 +1,57 @@
/*
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
* Copyright (C) 2017 Fankes Studio(qzmmcn@163.com)
* https://github.com/KitsunePie/AppErrorsTracking
*
* This software is non-free but opensource software: you can redistribute it
* and/or modify it under the terms of the GNU Affero General Public License
* as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* and eula along with this software. If not, see
* <https://www.gnu.org/licenses/>
*
* This file is created by fankes on 2023/11/3.
*/
package com.fankes.apperrorstracking.utils.tool
import android.content.Context
import com.fankes.apperrorstracking.databinding.DiaStackTraceShareBinding
import com.fankes.apperrorstracking.utils.factory.showDialog
/**
* 异常堆栈分享工具类
*/
object StackTraceShareHelper {
/**
* 显示分享选择器
* @param context 当前实例
* @param title 对话框标题
* @param onChoose 回调选择的结果
*/
fun showChoose(
context: Context,
title: String,
onChoose: (sDeviceBrand: Boolean, sDeviceModel: Boolean, sDisplay: Boolean, sPackageName: Boolean) -> Unit
) {
context.showDialog<DiaStackTraceShareBinding> {
this.title = title
confirmButton {
val sDeviceBrand = binding.configCheck0.isChecked
val sDeviceModel = binding.configCheck1.isChecked
val sDisplay = binding.configCheck2.isChecked
val sPackageName = binding.configCheck3.isChecked
onChoose(sDeviceBrand, sDeviceModel, sDisplay, sPackageName)
cancel()
}
cancelButton()
}
}
}

View File

@@ -1,6 +1,6 @@
/*
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* Copyright (C) 2017 Fankes Studio(qzmmcn@163.com)
* https://github.com/KitsunePie/AppErrorsTracking
*
* This software is non-free but opensource software: you can redistribute it
@@ -17,13 +17,19 @@
* and eula along with this software. If not, see
* <https://www.gnu.org/licenses/>
*
* This file is Created by fankes on 2022/5/13.
* This file is created by fankes on 2022/5/13.
*/
@file:Suppress("unused")
package com.fankes.apperrorstracking.utils.tool
import java.io.*
import java.io.BufferedInputStream
import java.io.BufferedOutputStream
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.io.IOException
import java.io.InputStream
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
import java.util.zip.ZipOutputStream

View File

@@ -0,0 +1,36 @@
/*
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
* Copyright (C) 2017 Fankes Studio(qzmmcn@163.com)
* https://github.com/KitsunePie/AppErrorsTracking
*
* This software is non-free but opensource software: you can redistribute it
* and/or modify it under the terms of the GNU Affero General Public License
* as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* and eula along with this software. If not, see
* <https://www.gnu.org/licenses/>
*
* This file is created by fankes on 2023/9/19.
*/
@file:Suppress("unused")
package com.fankes.apperrorstracking.wrapper
import com.fankes.apperrorstracking.BuildConfig
/**
* 对 [BuildConfig] 的包装
*/
object BuildConfigWrapper {
const val APPLICATION_ID = BuildConfig.APPLICATION_ID
const val VERSION_NAME = BuildConfig.VERSION_NAME
const val VERSION_CODE = BuildConfig.VERSION_CODE
val isDebug = BuildConfig.DEBUG
}

Some files were not shown because too many files have changed in this diff Show More