mirror of
https://github.com/KitsunePie/AppErrorsTracking.git
synced 2025-09-04 02:05:16 +08:00
Compare commits
45 Commits
Author | SHA1 | Date | |
---|---|---|---|
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
|
17
.editorconfig
Normal file
17
.editorconfig
Normal file
@@ -0,0 +1,17 @@
|
||||
# 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
|
||||
ij_continuation_indent_size = 2
|
||||
indent_size = 4
|
||||
indent_style = space
|
||||
insert_final_newline = false
|
||||
max_line_length = 150
|
1
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
1
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -33,6 +33,7 @@ body:
|
||||
attributes:
|
||||
label: Android version / Android 版本
|
||||
options:
|
||||
- 14
|
||||
- 13
|
||||
- 12L/12.1
|
||||
- 12
|
||||
|
73
.github/workflows/commit_ci.yml
vendored
73
.github/workflows/commit_ci.yml
vendored
@@ -13,17 +13,36 @@ on:
|
||||
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@v3
|
||||
- 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 11
|
||||
- name: Prepare Java 17
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
java-version: 11
|
||||
java-version: 17
|
||||
java-package: jdk
|
||||
distribution: 'temurin'
|
||||
cache: 'gradle'
|
||||
@@ -34,7 +53,7 @@ jobs:
|
||||
~/.gradle/caches
|
||||
~/.gradle/wrapper
|
||||
!~/.gradle/caches/build-cache-*
|
||||
key: gradle-deps-core-${{ hashFiles('**/build.gradle') }}
|
||||
key: gradle-deps-core-${{ hashFiles('**/build.gradle.kts') }}
|
||||
restore-keys: |
|
||||
gradle-deps
|
||||
- name: Cache Gradle Build
|
||||
@@ -47,31 +66,43 @@ jobs:
|
||||
gradle-builds
|
||||
- name: Build with Gradle
|
||||
run: |
|
||||
./gradlew :app:assembleDebug
|
||||
./gradlew :app:assembleRelease
|
||||
./gradlew :module-app:assembleDebug
|
||||
./gradlew :module-app:assembleRelease
|
||||
./gradlew :demo-app:assembleDebug
|
||||
./gradlew :demo-app:assembleRelease
|
||||
echo "MODULE_DEBUG_APK_FILE=$(find app/build/outputs/apk/debug -name '*.apk')" >> $GITHUB_ENV
|
||||
echo "DEMO_DEBUG_APK_FILE=$(find demo-app/build/outputs/apk/debug -name '*.apk')" >> $GITHUB_ENV
|
||||
echo "MODULE_RELEASE_APK_FILE=$(find app/build/outputs/apk/release -name '*.apk')" >> $GITHUB_ENV
|
||||
echo "DEMO_RELEASE_APK_FILE=$(find demo-app/build/outputs/apk/release -name '*.apk')" >> $GITHUB_ENV
|
||||
- name: Upload Artifacts(module-debug)
|
||||
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@v3
|
||||
with:
|
||||
path: ${{ env.MODULE_DEBUG_APK_FILE }}
|
||||
name: module-debug
|
||||
- name: Upload Artifacts(demo-debug)
|
||||
path: ${{ env.MODULE_DEBUG_APK_PATH }}
|
||||
name: AppErrorsTracking-module-debug-${{ github.event.head_commit.id }}
|
||||
- name: Upload Artifacts (Module-Release)
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
path: ${{ env.DEMO_DEBUG_APK_FILE }}
|
||||
name: demo-debug
|
||||
- name: Upload Artifacts(module-release)
|
||||
path: ${{ env.MODULE_RELEASE_APK_PATH }}
|
||||
name: AppErrorsTracking-module-release-${{ github.event.head_commit.id }}
|
||||
- name: Upload Artifacts (Demo-Debug)
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
path: ${{ env.MODULE_RELEASE_APK_FILE }}
|
||||
name: module-release
|
||||
- name: Upload Artifacts(demo-release)
|
||||
path: ${{ env.DEMO_DEBUG_APK_PATH }}
|
||||
name: AppErrorsTracking-demo-debug-${{ github.event.head_commit.id }}
|
||||
- name: Upload Artifacts (Demo-Release)
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
path: ${{ env.DEMO_RELEASE_APK_FILE }}
|
||||
name: demo-release
|
||||
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"
|
53
.github/workflows/pull_request_ci.yml
vendored
53
.github/workflows/pull_request_ci.yml
vendored
@@ -11,18 +11,27 @@ on:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Pull request check
|
||||
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@v3
|
||||
- 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 11
|
||||
- name: Prepare Java 17
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
java-version: 11
|
||||
java-version: 17
|
||||
java-package: jdk
|
||||
distribution: 'temurin'
|
||||
cache: 'gradle'
|
||||
@@ -33,7 +42,7 @@ jobs:
|
||||
~/.gradle/caches
|
||||
~/.gradle/wrapper
|
||||
!~/.gradle/caches/build-cache-*
|
||||
key: gradle-deps-core-${{ hashFiles('**/build.gradle') }}
|
||||
key: gradle-deps-core-${{ hashFiles('**/build.gradle.kts') }}
|
||||
restore-keys: |
|
||||
gradle-deps
|
||||
- name: Cache Gradle Build
|
||||
@@ -46,31 +55,31 @@ jobs:
|
||||
gradle-builds
|
||||
- name: Build with Gradle
|
||||
run: |
|
||||
./gradlew :app:assembleDebug
|
||||
./gradlew :app:assembleRelease
|
||||
./gradlew :module-app:assembleDebug
|
||||
./gradlew :module-app:assembleRelease
|
||||
./gradlew :demo-app:assembleDebug
|
||||
./gradlew :demo-app:assembleRelease
|
||||
echo "MODULE_DEBUG_APK_FILE=$(find app/build/outputs/apk/debug -name '*.apk')" >> $GITHUB_ENV
|
||||
echo "DEMO_DEBUG_APK_FILE=$(find demo-app/build/outputs/apk/debug -name '*.apk')" >> $GITHUB_ENV
|
||||
echo "MODULE_RELEASE_APK_FILE=$(find app/build/outputs/apk/release -name '*.apk')" >> $GITHUB_ENV
|
||||
echo "DEMO_RELEASE_APK_FILE=$(find demo-app/build/outputs/apk/release -name '*.apk')" >> $GITHUB_ENV
|
||||
- name: Upload Artifacts(module-debug)
|
||||
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@v3
|
||||
with:
|
||||
path: ${{ env.MODULE_DEBUG_APK_FILE }}
|
||||
name: module-debug
|
||||
- name: Upload Artifacts(demo-debug)
|
||||
path: ${{ env.MODULE_DEBUG_APK_PATH }}
|
||||
name: AppErrorsTracking-module-debug-${{ github.event.head_commit.id }}
|
||||
- name: Upload Artifacts (Module-Release)
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
path: ${{ env.DEMO_DEBUG_APK_FILE }}
|
||||
name: demo-debug
|
||||
- name: Upload Artifacts(module-release)
|
||||
path: ${{ env.MODULE_RELEASE_APK_PATH }}
|
||||
name: AppErrorsTracking-module-release-${{ github.event.head_commit.id }}
|
||||
- name: Upload Artifacts (Demo-Debug)
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
path: ${{ env.MODULE_RELEASE_APK_FILE }}
|
||||
name: module-release
|
||||
- name: Upload Artifacts(demo-release)
|
||||
path: ${{ env.DEMO_DEBUG_APK_PATH }}
|
||||
name: AppErrorsTracking-demo-debug-${{ github.event.head_commit.id }}
|
||||
- name: Upload Artifacts (Demo-Release)
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
path: ${{ env.DEMO_RELEASE_APK_FILE }}
|
||||
name: demo-release
|
||||
path: ${{ env.DEMO_RELEASE_APK_PATH }}
|
||||
name: AppErrorsTracking-demo-release-${{ github.event.head_commit.id }}
|
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,7 +1,6 @@
|
||||
# Project exclude paths
|
||||
*.iml
|
||||
.gradle
|
||||
.secret/APP_CENTER_SECRET
|
||||
/local.properties
|
||||
/.idea/caches
|
||||
/.idea/libraries
|
||||
|
4
.idea/.gitignore
generated
vendored
4
.idea/.gitignore
generated
vendored
@@ -1,3 +1,5 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
/workspace.xml
|
||||
/gradle.xml
|
||||
/misc.xml
|
26
.idea/appInsightsSettings.xml
generated
Normal file
26
.idea/appInsightsSettings.xml
generated
Normal file
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="AppInsightsSettings">
|
||||
<option name="tabSettings">
|
||||
<map>
|
||||
<entry key="Firebase Crashlytics">
|
||||
<value>
|
||||
<InsightsFilterSettings>
|
||||
<option name="connection">
|
||||
<ConnectionSetting>
|
||||
<option name="appId" value="PLACEHOLDER" />
|
||||
<option name="mobileSdkAppId" value="" />
|
||||
<option name="projectId" value="" />
|
||||
<option name="projectNumber" value="" />
|
||||
</ConnectionSetting>
|
||||
</option>
|
||||
<option name="signal" value="SIGNAL_UNSPECIFIED" />
|
||||
<option name="timeIntervalDays" value="THIRTY_DAYS" />
|
||||
<option name="visibilityType" value="ALL" />
|
||||
</InsightsFilterSettings>
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
2
.idea/compiler.xml
generated
2
.idea/compiler.xml
generated
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CompilerConfiguration">
|
||||
<bytecodeTargetLevel target="11" />
|
||||
<bytecodeTargetLevel target="17" />
|
||||
</component>
|
||||
</project>
|
20
.idea/deploymentTargetDropDown.xml
generated
20
.idea/deploymentTargetDropDown.xml
generated
@@ -1,17 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="deploymentTargetDropDown">
|
||||
<targetSelectedWithDropDown>
|
||||
<Target>
|
||||
<type value="QUICK_BOOT_TARGET" />
|
||||
<deviceKey>
|
||||
<Key>
|
||||
<type value="VIRTUAL_DEVICE_PATH" />
|
||||
<value value="$USER_HOME$/.android/avd/Pixel_2_API_29.avd" />
|
||||
</Key>
|
||||
</deviceKey>
|
||||
</Target>
|
||||
</targetSelectedWithDropDown>
|
||||
<timeTargetWasSelectedWithDropDown value="2022-05-19T02:00:42.222889Z" />
|
||||
<value>
|
||||
<entry key="demo-app">
|
||||
<State />
|
||||
</entry>
|
||||
<entry key="module-app">
|
||||
<State />
|
||||
</entry>
|
||||
</value>
|
||||
</component>
|
||||
</project>
|
21
.idea/gradle.xml
generated
21
.idea/gradle.xml
generated
@@ -1,21 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="GradleMigrationSettings" migrationVersion="1" />
|
||||
<component name="GradleSettings">
|
||||
<option name="linkedExternalProjectsSettings">
|
||||
<GradleProjectSettings>
|
||||
<option name="testRunner" value="GRADLE" />
|
||||
<option name="distributionType" value="DEFAULT_WRAPPED" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="gradleJvm" value="Embedded JDK" />
|
||||
<option name="modules">
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
<option value="$PROJECT_DIR$/app" />
|
||||
<option value="$PROJECT_DIR$/demo-app" />
|
||||
</set>
|
||||
</option>
|
||||
</GradleProjectSettings>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
1
.idea/inspectionProfiles/Project_Default.xml
generated
1
.idea/inspectionProfiles/Project_Default.xml
generated
@@ -6,5 +6,6 @@
|
||||
<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
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="1.9.10" />
|
||||
</component>
|
||||
</project>
|
6
.idea/ktlint.xml
generated
Normal file
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
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>
|
48
.idea/misc.xml
generated
48
.idea/misc.xml
generated
@@ -1,48 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="DesignSurface">
|
||||
<option name="filePathToZoomLevelMap">
|
||||
<map>
|
||||
<entry key="app/src/main/res/drawable-night/bg_black_round.xml" value="0.2425" />
|
||||
<entry key="app/src/main/res/drawable-night/bg_dark_round.xml" value="0.256" />
|
||||
<entry key="app/src/main/res/drawable-night/bg_green_round.xml" value="0.2425" />
|
||||
<entry key="app/src/main/res/drawable-night/bg_orange_round.xml" value="0.2425" />
|
||||
<entry key="app/src/main/res/drawable-night/bg_yellow_round.xml" value="0.2425" />
|
||||
<entry key="app/src/main/res/drawable/bg_black_round.xml" value="0.2425" />
|
||||
<entry key="app/src/main/res/drawable/bg_dark_round.xml" value="0.2425" />
|
||||
<entry key="app/src/main/res/drawable/bg_green_round.xml" value="0.2425" />
|
||||
<entry key="app/src/main/res/drawable/bg_orange_round.xml" value="0.2425" />
|
||||
<entry key="app/src/main/res/drawable/ic_debug.xml" value="0.2555" />
|
||||
<entry key="app/src/main/res/drawable/ic_exception.xml" value="0.256" />
|
||||
<entry key="app/src/main/res/drawable/ic_filter.xml" value="0.256" />
|
||||
<entry key="app/src/main/res/drawable/ic_share.xml" value="0.241" />
|
||||
<entry key="app/src/main/res/drawable/ic_statistics.xml" value="0.256" />
|
||||
<entry key="app/src/main/res/layout/activity_app_errors_detail.xml" value="0.43697916666666664" />
|
||||
<entry key="app/src/main/res/layout/activity_app_errors_display.xml" value="0.4359375" />
|
||||
<entry key="app/src/main/res/layout/activity_app_errors_ignored.xml" value="0.43697916666666664" />
|
||||
<entry key="app/src/main/res/layout/activity_app_errors_muted.xml" value="0.43697916666666664" />
|
||||
<entry key="app/src/main/res/layout/activity_app_errors_record.xml" value="0.43697916666666664" />
|
||||
<entry key="app/src/main/res/layout/activity_config.xml" value="0.4359375" />
|
||||
<entry key="app/src/main/res/layout/activity_main.xml" value="0.4359375" />
|
||||
<entry key="app/src/main/res/layout/adapter_app_errors_ignored.xml" value="0.43697916666666664" />
|
||||
<entry key="app/src/main/res/layout/adapter_app_errors_muted.xml" value="0.43697916666666664" />
|
||||
<entry key="app/src/main/res/layout/adapter_app_errors_record.xml" value="0.43697916666666664" />
|
||||
<entry key="app/src/main/res/layout/adapter_app_info.xml" value="0.43697916666666664" />
|
||||
<entry key="app/src/main/res/layout/dia_app_config.xml" value="0.43697916666666664" />
|
||||
<entry key="app/src/main/res/layout/dia_app_errors_display.xml" value="0.4359375" />
|
||||
<entry key="app/src/main/res/layout/dia_app_errors_statistics.xml" value="0.43697916666666664" />
|
||||
<entry key="app/src/main/res/layout/dia_apps_filter.xml" value="0.43697916666666664" />
|
||||
<entry key="app/src/main/res/menu/menu_list_detail_action.xml" value="0.43697916666666664" />
|
||||
<entry key="demo-app/src/main/res/layout/activity_main.xml" value="0.4083333333333333" />
|
||||
<entry key="demo-app/src/main/res/layout/activity_multi_process.xml" value="0.4359375" />
|
||||
</map>
|
||||
</option>
|
||||
</component>
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="JDK" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||
</component>
|
||||
<component name="ProjectType">
|
||||
<option name="id" value="Android" />
|
||||
</component>
|
||||
</project>
|
1
.secret/.gitignore
vendored
Normal file
1
.secret/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/secret.properties
|
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"keyAlias": "public",
|
||||
"keyPassword": "123456",
|
||||
"storeFileName": "universal.p12",
|
||||
"storePassword": "123456"
|
||||
}
|
@@ -1,11 +1,18 @@
|
||||
# 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="https://github.com/KitsunePie/AppErrorsTracking/blob/master/img-src/icon.png?raw=true" width = "100" height = "100" alt="LOGO"/>
|
||||
|
||||
[English](https://github.com/KitsunePie/AppErrorsTracking/blob/master/README.md) | 简体中文
|
||||
|
||||
为原生 FC 对话框增加更多功能并修复国内定制 ROM 删除 FC 对话框的问题,给 Android 开发者带来更好的体验。
|
||||
@@ -39,46 +46,51 @@
|
||||
|
||||
## 功能列表
|
||||
|
||||
- 完全取代系统的应用错误对话框
|
||||
- [x] 完全取代系统的应用错误对话框
|
||||
|
||||
- 记录每个应用的异常,直到重新启动前持续保留
|
||||
- [x] 记录每个应用的异常,直到重新启动前持续保留
|
||||
|
||||
- 复制、分享、导出异常堆栈功能
|
||||
- [x] 复制、分享、导出异常堆栈功能
|
||||
|
||||
- 异常历史记录功能,可通过通知栏磁贴“异常历史记录”进入和模块主界面进入
|
||||
- [x] 异常历史记录功能,可通过通知栏磁贴“异常历史记录”进入和模块主界面进入
|
||||
|
||||
- 应用异常统计功能
|
||||
- [x] 应用异常统计功能
|
||||
|
||||
- 多进程应用的异常显示功能
|
||||
- [x] 多进程应用的异常显示功能
|
||||
|
||||
## 翻译贡献
|
||||
|
||||
欢迎为此项目做出贡献,将其翻译为您国家的语言。
|
||||
|
||||
## 发行渠道说明
|
||||
## 发行渠道
|
||||
|
||||
- [Automatic Build on Commit](https://github.com/KitsunePie/AppErrorsTracking/actions/workflows/commit_ci.yml)
|
||||
| <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 自动构建 (测试版) |
|
||||
|------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------|---------------|
|
||||
|
||||
上述更新为代码 `commit` 后自动触发,具体更新内容可点击上方的文字前往 **GitHub Actions** 进行查看,本更新由开源的流程自动编译发布,**不保证其稳定性**,所发布的版本**仅供测试**,且不会特殊说明甚至可能会变更版本号或保持与当前稳定版相同的版本号。
|
||||
| <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 自动构建 (测试版) |
|
||||
|-----------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------|---------------|
|
||||
|
||||
- [Release](https://github.com/KitsunePie/AppErrorsTracking/releases)
|
||||
- [Xposed-Modules-Repo](https://github.com/Xposed-Modules-Repo/com.fankes.apperrorstracking/releases)
|
||||
| <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) | 正式版 (稳定版) |
|
||||
|------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------|-----------|
|
||||
|
||||
## 发行状态说明
|
||||
本模块发布地址仅限于上述所列出的地址,从其他非正规渠道下载到的版本或对您造成任何影响均与我们无关。
|
||||
|
||||

|
||||
## 项目推广
|
||||
|
||||
上述状态为当前稳定版与自动构建版本一致或当前代码改动与稳定版无功能差异。
|
||||
如果你正在寻找一个可以自动管理 Gradle 项目依赖的 Gradle 插件,你可以了解一下 [SweetDependency](https://github.com/HighCapable/SweetDependency) 项目。
|
||||
|
||||

|
||||
如果你正在寻找一个可以自动生成属性键值的 Gradle 插件,你可以了解一下 [SweetProperty](https://github.com/HighCapable/SweetProperty) 项目。
|
||||
|
||||
上述状态为存在自动构建版本和新功能的更新但当前并未发布稳定版,处于预发行状态。
|
||||
本项目同样使用了 **SweetDependency** 和 **SweetProperty**。
|
||||
|
||||

|
||||
## 捐赠支持
|
||||
|
||||
上述状态为当前发行的稳定版可能存在严重问题但并未及时进行修复且并未发布稳定版。
|
||||
工作不易,无意外情况此项目将继续维护下去,提供更多可能,欢迎打赏。
|
||||
|
||||
<img src="https://github.com/fankes/fankes/blob/main/img-src/payment_code.jpg?raw=true" width = "500" alt="Payment Code"/>
|
||||
|
||||
## Star History
|
||||
|
||||
@@ -102,9 +114,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)
|
||||
|
||||
版权所有 © 2017-2023 Fankes Studio(qzmmcn@163.com)
|
86
README.md
86
README.md
@@ -1,21 +1,28 @@
|
||||
# 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="https://github.com/KitsunePie/AppErrorsTracking/blob/master/img-src/icon.png?raw=true" width = "100" height = "100" alt="LOGO"/>
|
||||
|
||||
English | [简体中文](https://github.com/KitsunePie/AppErrorsTracking/blob/master/README-zh-CN.md)
|
||||
|
||||
Added more features to app's errors dialog, fixed custom rom deleted dialog, the best experience to Android developer.
|
||||
|
||||
This project is an Xposed module that can be used in any Android system, currently only tested in **LSPosed**.
|
||||
This project is a Xposed Module that can be used in any Android system, currently only tested in **LSPosed**.
|
||||
|
||||
This module is specially designed for Android developers.
|
||||
This Xposed Module is specially designed for Android developers.
|
||||
|
||||
When it is possible that the computer cannot be connected and ADB cannot be performed, this module can be used to quickly capture any errors of
|
||||
any installed apps, so as to quickly locate the problem.
|
||||
any installed apps, to quickly locate the problem.
|
||||
|
||||
The error log of apps crashing is an invaluable asset for developers. If you are not a developer, you can still install this module to provide
|
||||
developers with more exception information to quickly solve problems.
|
||||
@@ -45,59 +52,50 @@ Similar to **Bugly** to automatically collect errors, the system cannot obtain w
|
||||
|
||||
## Features List
|
||||
|
||||
- Completely replaces the system's apps errors dialog
|
||||
- [x] Completely replaces the system's apps errors dialog
|
||||
|
||||
- Logs exceptions for each apps and persists until restart
|
||||
- [x] Logs exceptions for each app and persists until restart
|
||||
|
||||
- Copy, share, export errors stack trace functions
|
||||
- [x] Copy, share, export errors stack trace functions
|
||||
|
||||
- Errors history record function, which can be entered through the notification bar tile "errors history record" and the main interface of the
|
||||
module
|
||||
- [x] Errors history record function,
|
||||
which can be entered through the notification bar tile "errors history record" and the main interface of the module
|
||||
|
||||
- Apps errors statistics function
|
||||
- [x] Apps errors statistics function
|
||||
|
||||
- Errors display function for multi-process apps
|
||||
- [x] Errors display function for multi-process apps
|
||||
|
||||
## Translation Contribution
|
||||
|
||||
Contributions to this project are welcome to translate it into your country's language.
|
||||
|
||||
## Release Channel Description
|
||||
## Release Channel
|
||||
|
||||
- [Automatic Build on Commit](https://github.com/KitsunePie/AppErrorsTracking/actions/workflows/commit_ci.yml)
|
||||
| <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) |
|
||||
|------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------|-----------------------------------|
|
||||
|
||||
The above update is automatically triggered after the code `commit`.
|
||||
| <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) |
|
||||
|-----------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------|-----------------------------------|
|
||||
|
||||
The specific update content can be viewed by clicking the text above and going to **GitHub Actions**.
|
||||
| <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) |
|
||||
|------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------|---------------------------------|
|
||||
|
||||
This update is automatically compiled and released by the open source process, **no guarantee of its stability**, so the released version is
|
||||
**for testing only**, and there is no special explanation or even the version may change or remain the same as the current 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) |
|
||||
|------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------|---------------------------------|
|
||||
|
||||
- [Release](https://github.com/KitsunePie/AppErrorsTracking/releases)
|
||||
- [Xposed-Modules-Repo](https://github.com/Xposed-Modules-Repo/com.fankes.apperrorstracking/releases)
|
||||
The releases of this Xposed Module is limited to the urls listed above.
|
||||
|
||||
The above update is a manually released stable version.
|
||||
We have nothing to do with versions downloaded from other informal channels or any impact on you.
|
||||
|
||||
For the specific update content, you can click the text above to go to the designated release page to view.
|
||||
## Promotion
|
||||
|
||||
The update of the stable version will be released to the above address at the same time and updated synchronously.
|
||||
If you are looking for a Gradle plugin that can automatically manage Gradle project dependencies,
|
||||
you can check out the [SweetDependency](https://github.com/HighCapable/SweetDependency) project.
|
||||
|
||||
## Release Status Description
|
||||
If you are looking for a Gradle plugin that can automatically generate properties key-values,
|
||||
you can check out the [SweetProperty](https://github.com/HighCapable/SweetProperty) project.
|
||||
|
||||

|
||||
|
||||
The above status is that the current stable version is consistent with the automatic build version or the current code changes and the stable
|
||||
version have no functional difference.
|
||||
|
||||

|
||||
|
||||
The above state is that there are automatic build versions and updates with new features but no stable version is currently released, and it is
|
||||
in a pre-release state.
|
||||
|
||||

|
||||
|
||||
The above status is that the currently released stable version may have serious problems but have not been fixed in time and the stable version
|
||||
has not been released.
|
||||
This project also uses **SweetDependency** and **SweetProperty**.
|
||||
|
||||
## Star History
|
||||
|
||||
@@ -121,9 +119,9 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
```
|
||||
|
||||
Powered by [YukiHookAPI](https://github.com/fankes/YukiHookAPI)
|
||||
Powered by [YukiHookAPI](https://github.com/HighCapable/YukiHookAPI)
|
||||
|
||||
Copyright © 2017-2023 Fankes Studio(qzmmcn@163.com)
|
102
app/build.gradle
102
app/build.gradle
@@ -1,102 +0,0 @@
|
||||
import groovy.json.JsonSlurper
|
||||
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'org.jetbrains.kotlin.android'
|
||||
id 'com.google.devtools.ksp'
|
||||
}
|
||||
|
||||
android {
|
||||
signingConfigs {
|
||||
universal {
|
||||
def dirPath = rootProject.ext.app.signingConfigs.secretConfigsDirPath
|
||||
def fileName = rootProject.ext.app.signingConfigs.secretConfigsFileName
|
||||
def configs = new JsonSlurper().parse(file("${dirPath}/${fileName}"))
|
||||
keyAlias configs.keyAlias
|
||||
keyPassword configs.keyPassword
|
||||
storeFile file("${dirPath}/${configs.storeFileName}")
|
||||
storePassword configs.storePassword
|
||||
v1SigningEnabled true
|
||||
v2SigningEnabled true
|
||||
}
|
||||
}
|
||||
|
||||
namespace 'com.fankes.apperrorstracking'
|
||||
compileSdk rootProject.ext.android.compileSdk
|
||||
|
||||
defaultConfig {
|
||||
applicationId 'com.fankes.apperrorstracking'
|
||||
|
||||
minSdk rootProject.ext.android.minSdk
|
||||
targetSdk rootProject.ext.android.targetSdk
|
||||
|
||||
versionCode rootProject.ext.app.versionCode
|
||||
versionName rootProject.ext.app.versionName
|
||||
|
||||
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
|
||||
|
||||
/** 添加 App Center Secret 到 BuildConfig */
|
||||
buildConfigField('String', 'APP_CENTER_SECRET', "\"${getAppCenterSecret()}\"")
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
debug {
|
||||
minifyEnabled false
|
||||
signingConfig signingConfigs.universal
|
||||
}
|
||||
release {
|
||||
minifyEnabled true
|
||||
shrinkResources true
|
||||
zipAlignEnabled true
|
||||
signingConfig signingConfigs.universal
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_11
|
||||
targetCompatibility JavaVersion.VERSION_11
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = '11'
|
||||
freeCompilerArgs = [
|
||||
'-Xno-param-assertions',
|
||||
'-Xno-call-assertions',
|
||||
'-Xno-receiver-assertions'
|
||||
]
|
||||
}
|
||||
buildFeatures {
|
||||
viewBinding true
|
||||
}
|
||||
lintOptions {
|
||||
checkReleaseBuilds false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 App Center Secret
|
||||
* @return [String]
|
||||
*/
|
||||
String getAppCenterSecret() {
|
||||
def fileName = '../.secret/APP_CENTER_SECRET'
|
||||
def content = ''
|
||||
if (file(fileName).exists()) file(fileName).eachLine { content = it }
|
||||
return content
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly 'de.robv.android.xposed:api:82'
|
||||
implementation 'com.highcapable.yukihookapi:api:1.1.9'
|
||||
ksp 'com.highcapable.yukihookapi:ksp-xposed:1.1.9'
|
||||
implementation 'com.microsoft.appcenter:appcenter-analytics:5.0.0'
|
||||
implementation 'com.microsoft.appcenter:appcenter-crashes:5.0.0'
|
||||
implementation 'com.squareup.okhttp3:okhttp:5.0.0-alpha.7'
|
||||
implementation 'com.google.code.gson:gson:2.10.1'
|
||||
implementation 'com.github.duanhong169:drawabletoolbox:1.0.7'
|
||||
implementation 'com.github.topjohnwu.libsu:core:5.0.4'
|
||||
implementation 'androidx.core:core-ktx:1.10.0'
|
||||
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||
implementation 'com.google.android.material:material:1.8.0'
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
|
||||
}
|
@@ -1,565 +0,0 @@
|
||||
/*
|
||||
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
|
||||
* Copyright (C) 2017-2023 Fankes Studio(qzmmcn@163.com)
|
||||
* https://github.com/KitsunePie/AppErrorsTracking
|
||||
*
|
||||
* This software is non-free but opensource software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Affero General Public License
|
||||
* as published by the Free Software Foundation; either
|
||||
* version 3 of the License, or any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/5/11.
|
||||
*/
|
||||
@file:Suppress("MemberVisibilityCanBePrivate", "StaticFieldLeak", "unused")
|
||||
|
||||
package com.fankes.apperrorstracking.locale
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.Resources
|
||||
import com.fankes.apperrorstracking.R
|
||||
import com.highcapable.yukihookapi.hook.param.PackageParam
|
||||
|
||||
/**
|
||||
* I18n 字符串实例
|
||||
*/
|
||||
object LocaleString {
|
||||
|
||||
/** 当前的 [Context] */
|
||||
private var baseContext: Context? = null
|
||||
|
||||
/** 当前的 [PackageParam] */
|
||||
private var basePackageParam: PackageParam? = null
|
||||
|
||||
/** 当前的 [Resources] */
|
||||
private var baseResources: Resources? = null
|
||||
|
||||
/**
|
||||
* 当前的 [Resources]
|
||||
* @return [Resources]
|
||||
* @throws IllegalStateException 如果 [LocaleString] 没有被绑定
|
||||
*/
|
||||
private val resources
|
||||
get() = baseContext?.resources ?: basePackageParam?.moduleAppResources ?: baseResources
|
||||
?: error("LocaleString must bind an instance first")
|
||||
|
||||
/**
|
||||
* 绑定并初始化
|
||||
* @param instance 可以是 [Context]、[PackageParam]、[Resources]
|
||||
*/
|
||||
fun bind(instance: Any) {
|
||||
when (instance) {
|
||||
is Context -> baseContext = instance
|
||||
is PackageParam -> basePackageParam = instance
|
||||
is Resources -> baseResources = instance
|
||||
else -> error("LocaleString bind an unknown instance")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据资源 Id 获取字符串
|
||||
* @param objArrs 格式化实例
|
||||
* @return [String]
|
||||
*/
|
||||
private fun Int.bind(vararg objArrs: Any) = resources.getString(this, *objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val appName get() = appName()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun appName(vararg objArrs: Any) = R.string.app_name.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val copied get() = copied()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun copied(vararg objArrs: Any) = R.string.copied.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val copyFail get() = copyFail()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun copyFail(vararg objArrs: Any) = R.string.copy_fail.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val printToLogcatSuccess get() = printToLogcatSuccess()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun printToLogcatSuccess(vararg objArrs: Any) = R.string.print_to_logcat_success.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val outputStackSuccess get() = outputStackSuccess()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun outputStackSuccess(vararg objArrs: Any) = R.string.output_stack_success.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val outputStackFail get() = outputStackFail()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun outputStackFail(vararg objArrs: Any) = R.string.output_stack_fail.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val aerrTitle get() = aerrTitle()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun aerrTitle(vararg objArrs: Any) = R.string.aerr_title.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val aerrRepeatedTitle get() = aerrRepeatedTitle()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun aerrRepeatedTitle(vararg objArrs: Any) = R.string.aerr_repeated_title.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val appInfo get() = appInfo()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun appInfo(vararg objArrs: Any) = R.string.app_info.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val closeApp get() = closeApp()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun closeApp(vararg objArrs: Any) = R.string.close_app.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val reopenApp get() = reopenApp()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun reopenApp(vararg objArrs: Any) = R.string.reopen_app.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val errorDetail get() = errorDetail()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun errorDetail(vararg objArrs: Any) = R.string.error_detail.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val muteIfUnlock get() = muteIfUnlock()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun muteIfUnlock(vararg objArrs: Any) = R.string.mute_if_unlock.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val muteIfRestart get() = muteIfRestart()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun muteIfRestart(vararg objArrs: Any) = R.string.mute_if_restart.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val muteIfUnlockTip get() = muteIfUnlockTip()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun muteIfUnlockTip(vararg objArrs: Any) = R.string.mute_if_unlock_tip.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val muteIfRestartTip get() = muteIfRestartTip()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun muteIfRestartTip(vararg objArrs: Any) = R.string.mute_if_restart_tip.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val noListData get() = noListData()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun noListData(vararg objArrs: Any) = R.string.no_list_data.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val confirm get() = confirm()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun confirm(vararg objArrs: Any) = R.string.confirm.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val cancel get() = cancel()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun cancel(vararg objArrs: Any) = R.string.cancel.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val more get() = more()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun more(vararg objArrs: Any) = R.string.more.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val notice get() = notice()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun notice(vararg objArrs: Any) = R.string.notice.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val warning get() = warning()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun warning(vararg objArrs: Any) = R.string.warning.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val areYouSureClearErrors get() = areYouSureClearErrors()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun areYouSureClearErrors(vararg objArrs: Any) = R.string.are_you_sure_clear_errors.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val allErrorsClearSuccess get() = allErrorsClearSuccess()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun allErrorsClearSuccess(vararg objArrs: Any) = R.string.all_errors_clear_success.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val areYouSureExportAllErrors get() = areYouSureExportAllErrors()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun areYouSureExportAllErrors(vararg objArrs: Any) = R.string.are_you_sure_export_all_errors.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val exportAllErrorsSuccess get() = exportAllErrorsSuccess()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun exportAllErrorsSuccess(vararg objArrs: Any) = R.string.export_all_errors_success.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val exportAllErrorsFail get() = exportAllErrorsFail()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun exportAllErrorsFail(vararg objArrs: Any) = R.string.export_all_errors_fail.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val noCpuAbi get() = noCpuAbi()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun noCpuAbi(vararg objArrs: Any) = R.string.no_cpu_abi.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val areYouSureRemoveRecord get() = areYouSureRemoveRecord()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun areYouSureRemoveRecord(vararg objArrs: Any) = R.string.are_you_sure_remove_record.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val gotIt get() = gotIt()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun gotIt(vararg objArrs: Any) = R.string.got_it.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val moduleVersion get() = moduleVersion()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun moduleVersion(vararg objArrs: Any) = R.string.module_version.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val systemVersion get() = systemVersion()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun systemVersion(vararg objArrs: Any) = R.string.system_version.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val areYouSureRestartSystem get() = areYouSureRestartSystem()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun areYouSureRestartSystem(vararg objArrs: Any) = R.string.are_your_sure_restart_system.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val fastRestart get() = fastRestart()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun fastRestart(vararg objArrs: Any) = R.string.fast_restart.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val accessRootFail get() = accessRootFail()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun accessRootFail(vararg objArrs: Any) = R.string.access_root_fail.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val moduleNotActivated get() = moduleNotActivated()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun moduleNotActivated(vararg objArrs: Any) = R.string.module_not_activated.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val moduleIsActivated get() = moduleIsActivated()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun moduleIsActivated(vararg objArrs: Any) = R.string.module_is_activated.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val moduleNotFullyActivated get() = moduleNotFullyActivated()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun moduleNotFullyActivated(vararg objArrs: Any) = R.string.module_not_fully_activated.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val momentAgo get() = momentAgo()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun momentAgo(vararg objArrs: Any) = R.string.moment_ago.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val secondAgo get() = secondAgo()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun secondAgo(vararg objArrs: Any) = R.string.second_ago.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val minuteAgo get() = minuteAgo()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun minuteAgo(vararg objArrs: Any) = R.string.minute_ago.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val hourAgo get() = hourAgo()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun hourAgo(vararg objArrs: Any) = R.string.hour_ago.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val dayAgo get() = dayAgo()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun dayAgo(vararg objArrs: Any) = R.string.day_ago.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val monthAgo get() = monthAgo()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun monthAgo(vararg objArrs: Any) = R.string.month_ago.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val yearAgo get() = yearAgo()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun yearAgo(vararg objArrs: Any) = R.string.year_ago.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val crashProcess get() = crashProcess()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun crashProcess(vararg objArrs: Any) = R.string.crash_process.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val shareErrorStack get() = shareErrorStack()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun shareErrorStack(vararg objArrs: Any) = R.string.share_error_stack.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val areYouSureUnmuteAll get() = areYouSureUnmuteAll()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun areYouSureUnmuteAll(vararg objArrs: Any) = R.string.are_you_sure_unmute_all.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val noListResult get() = noListResult()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun noListResult(vararg objArrs: Any) = R.string.no_list_result.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val filterByCondition get() = filterByCondition()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun filterByCondition(vararg objArrs: Any) = R.string.filter_by_condition.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val clearFilters get() = clearFilters()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun clearFilters(vararg objArrs: Any) = R.string.clear_filters.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val resultCount get() = resultCount()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun resultCount(vararg objArrs: Any) = R.string.result_count.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val loading get() = loading()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun loading(vararg objArrs: Any) = R.string.loading.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val showErrorsDialog get() = showErrorsDialog()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun showErrorsDialog(vararg objArrs: Any) = R.string.show_errors_dialog.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val showErrorsToast get() = showErrorsToast()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun showErrorsToast(vararg objArrs: Any) = R.string.show_errors_toast.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val showNothing get() = showNothing()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun showNothing(vararg objArrs: Any) = R.string.show_nothing.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val appErrorsStatistics get() = appErrorsStatistics()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun appErrorsStatistics(vararg objArrs: Any) = R.string.app_errors_statistics.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val totalErrorsUnit get() = totalErrorsUnit()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun totalErrorsUnit(vararg objArrs: Any) = R.string.total_errors_unit.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val totalAppsUnit get() = totalAppsUnit()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun totalAppsUnit(vararg objArrs: Any) = R.string.total_apps_unit.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val generatingStatistics get() = generatingStatistics()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun generatingStatistics(vararg objArrs: Any) = R.string.generating_statistics.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val moduleNotFullyActivatedTip get() = moduleNotFullyActivatedTip()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun moduleNotFullyActivatedTip(vararg objArrs: Any) = R.string.module_not_fully_activated_tip.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val showErrorsNotify get() = showErrorsNotify()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun showErrorsNotify(vararg objArrs: Any) = R.string.show_errors_notify.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val appErrorsTip get() = appErrorsTip()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun appErrorsTip(vararg objArrs: Any) = R.string.app_errors_tip.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val batchOperations get() = batchOperations()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun batchOperations(vararg objArrs: Any) = R.string.batch_operations.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val areYouSureApplySiteApps get() = areYouSureApplySiteApps()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun areYouSureApplySiteApps(vararg objArrs: Any) = R.string.are_you_sure_apply_site_apps.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val developerNoticeTip get() = developerNoticeTip()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun developerNoticeTip(vararg objArrs: Any) = R.string.developer_notice_tip.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val developerNotice get() = developerNotice()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun developerNotice(vararg objArrs: Any) = R.string.developer_notice.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val fastRestartProblem get() = fastRestartProblem()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun fastRestartProblem(vararg objArrs: Any) = R.string.fast_restart_problem.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val userId get() = userId()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun userId(vararg objArrs: Any) = R.string.user_id.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val unableGetAppErrorsRecordTip get() = unableGetAppErrorsRecordTip()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun unableGetAppErrorsRecordTip(vararg objArrs: Any) = R.string.unable_get_app_errors_record_tip.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val exportAllLogsSuccess get() = exportAllLogsSuccess()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun exportAllLogsSuccess(vararg objArrs: Any) = R.string.export_all_logs_success.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val exportAllLogsFail get() = exportAllLogsFail()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun exportAllLogsFail(vararg objArrs: Any) = R.string.export_all_logs_fail.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val goItNow get() = goItNow()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun goItNow(vararg objArrs: Any) = R.string.go_it_now.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val accessRootFailTip get() = accessRootFailTip()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun accessRootFailTip(vararg objArrs: Any) = R.string.access_root_fail_tip.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val globalConfig get() = globalConfig()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun globalConfig(vararg objArrs: Any) = R.string.global_config.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val followGlobalConfig get() = followGlobalConfig()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun followGlobalConfig(vararg objArrs: Any) = R.string.follow_global_config.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val batchOperationsNumber get() = batchOperationsNumber()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun batchOperationsNumber(vararg objArrs: Any) = R.string.batch_operations_number.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val clickToUpdate get() = clickToUpdate()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun clickToUpdate(vararg objArrs: Any) = R.string.click_to_update.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val latestVersion get() = latestVersion()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun latestVersion(vararg objArrs: Any) = R.string.latest_version.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val latestVersionTip get() = latestVersionTip()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun latestVersionTip(vararg objArrs: Any) = R.string.latest_version_tip.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val updateNow get() = updateNow()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun updateNow(vararg objArrs: Any) = R.string.update_now.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val recordCount get() = recordCount()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun recordCount(vararg objArrs: Any) = R.string.record_count.bind(*objArrs)
|
||||
}
|
23
build.gradle
23
build.gradle
@@ -1,23 +0,0 @@
|
||||
plugins {
|
||||
id 'com.android.application' version '7.4.1' apply false
|
||||
id 'com.android.library' version '7.4.1' apply false
|
||||
id 'org.jetbrains.kotlin.android' version '1.8.20' apply false
|
||||
id 'com.google.devtools.ksp' version '1.8.20-1.0.10' apply false
|
||||
}
|
||||
|
||||
ext {
|
||||
android = [
|
||||
compileSdk: 33,
|
||||
minSdk : 24,
|
||||
targetSdk : 33,
|
||||
ndkVersion: '24.0.8215888'
|
||||
]
|
||||
app = [
|
||||
versionName : '1.25',
|
||||
versionCode : 5,
|
||||
signingConfigs: [
|
||||
secretConfigsDirPath : "${projectDir.getAbsolutePath()}/.secret",
|
||||
secretConfigsFileName: "key_store_secret.json"
|
||||
]
|
||||
]
|
||||
}
|
5
build.gradle.kts
Normal file
5
build.gradle.kts
Normal file
@@ -0,0 +1,5 @@
|
||||
plugins {
|
||||
autowire(libs.plugins.android.application) apply false
|
||||
autowire(libs.plugins.kotlin.android) apply false
|
||||
autowire(libs.plugins.kotlin.ksp) apply false
|
||||
}
|
@@ -1,85 +0,0 @@
|
||||
import groovy.json.JsonSlurper
|
||||
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'org.jetbrains.kotlin.android'
|
||||
}
|
||||
|
||||
android {
|
||||
namespace 'com.fankes.apperrorsdemo'
|
||||
compileSdk rootProject.ext.android.compileSdk
|
||||
ndkVersion rootProject.ext.android.ndkVersion
|
||||
|
||||
signingConfigs {
|
||||
universal {
|
||||
def dirPath = rootProject.ext.app.signingConfigs.secretConfigsDirPath
|
||||
def fileName = rootProject.ext.app.signingConfigs.secretConfigsFileName
|
||||
def configs = new JsonSlurper().parse(file("${dirPath}/${fileName}"))
|
||||
keyAlias configs.keyAlias
|
||||
keyPassword configs.keyPassword
|
||||
storeFile file("${dirPath}/${configs.storeFileName}")
|
||||
storePassword configs.storePassword
|
||||
v1SigningEnabled true
|
||||
v2SigningEnabled true
|
||||
}
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
applicationId 'com.fankes.apperrorsdemo'
|
||||
|
||||
minSdk rootProject.ext.android.minSdk
|
||||
targetSdk rootProject.ext.android.targetSdk
|
||||
|
||||
versionCode rootProject.ext.app.versionCode
|
||||
versionName rootProject.ext.app.versionName
|
||||
|
||||
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
|
||||
consumerProguardFiles 'consumer-rules.pro'
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
debug {
|
||||
minifyEnabled false
|
||||
signingConfig signingConfigs.universal
|
||||
}
|
||||
release {
|
||||
minifyEnabled false
|
||||
signingConfig signingConfigs.universal
|
||||
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.yukireflection:api:1.0.1'
|
||||
implementation 'androidx.core:core-ktx:1.10.0'
|
||||
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||
implementation 'com.google.android.material:material:1.8.0'
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
|
||||
}
|
83
demo-app/build.gradle.kts
Normal file
83
demo-app/build.gradle.kts
Normal file
@@ -0,0 +1,83 @@
|
||||
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
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = "17"
|
||||
freeCompilerArgs = listOf(
|
||||
"-Xno-param-assertions",
|
||||
"-Xno-call-assertions",
|
||||
"-Xno-receiver-assertions"
|
||||
)
|
||||
}
|
||||
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
|
||||
val currentSuffix = property.github.ci.commit.id?.let { suffix -> if (suffix.isNotBlank()) "-$suffix" 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.yukireflection.api)
|
||||
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,14 +1,24 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<uses-permission
|
||||
android:name="${applicationId}.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION"
|
||||
tools:node="remove" />
|
||||
<permission
|
||||
android:name="${applicationId}.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION"
|
||||
tools:node="remove" />
|
||||
|
||||
<application
|
||||
android:name=".application.DemoApplication"
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:localeConfig="@xml/locales_config"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.AppErrorsDemo">
|
||||
android:theme="@style/Theme.AppErrorsDemo"
|
||||
tools:targetApi="tiramisu">
|
||||
|
||||
<activity
|
||||
android:name=".ui.activity.MainActivity"
|
||||
|
@@ -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
|
||||
|
||||
|
@@ -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
|
||||
|
||||
|
@@ -17,21 +17,27 @@
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/5/10.
|
||||
* This file is created by fankes on 2022/5/10.
|
||||
*/
|
||||
@file:Suppress("SetTextI18n")
|
||||
|
||||
package com.fankes.apperrorsdemo.ui.activity
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.SystemClock
|
||||
import com.fankes.apperrorsdemo.R
|
||||
import com.fankes.apperrorsdemo.databinding.ActivityMainBinding
|
||||
import com.fankes.apperrorsdemo.databinding.ActivityMultiProcessBinding
|
||||
import com.fankes.apperrorsdemo.generated.DemoAppProperties
|
||||
import com.fankes.apperrorsdemo.native.Channel
|
||||
import com.fankes.apperrorsdemo.ui.activity.base.BaseActivity
|
||||
|
||||
class MainActivity : BaseActivity<ActivityMainBinding>() {
|
||||
|
||||
override fun onCreate() {
|
||||
binding.titleBackIcon.setOnClickListener { finish() }
|
||||
DemoAppProperties.GITHUB_CI_COMMIT_ID.takeIf(String::isNotBlank)?.also {
|
||||
binding.titleText.text = "${getString(R.string.app_name)} ($it)"
|
||||
}; binding.titleBackIcon.setOnClickListener { finish() }
|
||||
binding.throwRuntimeButton.setOnClickListener { Channel.throwRuntimeException() }
|
||||
binding.throwIllegalStateButton.setOnClickListener { Channel.throwIllegalStateException() }
|
||||
binding.throwNullPointerButton.setOnClickListener { Channel.throwNullPointerException() }
|
||||
|
@@ -17,10 +17,8 @@
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/5/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
|
||||
|
@@ -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
|
||||
|
||||
|
@@ -30,6 +30,7 @@
|
||||
android:tooltipText="@string/back" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title_text"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="2.5dp"
|
||||
|
9
demo-app/src/main/res/xml/locales_config.xml
Normal file
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,27 @@
|
||||
# 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
|
||||
kotlin.incremental.useClasspathSnapshot=true
|
||||
# Project Configuration
|
||||
project.name=AppErrorsTracking
|
||||
project.android.compileSdk=34
|
||||
project.android.minSdk=24
|
||||
project.android.targetSdk=34
|
||||
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}
|
92
gradle/sweet-dependency/sweet-dependency-config.yaml
Normal file
92
gradle/sweet-dependency/sweet-dependency-config.yaml
Normal file
@@ -0,0 +1,92 @@
|
||||
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.1.2
|
||||
org.jetbrains.kotlin.android:
|
||||
alias: kotlin-android
|
||||
version: 1.9.10
|
||||
com.highcapable.flexilocale:
|
||||
alias: flexi-locale
|
||||
version: 1.0.1
|
||||
com.google.devtools.ksp:
|
||||
alias: kotlin-ksp
|
||||
version: 1.9.10-1.0.13
|
||||
|
||||
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.2.0
|
||||
ksp-xposed:
|
||||
version-ref: <this>::api
|
||||
com.highcapable.yukireflection:
|
||||
api:
|
||||
version: 1.0.3
|
||||
com.microsoft.appcenter:
|
||||
appcenter-analytics:
|
||||
version: 5.0.2
|
||||
appcenter-crashes:
|
||||
version-ref: <this>::appcenter-analytics
|
||||
com.github.topjohnwu.libsu:
|
||||
core:
|
||||
version: 5.2.1
|
||||
com.github.duanhong169:
|
||||
drawabletoolbox:
|
||||
version: 1.0.7
|
||||
com.google.code.gson:
|
||||
gson:
|
||||
version: 2.10.1
|
||||
com.squareup.okhttp3:
|
||||
okhttp:
|
||||
version: 5.0.0-alpha.11
|
||||
androidx.core:
|
||||
core-ktx:
|
||||
version: 1.12.0
|
||||
androidx.appcompat:
|
||||
appcompat:
|
||||
version: 1.6.1
|
||||
com.google.android.material:
|
||||
material:
|
||||
version: 1.10.0
|
||||
androidx.constraintlayout:
|
||||
constraintlayout:
|
||||
version: 2.1.4
|
||||
androidx.test.ext:
|
||||
junit:
|
||||
version: 1.1.5
|
||||
androidx.test.espresso:
|
||||
espresso-core:
|
||||
version: 3.5.1
|
||||
junit:
|
||||
junit:
|
||||
version: 4.13.2
|
5
gradle/wrapper/gradle-wrapper.properties
vendored
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-8.0.2-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip
|
||||
distributionPath=wrapper/dists
|
||||
zipStorePath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStoreBase=GRADLE_USER_HOME
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
0
app/.gitignore → module-app/.gitignore
vendored
0
app/.gitignore → module-app/.gitignore
vendored
88
module-app/build.gradle.kts
Normal file
88
module-app/build.gradle.kts
Normal file
@@ -0,0 +1,88 @@
|
||||
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
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = "17"
|
||||
freeCompilerArgs = listOf(
|
||||
"-Xno-param-assertions",
|
||||
"-Xno-call-assertions",
|
||||
"-Xno-receiver-assertions"
|
||||
)
|
||||
}
|
||||
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
|
||||
val currentSuffix = property.github.ci.commit.id?.let { suffix -> if (suffix.isNotBlank()) "-$suffix" 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.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)
|
||||
}
|
@@ -32,31 +32,36 @@
|
||||
-adaptresourcefilecontents
|
||||
|
||||
-renamesourcefileattribute P
|
||||
-keepattributes SourceFile,LineNumberTable
|
||||
-keepattributes SourceFile,Signature,LineNumberTable
|
||||
|
||||
## ---------------Begin: proguard configuration for Gson ----------
|
||||
# Gson uses generic type information stored in a class file when working with fields. Proguard
|
||||
# removes such information by default, so configure it to keep all of it.
|
||||
|
||||
# Explicitly preserve all serialization members. The Serializable interface
|
||||
# is only a marker interface, so it wouldn't save them.
|
||||
-keepclassmembers class * implements java.io.Serializable {
|
||||
static final long serialVersionUID;
|
||||
private static final java.io.ObjectStreamField[] serialPersistentFields;
|
||||
private void writeObject(java.io.ObjectOutputStream);
|
||||
private void readObject(java.io.ObjectInputStream);
|
||||
java.lang.Object writeReplace();
|
||||
java.lang.Object readResolve();
|
||||
}
|
||||
|
||||
# Gson specific classes
|
||||
-dontwarn sun.misc**
|
||||
-keep class com.google.gson.stream**{*;}
|
||||
|
||||
# Application classes that will be serialized/deserialized over Gson
|
||||
-keep class com.google.gson.examples.android.model** { <fields>; }
|
||||
|
||||
# Prevent proguard from stripping interface information from TypeAdapter, TypeAdapterFactory,
|
||||
# JsonSerializer, JsonDeserializer instances (so they can be used in @JsonAdapter)
|
||||
-keep class * implements com.google.gson.TypeAdapter
|
||||
-keep class * implements com.google.gson.TypeAdapterFactory
|
||||
-keep class * implements com.google.gson.JsonSerializer
|
||||
-keep class * implements com.google.gson.JsonDeserializer
|
||||
-dontwarn sun.misc.**
|
||||
-keep class com.google.gson.stream.** { *; }
|
||||
|
||||
# Prevent R8 from leaving Data object members always null
|
||||
-keepclassmembers,allowobfuscation class * {
|
||||
@com.google.gson.annotations.SerializedName <fields>;
|
||||
}
|
||||
|
||||
# Retain generic signatures of TypeToken and its subclasses with R8 version 3.0 and higher.
|
||||
-keep,allowobfuscation,allowshrinking class com.google.gson.reflect.TypeToken
|
||||
-keep,allowobfuscation,allowshrinking class * extends com.google.gson.reflect.TypeToken
|
||||
|
||||
## ---------------End: proguard configuration for Gson ----------
|
||||
|
||||
-assumenosideeffects class kotlin.jvm.internal.Intrinsics {
|
||||
@@ -64,4 +69,8 @@
|
||||
public static *** throwUninitializedPropertyAccessException(...);
|
||||
}
|
||||
|
||||
-keep class com.fankes.apperrorstracking.databinding**{*;}
|
||||
-keep class * extends android.app.Activity
|
||||
-keep class * implements androidx.viewbinding.ViewBinding {
|
||||
<init>();
|
||||
*** inflate(android.view.LayoutInflater);
|
||||
}
|
@@ -10,14 +10,23 @@
|
||||
android:name="android.permission.INTERACT_ACROSS_USERS"
|
||||
tools:ignore="ProtectedPermissions" />
|
||||
|
||||
<uses-permission
|
||||
android:name="${applicationId}.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION"
|
||||
tools:node="remove" />
|
||||
<permission
|
||||
android:name="${applicationId}.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION"
|
||||
tools:node="remove" />
|
||||
|
||||
<application
|
||||
android:name=".application.AppErrorsApplication"
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:localeConfig="@xml/locales_config"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.AppErrorsTracking">
|
||||
android:theme="@style/Theme.AppErrorsTracking"
|
||||
tools:targetApi="tiramisu">
|
||||
|
||||
<meta-data
|
||||
android:name="xposedmodule"
|
BIN
module-app/src/main/ic_launcher-playstore.png
Normal file
BIN
module-app/src/main/ic_launcher-playstore.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
@@ -17,13 +17,14 @@
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/5/10.
|
||||
* This file is created by fankes on 2022/5/10.
|
||||
*/
|
||||
package com.fankes.apperrorstracking.application
|
||||
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import com.fankes.apperrorstracking.data.ConfigData
|
||||
import com.fankes.apperrorstracking.locale.LocaleString
|
||||
import com.fankes.apperrorstracking.generated.locale.ModuleAppLocale
|
||||
import com.fankes.apperrorstracking.locale.locale
|
||||
import com.fankes.apperrorstracking.utils.tool.AppAnalyticsTool
|
||||
import com.highcapable.yukihookapi.hook.xposed.application.ModuleApplication
|
||||
|
||||
@@ -31,13 +32,13 @@ class AppErrorsApplication : ModuleApplication() {
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
/** 绑定 I18n */
|
||||
locale = ModuleAppLocale.attach(this)
|
||||
/** 跟随系统夜间模式 */
|
||||
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
|
||||
/** 绑定 I18n */
|
||||
LocaleString.bind(instance = this)
|
||||
/** 装载存储控制类 */
|
||||
ConfigData.init(instance = this)
|
||||
ConfigData.init(this)
|
||||
/** 装载 App Center */
|
||||
AppAnalyticsTool.init(instance = this)
|
||||
AppAnalyticsTool.init(this)
|
||||
}
|
||||
}
|
@@ -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
|
||||
|
@@ -17,19 +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.
|
||||
*/
|
||||
package com.fankes.apperrorstracking.bean
|
||||
|
||||
import android.app.ApplicationErrorReport
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import com.fankes.apperrorstracking.locale.LocaleString
|
||||
import com.fankes.apperrorstracking.utils.factory.*
|
||||
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.*
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
|
||||
/**
|
||||
* 应用异常信息 bean
|
||||
@@ -39,6 +47,8 @@ import java.util.*
|
||||
* @param packageName 包名
|
||||
* @param versionName 版本名称
|
||||
* @param versionCode 版本号
|
||||
* @param targetSdk 目标 SDK 版本
|
||||
* @param minSdk 最低 SDK 版本
|
||||
* @param isNativeCrash 是否为原生层异常
|
||||
* @param exceptionClassName 异常类名
|
||||
* @param exceptionMessage 异常信息
|
||||
@@ -62,6 +72,10 @@ data class AppErrorsInfoBean(
|
||||
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")
|
||||
@@ -102,6 +116,8 @@ data class AppErrorsInfoBean(
|
||||
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 {
|
||||
@@ -130,7 +146,7 @@ data class AppErrorsInfoBean(
|
||||
* 获取生成的 Json 文件名
|
||||
* @return [String]
|
||||
*/
|
||||
val jsonFileName get() = "${packageName}_${pid}_${timestamp}.json"
|
||||
val jsonFileName get() = "${packageName}_${pid}_$timestamp.json"
|
||||
|
||||
/**
|
||||
* 获取 APP 版本信息与版本号
|
||||
@@ -150,13 +166,13 @@ data class AppErrorsInfoBean(
|
||||
*/
|
||||
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
|
||||
now = locale.momentAgo,
|
||||
second = locale.secondAgo,
|
||||
minute = locale.minuteAgo,
|
||||
hour = locale.hourAgo,
|
||||
day = locale.dayAgo,
|
||||
month = locale.monthAgo,
|
||||
year = locale.yearAgo
|
||||
)
|
||||
|
||||
/**
|
||||
@@ -167,42 +183,80 @@ data class AppErrorsInfoBean(
|
||||
|
||||
/**
|
||||
* 获取异常堆栈分享模板
|
||||
* @param sDeviceBrand
|
||||
* @param sDeviceModel
|
||||
* @param sDisplay
|
||||
* @param sPackageName
|
||||
* @return [String]
|
||||
*/
|
||||
val stackOutputShareContent
|
||||
get() = "Generated by AppErrorsTracking\n" +
|
||||
"Project Url: https://github.com/KitsunePie/AppErrorsTracking\n" +
|
||||
"===============\n$environmentInfo"
|
||||
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]
|
||||
*/
|
||||
val stackOutputFileContent
|
||||
get() = "================================================================\n" +
|
||||
" Generated by AppErrorsTracking\n" +
|
||||
" Project Url: https://github.com/KitsunePie/AppErrorsTracking\n" +
|
||||
"================================================================\n" +
|
||||
environmentInfo
|
||||
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 val environmentInfo
|
||||
get() = "[Device Brand]: ${Build.BRAND}\n" +
|
||||
"[Device Model]: ${Build.MODEL}\n" +
|
||||
"[Display]: ${Build.DISPLAY}\n" +
|
||||
"[Android Version]: ${Build.VERSION.RELEASE}\n" +
|
||||
"[Android API Level]: ${Build.VERSION.SDK_INT}\n" +
|
||||
"[System Locale]: ${Locale.getDefault()}\n" +
|
||||
"[Process ID]: $pid\n" +
|
||||
(if (userId > 0) "[User Id]: $userId\n" else "") +
|
||||
"[CPU ABI]: ${cpuAbi.ifBlank { "none" }}\n" +
|
||||
"[Package Name]: $packageName\n" +
|
||||
"[Version Name]: ${versionName.ifBlank { "unknown" }}\n" +
|
||||
"[Version Code]: ${versionCode.takeIf { it != -1L } ?: "unknown"}\n" +
|
||||
"[Error Type]: ${if (isNativeCrash) "Native" else "JVM"}\n" +
|
||||
"[Crash Time]: $utcTime\n" +
|
||||
"[Stack Trace]:\n" + stackTrace
|
||||
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 "***"
|
||||
}
|
@@ -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/4.
|
||||
* This file is created by fankes on 2022/6/4.
|
||||
*/
|
||||
package com.fankes.apperrorstracking.bean
|
||||
|
@@ -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
|
||||
|
@@ -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/3.
|
||||
* This file is created by fankes on 2022/6/3.
|
||||
*/
|
||||
package com.fankes.apperrorstracking.bean
|
||||
|
@@ -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 2023/1/22.
|
||||
* This file is created by fankes on 2023/1/22.
|
||||
*/
|
||||
package com.fankes.apperrorstracking.bean.enum
|
||||
|
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
|
||||
* Copyright (C) 2017-2023 Fankes Studio(qzmmcn@163.com)
|
||||
* https://github.com/KitsunePie/AppErrorsTracking
|
||||
*
|
||||
* This software is non-free but opensource software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Affero General Public License
|
||||
* as published by the Free Software Foundation; either
|
||||
* version 3 of the License, or any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is created by fankes on 2023/9/19.
|
||||
*/
|
||||
@file:Suppress("MemberVisibilityCanBePrivate")
|
||||
|
||||
package com.fankes.apperrorstracking.const
|
||||
|
||||
import com.fankes.apperrorstracking.generated.ModuleAppProperties
|
||||
import com.fankes.apperrorstracking.wrapper.BuildConfigWrapper
|
||||
|
||||
/**
|
||||
* 包名常量定义类
|
||||
*/
|
||||
object PackageName {
|
||||
|
||||
/** 系统框架 */
|
||||
const val SYSTEM_FRAMEWORK = "android"
|
||||
}
|
||||
|
||||
/**
|
||||
* 模块版本常量定义类
|
||||
*/
|
||||
object ModuleVersion {
|
||||
|
||||
/** 当前 GitHub 提交的 ID (CI 自动构建) */
|
||||
const val GITHUB_COMMIT_ID = ModuleAppProperties.GITHUB_CI_COMMIT_ID
|
||||
|
||||
/** 版本名称 */
|
||||
const val NAME = BuildConfigWrapper.VERSION_NAME
|
||||
|
||||
/** 版本号 */
|
||||
const val CODE = BuildConfigWrapper.VERSION_CODE
|
||||
|
||||
/** 是否为 CI 自动构建版本 */
|
||||
val isCiMode = GITHUB_COMMIT_ID.isNotBlank()
|
||||
|
||||
/** 当前版本名称后缀 */
|
||||
val suffix = GITHUB_COMMIT_ID.let { if (it.isNotBlank()) "-$it" else "" }
|
||||
|
||||
override fun toString() = "$NAME$suffix($CODE)"
|
||||
}
|
@@ -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 2023/1/20.
|
||||
* This file is created by fankes on 2023/1/20.
|
||||
*/
|
||||
package com.fankes.apperrorstracking.data
|
||||
|
||||
@@ -74,9 +74,9 @@ object AppErrorsConfigData {
|
||||
if (packageName.isNotBlank()) when (type) {
|
||||
AppErrorsConfigType.GLOBAL ->
|
||||
showDialogApps.contains(packageName).not() &&
|
||||
showNotifyApps.contains(packageName).not() &&
|
||||
showToastApps.contains(packageName).not() &&
|
||||
showNothingApps.contains(packageName).not()
|
||||
showNotifyApps.contains(packageName).not() &&
|
||||
showToastApps.contains(packageName).not() &&
|
||||
showNothingApps.contains(packageName).not()
|
||||
AppErrorsConfigType.DIALOG -> showDialogApps.contains(packageName)
|
||||
AppErrorsConfigType.NOTIFY -> showNotifyApps.contains(packageName)
|
||||
AppErrorsConfigType.TOAST -> showToastApps.contains(packageName)
|
@@ -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 2023/1/17.
|
||||
* This file is created by fankes on 2023/1/17.
|
||||
*/
|
||||
@file:Suppress("StaticFieldLeak")
|
||||
|
||||
@@ -26,8 +26,12 @@ package com.fankes.apperrorstracking.data
|
||||
import android.content.Context
|
||||
import android.provider.Settings
|
||||
import com.fankes.apperrorstracking.bean.AppErrorsInfoBean
|
||||
import com.fankes.apperrorstracking.utils.factory.*
|
||||
import com.highcapable.yukihookapi.hook.log.loggerE
|
||||
import com.fankes.apperrorstracking.utils.factory.appCpuAbiOf
|
||||
import com.fankes.apperrorstracking.utils.factory.appVersionCodeOf
|
||||
import com.fankes.apperrorstracking.utils.factory.appVersionNameOf
|
||||
import com.fankes.apperrorstracking.utils.factory.toEntityOrNull
|
||||
import com.fankes.apperrorstracking.utils.factory.toJsonOrNull
|
||||
import com.highcapable.yukihookapi.hook.log.YLog
|
||||
import java.io.File
|
||||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
|
||||
@@ -72,7 +76,7 @@ object AppErrorsRecordData {
|
||||
runCatching {
|
||||
errorsInfoDataFolder.also { if (it.exists().not() || it.isFile) it.apply { delete(); mkdirs() } }
|
||||
}.onFailure {
|
||||
loggerE(msg = "Can't create directory \"$FOLDER_PATH\", there will be problems with the app errors records function", e = it)
|
||||
YLog.error("Can't create directory \"$FOLDER_PATH\", there will be problems with the app errors records function", it)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,11 +95,11 @@ object AppErrorsRecordData {
|
||||
e.versionCode = it.appVersionCodeOf(e.packageName)
|
||||
e.toJsonOrNull()?.also { json -> File(errorsInfoDataFolder.absolutePath, e.jsonFileName).writeText(json) }
|
||||
}.let { result ->
|
||||
if (result != null) {
|
||||
Settings.Secure.putString(it.contentResolver, keyName, "")
|
||||
result
|
||||
} else null
|
||||
}
|
||||
if (result != null) {
|
||||
Settings.Secure.putString(it.contentResolver, keyName, "")
|
||||
result
|
||||
} else null
|
||||
}
|
||||
}.getOrNull()
|
||||
}
|
||||
|
@@ -17,7 +17,7 @@
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/10/1.
|
||||
* This file is created by fankes on 2022/10/1.
|
||||
*/
|
||||
@file:Suppress("MemberVisibilityCanBePrivate")
|
||||
|
||||
@@ -26,7 +26,7 @@ package com.fankes.apperrorstracking.data
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import com.highcapable.yukihookapi.hook.factory.prefs
|
||||
import com.highcapable.yukihookapi.hook.log.loggerW
|
||||
import com.highcapable.yukihookapi.hook.log.YLog
|
||||
import com.highcapable.yukihookapi.hook.param.PackageParam
|
||||
import com.highcapable.yukihookapi.hook.xposed.prefs.data.PrefsData
|
||||
|
||||
@@ -91,7 +91,7 @@ object ConfigData {
|
||||
internal fun putStringSet(key: String, value: Set<String>) {
|
||||
when (instance) {
|
||||
is Context -> (instance as Context).prefs().edit { putStringSet(key, value) }
|
||||
is PackageParam -> loggerW(msg = "Not support for this method")
|
||||
is PackageParam -> YLog.warn("Not support for this method")
|
||||
else -> error("Unknown type for put prefs data")
|
||||
}
|
||||
}
|
||||
@@ -115,7 +115,7 @@ object ConfigData {
|
||||
internal fun putInt(data: PrefsData<Int>, value: Int) {
|
||||
when (instance) {
|
||||
is Context -> (instance as Context).prefs().edit { put(data, value) }
|
||||
is PackageParam -> loggerW(msg = "Not support for this method")
|
||||
is PackageParam -> YLog.warn("Not support for this method")
|
||||
else -> error("Unknown type for put prefs data")
|
||||
}
|
||||
}
|
||||
@@ -139,7 +139,7 @@ object ConfigData {
|
||||
internal fun putBoolean(data: PrefsData<Boolean>, value: Boolean) {
|
||||
when (instance) {
|
||||
is Context -> (instance as Context).prefs().edit { put(data, value) }
|
||||
is PackageParam -> loggerW(msg = "Not support for this method")
|
||||
is PackageParam -> YLog.warn("Not support for this method")
|
||||
else -> error("Unknown type for put prefs data")
|
||||
}
|
||||
}
|
@@ -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 2023/1/20.
|
||||
* This file is created by fankes on 2023/1/20.
|
||||
*/
|
||||
package com.fankes.apperrorstracking.data.enum
|
||||
|
@@ -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 2023/2/3.
|
||||
* This file is created by fankes on 2023/2/3.
|
||||
*/
|
||||
@file:Suppress("unused", "MemberVisibilityCanBePrivate")
|
||||
|
@@ -17,13 +17,14 @@
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/5/7.
|
||||
* This file is created by fankes on 2022/5/7.
|
||||
*/
|
||||
package com.fankes.apperrorstracking.hook
|
||||
|
||||
import com.fankes.apperrorstracking.data.ConfigData
|
||||
import com.fankes.apperrorstracking.generated.locale.ModuleAppLocale
|
||||
import com.fankes.apperrorstracking.hook.entity.FrameworkHooker
|
||||
import com.fankes.apperrorstracking.locale.LocaleString
|
||||
import com.fankes.apperrorstracking.locale.locale
|
||||
import com.highcapable.yukihookapi.annotation.xposed.InjectYukiHookWithXposed
|
||||
import com.highcapable.yukihookapi.hook.factory.configs
|
||||
import com.highcapable.yukihookapi.hook.factory.encase
|
||||
@@ -38,13 +39,12 @@ object HookEntry : IYukiHookXposedInit {
|
||||
isRecord = true
|
||||
}
|
||||
isDebug = false
|
||||
isEnablePrefsBridgeCache = false
|
||||
}
|
||||
|
||||
override fun onHook() = encase {
|
||||
loadSystem {
|
||||
LocaleString.bind(instance = this)
|
||||
ConfigData.init(instance = this)
|
||||
locale = ModuleAppLocale.attach { moduleAppResources }
|
||||
ConfigData.init(this)
|
||||
loadHooker(FrameworkHooker)
|
||||
}
|
||||
}
|
@@ -17,8 +17,10 @@
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/5/7.
|
||||
* This file is created by fankes on 2022/5/7.
|
||||
*/
|
||||
@file:Suppress("ConstPropertyName")
|
||||
|
||||
package com.fankes.apperrorstracking.hook.entity
|
||||
|
||||
import android.app.ApplicationErrorReport
|
||||
@@ -33,7 +35,6 @@ import android.os.SystemClock
|
||||
import android.util.ArrayMap
|
||||
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
|
||||
@@ -44,47 +45,57 @@ import com.fankes.apperrorstracking.data.AppErrorsConfigData
|
||||
import com.fankes.apperrorstracking.data.AppErrorsRecordData
|
||||
import com.fankes.apperrorstracking.data.ConfigData
|
||||
import com.fankes.apperrorstracking.data.enum.AppErrorsConfigType
|
||||
import com.fankes.apperrorstracking.locale.LocaleString
|
||||
import com.fankes.apperrorstracking.locale.locale
|
||||
import com.fankes.apperrorstracking.ui.activity.errors.AppErrorsDisplayActivity
|
||||
import com.fankes.apperrorstracking.ui.activity.errors.AppErrorsRecordActivity
|
||||
import com.fankes.apperrorstracking.utils.factory.*
|
||||
import com.fankes.apperrorstracking.utils.factory.appNameOf
|
||||
import com.fankes.apperrorstracking.utils.factory.drawableOf
|
||||
import com.fankes.apperrorstracking.utils.factory.isAppCanOpened
|
||||
import com.fankes.apperrorstracking.utils.factory.listOfPackages
|
||||
import com.fankes.apperrorstracking.utils.factory.openApp
|
||||
import com.fankes.apperrorstracking.utils.factory.pushNotify
|
||||
import com.fankes.apperrorstracking.utils.factory.toArrayList
|
||||
import com.fankes.apperrorstracking.utils.factory.toast
|
||||
import com.fankes.apperrorstracking.utils.tool.FrameworkTool
|
||||
import com.fankes.apperrorstracking.wrapper.BuildConfigWrapper
|
||||
import com.highcapable.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.log.loggerI
|
||||
import com.highcapable.yukihookapi.hook.log.loggerW
|
||||
import com.highcapable.yukihookapi.hook.log.YLog
|
||||
import com.highcapable.yukihookapi.hook.type.android.BundleClass
|
||||
import com.highcapable.yukihookapi.hook.type.android.MessageClass
|
||||
import com.highcapable.yukihookapi.hook.type.java.BooleanType
|
||||
|
||||
object FrameworkHooker : YukiBaseHooker() {
|
||||
|
||||
private const val ActivityManagerServiceClass = "com.android.server.am.ActivityManagerService"
|
||||
private const val UserControllerClass = "com.android.server.am.UserController"
|
||||
private const val AppErrorsClass = "com.android.server.am.AppErrors"
|
||||
private const val AppErrorDialogClass = "com.android.server.am.AppErrorDialog"
|
||||
private const val AppErrorDialog_DataClass = "com.android.server.am.AppErrorDialog\$Data"
|
||||
private const val ProcessRecordClass = "com.android.server.am.ProcessRecord"
|
||||
private const val ActivityTaskManagerService_LocalServiceClass = "com.android.server.wm.ActivityTaskManagerService\$LocalService"
|
||||
private val UserControllerClass by lazyClass("com.android.server.am.UserController")
|
||||
private val AppErrorsClass by lazyClass("com.android.server.am.AppErrors")
|
||||
private val AppErrorDialogClass by lazyClass("com.android.server.am.AppErrorDialog")
|
||||
private val AppErrorDialog_DataClass by lazyClass("com.android.server.am.AppErrorDialog\$Data")
|
||||
private val ProcessRecordClass by lazyClass("com.android.server.am.ProcessRecord")
|
||||
private val ActivityManagerServiceClass by lazyClassOrNull("com.android.server.am.ActivityManagerService")
|
||||
private val ActivityTaskManagerService_LocalServiceClass by lazyClassOrNull("com.android.server.wm.ActivityTaskManagerService\$LocalService")
|
||||
|
||||
private val PackageListClass = VariousClass(
|
||||
"com.android.server.am.ProcessRecord\$PackageList",
|
||||
"com.android.server.am.PackageList"
|
||||
private val PackageListClass by lazyClassOrNull(
|
||||
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"
|
||||
private val ErrorDialogControllerClass by lazyClassOrNull(
|
||||
VariousClass(
|
||||
"com.android.server.am.ProcessRecord\$ErrorDialogController",
|
||||
"com.android.server.am.ErrorDialogController"
|
||||
)
|
||||
)
|
||||
|
||||
/** 已忽略错误的 APP 数组 - 直到重新解锁 */
|
||||
private var mutedErrorsIfUnlockApps = HashSet<String>()
|
||||
private var mutedErrorsIfUnlockApps = mutableSetOf<String>()
|
||||
|
||||
/** 已忽略错误的 APP 数组 - 直到重新启动 */
|
||||
private var mutedErrorsIfRestartApps = HashSet<String>()
|
||||
private var mutedErrorsIfRestartApps = mutableSetOf<String>()
|
||||
|
||||
/**
|
||||
* APP 进程异常数据定义类
|
||||
@@ -98,40 +109,40 @@ object FrameworkHooker : YukiBaseHooker() {
|
||||
* 获取当前包列表实例
|
||||
* @return [Any] or null
|
||||
*/
|
||||
private val pkgList = if (ProcessRecordClass.toClass().hasMethod { name = "getPkgList"; emptyParam() })
|
||||
ProcessRecordClass.toClass().method { name = "getPkgList"; emptyParam() }.get(proc).call()
|
||||
else ProcessRecordClass.toClass().field { name = "pkgList" }.get(proc).any()
|
||||
private val pkgList = if (ProcessRecordClass.hasMethod { name = "getPkgList"; emptyParam() })
|
||||
ProcessRecordClass.method { name = "getPkgList"; emptyParam() }.get(proc).call()
|
||||
else ProcessRecordClass.field { name = "pkgList" }.get(proc).any()
|
||||
|
||||
/**
|
||||
* 获取当前包列表数组大小
|
||||
* @return [Int]
|
||||
*/
|
||||
private val pkgListSize = PackageListClass.toClassOrNull()?.method { name = "size"; emptyParam() }?.get(pkgList)?.int()
|
||||
?: ProcessRecordClass.toClass().field { name = "pkgList" }.get(proc).cast<ArrayMap<*, *>>()?.size ?: -1
|
||||
private val pkgListSize = PackageListClass?.method { name = "size"; emptyParam() }?.get(pkgList)?.int()
|
||||
?: ProcessRecordClass.field { name = "pkgList" }.get(proc).cast<ArrayMap<*, *>>()?.size ?: -1
|
||||
|
||||
/**
|
||||
* 获取当前 pid 信息
|
||||
* @return [Int]
|
||||
*/
|
||||
val pid = ProcessRecordClass.toClass().field { name { it == "mPid" || it == "pid" } }.get(proc).int()
|
||||
val pid = ProcessRecordClass.field { name { it == "mPid" || it == "pid" } }.get(proc).int()
|
||||
|
||||
/**
|
||||
* 获取当前用户 ID 信息
|
||||
* @return [Int]
|
||||
*/
|
||||
val userId = ProcessRecordClass.toClass().field { name = "userId" }.get(proc).int()
|
||||
val userId = ProcessRecordClass.field { name = "userId" }.get(proc).int()
|
||||
|
||||
/**
|
||||
* 获取当前 APP 信息
|
||||
* @return [ApplicationInfo] or null
|
||||
*/
|
||||
val appInfo = ProcessRecordClass.toClass().field { name = "info" }.get(proc).cast<ApplicationInfo>()
|
||||
val appInfo = ProcessRecordClass.field { name = "info" }.get(proc).cast<ApplicationInfo>()
|
||||
|
||||
/**
|
||||
* 获取当前进程名称
|
||||
* @return [String]
|
||||
*/
|
||||
val processName = ProcessRecordClass.toClass().field { name = "processName" }.get(proc).string()
|
||||
val processName = ProcessRecordClass.field { name = "processName" }.get(proc).string()
|
||||
|
||||
/**
|
||||
* 获取当前 APP、进程 包名
|
||||
@@ -155,17 +166,17 @@ object FrameworkHooker : YukiBaseHooker() {
|
||||
* 获取当前进程是否为后台进程
|
||||
* @return [Boolean]
|
||||
*/
|
||||
val isBackgroundProcess = UserControllerClass.toClass()
|
||||
val isBackgroundProcess = UserControllerClass
|
||||
.method { name { it == "getCurrentProfileIds" || it == "getCurrentProfileIdsLocked" } }
|
||||
.get(ActivityManagerServiceClass.toClass().field { name = "mUserController" }
|
||||
.get(AppErrorsClass.toClass().field { name = "mService" }.get(errors).any()).any())
|
||||
.get(ActivityManagerServiceClass?.field { name = "mUserController" }
|
||||
?.get(AppErrorsClass.field { name = "mService" }.get(errors).any())?.any())
|
||||
.invoke<IntArray>()?.takeIf { it.isNotEmpty() }?.any { it != userId } ?: false
|
||||
|
||||
/**
|
||||
* 获取当前进程是否短时内重复崩溃
|
||||
* @return [Boolean]
|
||||
*/
|
||||
val isRepeatingCrash = resultData?.let { AppErrorDialog_DataClass.toClass().field { name = "repeating" }.get(it).boolean() } ?: false
|
||||
val isRepeatingCrash = resultData?.let { AppErrorDialog_DataClass.field { name = "repeating" }.get(it).boolean() } ?: false
|
||||
}
|
||||
|
||||
/** 注册生命周期 */
|
||||
@@ -184,34 +195,34 @@ object FrameworkHooker : YukiBaseHooker() {
|
||||
SystemClock.sleep(100)
|
||||
/** 刷新存储类 */
|
||||
AppErrorsConfigData.refresh()
|
||||
if (prefs.isPreferencesAvailable.not()) loggerW(msg = "Cannot refreshing app errors config data, preferences is not available")
|
||||
if (prefs.isPreferencesAvailable.not()) YLog.warn("Cannot refreshing app errors config data, preferences is not available")
|
||||
}
|
||||
onOpenAppUsedFramework {
|
||||
appContext?.openApp(it.first, it.second)
|
||||
loggerI(msg = "Opened \"${it.first}\"${it.second.takeIf { e -> e > 0 }?.let { e -> " --user $e" } ?: ""}")
|
||||
YLog.info("Opened \"${it.first}\"${it.second.takeIf { e -> e > 0 }?.let { e -> " --user $e" } ?: ""}")
|
||||
}
|
||||
onPushAppErrorInfoData {
|
||||
AppErrorsRecordData.allData.firstOrNull { e -> e.pid == it } ?: run {
|
||||
loggerW(msg = "Cannot received crash application data --pid $it")
|
||||
YLog.warn("Cannot received crash application data --pid $it")
|
||||
AppErrorsInfoBean()
|
||||
}
|
||||
}
|
||||
onPushAppErrorsInfoData { AppErrorsRecordData.allData.toArrayList() }
|
||||
onRemoveAppErrorsInfoData {
|
||||
loggerI(msg = "Removed app errors info data for package \"${it.packageName}\"")
|
||||
YLog.info("Removed app errors info data for package \"${it.packageName}\"")
|
||||
AppErrorsRecordData.remove(it)
|
||||
}
|
||||
onClearAppErrorsInfoData {
|
||||
loggerI(msg = "Cleared all app errors info data, size ${AppErrorsRecordData.allData.size}")
|
||||
YLog.info("Cleared all app errors info data, size ${AppErrorsRecordData.allData.size}")
|
||||
AppErrorsRecordData.clearAll()
|
||||
}
|
||||
onMutedErrorsIfUnlock {
|
||||
mutedErrorsIfUnlockApps.add(it)
|
||||
loggerI(msg = "Muted \"$it\" until unlocks")
|
||||
YLog.info("Muted \"$it\" until unlocks")
|
||||
}
|
||||
onMutedErrorsIfRestart {
|
||||
mutedErrorsIfRestartApps.add(it)
|
||||
loggerI(msg = "Muted \"$it\" until restarts")
|
||||
YLog.info("Muted \"$it\" until restarts")
|
||||
}
|
||||
onPushMutedErrorsAppsData {
|
||||
arrayListOf<MutedErrorsAppBean>().apply {
|
||||
@@ -224,24 +235,24 @@ object FrameworkHooker : YukiBaseHooker() {
|
||||
onUnmuteErrorsApp {
|
||||
when (it.type) {
|
||||
MutedErrorsAppBean.MuteType.UNTIL_UNLOCKS -> {
|
||||
loggerI(msg = "Unmuted if unlocks errors app \"${it.packageName}\"")
|
||||
YLog.info("Unmuted if unlocks errors app \"${it.packageName}\"")
|
||||
mutedErrorsIfUnlockApps.remove(it.packageName)
|
||||
}
|
||||
MutedErrorsAppBean.MuteType.UNTIL_REBOOTS -> {
|
||||
loggerI(msg = "Unmuted if restarts errors app \"${it.packageName}\"")
|
||||
YLog.info("Unmuted if restarts errors app \"${it.packageName}\"")
|
||||
mutedErrorsIfRestartApps.remove(it.packageName)
|
||||
}
|
||||
}
|
||||
}
|
||||
onUnmuteAllErrorsApps {
|
||||
loggerI(msg = "Unmute all errors apps --unlocks ${mutedErrorsIfUnlockApps.size} --restarts ${mutedErrorsIfRestartApps.size}")
|
||||
YLog.info("Unmute all errors apps --unlocks ${mutedErrorsIfUnlockApps.size} --restarts ${mutedErrorsIfRestartApps.size}")
|
||||
mutedErrorsIfUnlockApps.clear()
|
||||
mutedErrorsIfRestartApps.clear()
|
||||
}
|
||||
onPushAppListData { filters ->
|
||||
appContext?.let { context ->
|
||||
context.listOfPackages()
|
||||
.filter { it.packageName.let { e -> e != "android" && e != BuildConfig.APPLICATION_ID } }
|
||||
.filter { it.packageName.let { e -> e != "android" && e != BuildConfigWrapper.APPLICATION_ID } }
|
||||
.let { info ->
|
||||
arrayListOf<AppInfoBean>().apply {
|
||||
if (info.isNotEmpty())
|
||||
@@ -260,7 +271,7 @@ object FrameworkHooker : YukiBaseHooker() {
|
||||
}
|
||||
}.sortedByDescending { it.lastUpdateTime }
|
||||
.forEach { add(AppInfoBean(name = context.appNameOf(it.packageName), packageName = it.packageName)) }
|
||||
else loggerW(msg = "Fetched installed packages but got empty list")
|
||||
else YLog.warn("Fetched installed packages but got empty list")
|
||||
}
|
||||
}
|
||||
} ?: arrayListOf()
|
||||
@@ -277,18 +288,18 @@ object FrameworkHooker : YukiBaseHooker() {
|
||||
val appName = appInfo?.let { context.appNameOf(it.packageName).ifBlank { it.packageName } } ?: packageName
|
||||
|
||||
/** 当前 APP 名称 (包含用户 ID) */
|
||||
val appNameWithUserId = if (userId != 0) "$appName (${LocaleString.userId(userId)})" else appName
|
||||
val appNameWithUserId = if (userId != 0) "$appName (${locale.userId(userId)})" else appName
|
||||
|
||||
/** 崩溃标题 */
|
||||
val errorTitle = if (isRepeatingCrash) LocaleString.aerrRepeatedTitle(appNameWithUserId) else LocaleString.aerrTitle(appNameWithUserId)
|
||||
val errorTitle = if (isRepeatingCrash) locale.aerrRepeatedTitle(appNameWithUserId) else locale.aerrTitle(appNameWithUserId)
|
||||
|
||||
/** 使用通知推送异常信息 */
|
||||
fun showAppErrorsWithNotify() =
|
||||
context.pushNotify(
|
||||
channelId = "APPS_ERRORS",
|
||||
channelName = LocaleString.appName,
|
||||
channelName = locale.appName,
|
||||
title = errorTitle,
|
||||
content = LocaleString.appErrorsTip,
|
||||
content = locale.appErrorsTip,
|
||||
icon = IconCompat.createWithBitmap(moduleAppResources.drawableOf(R.drawable.ic_notify).toBitmap()),
|
||||
color = 0xFFFF6200.toInt(),
|
||||
intent = AppErrorsRecordActivity.intent()
|
||||
@@ -309,9 +320,9 @@ object FrameworkHooker : YukiBaseHooker() {
|
||||
title = errorTitle,
|
||||
isShowAppInfoButton = isActualApp,
|
||||
isShowReopenButton = isActualApp &&
|
||||
(isRepeatingCrash.not() || ConfigData.isEnableAlwaysShowsReopenAppOptions) &&
|
||||
context.isAppCanOpened(packageName) &&
|
||||
isMainProcess,
|
||||
(isRepeatingCrash.not() || ConfigData.isEnableAlwaysShowsReopenAppOptions) &&
|
||||
context.isAppCanOpened(packageName) &&
|
||||
isMainProcess,
|
||||
isShowCloseAppButton = isActualApp
|
||||
)
|
||||
)
|
||||
@@ -322,9 +333,9 @@ object FrameworkHooker : YukiBaseHooker() {
|
||||
/** 判断是否为主进程 */
|
||||
if (isMainProcess.not() && ConfigData.isEnableOnlyShowErrorsInMain) return
|
||||
when {
|
||||
packageName == BuildConfig.APPLICATION_ID -> {
|
||||
packageName == BuildConfigWrapper.APPLICATION_ID -> {
|
||||
context.toast(msg = "AppErrorsTracking has crashed, please see the log in console")
|
||||
loggerE(msg = "AppErrorsTracking has crashed itself, please see the Android Runtime Exception in console")
|
||||
YLog.error("AppErrorsTracking has crashed itself, please see the Android Runtime Exception in console")
|
||||
}
|
||||
ConfigData.isEnableAppConfigTemplate -> when {
|
||||
AppErrorsConfigData.isAppShowingType(AppErrorsConfigType.GLOBAL, packageName) -> when {
|
||||
@@ -341,11 +352,11 @@ object FrameworkHooker : YukiBaseHooker() {
|
||||
else -> showAppErrorsWithDialog()
|
||||
}
|
||||
/** 打印错误日志 */
|
||||
if (isActualApp) loggerE(
|
||||
if (isActualApp) YLog.error(
|
||||
msg = "Application \"$packageName\" ${if (isRepeatingCrash) "keeps stopping" else "has stopped"}" +
|
||||
(if (packageName != processName) " --process \"$processName\"" else "") +
|
||||
"${if (userId != 0) " --user $userId" else ""} --pid $pid"
|
||||
) else loggerE(msg = "Process \"$processName\" ${if (isRepeatingCrash) "keeps stopping" else "has stopped"} --pid $pid")
|
||||
(if (packageName != processName) " --process \"$processName\"" else "") +
|
||||
"${if (userId != 0) " --user $userId" else ""} --pid $pid"
|
||||
) else YLog.error("Process \"$processName\" ${if (isRepeatingCrash) "keeps stopping" else "has stopped"} --pid $pid")
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -355,101 +366,96 @@ object FrameworkHooker : YukiBaseHooker() {
|
||||
*/
|
||||
private fun AppErrorsProcessData.handleAppErrorsInfo(context: Context, info: ApplicationErrorReport.CrashInfo?) {
|
||||
AppErrorsRecordData.add(AppErrorsInfoBean.clone(context, pid, userId, appInfo?.packageName, info))
|
||||
loggerI(msg = "Received crash application data${if (userId != 0) " --user $userId" else ""} --pid $pid")
|
||||
YLog.info("Received crash application data${if (userId != 0) " --user $userId" else ""} --pid $pid")
|
||||
}
|
||||
|
||||
override fun onHook() {
|
||||
/** 注册生命周期 */
|
||||
registerLifecycle()
|
||||
/** 干掉原生错误对话框 - 如果有 */
|
||||
ErrorDialogControllerClass.hook {
|
||||
injectMember {
|
||||
method {
|
||||
name = "hasCrashDialogs"
|
||||
emptyParam()
|
||||
}
|
||||
replaceToTrue()
|
||||
}
|
||||
injectMember {
|
||||
method {
|
||||
name = "showCrashDialogs"
|
||||
paramCount = 1
|
||||
}
|
||||
intercept()
|
||||
}
|
||||
}.ignoredHookClassNotFoundFailure()
|
||||
ErrorDialogControllerClass?.apply {
|
||||
method {
|
||||
name = "hasCrashDialogs"
|
||||
emptyParam()
|
||||
}.hook().replaceToTrue()
|
||||
method {
|
||||
name = "showCrashDialogs"
|
||||
paramCount = 1
|
||||
}.hook().intercept()
|
||||
}
|
||||
/** 干掉原生错误对话框 - API 30 以下 */
|
||||
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) {
|
||||
ActivityTaskManagerService_LocalServiceClass.hook {
|
||||
injectMember {
|
||||
method {
|
||||
name = "canShowErrorDialogs"
|
||||
emptyParam()
|
||||
}
|
||||
replaceToFalse()
|
||||
}.ignoredNoSuchMemberFailure()
|
||||
}.ignoredHookClassNotFoundFailure()
|
||||
ActivityManagerServiceClass.hook {
|
||||
injectMember {
|
||||
method {
|
||||
name = "canShowErrorDialogs"
|
||||
emptyParam()
|
||||
}
|
||||
replaceToFalse()
|
||||
}.ignoredNoSuchMemberFailure()
|
||||
}.ignoredHookClassNotFoundFailure()
|
||||
ActivityTaskManagerService_LocalServiceClass?.method {
|
||||
name = "canShowErrorDialogs"
|
||||
emptyParam()
|
||||
}?.ignored()?.hook()?.replaceToFalse()
|
||||
ActivityManagerServiceClass?.method {
|
||||
name = "canShowErrorDialogs"
|
||||
emptyParam()
|
||||
}?.ignored()?.hook()?.replaceToFalse()
|
||||
}
|
||||
/** 干掉原生错误对话框 - 如果上述方法全部失效则直接结束对话框 */
|
||||
AppErrorDialogClass.hook {
|
||||
injectMember {
|
||||
method {
|
||||
name = "onCreate"
|
||||
param(BundleClass)
|
||||
}
|
||||
afterHook { instance<Dialog>().cancel() }
|
||||
}.ignoredNoSuchMemberFailure()
|
||||
injectMember {
|
||||
method {
|
||||
name = "onStart"
|
||||
emptyParam()
|
||||
}
|
||||
afterHook { instance<Dialog>().cancel() }
|
||||
}.ignoredNoSuchMemberFailure()
|
||||
AppErrorDialogClass.apply {
|
||||
method {
|
||||
name = "onCreate"
|
||||
param(BundleClass)
|
||||
}.ignored().hook().after { instance<Dialog>().cancel() }
|
||||
method {
|
||||
name = "onStart"
|
||||
emptyParam()
|
||||
}.ignored().hook().after { instance<Dialog>().cancel() }
|
||||
}
|
||||
/** 注入自定义错误对话框 */
|
||||
AppErrorsClass.hook {
|
||||
injectMember {
|
||||
method {
|
||||
name = "handleShowAppErrorUi"
|
||||
param(MessageClass)
|
||||
AppErrorsClass.apply {
|
||||
when {
|
||||
Build.VERSION.SDK_INT > Build.VERSION_CODES.R -> {
|
||||
method {
|
||||
name = "handleAppCrashLSPB"
|
||||
paramCount = 6
|
||||
}.hook().after {
|
||||
/** 如果为用户终止则不展示异常 */
|
||||
if (args(index = 1).string() == "user-terminated") return@after
|
||||
/** 当前实例 */
|
||||
val context = appContext ?: field { name = "mContext" }.get(instance).cast<Context>() ?: return@after
|
||||
|
||||
/** 当前进程信息 */
|
||||
val proc = args().first().any() ?: return@after YLog.warn("Received but got null ProcessRecord (Show UI failed)")
|
||||
|
||||
/** 当前错误数据 */
|
||||
val resultData = args().last().any()
|
||||
/** 创建 APP 进程异常数据类 */
|
||||
AppErrorsProcessData(instance, proc, resultData).handleShowAppErrorUi(context)
|
||||
}
|
||||
}
|
||||
afterHook {
|
||||
/** 当前实例 */
|
||||
val context = appContext ?: field { name = "mContext" }.get(instance).cast<Context>() ?: return@afterHook
|
||||
else -> {
|
||||
method {
|
||||
name = "handleShowAppErrorUi"
|
||||
param(MessageClass)
|
||||
}.hook().after {
|
||||
/** 当前实例 */
|
||||
val context = appContext ?: field { name = "mContext" }.get(instance).cast<Context>() ?: return@after
|
||||
|
||||
/** 当前错误数据 */
|
||||
val resultData = args().first().cast<Message>()?.obj
|
||||
/** 当前错误数据 */
|
||||
val resultData = args().first().cast<Message>()?.obj
|
||||
|
||||
/** 当前进程信息 */
|
||||
val proc = AppErrorDialog_DataClass.toClass().field { name = "proc" }.get(resultData).any()
|
||||
/** 创建 APP 进程异常数据类 */
|
||||
AppErrorsProcessData(instance, proc, resultData).handleShowAppErrorUi(context)
|
||||
/** 当前进程信息 */
|
||||
val proc = AppErrorDialog_DataClass.field { name = "proc" }.get(resultData).any()
|
||||
/** 创建 APP 进程异常数据类 */
|
||||
AppErrorsProcessData(instance, proc, resultData).handleShowAppErrorUi(context)
|
||||
}
|
||||
}
|
||||
}
|
||||
injectMember {
|
||||
method {
|
||||
name = "handleAppCrashInActivityController"
|
||||
returnType = BooleanType
|
||||
}
|
||||
afterHook {
|
||||
/** 当前实例 */
|
||||
val context = appContext ?: field { name = "mContext" }.get(instance).cast<Context>() ?: return@afterHook
|
||||
method {
|
||||
name = "handleAppCrashInActivityController"
|
||||
returnType = BooleanType
|
||||
}.hook().after {
|
||||
/** 当前实例 */
|
||||
val context = appContext ?: field { name = "mContext" }.get(instance).cast<Context>() ?: return@after
|
||||
|
||||
/** 当前进程信息 */
|
||||
val proc = args().first().any() ?: return@afterHook loggerW(msg = "Received but got null ProcessRecord")
|
||||
/** 创建 APP 进程异常数据类 */
|
||||
AppErrorsProcessData(instance, proc).handleAppErrorsInfo(context, args(index = 1).cast())
|
||||
}
|
||||
/** 当前进程信息 */
|
||||
val proc = args().first().any() ?: return@after YLog.warn("Received but got null ProcessRecord")
|
||||
/** 创建 APP 进程异常数据类 */
|
||||
AppErrorsProcessData(instance, proc).handleAppErrorsInfo(context, args(index = 1).cast())
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
|
||||
* Copyright (C) 2017-2023 Fankes Studio(qzmmcn@163.com)
|
||||
* https://github.com/KitsunePie/AppErrorsTracking
|
||||
*
|
||||
* This software is non-free but opensource software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Affero General Public License
|
||||
* as published by the Free Software Foundation; either
|
||||
* version 3 of the License, or any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is created by fankes on 2023/10/13.
|
||||
*/
|
||||
@file:Suppress("StaticFieldLeak")
|
||||
|
||||
package com.fankes.apperrorstracking.locale
|
||||
|
||||
import com.fankes.apperrorstracking.generated.locale.ModuleAppLocale
|
||||
|
||||
/** I18ns 实例 */
|
||||
lateinit var locale: ModuleAppLocale
|
@@ -17,7 +17,7 @@
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/5/11.
|
||||
* This file is created by fankes on 2022/5/11.
|
||||
*/
|
||||
package com.fankes.apperrorstracking.service
|
||||
|
@@ -17,10 +17,8 @@
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/5/7.
|
||||
* This file is created by fankes on 2022/5/7.
|
||||
*/
|
||||
@file:Suppress("UNCHECKED_CAST")
|
||||
|
||||
package com.fankes.apperrorstracking.ui.activity.base
|
||||
|
||||
import android.app.ActivityManager
|
@@ -17,7 +17,7 @@
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/10/4.
|
||||
* This file is created by fankes on 2022/10/4.
|
||||
*/
|
||||
@file:Suppress("DEPRECATION", "OVERRIDE_DEPRECATION")
|
||||
|
||||
@@ -31,20 +31,24 @@ import android.view.View
|
||||
import android.widget.AdapterView
|
||||
import androidx.core.view.isVisible
|
||||
import com.fankes.apperrorstracking.R
|
||||
import com.fankes.apperrorstracking.const.PackageName
|
||||
import com.fankes.apperrorstracking.databinding.ActivitiyLoggerBinding
|
||||
import com.fankes.apperrorstracking.databinding.AdapterLoggerBinding
|
||||
import com.fankes.apperrorstracking.databinding.DiaLoggerFilterBinding
|
||||
import com.fankes.apperrorstracking.locale.LocaleString
|
||||
import com.fankes.apperrorstracking.locale.locale
|
||||
import com.fankes.apperrorstracking.ui.activity.base.BaseActivity
|
||||
import com.fankes.apperrorstracking.utils.factory.*
|
||||
import com.fankes.apperrorstracking.utils.tool.FrameworkTool
|
||||
import com.fankes.apperrorstracking.utils.factory.bindAdapter
|
||||
import com.fankes.apperrorstracking.utils.factory.copyToClipboard
|
||||
import com.fankes.apperrorstracking.utils.factory.showDialog
|
||||
import com.fankes.apperrorstracking.utils.factory.toUtcTime
|
||||
import com.fankes.apperrorstracking.utils.factory.toast
|
||||
import com.highcapable.yukihookapi.hook.factory.dataChannel
|
||||
import com.highcapable.yukihookapi.hook.log.YukiHookLogger
|
||||
import com.highcapable.yukihookapi.hook.log.YukiLoggerData
|
||||
import com.highcapable.yukihookapi.hook.log.YLog
|
||||
import com.highcapable.yukihookapi.hook.log.data.YLogData
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.PrintStream
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import java.util.Date
|
||||
|
||||
class LoggerActivity : BaseActivity<ActivitiyLoggerBinding>() {
|
||||
|
||||
@@ -61,14 +65,14 @@ class LoggerActivity : BaseActivity<ActivitiyLoggerBinding>() {
|
||||
private var filters = arrayListOf("D", "I", "W", "E")
|
||||
|
||||
/** 全部的调试日志数据 */
|
||||
private val listData = ArrayList<YukiLoggerData>()
|
||||
private val listData = mutableListOf<YLogData>()
|
||||
|
||||
override fun onCreate() {
|
||||
binding.titleBackIcon.setOnClickListener { finish() }
|
||||
binding.refreshIcon.setOnClickListener { refreshData() }
|
||||
binding.filterIcon.setOnClickListener {
|
||||
showDialog<DiaLoggerFilterBinding> {
|
||||
title = LocaleString.filterByCondition
|
||||
title = locale.filterByCondition
|
||||
binding.configCheck0.isChecked = filters.any { it == "D" }
|
||||
binding.configCheck1.isChecked = filters.any { it == "I" }
|
||||
binding.configCheck2.isChecked = filters.any { it == "W" }
|
||||
@@ -124,7 +128,7 @@ class LoggerActivity : BaseActivity<ActivitiyLoggerBinding>() {
|
||||
|
||||
/** 更新列表数据 */
|
||||
private fun refreshData() {
|
||||
dataChannel(FrameworkTool.SYSTEM_FRAMEWORK_NAME).obtainLoggerInMemoryData {
|
||||
dataChannel(PackageName.SYSTEM_FRAMEWORK).obtainLoggerInMemoryData {
|
||||
listData.clear()
|
||||
it.takeIf { e -> e.isNotEmpty() }?.reversed()?.filter { filters.any { e -> it.priority == e } }?.forEach { e -> listData.add(e) }
|
||||
onChanged?.invoke()
|
||||
@@ -132,7 +136,7 @@ class LoggerActivity : BaseActivity<ActivitiyLoggerBinding>() {
|
||||
binding.exportAllIcon.isVisible = listData.isNotEmpty()
|
||||
binding.listView.isVisible = listData.isNotEmpty()
|
||||
binding.listNoDataView.isVisible = listData.isEmpty()
|
||||
binding.listNoDataView.text = if (filters.size < 4) LocaleString.noListResult else LocaleString.noListData
|
||||
binding.listNoDataView.text = if (filters.size < 4) locale.noListResult else locale.noListData
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,10 +176,10 @@ class LoggerActivity : BaseActivity<ActivitiyLoggerBinding>() {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
if (requestCode == WRITE_REQUEST_CODE && resultCode == Activity.RESULT_OK) runCatching {
|
||||
data?.data?.let {
|
||||
contentResolver?.openOutputStream(it)?.apply { write(YukiHookLogger.contents(listData).toByteArray()) }?.close()
|
||||
toast(LocaleString.exportAllLogsSuccess)
|
||||
} ?: toast(LocaleString.exportAllLogsFail)
|
||||
}.onFailure { toast(LocaleString.exportAllLogsFail) }
|
||||
contentResolver?.openOutputStream(it)?.apply { write(YLog.contents(listData).toByteArray()) }?.close()
|
||||
toast(locale.exportAllLogsSuccess)
|
||||
} ?: toast(locale.exportAllLogsFail)
|
||||
}.onFailure { toast(locale.exportAllLogsFail) }
|
||||
}
|
||||
|
||||
override fun onResume() {
|
@@ -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/7.
|
||||
* This file is created by fankes on 2022/5/7.
|
||||
*/
|
||||
@file:Suppress("DEPRECATION", "OVERRIDE_DEPRECATION")
|
||||
|
||||
@@ -34,9 +34,18 @@ import com.fankes.apperrorstracking.bean.AppErrorsInfoBean
|
||||
import com.fankes.apperrorstracking.data.ConfigData
|
||||
import com.fankes.apperrorstracking.data.factory.bind
|
||||
import com.fankes.apperrorstracking.databinding.ActivityAppErrorsDetailBinding
|
||||
import com.fankes.apperrorstracking.locale.LocaleString
|
||||
import com.fankes.apperrorstracking.locale.locale
|
||||
import com.fankes.apperrorstracking.ui.activity.base.BaseActivity
|
||||
import com.fankes.apperrorstracking.utils.factory.*
|
||||
import com.fankes.apperrorstracking.utils.factory.appIconOf
|
||||
import com.fankes.apperrorstracking.utils.factory.appNameOf
|
||||
import com.fankes.apperrorstracking.utils.factory.copyToClipboard
|
||||
import com.fankes.apperrorstracking.utils.factory.dp
|
||||
import com.fankes.apperrorstracking.utils.factory.getSerializableExtraCompat
|
||||
import com.fankes.apperrorstracking.utils.factory.navigate
|
||||
import com.fankes.apperrorstracking.utils.factory.openSelfSetting
|
||||
import com.fankes.apperrorstracking.utils.factory.showDialog
|
||||
import com.fankes.apperrorstracking.utils.factory.toast
|
||||
import com.fankes.apperrorstracking.utils.tool.StackTraceShareHelper
|
||||
import com.highcapable.yukihookapi.hook.log.loggerE
|
||||
|
||||
class AppErrorsDetailActivity : BaseActivity<ActivityAppErrorsDetailBinding>() {
|
||||
@@ -62,55 +71,91 @@ class AppErrorsDetailActivity : BaseActivity<ActivityAppErrorsDetailBinding>() {
|
||||
private var stackTrace = ""
|
||||
|
||||
override fun onCreate() {
|
||||
if (initUi(intent).not()) return
|
||||
binding.titleBackIcon.setOnClickListener { onBackPressed() }
|
||||
binding.disableAutoWrapErrorStackTraceSwitch.bind(ConfigData.DISABLE_AUTO_WRAP_ERROR_STACK_TRACE) {
|
||||
onInitialize {
|
||||
binding.errorStackTraceScrollView.isVisible = it
|
||||
binding.errorStackTraceFixedText.isGone = it
|
||||
}
|
||||
onChanged {
|
||||
reinitialize()
|
||||
resetScrollView()
|
||||
}
|
||||
}
|
||||
binding.detailTitleText.setOnClickListener { binding.appPanelScrollView.smoothScrollTo(0, 0) }
|
||||
resetScrollView()
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 [Intent] 中解析 [AppErrorsInfoBean] 并加载至界面
|
||||
*
|
||||
* @param intent 用于获取并解析 [AppErrorsInfoBean] 的 [Intent] 实例
|
||||
* @return [Boolean] 是否解析成功:true 为成功;false 为失败,可能是 [Intent] 为空或者 [AppErrorsInfoBean] 为空
|
||||
*/
|
||||
private fun initUi(intent: Intent?): Boolean {
|
||||
val appErrorsInfo = runCatching { intent?.getSerializableExtraCompat<AppErrorsInfoBean>(EXTRA_APP_ERRORS_INFO) }.getOrNull()
|
||||
?: return toastAndFinish(name = "AppErrorsInfo")
|
||||
if (appErrorsInfo == null) {
|
||||
toastAndFinish(name = "AppErrorsInfo")
|
||||
return false
|
||||
}
|
||||
if (appErrorsInfo.isEmpty) {
|
||||
binding.appPanelScrollView.isVisible = false
|
||||
showDialog {
|
||||
title = LocaleString.notice
|
||||
msg = LocaleString.unableGetAppErrorsRecordTip
|
||||
confirmButton(LocaleString.gotIt) {
|
||||
title = locale.notice
|
||||
msg = locale.unableGetAppErrorsRecordTip
|
||||
confirmButton(locale.gotIt) {
|
||||
cancel()
|
||||
finish()
|
||||
}
|
||||
cancelButton(LocaleString.goItNow) {
|
||||
cancelButton(locale.goItNow) {
|
||||
cancel()
|
||||
finish()
|
||||
navigate<AppErrorsRecordActivity>()
|
||||
}
|
||||
noCancelable()
|
||||
}
|
||||
return
|
||||
return false
|
||||
}
|
||||
binding.appInfoItem.setOnClickListener { openSelfSetting(appErrorsInfo.packageName) }
|
||||
binding.titleBackIcon.setOnClickListener { onBackPressed() }
|
||||
binding.printIcon.setOnClickListener {
|
||||
loggerE(msg = appErrorsInfo.stackTrace)
|
||||
toast(LocaleString.printToLogcatSuccess)
|
||||
toast(locale.printToLogcatSuccess)
|
||||
}
|
||||
binding.copyIcon.setOnClickListener {
|
||||
StackTraceShareHelper.showChoose(context = this, locale.copyErrorStack) { sDeviceBrand, sDeviceModel, sDisplay, sPackageName ->
|
||||
copyToClipboard(appErrorsInfo.stackOutputShareContent(sDeviceBrand, sDeviceModel, sDisplay, sPackageName))
|
||||
}
|
||||
}
|
||||
binding.copyIcon.setOnClickListener { copyToClipboard(appErrorsInfo.stackOutputShareContent) }
|
||||
binding.exportIcon.setOnClickListener {
|
||||
stackTrace = appErrorsInfo.stackOutputFileContent
|
||||
runCatching {
|
||||
startActivityForResult(Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
|
||||
addCategory(Intent.CATEGORY_OPENABLE)
|
||||
type = "*/application"
|
||||
putExtra(Intent.EXTRA_TITLE, "${appErrorsInfo.packageName}_${appErrorsInfo.utcTime}.log")
|
||||
}, WRITE_REQUEST_CODE)
|
||||
}.onFailure { toast(msg = "Start Android SAF failed") }
|
||||
StackTraceShareHelper.showChoose(context = this, locale.exportToFile) { sDeviceBrand, sDeviceModel, sDisplay, sPackageName ->
|
||||
stackTrace = appErrorsInfo.stackOutputFileContent(sDeviceBrand, sDeviceModel, sDisplay, sPackageName)
|
||||
runCatching {
|
||||
startActivityForResult(Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
|
||||
addCategory(Intent.CATEGORY_OPENABLE)
|
||||
type = "*/application"
|
||||
val packageName = if (sPackageName) appErrorsInfo.packageName else "anonymous"
|
||||
putExtra(Intent.EXTRA_TITLE, "${packageName}_${appErrorsInfo.utcTime}.log")
|
||||
}, WRITE_REQUEST_CODE)
|
||||
}.onFailure { toast(msg = "Start Android SAF failed") }
|
||||
}
|
||||
}
|
||||
binding.shareIcon.setOnClickListener {
|
||||
startActivity(Intent.createChooser(Intent(Intent.ACTION_SEND).apply {
|
||||
type = "text/plain"
|
||||
putExtra(Intent.EXTRA_TEXT, appErrorsInfo.stackOutputShareContent)
|
||||
}, LocaleString.shareErrorStack))
|
||||
StackTraceShareHelper.showChoose(context = this, locale.shareErrorStack) { sDeviceBrand, sDeviceModel, sDisplay, sPackageName ->
|
||||
startActivity(Intent.createChooser(Intent(Intent.ACTION_SEND).apply {
|
||||
type = "text/plain"
|
||||
putExtra(Intent.EXTRA_TEXT, appErrorsInfo.stackOutputShareContent(sDeviceBrand, sDeviceModel, sDisplay, sPackageName))
|
||||
}, locale.shareErrorStack))
|
||||
}
|
||||
}
|
||||
binding.appIcon.setImageDrawable(appIconOf(appErrorsInfo.packageName))
|
||||
binding.appNameText.text = appNameOf(appErrorsInfo.packageName).ifBlank { appErrorsInfo.packageName }
|
||||
binding.appVersionText.text = appErrorsInfo.versionBrand
|
||||
binding.appUserIdText.isVisible = appErrorsInfo.userId > 0
|
||||
binding.appUserIdText.text = LocaleString.userId(appErrorsInfo.userId)
|
||||
binding.appCpuAbiText.text = appErrorsInfo.cpuAbi.ifBlank { LocaleString.noCpuAbi }
|
||||
binding.appUserIdText.text = locale.userId(appErrorsInfo.userId)
|
||||
binding.appCpuAbiText.text = appErrorsInfo.cpuAbi.ifBlank { locale.noCpuAbi }
|
||||
binding.appTargetSdkText.text = locale.appTargetSdk(appErrorsInfo.targetSdk)
|
||||
binding.appMinSdkText.text = locale.appMinSdk(appErrorsInfo.minSdk)
|
||||
binding.jvmErrorPanel.isGone = appErrorsInfo.isNativeCrash
|
||||
binding.errorTypeIcon.setImageResource(if (appErrorsInfo.isNativeCrash) R.drawable.ic_cpp else R.drawable.ic_java)
|
||||
binding.errorInfoText.text = appErrorsInfo.exceptionMessage
|
||||
@@ -122,23 +167,12 @@ class AppErrorsDetailActivity : BaseActivity<ActivityAppErrorsDetailBinding>() {
|
||||
binding.errorRecordTimeText.text = appErrorsInfo.dateTime
|
||||
binding.errorStackTraceMovableText.text = appErrorsInfo.stackTrace
|
||||
binding.errorStackTraceFixedText.text = appErrorsInfo.stackTrace
|
||||
binding.disableAutoWrapErrorStackTraceSwitch.bind(ConfigData.DISABLE_AUTO_WRAP_ERROR_STACK_TRACE) {
|
||||
onInitialize {
|
||||
binding.errorStackTraceScrollView.isVisible = it
|
||||
binding.errorStackTraceFixedText.isGone = it
|
||||
}
|
||||
onChanged {
|
||||
reinitialize()
|
||||
resetScrollView()
|
||||
}
|
||||
}
|
||||
binding.appPanelScrollView.setOnScrollChangeListener { _, _, y, _, _ ->
|
||||
binding.detailTitleText.text = if (y >= 30.dp(context = this))
|
||||
binding.detailTitleText.text = if (y >= 30.dp(context = this@AppErrorsDetailActivity))
|
||||
appNameOf(appErrorsInfo.packageName).ifBlank { appErrorsInfo.packageName }
|
||||
else LocaleString.appName
|
||||
else locale.appName
|
||||
}
|
||||
binding.detailTitleText.setOnClickListener { binding.appPanelScrollView.smoothScrollTo(0, 0) }
|
||||
resetScrollView()
|
||||
return true
|
||||
}
|
||||
|
||||
/** 修复在一些小屏设备上设置了 [TextView.setTextIsSelectable] 后布局自动上滑问题 */
|
||||
@@ -154,13 +188,18 @@ class AppErrorsDetailActivity : BaseActivity<ActivityAppErrorsDetailBinding>() {
|
||||
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) }
|
||||
toast(locale.outputStackSuccess)
|
||||
} ?: toast(locale.outputStackFail)
|
||||
}.onFailure { toast(locale.outputStackFail) }
|
||||
}
|
||||
|
||||
override fun onBackPressed() {
|
||||
intent?.removeExtra(EXTRA_APP_ERRORS_INFO)
|
||||
finish()
|
||||
}
|
||||
|
||||
override fun onNewIntent(intent: Intent?) {
|
||||
super.onNewIntent(intent)
|
||||
if (initUi(intent)) binding.appPanelScrollView.scrollTo(0, 0)
|
||||
}
|
||||
}
|
@@ -17,7 +17,7 @@
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/6/1.
|
||||
* This file is created by fankes on 2022/6/1.
|
||||
*/
|
||||
package com.fankes.apperrorstracking.ui.activity.errors
|
||||
|
||||
@@ -29,9 +29,14 @@ import com.fankes.apperrorstracking.bean.AppErrorsDisplayBean
|
||||
import com.fankes.apperrorstracking.data.ConfigData
|
||||
import com.fankes.apperrorstracking.databinding.ActivityAppErrorsDisplayBinding
|
||||
import com.fankes.apperrorstracking.databinding.DiaAppErrorsDisplayBinding
|
||||
import com.fankes.apperrorstracking.locale.LocaleString
|
||||
import com.fankes.apperrorstracking.locale.locale
|
||||
import com.fankes.apperrorstracking.ui.activity.base.BaseActivity
|
||||
import com.fankes.apperrorstracking.utils.factory.*
|
||||
import com.fankes.apperrorstracking.utils.factory.colorOf
|
||||
import com.fankes.apperrorstracking.utils.factory.getSerializableExtraCompat
|
||||
import com.fankes.apperrorstracking.utils.factory.navigate
|
||||
import com.fankes.apperrorstracking.utils.factory.openSelfSetting
|
||||
import com.fankes.apperrorstracking.utils.factory.showDialog
|
||||
import com.fankes.apperrorstracking.utils.factory.toast
|
||||
import com.fankes.apperrorstracking.utils.tool.FrameworkTool
|
||||
|
||||
class AppErrorsDisplayActivity : BaseActivity<ActivityAppErrorsDisplayBinding>() {
|
||||
@@ -73,7 +78,7 @@ class AppErrorsDisplayActivity : BaseActivity<ActivityAppErrorsDisplayBinding>()
|
||||
binding.appInfoItem.isVisible = appErrorsDisplay.isShowAppInfoButton
|
||||
binding.closeAppItem.isVisible = appErrorsDisplay.isShowReopenButton.not() && appErrorsDisplay.isShowCloseAppButton
|
||||
binding.reopenAppItem.isVisible = appErrorsDisplay.isShowReopenButton
|
||||
binding.processNameText.text = LocaleString.crashProcess(appErrorsDisplay.processName)
|
||||
binding.processNameText.text = locale.crashProcess(appErrorsDisplay.processName)
|
||||
binding.appInfoItem.setOnClickListener {
|
||||
cancel()
|
||||
openSelfSetting(appErrorsDisplay.packageName)
|
||||
@@ -91,13 +96,13 @@ class AppErrorsDisplayActivity : BaseActivity<ActivityAppErrorsDisplayBinding>()
|
||||
}
|
||||
binding.mutedIfUnlockItem.setOnClickListener {
|
||||
FrameworkTool.mutedErrorsIfUnlock(context, appErrorsDisplay.packageName) {
|
||||
toast(LocaleString.muteIfUnlockTip(appErrorsDisplay.appName))
|
||||
toast(locale.muteIfUnlockTip(appErrorsDisplay.appName))
|
||||
cancel()
|
||||
}
|
||||
}
|
||||
binding.mutedIfRestartItem.setOnClickListener {
|
||||
FrameworkTool.mutedErrorsIfRestart(context, appErrorsDisplay.packageName) {
|
||||
toast(LocaleString.muteIfRestartTip(appErrorsDisplay.appName))
|
||||
toast(locale.muteIfRestartTip(appErrorsDisplay.appName))
|
||||
cancel()
|
||||
}
|
||||
}
|
@@ -17,17 +17,15 @@
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/6/3.
|
||||
* This file is created by fankes on 2022/6/3.
|
||||
*/
|
||||
@file:Suppress("DEPRECATION", "OVERRIDE_DEPRECATION")
|
||||
|
||||
package com.fankes.apperrorstracking.ui.activity.errors
|
||||
|
||||
import androidx.core.view.isVisible
|
||||
import com.fankes.apperrorstracking.bean.MutedErrorsAppBean
|
||||
import com.fankes.apperrorstracking.databinding.ActivityAppErrorsMutedBinding
|
||||
import com.fankes.apperrorstracking.databinding.AdapterAppErrorsMutedBinding
|
||||
import com.fankes.apperrorstracking.locale.LocaleString
|
||||
import com.fankes.apperrorstracking.locale.locale
|
||||
import com.fankes.apperrorstracking.ui.activity.base.BaseActivity
|
||||
import com.fankes.apperrorstracking.utils.factory.appIconOf
|
||||
import com.fankes.apperrorstracking.utils.factory.appNameOf
|
||||
@@ -47,8 +45,8 @@ class AppErrorsMutedActivity : BaseActivity<ActivityAppErrorsMutedBinding>() {
|
||||
binding.titleBackIcon.setOnClickListener { onBackPressed() }
|
||||
binding.unmuteAllIcon.setOnClickListener {
|
||||
showDialog {
|
||||
title = LocaleString.notice
|
||||
msg = LocaleString.areYouSureUnmuteAll
|
||||
title = locale.notice
|
||||
msg = locale.areYouSureUnmuteAll
|
||||
confirmButton { FrameworkTool.unmuteAllErrorsApps(context) { refreshData() } }
|
||||
cancelButton()
|
||||
}
|
||||
@@ -61,8 +59,8 @@ class AppErrorsMutedActivity : BaseActivity<ActivityAppErrorsMutedBinding>() {
|
||||
binding.appIcon.setImageDrawable(appIconOf(bean.packageName))
|
||||
binding.appNameText.text = appNameOf(bean.packageName).ifBlank { bean.packageName }
|
||||
binding.muteTypeText.text = when (bean.type) {
|
||||
MutedErrorsAppBean.MuteType.UNTIL_UNLOCKS -> LocaleString.muteIfUnlock
|
||||
MutedErrorsAppBean.MuteType.UNTIL_REBOOTS -> LocaleString.muteIfRestart
|
||||
MutedErrorsAppBean.MuteType.UNTIL_UNLOCKS -> locale.muteIfUnlock
|
||||
MutedErrorsAppBean.MuteType.UNTIL_REBOOTS -> locale.muteIfRestart
|
||||
}
|
||||
binding.unmuteButton.setOnClickListener { FrameworkTool.unmuteErrorsApp(context, bean) { refreshData() } }
|
||||
}
|
@@ -17,7 +17,7 @@
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/5/11.
|
||||
* This file is created by fankes on 2022/5/11.
|
||||
*/
|
||||
@file:Suppress("DEPRECATION", "OVERRIDE_DEPRECATION", "SetTextI18n")
|
||||
|
||||
@@ -31,7 +31,6 @@ import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.widget.AdapterView.AdapterContextMenuInfo
|
||||
import androidx.core.view.isVisible
|
||||
import com.fankes.apperrorstracking.BuildConfig
|
||||
import com.fankes.apperrorstracking.R
|
||||
import com.fankes.apperrorstracking.bean.AppErrorsInfoBean
|
||||
import com.fankes.apperrorstracking.bean.AppFiltersBean
|
||||
@@ -39,11 +38,21 @@ import com.fankes.apperrorstracking.bean.enum.AppFiltersType
|
||||
import com.fankes.apperrorstracking.databinding.ActivityAppErrorsRecordBinding
|
||||
import com.fankes.apperrorstracking.databinding.AdapterAppErrorsRecordBinding
|
||||
import com.fankes.apperrorstracking.databinding.DiaAppErrorsStatisticsBinding
|
||||
import com.fankes.apperrorstracking.locale.LocaleString
|
||||
import com.fankes.apperrorstracking.locale.locale
|
||||
import com.fankes.apperrorstracking.ui.activity.base.BaseActivity
|
||||
import com.fankes.apperrorstracking.utils.factory.*
|
||||
import com.fankes.apperrorstracking.utils.factory.appIconOf
|
||||
import com.fankes.apperrorstracking.utils.factory.appNameOf
|
||||
import com.fankes.apperrorstracking.utils.factory.bindAdapter
|
||||
import com.fankes.apperrorstracking.utils.factory.decimal
|
||||
import com.fankes.apperrorstracking.utils.factory.newThread
|
||||
import com.fankes.apperrorstracking.utils.factory.openSelfSetting
|
||||
import com.fankes.apperrorstracking.utils.factory.showDialog
|
||||
import com.fankes.apperrorstracking.utils.factory.toUtcTime
|
||||
import com.fankes.apperrorstracking.utils.factory.toast
|
||||
import com.fankes.apperrorstracking.utils.tool.FrameworkTool
|
||||
import com.fankes.apperrorstracking.utils.tool.StackTraceShareHelper
|
||||
import com.fankes.apperrorstracking.utils.tool.ZipFileTool
|
||||
import com.fankes.apperrorstracking.wrapper.BuildConfigWrapper
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
|
||||
@@ -58,7 +67,7 @@ class AppErrorsRecordActivity : BaseActivity<ActivityAppErrorsRecordBinding>() {
|
||||
* 获取 [Intent]
|
||||
* @return [Intent]
|
||||
*/
|
||||
fun intent() = Intent().apply { component = ComponentName(BuildConfig.APPLICATION_ID, AppErrorsRecordActivity::class.java.name) }
|
||||
fun intent() = Intent().apply { component = ComponentName(BuildConfigWrapper.APPLICATION_ID, AppErrorsRecordActivity::class.java.name) }
|
||||
}
|
||||
|
||||
/** 当前导出文件的路径 */
|
||||
@@ -74,8 +83,8 @@ class AppErrorsRecordActivity : BaseActivity<ActivityAppErrorsRecordBinding>() {
|
||||
binding.titleBackIcon.setOnClickListener { onBackPressed() }
|
||||
binding.appErrorSisIcon.setOnClickListener {
|
||||
showDialog {
|
||||
title = LocaleString.notice
|
||||
progressContent = LocaleString.generatingStatistics
|
||||
title = locale.notice
|
||||
progressContent = locale.generatingStatistics
|
||||
noCancelable()
|
||||
FrameworkTool.fetchAppListData(context, AppFiltersBean(type = AppFiltersType.ALL)) {
|
||||
newThread {
|
||||
@@ -92,14 +101,14 @@ class AppErrorsRecordActivity : BaseActivity<ActivityAppErrorsRecordBinding>() {
|
||||
runOnUiThread {
|
||||
cancel()
|
||||
showDialog<DiaAppErrorsStatisticsBinding> {
|
||||
title = LocaleString.appErrorsStatistics
|
||||
binding.totalErrorsUnitText.text = LocaleString.totalErrorsUnit(listData.size)
|
||||
binding.totalAppsUnitText.text = LocaleString.totalAppsUnit(it.size)
|
||||
title = locale.appErrorsStatistics
|
||||
binding.totalErrorsUnitText.text = locale.totalErrorsUnit(listData.size)
|
||||
binding.totalAppsUnitText.text = locale.totalAppsUnit(it.size)
|
||||
binding.mostErrorsAppIcon.setImageDrawable(appIconOf(mostAppPackageName))
|
||||
binding.mostErrorsAppText.text = appNameOf(mostAppPackageName).ifBlank { mostAppPackageName }
|
||||
binding.mostErrorsTypeText.text = mostErrorsType
|
||||
binding.totalPptOfErrorsText.text = "$pptCount%"
|
||||
confirmButton(LocaleString.gotIt)
|
||||
confirmButton(locale.gotIt)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -108,12 +117,12 @@ class AppErrorsRecordActivity : BaseActivity<ActivityAppErrorsRecordBinding>() {
|
||||
}
|
||||
binding.clearAllIcon.setOnClickListener {
|
||||
showDialog {
|
||||
title = LocaleString.notice
|
||||
msg = LocaleString.areYouSureClearErrors
|
||||
title = locale.notice
|
||||
msg = locale.areYouSureClearErrors
|
||||
confirmButton {
|
||||
FrameworkTool.clearAppErrorsInfoData(context) {
|
||||
refreshData()
|
||||
toast(LocaleString.allErrorsClearSuccess)
|
||||
toast(locale.allErrorsClearSuccess)
|
||||
}
|
||||
}
|
||||
cancelButton()
|
||||
@@ -121,8 +130,8 @@ class AppErrorsRecordActivity : BaseActivity<ActivityAppErrorsRecordBinding>() {
|
||||
}
|
||||
binding.exportAllIcon.setOnClickListener {
|
||||
showDialog {
|
||||
title = LocaleString.notice
|
||||
msg = LocaleString.areYouSureExportAllErrors
|
||||
title = locale.notice
|
||||
msg = locale.areYouSureExportAllErrors
|
||||
confirmButton { exportAll() }
|
||||
cancelButton()
|
||||
}
|
||||
@@ -136,7 +145,7 @@ class AppErrorsRecordActivity : BaseActivity<ActivityAppErrorsRecordBinding>() {
|
||||
binding.appIcon.setImageDrawable(appIconOf(bean.packageName))
|
||||
binding.appNameText.text = appNameOf(bean.packageName).ifBlank { bean.packageName }
|
||||
binding.appUserIdText.isVisible = bean.userId > 0
|
||||
binding.appUserIdText.text = LocaleString.userId(bean.userId)
|
||||
binding.appUserIdText.text = locale.userId(bean.userId)
|
||||
binding.errorsTimeText.text = bean.crossTime
|
||||
binding.errorTypeIcon.setImageResource(if (bean.isNativeCrash) R.drawable.ic_cpp else R.drawable.ic_java)
|
||||
binding.errorTypeText.text = if (bean.isNativeCrash) "Native crash" else bean.exceptionClassName.simpleThwName()
|
||||
@@ -152,7 +161,7 @@ class AppErrorsRecordActivity : BaseActivity<ActivityAppErrorsRecordBinding>() {
|
||||
/** 更新列表数据 */
|
||||
private fun refreshData() {
|
||||
FrameworkTool.fetchAppErrorsInfoData(context = this) {
|
||||
binding.titleCountText.text = LocaleString.recordCount(it.size)
|
||||
binding.titleCountText.text = locale.recordCount(it.size)
|
||||
binding.listProgressView.isVisible = false
|
||||
binding.appErrorSisIcon.isVisible = it.size >= 5
|
||||
binding.clearAllIcon.isVisible = it.isNotEmpty()
|
||||
@@ -168,20 +177,24 @@ class AppErrorsRecordActivity : BaseActivity<ActivityAppErrorsRecordBinding>() {
|
||||
/** 打包导出全部 */
|
||||
private fun exportAll() {
|
||||
clearAllExportTemp()
|
||||
("${cacheDir.absolutePath}/temp").also { path ->
|
||||
File(path).mkdirs()
|
||||
listData.takeIf { it.isNotEmpty() }?.forEach {
|
||||
File("$path/${it.packageName}_${it.utcTime}.log").writeText(it.stackOutputFileContent)
|
||||
StackTraceShareHelper.showChoose(context = this, locale.exportAll) { sDeviceBrand, sDeviceModel, sDisplay, sPackageName ->
|
||||
("${cacheDir.absolutePath}/temp").also { path ->
|
||||
File(path).mkdirs()
|
||||
listData.takeIf { it.isNotEmpty() }?.forEachIndexed { index, bean ->
|
||||
val packageName = if (sPackageName) bean.packageName else "anonymous_$index"
|
||||
File("$path/${packageName}_${bean.utcTime}.log")
|
||||
.writeText(bean.stackOutputFileContent(sDeviceBrand, sDeviceModel, sDisplay, sPackageName))
|
||||
}
|
||||
outPutFilePath = "${cacheDir.absolutePath}/temp_${System.currentTimeMillis()}.zip"
|
||||
ZipFileTool.zipMultiFile(path, outPutFilePath)
|
||||
runCatching {
|
||||
startActivityForResult(Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
|
||||
addCategory(Intent.CATEGORY_OPENABLE)
|
||||
type = "*/application"
|
||||
putExtra(Intent.EXTRA_TITLE, "app_errors_info_${System.currentTimeMillis().toUtcTime()}.zip")
|
||||
}, WRITE_REQUEST_CODE)
|
||||
}.onFailure { toast(msg = "Start Android SAF failed") }
|
||||
}
|
||||
outPutFilePath = "${cacheDir.absolutePath}/temp_${System.currentTimeMillis()}.zip"
|
||||
ZipFileTool.zipMultiFile(path, outPutFilePath)
|
||||
runCatching {
|
||||
startActivityForResult(Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
|
||||
addCategory(Intent.CATEGORY_OPENABLE)
|
||||
type = "*/application"
|
||||
putExtra(Intent.EXTRA_TITLE, "app_errors_info_${System.currentTimeMillis().toUtcTime()}.zip")
|
||||
}, WRITE_REQUEST_CODE)
|
||||
}.onFailure { toast(msg = "Start Android SAF failed") }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,8 +224,8 @@ class AppErrorsRecordActivity : BaseActivity<ActivityAppErrorsRecordBinding>() {
|
||||
R.id.aerrors_app_info -> openSelfSetting(listData[it.position].packageName)
|
||||
R.id.aerrors_remove_record ->
|
||||
showDialog {
|
||||
title = LocaleString.notice
|
||||
msg = LocaleString.areYouSureRemoveRecord
|
||||
title = locale.notice
|
||||
msg = locale.areYouSureRemoveRecord
|
||||
confirmButton { FrameworkTool.removeAppErrorsInfoData(context, listData[it.position]) { refreshData() } }
|
||||
cancelButton()
|
||||
}
|
||||
@@ -227,9 +240,9 @@ class AppErrorsRecordActivity : BaseActivity<ActivityAppErrorsRecordBinding>() {
|
||||
data?.data?.let {
|
||||
contentResolver?.openOutputStream(it)?.apply { write(FileInputStream(outPutFilePath).readBytes()) }?.close()
|
||||
clearAllExportTemp()
|
||||
toast(LocaleString.exportAllErrorsSuccess)
|
||||
} ?: toast(LocaleString.exportAllErrorsFail)
|
||||
}.onFailure { toast(LocaleString.exportAllErrorsFail) }
|
||||
toast(locale.exportAllErrorsSuccess)
|
||||
} ?: toast(locale.exportAllErrorsFail)
|
||||
}.onFailure { toast(locale.exportAllErrorsFail) }
|
||||
}
|
||||
|
||||
override fun onResume() {
|
@@ -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/4.
|
||||
* This file is created by fankes on 2022/6/4.
|
||||
*/
|
||||
package com.fankes.apperrorstracking.ui.activity.main
|
||||
|
||||
@@ -31,7 +31,7 @@ import com.fankes.apperrorstracking.databinding.ActivityConfigBinding
|
||||
import com.fankes.apperrorstracking.databinding.AdapterAppInfoBinding
|
||||
import com.fankes.apperrorstracking.databinding.DiaAppConfigBinding
|
||||
import com.fankes.apperrorstracking.databinding.DiaAppsFilterBinding
|
||||
import com.fankes.apperrorstracking.locale.LocaleString
|
||||
import com.fankes.apperrorstracking.locale.locale
|
||||
import com.fankes.apperrorstracking.ui.activity.base.BaseActivity
|
||||
import com.fankes.apperrorstracking.utils.factory.appIconOf
|
||||
import com.fankes.apperrorstracking.utils.factory.bindAdapter
|
||||
@@ -53,16 +53,16 @@ class ConfigureActivity : BaseActivity<ActivityConfigBinding>() {
|
||||
override fun onCreate() {
|
||||
binding.titleBackIcon.setOnClickListener { finish() }
|
||||
binding.globalIcon.setOnClickListener {
|
||||
showAppConfigDialog(LocaleString.globalConfig, isShowGlobalConfig = false) { type ->
|
||||
showAppConfigDialog(locale.globalConfig, isShowGlobalConfig = false) { type ->
|
||||
AppErrorsConfigData.putAppShowingType(type)
|
||||
onChanged?.invoke()
|
||||
}
|
||||
}
|
||||
binding.batchIcon.setOnClickListener {
|
||||
showAppConfigDialog(LocaleString.batchOperationsNumber(listData.size), isNotSetDefaultValue = true) { type ->
|
||||
showAppConfigDialog(locale.batchOperationsNumber(listData.size), isNotSetDefaultValue = true) { type ->
|
||||
showDialog {
|
||||
title = LocaleString.notice
|
||||
msg = LocaleString.areYouSureApplySiteApps(listData.size)
|
||||
title = locale.notice
|
||||
msg = locale.areYouSureApplySiteApps(listData.size)
|
||||
confirmButton {
|
||||
listData.takeIf { it.isNotEmpty() }?.forEach { AppErrorsConfigData.putAppShowingType(type, it.packageName) }
|
||||
onChanged?.invoke()
|
||||
@@ -73,7 +73,7 @@ class ConfigureActivity : BaseActivity<ActivityConfigBinding>() {
|
||||
}
|
||||
binding.filterIcon.setOnClickListener {
|
||||
showDialog<DiaAppsFilterBinding> {
|
||||
title = LocaleString.filterByCondition
|
||||
title = locale.filterByCondition
|
||||
binding.filtersRadioUser.isChecked = appFilters.type == AppFiltersType.USER
|
||||
binding.filtersRadioSystem.isChecked = appFilters.type == AppFiltersType.SYSTEM
|
||||
binding.filtersRadioAll.isChecked = appFilters.type == AppFiltersType.ALL
|
||||
@@ -101,7 +101,7 @@ class ConfigureActivity : BaseActivity<ActivityConfigBinding>() {
|
||||
}
|
||||
cancelButton()
|
||||
if (appFilters.name.isNotBlank())
|
||||
neutralButton(LocaleString.clearFilters) {
|
||||
neutralButton(locale.clearFilters) {
|
||||
setAppFiltersType()
|
||||
appFilters.name = ""
|
||||
refreshData()
|
||||
@@ -116,11 +116,11 @@ class ConfigureActivity : BaseActivity<ActivityConfigBinding>() {
|
||||
binding.appIcon.setImageDrawable(bean.icon)
|
||||
binding.appNameText.text = bean.name
|
||||
binding.configTypeText.text = when {
|
||||
AppErrorsConfigData.isAppShowingType(AppErrorsConfigType.GLOBAL, bean.packageName) -> LocaleString.followGlobalConfig
|
||||
AppErrorsConfigData.isAppShowingType(AppErrorsConfigType.DIALOG, bean.packageName) -> LocaleString.showErrorsDialog
|
||||
AppErrorsConfigData.isAppShowingType(AppErrorsConfigType.NOTIFY, bean.packageName) -> LocaleString.showErrorsNotify
|
||||
AppErrorsConfigData.isAppShowingType(AppErrorsConfigType.TOAST, bean.packageName) -> LocaleString.showErrorsToast
|
||||
AppErrorsConfigData.isAppShowingType(AppErrorsConfigType.NOTHING, bean.packageName) -> LocaleString.showNothing
|
||||
AppErrorsConfigData.isAppShowingType(AppErrorsConfigType.GLOBAL, bean.packageName) -> locale.followGlobalConfig
|
||||
AppErrorsConfigData.isAppShowingType(AppErrorsConfigType.DIALOG, bean.packageName) -> locale.showErrorsDialog
|
||||
AppErrorsConfigData.isAppShowingType(AppErrorsConfigType.NOTIFY, bean.packageName) -> locale.showErrorsNotify
|
||||
AppErrorsConfigData.isAppShowingType(AppErrorsConfigType.TOAST, bean.packageName) -> locale.showErrorsToast
|
||||
AppErrorsConfigData.isAppShowingType(AppErrorsConfigType.NOTHING, bean.packageName) -> locale.showNothing
|
||||
else -> "Unknown type"
|
||||
}
|
||||
}
|
||||
@@ -138,8 +138,8 @@ class ConfigureActivity : BaseActivity<ActivityConfigBinding>() {
|
||||
/** 模块未完全激活将显示警告 */
|
||||
if (MainActivity.isModuleValied.not())
|
||||
showDialog {
|
||||
title = LocaleString.notice
|
||||
msg = LocaleString.moduleNotFullyActivatedTip
|
||||
title = locale.notice
|
||||
msg = locale.moduleNotFullyActivatedTip
|
||||
confirmButton { FrameworkTool.restartSystem(context) }
|
||||
cancelButton()
|
||||
noCancelable()
|
||||
@@ -199,7 +199,7 @@ class ConfigureActivity : BaseActivity<ActivityConfigBinding>() {
|
||||
binding.filterIcon.isVisible = false
|
||||
binding.listView.isVisible = false
|
||||
binding.listNoDataView.isVisible = false
|
||||
binding.titleCountText.text = LocaleString.loading
|
||||
binding.titleCountText.text = locale.loading
|
||||
FrameworkTool.fetchAppListData(context = this, appFilters) {
|
||||
/** 设置一个临时变量用于更新列表数据 */
|
||||
val tempsData = ArrayList<AppInfoBean>()
|
||||
@@ -221,7 +221,7 @@ class ConfigureActivity : BaseActivity<ActivityConfigBinding>() {
|
||||
binding.filterIcon.isVisible = true
|
||||
binding.listView.isVisible = listData.isNotEmpty()
|
||||
binding.listNoDataView.isVisible = listData.isEmpty()
|
||||
binding.titleCountText.text = LocaleString.resultCount(listData.size)
|
||||
binding.titleCountText.text = locale.resultCount(listData.size)
|
||||
} else tempsData.clear()
|
||||
}
|
||||
}
|
@@ -17,7 +17,7 @@
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/5/14.
|
||||
* This file is created by fankes on 2022/5/14.
|
||||
*/
|
||||
@file:Suppress("SetTextI18n")
|
||||
|
||||
@@ -25,20 +25,28 @@ package com.fankes.apperrorstracking.ui.activity.main
|
||||
|
||||
import android.os.Build
|
||||
import androidx.core.view.isVisible
|
||||
import com.fankes.apperrorstracking.BuildConfig
|
||||
import com.fankes.apperrorstracking.R
|
||||
import com.fankes.apperrorstracking.const.ModuleVersion
|
||||
import com.fankes.apperrorstracking.data.ConfigData
|
||||
import com.fankes.apperrorstracking.data.factory.bind
|
||||
import com.fankes.apperrorstracking.databinding.ActivityMainBinding
|
||||
import com.fankes.apperrorstracking.locale.LocaleString
|
||||
import com.fankes.apperrorstracking.locale.locale
|
||||
import com.fankes.apperrorstracking.ui.activity.base.BaseActivity
|
||||
import com.fankes.apperrorstracking.ui.activity.debug.LoggerActivity
|
||||
import com.fankes.apperrorstracking.ui.activity.errors.AppErrorsMutedActivity
|
||||
import com.fankes.apperrorstracking.ui.activity.errors.AppErrorsRecordActivity
|
||||
import com.fankes.apperrorstracking.utils.factory.*
|
||||
import com.fankes.apperrorstracking.utils.factory.hideOrShowLauncherIcon
|
||||
import com.fankes.apperrorstracking.utils.factory.isLauncherIconShowing
|
||||
import com.fankes.apperrorstracking.utils.factory.isSystemLanguageSimplifiedChinese
|
||||
import com.fankes.apperrorstracking.utils.factory.navigate
|
||||
import com.fankes.apperrorstracking.utils.factory.openBrowser
|
||||
import com.fankes.apperrorstracking.utils.factory.showDialog
|
||||
import com.fankes.apperrorstracking.utils.factory.toast
|
||||
import com.fankes.apperrorstracking.utils.tool.AppAnalyticsTool
|
||||
import com.fankes.apperrorstracking.utils.tool.AppAnalyticsTool.bindAppAnalytics
|
||||
import com.fankes.apperrorstracking.utils.tool.FrameworkTool
|
||||
import com.fankes.apperrorstracking.utils.tool.GithubReleaseTool
|
||||
import com.fankes.projectpromote.ProjectPromote
|
||||
import com.highcapable.yukihookapi.YukiHookAPI
|
||||
|
||||
class MainActivity : BaseActivity<ActivityMainBinding>() {
|
||||
@@ -55,23 +63,39 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
|
||||
override fun onCreate() {
|
||||
checkingTopComponentName()
|
||||
/** 检查更新 */
|
||||
GithubReleaseTool.checkingForUpdate(context = this, BuildConfig.VERSION_NAME) { version, function ->
|
||||
GithubReleaseTool.checkingForUpdate(context = this, ModuleVersion.NAME) { version, function ->
|
||||
binding.mainTextReleaseVersion.apply {
|
||||
text = LocaleString.clickToUpdate(version)
|
||||
text = locale.clickToUpdate(version)
|
||||
isVisible = true
|
||||
setOnClickListener { function() }
|
||||
}
|
||||
}
|
||||
/** 推广、恰饭 */
|
||||
if (YukiHookAPI.Status.isXposedModuleActive) ProjectPromote.show(activity = this, ModuleVersion.toString())
|
||||
/** 显示开发者提示 */
|
||||
if (ConfigData.isShowDeveloperNotice)
|
||||
showDialog {
|
||||
title = LocaleString.developerNotice
|
||||
msg = LocaleString.developerNoticeTip
|
||||
confirmButton(LocaleString.gotIt) { ConfigData.isShowDeveloperNotice = false }
|
||||
title = locale.developerNotice
|
||||
msg = locale.developerNoticeTip
|
||||
confirmButton(locale.gotIt) { ConfigData.isShowDeveloperNotice = false }
|
||||
noCancelable()
|
||||
}
|
||||
binding.mainTextVersion.text = LocaleString.moduleVersion(BuildConfig.VERSION_NAME)
|
||||
binding.mainTextSystemVersion.text = LocaleString.systemVersion(systemVersion)
|
||||
/** 设置 CI 自动构建标识 */
|
||||
if (ModuleVersion.isCiMode)
|
||||
binding.mainTextReleaseVersion.apply {
|
||||
text = "CI ${ModuleVersion.GITHUB_COMMIT_ID}"
|
||||
isVisible = true
|
||||
setOnClickListener {
|
||||
showDialog {
|
||||
title = locale.ciNoticeDialogTitle
|
||||
msg = locale.ciNoticeDialogContent(ModuleVersion.GITHUB_COMMIT_ID)
|
||||
confirmButton(locale.gotIt)
|
||||
noCancelable()
|
||||
}
|
||||
}
|
||||
}
|
||||
binding.mainTextVersion.text = locale.moduleVersion(ModuleVersion.NAME)
|
||||
binding.mainTextSystemVersion.text = locale.systemVersion(systemVersion)
|
||||
binding.onlyShowErrorsInFrontSwitch.bind(ConfigData.ENABLE_ONLY_SHOW_ERRORS_IN_FRONT)
|
||||
binding.onlyShowErrorsInMainProcessSwitch.bind(ConfigData.ENABLE_ONLY_SHOW_ERRORS_IN_MAIN)
|
||||
binding.alwaysShowsReopenAppOptionsSwitch.bind(ConfigData.ENABLE_ALWAYS_SHOWS_REOPEN_APP_OPTIONS)
|
||||
@@ -81,13 +105,14 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
|
||||
}
|
||||
binding.enableMaterial3AppErrorsDialogSwitch.bind(ConfigData.ENABLE_MATERIAL3_STYLE_APP_ERRORS_DIALOG)
|
||||
/** 设置匿名统计 */
|
||||
binding.appAnalyticsConfigItem.isVisible = AppAnalyticsTool.isAvailable
|
||||
binding.enableAnonymousStatisticsSwitch.bindAppAnalytics()
|
||||
/** 系统版本点击事件 */
|
||||
binding.mainTextSystemVersion.setOnClickListener {
|
||||
showDialog {
|
||||
title = LocaleString.notice
|
||||
title = locale.notice
|
||||
msg = systemVersion
|
||||
confirmButton(LocaleString.gotIt)
|
||||
confirmButton(locale.gotIt)
|
||||
}
|
||||
}
|
||||
/** 管理应用配置模板按钮点击事件 */
|
||||
@@ -101,6 +126,11 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
|
||||
binding.titleRestartIcon.setOnClickListener { FrameworkTool.restartSystem(context = this) }
|
||||
/** 项目地址按钮点击事件 */
|
||||
binding.titleGithubIcon.setOnClickListener { openBrowser(url = "https://github.com/KitsunePie/AppErrorsTracking") }
|
||||
/** 恰饭! */
|
||||
binding.paymentFollowingZhCnItem.isVisible = isSystemLanguageSimplifiedChinese
|
||||
binding.linkWithFollowMe.setOnClickListener {
|
||||
openBrowser(url = "https://www.coolapk.com/u/876977", packageName = "com.coolapk.market")
|
||||
}
|
||||
/** 设置桌面图标显示隐藏 */
|
||||
binding.hideIconInLauncherSwitch.isChecked = isLauncherIconShowing.not()
|
||||
binding.hideIconInLauncherSwitch.setOnCheckedChangeListener { btn, b ->
|
||||
@@ -125,9 +155,9 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
|
||||
}
|
||||
)
|
||||
binding.mainTextStatus.text = when {
|
||||
YukiHookAPI.Status.isXposedModuleActive && isModuleValied.not() -> LocaleString.moduleNotFullyActivated
|
||||
YukiHookAPI.Status.isXposedModuleActive -> LocaleString.moduleIsActivated
|
||||
else -> LocaleString.moduleNotActivated
|
||||
YukiHookAPI.Status.isXposedModuleActive && isModuleValied.not() -> locale.moduleNotFullyActivated
|
||||
YukiHookAPI.Status.isXposedModuleActive -> locale.moduleIsActivated
|
||||
else -> locale.moduleNotActivated
|
||||
}
|
||||
binding.mainTextApiWay.isVisible = YukiHookAPI.Status.isXposedModuleActive
|
||||
binding.mainTextApiWay.text = "Activated by ${YukiHookAPI.Status.Executor.name} API ${YukiHookAPI.Status.Executor.apiLevel}"
|
||||
@@ -138,7 +168,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
|
||||
* @param callback 激活后回调
|
||||
*/
|
||||
private inline fun whenActivated(callback: () -> Unit) {
|
||||
if (YukiHookAPI.Status.isXposedModuleActive) callback() else toast(LocaleString.moduleNotActivated)
|
||||
if (YukiHookAPI.Status.isXposedModuleActive) callback() else toast(locale.moduleNotActivated)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
@@ -17,7 +17,7 @@
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/6/1.
|
||||
* This file is created by fankes on 2022/6/1.
|
||||
*/
|
||||
package com.fankes.apperrorstracking.ui.widget
|
||||
|
@@ -17,7 +17,7 @@
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/5/14.
|
||||
* This file is created by fankes on 2022/5/14.
|
||||
*/
|
||||
@file:Suppress("SameParameterValue")
|
||||
|
@@ -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/3.
|
||||
* This file is created by fankes on 2022/6/3.
|
||||
*/
|
||||
package com.fankes.apperrorstracking.utils.factory
|
||||
|
@@ -17,9 +17,9 @@
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/5/12.
|
||||
* This file is created by fankes on 2022/5/12.
|
||||
*/
|
||||
@file:Suppress("unused", "DEPRECATION", "OPT_IN_USAGE", "EXPERIMENTAL_API_USAGE")
|
||||
@file:Suppress("unused")
|
||||
|
||||
package com.fankes.apperrorstracking.utils.factory
|
||||
|
||||
@@ -33,12 +33,11 @@ import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.viewbinding.ViewBinding
|
||||
import com.fankes.apperrorstracking.locale.LocaleString
|
||||
import com.fankes.apperrorstracking.locale.locale
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.progressindicator.CircularProgressIndicator
|
||||
import com.google.android.material.shape.MaterialShapeDrawable
|
||||
import com.highcapable.yukihookapi.YukiHookAPI
|
||||
import com.highcapable.yukihookapi.annotation.CauseProblemsApi
|
||||
import com.highcapable.yukihookapi.hook.factory.method
|
||||
import com.highcapable.yukihookapi.hook.type.android.LayoutInflaterClass
|
||||
|
||||
@@ -150,7 +149,7 @@ class DialogBuilder<VB : ViewBinding>(
|
||||
* @param text 按钮文本内容
|
||||
* @param callback 点击事件
|
||||
*/
|
||||
fun confirmButton(text: String = LocaleString.confirm, callback: () -> Unit = {}) {
|
||||
fun confirmButton(text: String = locale.confirm, callback: () -> Unit = {}) {
|
||||
instance?.setPositiveButton(text) { _, _ -> callback() }
|
||||
}
|
||||
|
||||
@@ -159,7 +158,7 @@ class DialogBuilder<VB : ViewBinding>(
|
||||
* @param text 按钮文本内容
|
||||
* @param callback 点击事件
|
||||
*/
|
||||
fun cancelButton(text: String = LocaleString.cancel, callback: () -> Unit = {}) {
|
||||
fun cancelButton(text: String = locale.cancel, callback: () -> Unit = {}) {
|
||||
instance?.setNegativeButton(text) { _, _ -> callback() }
|
||||
}
|
||||
|
||||
@@ -168,7 +167,7 @@ class DialogBuilder<VB : ViewBinding>(
|
||||
* @param text 按钮文本内容
|
||||
* @param callback 点击事件
|
||||
*/
|
||||
fun neutralButton(text: String = LocaleString.more, callback: () -> Unit = {}) {
|
||||
fun neutralButton(text: String = locale.more, callback: () -> Unit = {}) {
|
||||
instance?.setNeutralButton(text) { _, _ -> callback() }
|
||||
}
|
||||
|
||||
@@ -184,7 +183,6 @@ class DialogBuilder<VB : ViewBinding>(
|
||||
fun cancel() = dialogInstance?.cancel()
|
||||
|
||||
/** 显示对话框 */
|
||||
@CauseProblemsApi
|
||||
fun show() {
|
||||
/** 若当前自定义 View 的对话框没有调用 [binding] 将会对其手动调用一次以确保显示布局 */
|
||||
if (bindingClass != null) binding
|
@@ -17,14 +17,22 @@
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/5/7.
|
||||
* This file is created by fankes on 2022/5/7.
|
||||
*/
|
||||
@file:Suppress("unused", "NotificationPermission")
|
||||
|
||||
package com.fankes.apperrorstracking.utils.factory
|
||||
|
||||
import android.app.*
|
||||
import android.content.*
|
||||
import android.app.Activity
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.app.PendingIntent
|
||||
import android.app.Service
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.pm.PackageManager.PackageInfoFlags
|
||||
@@ -43,12 +51,13 @@ import androidx.core.content.getSystemService
|
||||
import androidx.core.content.pm.PackageInfoCompat
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import androidx.core.graphics.drawable.IconCompat
|
||||
import com.fankes.apperrorstracking.BuildConfig
|
||||
import com.fankes.apperrorstracking.R
|
||||
import com.fankes.apperrorstracking.locale.LocaleString
|
||||
import com.fankes.apperrorstracking.locale.locale
|
||||
import com.fankes.apperrorstracking.wrapper.BuildConfigWrapper
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.highcapable.yukihookapi.hook.factory.field
|
||||
import com.highcapable.yukihookapi.hook.factory.method
|
||||
import com.highcapable.yukihookapi.hook.log.YLog
|
||||
import com.highcapable.yukihookapi.hook.type.android.ApplicationInfoClass
|
||||
import com.highcapable.yukihookapi.hook.type.android.ContextClass
|
||||
import com.highcapable.yukihookapi.hook.type.android.IntentClass
|
||||
@@ -58,7 +67,18 @@ import java.io.Serializable
|
||||
import java.math.RoundingMode
|
||||
import java.text.DecimalFormat
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
|
||||
/**
|
||||
* 当前系统环境是否为简体中文
|
||||
* @return [Boolean]
|
||||
*/
|
||||
val isSystemLanguageSimplifiedChinese
|
||||
get(): Boolean {
|
||||
val locale = Locale.getDefault()
|
||||
return locale.language == "zh" && locale.country == "CN"
|
||||
}
|
||||
|
||||
/**
|
||||
* 系统深色模式是否开启
|
||||
@@ -107,7 +127,7 @@ fun Resources.colorOf(@ColorRes resId: Int) = ResourcesCompat.getColor(this, res
|
||||
* @return [PackageInfo] or null
|
||||
*/
|
||||
private fun Context.getPackageInfoCompat(packageName: String, flag: Number = 0) = runCatching {
|
||||
@Suppress("DEPRECATION")
|
||||
@Suppress("DEPRECATION", "KotlinRedundantDiagnosticSuppress")
|
||||
if (Build.VERSION.SDK_INT >= 33)
|
||||
packageManager?.getPackageInfo(packageName, PackageInfoFlags.of(flag.toLong()))
|
||||
else packageManager?.getPackageInfo(packageName, flag.toInt())
|
||||
@@ -124,7 +144,7 @@ private val PackageInfo.versionCodeCompat get() = PackageInfoCompat.getLongVersi
|
||||
* @return [List]<[PackageInfo]>
|
||||
*/
|
||||
fun Context.listOfPackages() = runCatching {
|
||||
@Suppress("DEPRECATION")
|
||||
@Suppress("DEPRECATION", "KotlinRedundantDiagnosticSuppress")
|
||||
if (Build.VERSION.SDK_INT >= 33)
|
||||
packageManager?.getInstalledPackages(PackageInfoFlags.of(PackageManager.GET_CONFIGURATIONS.toLong()))
|
||||
else packageManager?.getInstalledPackages(PackageManager.GET_CONFIGURATIONS)
|
||||
@@ -160,6 +180,20 @@ fun Context.appVersionNameOf(packageName: String = getPackageName()) = getPackag
|
||||
*/
|
||||
fun Context.appVersionCodeOf(packageName: String = getPackageName()) = getPackageInfoCompat(packageName)?.versionCodeCompat ?: -1L
|
||||
|
||||
/**
|
||||
* 得到 APP 目标 SDK 版本
|
||||
* @param packageName APP 包名 - 默认为当前 APP
|
||||
* @return [Int] 无法获取时返回 -1
|
||||
*/
|
||||
fun Context.appTargetSdkOf(packageName: String = getPackageName()) = getPackageInfoCompat(packageName)?.applicationInfo?.targetSdkVersion ?: -1
|
||||
|
||||
/**
|
||||
* 得到 APP 最低 SDK 版本
|
||||
* @param packageName APP 包名 - 默认为当前 APP
|
||||
* @return [Int] 无法获取时返回 -1
|
||||
*/
|
||||
fun Context.appMinSdkOf(packageName: String = getPackageName()) = getPackageInfoCompat(packageName)?.applicationInfo?.minSdkVersion ?: -1
|
||||
|
||||
/**
|
||||
* 获取 APP CPU ABI 名称
|
||||
* @param packageName APP 包名 - 默认为当前 APP
|
||||
@@ -254,7 +288,11 @@ fun Long.toUtcTime() = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS", Locale.ROOT
|
||||
* 弹出 [Toast]
|
||||
* @param msg 提示内容
|
||||
*/
|
||||
fun Context.toast(msg: String) = Toast.makeText(this, msg, Toast.LENGTH_SHORT).show()
|
||||
fun Context.toast(msg: String) {
|
||||
runCatching {
|
||||
Toast.makeText(this, msg, Toast.LENGTH_SHORT).show()
|
||||
}.onFailure { YLog.warn(msg) }
|
||||
}
|
||||
|
||||
/**
|
||||
* 弹出 [Snackbar]
|
||||
@@ -306,7 +344,7 @@ inline fun <reified T : Activity> Context.navigate(isOutSide: Boolean = false, i
|
||||
startActivity((if (isOutSide) Intent() else Intent(if (this is Service) applicationContext else this, T::class.java)).apply {
|
||||
flags = if (this@navigate !is Activity) Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||
else Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
if (isOutSide) component = ComponentName(BuildConfig.APPLICATION_ID, T::class.java.name)
|
||||
if (isOutSide) component = ComponentName(BuildConfigWrapper.APPLICATION_ID, T::class.java.name)
|
||||
initiate(this)
|
||||
})
|
||||
}.onFailure { toast(msg = "Start ${T::class.java.name} failed") }
|
||||
@@ -319,7 +357,7 @@ fun Context.copyToClipboard(content: String) = runCatching {
|
||||
(getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager).apply {
|
||||
setPrimaryClip(ClipData.newPlainText(null, content))
|
||||
(primaryClip?.getItemAt(0)?.text ?: "").also {
|
||||
if (it != content) toast(LocaleString.copyFail) else toast(LocaleString.copied)
|
||||
if (it != content) toast(locale.copyFail) else toast(locale.copied)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -377,7 +415,10 @@ fun Context.openApp(packageName: String = getPackageName(), userId: Int = 0) = r
|
||||
* 是否有 Root 权限
|
||||
* @return [Boolean]
|
||||
*/
|
||||
val isRootAccess get() = runCatching { Shell.rootAccess() }.getOrNull() ?: false
|
||||
val isRootAccess get() = runCatching {
|
||||
@Suppress("DEPRECATION")
|
||||
Shell.rootAccess()
|
||||
}.getOrNull() ?: false
|
||||
|
||||
/**
|
||||
* 执行命令
|
||||
@@ -386,6 +427,7 @@ val isRootAccess get() = runCatching { Shell.rootAccess() }.getOrNull() ?: false
|
||||
* @return [String] 执行结果
|
||||
*/
|
||||
fun execShell(cmd: String, isSu: Boolean = true) = runCatching {
|
||||
@Suppress("DEPRECATION")
|
||||
(if (isSu) Shell.su(cmd) else Shell.sh(cmd)).exec().out.let {
|
||||
if (it.isNotEmpty()) it[0].trim() else ""
|
||||
}
|
||||
@@ -399,7 +441,7 @@ fun execShell(cmd: String, isSu: Boolean = true) = runCatching {
|
||||
*/
|
||||
fun Context.hideOrShowLauncherIcon(isShow: Boolean) {
|
||||
packageManager?.setComponentEnabledSetting(
|
||||
ComponentName(packageName, "${BuildConfig.APPLICATION_ID}.Home"),
|
||||
ComponentName(packageName, "${BuildConfigWrapper.APPLICATION_ID}.Home"),
|
||||
if (isShow) PackageManager.COMPONENT_ENABLED_STATE_DISABLED else PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
|
||||
PackageManager.DONT_KILL_APP
|
||||
)
|
||||
@@ -411,5 +453,5 @@ fun Context.hideOrShowLauncherIcon(isShow: Boolean) {
|
||||
*/
|
||||
val Context.isLauncherIconShowing
|
||||
get() = packageManager?.getComponentEnabledSetting(
|
||||
ComponentName(packageName, "${BuildConfig.APPLICATION_ID}.Home")
|
||||
ComponentName(packageName, "${BuildConfigWrapper.APPLICATION_ID}.Home")
|
||||
) != PackageManager.COMPONENT_ENABLED_STATE_DISABLED
|
@@ -17,7 +17,7 @@
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/10/3.
|
||||
* This file is created by fankes on 2022/10/3.
|
||||
*/
|
||||
package com.fankes.apperrorstracking.utils.factory
|
||||
|
@@ -17,7 +17,7 @@
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/10/3.
|
||||
* This file is created by fankes on 2022/10/3.
|
||||
*/
|
||||
package com.fankes.apperrorstracking.utils.factory
|
||||
|
@@ -17,7 +17,7 @@
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/10/5.
|
||||
* This file is created by fankes on 2022/10/5.
|
||||
*/
|
||||
@file:Suppress("unused")
|
||||
|
||||
@@ -25,7 +25,7 @@ package com.fankes.apperrorstracking.utils.tool
|
||||
|
||||
import android.app.Application
|
||||
import android.widget.CompoundButton
|
||||
import com.fankes.apperrorstracking.BuildConfig
|
||||
import com.fankes.apperrorstracking.generated.ModuleAppProperties
|
||||
import com.highcapable.yukihookapi.hook.factory.prefs
|
||||
import com.highcapable.yukihookapi.hook.xposed.prefs.data.PrefsData
|
||||
import com.microsoft.appcenter.AppCenter
|
||||
@@ -38,7 +38,7 @@ import com.microsoft.appcenter.crashes.Crashes
|
||||
object AppAnalyticsTool {
|
||||
|
||||
/** App Secret */
|
||||
private const val APP_CENTER_SECRET = BuildConfig.APP_CENTER_SECRET
|
||||
private const val APP_CENTER_SECRET = ModuleAppProperties.APP_CENTER_SECRET
|
||||
|
||||
/** 启用匿名统计收集使用情况功能 */
|
||||
private val ENABLE_APP_CENTER_ANALYTICS = PrefsData("_enable_app_center_analytics", true)
|
||||
@@ -56,6 +56,9 @@ object AppAnalyticsTool {
|
||||
instance?.prefs()?.edit { put(ENABLE_APP_CENTER_ANALYTICS, value) }
|
||||
}
|
||||
|
||||
/** 是否可用 */
|
||||
val isAvailable = APP_CENTER_SECRET.isNotBlank()
|
||||
|
||||
/** 绑定到 [CompoundButton] 自动设置选中状态 */
|
||||
fun CompoundButton.bindAppAnalytics() {
|
||||
isChecked = isEnableAppCenterAnalytics
|
||||
@@ -81,7 +84,7 @@ object AppAnalyticsTool {
|
||||
*/
|
||||
fun init(instance: Application) {
|
||||
this.instance = instance
|
||||
if (isEnableAppCenterAnalytics && APP_CENTER_SECRET.isNotBlank())
|
||||
if (isEnableAppCenterAnalytics && isAvailable)
|
||||
AppCenter.start(instance, APP_CENTER_SECRET, Analytics::class.java, Crashes::class.java)
|
||||
}
|
||||
}
|
@@ -17,10 +17,8 @@
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/5/12.
|
||||
* This file is created by fankes on 2022/5/12.
|
||||
*/
|
||||
@file:Suppress("UNCHECKED_CAST")
|
||||
|
||||
package com.fankes.apperrorstracking.utils.tool
|
||||
|
||||
import android.content.Context
|
||||
@@ -28,7 +26,8 @@ import com.fankes.apperrorstracking.bean.AppErrorsInfoBean
|
||||
import com.fankes.apperrorstracking.bean.AppFiltersBean
|
||||
import com.fankes.apperrorstracking.bean.AppInfoBean
|
||||
import com.fankes.apperrorstracking.bean.MutedErrorsAppBean
|
||||
import com.fankes.apperrorstracking.locale.LocaleString
|
||||
import com.fankes.apperrorstracking.const.PackageName
|
||||
import com.fankes.apperrorstracking.locale.locale
|
||||
import com.fankes.apperrorstracking.utils.factory.execShell
|
||||
import com.fankes.apperrorstracking.utils.factory.isRootAccess
|
||||
import com.fankes.apperrorstracking.utils.factory.showDialog
|
||||
@@ -41,9 +40,6 @@ import com.highcapable.yukihookapi.hook.xposed.channel.data.ChannelData
|
||||
*/
|
||||
object FrameworkTool {
|
||||
|
||||
/** 系统框架包名 */
|
||||
const val SYSTEM_FRAMEWORK_NAME = "android"
|
||||
|
||||
private const val CALL_REFRESH_HOST_PREFS_DATA = "call_refresh_host_prefs_data"
|
||||
private const val CALL_APP_ERRORS_DATA_GET = "call_app_errors_data_get"
|
||||
private const val CALL_MUTED_ERRORS_APP_DATA_GET = "call_muted_app_errors_data_get"
|
||||
@@ -215,22 +211,22 @@ object FrameworkTool {
|
||||
/** 当 Root 权限获取失败时显示对话框 */
|
||||
fun showWhenAccessRootFail() =
|
||||
context.showDialog {
|
||||
title = LocaleString.accessRootFail
|
||||
msg = LocaleString.accessRootFailTip
|
||||
confirmButton(LocaleString.gotIt)
|
||||
title = locale.accessRootFail
|
||||
msg = locale.accessRootFailTip
|
||||
confirmButton(locale.gotIt)
|
||||
}
|
||||
context.showDialog {
|
||||
title = LocaleString.notice
|
||||
msg = LocaleString.areYouSureRestartSystem
|
||||
title = locale.notice
|
||||
msg = locale.areYourSureRestartSystem
|
||||
confirmButton {
|
||||
if (isRootAccess)
|
||||
execShell(cmd = "reboot")
|
||||
else showWhenAccessRootFail()
|
||||
}
|
||||
neutralButton(LocaleString.fastRestart) {
|
||||
neutralButton(locale.fastRestart) {
|
||||
context.showDialog {
|
||||
title = LocaleString.warning
|
||||
msg = LocaleString.fastRestartProblem
|
||||
title = locale.warning
|
||||
msg = locale.fastRestartProblem
|
||||
confirmButton {
|
||||
if (isRootAccess)
|
||||
execShell(cmd = "killall zygote")
|
||||
@@ -249,13 +245,13 @@ object FrameworkTool {
|
||||
* @param result 成功后回调
|
||||
*/
|
||||
fun checkingActivated(context: Context, result: (Boolean) -> Unit) =
|
||||
context.dataChannel(SYSTEM_FRAMEWORK_NAME).checkingVersionEquals(result = result)
|
||||
context.dataChannel(PackageName.SYSTEM_FRAMEWORK).checkingVersionEquals(result = result)
|
||||
|
||||
/**
|
||||
* 通知系统框架刷新存储的数据
|
||||
* @param context 实例
|
||||
*/
|
||||
fun refreshFrameworkPrefsData(context: Context) = context.dataChannel(SYSTEM_FRAMEWORK_NAME).put(CALL_REFRESH_HOST_PREFS_DATA)
|
||||
fun refreshFrameworkPrefsData(context: Context) = context.dataChannel(PackageName.SYSTEM_FRAMEWORK).put(CALL_REFRESH_HOST_PREFS_DATA)
|
||||
|
||||
/**
|
||||
* 使用系统框架打开 [packageName]
|
||||
@@ -264,7 +260,7 @@ object FrameworkTool {
|
||||
* @param userId APP 用户 ID
|
||||
*/
|
||||
fun openAppUsedFramework(context: Context, packageName: String, userId: Int) =
|
||||
context.dataChannel(SYSTEM_FRAMEWORK_NAME).put(CALL_OPEN_SPECIFY_APP, Pair(packageName, userId))
|
||||
context.dataChannel(PackageName.SYSTEM_FRAMEWORK).put(CALL_OPEN_SPECIFY_APP, Pair(packageName, userId))
|
||||
|
||||
/**
|
||||
* 获取指定 APP 异常信息
|
||||
@@ -273,7 +269,7 @@ object FrameworkTool {
|
||||
* @param result 回调数据
|
||||
*/
|
||||
fun fetchAppErrorInfoData(context: Context, pid: Int, result: (AppErrorsInfoBean) -> Unit) {
|
||||
context.dataChannel(SYSTEM_FRAMEWORK_NAME).with {
|
||||
context.dataChannel(PackageName.SYSTEM_FRAMEWORK).with {
|
||||
wait(CALL_APP_ERROR_DATA_GET_RESULT) { result(it) }
|
||||
put(CALL_APP_ERROR_DATA_GET, pid)
|
||||
}
|
||||
@@ -285,7 +281,7 @@ object FrameworkTool {
|
||||
* @param result 回调数据
|
||||
*/
|
||||
fun fetchAppErrorsInfoData(context: Context, result: (ArrayList<AppErrorsInfoBean>) -> Unit) {
|
||||
context.dataChannel(SYSTEM_FRAMEWORK_NAME).with {
|
||||
context.dataChannel(PackageName.SYSTEM_FRAMEWORK).with {
|
||||
wait(CALL_APP_ERRORS_DATA_GET_RESULT) { result(it) }
|
||||
put(CALL_APP_ERRORS_DATA_GET)
|
||||
}
|
||||
@@ -298,7 +294,7 @@ object FrameworkTool {
|
||||
* @param callback 成功后回调
|
||||
*/
|
||||
fun removeAppErrorsInfoData(context: Context, appErrorsInfo: AppErrorsInfoBean, callback: () -> Unit) {
|
||||
context.dataChannel(SYSTEM_FRAMEWORK_NAME).with {
|
||||
context.dataChannel(PackageName.SYSTEM_FRAMEWORK).with {
|
||||
wait(CALL_APP_ERRORS_DATA_REMOVE_RESULT) { callback() }
|
||||
put(CALL_APP_ERRORS_DATA_REMOVE, appErrorsInfo)
|
||||
}
|
||||
@@ -310,7 +306,7 @@ object FrameworkTool {
|
||||
* @param callback 成功后回调
|
||||
*/
|
||||
fun clearAppErrorsInfoData(context: Context, callback: () -> Unit) {
|
||||
context.dataChannel(SYSTEM_FRAMEWORK_NAME).with {
|
||||
context.dataChannel(PackageName.SYSTEM_FRAMEWORK).with {
|
||||
wait(CALL_APP_ERRORS_DATA_CLEAR_RESULT) { callback() }
|
||||
put(CALL_APP_ERRORS_DATA_CLEAR)
|
||||
}
|
||||
@@ -323,7 +319,7 @@ object FrameworkTool {
|
||||
* @param callback 成功后回调
|
||||
*/
|
||||
fun mutedErrorsIfUnlock(context: Context, packageName: String, callback: () -> Unit) {
|
||||
context.dataChannel(SYSTEM_FRAMEWORK_NAME).with {
|
||||
context.dataChannel(PackageName.SYSTEM_FRAMEWORK).with {
|
||||
wait(CALL_MUTED_ERRORS_IF_UNLOCK_RESULT) { callback() }
|
||||
put(CALL_MUTED_ERRORS_IF_UNLOCK, packageName)
|
||||
}
|
||||
@@ -336,7 +332,7 @@ object FrameworkTool {
|
||||
* @param callback 成功后回调
|
||||
*/
|
||||
fun mutedErrorsIfRestart(context: Context, packageName: String, callback: () -> Unit) {
|
||||
context.dataChannel(SYSTEM_FRAMEWORK_NAME).with {
|
||||
context.dataChannel(PackageName.SYSTEM_FRAMEWORK).with {
|
||||
wait(CALL_MUTED_ERRORS_IF_RESTART_RESULT) { callback() }
|
||||
put(CALL_MUTED_ERRORS_IF_RESTART, packageName)
|
||||
}
|
||||
@@ -348,7 +344,7 @@ object FrameworkTool {
|
||||
* @param result 回调数据
|
||||
*/
|
||||
fun fetchMutedErrorsAppsData(context: Context, result: (ArrayList<MutedErrorsAppBean>) -> Unit) {
|
||||
context.dataChannel(SYSTEM_FRAMEWORK_NAME).with {
|
||||
context.dataChannel(PackageName.SYSTEM_FRAMEWORK).with {
|
||||
wait(CALL_MUTED_ERRORS_APP_DATA_GET_RESULT) { result(it) }
|
||||
put(CALL_MUTED_ERRORS_APP_DATA_GET)
|
||||
}
|
||||
@@ -361,7 +357,7 @@ object FrameworkTool {
|
||||
* @param callback 成功后回调
|
||||
*/
|
||||
fun unmuteErrorsApp(context: Context, mutedErrorsApp: MutedErrorsAppBean, callback: () -> Unit) {
|
||||
context.dataChannel(SYSTEM_FRAMEWORK_NAME).with {
|
||||
context.dataChannel(PackageName.SYSTEM_FRAMEWORK).with {
|
||||
wait(CALL_UNMUTE_ERRORS_APP_DATA_RESULT) { callback() }
|
||||
put(CALL_UNMUTE_ERRORS_APP_DATA, mutedErrorsApp)
|
||||
}
|
||||
@@ -373,7 +369,7 @@ object FrameworkTool {
|
||||
* @param callback 成功后回调
|
||||
*/
|
||||
fun unmuteAllErrorsApps(context: Context, callback: () -> Unit) {
|
||||
context.dataChannel(SYSTEM_FRAMEWORK_NAME).with {
|
||||
context.dataChannel(PackageName.SYSTEM_FRAMEWORK).with {
|
||||
wait(CALL_UNMUTE_ALL_ERRORS_APPS_DATA_RESULT) { callback() }
|
||||
put(CALL_UNMUTE_ALL_ERRORS_APPS_DATA)
|
||||
}
|
||||
@@ -386,7 +382,7 @@ object FrameworkTool {
|
||||
* @param result 回调数据
|
||||
*/
|
||||
fun fetchAppListData(context: Context, appFilters: AppFiltersBean, result: (ArrayList<AppInfoBean>) -> Unit) {
|
||||
context.dataChannel(SYSTEM_FRAMEWORK_NAME).with {
|
||||
context.dataChannel(PackageName.SYSTEM_FRAMEWORK).with {
|
||||
wait(CALL_APP_LIST_DATA_GET_RESULT) { result(it) }
|
||||
put(CALL_APP_LIST_DATA_GET, appFilters)
|
||||
}
|
@@ -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 2023/1/23.
|
||||
* This file is created by fankes on 2023/1/23.
|
||||
*/
|
||||
package com.fankes.apperrorstracking.utils.tool
|
||||
|
||||
@@ -26,14 +26,18 @@ import android.content.Context
|
||||
import android.icu.text.SimpleDateFormat
|
||||
import android.icu.util.Calendar
|
||||
import android.icu.util.TimeZone
|
||||
import com.fankes.apperrorstracking.locale.LocaleString
|
||||
import com.fankes.apperrorstracking.locale.locale
|
||||
import com.fankes.apperrorstracking.utils.factory.openBrowser
|
||||
import com.fankes.apperrorstracking.utils.factory.showDialog
|
||||
import okhttp3.*
|
||||
import okhttp3.Call
|
||||
import okhttp3.Callback
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import org.json.JSONObject
|
||||
import java.io.IOException
|
||||
import java.io.Serializable
|
||||
import java.util.*
|
||||
import java.util.Locale
|
||||
|
||||
/**
|
||||
* 获取 GitHub Release 最新版本工具类
|
||||
@@ -70,9 +74,9 @@ object GithubReleaseTool {
|
||||
date = getString("published_at").localTime()
|
||||
).apply {
|
||||
fun showUpdate() = context.showDialog {
|
||||
title = LocaleString.latestVersion(name)
|
||||
msg = LocaleString.latestVersionTip(date, content)
|
||||
confirmButton(LocaleString.updateNow) { context.openBrowser(htmlUrl) }
|
||||
title = locale.latestVersion(name)
|
||||
msg = locale.latestVersionTip(date, content)
|
||||
confirmButton(locale.updateNow) { context.openBrowser(htmlUrl) }
|
||||
cancelButton()
|
||||
}
|
||||
if (name != version) (context as? Activity?)?.runOnUiThread {
|
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
|
||||
* Copyright (C) 2017-2023 Fankes Studio(qzmmcn@163.com)
|
||||
* https://github.com/KitsunePie/AppErrorsTracking
|
||||
*
|
||||
* This software is non-free but opensource software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Affero General Public License
|
||||
* as published by the Free Software Foundation; either
|
||||
* version 3 of the License, or any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is created by fankes on 2023/11/3.
|
||||
*/
|
||||
package com.fankes.apperrorstracking.utils.tool
|
||||
|
||||
import android.content.Context
|
||||
import com.fankes.apperrorstracking.databinding.DiaStackTraceShareBinding
|
||||
import com.fankes.apperrorstracking.utils.factory.showDialog
|
||||
|
||||
/**
|
||||
* 异常堆栈分享工具类
|
||||
*/
|
||||
object StackTraceShareHelper {
|
||||
|
||||
/**
|
||||
* 显示分享选择器
|
||||
* @param context 当前实例
|
||||
* @param title 对话框标题
|
||||
* @param onChoose 回调选择的结果
|
||||
*/
|
||||
fun showChoose(
|
||||
context: Context,
|
||||
title: String,
|
||||
onChoose: (sDeviceBrand: Boolean, sDeviceModel: Boolean, sDisplay: Boolean, sPackageName: Boolean) -> Unit
|
||||
) {
|
||||
context.showDialog<DiaStackTraceShareBinding> {
|
||||
this.title = title
|
||||
confirmButton {
|
||||
val sDeviceBrand = binding.configCheck0.isChecked
|
||||
val sDeviceModel = binding.configCheck1.isChecked
|
||||
val sDisplay = binding.configCheck2.isChecked
|
||||
val sPackageName = binding.configCheck3.isChecked
|
||||
onChoose(sDeviceBrand, sDeviceModel, sDisplay, sPackageName)
|
||||
cancel()
|
||||
}
|
||||
cancelButton()
|
||||
}
|
||||
}
|
||||
}
|
@@ -17,13 +17,19 @@
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/5/13.
|
||||
* This file is created by fankes on 2022/5/13.
|
||||
*/
|
||||
@file:Suppress("unused")
|
||||
|
||||
package com.fankes.apperrorstracking.utils.tool
|
||||
|
||||
import java.io.*
|
||||
import java.io.BufferedInputStream
|
||||
import java.io.BufferedOutputStream
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import java.io.FileOutputStream
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipInputStream
|
||||
import java.util.zip.ZipOutputStream
|
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
|
||||
* Copyright (C) 2017-2023 Fankes Studio(qzmmcn@163.com)
|
||||
* https://github.com/KitsunePie/AppErrorsTracking
|
||||
*
|
||||
* This software is non-free but opensource software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Affero General Public License
|
||||
* as published by the Free Software Foundation; either
|
||||
* version 3 of the License, or any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is created by fankes on 2023/9/19.
|
||||
*/
|
||||
@file:Suppress("unused")
|
||||
|
||||
package com.fankes.apperrorstracking.wrapper
|
||||
|
||||
import com.fankes.apperrorstracking.BuildConfig
|
||||
|
||||
/**
|
||||
* 对 [BuildConfig] 的包装
|
||||
*/
|
||||
object BuildConfigWrapper {
|
||||
const val APPLICATION_ID = BuildConfig.APPLICATION_ID
|
||||
const val VERSION_NAME = BuildConfig.VERSION_NAME
|
||||
const val VERSION_CODE = BuildConfig.VERSION_CODE
|
||||
val isDebug = BuildConfig.DEBUG
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user