Compare commits
260 Commits
1.0
...
c301d5aab7
Author | SHA1 | Date | |
---|---|---|---|
c301d5aab7
|
|||
2c673280cc
|
|||
036a3f90e9
|
|||
5b6caf4769
|
|||
140b74fcab
|
|||
e59b7f501a
|
|||
ab03c74659
|
|||
a17e823a46
|
|||
5a60ef47b5
|
|||
|
9092e4d17a | ||
d70cf11a42
|
|||
8b73ad6610
|
|||
652c8383c1
|
|||
fbabb41f33
|
|||
f0b52d54c5
|
|||
40747a81ea | |||
cabf025082
|
|||
248daa5c80
|
|||
d253b26294
|
|||
d7c26180ae
|
|||
0facd0bcbf
|
|||
32855f4b61
|
|||
183999ca30
|
|||
0ef91d8578
|
|||
|
ff948fd1ae | ||
|
7c237dd024 | ||
28749a9226
|
|||
46b810e409
|
|||
b497173e02
|
|||
18c49405a8
|
|||
7d96a5097e
|
|||
|
773a5258df | ||
|
59b835dd2e | ||
|
69a96cb819 | ||
53c6f8a204
|
|||
d1480ae8f3
|
|||
|
91cfda35bc | ||
c774dbebf2
|
|||
e81ca4ec10
|
|||
cafb664c1a
|
|||
7939d25bb6
|
|||
1b6bc71e51
|
|||
9483cd7940
|
|||
d1bd76221d
|
|||
b13df7179a
|
|||
10a51c800b
|
|||
308afa7d6a
|
|||
7775c82caa
|
|||
60b3963df1
|
|||
b7ffdf3231
|
|||
5f96817d8c
|
|||
063b093eed
|
|||
fb388ee783
|
|||
389f3a69ee
|
|||
e0b8799a9b
|
|||
a64ad86e64
|
|||
2d42581e36
|
|||
936d66a81e
|
|||
66b9407b34
|
|||
c8a0631034
|
|||
32ca130da0
|
|||
273dca6042
|
|||
315dfd7e22
|
|||
b5baf8243e
|
|||
2064c01350
|
|||
72d76a486c
|
|||
d215440e83
|
|||
52367b5c41
|
|||
061b73fef7
|
|||
13e63a8fb3
|
|||
7eacb56857
|
|||
684456bb5b
|
|||
3b5aee9fb8
|
|||
86ba9749be
|
|||
4fff1d7c17
|
|||
c1e584d739
|
|||
ccc50d720e
|
|||
8df2fd5c14
|
|||
99472dedc4
|
|||
d22c5801b2
|
|||
0a87f13af7
|
|||
fd7bb9bf77
|
|||
b0a6c71300
|
|||
00512d6f95
|
|||
4bc2d84e7d
|
|||
|
75e6f1c16c | ||
886e8d1e37 | |||
a876854010 | |||
9834b6c8dd | |||
3a5a1df270 | |||
4afe549a8d
|
|||
3a7c97a1ac
|
|||
|
2b8b769aa9 | ||
|
fa793d764b | ||
4f9ef060ea
|
|||
47be6b10b7
|
|||
340034b531
|
|||
58aa7bc498
|
|||
06324d104a
|
|||
0ed2cf4903
|
|||
d278faf7c9
|
|||
823c5c690f
|
|||
a4e1c82ad0
|
|||
07e13ccf04
|
|||
e19f8ea4af
|
|||
45c337d475
|
|||
8690e8afdb
|
|||
ec7dcabdb8
|
|||
0db82b5630
|
|||
1207dbb561
|
|||
785208a5bb
|
|||
3dbd80e06e
|
|||
f452bb2b32
|
|||
39b779eab9
|
|||
948095c016
|
|||
1a7775d5cc
|
|||
00cfca12b0
|
|||
556951c27c
|
|||
b478654008
|
|||
b47543e71a
|
|||
6ffe4bc15d
|
|||
bb94689baa
|
|||
6803b60dca
|
|||
82946e55fe
|
|||
910b3523a3
|
|||
a6fd0b699c
|
|||
6ddbfb5444
|
|||
fe4f91291e
|
|||
5e0336a434
|
|||
55a4d68a18
|
|||
358ac848e2
|
|||
567d0a7b6c
|
|||
4d5dadae8b
|
|||
9dce54f326
|
|||
37af877a7d
|
|||
59f5b27d19
|
|||
1299779868
|
|||
27c5e92879
|
|||
d22745f2ea
|
|||
dfcee71168
|
|||
5b30499cc3
|
|||
1889b848b0
|
|||
c349ee5dc7
|
|||
72e99bd4f2
|
|||
2d9550d5b9
|
|||
27e068898e
|
|||
92a0591d5b
|
|||
2a97dcbc06
|
|||
0dc12ae3e6
|
|||
075850d239
|
|||
c48a23f07d
|
|||
cf1796b7cb
|
|||
5bb1f38146
|
|||
a055d7f53d
|
|||
2eae45a640
|
|||
fd168f8810
|
|||
37580519db
|
|||
5144494f82
|
|||
01a6c1ffa5
|
|||
2aaa422a56
|
|||
7c0c1754e9
|
|||
587c718d0a
|
|||
6b2b538047
|
|||
6a5fee830c
|
|||
1d0cc1d24f
|
|||
eeaf386635
|
|||
a15b5b008d
|
|||
37962fa12f
|
|||
3798479c23
|
|||
a214f5773f
|
|||
aeda0f183e
|
|||
c6d5f07b8c
|
|||
5991d976b9
|
|||
3fb4e4f375
|
|||
8c4a1ea5f0
|
|||
7749cb9aeb
|
|||
9d9cb473e8
|
|||
afeb16e69d
|
|||
211343a6e7
|
|||
2c0cfb6863
|
|||
15293950a9
|
|||
88fe23ab9a
|
|||
|
150d2e8aa5 | ||
|
dd6e971a34 | ||
|
051da4df5f | ||
|
368a4b347f | ||
30b92770e9
|
|||
|
2491547e3e | ||
|
182a65255f | ||
aef2e0814a
|
|||
e5062d3947
|
|||
1a1856ce9e
|
|||
4ae7fff484
|
|||
1f29ac1bba
|
|||
92f6837c30
|
|||
a44d29e102
|
|||
6fbaf6d7a2
|
|||
5320ce1b0e
|
|||
2b317070a2
|
|||
f7784b393d
|
|||
8eb814a345
|
|||
d350944f0d
|
|||
56b3656bf2
|
|||
332cf0d3c0
|
|||
0974c38d76
|
|||
a35dcfed66
|
|||
5944842c8e
|
|||
0b39bd9865
|
|||
d194da21ca
|
|||
777af500d4
|
|||
2472f8b7e5
|
|||
1153494dfa
|
|||
95ee9de477
|
|||
fe94441e9a
|
|||
3156a1721e
|
|||
9948a3fbc1
|
|||
2481263c00
|
|||
a6bf4e8a80
|
|||
64e54348f4
|
|||
33ee056ed9
|
|||
1fc9f07b9f
|
|||
499e7d9296
|
|||
f2311f31cb
|
|||
b9f52b4e67
|
|||
c272dbc109
|
|||
4c47cbb271
|
|||
3de258f95d
|
|||
b173d3bfed
|
|||
310bc2c9dc
|
|||
af0d29a8f4
|
|||
4db70d02ec
|
|||
167199ba34
|
|||
98f22d6bca
|
|||
8f09a32d22
|
|||
f14b7d8f20
|
|||
d38474e082
|
|||
5c1b8d3d6a
|
|||
68dff21c42
|
|||
1014f42584
|
|||
1a57a331b9
|
|||
bfb5e037e5
|
|||
1862faf637
|
|||
abf6103b9d
|
|||
c47d356e4c
|
|||
e8990f50f9
|
|||
a1d2d7ddf0
|
|||
ec829a85af
|
|||
ce7a483106
|
|||
8d64f9c989
|
|||
1973c1acaf
|
|||
936a5b6802
|
|||
c5e9df257d
|
|||
57836f4a7f
|
|||
2e09ab584d
|
|||
a9a7b5bf26
|
|||
981e0ba746
|
|||
fed9fd3ae8
|
|||
20eb229e02
|
|||
60a619ab3e
|
|||
447793cc7a
|
33
.editorconfig
Normal 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
@@ -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
@@ -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"
|
62
.github/workflows/pr_ci.yml
vendored
@@ -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
@@ -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 }}
|
63
.github/workflows/push_ci.yml
vendored
@@ -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
|
116
.gitignore
vendored
@@ -1,14 +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
|
||||
/local.properties
|
||||
.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/
|
||||
/.idea/
|
||||
|
||||
# Gradle projects
|
||||
.gradle
|
||||
build/
|
||||
|
||||
# Mkdocs temporary serving folder
|
||||
docs-gen
|
||||
site
|
||||
*.bak
|
||||
.idea/appInsightsSettings.xml
|
||||
|
||||
# Mac OS
|
||||
.DS_Store
|
6
.idea/AndroidProjectSystem.xml
generated
Normal 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>
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
12
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
@@ -0,0 +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
@@ -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
@@ -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
@@ -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
@@ -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>
|
17
.idea/runConfigurations.xml
generated
Normal 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>
|
6
.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
1
.secret/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/secret.properties
|
121
README-ja-JP.md
Normal file
@@ -0,0 +1,121 @@
|
||||
# AppErrorsTracking
|
||||
|
||||
[](https://github.com/KitsunePie/AppErrorsTracking/blob/master/LICENSE)
|
||||
[](https://github.com/KitsunePie/AppErrorsTracking/actions/workflows/commit_ci.yml)
|
||||
[](https://github.com/KitsunePie/AppErrorsTracking/releases)
|
||||

|
||||

|
||||
|
||||
[](https://t.me/AppErrorsTracking_CI)
|
||||
[](https://t.me/XiaofangInternet)
|
||||
[](https://qm.qq.com/cgi-bin/qm/qr?k=dp2h5YhWiga9WWb_Oh7kSHmx01X8I8ii&jump_from=webapi&authKey=Za5CaFP0lk7+Zgsk2KpoBD7sSaYbeXbsDgFjiWelOeH4VSionpxFJ7V0qQBSqvFM)
|
||||
[](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の推移
|
||||
|
||||

|
||||
|
||||
## ライセンス
|
||||
|
||||
- [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)
|
132
README-zh-CN.md
Normal file
@@ -0,0 +1,132 @@
|
||||
# AppErrorsTracking
|
||||
|
||||
[](https://github.com/KitsunePie/AppErrorsTracking/blob/master/LICENSE)
|
||||
[](https://github.com/KitsunePie/AppErrorsTracking/actions/workflows/commit_ci.yml)
|
||||
[](https://github.com/KitsunePie/AppErrorsTracking/releases)
|
||||

|
||||

|
||||
|
||||
[](https://t.me/AppErrorsTracking_CI)
|
||||
[](https://t.me/XiaofangInternet)
|
||||
[](https://qm.qq.com/cgi-bin/qm/qr?k=dp2h5YhWiga9WWb_Oh7kSHmx01X8I8ii&jump_from=webapi&authKey=Za5CaFP0lk7+Zgsk2KpoBD7sSaYbeXbsDgFjiWelOeH4VSionpxFJ7V0qQBSqvFM)
|
||||
[](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 开发者带来更好的体验。
|
||||
|
||||
此项目为 Xposed 模块,可用在任何 Android 系统中,目前仅在 **LSPosed** 中测试通过。
|
||||
|
||||
此模块专为 Android 开发者而打造。
|
||||
|
||||
在可能的无法连接电脑,不能进行 ADB 调试的时候,可通过此模块来快速捕获任意已安装应用的任意异常,以便快速定位问题。
|
||||
|
||||
应用发生崩溃的错误日志对开发者来说是无价的财富,若你不是开发者,你依然可以安装此模块,以便给开发者提供更多异常信息快速解决问题。
|
||||
|
||||
> 最低支持 Android 7.0
|
||||
|
||||
## 项目缘由
|
||||
|
||||
我实在是不能理解,国内 ROM 除了 MIUI(稳定版除外) 都选择了删除应用程序崩溃的对话框(FC 对话框),我曾以为这一直是一个特性,直到我去反编译了系统框架,才确认确实是被删掉了。
|
||||
|
||||
难道产品经理认为,让用户看不到错误,应用直接闪退,逃避就是最好的解决方案吗,还是说**另有隐情**呢?
|
||||
|
||||
## 工作原理
|
||||
|
||||
不同于 `Thread.UncaughtExceptionHandler`,我们通过注入系统框架,使用原生方式全方位捕获应用异常,不会产生额外的注册监听,在性能上相比原始的异常监听会更好。
|
||||
|
||||
同时系统级别的异常捕获还可捕获原生层的 `stack trace`。
|
||||
|
||||
## 注意事项
|
||||
|
||||
系统原生方式捕获的异常只能为 APP 自身未进行处理的异常,若 APP 自身拥有自定义的 `Thread.UncaughtExceptionHandler`
|
||||
类似 **Bugly** 这样的自动收集异常功能,系统就无法获取到 APP 是否真正发生异常而闪退(FC),例如 **QQ**、**TIM**。
|
||||
|
||||
## 功能列表
|
||||
|
||||
- [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. 本软件免费、由兴趣驱动开发,仅供学习交流使用。如果你是从其他非官方渠道付费获得本软件,可能已遭遇欺诈,欢迎向我们举报可疑行为。</h3>
|
||||
|
||||
<h3>2. 本软件采用 <strong>GNU Affero General Public License (AGPL 3.0)</strong> 许可证。根据该许可证的要求:</h3>
|
||||
|
||||
- 任何衍生作品必须采用相同的 AGPL 许可证
|
||||
- 分发本软件或其修改版本时,必须提供完整的源代码
|
||||
- 必须保留原始的版权声明及许可证信息
|
||||
- 不得额外施加限制来限制他人对本软件的自由使用
|
||||
|
||||
<h3>3. 我们鼓励在遵守 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
|
||||
|
||||

|
||||
|
||||
## 许可证
|
||||
|
||||
- [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)
|
||||
|
||||
版权所有 © 2017 Fankes Studio(qzmmcn@163.com)
|
134
README.md
@@ -1,53 +1,42 @@
|
||||
# AppErrorsTracking
|
||||
|
||||
[](https://github.com/KitsunePie/AppErrorsTracking)
|
||||
[](https://github.com/KitsunePie/AppErrorsTracking/blob/master/LICENSE)
|
||||
[](https://github.com/KitsunePie/AppErrorsTracking/releases)
|
||||
[](https://github.com/KitsunePie/AppErrorsTracking/releases)
|
||||
[](https://github.com/Xposed-Modules-Repo/com.fankes.apperrorstracking/releases)
|
||||
<br/><br/>
|
||||
[](https://github.com/KitsunePie/AppErrorsTracking/blob/master/LICENSE)
|
||||
[](https://github.com/KitsunePie/AppErrorsTracking/actions/workflows/commit_ci.yml)
|
||||
[](https://github.com/KitsunePie/AppErrorsTracking/releases)
|
||||

|
||||

|
||||
|
||||
[](https://t.me/AppErrorsTracking_CI)
|
||||
[](https://t.me/XiaofangInternet)
|
||||
[](https://qm.qq.com/cgi-bin/qm/qr?k=dp2h5YhWiga9WWb_Oh7kSHmx01X8I8ii&jump_from=webapi&authKey=Za5CaFP0lk7+Zgsk2KpoBD7sSaYbeXbsDgFjiWelOeH4VSionpxFJ7V0qQBSqvFM)
|
||||
[](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 exception
|
||||
of any installed apps, so as to quickly locate the problem.
|
||||
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, 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
|
||||
|
||||
**应用异常跟踪**
|
||||
|
||||
为原生 FC 对话框增加更多功能并修复国内定制 ROM 删除 FC 对话框的问题,给 Android 开发者带来更好的体验。
|
||||
|
||||
此项目为 Xposed 模块,可用在任何 Android 系统中,目前仅在 **LSPosed** 中测试通过。
|
||||
|
||||
此模块专为 Android 开发者而打造。
|
||||
|
||||
在可能的无法连接电脑,不能进行 ADB 调试的时候,可通过此模块来快速捕获任意已安装应用的任意异常,以便快速定位问题。
|
||||
|
||||
应用发生崩溃的错误日志对开发者来说是无价的财富,若你不是开发者,你依然可以安装此模块,以便给开发者提供更多异常信息快速解决问题。
|
||||
|
||||
> 最低支持 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**?
|
||||
|
||||
**项目缘由**
|
||||
|
||||
我实在是不能理解,国内 ROM 除了 MIUI(稳定版除外) 都选择了删除应用程序崩溃的对话框(FC 对话框),我曾以为这一直是一个特性,直到我去反编译了系统框架,才确认确实是被删掉了。
|
||||
|
||||
难道产品经理认为,让用户看不到错误,应用直接闪退,逃避就是最好的解决方案吗,还是说**另有隐情**呢?
|
||||
|
||||
## Woking Principle
|
||||
|
||||
Unlike `Thread.UncaughtExceptionHandler`, we use the native method to capture apps errors in all directions by injecting the system framework,
|
||||
@@ -55,55 +44,70 @@ without generating additional registration monitoring, which is better than the
|
||||
|
||||
At the same time, the system-level exception capture can also capture the `stack trace` of the native platform.
|
||||
|
||||
**工作原理**
|
||||
## Precautions
|
||||
|
||||
不同于 `Thread.UncaughtExceptionHandler`,我们通过注入系统框架,使用原生方式全方位捕获应用异常,不会产生额外的注册监听,在性能上相比原始的异常监听会更好。
|
||||
The errors captured by the system natively can only be errors that are not handled by the apps itself. If the apps itself has a
|
||||
custom `Thread.UncaughtExceptionHandler`
|
||||
Similar to **Bugly** to automatically collect errors, the system cannot obtain whether the apps actually crashes **(FC)**.
|
||||
|
||||
同时系统级别的异常捕获还可捕获原生层的 `stack trace`。
|
||||
## Features List
|
||||
|
||||
## Feature
|
||||
- [x] Completely replaces the system's apps errors dialog
|
||||
|
||||
- Completely replaces the system's apps errors dialog
|
||||
- [x] Logs exceptions for each app and persists until restart
|
||||
|
||||
- Logs exceptions for each apps and persists until restart
|
||||
- [x] Copy, share, export errors stack trace functions
|
||||
|
||||
- Copy, share, export errors stack trace functions
|
||||
- [x] Errors history record function,
|
||||
which can be entered through the notification bar tile "errors history record" and the main interface of the module
|
||||
|
||||
- Errors history record function, which can be entered through the notification bar tile "errors history record" and the main interface of the
|
||||
module
|
||||
- [x] Apps errors statistics function
|
||||
|
||||
- Apps errors statistics function
|
||||
- [x] Errors display function for multi-process apps
|
||||
|
||||
- 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
|
||||
|
||||

|
||||
|
||||
## 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
|
||||
@@ -116,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)
|
||||
|
||||
版权所有 © 2019-2022 Fankes Studio(qzmmcn@163.com)
|
||||
Copyright © 2017 Fankes Studio(qzmmcn@163.com)
|
||||
|
15
app/.gitignore
vendored
@@ -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
|
@@ -1,72 +0,0 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'org.jetbrains.kotlin.android'
|
||||
id 'com.google.devtools.ksp' version '1.7.0-1.0.6'
|
||||
}
|
||||
|
||||
android {
|
||||
namespace 'com.fankes.apperrorstracking'
|
||||
compileSdk 32
|
||||
|
||||
signingConfigs {
|
||||
debug {
|
||||
storeFile file('../keystore/public')
|
||||
storePassword '123456'
|
||||
keyAlias 'public'
|
||||
keyPassword '123456'
|
||||
v1SigningEnabled true
|
||||
v2SigningEnabled true
|
||||
}
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.fankes.apperrorstracking"
|
||||
minSdk 27
|
||||
targetSdk 32
|
||||
versionCode rootProject.ext.appVersionCode
|
||||
versionName rootProject.ext.appVersionName
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly 'de.robv.android.xposed:api:82'
|
||||
implementation 'com.highcapable.yukihookapi:api:1.0.92'
|
||||
ksp 'com.highcapable.yukihookapi:ksp-xposed:1.0.92'
|
||||
implementation "com.github.topjohnwu.libsu:core:3.1.2"
|
||||
implementation 'androidx.core:core-ktx:1.8.0'
|
||||
implementation 'androidx.appcompat:appcompat:1.4.2'
|
||||
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'
|
||||
}
|
42
app/proguard-rules.pro
vendored
@@ -1,42 +0,0 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
|
||||
-ignorewarnings
|
||||
-optimizationpasses 10
|
||||
-dontusemixedcaseclassnames
|
||||
-dontoptimize
|
||||
-verbose
|
||||
-overloadaggressively
|
||||
-allowaccessmodification
|
||||
-adaptclassstrings
|
||||
-adaptresourcefilenames
|
||||
-adaptresourcefilecontents
|
||||
|
||||
-renamesourcefileattribute P
|
||||
-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
-assumenosideeffects class kotlin.jvm.internal.Intrinsics {
|
||||
public static *** throwUninitializedProperty(...);
|
||||
public static *** throwUninitializedPropertyAccessException(...);
|
||||
}
|
||||
|
||||
-keep class com.fankes.apperrorstracking.databinding**{*;}
|
@@ -1 +0,0 @@
|
||||
com.fankes.apperrorstracking.hook.AppErrorsTracking
|
@@ -1 +0,0 @@
|
||||
com.fankes.apperrorstracking.hook.HookEntry
|
@@ -1,147 +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 com.fankes.apperrorstracking.locale.LocaleString
|
||||
import com.fankes.apperrorstracking.utils.factory.difference
|
||||
import java.io.Serializable
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* 应用异常信息 bean
|
||||
* @param packageName 包名
|
||||
* @param isNativeCrash 是否为原生层异常
|
||||
* @param exceptionClassName 异常类名
|
||||
* @param exceptionMessage 异常信息
|
||||
* @param throwClassName 抛出异常的类名
|
||||
* @param throwFileName 抛出异常的文件名
|
||||
* @param throwMethodName 抛出异常的方法名
|
||||
* @param throwLineNumber 抛出异常的行号
|
||||
* @param stackTrace 异常堆栈
|
||||
* @param timestamp 记录时间戳
|
||||
*/
|
||||
data class AppErrorsInfoBean(
|
||||
var packageName: String,
|
||||
var isNativeCrash: Boolean,
|
||||
var exceptionClassName: String,
|
||||
var exceptionMessage: String,
|
||||
var throwFileName: String,
|
||||
var throwClassName: String,
|
||||
var throwMethodName: String,
|
||||
var throwLineNumber: Int,
|
||||
var stackTrace: String,
|
||||
var timestamp: Long,
|
||||
) : Serializable {
|
||||
|
||||
companion object {
|
||||
|
||||
/**
|
||||
* 从 [ApplicationErrorReport.CrashInfo] 克隆
|
||||
* @param packageName APP 包名
|
||||
* @param crashInfo [ApplicationErrorReport.CrashInfo]
|
||||
* @return [AppErrorsInfoBean]
|
||||
*/
|
||||
fun clone(packageName: String?, crashInfo: ApplicationErrorReport.CrashInfo?) =
|
||||
(crashInfo?.exceptionClassName?.lowercase() == "native crash").let { isNativeCrash ->
|
||||
AppErrorsInfoBean(
|
||||
packageName = packageName ?: "null",
|
||||
isNativeCrash = isNativeCrash,
|
||||
exceptionClassName = crashInfo?.exceptionClassName ?: "null",
|
||||
exceptionMessage = if (isNativeCrash) crashInfo?.stackTrace.let {
|
||||
if (it?.contains(other = "Abort message: '") == true)
|
||||
runCatching { it.split("Abort message: '")[1].split("'")[0] }.getOrNull()
|
||||
?: crashInfo?.exceptionMessage ?: "null"
|
||||
else crashInfo?.exceptionMessage ?: "null"
|
||||
} else crashInfo?.exceptionMessage ?: "null",
|
||||
throwFileName = crashInfo?.throwFileName ?: "null",
|
||||
throwClassName = crashInfo?.throwClassName ?: "null",
|
||||
throwMethodName = crashInfo?.throwMethodName ?: "null",
|
||||
throwLineNumber = crashInfo?.throwLineNumber ?: -1,
|
||||
stackTrace = crashInfo?.stackTrace?.trim() ?: "null",
|
||||
timestamp = System.currentTimeMillis()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取异常本地化经过时间
|
||||
* @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)) ?: "DateTime not found"
|
||||
|
||||
/**
|
||||
* 获取异常堆栈分享模板
|
||||
* @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" +
|
||||
"[Package Name]: $packageName\n" +
|
||||
"[Error Type]: ${if (isNativeCrash) "Native" else "Jvm"}\n" +
|
||||
"[Crash Time]: $dateTime\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" +
|
||||
"[Package Name]: $packageName\n" +
|
||||
"[Error Type]: ${if (isNativeCrash) "Native" else "Jvm"}\n" +
|
||||
"[Crash Time]: $dateTime\n" +
|
||||
"[Stack Trace]:\n" +
|
||||
stackTrace
|
||||
}
|
@@ -1,35 +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/14.
|
||||
*/
|
||||
package com.fankes.apperrorstracking.data
|
||||
|
||||
import com.highcapable.yukihookapi.hook.xposed.prefs.data.PrefsData
|
||||
|
||||
object DataConst {
|
||||
|
||||
val SHOW_DEVELOPER_NOTICE = PrefsData("_show_developer_notice", true)
|
||||
|
||||
val ENABLE_HIDE_ICON = PrefsData("_hide_icon", false)
|
||||
val ENABLE_ONLY_SHOW_ERRORS_IN_FRONT = PrefsData("_enable_only_show_errors_in_front", false)
|
||||
val ENABLE_ONLY_SHOW_ERRORS_IN_MAIN = PrefsData("_enable_only_show_errors_in_main", false)
|
||||
val ENABLE_ALWAYS_SHOWS_REOPEN_APP_OPTIONS = PrefsData("_enable_always_shows_reopen_app_options", false)
|
||||
val ENABLE_APP_CONFIG_TEMPLATE = PrefsData("_enable_app_config_template", false)
|
||||
}
|
@@ -1,284 +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.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.content.pm.PackageManager
|
||||
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.DataConst
|
||||
import com.fankes.apperrorstracking.hook.factory.isAppShowErrorsNotify
|
||||
import com.fankes.apperrorstracking.hook.factory.isAppShowErrorsToast
|
||||
import com.fankes.apperrorstracking.hook.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.loggerE
|
||||
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 AppErrorDialog_DataClass = "com.android.server.am.AppErrorDialog\$Data"
|
||||
private const val ProcessRecordClass = "com.android.server.am.ProcessRecord"
|
||||
|
||||
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 数组 - 直到重新解锁 */
|
||||
private var mutedErrorsIfUnlockApps = HashSet<String>()
|
||||
|
||||
/** 已忽略错误的 APP 数组 - 直到重新启动 */
|
||||
private var mutedErrorsIfRestartApps = HashSet<String>()
|
||||
|
||||
/** 已记录的 APP 异常信息数组 - 直到重新启动 */
|
||||
private val appErrorsRecords = ArrayList<AppErrorsInfoBean>()
|
||||
|
||||
/** 注册 */
|
||||
private fun register() {
|
||||
onAppLifecycle {
|
||||
/** 解锁后清空已记录的忽略错误 APP */
|
||||
registerReceiver(Intent.ACTION_USER_PRESENT) { _, _ -> mutedErrorsIfUnlockApps.clear() }
|
||||
/** 刷新模块 Resources 缓存 */
|
||||
registerReceiver(Intent.ACTION_LOCALE_CHANGED) { _, _ -> refreshModuleAppResources() }
|
||||
}
|
||||
FrameworkTool.Host.with(instance = this) {
|
||||
onOpenAppUsedFramework { appContext.openApp(it) }
|
||||
onPushAppErrorsInfoData { appErrorsRecords }
|
||||
onRemoveAppErrorsInfoData { appErrorsRecords.remove(it) }
|
||||
onClearAppErrorsInfoData { appErrorsRecords.clear() }
|
||||
onMutedErrorsIfUnlock { mutedErrorsIfUnlockApps.add(it) }
|
||||
onMutedErrorsIfRestart { mutedErrorsIfRestartApps.add(it) }
|
||||
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 -> mutedErrorsIfUnlockApps.remove(it.packageName)
|
||||
MutedErrorsAppBean.MuteType.UNTIL_REBOOTS -> mutedErrorsIfRestartApps.remove(it.packageName)
|
||||
}
|
||||
}
|
||||
onUnmuteAllErrorsApps {
|
||||
mutedErrorsIfUnlockApps.clear()
|
||||
mutedErrorsIfRestartApps.clear()
|
||||
}
|
||||
onPushAppListData { filters ->
|
||||
arrayListOf<AppInfoBean>().apply {
|
||||
appContext.packageManager.getInstalledPackages(PackageManager.GET_CONFIGURATIONS).also { info ->
|
||||
(if (filters.name.isNotBlank())
|
||||
info.filter { it.packageName.contains(filters.name) || appContext.appName(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 = appContext.appName(it.packageName), packageName = it.packageName)) }
|
||||
/** 移除模块自身 */
|
||||
removeIf { it.packageName == BuildConfig.APPLICATION_ID }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onHook() {
|
||||
/** 注册 */
|
||||
register()
|
||||
/** 干掉原生错误对话框 - 如果有 */
|
||||
ErrorDialogControllerClass.hook {
|
||||
injectMember {
|
||||
method {
|
||||
name = "hasCrashDialogs"
|
||||
emptyParam()
|
||||
}
|
||||
replaceToTrue()
|
||||
}
|
||||
injectMember {
|
||||
method {
|
||||
name = "showCrashDialogs"
|
||||
paramCount = 1
|
||||
}
|
||||
intercept()
|
||||
}
|
||||
}
|
||||
/** 注入自定义错误对话框 */
|
||||
AppErrorsClass.hook {
|
||||
injectMember {
|
||||
method {
|
||||
name = "handleShowAppErrorUi"
|
||||
param(MessageClass)
|
||||
}
|
||||
afterHook {
|
||||
/** 当前实例 */
|
||||
val context = field { name = "mContext" }.get(instance).cast<Context>() ?: return@afterHook
|
||||
|
||||
/** 错误数据 */
|
||||
val errData = args().first().cast<Message>()?.obj
|
||||
|
||||
/** 当前进程信息 */
|
||||
val proc = AppErrorDialog_DataClass.clazz.field { name = "proc" }.get(errData).any()
|
||||
|
||||
/** 当前 UserId 信息 */
|
||||
val userId = ProcessRecordClass.clazz.field { name = "userId" }.get(proc).int()
|
||||
|
||||
/** 当前 APP 信息 */
|
||||
val appInfo = ProcessRecordClass.clazz.field { name = "info" }.get(proc).cast<ApplicationInfo>()
|
||||
|
||||
/** 当前进程名称 */
|
||||
val processName = ProcessRecordClass.clazz.field { name = "processName" }.get(proc).string()
|
||||
|
||||
/** 当前 APP、进程 包名 */
|
||||
val packageName = appInfo?.packageName ?: processName
|
||||
|
||||
/** 当前 APP 名称 */
|
||||
val appName = appInfo?.let { context.appName(it.packageName) } ?: packageName
|
||||
|
||||
/** 是否为 APP */
|
||||
val isApp = (PackageListClass.clazz.method {
|
||||
name = "size"
|
||||
emptyParam()
|
||||
}.get(if (ProcessRecordClass.clazz.hasMethod {
|
||||
name = "getPkgList"
|
||||
emptyParam()
|
||||
}) ProcessRecordClass.clazz.method {
|
||||
name = "getPkgList"
|
||||
emptyParam()
|
||||
}.get(proc).call() else ProcessRecordClass.clazz.field {
|
||||
name = "pkgList"
|
||||
}.get(proc).self).int() == 1 && appInfo != null)
|
||||
|
||||
/** 是否为主进程 */
|
||||
val isMainProcess = packageName == processName
|
||||
|
||||
/** 是否为后台进程 */
|
||||
val isBackgroundProcess = UserControllerClass.clazz.method { name = "getCurrentProfileIds" }
|
||||
.get(ActivityManagerServiceClass.clazz.field { name = "mUserController" }
|
||||
.get(field { name = "mService" }.get(instance).any()).any())
|
||||
.invoke<IntArray>()?.takeIf { it.isNotEmpty() }?.any { it != userId } ?: false
|
||||
|
||||
/** 是否短时内重复错误 */
|
||||
val isRepeating = AppErrorDialog_DataClass.clazz.field { name = "repeating" }.get(errData).boolean()
|
||||
|
||||
/** 崩溃标题 */
|
||||
val errorTitle = if (isRepeating) LocaleString.aerrRepeatedTitle(appName) else LocaleString.aerrTitle(appName)
|
||||
|
||||
/** 是否始终显示重新打开按钮 */
|
||||
val isAlwaysShowsReopenApp = prefs.get(DataConst.ENABLE_ALWAYS_SHOWS_REOPEN_APP_OPTIONS)
|
||||
/** 打印错误日志 */
|
||||
if (isApp) loggerE(
|
||||
msg = "App \"$packageName\"${if (packageName != processName) " --process \"$processName\"" else ""}" +
|
||||
" has crashed${if (isRepeating) " again" else ""}"
|
||||
) else loggerE(msg = "Process \"$processName\" has crashed${if (isRepeating) " again" else ""}")
|
||||
/** 判断是否为模块自身崩溃 */
|
||||
if (packageName == BuildConfig.APPLICATION_ID) {
|
||||
context.toast(msg = "AppErrorsTracking has crashed, please see the log in console")
|
||||
return@afterHook
|
||||
}
|
||||
/** 判断是否为已忽略的 APP */
|
||||
if (mutedErrorsIfUnlockApps.contains(packageName) || mutedErrorsIfRestartApps.contains(packageName)) return@afterHook
|
||||
/** 判断配置模块启用状态 */
|
||||
if (prefs.get(DataConst.ENABLE_APP_CONFIG_TEMPLATE)) {
|
||||
if (isAppShowNothing(packageName)) return@afterHook
|
||||
if (isAppShowErrorsNotify(packageName)) {
|
||||
context.pushNotify(
|
||||
channelId = "APPS_ERRORS",
|
||||
channelName = LocaleString.appName,
|
||||
title = errorTitle,
|
||||
content = LocaleString.appErrorsTip,
|
||||
icon = IconCompat.createWithBitmap(R.mipmap.ic_notify.drawableOf(moduleAppResources).toBitmap()),
|
||||
color = 0xFFFF6200.toInt(),
|
||||
intent = AppErrorsRecordActivity.intent()
|
||||
)
|
||||
return@afterHook
|
||||
}
|
||||
if (isAppShowErrorsToast(packageName)) {
|
||||
context.toast(errorTitle)
|
||||
return@afterHook
|
||||
}
|
||||
}
|
||||
/** 判断是否为后台进程 */
|
||||
if ((isBackgroundProcess || context.isAppCanOpened(packageName).not())
|
||||
&& prefs.get(DataConst.ENABLE_ONLY_SHOW_ERRORS_IN_FRONT)
|
||||
) return@afterHook
|
||||
/** 判断是否为主进程 */
|
||||
if (isMainProcess.not() && prefs.get(DataConst.ENABLE_ONLY_SHOW_ERRORS_IN_MAIN)) return@afterHook
|
||||
/** 启动错误对话框显示窗口 */
|
||||
AppErrorsDisplayActivity.start(
|
||||
context, AppErrorsDisplayBean(
|
||||
packageName = packageName,
|
||||
processName = processName,
|
||||
appName = appName,
|
||||
title = errorTitle,
|
||||
isShowAppInfoButton = isApp,
|
||||
isShowReopenButton = isApp && (isRepeating.not() || isAlwaysShowsReopenApp)
|
||||
&& context.isAppCanOpened(packageName) && isMainProcess,
|
||||
isShowCloseAppButton = isApp
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
injectMember {
|
||||
method {
|
||||
name = "crashApplication"
|
||||
paramCount = 2
|
||||
}
|
||||
afterHook {
|
||||
/** 当前 APP 信息 */
|
||||
val appInfo = ProcessRecordClass.clazz.field { name = "info" }.get(args().first().any()).cast<ApplicationInfo>()
|
||||
/** 添加当前异常信息到第一位 */
|
||||
appErrorsRecords.add(0, AppErrorsInfoBean.clone(appInfo?.packageName, args().last().cast()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -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.hook.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)
|
@@ -1,457 +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 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 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 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)
|
||||
}
|
@@ -1,120 +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 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?.getSerializableExtra(EXTRA_APP_ERRORS_INFO) as? AppErrorsInfoBean }.getOrNull()
|
||||
?: return toastAndFinish(name = "AppErrorsInfo")
|
||||
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.timestamp}.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(appIcon(appErrorsInfo.packageName))
|
||||
binding.appNameText.text = appName(appErrorsInfo.packageName)
|
||||
binding.appVersionText.text = appVersion(appErrorsInfo.packageName)
|
||||
binding.appAbiText.text = appCpuAbi(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)) appName(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()
|
||||
}
|
||||
}
|
@@ -1,182 +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/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.databinding.ActivityConfigBinding
|
||||
import com.fankes.apperrorstracking.databinding.AdapterAppInfoBinding
|
||||
import com.fankes.apperrorstracking.databinding.DiaAppConfigBinding
|
||||
import com.fankes.apperrorstracking.databinding.DiaAppsFilterBinding
|
||||
import com.fankes.apperrorstracking.hook.factory.*
|
||||
import com.fankes.apperrorstracking.locale.LocaleString
|
||||
import com.fankes.apperrorstracking.ui.activity.base.BaseActivity
|
||||
import com.fankes.apperrorstracking.utils.factory.appIcon
|
||||
import com.fankes.apperrorstracking.utils.factory.bindAdapter
|
||||
import com.fankes.apperrorstracking.utils.factory.showDialog
|
||||
import com.fankes.apperrorstracking.utils.tool.FrameworkTool
|
||||
|
||||
class ConfigureActivity : BaseActivity<ActivityConfigBinding>() {
|
||||
|
||||
/** 过滤条件 */
|
||||
private var appFilters = AppFiltersBean()
|
||||
|
||||
/** 回调适配器改变 */
|
||||
private var onChanged: (() -> Unit)? = null
|
||||
|
||||
/** 全部的 APP 信息 */
|
||||
private val listData = ArrayList<AppInfoBean>()
|
||||
|
||||
override fun onCreate() {
|
||||
binding.titleBackIcon.setOnClickListener { onBackPressed() }
|
||||
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()
|
||||
}
|
||||
}
|
||||
cancelButton()
|
||||
}
|
||||
}
|
||||
binding.filterIcon.setOnClickListener {
|
||||
showDialog<DiaAppsFilterBinding> {
|
||||
title = LocaleString.filterByCondition
|
||||
binding.containsSystemSwitch.isChecked = appFilters.isContainsSystem
|
||||
binding.appFiltersEdit.apply {
|
||||
requestFocus()
|
||||
invalidate()
|
||||
if (appFilters.name.isNotBlank()) {
|
||||
setText(appFilters.name)
|
||||
setSelection(appFilters.name.length)
|
||||
}
|
||||
}
|
||||
confirmButton {
|
||||
appFilters.isContainsSystem = binding.containsSystemSwitch.isChecked
|
||||
appFilters.name = binding.appFiltersEdit.text.toString().trim()
|
||||
refreshData()
|
||||
}
|
||||
cancelButton()
|
||||
if (appFilters.name.isNotBlank())
|
||||
neutralButton(LocaleString.clearFilters) {
|
||||
appFilters.isContainsSystem = binding.containsSystemSwitch.isChecked
|
||||
appFilters.name = ""
|
||||
refreshData()
|
||||
}
|
||||
}
|
||||
}
|
||||
binding.listView.apply {
|
||||
bindAdapter {
|
||||
onBindDatas { listData }
|
||||
onBindViews<AdapterAppInfoBinding> { binding, position ->
|
||||
listData[position].also { bean ->
|
||||
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
|
||||
else -> "Unknown type"
|
||||
}
|
||||
}
|
||||
}
|
||||
}.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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/** 模块未完全激活将显示警告 */
|
||||
if (MainActivity.isModuleValied.not())
|
||||
showDialog {
|
||||
title = LocaleString.notice
|
||||
msg = LocaleString.moduleNotFullyActivatedTip
|
||||
confirmButton { FrameworkTool.restartSystem(context) }
|
||||
cancelButton()
|
||||
noCancelable()
|
||||
}
|
||||
/** 开始刷新数据 */
|
||||
refreshData()
|
||||
}
|
||||
|
||||
/** 刷新列表数据 */
|
||||
private fun refreshData() {
|
||||
binding.listProgressView.isVisible = true
|
||||
binding.batchIcon.isVisible = false
|
||||
binding.filterIcon.isVisible = false
|
||||
binding.listView.isVisible = false
|
||||
binding.listNoDataView.isVisible = false
|
||||
binding.titleCountText.text = LocaleString.loading
|
||||
FrameworkTool.fetchAppListData(context = this, appFilters) {
|
||||
listData.clear()
|
||||
Thread {
|
||||
it.takeIf { e -> e.isNotEmpty() }?.forEach { e ->
|
||||
listData.add(e)
|
||||
e.icon = appIcon(e.packageName)
|
||||
}
|
||||
runOnUiThread {
|
||||
onChanged?.invoke()
|
||||
binding.listView.post { binding.listView.setSelection(0) }
|
||||
binding.listProgressView.isVisible = false
|
||||
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)
|
||||
}
|
||||
}.start()
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,152 +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/14.
|
||||
*/
|
||||
@file:Suppress("SetTextI18n")
|
||||
|
||||
package com.fankes.apperrorstracking.ui.activity.main
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import androidx.core.view.isVisible
|
||||
import com.fankes.apperrorstracking.BuildConfig
|
||||
import com.fankes.apperrorstracking.R
|
||||
import com.fankes.apperrorstracking.data.DataConst
|
||||
import com.fankes.apperrorstracking.databinding.ActivityMainBinding
|
||||
import com.fankes.apperrorstracking.locale.LocaleString
|
||||
import com.fankes.apperrorstracking.ui.activity.base.BaseActivity
|
||||
import com.fankes.apperrorstracking.ui.activity.errors.AppErrorsMutedActivity
|
||||
import com.fankes.apperrorstracking.ui.activity.errors.AppErrorsRecordActivity
|
||||
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.FrameworkTool
|
||||
import com.highcapable.yukihookapi.YukiHookAPI
|
||||
import com.highcapable.yukihookapi.hook.factory.modulePrefs
|
||||
|
||||
class MainActivity : BaseActivity<ActivityMainBinding>() {
|
||||
|
||||
companion object {
|
||||
|
||||
/** 模块是否有效 */
|
||||
var isModuleValied = false
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
binding.mainTextVersion.text = LocaleString.moduleVersion(BuildConfig.VERSION_NAME)
|
||||
binding.mainTextSystemVersion.text =
|
||||
LocaleString.systemVersion("${Build.VERSION.RELEASE} (API ${Build.VERSION.SDK_INT}) ${Build.DISPLAY}")
|
||||
binding.onlyShowErrorsInFrontSwitch.isChecked = modulePrefs.get(DataConst.ENABLE_ONLY_SHOW_ERRORS_IN_FRONT)
|
||||
binding.onlyShowErrorsInMainProcessSwitch.isChecked = modulePrefs.get(DataConst.ENABLE_ONLY_SHOW_ERRORS_IN_MAIN)
|
||||
binding.alwaysShowsReopenAppOptionsSwitch.isChecked = modulePrefs.get(DataConst.ENABLE_ALWAYS_SHOWS_REOPEN_APP_OPTIONS)
|
||||
binding.enableAppsConfigsTemplateSwitch.isChecked = modulePrefs.get(DataConst.ENABLE_APP_CONFIG_TEMPLATE)
|
||||
binding.hideIconInLauncherSwitch.isChecked = modulePrefs.get(DataConst.ENABLE_HIDE_ICON)
|
||||
binding.mgrAppsConfigsTemplateButton.isVisible = modulePrefs.get(DataConst.ENABLE_APP_CONFIG_TEMPLATE)
|
||||
binding.hideIconInLauncherSwitch.setOnCheckedChangeListener { btn, b ->
|
||||
if (btn.isPressed.not()) return@setOnCheckedChangeListener
|
||||
modulePrefs.put(DataConst.ENABLE_HIDE_ICON, b)
|
||||
packageManager.setComponentEnabledSetting(
|
||||
ComponentName(packageName, "${BuildConfig.APPLICATION_ID}.Home"),
|
||||
if (b) PackageManager.COMPONENT_ENABLED_STATE_DISABLED else PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
|
||||
PackageManager.DONT_KILL_APP
|
||||
)
|
||||
}
|
||||
binding.onlyShowErrorsInFrontSwitch.setOnCheckedChangeListener { btn, b ->
|
||||
if (btn.isPressed.not()) return@setOnCheckedChangeListener
|
||||
modulePrefs.put(DataConst.ENABLE_ONLY_SHOW_ERRORS_IN_FRONT, b)
|
||||
}
|
||||
binding.onlyShowErrorsInMainProcessSwitch.setOnCheckedChangeListener { btn, b ->
|
||||
if (btn.isPressed.not()) return@setOnCheckedChangeListener
|
||||
modulePrefs.put(DataConst.ENABLE_ONLY_SHOW_ERRORS_IN_MAIN, b)
|
||||
}
|
||||
binding.alwaysShowsReopenAppOptionsSwitch.setOnCheckedChangeListener { btn, b ->
|
||||
if (btn.isPressed.not()) return@setOnCheckedChangeListener
|
||||
modulePrefs.put(DataConst.ENABLE_ALWAYS_SHOWS_REOPEN_APP_OPTIONS, b)
|
||||
}
|
||||
binding.enableAppsConfigsTemplateSwitch.setOnCheckedChangeListener { btn, b ->
|
||||
if (btn.isPressed.not()) return@setOnCheckedChangeListener
|
||||
binding.mgrAppsConfigsTemplateButton.isVisible = b
|
||||
modulePrefs.put(DataConst.ENABLE_APP_CONFIG_TEMPLATE, b)
|
||||
}
|
||||
/** 管理应用配置模板按钮点击事件 */
|
||||
binding.mgrAppsConfigsTemplateButton.setOnClickListener { whenActivated { navigate<ConfigureActivity>() } }
|
||||
/** 功能管理按钮点击事件 */
|
||||
binding.viewErrorsRecordButton.setOnClickListener { whenActivated { navigate<AppErrorsRecordActivity>() } }
|
||||
binding.viewMutedErrorsAppsButton.setOnClickListener { whenActivated { navigate<AppErrorsMutedActivity>() } }
|
||||
/** 重启按钮点击事件 */
|
||||
binding.titleRestartIcon.setOnClickListener { FrameworkTool.restartSystem(context = this) }
|
||||
/** 项目地址按钮点击事件 */
|
||||
binding.titleGithubIcon.setOnClickListener { openBrowser(url = "https://github.com/KitsunePie/AppErrorsTracking") }
|
||||
/** 显示开发者提示 */
|
||||
if (modulePrefs.get(DataConst.SHOW_DEVELOPER_NOTICE))
|
||||
showDialog {
|
||||
title = LocaleString.developerNotice
|
||||
msg = LocaleString.developerNoticeTip
|
||||
confirmButton(LocaleString.gotIt) { modulePrefs.put(DataConst.SHOW_DEVELOPER_NOTICE, value = false) }
|
||||
noCancelable()
|
||||
}
|
||||
}
|
||||
|
||||
/** 刷新模块状态 */
|
||||
private fun refreshModuleStatus() {
|
||||
binding.mainLinStatus.setBackgroundResource(
|
||||
when {
|
||||
YukiHookAPI.Status.isXposedModuleActive && isModuleValied.not() -> R.drawable.bg_yellow_round
|
||||
YukiHookAPI.Status.isXposedModuleActive -> R.drawable.bg_green_round
|
||||
else -> R.drawable.bg_dark_round
|
||||
}
|
||||
)
|
||||
binding.mainImgStatus.setImageResource(
|
||||
when {
|
||||
YukiHookAPI.Status.isXposedModuleActive -> R.mipmap.ic_success
|
||||
else -> R.mipmap.ic_warn
|
||||
}
|
||||
)
|
||||
binding.mainTextStatus.text =
|
||||
when {
|
||||
YukiHookAPI.Status.isXposedModuleActive && isModuleValied.not() -> LocaleString.moduleNotFullyActivated
|
||||
YukiHookAPI.Status.isXposedModuleActive -> LocaleString.moduleIsActivated
|
||||
else -> LocaleString.moduleNotActivated
|
||||
}
|
||||
binding.mainTextApiWay.isVisible = YukiHookAPI.Status.isXposedModuleActive
|
||||
binding.mainTextApiWay.text = "Activated by ${YukiHookAPI.Status.executorName} API ${YukiHookAPI.Status.executorVersion}"
|
||||
}
|
||||
|
||||
/**
|
||||
* 当模块激活后才能执行相应功能
|
||||
* @param callback 激活后回调
|
||||
*/
|
||||
private inline fun whenActivated(callback: () -> Unit) {
|
||||
if (YukiHookAPI.Status.isXposedModuleActive) callback() else toast(LocaleString.moduleNotActivated)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
/** 刷新模块状态 */
|
||||
refreshModuleStatus()
|
||||
/** 检查模块激活状态 */
|
||||
FrameworkTool.checkingActivated(context = this) { isValied ->
|
||||
isModuleValied = isValied
|
||||
refreshModuleStatus()
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,292 +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("SameParameterValue")
|
||||
|
||||
package com.fankes.apperrorstracking.utils.drawable.drawabletoolbox
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.graphics.drawable.GradientDrawable
|
||||
import android.graphics.drawable.RippleDrawable
|
||||
import android.graphics.drawable.RotateDrawable
|
||||
import android.os.Build
|
||||
import java.lang.reflect.Field
|
||||
import java.lang.reflect.Method
|
||||
|
||||
private val gradientState = resolveGradientState()
|
||||
|
||||
private fun resolveGradientState(): Class<*> {
|
||||
val classes = GradientDrawable::class.java.declaredClasses
|
||||
for (singleClass in classes) {
|
||||
if (singleClass.simpleName == "GradientState") return singleClass
|
||||
}
|
||||
throw RuntimeException("GradientState could not be found in thisAny GradientDrawable implementation")
|
||||
}
|
||||
|
||||
private val rotateState = resolveRotateState()
|
||||
|
||||
private fun resolveRotateState(): Class<*> {
|
||||
val classes = RotateDrawable::class.java.declaredClasses
|
||||
for (singleClass in classes) {
|
||||
if (singleClass.simpleName == "RotateState") return singleClass
|
||||
}
|
||||
throw RuntimeException("RotateState could not be found in thisAny RotateDrawable implementation")
|
||||
}
|
||||
|
||||
@Throws(SecurityException::class, NoSuchFieldException::class)
|
||||
private fun resolveField(source: Class<*>, fieldName: String): Field {
|
||||
val field = source.getDeclaredField(fieldName)
|
||||
field.isAccessible = true
|
||||
return field
|
||||
}
|
||||
|
||||
@Throws(SecurityException::class, NoSuchMethodException::class)
|
||||
private fun resolveMethod(
|
||||
source: Class<*>,
|
||||
methodName: String,
|
||||
vararg parameterTypes: Class<*>
|
||||
): Method {
|
||||
val method = source.getDeclaredMethod(methodName, *parameterTypes)
|
||||
method.isAccessible = true
|
||||
return method
|
||||
}
|
||||
|
||||
fun setInnerRadius(drawable: GradientDrawable, value: Int) {
|
||||
try {
|
||||
val innerRadius = resolveField(gradientState, "mInnerRadius")
|
||||
innerRadius.setInt(drawable.constantState, value)
|
||||
} catch (e: NoSuchFieldException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: IllegalAccessException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
fun setInnerRadiusRatio(drawable: GradientDrawable, value: Float) {
|
||||
try {
|
||||
val innerRadius = resolveField(gradientState, "mInnerRadiusRatio")
|
||||
innerRadius.setFloat(drawable.constantState, value)
|
||||
} catch (e: NoSuchFieldException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: IllegalAccessException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
fun setThickness(drawable: GradientDrawable, value: Int) {
|
||||
try {
|
||||
val innerRadius = resolveField(gradientState, "mThickness")
|
||||
innerRadius.setInt(drawable.constantState, value)
|
||||
} catch (e: NoSuchFieldException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: IllegalAccessException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
fun setThicknessRatio(drawable: GradientDrawable, value: Float) {
|
||||
try {
|
||||
val innerRadius = resolveField(gradientState, "mThicknessRatio")
|
||||
innerRadius.setFloat(drawable.constantState, value)
|
||||
} catch (e: NoSuchFieldException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: IllegalAccessException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
fun setUseLevelForShape(drawable: GradientDrawable, value: Boolean) {
|
||||
try {
|
||||
val useLevelForShape = resolveField(gradientState, "mUseLevelForShape")
|
||||
useLevelForShape.setBoolean(drawable.constantState, value)
|
||||
} catch (e: NoSuchFieldException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: IllegalAccessException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("ObsoleteSdkInt")
|
||||
fun setOrientation(drawable: GradientDrawable, value: GradientDrawable.Orientation) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
||||
drawable.orientation = value
|
||||
} else {
|
||||
try {
|
||||
val orientation = resolveField(gradientState, "mOrientation")
|
||||
orientation.set(drawable.constantState, value)
|
||||
val rectIdDirty = resolveField(GradientDrawable::class.java, "mRectIsDirty")
|
||||
rectIdDirty.setBoolean(drawable, true)
|
||||
drawable.invalidateSelf()
|
||||
} catch (e: NoSuchFieldException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: IllegalAccessException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("ObsoleteSdkInt")
|
||||
fun setColors(drawable: GradientDrawable, value: IntArray) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
||||
drawable.colors = value
|
||||
} else {
|
||||
try {
|
||||
val colors = resolveField(gradientState, "mColors")
|
||||
colors.set(drawable.constantState, value)
|
||||
drawable.invalidateSelf()
|
||||
} catch (e: NoSuchFieldException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: IllegalAccessException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun setGradientRadiusType(drawable: GradientDrawable, value: Int) {
|
||||
try {
|
||||
val type = resolveField(gradientState, "mGradientRadiusType")
|
||||
type.setInt(drawable.constantState, value)
|
||||
} catch (e: NoSuchFieldException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: IllegalAccessException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
fun setGradientRadius(drawable: GradientDrawable, value: Float) {
|
||||
try {
|
||||
val gradientRadius = resolveField(gradientState, "mGradientRadius")
|
||||
gradientRadius.setFloat(drawable.constantState, value)
|
||||
} catch (e: NoSuchFieldException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: IllegalAccessException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
fun setStrokeColor(drawable: GradientDrawable, value: Int) {
|
||||
try {
|
||||
val type = resolveField(gradientState, "mStrokeColor")
|
||||
type.setInt(drawable.constantState, value)
|
||||
} catch (e: NoSuchFieldException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: IllegalAccessException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
fun setDrawable(rotateDrawable: RotateDrawable, drawable: Drawable) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
rotateDrawable.drawable = drawable
|
||||
} else {
|
||||
try {
|
||||
val drawableField = resolveField(rotateState, "mDrawable")
|
||||
val stateField = resolveField(RotateDrawable::class.java, "mState")
|
||||
drawableField.set(stateField.get(rotateDrawable), drawable)
|
||||
drawable.callback = rotateDrawable
|
||||
} catch (e: NoSuchFieldException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: IllegalAccessException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun setPivotX(rotateDrawable: RotateDrawable, value: Float) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
rotateDrawable.pivotX = value
|
||||
} else {
|
||||
try {
|
||||
val pivotXField = resolveField(rotateState, "mPivotX")
|
||||
pivotXField.setFloat(rotateDrawable.constantState, value)
|
||||
val pivotXRelField = resolveField(rotateState, "mPivotXRel")
|
||||
pivotXRelField.setBoolean(rotateDrawable.constantState, true)
|
||||
} catch (e: NoSuchFieldException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: IllegalAccessException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun setPivotY(rotateDrawable: RotateDrawable, value: Float) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
rotateDrawable.pivotY = value
|
||||
} else {
|
||||
try {
|
||||
val pivotYField = resolveField(rotateState, "mPivotY")
|
||||
pivotYField.setFloat(rotateDrawable.constantState, value)
|
||||
val pivotYRelField = resolveField(rotateState, "mPivotYRel")
|
||||
pivotYRelField.setBoolean(rotateDrawable.constantState, true)
|
||||
} catch (e: NoSuchFieldException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: IllegalAccessException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun setFromDegrees(rotateDrawable: RotateDrawable, value: Float) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
rotateDrawable.fromDegrees = value
|
||||
} else {
|
||||
try {
|
||||
val fromDegreesField = resolveField(rotateState, "mFromDegrees")
|
||||
fromDegreesField.setFloat(rotateDrawable.constantState, value)
|
||||
} catch (e: NoSuchFieldException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: IllegalAccessException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun setToDegrees(rotateDrawable: RotateDrawable, value: Float) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
rotateDrawable.toDegrees = value
|
||||
} else {
|
||||
try {
|
||||
val toDegreesField = resolveField(rotateState, "mToDegrees")
|
||||
toDegreesField.setFloat(rotateDrawable.constantState, value)
|
||||
} catch (e: NoSuchFieldException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: IllegalAccessException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun setRadius(rippleDrawable: RippleDrawable, value: Int) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
rippleDrawable.radius = value
|
||||
} else {
|
||||
try {
|
||||
val setRadiusMethod =
|
||||
resolveMethod(RippleDrawable::class.java, "setMaxRadius", Int::class.java)
|
||||
setRadiusMethod.invoke(rippleDrawable, value)
|
||||
} catch (e: NoSuchFieldException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: IllegalAccessException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,489 +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("unused", "MemberVisibilityCanBePrivate")
|
||||
|
||||
package com.fankes.apperrorstracking.utils.drawable.drawabletoolbox
|
||||
|
||||
import android.content.res.ColorStateList
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.graphics.drawable.GradientDrawable
|
||||
import android.util.StateSet
|
||||
import java.util.*
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
|
||||
class DrawableBuilder {
|
||||
|
||||
private var properties = DrawableProperties()
|
||||
private var order: AtomicInteger = AtomicInteger(1)
|
||||
private var transformsMap = TreeMap<Int, (Drawable) -> Drawable>()
|
||||
private var baseDrawable: Drawable? = null
|
||||
|
||||
fun batch(properties: DrawableProperties) = apply { this.properties = properties.copy() }
|
||||
fun baseDrawable(drawable: Drawable) = apply { baseDrawable = drawable }
|
||||
|
||||
// <shape>
|
||||
fun shape(shape: Int) = apply { properties.shape = shape }
|
||||
|
||||
fun rectangle() = apply { shape(GradientDrawable.RECTANGLE) }
|
||||
fun oval() = apply { shape(GradientDrawable.OVAL) }
|
||||
fun line() = apply { shape(GradientDrawable.LINE) }
|
||||
fun ring() = apply { shape(GradientDrawable.RING) }
|
||||
fun innerRadius(innerRadius: Int) = apply { properties.innerRadius = innerRadius }
|
||||
fun innerRadiusRatio(innerRadiusRatio: Float) =
|
||||
apply { properties.innerRadiusRatio = innerRadiusRatio }
|
||||
|
||||
fun thickness(thickness: Int) = apply { properties.thickness = thickness }
|
||||
fun thicknessRatio(thicknessRatio: Float) = apply { properties.thicknessRatio = thicknessRatio }
|
||||
|
||||
fun useLevelForRing(use: Boolean = true) = apply { properties.useLevelForRing = use }
|
||||
|
||||
// <corner>
|
||||
fun cornerRadius(cornerRadius: Int) = apply { properties.cornerRadius = cornerRadius }
|
||||
|
||||
fun topLeftRadius(topLeftRadius: Int) = apply { properties.topLeftRadius = topLeftRadius }
|
||||
fun topRightRadius(topRightRadius: Int) = apply { properties.topRightRadius = topRightRadius }
|
||||
fun bottomRightRadius(bottomRightRadius: Int) =
|
||||
apply { properties.bottomRightRadius = bottomRightRadius }
|
||||
|
||||
fun bottomLeftRadius(bottomLeftRadius: Int) =
|
||||
apply { properties.bottomLeftRadius = bottomLeftRadius }
|
||||
|
||||
fun rounded() = apply { cornerRadius(Int.MAX_VALUE) }
|
||||
fun cornerRadii(
|
||||
topLeftRadius: Int,
|
||||
topRightRadius: Int,
|
||||
bottomRightRadius: Int,
|
||||
bottomLeftRadius: Int
|
||||
) = apply {
|
||||
topLeftRadius(topLeftRadius); topRightRadius(topRightRadius); bottomRightRadius(
|
||||
bottomRightRadius
|
||||
); bottomLeftRadius(bottomLeftRadius)
|
||||
}
|
||||
|
||||
// <gradient>
|
||||
|
||||
fun gradient(useGradient: Boolean = true) = apply { properties.useGradient = useGradient }
|
||||
|
||||
fun gradientType(type: Int) = apply { properties.type = type }
|
||||
fun linearGradient() = apply { gradientType(GradientDrawable.LINEAR_GRADIENT) }
|
||||
fun radialGradient() = apply { gradientType(GradientDrawable.RADIAL_GRADIENT) }
|
||||
fun sweepGradient() = apply { gradientType(GradientDrawable.SWEEP_GRADIENT) }
|
||||
fun angle(angle: Int) = apply { properties.angle = angle }
|
||||
fun centerX(centerX: Float) = apply { properties.centerX = centerX }
|
||||
fun centerY(centerY: Float) = apply { properties.centerY = centerY }
|
||||
fun center(centerX: Float, centerY: Float) = apply { centerX(centerX); centerY(centerY) }
|
||||
|
||||
fun useCenterColor(useCenterColor: Boolean = true) =
|
||||
apply { properties.useCenterColor = useCenterColor }
|
||||
|
||||
fun startColor(startColor: Int) = apply { properties.startColor = startColor }
|
||||
fun centerColor(centerColor: Int) = apply {
|
||||
properties.centerColor = centerColor
|
||||
useCenterColor(true)
|
||||
}
|
||||
|
||||
fun endColor(endColor: Int) = apply { properties.endColor = endColor }
|
||||
fun gradientColors(startColor: Int, endColor: Int, centerColor: Int?) = apply {
|
||||
startColor(startColor); endColor(endColor)
|
||||
useCenterColor(centerColor != null)
|
||||
centerColor?.let {
|
||||
centerColor(it)
|
||||
}
|
||||
}
|
||||
|
||||
fun gradientRadiusType(gradientRadiusType: Int) =
|
||||
apply { properties.gradientRadiusType = gradientRadiusType }
|
||||
|
||||
fun gradientRadius(gradientRadius: Float) = apply { properties.gradientRadius = gradientRadius }
|
||||
fun gradientRadius(radius: Float, type: Int) =
|
||||
apply { gradientRadius(radius); gradientRadiusType(type) }
|
||||
|
||||
fun gradientRadiusInPixel(radius: Float) =
|
||||
apply { gradientRadius(radius); gradientRadiusType(DrawableProperties.RADIUS_TYPE_PIXELS) }
|
||||
|
||||
fun gradientRadiusInFraction(radius: Float) =
|
||||
apply { gradientRadius(radius); gradientRadiusType(DrawableProperties.RADIUS_TYPE_FRACTION) }
|
||||
|
||||
fun useLevelForGradient(use: Boolean) = apply { properties.useLevelForGradient = use }
|
||||
fun useLevelForGradient() = apply { useLevelForGradient(true) }
|
||||
|
||||
// <size>
|
||||
fun width(width: Int) = apply { properties.width = width }
|
||||
|
||||
fun height(height: Int) = apply { properties.height = height }
|
||||
fun size(width: Int, height: Int) = apply { width(width); height(height) }
|
||||
fun size(size: Int) = apply { width(size).height(size) }
|
||||
|
||||
// <solid>
|
||||
fun solidColor(solidColor: Int) = apply { properties.solidColor = solidColor }
|
||||
|
||||
private var solidColorPressed: Int? = null
|
||||
fun solidColorPressed(color: Int?) = apply { solidColorPressed = color }
|
||||
private var solidColorPressedWhenRippleUnsupported: Int? = null
|
||||
fun solidColorPressedWhenRippleUnsupported(color: Int?) =
|
||||
apply { solidColorPressedWhenRippleUnsupported = color }
|
||||
|
||||
private var solidColorDisabled: Int? = null
|
||||
fun solidColorDisabled(color: Int?) = apply { solidColorDisabled = color }
|
||||
private var solidColorSelected: Int? = null
|
||||
fun solidColorSelected(color: Int?) = apply { solidColorSelected = color }
|
||||
fun solidColorStateList(colorStateList: ColorStateList) =
|
||||
apply { properties.solidColorStateList = colorStateList }
|
||||
|
||||
// <stroke>
|
||||
fun strokeWidth(strokeWidth: Int) = apply { properties.strokeWidth = strokeWidth }
|
||||
|
||||
fun strokeColor(strokeColor: Int) = apply { properties.strokeColor = strokeColor }
|
||||
private var strokeColorPressed: Int? = null
|
||||
fun strokeColorPressed(color: Int?) = apply { strokeColorPressed = color }
|
||||
private var strokeColorDisabled: Int? = null
|
||||
fun strokeColorDisabled(color: Int?) = apply { strokeColorDisabled = color }
|
||||
private var strokeColorSelected: Int? = null
|
||||
fun strokeColorSelected(color: Int?) = apply { strokeColorSelected = color }
|
||||
fun strokeColorStateList(colorStateList: ColorStateList) =
|
||||
apply { properties.strokeColorStateList = colorStateList }
|
||||
|
||||
fun dashWidth(dashWidth: Int) = apply { properties.dashWidth = dashWidth }
|
||||
fun dashGap(dashGap: Int) = apply { properties.dashGap = dashGap }
|
||||
fun hairlineBordered() = apply { strokeWidth(1) }
|
||||
fun shortDashed() = apply { dashWidth(12).dashGap(12) }
|
||||
fun mediumDashed() = apply { dashWidth(24).dashGap(24) }
|
||||
fun longDashed() = apply { dashWidth(36).dashGap(36) }
|
||||
fun dashed() = apply { mediumDashed() }
|
||||
|
||||
// <rotate>
|
||||
private var rotateOrder = 0
|
||||
|
||||
|
||||
fun rotate(boolean: Boolean = true) = apply {
|
||||
properties.useRotate = boolean
|
||||
rotateOrder = if (boolean) {
|
||||
order.getAndIncrement()
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
fun pivotX(pivotX: Float) = apply { properties.pivotX = pivotX }
|
||||
fun pivotY(pivotY: Float) = apply { properties.pivotY = pivotY }
|
||||
fun pivot(pivotX: Float, pivotY: Float) = apply { pivotX(pivotX).pivotY(pivotY) }
|
||||
fun fromDegrees(degrees: Float) = apply { properties.fromDegrees = degrees }
|
||||
fun toDegrees(degrees: Float) = apply { properties.toDegrees = degrees }
|
||||
fun degrees(fromDegrees: Float, toDegrees: Float) =
|
||||
apply { fromDegrees(fromDegrees).toDegrees(toDegrees) }
|
||||
|
||||
fun degrees(degrees: Float) = apply { fromDegrees(degrees).toDegrees(degrees) }
|
||||
fun rotate(fromDegrees: Float, toDegrees: Float) =
|
||||
apply { rotate().fromDegrees(fromDegrees).toDegrees(toDegrees) }
|
||||
|
||||
fun rotate(degrees: Float) = apply { rotate().degrees(degrees) }
|
||||
|
||||
// <scale>
|
||||
private var scaleOrder = 0
|
||||
|
||||
|
||||
fun scale(boolean: Boolean = true) = apply {
|
||||
properties.useScale = boolean
|
||||
scaleOrder = if (boolean) {
|
||||
order.getAndIncrement()
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
fun scaleLevel(level: Int) = apply { properties.scaleLevel = level }
|
||||
fun scaleGravity(gravity: Int) = apply { properties.scaleGravity = gravity }
|
||||
fun scaleWidth(scale: Float) = apply { properties.scaleWidth = scale }
|
||||
fun scaleHeight(scale: Float) = apply { properties.scaleHeight = scale }
|
||||
fun scale(scale: Float) = apply { scale().scaleWidth(scale).scaleHeight(scale) }
|
||||
fun scale(scaleWidth: Float, scaleHeight: Float) =
|
||||
apply { scale().scaleWidth(scaleWidth).scaleHeight(scaleHeight) }
|
||||
|
||||
// flip
|
||||
|
||||
fun flip(boolean: Boolean = true) = apply { properties.useFlip = boolean }
|
||||
|
||||
fun orientation(orientation: Int) = apply { properties.orientation = orientation }
|
||||
fun flipVertical() = apply { flip().orientation(FlipDrawable.ORIENTATION_VERTICAL) }
|
||||
|
||||
// <ripple>
|
||||
|
||||
fun ripple(boolean: Boolean = true) = apply { properties.useRipple = boolean }
|
||||
|
||||
fun rippleColor(color: Int) = apply { properties.rippleColor = color }
|
||||
fun rippleColorStateList(colorStateList: ColorStateList) =
|
||||
apply { properties.rippleColorStateList = colorStateList }
|
||||
|
||||
fun rippleRadius(radius: Int) = apply { properties.rippleRadius = radius }
|
||||
|
||||
fun build(): Drawable {
|
||||
if (baseDrawable != null) {
|
||||
return wrap(baseDrawable!!)
|
||||
}
|
||||
|
||||
var drawable: Drawable
|
||||
|
||||
// fall back when ripple is unavailable on devices with API < 21
|
||||
if (shouldFallbackRipple()) {
|
||||
if (solidColorPressedWhenRippleUnsupported != null) {
|
||||
solidColorPressed(solidColorPressedWhenRippleUnsupported)
|
||||
} else {
|
||||
solidColorPressed(properties.rippleColor)
|
||||
}
|
||||
}
|
||||
|
||||
if (needStateListDrawable()) {
|
||||
drawable = StateListDrawableBuilder()
|
||||
.pressed(buildPressedDrawable())
|
||||
.disabled(buildDisabledDrawable())
|
||||
.selected(buildSelectedDrawable())
|
||||
.normal(buildNormalDrawable())
|
||||
.build()
|
||||
} else {
|
||||
drawable = GradientDrawable()
|
||||
setupGradientDrawable(drawable)
|
||||
}
|
||||
drawable = wrap(drawable)
|
||||
return drawable
|
||||
}
|
||||
|
||||
private fun getSolidColorStateList(): ColorStateList {
|
||||
if (properties.solidColorStateList != null) {
|
||||
return properties.solidColorStateList!!
|
||||
}
|
||||
|
||||
val states = mutableListOf<IntArray>()
|
||||
val colors = mutableListOf<Int>()
|
||||
|
||||
solidColorPressed?.let {
|
||||
states.add(intArrayOf(android.R.attr.state_pressed))
|
||||
colors.add(it)
|
||||
}
|
||||
solidColorDisabled?.let {
|
||||
states.add(intArrayOf(-android.R.attr.state_enabled))
|
||||
colors.add(it)
|
||||
}
|
||||
solidColorSelected?.let {
|
||||
states.add(intArrayOf(android.R.attr.state_selected))
|
||||
colors.add(it)
|
||||
}
|
||||
states.add(StateSet.WILD_CARD)
|
||||
colors.add(properties.solidColor)
|
||||
|
||||
return ColorStateList(states.toTypedArray(), colors.toIntArray())
|
||||
}
|
||||
|
||||
private fun getStrokeColorStateList(): ColorStateList {
|
||||
if (properties.strokeColorStateList != null) {
|
||||
return properties.strokeColorStateList!!
|
||||
}
|
||||
|
||||
val states = mutableListOf<IntArray>()
|
||||
val colors = mutableListOf<Int>()
|
||||
|
||||
strokeColorPressed?.let {
|
||||
states.add(intArrayOf(android.R.attr.state_pressed))
|
||||
colors.add(it)
|
||||
}
|
||||
strokeColorDisabled?.let {
|
||||
states.add(intArrayOf(-android.R.attr.state_enabled))
|
||||
colors.add(it)
|
||||
}
|
||||
strokeColorSelected?.let {
|
||||
states.add(intArrayOf(android.R.attr.state_selected))
|
||||
colors.add(it)
|
||||
}
|
||||
states.add(StateSet.WILD_CARD)
|
||||
colors.add(properties.strokeColor)
|
||||
|
||||
return ColorStateList(states.toTypedArray(), colors.toIntArray())
|
||||
}
|
||||
|
||||
private fun buildPressedDrawable(): Drawable? {
|
||||
if (solidColorPressed == null && strokeColorPressed == null) return null
|
||||
|
||||
val pressedDrawable = GradientDrawable()
|
||||
setupGradientDrawable(pressedDrawable)
|
||||
solidColorPressed?.let {
|
||||
pressedDrawable.setColor(it)
|
||||
}
|
||||
strokeColorPressed?.let {
|
||||
setStrokeColor(pressedDrawable, it)
|
||||
}
|
||||
return pressedDrawable
|
||||
}
|
||||
|
||||
private fun buildDisabledDrawable(): Drawable? {
|
||||
if (solidColorDisabled == null && strokeColorDisabled == null) return null
|
||||
|
||||
val disabledDrawable = GradientDrawable()
|
||||
setupGradientDrawable(disabledDrawable)
|
||||
solidColorDisabled?.let {
|
||||
disabledDrawable.setColor(it)
|
||||
}
|
||||
strokeColorDisabled?.let {
|
||||
setStrokeColor(disabledDrawable, it)
|
||||
}
|
||||
return disabledDrawable
|
||||
}
|
||||
|
||||
private fun buildSelectedDrawable(): Drawable? {
|
||||
if (solidColorSelected == null && strokeColorSelected == null) return null
|
||||
|
||||
val selectedDrawable = GradientDrawable()
|
||||
setupGradientDrawable(selectedDrawable)
|
||||
solidColorSelected?.let {
|
||||
selectedDrawable.setColor(it)
|
||||
}
|
||||
strokeColorSelected?.let {
|
||||
setStrokeColor(selectedDrawable, it)
|
||||
}
|
||||
return selectedDrawable
|
||||
}
|
||||
|
||||
private fun buildNormalDrawable(): Drawable {
|
||||
val pressedDrawable = GradientDrawable()
|
||||
setupGradientDrawable(pressedDrawable)
|
||||
return pressedDrawable
|
||||
}
|
||||
|
||||
private fun setupGradientDrawable(drawable: GradientDrawable) {
|
||||
properties.apply {
|
||||
drawable.shape = shape
|
||||
if (shape == GradientDrawable.RING) {
|
||||
setInnerRadius(drawable, innerRadius)
|
||||
setInnerRadiusRatio(drawable, innerRadiusRatio)
|
||||
setThickness(drawable, thickness)
|
||||
setThicknessRatio(drawable, thicknessRatio)
|
||||
setUseLevelForShape(drawable, useLevelForRing)
|
||||
}
|
||||
drawable.cornerRadii = getCornerRadii()
|
||||
if (useGradient) {
|
||||
drawable.gradientType = type
|
||||
setGradientRadiusType(drawable, gradientRadiusType)
|
||||
setGradientRadius(drawable, gradientRadius)
|
||||
drawable.setGradientCenter(centerX, centerY)
|
||||
setOrientation(drawable, getOrientation())
|
||||
setColors(drawable, getColors())
|
||||
drawable.useLevel = useLevelForGradient
|
||||
} else {
|
||||
drawable.color = getSolidColorStateList()
|
||||
}
|
||||
drawable.setSize(width, height)
|
||||
drawable.setStroke(
|
||||
strokeWidth,
|
||||
getStrokeColorStateList(),
|
||||
dashWidth.toFloat(),
|
||||
dashGap.toFloat()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun needStateListDrawable(): Boolean {
|
||||
return (hasStrokeColorStateList() || (!properties.useGradient && hasSolidColorStateList()))
|
||||
}
|
||||
|
||||
private fun needRotateDrawable(): Boolean {
|
||||
return properties.useRotate &&
|
||||
!(properties.pivotX == 0.5f && properties.pivotY == 0.5f
|
||||
&& properties.fromDegrees == 0f && properties.toDegrees == 0f)
|
||||
}
|
||||
|
||||
private fun needScaleDrawable(): Boolean {
|
||||
return properties.useScale
|
||||
}
|
||||
|
||||
private fun wrap(drawable: Drawable): Drawable {
|
||||
var wrappedDrawable = drawable
|
||||
|
||||
if (rotateOrder > 0) {
|
||||
transformsMap[rotateOrder] = ::wrapRotateIfNeeded
|
||||
}
|
||||
if (scaleOrder > 0) {
|
||||
transformsMap[scaleOrder] = ::wrapScaleIfNeeded
|
||||
}
|
||||
|
||||
for (action in transformsMap.values) {
|
||||
wrappedDrawable = action.invoke(wrappedDrawable)
|
||||
}
|
||||
|
||||
if (properties.useFlip) {
|
||||
wrappedDrawable = FlipDrawableBuilder()
|
||||
.drawable(wrappedDrawable)
|
||||
.orientation(properties.orientation)
|
||||
.build()
|
||||
}
|
||||
|
||||
if (isRippleSupported() && properties.useRipple) {
|
||||
wrappedDrawable = RippleDrawableBuilder()
|
||||
.drawable(wrappedDrawable)
|
||||
.color(properties.rippleColor)
|
||||
.colorStateList(properties.rippleColorStateList)
|
||||
.radius(properties.rippleRadius)
|
||||
.build()
|
||||
}
|
||||
|
||||
return wrappedDrawable
|
||||
}
|
||||
|
||||
private fun shouldFallbackRipple(): Boolean {
|
||||
return properties.useRipple && !isRippleSupported()
|
||||
}
|
||||
|
||||
private fun isRippleSupported() = true
|
||||
|
||||
private fun wrapRotateIfNeeded(drawable: Drawable): Drawable {
|
||||
if (!needRotateDrawable()) return drawable
|
||||
|
||||
with(properties) {
|
||||
return RotateDrawableBuilder()
|
||||
.drawable(drawable)
|
||||
.pivotX(pivotX)
|
||||
.pivotY(pivotY)
|
||||
.fromDegrees(fromDegrees)
|
||||
.toDegrees(toDegrees)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
||||
private fun wrapScaleIfNeeded(drawable: Drawable): Drawable {
|
||||
if (!needScaleDrawable()) return drawable
|
||||
|
||||
with(properties) {
|
||||
return ScaleDrawableBuilder()
|
||||
.drawable(drawable)
|
||||
.level(scaleLevel)
|
||||
.scaleGravity(scaleGravity)
|
||||
.scaleWidth(scaleWidth)
|
||||
.scaleHeight(scaleHeight)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
||||
private fun hasSolidColorStateList(): Boolean {
|
||||
return solidColorPressed != null || solidColorDisabled != null || solidColorSelected != null
|
||||
}
|
||||
|
||||
private fun hasStrokeColorStateList(): Boolean {
|
||||
return strokeColorPressed != null || strokeColorDisabled != null || strokeColorSelected != null
|
||||
}
|
||||
}
|
@@ -1,221 +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("SetterBackingFieldAssignment", "unused")
|
||||
|
||||
package com.fankes.apperrorstracking.utils.drawable.drawabletoolbox
|
||||
|
||||
import android.content.res.ColorStateList
|
||||
import android.graphics.Color
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.graphics.drawable.GradientDrawable
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
import android.view.Gravity
|
||||
import java.io.Serializable
|
||||
|
||||
data class DrawableProperties(
|
||||
|
||||
// <shape>
|
||||
@JvmField var shape: Int = GradientDrawable.RECTANGLE,
|
||||
@JvmField var innerRadius: Int = -1,
|
||||
@JvmField var innerRadiusRatio: Float = 9f,
|
||||
@JvmField var thickness: Int = -1,
|
||||
@JvmField var thicknessRatio: Float = 3f,
|
||||
@JvmField var useLevelForRing: Boolean = false,
|
||||
|
||||
// <corner>
|
||||
private var _cornerRadius: Int = 0,
|
||||
@JvmField var topLeftRadius: Int = 0,
|
||||
@JvmField var topRightRadius: Int = 0,
|
||||
@JvmField var bottomRightRadius: Int = 0,
|
||||
@JvmField var bottomLeftRadius: Int = 0,
|
||||
|
||||
// <gradient>
|
||||
@JvmField var useGradient: Boolean = false,
|
||||
@JvmField var type: Int = GradientDrawable.RADIAL_GRADIENT,
|
||||
@JvmField var angle: Int = 0,
|
||||
@JvmField var centerX: Float = 0.5f,
|
||||
@JvmField var centerY: Float = 0.5f,
|
||||
@JvmField var useCenterColor: Boolean = false,
|
||||
@JvmField var startColor: Int = Constants.DEFAULT_COLOR,
|
||||
@JvmField var centerColor: Int? = null,
|
||||
@JvmField var endColor: Int = 0x7FFFFFFF,
|
||||
@JvmField var gradientRadiusType: Int = RADIUS_TYPE_FRACTION,
|
||||
@JvmField var gradientRadius: Float = 0.5f,
|
||||
@JvmField var useLevelForGradient: Boolean = false,
|
||||
|
||||
// <size>
|
||||
@JvmField var width: Int = -1,
|
||||
@JvmField var height: Int = -1,
|
||||
|
||||
// <solid>
|
||||
@JvmField var solidColor: Int = Color.TRANSPARENT,
|
||||
@JvmField var solidColorStateList: ColorStateList? = null,
|
||||
|
||||
// <stroke>
|
||||
@JvmField var strokeWidth: Int = 0,
|
||||
@JvmField var strokeColor: Int = Color.DKGRAY,
|
||||
@JvmField var strokeColorStateList: ColorStateList? = null,
|
||||
@JvmField var dashWidth: Int = 0,
|
||||
@JvmField var dashGap: Int = 0,
|
||||
|
||||
// <rotate>
|
||||
@JvmField var useRotate: Boolean = false,
|
||||
@JvmField var pivotX: Float = 0.5f,
|
||||
@JvmField var pivotY: Float = 0.5f,
|
||||
@JvmField var fromDegrees: Float = 0f,
|
||||
@JvmField var toDegrees: Float = 0f,
|
||||
|
||||
// <scale>
|
||||
@JvmField var useScale: Boolean = false,
|
||||
@JvmField var scaleLevel: Int = 10000,
|
||||
@JvmField var scaleGravity: Int = Gravity.CENTER,
|
||||
@JvmField var scaleWidth: Float = 0f,
|
||||
@JvmField var scaleHeight: Float = 0f,
|
||||
|
||||
// flip
|
||||
@JvmField var useFlip: Boolean = false,
|
||||
@JvmField var orientation: Int = FlipDrawable.ORIENTATION_HORIZONTAL,
|
||||
|
||||
// ripple
|
||||
@JvmField var useRipple: Boolean = false,
|
||||
@JvmField var rippleColor: Int = Constants.DEFAULT_COLOR,
|
||||
@JvmField var rippleColorStateList: ColorStateList? = null,
|
||||
@JvmField var rippleRadius: Int = -1
|
||||
) : Serializable {
|
||||
|
||||
companion object {
|
||||
const val RADIUS_TYPE_PIXELS = 0
|
||||
const val RADIUS_TYPE_FRACTION = 1
|
||||
|
||||
@JvmField
|
||||
val CREATOR = object : Parcelable.Creator<DrawableProperties> {
|
||||
override fun createFromParcel(parcel: Parcel): DrawableProperties {
|
||||
return DrawableProperties(parcel)
|
||||
}
|
||||
|
||||
override fun newArray(size: Int): Array<DrawableProperties?> {
|
||||
return arrayOfNulls(size)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var cornerRadius: Int = _cornerRadius
|
||||
set(value) {
|
||||
_cornerRadius = value
|
||||
topLeftRadius = value
|
||||
topRightRadius = value
|
||||
bottomRightRadius = value
|
||||
bottomLeftRadius = value
|
||||
}
|
||||
|
||||
constructor(parcel: Parcel) : this(
|
||||
parcel.readInt(),
|
||||
parcel.readInt(),
|
||||
parcel.readFloat(),
|
||||
parcel.readInt(),
|
||||
parcel.readFloat(),
|
||||
parcel.readByte() != 0.toByte(),
|
||||
parcel.readInt(),
|
||||
parcel.readInt(),
|
||||
parcel.readInt(),
|
||||
parcel.readInt(),
|
||||
parcel.readInt(),
|
||||
parcel.readByte() != 0.toByte(),
|
||||
parcel.readInt(),
|
||||
parcel.readInt(),
|
||||
parcel.readFloat(),
|
||||
parcel.readFloat(),
|
||||
parcel.readByte() != 0.toByte(),
|
||||
parcel.readInt(),
|
||||
parcel.readValue(Int::class.java.classLoader) as? Int,
|
||||
parcel.readInt(),
|
||||
parcel.readInt(),
|
||||
parcel.readFloat(),
|
||||
parcel.readByte() != 0.toByte(),
|
||||
parcel.readInt(),
|
||||
parcel.readInt(),
|
||||
parcel.readInt(),
|
||||
parcel.readParcelable(ColorStateList::class.java.classLoader),
|
||||
parcel.readInt(),
|
||||
parcel.readInt(),
|
||||
parcel.readParcelable(ColorStateList::class.java.classLoader),
|
||||
parcel.readInt(),
|
||||
parcel.readInt(),
|
||||
parcel.readByte() != 0.toByte(),
|
||||
parcel.readFloat(),
|
||||
parcel.readFloat(),
|
||||
parcel.readFloat(),
|
||||
parcel.readFloat(),
|
||||
parcel.readByte() != 0.toByte(),
|
||||
parcel.readInt(),
|
||||
parcel.readInt(),
|
||||
parcel.readFloat(),
|
||||
parcel.readFloat(),
|
||||
parcel.readByte() != 0.toByte(),
|
||||
parcel.readInt(),
|
||||
parcel.readByte() != 0.toByte(),
|
||||
parcel.readInt(),
|
||||
parcel.readParcelable(ColorStateList::class.java.classLoader),
|
||||
parcel.readInt()
|
||||
)
|
||||
|
||||
fun copy(): DrawableProperties {
|
||||
val parcel = Parcel.obtain()
|
||||
parcel.setDataPosition(0)
|
||||
val properties = CREATOR.createFromParcel(parcel)
|
||||
parcel.recycle()
|
||||
return properties
|
||||
}
|
||||
|
||||
fun getCornerRadii(): FloatArray {
|
||||
return floatArrayOf(
|
||||
topLeftRadius.toFloat(), topLeftRadius.toFloat(),
|
||||
topRightRadius.toFloat(), topRightRadius.toFloat(),
|
||||
bottomRightRadius.toFloat(), bottomRightRadius.toFloat(),
|
||||
bottomLeftRadius.toFloat(), bottomLeftRadius.toFloat()
|
||||
)
|
||||
}
|
||||
|
||||
fun getOrientation(): GradientDrawable.Orientation {
|
||||
val orientation: GradientDrawable.Orientation = when (val angle = this.angle % 360) {
|
||||
0 -> GradientDrawable.Orientation.LEFT_RIGHT
|
||||
45 -> GradientDrawable.Orientation.BL_TR
|
||||
90 -> GradientDrawable.Orientation.BOTTOM_TOP
|
||||
135 -> GradientDrawable.Orientation.BR_TL
|
||||
180 -> GradientDrawable.Orientation.RIGHT_LEFT
|
||||
225 -> GradientDrawable.Orientation.TR_BL
|
||||
270 -> GradientDrawable.Orientation.TOP_BOTTOM
|
||||
315 -> GradientDrawable.Orientation.TL_BR
|
||||
else -> throw IllegalArgumentException("Unsupported angle: $angle")
|
||||
}
|
||||
return orientation
|
||||
}
|
||||
|
||||
fun getColors(): IntArray {
|
||||
return if (useCenterColor && centerColor != null) {
|
||||
intArrayOf(startColor, centerColor!!, endColor)
|
||||
} else intArrayOf(startColor, endColor)
|
||||
}
|
||||
|
||||
fun materialization(): Drawable = DrawableBuilder().batch(this).build()
|
||||
}
|
@@ -1,78 +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", "CanvasSize", "OVERRIDE_DEPRECATION")
|
||||
|
||||
package com.fankes.apperrorstracking.utils.drawable.drawabletoolbox
|
||||
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.ColorFilter
|
||||
import android.graphics.Rect
|
||||
import android.graphics.drawable.Drawable
|
||||
|
||||
class FlipDrawable(
|
||||
private var drawable: Drawable,
|
||||
private var orientation: Int = ORIENTATION_HORIZONTAL
|
||||
) : Drawable() {
|
||||
|
||||
companion object {
|
||||
const val ORIENTATION_HORIZONTAL = 0
|
||||
const val ORIENTATION_VERTICAL = 1
|
||||
}
|
||||
|
||||
override fun draw(canvas: Canvas) {
|
||||
val saveCount = canvas.save()
|
||||
if (orientation == ORIENTATION_VERTICAL) {
|
||||
canvas.scale(1f, -1f, (canvas.width / 2).toFloat(), (canvas.height / 2).toFloat())
|
||||
} else {
|
||||
canvas.scale(-1f, 1f, (canvas.width / 2).toFloat(), (canvas.height / 2).toFloat())
|
||||
}
|
||||
drawable.bounds = Rect(0, 0, canvas.width, canvas.height)
|
||||
drawable.draw(canvas)
|
||||
canvas.restoreToCount(saveCount)
|
||||
}
|
||||
|
||||
override fun onLevelChange(level: Int): Boolean {
|
||||
drawable.level = level
|
||||
invalidateSelf()
|
||||
return true
|
||||
}
|
||||
|
||||
override fun getIntrinsicWidth(): Int {
|
||||
return drawable.intrinsicWidth
|
||||
}
|
||||
|
||||
override fun getIntrinsicHeight(): Int {
|
||||
return drawable.intrinsicHeight
|
||||
}
|
||||
|
||||
override fun setAlpha(alpha: Int) {
|
||||
drawable.alpha = alpha
|
||||
}
|
||||
|
||||
override fun getOpacity(): Int {
|
||||
return drawable.opacity
|
||||
}
|
||||
|
||||
override fun setColorFilter(colorFilter: ColorFilter?) {
|
||||
drawable.colorFilter = colorFilter
|
||||
}
|
||||
}
|
@@ -1,181 +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.
|
||||
*/
|
||||
package com.fankes.apperrorstracking.utils.drawable.drawabletoolbox
|
||||
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.graphics.drawable.LayerDrawable
|
||||
import android.os.Build
|
||||
import android.view.Gravity
|
||||
import androidx.annotation.RequiresApi
|
||||
|
||||
class LayerDrawableBuilder {
|
||||
|
||||
companion object {
|
||||
const val DIMEN_UNDEFINED = Int.MIN_VALUE
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
private var paddingMode = LayerDrawable.PADDING_MODE_NEST
|
||||
private var paddingLeft = 0
|
||||
private var paddingTop = 0
|
||||
private var paddingRight = 0
|
||||
private var paddingBottom = 0
|
||||
private var paddingStart = 0
|
||||
private var paddingEnd = 0
|
||||
private val layers = ArrayList<Layer>()
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
fun paddingMode(mode: Int) = apply { paddingMode = mode }
|
||||
fun paddingLeft(padding: Int) = apply { paddingLeft = padding }
|
||||
fun paddingTop(padding: Int) = apply { paddingTop = padding }
|
||||
fun paddingRight(padding: Int) = apply { paddingRight = padding }
|
||||
fun paddingBottom(padding: Int) = apply { paddingBottom = padding }
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
fun paddingStart(padding: Int) = apply { paddingStart = padding }
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
fun paddingEnd(padding: Int) = apply { paddingEnd = padding }
|
||||
fun padding(padding: Int) = apply {
|
||||
paddingLeft(padding).paddingTop(padding).paddingRight(padding).paddingBottom(padding)
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
fun paddingRelative(padding: Int) = apply {
|
||||
paddingStart(padding).paddingTop(padding).paddingEnd(padding).paddingBottom(padding)
|
||||
}
|
||||
|
||||
fun add(drawable: Drawable) = apply { layers.add(Layer(drawable)) }
|
||||
|
||||
fun width(width: Int) = apply { layers.last().width = width }
|
||||
fun height(height: Int) = apply { layers.last().height = height }
|
||||
|
||||
fun insetLeft(inset: Int) = apply { layers.last().insetLeft = inset }
|
||||
fun insetTop(inset: Int) = apply { layers.last().insetTop = inset }
|
||||
fun insetRight(inset: Int) = apply { layers.last().insetRight = inset }
|
||||
fun insetBottom(inset: Int) = apply { layers.last().insetBottom = inset }
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
fun insetStart(inset: Int) = apply { layers.last().insetStart = inset }
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
fun insetEnd(inset: Int) = apply { layers.last().insetEnd = inset }
|
||||
fun inset(inset: Int) =
|
||||
apply { insetLeft(inset).insetTop(inset).insetRight(inset).insetBottom(inset) }
|
||||
|
||||
fun inset(insetLeft: Int, insetTop: Int, insetRight: Int, insetBottom: Int) = apply {
|
||||
insetLeft(insetLeft).insetTop(insetTop).insetRight(insetRight).insetBottom(insetBottom)
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
fun insetRelative(inset: Int) =
|
||||
apply { insetStart(inset).insetTop(inset).insetEnd(inset).insetBottom(inset) }
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
fun insetRelative(insetStart: Int, insetTop: Int, insetEnd: Int, insetBottom: Int) = apply {
|
||||
insetStart(insetStart).insetTop(insetTop).insetEnd(insetEnd).insetBottom(insetBottom)
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
fun gravity(gravity: Int) = apply { layers.last().gravity = gravity }
|
||||
|
||||
fun modify(
|
||||
index: Int, drawable: Drawable,
|
||||
width: Int = DIMEN_UNDEFINED,
|
||||
height: Int = DIMEN_UNDEFINED,
|
||||
insetLeft: Int = DIMEN_UNDEFINED,
|
||||
insetTop: Int = DIMEN_UNDEFINED,
|
||||
insetRight: Int = DIMEN_UNDEFINED,
|
||||
insetBottom: Int = DIMEN_UNDEFINED,
|
||||
insetStart: Int = DIMEN_UNDEFINED,
|
||||
insetEnd: Int = DIMEN_UNDEFINED
|
||||
) =
|
||||
apply {
|
||||
val layer = layers[index]
|
||||
layer.drawable = drawable
|
||||
if (width != DIMEN_UNDEFINED) layer.width = width
|
||||
if (height != DIMEN_UNDEFINED) layer.height = height
|
||||
if (insetLeft != DIMEN_UNDEFINED) layer.insetLeft = insetLeft
|
||||
if (insetTop != DIMEN_UNDEFINED) layer.insetTop = insetTop
|
||||
if (insetRight != DIMEN_UNDEFINED) layer.insetRight = insetRight
|
||||
if (insetBottom != DIMEN_UNDEFINED) layer.insetBottom = insetBottom
|
||||
if (insetStart != DIMEN_UNDEFINED) layer.insetStart = insetStart
|
||||
if (insetEnd != DIMEN_UNDEFINED) layer.insetEnd = insetEnd
|
||||
}
|
||||
|
||||
fun build(): LayerDrawable {
|
||||
val layerDrawable = LayerDrawable(layers.map { it -> it.drawable }.toTypedArray())
|
||||
for (i in 0 until layers.size) {
|
||||
val layer = layers[i]
|
||||
layerDrawable.setLayerInset(
|
||||
i,
|
||||
layer.insetLeft,
|
||||
layer.insetTop,
|
||||
layer.insetRight,
|
||||
layer.insetBottom
|
||||
)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
if (layer.insetStart != DIMEN_UNDEFINED || layer.insetEnd != DIMEN_UNDEFINED) {
|
||||
layerDrawable.setLayerInsetRelative(
|
||||
i,
|
||||
layer.insetStart,
|
||||
layer.insetTop,
|
||||
layer.insetEnd,
|
||||
layer.insetBottom
|
||||
)
|
||||
}
|
||||
}
|
||||
layerDrawable.setId(i, i)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
layerDrawable.setLayerGravity(i, layer.gravity)
|
||||
layerDrawable.setLayerInsetStart(i, layer.insetStart)
|
||||
layerDrawable.setLayerInsetEnd(i, layer.insetEnd)
|
||||
}
|
||||
}
|
||||
layerDrawable.paddingMode = paddingMode
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
layerDrawable.setPadding(paddingLeft, paddingTop, paddingRight, paddingBottom)
|
||||
if (paddingStart != DIMEN_UNDEFINED || paddingEnd != DIMEN_UNDEFINED) {
|
||||
layerDrawable.setPaddingRelative(
|
||||
paddingStart,
|
||||
paddingTop,
|
||||
paddingEnd,
|
||||
paddingBottom
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return layerDrawable
|
||||
}
|
||||
|
||||
internal class Layer(var drawable: Drawable) {
|
||||
var gravity: Int = Gravity.NO_GRAVITY
|
||||
var width: Int = -1
|
||||
var height: Int = -1
|
||||
var insetLeft: Int = 0
|
||||
var insetTop: Int = 0
|
||||
var insetRight: Int = 0
|
||||
var insetBottom: Int = 0
|
||||
var insetStart: Int = DIMEN_UNDEFINED
|
||||
var insetEnd: Int = DIMEN_UNDEFINED
|
||||
}
|
||||
}
|
@@ -1,61 +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.
|
||||
*/
|
||||
package com.fankes.apperrorstracking.utils.drawable.drawabletoolbox
|
||||
|
||||
import android.graphics.Path
|
||||
import android.graphics.drawable.ShapeDrawable
|
||||
import android.graphics.drawable.shapes.PathShape
|
||||
|
||||
class PathShapeDrawableBuilder {
|
||||
|
||||
private var path: Path? = null
|
||||
private var pathStandardWidth: Float = 100f
|
||||
private var pathStandardHeight: Float = 100f
|
||||
private var width: Int = -1
|
||||
private var height: Int = -1
|
||||
|
||||
fun path(path: Path, pathStandardWidth: Float, pathStandardHeight: Float) = apply {
|
||||
this.path = path
|
||||
this.pathStandardWidth = pathStandardWidth
|
||||
this.pathStandardHeight = pathStandardHeight
|
||||
}
|
||||
|
||||
fun width(width: Int) = apply { this.width = width }
|
||||
fun height(height: Int) = apply { this.height = height }
|
||||
fun size(size: Int) = apply { width(size).height(size) }
|
||||
|
||||
fun build(custom: ((shapeDrawable: ShapeDrawable) -> Unit)? = null): ShapeDrawable {
|
||||
val shapeDrawable = ShapeDrawable()
|
||||
if (path == null || width <= 0 || height <= 0) {
|
||||
return shapeDrawable
|
||||
}
|
||||
val pathShape = PathShape(path!!, pathStandardWidth, pathStandardHeight)
|
||||
|
||||
shapeDrawable.shape = pathShape
|
||||
shapeDrawable.intrinsicWidth = width
|
||||
shapeDrawable.intrinsicHeight = height
|
||||
if (custom != null) {
|
||||
custom(shapeDrawable)
|
||||
}
|
||||
return shapeDrawable
|
||||
}
|
||||
}
|
@@ -1,73 +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.
|
||||
*/
|
||||
package com.fankes.apperrorstracking.utils.drawable.drawabletoolbox
|
||||
|
||||
import android.content.res.ColorStateList
|
||||
import android.graphics.Color
|
||||
import android.graphics.drawable.*
|
||||
import android.util.StateSet
|
||||
|
||||
class RippleDrawableBuilder : DrawableWrapperBuilder<RippleDrawableBuilder>() {
|
||||
|
||||
private var color: Int = Constants.DEFAULT_COLOR
|
||||
private var colorStateList: ColorStateList? = null
|
||||
private var radius: Int = -1
|
||||
|
||||
fun color(color: Int) = apply { this.color = color }
|
||||
fun colorStateList(colorStateList: ColorStateList?) =
|
||||
apply { this.colorStateList = colorStateList }
|
||||
|
||||
fun radius(radius: Int) = apply { this.radius = radius }
|
||||
|
||||
override fun build(): Drawable {
|
||||
var drawable = this.drawable!!
|
||||
val colorStateList = this.colorStateList ?: ColorStateList(
|
||||
arrayOf(StateSet.WILD_CARD),
|
||||
intArrayOf(color)
|
||||
)
|
||||
|
||||
var mask = if (drawable is DrawableContainer) drawable.getCurrent() else drawable
|
||||
if (mask is ShapeDrawable) {
|
||||
val state = mask.getConstantState()
|
||||
if (state != null) {
|
||||
val temp = state.newDrawable().mutate() as ShapeDrawable
|
||||
temp.paint.color = Color.BLACK
|
||||
mask = temp
|
||||
}
|
||||
} else if (mask is GradientDrawable) {
|
||||
val state = mask.getConstantState()
|
||||
if (state != null) {
|
||||
val temp = state.newDrawable().mutate() as GradientDrawable
|
||||
temp.setColor(Color.BLACK)
|
||||
mask = temp
|
||||
}
|
||||
} else {
|
||||
mask = ColorDrawable(Color.BLACK)
|
||||
}
|
||||
|
||||
val rippleDrawable = RippleDrawable(colorStateList, drawable, mask)
|
||||
setRadius(rippleDrawable, radius)
|
||||
rippleDrawable.invalidateSelf()
|
||||
drawable = rippleDrawable
|
||||
return drawable
|
||||
}
|
||||
}
|
@@ -1,52 +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.
|
||||
*/
|
||||
package com.fankes.apperrorstracking.utils.drawable.drawabletoolbox
|
||||
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.graphics.drawable.RotateDrawable
|
||||
|
||||
class RotateDrawableBuilder : DrawableWrapperBuilder<RotateDrawableBuilder>() {
|
||||
|
||||
private var pivotX: Float = 0.5f
|
||||
private var pivotY: Float = 0.5f
|
||||
private var fromDegrees: Float = 0f
|
||||
private var toDegrees: Float = 360f
|
||||
|
||||
fun pivotX(x: Float) = apply { pivotX = x }
|
||||
fun pivotY(y: Float) = apply { pivotY = y }
|
||||
fun fromDegrees(degree: Float) = apply { fromDegrees = degree }
|
||||
fun toDegrees(degree: Float) = apply { toDegrees = degree }
|
||||
|
||||
override fun build(): Drawable {
|
||||
val rotateDrawable = RotateDrawable()
|
||||
drawable?.let {
|
||||
setDrawable(rotateDrawable, it)
|
||||
apply {
|
||||
setPivotX(rotateDrawable, pivotX)
|
||||
setPivotY(rotateDrawable, pivotY)
|
||||
setFromDegrees(rotateDrawable, fromDegrees)
|
||||
setToDegrees(rotateDrawable, toDegrees)
|
||||
}
|
||||
}
|
||||
return rotateDrawable
|
||||
}
|
||||
}
|
@@ -1,45 +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.
|
||||
*/
|
||||
package com.fankes.apperrorstracking.utils.drawable.drawabletoolbox
|
||||
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.graphics.drawable.ScaleDrawable
|
||||
import android.view.Gravity
|
||||
|
||||
class ScaleDrawableBuilder : DrawableWrapperBuilder<ScaleDrawableBuilder>() {
|
||||
|
||||
private var level: Int = 10000
|
||||
private var scaleGravity = Gravity.CENTER
|
||||
private var scaleWidth: Float = 0f
|
||||
private var scaleHeight: Float = 0f
|
||||
|
||||
fun level(level: Int) = apply { this.level = level }
|
||||
fun scaleGravity(gravity: Int) = apply { this.scaleGravity = gravity }
|
||||
fun scaleWidth(scale: Float) = apply { this.scaleWidth = scale }
|
||||
fun scaleHeight(scale: Float) = apply { this.scaleHeight = scale }
|
||||
|
||||
override fun build(): Drawable {
|
||||
val scaleDrawable = ScaleDrawable(drawable, scaleGravity, scaleWidth, scaleHeight)
|
||||
scaleDrawable.level = level
|
||||
return scaleDrawable
|
||||
}
|
||||
}
|
@@ -1,60 +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.
|
||||
*/
|
||||
package com.fankes.apperrorstracking.utils.drawable.drawabletoolbox
|
||||
|
||||
import android.graphics.Color
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.graphics.drawable.StateListDrawable
|
||||
import android.util.StateSet
|
||||
|
||||
class StateListDrawableBuilder {
|
||||
|
||||
private var pressed: Drawable? = null
|
||||
private var disabled: Drawable? = null
|
||||
private var selected: Drawable? = null
|
||||
private var normal: Drawable = ColorDrawable(Color.TRANSPARENT)
|
||||
|
||||
fun pressed(pressed: Drawable?) = apply { this.pressed = pressed }
|
||||
fun disabled(disabled: Drawable?) = apply { this.disabled = disabled }
|
||||
fun selected(selected: Drawable?) = apply { this.selected = selected }
|
||||
fun normal(normal: Drawable) = apply { this.normal = normal }
|
||||
|
||||
fun build(): StateListDrawable {
|
||||
val stateListDrawable = StateListDrawable()
|
||||
setupStateListDrawable(stateListDrawable)
|
||||
return stateListDrawable
|
||||
}
|
||||
|
||||
private fun setupStateListDrawable(stateListDrawable: StateListDrawable) {
|
||||
pressed?.let {
|
||||
stateListDrawable.addState(intArrayOf(android.R.attr.state_pressed), it)
|
||||
}
|
||||
disabled?.let {
|
||||
stateListDrawable.addState(intArrayOf(-android.R.attr.state_enabled), it)
|
||||
}
|
||||
selected?.let {
|
||||
stateListDrawable.addState(intArrayOf(android.R.attr.state_selected), it)
|
||||
}
|
||||
stateListDrawable.addState(StateSet.WILD_CARD, normal)
|
||||
}
|
||||
}
|
@@ -1,229 +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/12.
|
||||
*/
|
||||
@file:Suppress("unused", "DEPRECATION", "OPT_IN_USAGE", "EXPERIMENTAL_API_USAGE")
|
||||
|
||||
package com.fankes.apperrorstracking.utils.factory
|
||||
|
||||
import android.app.Dialog
|
||||
import android.content.Context
|
||||
import android.graphics.Color
|
||||
import android.graphics.drawable.GradientDrawable
|
||||
import android.graphics.drawable.InsetDrawable
|
||||
import android.view.Gravity
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.ProgressBar
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.viewbinding.ViewBinding
|
||||
import com.fankes.apperrorstracking.locale.LocaleString
|
||||
import com.fankes.apperrorstracking.ui.activity.errors.AppErrorsDisplayActivity
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.shape.MaterialShapeDrawable
|
||||
import com.highcapable.yukihookapi.annotation.CauseProblemsApi
|
||||
import com.highcapable.yukihookapi.hook.factory.method
|
||||
import com.highcapable.yukihookapi.hook.type.android.LayoutInflaterClass
|
||||
|
||||
/**
|
||||
* 构造 [VB] 自定义 View 对话框
|
||||
* @param initiate 对话框方法体
|
||||
*/
|
||||
@JvmName(name = "showDialog-VB")
|
||||
inline fun <reified VB : ViewBinding> Context.showDialog(initiate: DialogBuilder<VB>.() -> Unit) =
|
||||
DialogBuilder<VB>(context = this, VB::class.java).apply(initiate).show()
|
||||
|
||||
/**
|
||||
* 构造对话框
|
||||
* @param initiate 对话框方法体
|
||||
*/
|
||||
inline fun Context.showDialog(initiate: DialogBuilder<*>.() -> Unit) = DialogBuilder<ViewBinding>(context = this).apply(initiate).show()
|
||||
|
||||
/**
|
||||
* 对话框构造器
|
||||
* @param context 实例
|
||||
* @param bindingClass [ViewBinding] 的 [Class] 实例 or null
|
||||
*/
|
||||
class DialogBuilder<VB : ViewBinding>(val context: Context, private val bindingClass: Class<*>? = null) {
|
||||
|
||||
private var instanceAndroidX: androidx.appcompat.app.AlertDialog.Builder? = null // 实例对象
|
||||
private var instanceAndroid: android.app.AlertDialog.Builder? = null // 实例对象
|
||||
|
||||
private var onCancel: (() -> Unit)? = null // 对话框取消监听
|
||||
private var dialogInstance: Dialog? = null // 对话框实例
|
||||
private var customLayoutView: View? = null // 自定义布局
|
||||
|
||||
/**
|
||||
* 获取 [DialogBuilder] 绑定布局对象
|
||||
* @return [VB]
|
||||
*/
|
||||
val binding by lazy {
|
||||
bindingClass?.method {
|
||||
name = "inflate"
|
||||
param(LayoutInflaterClass)
|
||||
}?.get()?.invoke<VB>(LayoutInflater.from(context))?.apply {
|
||||
customLayoutView = root
|
||||
} ?: error("This dialog maybe not a custom view dialog")
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否需要使用 AndroidX 风格对话框
|
||||
* @return [Boolean]
|
||||
*/
|
||||
private val isUsingAndroidX get() = runCatching { context is AppCompatActivity }.getOrNull() ?: false
|
||||
|
||||
init {
|
||||
if (isUsingAndroidX) runCatching {
|
||||
instanceAndroidX = MaterialAlertDialogBuilder(context).also { builder ->
|
||||
if (context is AppErrorsDisplayActivity)
|
||||
builder.background = (builder.background as MaterialShapeDrawable).apply { setCornerSize(15.dpFloat(context)) }
|
||||
}
|
||||
} else runCatching {
|
||||
instanceAndroid = android.app.AlertDialog.Builder(
|
||||
context,
|
||||
if (context.isSystemInDarkMode) android.R.style.Theme_Material_Dialog else android.R.style.Theme_Material_Light_Dialog
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** 设置对话框不可关闭 */
|
||||
fun noCancelable() {
|
||||
if (isUsingAndroidX)
|
||||
runCatching { instanceAndroidX?.setCancelable(false) }
|
||||
else runCatching { instanceAndroid?.setCancelable(false) }
|
||||
}
|
||||
|
||||
/** 设置对话框标题 */
|
||||
var title
|
||||
get() = ""
|
||||
set(value) {
|
||||
if (isUsingAndroidX)
|
||||
runCatching { instanceAndroidX?.setTitle(value) }
|
||||
else runCatching { instanceAndroid?.setTitle(value) }
|
||||
}
|
||||
|
||||
/** 设置对话框消息内容 */
|
||||
var msg
|
||||
get() = ""
|
||||
set(value) {
|
||||
if (isUsingAndroidX)
|
||||
runCatching { instanceAndroidX?.setMessage(value) }
|
||||
else runCatching { instanceAndroid?.setMessage(value) }
|
||||
}
|
||||
|
||||
/** 设置进度条对话框消息内容 */
|
||||
var progressContent
|
||||
get() = ""
|
||||
set(value) {
|
||||
if (customLayoutView == null)
|
||||
customLayoutView = LinearLayout(context).apply {
|
||||
orientation = LinearLayout.HORIZONTAL
|
||||
gravity = Gravity.CENTER or Gravity.START
|
||||
addView(ProgressBar(context))
|
||||
addView(View(context).apply { layoutParams = ViewGroup.LayoutParams(20.dp(context), 5) })
|
||||
addView(TextView(context).apply {
|
||||
tag = "progressContent"
|
||||
text = value
|
||||
})
|
||||
setPadding(20.dp(context), 20.dp(context), 20.dp(context), 20.dp(context))
|
||||
}
|
||||
else customLayoutView?.findViewWithTag<TextView>("progressContent")?.text = value
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置对话框确定按钮
|
||||
* @param text 按钮文本内容
|
||||
* @param callback 点击事件
|
||||
*/
|
||||
fun confirmButton(text: String = LocaleString.confirm, callback: () -> Unit = {}) {
|
||||
if (isUsingAndroidX)
|
||||
runCatching { instanceAndroidX?.setPositiveButton(text) { _, _ -> callback() } }
|
||||
else runCatching { instanceAndroid?.setPositiveButton(text) { _, _ -> callback() } }
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置对话框取消按钮
|
||||
* @param text 按钮文本内容
|
||||
* @param callback 点击事件
|
||||
*/
|
||||
fun cancelButton(text: String = LocaleString.cancel, callback: () -> Unit = {}) {
|
||||
if (isUsingAndroidX)
|
||||
runCatching { instanceAndroidX?.setNegativeButton(text) { _, _ -> callback() } }
|
||||
else runCatching { instanceAndroid?.setNegativeButton(text) { _, _ -> callback() } }
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置对话框第三个按钮
|
||||
* @param text 按钮文本内容
|
||||
* @param callback 点击事件
|
||||
*/
|
||||
fun neutralButton(text: String = LocaleString.more, callback: () -> Unit = {}) {
|
||||
if (isUsingAndroidX)
|
||||
runCatching { instanceAndroidX?.setNeutralButton(text) { _, _ -> callback() } }
|
||||
else runCatching { instanceAndroid?.setNeutralButton(text) { _, _ -> callback() } }
|
||||
}
|
||||
|
||||
/**
|
||||
* 当对话框关闭时
|
||||
* @param callback 回调
|
||||
*/
|
||||
fun onCancel(callback: () -> Unit) {
|
||||
onCancel = callback
|
||||
}
|
||||
|
||||
/** 取消对话框 */
|
||||
fun cancel() = dialogInstance?.cancel()
|
||||
|
||||
/** 显示对话框 */
|
||||
@CauseProblemsApi
|
||||
fun show() {
|
||||
/** 若当前自定义 View 的对话框没有调用 [binding] 将会对其手动调用一次以确保显示布局 */
|
||||
if (bindingClass != null) binding
|
||||
if (isUsingAndroidX) runCatching {
|
||||
instanceAndroidX?.create()?.apply {
|
||||
customLayoutView?.let { setView(it) }
|
||||
dialogInstance = this
|
||||
setOnCancelListener { onCancel?.invoke() }
|
||||
}?.show()
|
||||
} else runCatching {
|
||||
instanceAndroid?.create()?.apply {
|
||||
customLayoutView?.let { setView(it) }
|
||||
window?.setBackgroundDrawable(
|
||||
InsetDrawable(
|
||||
GradientDrawable(
|
||||
GradientDrawable.Orientation.TOP_BOTTOM,
|
||||
if (context.isSystemInDarkMode) intArrayOf(0xFF2D2D2D.toInt(), 0xFF2D2D2D.toInt())
|
||||
else intArrayOf(Color.WHITE, Color.WHITE)
|
||||
).apply {
|
||||
shape = GradientDrawable.RECTANGLE
|
||||
gradientType = GradientDrawable.LINEAR_GRADIENT
|
||||
cornerRadius = 15.dpFloat(this@DialogBuilder.context)
|
||||
}, 30.dp(context), 0, 30.dp(context), 0
|
||||
)
|
||||
)
|
||||
dialogInstance = this
|
||||
setOnCancelListener { onCancel?.invoke() }
|
||||
}?.show()
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,311 +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", "PrivateApi", "unused", "ObsoleteSdkInt")
|
||||
|
||||
package com.fankes.apperrorstracking.utils.factory
|
||||
|
||||
import android.app.*
|
||||
import android.content.*
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.res.Configuration
|
||||
import android.content.res.Resources
|
||||
import android.graphics.Color
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.net.Uri
|
||||
import android.provider.Settings
|
||||
import android.widget.Toast
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.content.getSystemService
|
||||
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.google.android.material.snackbar.Snackbar
|
||||
import com.highcapable.yukihookapi.hook.factory.field
|
||||
import com.highcapable.yukihookapi.hook.type.android.ApplicationInfoClass
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import java.math.RoundingMode
|
||||
import java.text.DecimalFormat
|
||||
|
||||
/**
|
||||
* 系统深色模式是否开启
|
||||
* @return [Boolean] 是否开启
|
||||
*/
|
||||
val Context.isSystemInDarkMode get() = (resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES
|
||||
|
||||
/**
|
||||
* 系统深色模式是否没开启
|
||||
* @return [Boolean] 是否开启
|
||||
*/
|
||||
inline val Context.isNotSystemInDarkMode get() = !isSystemInDarkMode
|
||||
|
||||
/**
|
||||
* dp 转换为 pxInt
|
||||
* @param context 使用的实例
|
||||
* @return [Int]
|
||||
*/
|
||||
fun Number.dp(context: Context) = dpFloat(context).toInt()
|
||||
|
||||
/**
|
||||
* dp 转换为 pxFloat
|
||||
* @param context 使用的实例
|
||||
* @return [Float]
|
||||
*/
|
||||
fun Number.dpFloat(context: Context) = toFloat() * context.resources.displayMetrics.density
|
||||
|
||||
/**
|
||||
* 从 [Int] resId 获取 [Drawable]
|
||||
* @param resources 使用的 [Resources]
|
||||
* @return [Drawable]
|
||||
*/
|
||||
fun Int.drawableOf(resources: Resources) = ResourcesCompat.getDrawable(resources, this, null) ?: error("Invalid resources")
|
||||
|
||||
/**
|
||||
* 获取 APP 名称
|
||||
* @param packageName 包名
|
||||
* @return [String]
|
||||
*/
|
||||
fun Context.appName(packageName: String) =
|
||||
runCatching {
|
||||
packageManager.getPackageInfo(packageName, PackageManager.GET_META_DATA)
|
||||
.applicationInfo.loadLabel(packageManager).toString()
|
||||
}.getOrNull() ?: packageName
|
||||
|
||||
/**
|
||||
* 获取 APP 完整版本
|
||||
* @param packageName 包名
|
||||
* @return [String]
|
||||
*/
|
||||
fun Context.appVersion(packageName: String) =
|
||||
runCatching {
|
||||
packageManager.getPackageInfo(packageName, PackageManager.GET_META_DATA)?.let { "${it.versionName} (${it.versionCode})" }
|
||||
}.getOrNull() ?: "<unknown>"
|
||||
|
||||
/**
|
||||
* 获取 APP CPU ABI 名称
|
||||
* @param packageName 包名
|
||||
* @return [String]
|
||||
*/
|
||||
fun Context.appCpuAbi(packageName: String) =
|
||||
runCatching {
|
||||
ApplicationInfoClass.field { name = "primaryCpuAbi" }
|
||||
.get(packageManager.getPackageInfo(packageName, PackageManager.GET_META_DATA)?.applicationInfo).string()
|
||||
}.getOrNull() ?: ""
|
||||
|
||||
/**
|
||||
* 获取 APP 图标
|
||||
* @param packageName 包名
|
||||
* @return [Drawable]
|
||||
*/
|
||||
fun Context.appIcon(packageName: String) =
|
||||
runCatching {
|
||||
packageManager.getPackageInfo(packageName, PackageManager.GET_META_DATA)
|
||||
.applicationInfo.loadIcon(packageManager)
|
||||
}.getOrNull() ?: R.drawable.ic_android.drawableOf(resources)
|
||||
|
||||
/**
|
||||
* 计算与当前时间戳相差的友好时间
|
||||
* @param now 刚刚
|
||||
* @param second 秒前
|
||||
* @param minute 分钟前
|
||||
* @param hour 小时前
|
||||
* @param day 天前
|
||||
* @param month 月前
|
||||
* @param year 年前
|
||||
* @return [String] 友好时间
|
||||
*/
|
||||
fun Long.difference(now: String, second: String, minute: String, hour: String, day: String, month: String, year: String) =
|
||||
((System.currentTimeMillis() - this) / 1000).toInt().let { diff ->
|
||||
when (diff) {
|
||||
in 0..10 -> now
|
||||
in 11..20 -> "10 $second"
|
||||
in 21..30 -> "20 $second"
|
||||
in 31..40 -> "30 $second"
|
||||
in 41..50 -> "40 $second"
|
||||
in 51..59 -> "50 $second"
|
||||
in 60..3599 -> "${(diff / 60).coerceAtLeast(1)} $minute"
|
||||
in 3600..86399 -> "${diff / 3600} $hour"
|
||||
in 86400..2591999 -> "${diff / 86400} $day"
|
||||
in 2592000..31103999 -> "${diff / 2592000} $month"
|
||||
else -> "${diff / 31104000} $year"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保留小数
|
||||
* @param count 要保留的位数 - 默认 2 位 - 最多 7 位
|
||||
* @return [String] 得到的字符串数字 - 格式化失败返回原始数字的字符串
|
||||
*/
|
||||
fun Number.decimal(count: Int = 2) = runCatching {
|
||||
DecimalFormat(
|
||||
when (count) {
|
||||
0 -> "0"
|
||||
1 -> "0.0"
|
||||
2 -> "0.00"
|
||||
3 -> "0.000"
|
||||
4 -> "0.0000"
|
||||
5 -> "0.00000"
|
||||
6 -> "0.000000"
|
||||
7 -> "0.0000000"
|
||||
else -> "0.0"
|
||||
}
|
||||
).apply { roundingMode = RoundingMode.HALF_UP }.format(this) ?: toString()
|
||||
}.getOrNull() ?: this
|
||||
|
||||
/**
|
||||
* 弹出 [Toast]
|
||||
* @param msg 提示内容
|
||||
*/
|
||||
fun Context.toast(msg: String) = Toast.makeText(this, msg, Toast.LENGTH_SHORT).show()
|
||||
|
||||
/**
|
||||
* 弹出 [Snackbar]
|
||||
* @param msg 提示内容
|
||||
* @param actionText 按钮文本 - 不写默认取消按钮
|
||||
* @param callback 按钮事件回调
|
||||
*/
|
||||
fun Context.snake(msg: String, actionText: String = "", callback: () -> Unit = {}) =
|
||||
Snackbar.make((this as Activity).findViewById(android.R.id.content), msg, Snackbar.LENGTH_LONG).apply {
|
||||
if (actionText.isBlank()) return@apply
|
||||
setActionTextColor(if (isSystemInDarkMode) Color.BLACK else Color.WHITE)
|
||||
setAction(actionText) { callback() }
|
||||
}.show()
|
||||
|
||||
/**
|
||||
* 推送通知
|
||||
* @param channelId 渠道 Id
|
||||
* @param channelName 渠道名称
|
||||
* @param title 标题
|
||||
* @param content 内容
|
||||
* @param icon 图标
|
||||
* @param color 颜色
|
||||
* @param intent [Intent]
|
||||
*/
|
||||
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))
|
||||
notify((0..999).random(), NotificationCompat.Builder(this@pushNotify, channelId).apply {
|
||||
this.color = color
|
||||
setAutoCancel(true)
|
||||
setContentTitle(title)
|
||||
setContentText(content)
|
||||
setSmallIcon(icon)
|
||||
setContentIntent(PendingIntent.getActivity(this@pushNotify, (0..999).random(), intent, PendingIntent.FLAG_IMMUTABLE))
|
||||
setDefaults(NotificationCompat.DEFAULT_ALL)
|
||||
}.build())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 跳转到指定页面
|
||||
*
|
||||
* [T] 为指定的 [Activity]
|
||||
* @param isOutSide 是否从外部启动
|
||||
* @param initiate [Intent] 方法体
|
||||
*/
|
||||
inline fun <reified T : Activity> Context.navigate(isOutSide: Boolean = false, initiate: Intent.() -> Unit = {}) = runCatching {
|
||||
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)
|
||||
initiate(this)
|
||||
})
|
||||
}.onFailure { toast(msg = "Start ${T::class.java.name} failed") }
|
||||
|
||||
/**
|
||||
* 复制到剪贴板
|
||||
* @param content 要复制的文本
|
||||
*/
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 跳转 APP 自身设置界面
|
||||
* @param packageName 包名
|
||||
*/
|
||||
fun Context.openSelfSetting(packageName: String = this.packageName) = runCatching {
|
||||
startActivity(Intent().apply {
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS
|
||||
data = Uri.fromParts("package", packageName, null)
|
||||
})
|
||||
}.onFailure { toast(msg = "Cannot open '$packageName'") }
|
||||
|
||||
/**
|
||||
* 启动系统浏览器
|
||||
* @param url 网址
|
||||
* @param packageName 指定包名 - 可不填
|
||||
*/
|
||||
fun Context.openBrowser(url: String, packageName: String = "") = runCatching {
|
||||
startActivity(Intent().apply {
|
||||
if (packageName.isNotBlank()) setPackage(packageName)
|
||||
action = Intent.ACTION_VIEW
|
||||
data = Uri.parse(url)
|
||||
/** 防止顶栈一样重叠在自己的 APP 中 */
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
})
|
||||
}.onFailure {
|
||||
if (packageName.isNotBlank()) snake(msg = "Cannot start '$packageName'")
|
||||
else snake(msg = "Start system browser failed")
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前 APP 是否可被启动
|
||||
* @param packageName 包名
|
||||
*/
|
||||
fun Context.isAppCanOpened(packageName: String = this.packageName) =
|
||||
runCatching { packageManager?.getLaunchIntentForPackage(packageName) != null }.getOrNull() ?: false
|
||||
|
||||
/**
|
||||
* 启动指定 APP
|
||||
* @param packageName 包名
|
||||
*/
|
||||
fun Context.openApp(packageName: String = this.packageName) = runCatching {
|
||||
startActivity(packageManager.getLaunchIntentForPackage(packageName)?.apply {
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
})
|
||||
}.onFailure { toast(msg = "Cannot start '$packageName'") }
|
||||
|
||||
/**
|
||||
* 是否有 Root 权限
|
||||
* @return [Boolean]
|
||||
*/
|
||||
val isRootAccess get() = runCatching { Shell.rootAccess() }.getOrNull() ?: false
|
||||
|
||||
/**
|
||||
* 执行命令
|
||||
* @param cmd 命令
|
||||
* @param isSu 是否使用 Root 权限执行 - 默认:是
|
||||
* @return [String] 执行结果
|
||||
*/
|
||||
fun execShell(cmd: String, isSu: Boolean = true) = runCatching {
|
||||
(if (isSu) Shell.su(cmd) else Shell.sh(cmd)).exec().out.let {
|
||||
if (it.isNotEmpty()) it[0].trim() else ""
|
||||
}
|
||||
}.getOrNull() ?: ""
|
@@ -1,9 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="150dp"
|
||||
android:height="150dp"
|
||||
android:viewportWidth="1024"
|
||||
android:viewportHeight="1024">
|
||||
<path
|
||||
android:pathData="M851.5,239.1l-66.6,24.6v499.7l66.6,24.6c29.7,10.8 55.3,-17.9 55.3,-65V304.1c0,-47.1 -25.6,-76.3 -55.3,-65M362.5,374.8l-185.3,23.6c-7.2,1 -13.3,-10.2 -13.3,-24.6s5.6,-27.1 13.3,-28.7l185.3,-35.8c11.3,-2 20.5,10.8 20.5,29.2 -0.5,18.9 -9.7,34.8 -20.5,36.4M164.4,503.8c0,-14.3 5.6,-26.6 13.3,-26.6l93.7,-3.1c9.2,-0.5 16.9,12.8 16.9,29.7s-7.7,30.2 -16.9,29.7l-93.7,-3.1c-7.7,0 -13.3,-11.8 -13.3,-26.6m135.2,182.8l-122.4,-23.6c-7.2,-1.5 -13.3,-14.3 -13.3,-28.7s5.6,-25.6 13.3,-24.6l122.4,15.9c9.7,1 17.9,16.4 17.9,33.3s-8.2,29.2 -17.9,27.6M414.2,110.1L145.4,209.9c-22,8.2 -38.9,50.7 -38.9,95.7v397.3c0,45.1 16.9,87.6 38.9,95.7l268.8,99.8c42,15.9 78.8,-25.6 78.8,-93.2V203.3c0,-67.6 -36.9,-109.1 -78.8,-93.2m250.4,129L558.1,278.5v470.5l106.5,39.4c29.7,10.8 55.3,-17.9 55.3,-65V304.1c0,-47.1 -26.1,-76.3 -55.3,-65"
|
||||
android:fillColor="#ffffff"/>
|
||||
</vector>
|
@@ -1,9 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="150dp"
|
||||
android:height="150dp"
|
||||
android:viewportWidth="1024"
|
||||
android:viewportHeight="1024">
|
||||
<path
|
||||
android:pathData="M785.8,927.4h-64.9c-9.7,0 -16.9,-9.7 -14.3,-19.5 0,-0.6 0,-0.6 0.6,-1.3 4.5,-16.9 -18.1,-26.6 -27.2,-11.7 -7.2,11 -13,20.2 -16.9,26a14.8,14.8 0,0 1,-12.3 6.5h-56.5c-9.7,0 -16.9,-9.7 -14.3,-19.5 0,-0.6 0,-0.6 0.6,-1.3 4.5,-16.9 -18.2,-26.6 -27.3,-11.7 -7.1,11 -13,20.2 -16.9,26a14.8,14.8 0,0 1,-12.3 6.5h-55.8c-9.7,0 -16.9,-9.7 -14.3,-19.5 0,-0.6 0,-0.6 0.6,-1.3 4.5,-16.9 -18.2,-26.6 -27.3,-11.7 -7.1,11 -13,20.2 -16.9,26a14.8,14.8 0,0 1,-12.3 6.5H360c-9.7,0 -16.9,-9.7 -14.3,-19.5 0,-0.6 0,-0.6 0.6,-1.3 4.5,-16.9 -18.2,-26.6 -27.3,-11.7 -7.1,11 -13,20.2 -16.9,26a14.8,14.8 0,0 1,-12.4 6.5H207.5a13.1,13.1 0,0 1,-7.1 -1.9c-43.5,-25.3 -62.9,-84.4 0.6,-138.9 42.2,-36.4 46.7,-94.8 45.4,-147.3 0,-8.4 6.5,-15.6 14.9,-15.6h526.4c7.1,0 13,4.5 14.3,11.7 14.9,64.9 20.1,116.2 32.4,205.8 8.4,65.5 -48.7,86.3 -48.7,86.3zM215.3,570.4c-20.1,0 -29.2,-14.9 -20.8,-32.5l70.8,-150.6c8.4,-18.2 31.8,-32.5 51.3,-32.5h113.6a14.7,14.7 0,0 0,14.9 -14.9V147.8c0.6,-28.6 23.4,-51.3 51.3,-51.3h42.2c27.9,0 51.3,22.7 51.3,51.3v190.8c0,8.4 6.5,14.9 14.9,14.9h109.7c20.1,0 42.8,14.9 51.3,32.4l70.7,150.6c8.5,18.2 -0.6,32.4 -20.7,32.4l-600.4,1.3z"
|
||||
android:fillColor="#ffffff"/>
|
||||
</vector>
|
@@ -1,9 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="250dp"
|
||||
android:height="250dp"
|
||||
android:viewportWidth="1024"
|
||||
android:viewportHeight="1024">
|
||||
<path
|
||||
android:pathData="M511,0a376.4,376.4 0,0 0,-265.1 106.8,365.1 365.1,0 0,0 -108,258.6v498.7h742L880,365.5a362.4,362.4 0,0 0,-108.9 -258.6A374.9,374.9 0,0 0,511 0zM480.3,744.6L469.5,502.9L286.9,502.9l239.9,-303.1 20.5,195.2 174.1,6.1 -243.2,343.3zM0.1,966.4a55.6,55.6 0,0 1,55.6 -56.2h910.6a57.6,57.6 0,0 1,39.3 16.3,58.7 58.7,0 0,1 16.4,39.9 55.7,55.7 0,0 1,-55.7 55.6L55.6,1022.1a55.6,55.6 0,0 1,-55.5 -55.6z"
|
||||
android:fillColor="#ffffff"/>
|
||||
</vector>
|
@@ -1,16 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108"
|
||||
tools:ignore="VectorRaster">
|
||||
<group android:scaleX="0.038671874"
|
||||
android:scaleY="0.038671874"
|
||||
android:translateX="34.2"
|
||||
android:translateY="34.2">
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M1024,577.8c-1,34.9 -30.4,62.2 -65.3,62.2H848v32c0,43.7 -9.8,85.2 -27.2,122.3l120.5,120.5c25,25 25,65.5 0,90.5 -25,25 -65.5,25 -90.5,0l-109.5,-109.5C691.8,935.9 628.7,960 560,960V472c0,-13.3 -10.7,-24 -24,-24h-48c-13.3,0 -24,10.7 -24,24v488c-68.7,0 -131.8,-24.1 -181.3,-64.2l-109.5,109.5c-25,25 -65.5,25 -90.5,0 -25,-25 -25,-65.5 0,-90.5l120.5,-120.5C185.8,757.2 176,715.7 176,672v-32H65.3C30.5,640 1,612.7 0,577.8 -1,541.6 28.1,512 64,512h112v-117.5l-93.3,-93.3c-25,-25 -25,-65.5 0,-90.5 25,-25 65.5,-25 90.5,0L282.5,320h459l109.3,-109.3c25,-25 65.5,-25 90.5,0 25,25 25,65.5 0,90.5L848,394.5V512h112c35.9,0 65,29.6 64,65.8zM514,0c-123.7,0 -224,100.3 -224,224h448C738,100.3 637.7,0 514,0z" />
|
||||
</group>
|
||||
</vector>
|
@@ -1,9 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="150dp"
|
||||
android:height="150dp"
|
||||
android:viewportWidth="1024"
|
||||
android:viewportHeight="1024">
|
||||
<path
|
||||
android:pathData="M851.2,957.9L179.2,957.9c-61.9,0 -110.9,-49.1 -110.9,-110.9L68.3,454.4c0,-32 25.6,-55.5 55.5,-55.5h55.5v224c0,32 25.6,55.5 55.5,55.5h558.9c32,0 55.5,-25.6 55.5,-55.5L849.1,398.9h55.5c32,0 55.5,25.6 55.5,55.5v390.4c2.1,64 -46.9,113.1 -108.8,113.1zM738.1,622.9h-448c-32,0 -55.5,-25.6 -55.5,-55.5v-448C234.7,89.6 260.3,64 290.1,64h448c32,0 55.5,25.6 55.5,55.5v448c0,29.9 -23.5,55.5 -55.5,55.5zM657.1,232.5L377.6,232.5c-19.2,0 -32,19.2 -32,32s12.8,25.6 32,32h279.5c19.2,0 32,-12.8 32,-32s-19.2,-32 -32,-32zM657.1,398.9L377.6,398.9c-19.2,0 -32,12.8 -32,32 0,12.8 19.2,25.6 32,25.6h279.5c19.2,0 32,-12.8 32,-25.6 0,-19.2 -19.2,-32 -32,-32z"
|
||||
android:fillColor="#ffffff"/>
|
||||
</vector>
|
@@ -1,9 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="150dp"
|
||||
android:height="150dp"
|
||||
android:viewportWidth="1024"
|
||||
android:viewportHeight="1024">
|
||||
<path
|
||||
android:pathData="M579.2,736.9l-218,-118.9a149.3,149.3 0,1 1,0 -212l218,-118.9a149.3,149.3 0,1 1,40.9 74.9l-218,118.9a149.9,149.9 0,0 1,0 62.2l218,118.9a149.3,149.3 0,1 1,-40.9 74.9z"
|
||||
android:fillColor="#ffffff"/>
|
||||
</vector>
|
@@ -1,154 +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="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:padding="10dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/process_name_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:ellipsize="end"
|
||||
android:paddingLeft="14dp"
|
||||
android:paddingRight="14dp"
|
||||
android:singleLine="true"
|
||||
android:textColor="@color/colorTextDark"
|
||||
android:textSize="12sp"
|
||||
android:visibility="gone" />
|
||||
|
||||
<com.fankes.apperrorstracking.ui.view.ItemLinearLayout
|
||||
android:id="@+id/app_info_item"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingHorizontal="10dp"
|
||||
android:paddingVertical="15dp">
|
||||
|
||||
<androidx.constraintlayout.utils.widget.ImageFilterView
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
android:layout_marginEnd="15dp"
|
||||
android:padding="1dp"
|
||||
android:src="@drawable/ic_baseline_info"
|
||||
app:tint="@color/colorTextGray" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/app_info"
|
||||
android:textColor="@color/colorTextGray"
|
||||
android:textSize="15sp" />
|
||||
</com.fankes.apperrorstracking.ui.view.ItemLinearLayout>
|
||||
|
||||
<com.fankes.apperrorstracking.ui.view.ItemLinearLayout
|
||||
android:id="@+id/close_app_item"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingHorizontal="10dp"
|
||||
android:paddingVertical="15dp">
|
||||
|
||||
<androidx.constraintlayout.utils.widget.ImageFilterView
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
android:layout_marginEnd="15dp"
|
||||
android:src="@drawable/ic_baseline_close"
|
||||
app:tint="@color/colorTextGray" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/close_app"
|
||||
android:textColor="@color/colorTextGray"
|
||||
android:textSize="15sp" />
|
||||
</com.fankes.apperrorstracking.ui.view.ItemLinearLayout>
|
||||
|
||||
<com.fankes.apperrorstracking.ui.view.ItemLinearLayout
|
||||
android:id="@+id/reopen_app_item"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingHorizontal="10dp"
|
||||
android:paddingVertical="15dp">
|
||||
|
||||
<androidx.constraintlayout.utils.widget.ImageFilterView
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
android:layout_marginEnd="15dp"
|
||||
android:src="@drawable/ic_baseline_refresh"
|
||||
app:tint="@color/colorTextGray" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/reopen_app"
|
||||
android:textColor="@color/colorTextGray"
|
||||
android:textSize="15sp" />
|
||||
</com.fankes.apperrorstracking.ui.view.ItemLinearLayout>
|
||||
|
||||
<com.fankes.apperrorstracking.ui.view.ItemLinearLayout
|
||||
android:id="@+id/error_detail_item"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingHorizontal="10dp"
|
||||
android:paddingVertical="15dp">
|
||||
|
||||
<androidx.constraintlayout.utils.widget.ImageFilterView
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
android:layout_marginEnd="15dp"
|
||||
android:src="@drawable/ic_baseline_bug_report"
|
||||
app:tint="@color/colorTextGray" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/error_detail"
|
||||
android:textColor="@color/colorTextGray"
|
||||
android:textSize="15sp" />
|
||||
</com.fankes.apperrorstracking.ui.view.ItemLinearLayout>
|
||||
|
||||
<com.fankes.apperrorstracking.ui.view.ItemLinearLayout
|
||||
android:id="@+id/muted_if_unlock_item"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingHorizontal="10dp"
|
||||
android:paddingVertical="15dp">
|
||||
|
||||
<androidx.constraintlayout.utils.widget.ImageFilterView
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
android:layout_marginEnd="15dp"
|
||||
android:src="@drawable/ic_baseline_eject"
|
||||
app:tint="@color/colorTextGray" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/mute_if_unlock"
|
||||
android:textColor="@color/colorTextGray"
|
||||
android:textSize="15sp" />
|
||||
</com.fankes.apperrorstracking.ui.view.ItemLinearLayout>
|
||||
|
||||
<com.fankes.apperrorstracking.ui.view.ItemLinearLayout
|
||||
android:id="@+id/muted_if_restart_item"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingHorizontal="10dp"
|
||||
android:paddingVertical="15dp">
|
||||
|
||||
<androidx.constraintlayout.utils.widget.ImageFilterView
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
android:layout_marginEnd="15dp"
|
||||
android:src="@drawable/ic_baseline_eject"
|
||||
app:tint="@color/colorTextGray" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/mute_if_restart"
|
||||
android:textColor="@color/colorTextGray"
|
||||
android:textSize="15sp" />
|
||||
</com.fankes.apperrorstracking.ui.view.ItemLinearLayout>
|
||||
</LinearLayout>
|
@@ -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>
|
Before Width: | Height: | Size: 4.3 KiB |
Before Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 6.4 KiB |
Before Width: | Height: | Size: 5.7 KiB |
Before Width: | Height: | Size: 5.8 KiB |
Before Width: | Height: | Size: 4.9 KiB |
Before Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 4.8 KiB |
Before Width: | Height: | Size: 4.8 KiB |
Before Width: | Height: | Size: 4.0 KiB |
@@ -1,120 +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_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">アプリ名とパッケージ名を入力できます</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>
|
||||
</resources>
|
@@ -1,120 +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>
|
||||
</resources>
|
@@ -1,120 +0,0 @@
|
||||
<resources>
|
||||
<string name="app_name">AppErrorsTracking</string>
|
||||
<string name="xposed_desc">Added more features to app\'s errors dialog, fixed custom rom deleted dialog, the best experience to Android developer.</string>
|
||||
<string name="empty_lable" translatable="false" />
|
||||
<string name="app_info">App Info</string>
|
||||
<string name="reopen_app">Reopen App</string>
|
||||
<string name="error_detail">Error Detail</string>
|
||||
<string name="mute_if_unlock">Mute until device unlocks</string>
|
||||
<string name="mute_if_restart">Mute until device restarts</string>
|
||||
<string name="close_app">Close App</string>
|
||||
<string name="aerr_title">%1$s has stopped</string>
|
||||
<string name="aerr_repeated_title">%1$s keeps stopping</string>
|
||||
<string name="mute_if_unlock_tip">Muted errors for \'%1$s\' until device is re-unlocked</string>
|
||||
<string name="mute_if_restart_tip">Muted errors for \'%1$s\' until device reboots</string>
|
||||
<string name="back">Back</string>
|
||||
<string name="copy_error_stack">Copy error stack</string>
|
||||
<string name="export_to_file">Export to file</string>
|
||||
<string name="error_info">Error Info</string>
|
||||
<string name="error_type">Error Type</string>
|
||||
<string name="error_file_name">File Name</string>
|
||||
<string name="error_throw_class">Throw Class</string>
|
||||
<string name="error_throw_method">Throw Method</string>
|
||||
<string name="error_line_number">Line Number</string>
|
||||
<string name="error_record_time">Record Time</string>
|
||||
<string name="copied">Copied</string>
|
||||
<string name="copy_fail">Copy failed</string>
|
||||
<string name="print_to_logcat">Print to logcat</string>
|
||||
<string name="print_to_logcat_success">Printed to logcat</string>
|
||||
<string name="output_stack_success">Export exception stack succeeded</string>
|
||||
<string name="output_stack_fail">Failed to export exception stack</string>
|
||||
<string name="export_all">Export all</string>
|
||||
<string name="clear_all">Clear all</string>
|
||||
<string name="errors_record">App Errors Record</string>
|
||||
<string name="no_list_data">No data for now</string>
|
||||
<string name="confirm">Confirm</string>
|
||||
<string name="cancel">Cancel</string>
|
||||
<string name="more">More</string>
|
||||
<string name="notice">Notice</string>
|
||||
<string name="are_you_sure_clear_errors">Are you sure you want to clear all errors records?</string>
|
||||
<string name="all_errors_clear_success">All errors records have been cleared</string>
|
||||
<string name="are_you_sure_export_all_errors">Are you sure you want to export all log files? The packaging process may take a while.</string>
|
||||
<string name="view_detail">View detail</string>
|
||||
<string name="export_all_errors_success">All errors record exported</string>
|
||||
<string name="export_all_errors_fail">Failed to exported all errors record</string>
|
||||
<string name="no_cpu_abi">No ABI</string>
|
||||
<string name="remove_record">Remove record</string>
|
||||
<string name="are_you_sure_remove_record">Are you sure you want to remove this record?</string>
|
||||
<string name="got_it">Got it</string>
|
||||
<string name="restart_system">Restart system</string>
|
||||
<string name="project_address">Project address</string>
|
||||
<string name="module_not_activated">Module not activated</string>
|
||||
<string name="module_version">Module version: %1$s</string>
|
||||
<string name="system_version">System version: %1$s</string>
|
||||
<string name="module_is_activated">Module is activated</string>
|
||||
<string name="display_settings">Display settings</string>
|
||||
<string name="hide_app_icon_on_launcher">Hide app icons on launcher</string>
|
||||
<string name="hide_app_icon_on_launcher_tip">After hiding the app icon, the interface may be closed and will no longer be displayed on the launcher. You can find and open the module settings in EdXposed or LSPosed.</string>
|
||||
<string name="hide_app_icon_on_launcher_notice">Note: Be sure to turn off the \"Force apps to show launcher icons\" feature in LSPosed</string>
|
||||
<string name="about_module">This module is made by YukiHookAPI. \nLearn more https://github.com/fankes/YukiHookAPI</string>
|
||||
<string name="module_not_fully_activated">Module not fully activated</string>
|
||||
<string name="are_your_sure_restart_system">Are you sure you want to restart system?</string>
|
||||
<string name="fast_restart">Fast restart</string>
|
||||
<string name="access_root_fail">Access Root failed</string>
|
||||
<string name="moment_ago">moment ago</string>
|
||||
<string name="second_ago">second ago</string>
|
||||
<string name="minute_ago">minutes ago</string>
|
||||
<string name="hour_ago">hour ago</string>
|
||||
<string name="day_ago">days ago</string>
|
||||
<string name="month_ago">month ago</string>
|
||||
<string name="year_ago">year ago</string>
|
||||
<string name="crash_process">Crashed Process \"%1$s\"</string>
|
||||
<string name="share_error_stack">Share error stack</string>
|
||||
<string name="preference_settings">Preference Settings</string>
|
||||
<string name="function_mgr">Function Management</string>
|
||||
<string name="only_show_errors_in_front">Show error dialogs only foreground apps</string>
|
||||
<string name="only_show_errors_in_main_process">Show error dialogs only main process apps</string>
|
||||
<string name="enable_apps_config_template">Enable apps config template</string>
|
||||
<string name="mgr_apps_config_template">Management apps config template</string>
|
||||
<string name="view_errors_record">View app errors record</string>
|
||||
<string name="view_muted_errors_apps">View muted errors apps</string>
|
||||
<string name="only_show_errors_in_front_tip">After enabling, the error dialog will only be displayed when the abnormal apps is in the foreground (in use). Although the abnormal background application will not display the error dialog, they will be recorded in the errors history.</string>
|
||||
<string name="only_show_errors_in_main_process_tip">After enabling, the error dialog will only be displayed if the exception occurred in the application is in the main process (the first Application instance object).</string>
|
||||
<string name="apps_config_template_tip">Here you can individually configure whether to display an error dialog and other error tips when an exception occurs for each apps.</string>
|
||||
<string name="view_errors_record_tip">Here you can find all apps errors records since the system was turned on until now. The exception history will be automatically cleared after restarting. You can view, export, share and clear the records.</string>
|
||||
<string name="view_muted_errors_apps_tip">Here you can find apps that you have manually muted errors in different forms. This list will be automatically cleared after restarting. You can manage these ignored applications and remove them from the mute list.</string>
|
||||
<string name="muted_errors_apps">Muted Errors Apps</string>
|
||||
<string name="unmute">Unmute</string>
|
||||
<string name="unmute_all">Unmute all</string>
|
||||
<string name="are_you_sure_unmute_all">Are you sure you want to unmute all apps?</string>
|
||||
<string name="apps_config_template">Apps Config Template</string>
|
||||
<string name="filter_by_condition">Filter by condition</string>
|
||||
<string name="no_list_result">No result to display</string>
|
||||
<string name="typo_app_name_pkg_name">Typo apps name or package name</string>
|
||||
<string name="result_contains_system_apps">Result contains system apps</string>
|
||||
<string name="clear_filters">Clear filters</string>
|
||||
<string name="result_count">%1$s results found</string>
|
||||
<string name="loading">Loading</string>
|
||||
<string name="when_errors_how_to_show_tip">You can configure how the system will show you errors when the apps crashes.</string>
|
||||
<string name="show_errors_dialog">Show errors dialog</string>
|
||||
<string name="show_errors_toast">Show errors Toast</string>
|
||||
<string name="show_nothing">Don\'t show anything</string>
|
||||
<string name="app_errors_statistics">App errors statistics</string>
|
||||
<string name="total_errors">Total errors</string>
|
||||
<string name="total_apps">Total apps</string>
|
||||
<string name="most_errors_app">The most errors app</string>
|
||||
<string name="most_errors_type">The most errors type</string>
|
||||
<string name="total_proportion_of_errors">Total proportion of errors</string>
|
||||
<string name="total_errors_unit">%1$s</string>
|
||||
<string name="total_apps_unit">%1$s (including system apps)</string>
|
||||
<string name="generating_statistics">Generating statistics</string>
|
||||
<string name="module_not_fully_activated_tip">The module is not fully activated and may not be able to load the current settings item. It is recommended to re -start the system and try it out.</string>
|
||||
<string name="show_errors_notify">Show errors notification</string>
|
||||
<string name="app_errors_tip">App collapses an unprocessed abnormality. Click to view details or feedback to the App developer.</string>
|
||||
<string name="batch_operations">Batch operations</string>
|
||||
<string name="are_you_sure_apply_site_apps">Are you sure you want to apply settings to %1$s apps at once?</string>
|
||||
<string name="errors_dialog_always_show_reopen">Error dialog always shows \"Reopen App\"</string>
|
||||
<string name="errors_dialog_always_show_reopen_tip">After enabling, the \"Reopen App\" option will also be shows when the app is not errors for the first time. If the current errors is not the main process or the app cannot be opened, this option will still not be showing.</string>
|
||||
<string name="developer_notice_tip">This module is specially designed for Android developers.\n\nWhen it is possible that the computer cannot be connected and ADB cannot be performed, this module can be used to quickly capture any exception of any installed apps, so as to quickly locate the problem.\n\nThe 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.</string>
|
||||
<string name="developer_notice">Instructions</string>
|
||||
</resources>
|
11
build.gradle
@@ -1,11 +0,0 @@
|
||||
plugins {
|
||||
id 'com.android.application' version '7.2.1' apply false
|
||||
id 'com.android.library' version '7.2.1' apply false
|
||||
id 'org.jetbrains.kotlin.android' version '1.7.0' apply false
|
||||
}
|
||||
|
||||
ext {
|
||||
appVersionName = "1.0"
|
||||
appVersionCode = 1
|
||||
enableR8 = true
|
||||
}
|
21
build.gradle.kts
Normal 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
@@ -1 +0,0 @@
|
||||
/build
|
@@ -1,78 +0,0 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'org.jetbrains.kotlin.android'
|
||||
}
|
||||
|
||||
android {
|
||||
namespace 'com.fankes.apperrorsdemo'
|
||||
compileSdk 32
|
||||
ndkVersion '24.0.8215888'
|
||||
|
||||
signingConfigs {
|
||||
debug {
|
||||
storeFile file('../keystore/public')
|
||||
storePassword '123456'
|
||||
keyAlias 'public'
|
||||
keyPassword '123456'
|
||||
v1SigningEnabled true
|
||||
v2SigningEnabled true
|
||||
}
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
minSdk 27
|
||||
targetSdk 32
|
||||
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.0.92'
|
||||
implementation 'androidx.core:core-ktx:1.8.0'
|
||||
implementation 'androidx.appcompat:appcompat:1.4.2'
|
||||
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
@@ -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)
|
||||
}
|
@@ -1,19 +1,28 @@
|
||||
<?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"
|
||||
android:exported="true"
|
||||
android:screenOrientation="behind">
|
||||
android:exported="true">
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
@@ -25,7 +34,6 @@
|
||||
<activity
|
||||
android:name=".ui.activity.MainActivity$MultiProcessActivity"
|
||||
android:exported="false"
|
||||
android:process=":multi_process"
|
||||
android:screenOrientation="behind" />
|
||||
android:process=":multi_process" />
|
||||
</application>
|
||||
</manifest>
|
@@ -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
|
||||
|
||||
|
@@ -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
|
||||
|
||||
|
@@ -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 { onBackPressed() }
|
||||
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() }
|
||||
|
@@ -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.method
|
||||
import com.highcapable.yukihookapi.hook.type.android.LayoutInflaterClass
|
||||
import java.lang.reflect.ParameterizedType
|
||||
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,15 +41,13 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
javaClass.genericSuperclass.also { type ->
|
||||
if (type is ParameterizedType) {
|
||||
binding = (type.actualTypeArguments[0] as Class<VB>).method {
|
||||
name = "inflate"
|
||||
param(LayoutInflaterClass)
|
||||
}.get().invoke<VB>(layoutInflater) ?: error("binding failed")
|
||||
setContentView(binding.root)
|
||||
} else error("binding but got wrong type")
|
||||
}
|
||||
val bindingClass = javaClass.genericSuperclassTypeArguments().firstOrNull()?.toClassOrNull()
|
||||
binding = bindingClass?.resolve()?.optional()?.firstMethodOrNull {
|
||||
name = "inflate"
|
||||
parameters(LayoutInflater::class)
|
||||
}?.invoke<VB>(layoutInflater) ?: error("binding failed")
|
||||
if (Build.VERSION.SDK_INT >= 35) binding.root.fitsSystemWindows = true
|
||||
setContentView(binding.root)
|
||||
/** 隐藏系统的标题栏 */
|
||||
supportActionBar?.hide()
|
||||
/** 初始化沉浸状态栏 */
|
||||
@@ -58,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
|
||||
|
@@ -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
|
||||
|
||||
|
9
demo-app/src/main/res/drawable/ic_back.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="192dp"
|
||||
android:height="150.39165dp"
|
||||
android:viewportWidth="1307"
|
||||
android:viewportHeight="1024">
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M268.7,566.5h929.6c36.3,0 72.6,-29 72.6,-72.6 0,-36.3 -29,-72.6 -72.6,-72.6H305l297.8,-297.8c29,-29 29,-72.6 0,-101.7 -29,-29 -72.6,-29 -101.7,0L72.6,450.3c-14.5,14.5 -21.8,36.3 -21.8,58.1 0,21.8 0,43.6 21.8,58.1l428.5,428.5c29,29 72.6,29 101.7,0 29,-29 29,-72.6 0,-101.7l-334.1,-326.8z" />
|
||||
</vector>
|
@@ -5,12 +5,13 @@
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108"
|
||||
tools:ignore="VectorRaster">
|
||||
<group android:scaleX="0.038671874"
|
||||
android:scaleY="0.038671874"
|
||||
android:translateX="34.2"
|
||||
android:translateY="34.2">
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M1024,577.8c-1,34.9 -30.4,62.2 -65.3,62.2H848v32c0,43.7 -9.8,85.2 -27.2,122.3l120.5,120.5c25,25 25,65.5 0,90.5 -25,25 -65.5,25 -90.5,0l-109.5,-109.5C691.8,935.9 628.7,960 560,960V472c0,-13.3 -10.7,-24 -24,-24h-48c-13.3,0 -24,10.7 -24,24v488c-68.7,0 -131.8,-24.1 -181.3,-64.2l-109.5,109.5c-25,25 -65.5,25 -90.5,0 -25,-25 -25,-65.5 0,-90.5l120.5,-120.5C185.8,757.2 176,715.7 176,672v-32H65.3C30.5,640 1,612.7 0,577.8 -1,541.6 28.1,512 64,512h112v-117.5l-93.3,-93.3c-25,-25 -25,-65.5 0,-90.5 25,-25 65.5,-25 90.5,0L282.5,320h459l109.3,-109.3c25,-25 65.5,-25 90.5,0 25,25 25,65.5 0,90.5L848,394.5V512h112c35.9,0 65,29.6 64,65.8zM514,0c-123.7,0 -224,100.3 -224,224h448C738,100.3 637.7,0 514,0z" />
|
||||
</group>
|
||||
<group
|
||||
android:scaleX="0.038671874"
|
||||
android:scaleY="0.038671874"
|
||||
android:translateX="34.2"
|
||||
android:translateY="34.2">
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M1024,577.8c-1,34.9 -30.4,62.2 -65.3,62.2H848v32c0,43.7 -9.8,85.2 -27.2,122.3l120.5,120.5c25,25 25,65.5 0,90.5 -25,25 -65.5,25 -90.5,0l-109.5,-109.5C691.8,935.9 628.7,960 560,960V472c0,-13.3 -10.7,-24 -24,-24h-48c-13.3,0 -24,10.7 -24,24v488c-68.7,0 -131.8,-24.1 -181.3,-64.2l-109.5,109.5c-25,25 -65.5,25 -90.5,0 -25,-25 -25,-65.5 0,-90.5l120.5,-120.5C185.8,757.2 176,715.7 176,672v-32H65.3C30.5,640 1,612.7 0,577.8 -1,541.6 28.1,512 64,512h112v-117.5l-93.3,-93.3c-25,-25 -25,-65.5 0,-90.5 25,-25 65.5,-25 90.5,0L282.5,320h459l109.3,-109.3c25,-25 65.5,-25 90.5,0 25,25 25,65.5 0,90.5L848,394.5V512h112c35.9,0 65,29.6 64,65.8zM514,0c-123.7,0 -224,100.3 -224,224h448C738,100.3 637.7,0 514,0z" />
|
||||
</group>
|
||||
</vector>
|
@@ -25,11 +25,12 @@
|
||||
android:layout_height="20dp"
|
||||
android:layout_marginStart="10dp"
|
||||
android:layout_marginEnd="20dp"
|
||||
android:src="@mipmap/ic_back"
|
||||
android:src="@drawable/ic_back"
|
||||
android:tint="@color/colorTextGray"
|
||||
android:tooltipText="@string/back" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title_text"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="2.5dp"
|
||||
@@ -42,7 +43,7 @@
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="10dp"
|
||||
android:layout_marginTop="5dp"
|
||||
@@ -55,93 +56,108 @@
|
||||
android:textColor="@color/colorTextDark"
|
||||
android:textSize="13sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/throw_runtime_button"
|
||||
<androidx.core.widget.NestedScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="10dp"
|
||||
android:layout_marginRight="10dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:background="@drawable/bg_button_round"
|
||||
android:gravity="center"
|
||||
android:padding="10dp"
|
||||
android:singleLine="true"
|
||||
android:text="@string/throw_runtime"
|
||||
android:textColor="@color/colorTextGray"
|
||||
android:textSize="15sp" />
|
||||
android:fadingEdgeLength="10dp"
|
||||
android:fillViewport="true"
|
||||
android:paddingBottom="10dp"
|
||||
android:requiresFadingEdge="vertical"
|
||||
android:scrollbars="none">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/throw_illegal_state_button"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="10dp"
|
||||
android:layout_marginRight="10dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:background="@drawable/bg_button_round"
|
||||
android:gravity="center"
|
||||
android:padding="10dp"
|
||||
android:singleLine="true"
|
||||
android:text="@string/throw_illegalstate"
|
||||
android:textColor="@color/colorTextGray"
|
||||
android:textSize="15sp" />
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/throw_null_pointer_button"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="10dp"
|
||||
android:layout_marginRight="10dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:background="@drawable/bg_button_round"
|
||||
android:gravity="center"
|
||||
android:padding="10dp"
|
||||
android:singleLine="true"
|
||||
android:text="@string/throw_nullpointer"
|
||||
android:textColor="@color/colorTextGray"
|
||||
android:textSize="15sp" />
|
||||
<TextView
|
||||
android:id="@+id/throw_runtime_button"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="10dp"
|
||||
android:layout_marginRight="10dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:background="@drawable/bg_button_round"
|
||||
android:gravity="center"
|
||||
android:padding="10dp"
|
||||
android:singleLine="true"
|
||||
android:text="@string/throw_runtime"
|
||||
android:textColor="@color/colorTextGray"
|
||||
android:textSize="15sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/throw_exception_button"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="10dp"
|
||||
android:layout_marginRight="10dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:background="@drawable/bg_button_round"
|
||||
android:gravity="center"
|
||||
android:padding="10dp"
|
||||
android:singleLine="true"
|
||||
android:text="@string/throw_exception"
|
||||
android:textColor="@color/colorTextGray"
|
||||
android:textSize="15sp" />
|
||||
<TextView
|
||||
android:id="@+id/throw_illegal_state_button"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="10dp"
|
||||
android:layout_marginRight="10dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:background="@drawable/bg_button_round"
|
||||
android:gravity="center"
|
||||
android:padding="10dp"
|
||||
android:singleLine="true"
|
||||
android:text="@string/throw_illegalstate"
|
||||
android:textColor="@color/colorTextGray"
|
||||
android:textSize="15sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/throw_native_error_button"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="10dp"
|
||||
android:layout_marginRight="10dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:background="@drawable/bg_button_round"
|
||||
android:gravity="center"
|
||||
android:padding="10dp"
|
||||
android:singleLine="true"
|
||||
android:text="@string/throw_native_error"
|
||||
android:textColor="@color/colorTextGray"
|
||||
android:textSize="15sp" />
|
||||
<TextView
|
||||
android:id="@+id/throw_null_pointer_button"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="10dp"
|
||||
android:layout_marginRight="10dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:background="@drawable/bg_button_round"
|
||||
android:gravity="center"
|
||||
android:padding="10dp"
|
||||
android:singleLine="true"
|
||||
android:text="@string/throw_nullpointer"
|
||||
android:textColor="@color/colorTextGray"
|
||||
android:textSize="15sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/throw_multi_process_error_button"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="10dp"
|
||||
android:layout_marginRight="10dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:background="@drawable/bg_button_round"
|
||||
android:gravity="center"
|
||||
android:padding="10dp"
|
||||
android:singleLine="true"
|
||||
android:text="@string/throw_multi_process_error"
|
||||
android:textColor="@color/colorTextGray"
|
||||
android:textSize="15sp" />
|
||||
<TextView
|
||||
android:id="@+id/throw_exception_button"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="10dp"
|
||||
android:layout_marginRight="10dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:background="@drawable/bg_button_round"
|
||||
android:gravity="center"
|
||||
android:padding="10dp"
|
||||
android:singleLine="true"
|
||||
android:text="@string/throw_exception"
|
||||
android:textColor="@color/colorTextGray"
|
||||
android:textSize="15sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/throw_native_error_button"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="10dp"
|
||||
android:layout_marginRight="10dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:background="@drawable/bg_button_round"
|
||||
android:gravity="center"
|
||||
android:padding="10dp"
|
||||
android:singleLine="true"
|
||||
android:text="@string/throw_native_error"
|
||||
android:textColor="@color/colorTextGray"
|
||||
android:textSize="15sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/throw_multi_process_error_button"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="10dp"
|
||||
android:layout_marginRight="10dp"
|
||||
android:background="@drawable/bg_button_round"
|
||||
android:gravity="center"
|
||||
android:padding="10dp"
|
||||
android:singleLine="true"
|
||||
android:text="@string/throw_multi_process_error"
|
||||
android:textColor="@color/colorTextGray"
|
||||
android:textSize="15sp" />
|
||||
</LinearLayout>
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
</LinearLayout>
|
Before Width: | Height: | Size: 3.5 KiB |
@@ -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>
|
||||
|
@@ -1,12 +1,12 @@
|
||||
<resources>
|
||||
<string name="app_name">AppErrorsDemo</string>
|
||||
<string name="back">Back</string>
|
||||
<string name="function_desc">The business exception logic in common scenarios is provided here. After activating the Xposed module, you can test whether the exception can be caught here. In order to verify the effectiveness of the Xposed module, the current App does not have any logic code for manually handling exceptions.</string>
|
||||
<string name="throw_runtime">throw RuntimeException</string>
|
||||
<string name="throw_illegalstate">throw IllegalStateException</string>
|
||||
<string name="throw_nullpointer">throw NullPointerException</string>
|
||||
<string name="throw_exception">throw Exception</string>
|
||||
<string name="throw_native_error">throw Native Error</string>
|
||||
<string name="throw_multi_process_error">throw Multi-Process Exception</string>
|
||||
<string name="function_desc">The business exception logic in common scenarios is provided here.\nAfter activating the Xposed Module, you can test whether the exception can be caught here.\nIn order to verify the effectiveness of the Xposed Module, the current App does not have any logic code for manually handling exceptions.</string>
|
||||
<string name="throw_runtime">Throw RuntimeException</string>
|
||||
<string name="throw_illegalstate">Throw IllegalStateException</string>
|
||||
<string name="throw_nullpointer">Throw NullPointerException</string>
|
||||
<string name="throw_exception">Throw Exception</string>
|
||||
<string name="throw_native_error">Throw Native Error</string>
|
||||
<string name="throw_multi_process_error">Throw Multi-Process Exception</string>
|
||||
<string name="suicide_in_progress">Suicide in progress</string>
|
||||
</resources>
|
9
demo-app/src/main/res/xml/locales_config.xml
Normal 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>
|
@@ -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}"
|
95
gradle/sweet-dependency/sweet-dependency-config.yaml
Normal 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
|
5
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +1,5 @@
|
||||
#Wed May 04 08:35:13 CST 2022
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-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
|
BIN
img-src/icon.png
Normal file
After Width: | Height: | Size: 12 KiB |
2
module-app/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/src/main/assets/xposed_init
|
||||
/src/main/resources/META-INF/yukihookapi_init
|
93
module-app/build.gradle.kts
Normal 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)
|
||||
}
|
76
module-app/proguard-rules.pro
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
|
||||
-ignorewarnings
|
||||
-optimizationpasses 10
|
||||
-dontusemixedcaseclassnames
|
||||
-dontoptimize
|
||||
-verbose
|
||||
-overloadaggressively
|
||||
-allowaccessmodification
|
||||
-adaptclassstrings
|
||||
-adaptresourcefilenames
|
||||
-adaptresourcefilecontents
|
||||
|
||||
-renamesourcefileattribute P
|
||||
-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.** { *; }
|
||||
|
||||
# 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 {
|
||||
public static *** throwUninitializedProperty(...);
|
||||
public static *** throwUninitializedPropertyAccessException(...);
|
||||
}
|
||||
|
||||
-keep class * extends android.app.Activity
|
||||
-keep class * implements androidx.viewbinding.ViewBinding {
|
||||
<init>();
|
||||
*** inflate(android.view.LayoutInflater);
|
||||
}
|
@@ -6,14 +6,27 @@
|
||||
android:name="android.permission.QUERY_ALL_PACKAGES"
|
||||
tools:ignore="QueryAllPackagesPermission" />
|
||||
|
||||
<uses-permission
|
||||
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"
|
||||
@@ -23,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" />
|
||||
@@ -59,6 +75,11 @@
|
||||
android:exported="false"
|
||||
android:screenOrientation="behind" />
|
||||
|
||||
<activity
|
||||
android:name=".ui.activity.debug.LoggerActivity"
|
||||
android:exported="false"
|
||||
android:screenOrientation="behind" />
|
||||
|
||||
<activity
|
||||
android:name=".ui.activity.errors.AppErrorsRecordActivity"
|
||||
android:exported="true"
|
||||
@@ -83,7 +104,6 @@
|
||||
android:name=".ui.activity.errors.AppErrorsDetailActivity"
|
||||
android:exported="true"
|
||||
android:launchMode="singleTask"
|
||||
android:screenOrientation="behind"
|
||||
android:taskAffinity=":detail" />
|
||||
|
||||
<service
|
||||
@@ -97,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>
|
BIN
module-app/src/main/ic_launcher-playstore.png
Normal file
After Width: | Height: | Size: 12 KiB |
@@ -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,28 @@
|
||||
* 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.locale.LocaleString
|
||||
import com.fankes.apperrorstracking.data.ConfigData
|
||||
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
|
||||
|
||||
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(this)
|
||||
/** 装载 App Center */
|
||||
AppAnalyticsTool.init(this)
|
||||
}
|
||||
}
|
@@ -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.bean
|
||||
|
||||
@@ -25,6 +25,8 @@ import java.io.Serializable
|
||||
|
||||
/**
|
||||
* 应用异常信息显示 bean
|
||||
* @param pid APP 进程 ID
|
||||
* @param userId APP 用户 ID
|
||||
* @param packageName APP 包名
|
||||
* @param processName APP 进程名
|
||||
* @param appName APP 名称
|
||||
@@ -34,6 +36,8 @@ import java.io.Serializable
|
||||
* @param isShowReopenButton 是否显示重新打开按钮
|
||||
*/
|
||||
data class AppErrorsDisplayBean(
|
||||
var pid: Int,
|
||||
var userId: Int,
|
||||
var packageName: String,
|
||||
var processName: String,
|
||||
var appName: String,
|
@@ -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 "***"
|
||||
}
|
@@ -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,15 +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 com.fankes.apperrorstracking.bean.enum.AppFiltersType
|
||||
import java.io.Serializable
|
||||
|
||||
/**
|
||||
* 应用过滤条件 bean
|
||||
* @param name 名称或包名
|
||||
* @param isContainsSystem 是否包含系统应用
|
||||
* @param type 过滤条件类型
|
||||
*/
|
||||
data class AppFiltersBean(var name: String = "", var isContainsSystem: Boolean = false) : Serializable
|
||||
data class AppFiltersBean(
|
||||
var name: String = "",
|
||||
var type: AppFiltersType = AppFiltersType.USER
|
||||
) : Serializable
|
@@ -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/8.
|
||||
* This file is created by fankes on 2022/6/8.
|
||||
*/
|
||||
package com.fankes.apperrorstracking.bean
|
||||
|
||||
@@ -30,4 +30,8 @@ import java.io.Serializable
|
||||
* @param name APP 名称
|
||||
* @param packageName APP 包名
|
||||
*/
|
||||
data class AppInfoBean(var icon: Drawable? = null, var name: String, var packageName: String) : Serializable
|
||||
data class AppInfoBean(
|
||||
var icon: Drawable? = null,
|
||||
var name: String,
|
||||
var packageName: String
|
||||
) : Serializable
|