45 Commits
1.25 ... 1.3

Author SHA1 Message Date
389f3a69ee Bump version to 1.3 2023-11-03 20:05:58 +08:00
e0b8799a9b feat: ignore user-terminated crash 2023-11-03 19:51:44 +08:00
a64ad86e64 feat: add stack trace share optional dialog 2023-11-03 18:46:07 +08:00
2d42581e36 docs: update release channel 2023-10-26 21:40:50 +08:00
936d66a81e feat: add errors app's target and min sdk record 2023-10-22 23:13:26 +08:00
66b9407b34 fix: catch toast when no looper 2023-10-22 22:36:29 +08:00
c8a0631034 feat: add module app version in shared errors info data 2023-10-21 17:13:57 +08:00
32ca130da0 feat: use handleAppCrashLSPB method on system higher than Android 11 2023-10-21 02:30:11 +08:00
273dca6042 chore: update target sdk to 34 2023-10-21 01:27:10 +08:00
315dfd7e22 feat(docs): update YukiHookAPI owner link 2023-10-21 01:26:20 +08:00
b5baf8243e refactor: remove DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION 2023-10-21 01:25:04 +08:00
2064c01350 chore: bump "com.highcapable.flexilocale" version to 1.0.1 2023-10-13 20:02:06 +08:00
72d76a486c refactor: migrate i18ns generation to FlexiLocale plugin 2023-10-13 18:49:15 +08:00
d215440e83 refactor: migrate to YukiHookAPI new usage 2023-10-07 21:15:15 +08:00
52367b5c41 chore: bump dependency versions 2023-10-07 21:15:05 +08:00
061b73fef7 chore: bump plugin versions
- bump "com.highcapable.sweetdependency" version to 1.0.2
- bump "com.highcapable.sweetproperty" version to 1.0.3
2023-09-26 09:10:38 +08:00
13e63a8fb3 refactor: change "Url" to "URL" 2023-09-21 00:42:16 +08:00
7eacb56857 refactor: add new R8 rules to fix possible problems 2023-09-19 08:13:54 +08:00
684456bb5b style: rearrange imports 2023-09-19 07:41:23 +08:00
3b5aee9fb8 fix: data format and style problems 2023-09-19 07:30:58 +08:00
86ba9749be ci: use allowed symbols 2023-09-19 05:29:00 +08:00
4fff1d7c17 docs: optimize comments 2023-09-19 05:13:25 +08:00
c1e584d739 feat: lots of changes
- add BuildConfigWrapper
- add project promote
- add ci version tag support
- change app analytics config item show when available
- fix system api compat issues
2023-09-19 05:12:44 +08:00
ccc50d720e feat: add i18n strings 2023-09-19 05:10:35 +08:00
8df2fd5c14 docs: update README, README-zh-CN 2023-09-19 05:09:43 +08:00
99472dedc4 refactor: add package name 2023-09-19 05:07:19 +08:00
d22c5801b2 docs: add icon in img-src 2023-09-19 05:06:26 +08:00
0a87f13af7 ci: optimize and add artifacts post to Telegram 2023-09-19 05:02:57 +08:00
fd7bb9bf77 chore: add Android 14 option 2023-09-19 05:02:37 +08:00
b0a6c71300 chore: migrate build script from groovy to kts
- using SweetDependency, SweetProperty
- merge singing key file configs to properties
- update gradle and dependencies
2023-09-19 05:02:15 +08:00
00512d6f95 chore: clean up build step files 2023-09-19 05:00:58 +08:00
4bc2d84e7d [Change Commit Specification] Use the new commit spec from here on
child commits:
chore: add .editorconfig
2023-09-19 04:59:33 +08:00
Fankesyooni
75e6f1c16c Merge pull request #132 from ZQDesigned/master
[BUG FIX] Deprecated AppErrorInfo when dialog covers AppErrorsDetailActivity
2023-06-21 02:03:30 +08:00
886e8d1e37 Complete method annotation 2023-06-21 01:43:29 +08:00
a876854010 Revert some changes and Refactor code style again 2023-06-21 01:32:06 +08:00
9834b6c8dd Refactor code style 2023-06-21 01:14:17 +08:00
3a5a1df270 Fix:deprecated AppErrorInfo when dialog covers AppErrorsDetailActivity 2023-06-21 00:22:43 +08:00
4afe549a8d Modify refactor the locales config code 2023-05-09 11:51:11 +08:00
3a7c97a1ac Update Gradle dependencies 2023-05-09 11:50:06 +08:00
Fankesyooni
2b8b769aa9 Merge pull request #112 from huajijam/master
feat: Per-app language preferences
2023-05-04 22:34:22 +08:00
‭huajijam
fa793d764b feat: Per-app language preferences 2023-05-04 21:42:35 +08:00
4f9ef060ea Update Gradle dependencies 2023-04-25 07:30:55 +08:00
47be6b10b7 Modify merge to YukiHookAPI new usage 2023-04-25 06:29:55 +08:00
340034b531 Update YukiHookAPI 2023-04-25 06:29:35 +08:00
58aa7bc498 Update YukiHookAPI 2023-04-21 01:26:35 +08:00
181 changed files with 1669 additions and 1481 deletions

17
.editorconfig Normal file
View 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

View File

@@ -33,6 +33,7 @@ body:
attributes: attributes:
label: Android version / Android 版本 label: Android version / Android 版本
options: options:
- 14
- 13 - 13
- 12L/12.1 - 12L/12.1
- 12 - 12

View File

@@ -13,17 +13,36 @@ on:
jobs: jobs:
build: build:
name: Build CI name: Build CI
if: ${{ success() }}
runs-on: ubuntu-latest 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: steps:
- uses: actions/checkout@v3 - 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 - name: Setup cmake
uses: jwlawson/actions-setup-cmake@v1 uses: jwlawson/actions-setup-cmake@v1
with: with:
cmake-version: '3.22.1' cmake-version: '3.22.1'
- name: Prepare Java 11 - name: Prepare Java 17
uses: actions/setup-java@v3 uses: actions/setup-java@v3
with: with:
java-version: 11 java-version: 17
java-package: jdk java-package: jdk
distribution: 'temurin' distribution: 'temurin'
cache: 'gradle' cache: 'gradle'
@@ -34,7 +53,7 @@ jobs:
~/.gradle/caches ~/.gradle/caches
~/.gradle/wrapper ~/.gradle/wrapper
!~/.gradle/caches/build-cache-* !~/.gradle/caches/build-cache-*
key: gradle-deps-core-${{ hashFiles('**/build.gradle') }} key: gradle-deps-core-${{ hashFiles('**/build.gradle.kts') }}
restore-keys: | restore-keys: |
gradle-deps gradle-deps
- name: Cache Gradle Build - name: Cache Gradle Build
@@ -47,31 +66,43 @@ jobs:
gradle-builds gradle-builds
- name: Build with Gradle - name: Build with Gradle
run: | run: |
./gradlew :app:assembleDebug ./gradlew :module-app:assembleDebug
./gradlew :app:assembleRelease ./gradlew :module-app:assembleRelease
./gradlew :demo-app:assembleDebug ./gradlew :demo-app:assembleDebug
./gradlew :demo-app:assembleRelease ./gradlew :demo-app:assembleRelease
echo "MODULE_DEBUG_APK_FILE=$(find app/build/outputs/apk/debug -name '*.apk')" >> $GITHUB_ENV echo "MODULE_DEBUG_APK_PATH=$(find ${{ env.MODULE_APK_OUTPUT_PATH }}/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_PATH=$(find ${{ env.MODULE_APK_OUTPUT_PATH }}/release -name '*.apk')" >> $GITHUB_ENV
echo "MODULE_RELEASE_APK_FILE=$(find app/build/outputs/apk/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_FILE=$(find demo-app/build/outputs/apk/release -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) - name: Upload Artifacts (Module-Debug)
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
path: ${{ env.MODULE_DEBUG_APK_FILE }} path: ${{ env.MODULE_DEBUG_APK_PATH }}
name: module-debug name: AppErrorsTracking-module-debug-${{ github.event.head_commit.id }}
- name: Upload Artifacts(demo-debug) - name: Upload Artifacts (Module-Release)
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
path: ${{ env.DEMO_DEBUG_APK_FILE }} path: ${{ env.MODULE_RELEASE_APK_PATH }}
name: demo-debug name: AppErrorsTracking-module-release-${{ github.event.head_commit.id }}
- name: Upload Artifacts(module-release) - name: Upload Artifacts (Demo-Debug)
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
path: ${{ env.MODULE_RELEASE_APK_FILE }} path: ${{ env.DEMO_DEBUG_APK_PATH }}
name: module-release name: AppErrorsTracking-demo-debug-${{ github.event.head_commit.id }}
- name: Upload Artifacts(demo-release) - name: Upload Artifacts (Demo-Release)
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
path: ${{ env.DEMO_RELEASE_APK_FILE }} path: ${{ env.DEMO_RELEASE_APK_PATH }}
name: demo-release name: AppErrorsTracking-demo-release-${{ github.event.head_commit.id }}
- name: Post Artifacts to Telegram
run: |
export module_debug=$(find ${{ env.MODULE_APK_OUTPUT_PATH }}/debug -name "*.apk")
export module_release=$(find ${{ env.MODULE_APK_OUTPUT_PATH }}/release -name "*.apk")
export demo_debug=$(find ${{ env.DEMO_APK_OUTPUT_PATH }}/debug -name "*.apk")
export demo_release=$(find ${{ env.DEMO_APK_OUTPUT_PATH }}/release -name "*.apk")
ESCAPED=`python3 -c 'import json,os,urllib.parse; msg = json.dumps(os.environ["COMMIT_MESSAGE"]); print(urllib.parse.quote(msg if len(msg) <= 1024 else json.dumps(os.environ["COMMIT_URL"])))'`
curl -v "https://api.telegram.org/bot${TG_BOT_TOKEN}/sendMediaGroup?chat_id=${TG_CHAT_ID}&media=%5B%7B%22type%22%3A%22document%22%2C%20%22media%22%3A%22attach%3A%2F%2Fmodule_debug%22%7D%2C%7B%22type%22%3A%22document%22%2C%20%22media%22%3A%22attach%3A%2F%2Fmodule_release%22%7D%2C%7B%22type%22%3A%22document%22%2C%20%22media%22%3A%22attach%3A%2F%2Fdemo_debug%22%7D%2C%7B%22type%22%3A%22document%22%2C%20%22media%22%3A%22attach%3A%2F%2Fdemo_release%22%2C%22parse_mode%22%3A%22MarkdownV2%22%2C%22caption%22:${ESCAPED}%7D%5D" \
-F module_debug="@$module_debug" \
-F module_release="@$module_release" \
-F demo_debug="@$demo_debug" \
-F demo_release="@$demo_release"

View File

@@ -11,18 +11,27 @@ on:
jobs: jobs:
build: build:
name: Pull request check name: Pull Request Check
if: ${{ success() }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
env:
MODULE_APK_OUTPUT_PATH: 'module-app/build/outputs/apk'
DEMO_APK_OUTPUT_PATH: 'demo-app/build/outputs/apk'
steps: steps:
- uses: actions/checkout@v3 - 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 - name: Setup cmake
uses: jwlawson/actions-setup-cmake@v1 uses: jwlawson/actions-setup-cmake@v1
with: with:
cmake-version: '3.22.1' cmake-version: '3.22.1'
- name: Prepare Java 11 - name: Prepare Java 17
uses: actions/setup-java@v3 uses: actions/setup-java@v3
with: with:
java-version: 11 java-version: 17
java-package: jdk java-package: jdk
distribution: 'temurin' distribution: 'temurin'
cache: 'gradle' cache: 'gradle'
@@ -33,7 +42,7 @@ jobs:
~/.gradle/caches ~/.gradle/caches
~/.gradle/wrapper ~/.gradle/wrapper
!~/.gradle/caches/build-cache-* !~/.gradle/caches/build-cache-*
key: gradle-deps-core-${{ hashFiles('**/build.gradle') }} key: gradle-deps-core-${{ hashFiles('**/build.gradle.kts') }}
restore-keys: | restore-keys: |
gradle-deps gradle-deps
- name: Cache Gradle Build - name: Cache Gradle Build
@@ -46,31 +55,31 @@ jobs:
gradle-builds gradle-builds
- name: Build with Gradle - name: Build with Gradle
run: | run: |
./gradlew :app:assembleDebug ./gradlew :module-app:assembleDebug
./gradlew :app:assembleRelease ./gradlew :module-app:assembleRelease
./gradlew :demo-app:assembleDebug ./gradlew :demo-app:assembleDebug
./gradlew :demo-app:assembleRelease ./gradlew :demo-app:assembleRelease
echo "MODULE_DEBUG_APK_FILE=$(find app/build/outputs/apk/debug -name '*.apk')" >> $GITHUB_ENV echo "MODULE_DEBUG_APK_PATH=$(find ${{ env.MODULE_APK_OUTPUT_PATH }}/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_PATH=$(find ${{ env.MODULE_APK_OUTPUT_PATH }}/release -name '*.apk')" >> $GITHUB_ENV
echo "MODULE_RELEASE_APK_FILE=$(find app/build/outputs/apk/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_FILE=$(find demo-app/build/outputs/apk/release -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) - name: Upload Artifacts (Module-Debug)
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
path: ${{ env.MODULE_DEBUG_APK_FILE }} path: ${{ env.MODULE_DEBUG_APK_PATH }}
name: module-debug name: AppErrorsTracking-module-debug-${{ github.event.head_commit.id }}
- name: Upload Artifacts(demo-debug) - name: Upload Artifacts (Module-Release)
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
path: ${{ env.DEMO_DEBUG_APK_FILE }} path: ${{ env.MODULE_RELEASE_APK_PATH }}
name: demo-debug name: AppErrorsTracking-module-release-${{ github.event.head_commit.id }}
- name: Upload Artifacts(module-release) - name: Upload Artifacts (Demo-Debug)
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
path: ${{ env.MODULE_RELEASE_APK_FILE }} path: ${{ env.DEMO_DEBUG_APK_PATH }}
name: module-release name: AppErrorsTracking-demo-debug-${{ github.event.head_commit.id }}
- name: Upload Artifacts(demo-release) - name: Upload Artifacts (Demo-Release)
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
path: ${{ env.DEMO_RELEASE_APK_FILE }} path: ${{ env.DEMO_RELEASE_APK_PATH }}
name: demo-release name: AppErrorsTracking-demo-release-${{ github.event.head_commit.id }}

1
.gitignore vendored
View File

@@ -1,7 +1,6 @@
# Project exclude paths # Project exclude paths
*.iml *.iml
.gradle .gradle
.secret/APP_CENTER_SECRET
/local.properties /local.properties
/.idea/caches /.idea/caches
/.idea/libraries /.idea/libraries

4
.idea/.gitignore generated vendored
View File

@@ -1,3 +1,5 @@
# Default ignored files # Default ignored files
/shelf/ /shelf/
/workspace.xml /workspace.xml
/gradle.xml
/misc.xml

26
.idea/appInsightsSettings.xml generated Normal file
View 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
View File

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

View File

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

21
.idea/gradle.xml generated
View File

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

View File

@@ -6,5 +6,6 @@
<option name="processLiterals" value="true" /> <option name="processLiterals" value="true" />
<option name="processComments" value="true" /> <option name="processComments" value="true" />
</inspection_tool> </inspection_tool>
<inspection_tool class="YAMLSchemaValidation" enabled="false" level="WARNING" enabled_by_default="false" />
</profile> </profile>
</component> </component>

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

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

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

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

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

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

48
.idea/misc.xml generated
View File

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

1
.secret/.gitignore vendored Normal file
View File

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

View File

@@ -1,6 +0,0 @@
{
"keyAlias": "public",
"keyPassword": "123456",
"storeFileName": "universal.p12",
"storePassword": "123456"
}

View File

@@ -1,11 +1,18 @@
# AppErrorsTracking # AppErrorsTracking
[![Blank](https://img.shields.io/badge/build-passing-brightgreen)](https://github.com/KitsunePie/AppErrorsTracking) [![GitHub license](https://img.shields.io/github/license/KitsunePie/AppErrorsTracking?color=blue)](https://github.com/KitsunePie/AppErrorsTracking/blob/master/LICENSE)
[![Blank](https://img.shields.io/badge/license-AGPL3.0-blue)](https://github.com/KitsunePie/AppErrorsTracking/blob/master/LICENSE) [![GitHub CI](https://img.shields.io/github/actions/workflow/status/KitsunePie/AppErrorsTracking/commit_ci.yml?label=CI%20builds)](https://github.com/KitsunePie/AppErrorsTracking/actions/workflows/commit_ci.yml)
[![Blank](https://img.shields.io/badge/version-v1.25-green)](https://github.com/KitsunePie/AppErrorsTracking/releases) [![GitHub release](https://img.shields.io/github/v/release/KitsunePie/AppErrorsTracking?display_name=release&logo=github&color=green)](https://github.com/KitsunePie/AppErrorsTracking/releases)
[![Blank](https://img.shields.io/github/downloads/KitsunePie/AppErrorsTracking/total?label=Release)](https://github.com/KitsunePie/AppErrorsTracking/releases) ![GitHub all releases](https://img.shields.io/github/downloads/KitsunePie/AppErrorsTracking/total?label=downloads)
[![Blank](https://img.shields.io/github/downloads/Xposed-Modules-Repo/com.fankes.apperrorstracking/total?label=LSPosed%20Repo&logo=Android&style=flat&labelColor=F48FB1&logoColor=ffffff)](https://github.com/Xposed-Modules-Repo/com.fankes.apperrorstracking/releases) ![GitHub all releases](https://img.shields.io/github/downloads/Xposed-Modules-Repo/com.fankes.apperrorstracking/total?label=LSPosed%20downloads&labelColor=F48FB1)
<br/><br/>
[![Telegram CI](https://img.shields.io/badge/CI%20builds-Telegram-blue.svg?logo=telegram)](https://t.me/AppErrorsTracking_CI)
[![Telegram](https://img.shields.io/badge/discussion-Telegram-blue.svg?logo=telegram)](https://t.me/XiaofangInternet)
[![QQ](https://img.shields.io/badge/discussion-QQ-blue.svg?logo=tencent-qq&logoColor=red)](https://qm.qq.com/cgi-bin/qm/qr?k=dp2h5YhWiga9WWb_Oh7kSHmx01X8I8ii&jump_from=webapi&authKey=Za5CaFP0lk7+Zgsk2KpoBD7sSaYbeXbsDgFjiWelOeH4VSionpxFJ7V0qQBSqvFM)
[![QQ 频道](https://img.shields.io/badge/discussion-QQ%20频道-blue.svg?logo=tencent-qq&logoColor=red)](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) | 简体中文 [English](https://github.com/KitsunePie/AppErrorsTracking/blob/master/README.md) | 简体中文
为原生 FC 对话框增加更多功能并修复国内定制 ROM 删除 FC 对话框的问题,给 Android 开发者带来更好的体验。 为原生 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) | <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) | 正式版 (稳定版) |
- [Xposed-Modules-Repo](https://github.com/Xposed-Modules-Repo/com.fankes.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) | 正式版 (稳定版) |
|------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------|-----------|
## 发行状态说明 本模块发布地址仅限于上述所列出的地址,从其他非正规渠道下载到的版本或对您造成任何影响均与我们无关。
![Blank](https://img.shields.io/badge/build-passing-brightgreen) ## 项目推广
上述状态为当前稳定版与自动构建版本一致或当前代码改动与稳定版无功能差异 如果你正在寻找一个可以自动管理 Gradle 项目依赖的 Gradle 插件,你可以了解一下 [SweetDependency](https://github.com/HighCapable/SweetDependency) 项目
![Blank](https://img.shields.io/badge/build-pending-dbab09) 如果你正在寻找一个可以自动生成属性键值的 Gradle 插件,你可以了解一下 [SweetProperty](https://github.com/HighCapable/SweetProperty) 项目。
上述状态为存在自动构建版本和新功能的更新但当前并未发布稳定版,处于预发行状态 本项目同样使用了 **SweetDependency****SweetProperty**
![Blank](https://img.shields.io/badge/build-problem-red) ## 捐赠支持
上述状态为当前发行的稳定版可能存在严重问题但并未及时进行修复且并未发布稳定版 工作不易,无意外情况此项目将继续维护下去,提供更多可能,欢迎打赏
<img src="https://github.com/fankes/fankes/blob/main/img-src/payment_code.jpg?raw=true" width = "500" alt="Payment Code"/>
## Star History ## Star History
@@ -102,9 +114,9 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details. GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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) 版权所有 © 2017-2023 Fankes Studio(qzmmcn@163.com)

View File

@@ -1,21 +1,28 @@
# AppErrorsTracking # AppErrorsTracking
[![Blank](https://img.shields.io/badge/build-passing-brightgreen)](https://github.com/KitsunePie/AppErrorsTracking) [![GitHub license](https://img.shields.io/github/license/KitsunePie/AppErrorsTracking?color=blue)](https://github.com/KitsunePie/AppErrorsTracking/blob/master/LICENSE)
[![Blank](https://img.shields.io/badge/license-AGPL3.0-blue)](https://github.com/KitsunePie/AppErrorsTracking/blob/master/LICENSE) [![GitHub CI](https://img.shields.io/github/actions/workflow/status/KitsunePie/AppErrorsTracking/commit_ci.yml?label=CI%20builds)](https://github.com/KitsunePie/AppErrorsTracking/actions/workflows/commit_ci.yml)
[![Blank](https://img.shields.io/badge/version-v1.25-green)](https://github.com/KitsunePie/AppErrorsTracking/releases) [![GitHub release](https://img.shields.io/github/v/release/KitsunePie/AppErrorsTracking?display_name=release&logo=github&color=green)](https://github.com/KitsunePie/AppErrorsTracking/releases)
[![Blank](https://img.shields.io/github/downloads/KitsunePie/AppErrorsTracking/total?label=Release)](https://github.com/KitsunePie/AppErrorsTracking/releases) ![GitHub all releases](https://img.shields.io/github/downloads/KitsunePie/AppErrorsTracking/total?label=downloads)
[![Blank](https://img.shields.io/github/downloads/Xposed-Modules-Repo/com.fankes.apperrorstracking/total?label=LSPosed%20Repo&logo=Android&style=flat&labelColor=F48FB1&logoColor=ffffff)](https://github.com/Xposed-Modules-Repo/com.fankes.apperrorstracking/releases) ![GitHub all releases](https://img.shields.io/github/downloads/Xposed-Modules-Repo/com.fankes.apperrorstracking/total?label=LSPosed%20downloads&labelColor=F48FB1)
<br/><br/>
[![Telegram CI](https://img.shields.io/badge/CI%20builds-Telegram-blue.svg?logo=telegram)](https://t.me/AppErrorsTracking_CI)
[![Telegram](https://img.shields.io/badge/discussion-Telegram-blue.svg?logo=telegram)](https://t.me/XiaofangInternet)
[![QQ](https://img.shields.io/badge/discussion-QQ-blue.svg?logo=tencent-qq&logoColor=red)](https://qm.qq.com/cgi-bin/qm/qr?k=dp2h5YhWiga9WWb_Oh7kSHmx01X8I8ii&jump_from=webapi&authKey=Za5CaFP0lk7+Zgsk2KpoBD7sSaYbeXbsDgFjiWelOeH4VSionpxFJ7V0qQBSqvFM)
[![QQ 频道](https://img.shields.io/badge/discussion-QQ%20频道-blue.svg?logo=tencent-qq&logoColor=red)](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) 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. 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 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 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. 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 ## 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 - [x] Errors history record function,
module which can be entered through the notification bar tile "errors history record" and the main interface of the module
- Apps errors statistics function - [x] Apps errors statistics function
- Errors display function for multi-process apps - [x] Errors display function for multi-process apps
## Translation Contribution ## Translation Contribution
Contributions to this project are welcome to translate it into your country's language. 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 | <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) |
**for testing only**, and there is no special explanation or even the version may change or remain the same as the current stable version. |------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------|---------------------------------|
- [Release](https://github.com/KitsunePie/AppErrorsTracking/releases) The releases of this Xposed Module is limited to the urls listed above.
- [Xposed-Modules-Repo](https://github.com/Xposed-Modules-Repo/com.fankes.apperrorstracking/releases)
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.
![Blank](https://img.shields.io/badge/build-passing-brightgreen) This project also uses **SweetDependency** and **SweetProperty**.
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.
![Blank](https://img.shields.io/badge/build-pending-dbab09)
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.
![Blank](https://img.shields.io/badge/build-problem-red)
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.
## Star History ## Star History
@@ -121,9 +119,9 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details. GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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) Copyright © 2017-2023 Fankes Studio(qzmmcn@163.com)

View File

@@ -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'
}

View File

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

View File

@@ -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
View 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
}

View File

@@ -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
View 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)
}

View File

@@ -1,14 +1,24 @@
<?xml version="1.0" encoding="utf-8"?> <?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 <application
android:name=".application.DemoApplication" android:name=".application.DemoApplication"
android:allowBackup="true" android:allowBackup="true"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:localeConfig="@xml/locales_config"
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/Theme.AppErrorsDemo"> android:theme="@style/Theme.AppErrorsDemo"
tools:targetApi="tiramisu">
<activity <activity
android:name=".ui.activity.MainActivity" android:name=".ui.activity.MainActivity"

View File

@@ -17,7 +17,7 @@
* and eula along with this software. If not, see * and eula along with this software. If not, see
* <https://www.gnu.org/licenses/> * <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 package com.fankes.apperrorsdemo.application

View File

@@ -17,7 +17,7 @@
* and eula along with this software. If not, see * and eula along with this software. If not, see
* <https://www.gnu.org/licenses/> * <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 package com.fankes.apperrorsdemo.native

View File

@@ -17,21 +17,27 @@
* and eula along with this software. If not, see * and eula along with this software. If not, see
* <https://www.gnu.org/licenses/> * <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 package com.fankes.apperrorsdemo.ui.activity
import android.content.Intent import android.content.Intent
import android.os.SystemClock import android.os.SystemClock
import com.fankes.apperrorsdemo.R
import com.fankes.apperrorsdemo.databinding.ActivityMainBinding import com.fankes.apperrorsdemo.databinding.ActivityMainBinding
import com.fankes.apperrorsdemo.databinding.ActivityMultiProcessBinding import com.fankes.apperrorsdemo.databinding.ActivityMultiProcessBinding
import com.fankes.apperrorsdemo.generated.DemoAppProperties
import com.fankes.apperrorsdemo.native.Channel import com.fankes.apperrorsdemo.native.Channel
import com.fankes.apperrorsdemo.ui.activity.base.BaseActivity import com.fankes.apperrorsdemo.ui.activity.base.BaseActivity
class MainActivity : BaseActivity<ActivityMainBinding>() { class MainActivity : BaseActivity<ActivityMainBinding>() {
override fun onCreate() { 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.throwRuntimeButton.setOnClickListener { Channel.throwRuntimeException() }
binding.throwIllegalStateButton.setOnClickListener { Channel.throwIllegalStateException() } binding.throwIllegalStateButton.setOnClickListener { Channel.throwIllegalStateException() }
binding.throwNullPointerButton.setOnClickListener { Channel.throwNullPointerException() } binding.throwNullPointerButton.setOnClickListener { Channel.throwNullPointerException() }

View File

@@ -17,10 +17,8 @@
* and eula along with this software. If not, see * and eula along with this software. If not, see
* <https://www.gnu.org/licenses/> * <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 package com.fankes.apperrorsdemo.ui.activity.base
import android.os.Build import android.os.Build

View File

@@ -17,7 +17,7 @@
* and eula along with this software. If not, see * and eula along with this software. If not, see
* <https://www.gnu.org/licenses/> * <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 package com.fankes.apperrorsdemo.utils.factory

View File

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

View File

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

View File

@@ -1,25 +1,27 @@
# Project-wide Gradle settings. # Compiler Configuration
# IDE (e.g. Android Studio) users: org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
# 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
android.useAndroidX=true 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 android.nonTransitiveRClass=true
# Incremental kotlin.code.style=official
kotlin.incremental.useClasspathSnapshot=true 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}

View 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

View File

@@ -1,6 +1,5 @@
#Wed May 04 08:35:13 CST 2022
distributionBase=GRADLE_USER_HOME 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 distributionPath=wrapper/dists
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME

View File

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View 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)
}

View File

@@ -32,31 +32,36 @@
-adaptresourcefilecontents -adaptresourcefilecontents
-renamesourcefileattribute P -renamesourcefileattribute P
-keepattributes SourceFile,LineNumberTable -keepattributes SourceFile,Signature,LineNumberTable
## ---------------Begin: proguard configuration for Gson ---------- ## ---------------Begin: proguard configuration for Gson ----------
# Gson uses generic type information stored in a class file when working with fields. Proguard # 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. # 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 # Gson specific classes
-dontwarn sun.misc** -dontwarn sun.misc.**
-keep class com.google.gson.stream**{*;} -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
# Prevent R8 from leaving Data object members always null # Prevent R8 from leaving Data object members always null
-keepclassmembers,allowobfuscation class * { -keepclassmembers,allowobfuscation class * {
@com.google.gson.annotations.SerializedName <fields>; @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 ---------- ## ---------------End: proguard configuration for Gson ----------
-assumenosideeffects class kotlin.jvm.internal.Intrinsics { -assumenosideeffects class kotlin.jvm.internal.Intrinsics {
@@ -64,4 +69,8 @@
public static *** throwUninitializedPropertyAccessException(...); public static *** throwUninitializedPropertyAccessException(...);
} }
-keep class com.fankes.apperrorstracking.databinding**{*;} -keep class * extends android.app.Activity
-keep class * implements androidx.viewbinding.ViewBinding {
<init>();
*** inflate(android.view.LayoutInflater);
}

View File

@@ -10,14 +10,23 @@
android:name="android.permission.INTERACT_ACROSS_USERS" android:name="android.permission.INTERACT_ACROSS_USERS"
tools:ignore="ProtectedPermissions" /> 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 <application
android:name=".application.AppErrorsApplication" android:name=".application.AppErrorsApplication"
android:allowBackup="true" android:allowBackup="true"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:localeConfig="@xml/locales_config"
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/Theme.AppErrorsTracking"> android:theme="@style/Theme.AppErrorsTracking"
tools:targetApi="tiramisu">
<meta-data <meta-data
android:name="xposedmodule" android:name="xposedmodule"

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -17,13 +17,14 @@
* and eula along with this software. If not, see * and eula along with this software. If not, see
* <https://www.gnu.org/licenses/> * <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 package com.fankes.apperrorstracking.application
import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.app.AppCompatDelegate
import com.fankes.apperrorstracking.data.ConfigData 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.fankes.apperrorstracking.utils.tool.AppAnalyticsTool
import com.highcapable.yukihookapi.hook.xposed.application.ModuleApplication import com.highcapable.yukihookapi.hook.xposed.application.ModuleApplication
@@ -31,13 +32,13 @@ class AppErrorsApplication : ModuleApplication() {
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
/** 绑定 I18n */
locale = ModuleAppLocale.attach(this)
/** 跟随系统夜间模式 */ /** 跟随系统夜间模式 */
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM) AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
/** 绑定 I18n */
LocaleString.bind(instance = this)
/** 装载存储控制类 */ /** 装载存储控制类 */
ConfigData.init(instance = this) ConfigData.init(this)
/** 装载 App Center */ /** 装载 App Center */
AppAnalyticsTool.init(instance = this) AppAnalyticsTool.init(this)
} }
} }

View File

@@ -17,7 +17,7 @@
* and eula along with this software. If not, see * and eula along with this software. If not, see
* <https://www.gnu.org/licenses/> * <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 package com.fankes.apperrorstracking.bean

View File

@@ -17,19 +17,27 @@
* and eula along with this software. If not, see * and eula along with this software. If not, see
* <https://www.gnu.org/licenses/> * <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 package com.fankes.apperrorstracking.bean
import android.app.ApplicationErrorReport import android.app.ApplicationErrorReport
import android.content.Context import android.content.Context
import android.os.Build import android.os.Build
import com.fankes.apperrorstracking.locale.LocaleString import com.fankes.apperrorstracking.const.ModuleVersion
import com.fankes.apperrorstracking.utils.factory.* 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 com.google.gson.annotations.SerializedName
import java.io.Serializable import java.io.Serializable
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.Date
import java.util.Locale
/** /**
* 应用异常信息 bean * 应用异常信息 bean
@@ -39,6 +47,8 @@ import java.util.*
* @param packageName 包名 * @param packageName 包名
* @param versionName 版本名称 * @param versionName 版本名称
* @param versionCode 版本号 * @param versionCode 版本号
* @param targetSdk 目标 SDK 版本
* @param minSdk 最低 SDK 版本
* @param isNativeCrash 是否为原生层异常 * @param isNativeCrash 是否为原生层异常
* @param exceptionClassName 异常类名 * @param exceptionClassName 异常类名
* @param exceptionMessage 异常信息 * @param exceptionMessage 异常信息
@@ -62,6 +72,10 @@ data class AppErrorsInfoBean(
var versionName: String = "", var versionName: String = "",
@SerializedName("versionCode") @SerializedName("versionCode")
var versionCode: Long = -1L, var versionCode: Long = -1L,
@SerializedName("targetSdk")
var targetSdk: Int = -1,
@SerializedName("minSdk")
var minSdk: Int = -1,
@SerializedName("isNativeCrash") @SerializedName("isNativeCrash")
var isNativeCrash: Boolean = false, var isNativeCrash: Boolean = false,
@SerializedName("exceptionClassName") @SerializedName("exceptionClassName")
@@ -102,6 +116,8 @@ data class AppErrorsInfoBean(
packageName = packageName ?: "unknown", packageName = packageName ?: "unknown",
versionName = packageName?.let { context.appVersionNameOf(it).ifBlank { "unknown" } } ?: "", versionName = packageName?.let { context.appVersionNameOf(it).ifBlank { "unknown" } } ?: "",
versionCode = packageName?.let { context.appVersionCodeOf(it) } ?: -1L, versionCode = packageName?.let { context.appVersionCodeOf(it) } ?: -1L,
targetSdk = packageName?.let { context.appTargetSdkOf(it) } ?: -1,
minSdk = packageName?.let { context.appMinSdkOf(it) } ?: -1,
isNativeCrash = isNativeCrash, isNativeCrash = isNativeCrash,
exceptionClassName = crashInfo?.exceptionClassName ?: "unknown", exceptionClassName = crashInfo?.exceptionClassName ?: "unknown",
exceptionMessage = if (isNativeCrash) crashInfo?.stackTrace.let { exceptionMessage = if (isNativeCrash) crashInfo?.stackTrace.let {
@@ -130,7 +146,7 @@ data class AppErrorsInfoBean(
* 获取生成的 Json 文件名 * 获取生成的 Json 文件名
* @return [String] * @return [String]
*/ */
val jsonFileName get() = "${packageName}_${pid}_${timestamp}.json" val jsonFileName get() = "${packageName}_${pid}_$timestamp.json"
/** /**
* 获取 APP 版本信息与版本号 * 获取 APP 版本信息与版本号
@@ -150,13 +166,13 @@ data class AppErrorsInfoBean(
*/ */
val crossTime val crossTime
get() = timestamp.difference( get() = timestamp.difference(
now = LocaleString.momentAgo, now = locale.momentAgo,
second = LocaleString.secondAgo, second = locale.secondAgo,
minute = LocaleString.minuteAgo, minute = locale.minuteAgo,
hour = LocaleString.hourAgo, hour = locale.hourAgo,
day = LocaleString.dayAgo, day = locale.dayAgo,
month = LocaleString.monthAgo, month = locale.monthAgo,
year = LocaleString.yearAgo year = locale.yearAgo
) )
/** /**
@@ -167,42 +183,80 @@ data class AppErrorsInfoBean(
/** /**
* 获取异常堆栈分享模板 * 获取异常堆栈分享模板
* @param sDeviceBrand
* @param sDeviceModel
* @param sDisplay
* @param sPackageName
* @return [String] * @return [String]
*/ */
val stackOutputShareContent fun stackOutputShareContent(
get() = "Generated by AppErrorsTracking\n" + sDeviceBrand: Boolean = true,
"Project Url: https://github.com/KitsunePie/AppErrorsTracking\n" + sDeviceModel: Boolean = true,
"===============\n$environmentInfo" 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] * @return [String]
*/ */
val stackOutputFileContent fun stackOutputFileContent(
get() = "================================================================\n" + sDeviceBrand: Boolean = true,
" Generated by AppErrorsTracking\n" + sDeviceModel: Boolean = true,
" Project Url: https://github.com/KitsunePie/AppErrorsTracking\n" + sDisplay: Boolean = true,
"================================================================\n" + sPackageName: Boolean = true
environmentInfo ) = """
================================================================
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] * @return [String]
*/ */
private val environmentInfo private fun environmentInfo(
get() = "[Device Brand]: ${Build.BRAND}\n" + sDeviceBrand: Boolean,
"[Device Model]: ${Build.MODEL}\n" + sDeviceModel: Boolean,
"[Display]: ${Build.DISPLAY}\n" + sDisplay: Boolean,
"[Android Version]: ${Build.VERSION.RELEASE}\n" + sPackageName: Boolean
"[Android API Level]: ${Build.VERSION.SDK_INT}\n" + ) = """
"[System Locale]: ${Locale.getDefault()}\n" + [Device Brand]: ${Build.BRAND.by(sDeviceBrand)}
"[Process ID]: $pid\n" + [Device Model]: ${Build.MODEL.by(sDeviceModel)}
(if (userId > 0) "[User Id]: $userId\n" else "") + [Display]: ${Build.DISPLAY.by(sDisplay)}
"[CPU ABI]: ${cpuAbi.ifBlank { "none" }}\n" + [Android Version]: ${Build.VERSION.RELEASE}
"[Package Name]: $packageName\n" + [Android API Level]: ${Build.VERSION.SDK_INT}
"[Version Name]: ${versionName.ifBlank { "unknown" }}\n" + [System Locale]: ${Locale.getDefault()}
"[Version Code]: ${versionCode.takeIf { it != -1L } ?: "unknown"}\n" + [Process ID]: $pid
"[Error Type]: ${if (isNativeCrash) "Native" else "JVM"}\n" + [User ID]: $userId
"[Crash Time]: $utcTime\n" + [CPU ABI]: ${cpuAbi.ifBlank { "none" }}
"[Stack Trace]:\n" + stackTrace [Package Name]: ${packageName.by(sPackageName)}
[Version Name]: ${versionName.ifBlank { "unknown" }}
[Version Code]: ${versionCode.takeIf { it != -1L } ?: "unknown"}
[Target SDK]: ${targetSdk.takeIf { it != -1 } ?: "unknown"}
[Min SDK]: ${minSdk.takeIf { it != -1 } ?: "unknown"}
[Error Type]: ${if (isNativeCrash) "Native" else "JVM"}
[Crash Time]: $utcTime
[Stack Trace]:
""".trimIndent() + "\n$stackTrace"
/**
* 判断字符串是否需要显示
* @param isDisplay 是否需要显示
* @return [String]
*/
private fun String.by(isDisplay: Boolean) = if (isDisplay) this else "***"
} }

View File

@@ -17,7 +17,7 @@
* and eula along with this software. If not, see * and eula along with this software. If not, see
* <https://www.gnu.org/licenses/> * <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 package com.fankes.apperrorstracking.bean

View File

@@ -17,7 +17,7 @@
* and eula along with this software. If not, see * and eula along with this software. If not, see
* <https://www.gnu.org/licenses/> * <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 package com.fankes.apperrorstracking.bean

View File

@@ -17,7 +17,7 @@
* and eula along with this software. If not, see * and eula along with this software. If not, see
* <https://www.gnu.org/licenses/> * <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 package com.fankes.apperrorstracking.bean

View File

@@ -17,7 +17,7 @@
* and eula along with this software. If not, see * and eula along with this software. If not, see
* <https://www.gnu.org/licenses/> * <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 package com.fankes.apperrorstracking.bean.enum

View File

@@ -0,0 +1,59 @@
/*
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
* Copyright (C) 2017-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)"
}

View File

@@ -17,7 +17,7 @@
* and eula along with this software. If not, see * and eula along with this software. If not, see
* <https://www.gnu.org/licenses/> * <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 package com.fankes.apperrorstracking.data
@@ -74,9 +74,9 @@ object AppErrorsConfigData {
if (packageName.isNotBlank()) when (type) { if (packageName.isNotBlank()) when (type) {
AppErrorsConfigType.GLOBAL -> AppErrorsConfigType.GLOBAL ->
showDialogApps.contains(packageName).not() && showDialogApps.contains(packageName).not() &&
showNotifyApps.contains(packageName).not() && showNotifyApps.contains(packageName).not() &&
showToastApps.contains(packageName).not() && showToastApps.contains(packageName).not() &&
showNothingApps.contains(packageName).not() showNothingApps.contains(packageName).not()
AppErrorsConfigType.DIALOG -> showDialogApps.contains(packageName) AppErrorsConfigType.DIALOG -> showDialogApps.contains(packageName)
AppErrorsConfigType.NOTIFY -> showNotifyApps.contains(packageName) AppErrorsConfigType.NOTIFY -> showNotifyApps.contains(packageName)
AppErrorsConfigType.TOAST -> showToastApps.contains(packageName) AppErrorsConfigType.TOAST -> showToastApps.contains(packageName)

View File

@@ -17,7 +17,7 @@
* and eula along with this software. If not, see * and eula along with this software. If not, see
* <https://www.gnu.org/licenses/> * <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") @file:Suppress("StaticFieldLeak")
@@ -26,8 +26,12 @@ package com.fankes.apperrorstracking.data
import android.content.Context import android.content.Context
import android.provider.Settings import android.provider.Settings
import com.fankes.apperrorstracking.bean.AppErrorsInfoBean import com.fankes.apperrorstracking.bean.AppErrorsInfoBean
import com.fankes.apperrorstracking.utils.factory.* import com.fankes.apperrorstracking.utils.factory.appCpuAbiOf
import com.highcapable.yukihookapi.hook.log.loggerE 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.io.File
import java.util.concurrent.CopyOnWriteArrayList import java.util.concurrent.CopyOnWriteArrayList
@@ -72,7 +76,7 @@ object AppErrorsRecordData {
runCatching { runCatching {
errorsInfoDataFolder.also { if (it.exists().not() || it.isFile) it.apply { delete(); mkdirs() } } errorsInfoDataFolder.also { if (it.exists().not() || it.isFile) it.apply { delete(); mkdirs() } }
}.onFailure { }.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.versionCode = it.appVersionCodeOf(e.packageName)
e.toJsonOrNull()?.also { json -> File(errorsInfoDataFolder.absolutePath, e.jsonFileName).writeText(json) } e.toJsonOrNull()?.also { json -> File(errorsInfoDataFolder.absolutePath, e.jsonFileName).writeText(json) }
}.let { result -> }.let { result ->
if (result != null) { if (result != null) {
Settings.Secure.putString(it.contentResolver, keyName, "") Settings.Secure.putString(it.contentResolver, keyName, "")
result result
} else null } else null
} }
}.getOrNull() }.getOrNull()
} }

View File

@@ -17,7 +17,7 @@
* and eula along with this software. If not, see * and eula along with this software. If not, see
* <https://www.gnu.org/licenses/> * <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") @file:Suppress("MemberVisibilityCanBePrivate")
@@ -26,7 +26,7 @@ package com.fankes.apperrorstracking.data
import android.content.Context import android.content.Context
import android.os.Build import android.os.Build
import com.highcapable.yukihookapi.hook.factory.prefs 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.param.PackageParam
import com.highcapable.yukihookapi.hook.xposed.prefs.data.PrefsData import com.highcapable.yukihookapi.hook.xposed.prefs.data.PrefsData
@@ -91,7 +91,7 @@ object ConfigData {
internal fun putStringSet(key: String, value: Set<String>) { internal fun putStringSet(key: String, value: Set<String>) {
when (instance) { when (instance) {
is Context -> (instance as Context).prefs().edit { putStringSet(key, value) } 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") else -> error("Unknown type for put prefs data")
} }
} }
@@ -115,7 +115,7 @@ object ConfigData {
internal fun putInt(data: PrefsData<Int>, value: Int) { internal fun putInt(data: PrefsData<Int>, value: Int) {
when (instance) { when (instance) {
is Context -> (instance as Context).prefs().edit { put(data, value) } 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") else -> error("Unknown type for put prefs data")
} }
} }
@@ -139,7 +139,7 @@ object ConfigData {
internal fun putBoolean(data: PrefsData<Boolean>, value: Boolean) { internal fun putBoolean(data: PrefsData<Boolean>, value: Boolean) {
when (instance) { when (instance) {
is Context -> (instance as Context).prefs().edit { put(data, value) } 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") else -> error("Unknown type for put prefs data")
} }
} }

View File

@@ -17,7 +17,7 @@
* and eula along with this software. If not, see * and eula along with this software. If not, see
* <https://www.gnu.org/licenses/> * <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 package com.fankes.apperrorstracking.data.enum

View File

@@ -17,7 +17,7 @@
* and eula along with this software. If not, see * and eula along with this software. If not, see
* <https://www.gnu.org/licenses/> * <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") @file:Suppress("unused", "MemberVisibilityCanBePrivate")

View File

@@ -17,13 +17,14 @@
* and eula along with this software. If not, see * and eula along with this software. If not, see
* <https://www.gnu.org/licenses/> * <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 package com.fankes.apperrorstracking.hook
import com.fankes.apperrorstracking.data.ConfigData import com.fankes.apperrorstracking.data.ConfigData
import com.fankes.apperrorstracking.generated.locale.ModuleAppLocale
import com.fankes.apperrorstracking.hook.entity.FrameworkHooker 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.annotation.xposed.InjectYukiHookWithXposed
import com.highcapable.yukihookapi.hook.factory.configs import com.highcapable.yukihookapi.hook.factory.configs
import com.highcapable.yukihookapi.hook.factory.encase import com.highcapable.yukihookapi.hook.factory.encase
@@ -38,13 +39,12 @@ object HookEntry : IYukiHookXposedInit {
isRecord = true isRecord = true
} }
isDebug = false isDebug = false
isEnablePrefsBridgeCache = false
} }
override fun onHook() = encase { override fun onHook() = encase {
loadSystem { loadSystem {
LocaleString.bind(instance = this) locale = ModuleAppLocale.attach { moduleAppResources }
ConfigData.init(instance = this) ConfigData.init(this)
loadHooker(FrameworkHooker) loadHooker(FrameworkHooker)
} }
} }

View File

@@ -17,8 +17,10 @@
* and eula along with this software. If not, see * and eula along with this software. If not, see
* <https://www.gnu.org/licenses/> * <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 package com.fankes.apperrorstracking.hook.entity
import android.app.ApplicationErrorReport import android.app.ApplicationErrorReport
@@ -33,7 +35,6 @@ import android.os.SystemClock
import android.util.ArrayMap import android.util.ArrayMap
import androidx.core.graphics.drawable.IconCompat import androidx.core.graphics.drawable.IconCompat
import androidx.core.graphics.drawable.toBitmap import androidx.core.graphics.drawable.toBitmap
import com.fankes.apperrorstracking.BuildConfig
import com.fankes.apperrorstracking.R import com.fankes.apperrorstracking.R
import com.fankes.apperrorstracking.bean.AppErrorsDisplayBean import com.fankes.apperrorstracking.bean.AppErrorsDisplayBean
import com.fankes.apperrorstracking.bean.AppErrorsInfoBean 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.AppErrorsRecordData
import com.fankes.apperrorstracking.data.ConfigData import com.fankes.apperrorstracking.data.ConfigData
import com.fankes.apperrorstracking.data.enum.AppErrorsConfigType 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.AppErrorsDisplayActivity
import com.fankes.apperrorstracking.ui.activity.errors.AppErrorsRecordActivity 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.utils.tool.FrameworkTool
import com.fankes.apperrorstracking.wrapper.BuildConfigWrapper
import com.highcapable.yukihookapi.hook.bean.VariousClass import com.highcapable.yukihookapi.hook.bean.VariousClass
import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker
import com.highcapable.yukihookapi.hook.factory.field import com.highcapable.yukihookapi.hook.factory.field
import com.highcapable.yukihookapi.hook.factory.hasMethod import com.highcapable.yukihookapi.hook.factory.hasMethod
import com.highcapable.yukihookapi.hook.factory.method import com.highcapable.yukihookapi.hook.factory.method
import com.highcapable.yukihookapi.hook.log.loggerE import com.highcapable.yukihookapi.hook.log.YLog
import com.highcapable.yukihookapi.hook.log.loggerI
import com.highcapable.yukihookapi.hook.log.loggerW
import com.highcapable.yukihookapi.hook.type.android.BundleClass import com.highcapable.yukihookapi.hook.type.android.BundleClass
import com.highcapable.yukihookapi.hook.type.android.MessageClass import com.highcapable.yukihookapi.hook.type.android.MessageClass
import com.highcapable.yukihookapi.hook.type.java.BooleanType import com.highcapable.yukihookapi.hook.type.java.BooleanType
object FrameworkHooker : YukiBaseHooker() { object FrameworkHooker : YukiBaseHooker() {
private const val ActivityManagerServiceClass = "com.android.server.am.ActivityManagerService" private val UserControllerClass by lazyClass("com.android.server.am.UserController")
private const val UserControllerClass = "com.android.server.am.UserController" private val AppErrorsClass by lazyClass("com.android.server.am.AppErrors")
private const val AppErrorsClass = "com.android.server.am.AppErrors" private val AppErrorDialogClass by lazyClass("com.android.server.am.AppErrorDialog")
private const val AppErrorDialogClass = "com.android.server.am.AppErrorDialog" private val AppErrorDialog_DataClass by lazyClass("com.android.server.am.AppErrorDialog\$Data")
private const val AppErrorDialog_DataClass = "com.android.server.am.AppErrorDialog\$Data" private val ProcessRecordClass by lazyClass("com.android.server.am.ProcessRecord")
private const val ProcessRecordClass = "com.android.server.am.ProcessRecord" private val ActivityManagerServiceClass by lazyClassOrNull("com.android.server.am.ActivityManagerService")
private const val ActivityTaskManagerService_LocalServiceClass = "com.android.server.wm.ActivityTaskManagerService\$LocalService" private val ActivityTaskManagerService_LocalServiceClass by lazyClassOrNull("com.android.server.wm.ActivityTaskManagerService\$LocalService")
private val PackageListClass = VariousClass( private val PackageListClass by lazyClassOrNull(
"com.android.server.am.ProcessRecord\$PackageList", VariousClass(
"com.android.server.am.PackageList" "com.android.server.am.ProcessRecord\$PackageList",
"com.android.server.am.PackageList"
)
) )
private val ErrorDialogControllerClass = VariousClass( private val ErrorDialogControllerClass by lazyClassOrNull(
"com.android.server.am.ProcessRecord\$ErrorDialogController", VariousClass(
"com.android.server.am.ErrorDialogController" "com.android.server.am.ProcessRecord\$ErrorDialogController",
"com.android.server.am.ErrorDialogController"
)
) )
/** 已忽略错误的 APP 数组 - 直到重新解锁 */ /** 已忽略错误的 APP 数组 - 直到重新解锁 */
private var mutedErrorsIfUnlockApps = HashSet<String>() private var mutedErrorsIfUnlockApps = mutableSetOf<String>()
/** 已忽略错误的 APP 数组 - 直到重新启动 */ /** 已忽略错误的 APP 数组 - 直到重新启动 */
private var mutedErrorsIfRestartApps = HashSet<String>() private var mutedErrorsIfRestartApps = mutableSetOf<String>()
/** /**
* APP 进程异常数据定义类 * APP 进程异常数据定义类
@@ -98,40 +109,40 @@ object FrameworkHooker : YukiBaseHooker() {
* 获取当前包列表实例 * 获取当前包列表实例
* @return [Any] or null * @return [Any] or null
*/ */
private val pkgList = if (ProcessRecordClass.toClass().hasMethod { name = "getPkgList"; emptyParam() }) private val pkgList = if (ProcessRecordClass.hasMethod { name = "getPkgList"; emptyParam() })
ProcessRecordClass.toClass().method { name = "getPkgList"; emptyParam() }.get(proc).call() ProcessRecordClass.method { name = "getPkgList"; emptyParam() }.get(proc).call()
else ProcessRecordClass.toClass().field { name = "pkgList" }.get(proc).any() else ProcessRecordClass.field { name = "pkgList" }.get(proc).any()
/** /**
* 获取当前包列表数组大小 * 获取当前包列表数组大小
* @return [Int] * @return [Int]
*/ */
private val pkgListSize = PackageListClass.toClassOrNull()?.method { name = "size"; emptyParam() }?.get(pkgList)?.int() private val pkgListSize = PackageListClass?.method { name = "size"; emptyParam() }?.get(pkgList)?.int()
?: ProcessRecordClass.toClass().field { name = "pkgList" }.get(proc).cast<ArrayMap<*, *>>()?.size ?: -1 ?: ProcessRecordClass.field { name = "pkgList" }.get(proc).cast<ArrayMap<*, *>>()?.size ?: -1
/** /**
* 获取当前 pid 信息 * 获取当前 pid 信息
* @return [Int] * @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 信息 * 获取当前用户 ID 信息
* @return [Int] * @return [Int]
*/ */
val userId = ProcessRecordClass.toClass().field { name = "userId" }.get(proc).int() val userId = ProcessRecordClass.field { name = "userId" }.get(proc).int()
/** /**
* 获取当前 APP 信息 * 获取当前 APP 信息
* @return [ApplicationInfo] or null * @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] * @return [String]
*/ */
val processName = ProcessRecordClass.toClass().field { name = "processName" }.get(proc).string() val processName = ProcessRecordClass.field { name = "processName" }.get(proc).string()
/** /**
* 获取当前 APP进程 包名 * 获取当前 APP进程 包名
@@ -155,17 +166,17 @@ object FrameworkHooker : YukiBaseHooker() {
* 获取当前进程是否为后台进程 * 获取当前进程是否为后台进程
* @return [Boolean] * @return [Boolean]
*/ */
val isBackgroundProcess = UserControllerClass.toClass() val isBackgroundProcess = UserControllerClass
.method { name { it == "getCurrentProfileIds" || it == "getCurrentProfileIdsLocked" } } .method { name { it == "getCurrentProfileIds" || it == "getCurrentProfileIdsLocked" } }
.get(ActivityManagerServiceClass.toClass().field { name = "mUserController" } .get(ActivityManagerServiceClass?.field { name = "mUserController" }
.get(AppErrorsClass.toClass().field { name = "mService" }.get(errors).any()).any()) ?.get(AppErrorsClass.field { name = "mService" }.get(errors).any())?.any())
.invoke<IntArray>()?.takeIf { it.isNotEmpty() }?.any { it != userId } ?: false .invoke<IntArray>()?.takeIf { it.isNotEmpty() }?.any { it != userId } ?: false
/** /**
* 获取当前进程是否短时内重复崩溃 * 获取当前进程是否短时内重复崩溃
* @return [Boolean] * @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) SystemClock.sleep(100)
/** 刷新存储类 */ /** 刷新存储类 */
AppErrorsConfigData.refresh() 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 { onOpenAppUsedFramework {
appContext?.openApp(it.first, it.second) 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 { onPushAppErrorInfoData {
AppErrorsRecordData.allData.firstOrNull { e -> e.pid == it } ?: run { 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() AppErrorsInfoBean()
} }
} }
onPushAppErrorsInfoData { AppErrorsRecordData.allData.toArrayList() } onPushAppErrorsInfoData { AppErrorsRecordData.allData.toArrayList() }
onRemoveAppErrorsInfoData { 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) AppErrorsRecordData.remove(it)
} }
onClearAppErrorsInfoData { 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() AppErrorsRecordData.clearAll()
} }
onMutedErrorsIfUnlock { onMutedErrorsIfUnlock {
mutedErrorsIfUnlockApps.add(it) mutedErrorsIfUnlockApps.add(it)
loggerI(msg = "Muted \"$it\" until unlocks") YLog.info("Muted \"$it\" until unlocks")
} }
onMutedErrorsIfRestart { onMutedErrorsIfRestart {
mutedErrorsIfRestartApps.add(it) mutedErrorsIfRestartApps.add(it)
loggerI(msg = "Muted \"$it\" until restarts") YLog.info("Muted \"$it\" until restarts")
} }
onPushMutedErrorsAppsData { onPushMutedErrorsAppsData {
arrayListOf<MutedErrorsAppBean>().apply { arrayListOf<MutedErrorsAppBean>().apply {
@@ -224,24 +235,24 @@ object FrameworkHooker : YukiBaseHooker() {
onUnmuteErrorsApp { onUnmuteErrorsApp {
when (it.type) { when (it.type) {
MutedErrorsAppBean.MuteType.UNTIL_UNLOCKS -> { 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) mutedErrorsIfUnlockApps.remove(it.packageName)
} }
MutedErrorsAppBean.MuteType.UNTIL_REBOOTS -> { 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) mutedErrorsIfRestartApps.remove(it.packageName)
} }
} }
} }
onUnmuteAllErrorsApps { 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() mutedErrorsIfUnlockApps.clear()
mutedErrorsIfRestartApps.clear() mutedErrorsIfRestartApps.clear()
} }
onPushAppListData { filters -> onPushAppListData { filters ->
appContext?.let { context -> appContext?.let { context ->
context.listOfPackages() 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 -> .let { info ->
arrayListOf<AppInfoBean>().apply { arrayListOf<AppInfoBean>().apply {
if (info.isNotEmpty()) if (info.isNotEmpty())
@@ -260,7 +271,7 @@ object FrameworkHooker : YukiBaseHooker() {
} }
}.sortedByDescending { it.lastUpdateTime } }.sortedByDescending { it.lastUpdateTime }
.forEach { add(AppInfoBean(name = context.appNameOf(it.packageName), packageName = it.packageName)) } .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() } ?: arrayListOf()
@@ -277,18 +288,18 @@ object FrameworkHooker : YukiBaseHooker() {
val appName = appInfo?.let { context.appNameOf(it.packageName).ifBlank { it.packageName } } ?: packageName val appName = appInfo?.let { context.appNameOf(it.packageName).ifBlank { it.packageName } } ?: packageName
/** 当前 APP 名称 (包含用户 ID) */ /** 当前 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() = fun showAppErrorsWithNotify() =
context.pushNotify( context.pushNotify(
channelId = "APPS_ERRORS", channelId = "APPS_ERRORS",
channelName = LocaleString.appName, channelName = locale.appName,
title = errorTitle, title = errorTitle,
content = LocaleString.appErrorsTip, content = locale.appErrorsTip,
icon = IconCompat.createWithBitmap(moduleAppResources.drawableOf(R.drawable.ic_notify).toBitmap()), icon = IconCompat.createWithBitmap(moduleAppResources.drawableOf(R.drawable.ic_notify).toBitmap()),
color = 0xFFFF6200.toInt(), color = 0xFFFF6200.toInt(),
intent = AppErrorsRecordActivity.intent() intent = AppErrorsRecordActivity.intent()
@@ -309,9 +320,9 @@ object FrameworkHooker : YukiBaseHooker() {
title = errorTitle, title = errorTitle,
isShowAppInfoButton = isActualApp, isShowAppInfoButton = isActualApp,
isShowReopenButton = isActualApp && isShowReopenButton = isActualApp &&
(isRepeatingCrash.not() || ConfigData.isEnableAlwaysShowsReopenAppOptions) && (isRepeatingCrash.not() || ConfigData.isEnableAlwaysShowsReopenAppOptions) &&
context.isAppCanOpened(packageName) && context.isAppCanOpened(packageName) &&
isMainProcess, isMainProcess,
isShowCloseAppButton = isActualApp isShowCloseAppButton = isActualApp
) )
) )
@@ -322,9 +333,9 @@ object FrameworkHooker : YukiBaseHooker() {
/** 判断是否为主进程 */ /** 判断是否为主进程 */
if (isMainProcess.not() && ConfigData.isEnableOnlyShowErrorsInMain) return if (isMainProcess.not() && ConfigData.isEnableOnlyShowErrorsInMain) return
when { when {
packageName == BuildConfig.APPLICATION_ID -> { packageName == BuildConfigWrapper.APPLICATION_ID -> {
context.toast(msg = "AppErrorsTracking has crashed, please see the log in console") 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 { ConfigData.isEnableAppConfigTemplate -> when {
AppErrorsConfigData.isAppShowingType(AppErrorsConfigType.GLOBAL, packageName) -> when { AppErrorsConfigData.isAppShowingType(AppErrorsConfigType.GLOBAL, packageName) -> when {
@@ -341,11 +352,11 @@ object FrameworkHooker : YukiBaseHooker() {
else -> showAppErrorsWithDialog() else -> showAppErrorsWithDialog()
} }
/** 打印错误日志 */ /** 打印错误日志 */
if (isActualApp) loggerE( if (isActualApp) YLog.error(
msg = "Application \"$packageName\" ${if (isRepeatingCrash) "keeps stopping" else "has stopped"}" + msg = "Application \"$packageName\" ${if (isRepeatingCrash) "keeps stopping" else "has stopped"}" +
(if (packageName != processName) " --process \"$processName\"" else "") + (if (packageName != processName) " --process \"$processName\"" else "") +
"${if (userId != 0) " --user $userId" else ""} --pid $pid" "${if (userId != 0) " --user $userId" else ""} --pid $pid"
) else loggerE(msg = "Process \"$processName\" ${if (isRepeatingCrash) "keeps stopping" else "has stopped"} --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?) { private fun AppErrorsProcessData.handleAppErrorsInfo(context: Context, info: ApplicationErrorReport.CrashInfo?) {
AppErrorsRecordData.add(AppErrorsInfoBean.clone(context, pid, userId, appInfo?.packageName, info)) 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() { override fun onHook() {
/** 注册生命周期 */ /** 注册生命周期 */
registerLifecycle() registerLifecycle()
/** 干掉原生错误对话框 - 如果有 */ /** 干掉原生错误对话框 - 如果有 */
ErrorDialogControllerClass.hook { ErrorDialogControllerClass?.apply {
injectMember { method {
method { name = "hasCrashDialogs"
name = "hasCrashDialogs" emptyParam()
emptyParam() }.hook().replaceToTrue()
} method {
replaceToTrue() name = "showCrashDialogs"
} paramCount = 1
injectMember { }.hook().intercept()
method { }
name = "showCrashDialogs"
paramCount = 1
}
intercept()
}
}.ignoredHookClassNotFoundFailure()
/** 干掉原生错误对话框 - API 30 以下 */ /** 干掉原生错误对话框 - API 30 以下 */
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) { if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) {
ActivityTaskManagerService_LocalServiceClass.hook { ActivityTaskManagerService_LocalServiceClass?.method {
injectMember { name = "canShowErrorDialogs"
method { emptyParam()
name = "canShowErrorDialogs" }?.ignored()?.hook()?.replaceToFalse()
emptyParam() ActivityManagerServiceClass?.method {
} name = "canShowErrorDialogs"
replaceToFalse() emptyParam()
}.ignoredNoSuchMemberFailure() }?.ignored()?.hook()?.replaceToFalse()
}.ignoredHookClassNotFoundFailure()
ActivityManagerServiceClass.hook {
injectMember {
method {
name = "canShowErrorDialogs"
emptyParam()
}
replaceToFalse()
}.ignoredNoSuchMemberFailure()
}.ignoredHookClassNotFoundFailure()
} }
/** 干掉原生错误对话框 - 如果上述方法全部失效则直接结束对话框 */ /** 干掉原生错误对话框 - 如果上述方法全部失效则直接结束对话框 */
AppErrorDialogClass.hook { AppErrorDialogClass.apply {
injectMember { method {
method { name = "onCreate"
name = "onCreate" param(BundleClass)
param(BundleClass) }.ignored().hook().after { instance<Dialog>().cancel() }
} method {
afterHook { instance<Dialog>().cancel() } name = "onStart"
}.ignoredNoSuchMemberFailure() emptyParam()
injectMember { }.ignored().hook().after { instance<Dialog>().cancel() }
method {
name = "onStart"
emptyParam()
}
afterHook { instance<Dialog>().cancel() }
}.ignoredNoSuchMemberFailure()
} }
/** 注入自定义错误对话框 */ /** 注入自定义错误对话框 */
AppErrorsClass.hook { AppErrorsClass.apply {
injectMember { when {
method { Build.VERSION.SDK_INT > Build.VERSION_CODES.R -> {
name = "handleShowAppErrorUi" method {
param(MessageClass) 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 { else -> {
/** 当前实例 */ method {
val context = appContext ?: field { name = "mContext" }.get(instance).cast<Context>() ?: return@afterHook 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() val proc = AppErrorDialog_DataClass.field { name = "proc" }.get(resultData).any()
/** 创建 APP 进程异常数据类 */ /** 创建 APP 进程异常数据类 */
AppErrorsProcessData(instance, proc, resultData).handleShowAppErrorUi(context) AppErrorsProcessData(instance, proc, resultData).handleShowAppErrorUi(context)
}
} }
} }
injectMember { method {
method { name = "handleAppCrashInActivityController"
name = "handleAppCrashInActivityController" returnType = BooleanType
returnType = BooleanType }.hook().after {
} /** 当前实例 */
afterHook { val context = appContext ?: field { name = "mContext" }.get(instance).cast<Context>() ?: return@after
/** 当前实例 */
val context = appContext ?: field { name = "mContext" }.get(instance).cast<Context>() ?: return@afterHook
/** 当前进程信息 */ /** 当前进程信息 */
val proc = args().first().any() ?: return@afterHook loggerW(msg = "Received but got null ProcessRecord") val proc = args().first().any() ?: return@after YLog.warn("Received but got null ProcessRecord")
/** 创建 APP 进程异常数据类 */ /** 创建 APP 进程异常数据类 */
AppErrorsProcessData(instance, proc).handleAppErrorsInfo(context, args(index = 1).cast()) AppErrorsProcessData(instance, proc).handleAppErrorsInfo(context, args(index = 1).cast())
}
} }
} }
} }

View File

@@ -0,0 +1,29 @@
/*
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
* Copyright (C) 2017-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

View File

@@ -17,7 +17,7 @@
* and eula along with this software. If not, see * and eula along with this software. If not, see
* <https://www.gnu.org/licenses/> * <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 package com.fankes.apperrorstracking.service

View File

@@ -17,10 +17,8 @@
* and eula along with this software. If not, see * and eula along with this software. If not, see
* <https://www.gnu.org/licenses/> * <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 package com.fankes.apperrorstracking.ui.activity.base
import android.app.ActivityManager import android.app.ActivityManager

View File

@@ -17,7 +17,7 @@
* and eula along with this software. If not, see * and eula along with this software. If not, see
* <https://www.gnu.org/licenses/> * <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") @file:Suppress("DEPRECATION", "OVERRIDE_DEPRECATION")
@@ -31,20 +31,24 @@ import android.view.View
import android.widget.AdapterView import android.widget.AdapterView
import androidx.core.view.isVisible import androidx.core.view.isVisible
import com.fankes.apperrorstracking.R import com.fankes.apperrorstracking.R
import com.fankes.apperrorstracking.const.PackageName
import com.fankes.apperrorstracking.databinding.ActivitiyLoggerBinding import com.fankes.apperrorstracking.databinding.ActivitiyLoggerBinding
import com.fankes.apperrorstracking.databinding.AdapterLoggerBinding import com.fankes.apperrorstracking.databinding.AdapterLoggerBinding
import com.fankes.apperrorstracking.databinding.DiaLoggerFilterBinding 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.ui.activity.base.BaseActivity
import com.fankes.apperrorstracking.utils.factory.* import com.fankes.apperrorstracking.utils.factory.bindAdapter
import com.fankes.apperrorstracking.utils.tool.FrameworkTool 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.factory.dataChannel
import com.highcapable.yukihookapi.hook.log.YukiHookLogger import com.highcapable.yukihookapi.hook.log.YLog
import com.highcapable.yukihookapi.hook.log.YukiLoggerData import com.highcapable.yukihookapi.hook.log.data.YLogData
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.io.PrintStream import java.io.PrintStream
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.Date
class LoggerActivity : BaseActivity<ActivitiyLoggerBinding>() { class LoggerActivity : BaseActivity<ActivitiyLoggerBinding>() {
@@ -61,14 +65,14 @@ class LoggerActivity : BaseActivity<ActivitiyLoggerBinding>() {
private var filters = arrayListOf("D", "I", "W", "E") private var filters = arrayListOf("D", "I", "W", "E")
/** 全部的调试日志数据 */ /** 全部的调试日志数据 */
private val listData = ArrayList<YukiLoggerData>() private val listData = mutableListOf<YLogData>()
override fun onCreate() { override fun onCreate() {
binding.titleBackIcon.setOnClickListener { finish() } binding.titleBackIcon.setOnClickListener { finish() }
binding.refreshIcon.setOnClickListener { refreshData() } binding.refreshIcon.setOnClickListener { refreshData() }
binding.filterIcon.setOnClickListener { binding.filterIcon.setOnClickListener {
showDialog<DiaLoggerFilterBinding> { showDialog<DiaLoggerFilterBinding> {
title = LocaleString.filterByCondition title = locale.filterByCondition
binding.configCheck0.isChecked = filters.any { it == "D" } binding.configCheck0.isChecked = filters.any { it == "D" }
binding.configCheck1.isChecked = filters.any { it == "I" } binding.configCheck1.isChecked = filters.any { it == "I" }
binding.configCheck2.isChecked = filters.any { it == "W" } binding.configCheck2.isChecked = filters.any { it == "W" }
@@ -124,7 +128,7 @@ class LoggerActivity : BaseActivity<ActivitiyLoggerBinding>() {
/** 更新列表数据 */ /** 更新列表数据 */
private fun refreshData() { private fun refreshData() {
dataChannel(FrameworkTool.SYSTEM_FRAMEWORK_NAME).obtainLoggerInMemoryData { dataChannel(PackageName.SYSTEM_FRAMEWORK).obtainLoggerInMemoryData {
listData.clear() listData.clear()
it.takeIf { e -> e.isNotEmpty() }?.reversed()?.filter { filters.any { e -> it.priority == e } }?.forEach { e -> listData.add(e) } it.takeIf { e -> e.isNotEmpty() }?.reversed()?.filter { filters.any { e -> it.priority == e } }?.forEach { e -> listData.add(e) }
onChanged?.invoke() onChanged?.invoke()
@@ -132,7 +136,7 @@ class LoggerActivity : BaseActivity<ActivitiyLoggerBinding>() {
binding.exportAllIcon.isVisible = listData.isNotEmpty() binding.exportAllIcon.isVisible = listData.isNotEmpty()
binding.listView.isVisible = listData.isNotEmpty() binding.listView.isVisible = listData.isNotEmpty()
binding.listNoDataView.isVisible = listData.isEmpty() 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) super.onActivityResult(requestCode, resultCode, data)
if (requestCode == WRITE_REQUEST_CODE && resultCode == Activity.RESULT_OK) runCatching { if (requestCode == WRITE_REQUEST_CODE && resultCode == Activity.RESULT_OK) runCatching {
data?.data?.let { data?.data?.let {
contentResolver?.openOutputStream(it)?.apply { write(YukiHookLogger.contents(listData).toByteArray()) }?.close() contentResolver?.openOutputStream(it)?.apply { write(YLog.contents(listData).toByteArray()) }?.close()
toast(LocaleString.exportAllLogsSuccess) toast(locale.exportAllLogsSuccess)
} ?: toast(LocaleString.exportAllLogsFail) } ?: toast(locale.exportAllLogsFail)
}.onFailure { toast(LocaleString.exportAllLogsFail) } }.onFailure { toast(locale.exportAllLogsFail) }
} }
override fun onResume() { override fun onResume() {

View File

@@ -17,7 +17,7 @@
* and eula along with this software. If not, see * and eula along with this software. If not, see
* <https://www.gnu.org/licenses/> * <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") @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.ConfigData
import com.fankes.apperrorstracking.data.factory.bind import com.fankes.apperrorstracking.data.factory.bind
import com.fankes.apperrorstracking.databinding.ActivityAppErrorsDetailBinding 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.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 import com.highcapable.yukihookapi.hook.log.loggerE
class AppErrorsDetailActivity : BaseActivity<ActivityAppErrorsDetailBinding>() { class AppErrorsDetailActivity : BaseActivity<ActivityAppErrorsDetailBinding>() {
@@ -62,55 +71,91 @@ class AppErrorsDetailActivity : BaseActivity<ActivityAppErrorsDetailBinding>() {
private var stackTrace = "" private var stackTrace = ""
override fun onCreate() { 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() 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) { if (appErrorsInfo.isEmpty) {
binding.appPanelScrollView.isVisible = false binding.appPanelScrollView.isVisible = false
showDialog { showDialog {
title = LocaleString.notice title = locale.notice
msg = LocaleString.unableGetAppErrorsRecordTip msg = locale.unableGetAppErrorsRecordTip
confirmButton(LocaleString.gotIt) { confirmButton(locale.gotIt) {
cancel() cancel()
finish() finish()
} }
cancelButton(LocaleString.goItNow) { cancelButton(locale.goItNow) {
cancel() cancel()
finish() finish()
navigate<AppErrorsRecordActivity>() navigate<AppErrorsRecordActivity>()
} }
noCancelable() noCancelable()
} }
return return false
} }
binding.appInfoItem.setOnClickListener { openSelfSetting(appErrorsInfo.packageName) } binding.appInfoItem.setOnClickListener { openSelfSetting(appErrorsInfo.packageName) }
binding.titleBackIcon.setOnClickListener { onBackPressed() }
binding.printIcon.setOnClickListener { binding.printIcon.setOnClickListener {
loggerE(msg = appErrorsInfo.stackTrace) 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 { binding.exportIcon.setOnClickListener {
stackTrace = appErrorsInfo.stackOutputFileContent StackTraceShareHelper.showChoose(context = this, locale.exportToFile) { sDeviceBrand, sDeviceModel, sDisplay, sPackageName ->
runCatching { stackTrace = appErrorsInfo.stackOutputFileContent(sDeviceBrand, sDeviceModel, sDisplay, sPackageName)
startActivityForResult(Intent(Intent.ACTION_CREATE_DOCUMENT).apply { runCatching {
addCategory(Intent.CATEGORY_OPENABLE) startActivityForResult(Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
type = "*/application" addCategory(Intent.CATEGORY_OPENABLE)
putExtra(Intent.EXTRA_TITLE, "${appErrorsInfo.packageName}_${appErrorsInfo.utcTime}.log") type = "*/application"
}, WRITE_REQUEST_CODE) val packageName = if (sPackageName) appErrorsInfo.packageName else "anonymous"
}.onFailure { toast(msg = "Start Android SAF failed") } putExtra(Intent.EXTRA_TITLE, "${packageName}_${appErrorsInfo.utcTime}.log")
}, WRITE_REQUEST_CODE)
}.onFailure { toast(msg = "Start Android SAF failed") }
}
} }
binding.shareIcon.setOnClickListener { binding.shareIcon.setOnClickListener {
startActivity(Intent.createChooser(Intent(Intent.ACTION_SEND).apply { StackTraceShareHelper.showChoose(context = this, locale.shareErrorStack) { sDeviceBrand, sDeviceModel, sDisplay, sPackageName ->
type = "text/plain" startActivity(Intent.createChooser(Intent(Intent.ACTION_SEND).apply {
putExtra(Intent.EXTRA_TEXT, appErrorsInfo.stackOutputShareContent) type = "text/plain"
}, LocaleString.shareErrorStack)) putExtra(Intent.EXTRA_TEXT, appErrorsInfo.stackOutputShareContent(sDeviceBrand, sDeviceModel, sDisplay, sPackageName))
}, locale.shareErrorStack))
}
} }
binding.appIcon.setImageDrawable(appIconOf(appErrorsInfo.packageName)) binding.appIcon.setImageDrawable(appIconOf(appErrorsInfo.packageName))
binding.appNameText.text = appNameOf(appErrorsInfo.packageName).ifBlank { appErrorsInfo.packageName } binding.appNameText.text = appNameOf(appErrorsInfo.packageName).ifBlank { appErrorsInfo.packageName }
binding.appVersionText.text = appErrorsInfo.versionBrand binding.appVersionText.text = appErrorsInfo.versionBrand
binding.appUserIdText.isVisible = appErrorsInfo.userId > 0 binding.appUserIdText.isVisible = appErrorsInfo.userId > 0
binding.appUserIdText.text = LocaleString.userId(appErrorsInfo.userId) binding.appUserIdText.text = locale.userId(appErrorsInfo.userId)
binding.appCpuAbiText.text = appErrorsInfo.cpuAbi.ifBlank { LocaleString.noCpuAbi } 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.jvmErrorPanel.isGone = appErrorsInfo.isNativeCrash
binding.errorTypeIcon.setImageResource(if (appErrorsInfo.isNativeCrash) R.drawable.ic_cpp else R.drawable.ic_java) binding.errorTypeIcon.setImageResource(if (appErrorsInfo.isNativeCrash) R.drawable.ic_cpp else R.drawable.ic_java)
binding.errorInfoText.text = appErrorsInfo.exceptionMessage binding.errorInfoText.text = appErrorsInfo.exceptionMessage
@@ -122,23 +167,12 @@ class AppErrorsDetailActivity : BaseActivity<ActivityAppErrorsDetailBinding>() {
binding.errorRecordTimeText.text = appErrorsInfo.dateTime binding.errorRecordTimeText.text = appErrorsInfo.dateTime
binding.errorStackTraceMovableText.text = appErrorsInfo.stackTrace binding.errorStackTraceMovableText.text = appErrorsInfo.stackTrace
binding.errorStackTraceFixedText.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.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 } appNameOf(appErrorsInfo.packageName).ifBlank { appErrorsInfo.packageName }
else LocaleString.appName else locale.appName
} }
binding.detailTitleText.setOnClickListener { binding.appPanelScrollView.smoothScrollTo(0, 0) } return true
resetScrollView()
} }
/** 修复在一些小屏设备上设置了 [TextView.setTextIsSelectable] 后布局自动上滑问题 */ /** 修复在一些小屏设备上设置了 [TextView.setTextIsSelectable] 后布局自动上滑问题 */
@@ -154,13 +188,18 @@ class AppErrorsDetailActivity : BaseActivity<ActivityAppErrorsDetailBinding>() {
if (requestCode == WRITE_REQUEST_CODE && resultCode == Activity.RESULT_OK) runCatching { if (requestCode == WRITE_REQUEST_CODE && resultCode == Activity.RESULT_OK) runCatching {
data?.data?.let { data?.data?.let {
contentResolver?.openOutputStream(it)?.apply { write(stackTrace.toByteArray()) }?.close() contentResolver?.openOutputStream(it)?.apply { write(stackTrace.toByteArray()) }?.close()
toast(LocaleString.outputStackSuccess) toast(locale.outputStackSuccess)
} ?: toast(LocaleString.outputStackFail) } ?: toast(locale.outputStackFail)
}.onFailure { toast(LocaleString.outputStackFail) } }.onFailure { toast(locale.outputStackFail) }
} }
override fun onBackPressed() { override fun onBackPressed() {
intent?.removeExtra(EXTRA_APP_ERRORS_INFO) intent?.removeExtra(EXTRA_APP_ERRORS_INFO)
finish() finish()
} }
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
if (initUi(intent)) binding.appPanelScrollView.scrollTo(0, 0)
}
} }

View File

@@ -17,7 +17,7 @@
* and eula along with this software. If not, see * and eula along with this software. If not, see
* <https://www.gnu.org/licenses/> * <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 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.data.ConfigData
import com.fankes.apperrorstracking.databinding.ActivityAppErrorsDisplayBinding import com.fankes.apperrorstracking.databinding.ActivityAppErrorsDisplayBinding
import com.fankes.apperrorstracking.databinding.DiaAppErrorsDisplayBinding 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.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 import com.fankes.apperrorstracking.utils.tool.FrameworkTool
class AppErrorsDisplayActivity : BaseActivity<ActivityAppErrorsDisplayBinding>() { class AppErrorsDisplayActivity : BaseActivity<ActivityAppErrorsDisplayBinding>() {
@@ -73,7 +78,7 @@ class AppErrorsDisplayActivity : BaseActivity<ActivityAppErrorsDisplayBinding>()
binding.appInfoItem.isVisible = appErrorsDisplay.isShowAppInfoButton binding.appInfoItem.isVisible = appErrorsDisplay.isShowAppInfoButton
binding.closeAppItem.isVisible = appErrorsDisplay.isShowReopenButton.not() && appErrorsDisplay.isShowCloseAppButton binding.closeAppItem.isVisible = appErrorsDisplay.isShowReopenButton.not() && appErrorsDisplay.isShowCloseAppButton
binding.reopenAppItem.isVisible = appErrorsDisplay.isShowReopenButton binding.reopenAppItem.isVisible = appErrorsDisplay.isShowReopenButton
binding.processNameText.text = LocaleString.crashProcess(appErrorsDisplay.processName) binding.processNameText.text = locale.crashProcess(appErrorsDisplay.processName)
binding.appInfoItem.setOnClickListener { binding.appInfoItem.setOnClickListener {
cancel() cancel()
openSelfSetting(appErrorsDisplay.packageName) openSelfSetting(appErrorsDisplay.packageName)
@@ -91,13 +96,13 @@ class AppErrorsDisplayActivity : BaseActivity<ActivityAppErrorsDisplayBinding>()
} }
binding.mutedIfUnlockItem.setOnClickListener { binding.mutedIfUnlockItem.setOnClickListener {
FrameworkTool.mutedErrorsIfUnlock(context, appErrorsDisplay.packageName) { FrameworkTool.mutedErrorsIfUnlock(context, appErrorsDisplay.packageName) {
toast(LocaleString.muteIfUnlockTip(appErrorsDisplay.appName)) toast(locale.muteIfUnlockTip(appErrorsDisplay.appName))
cancel() cancel()
} }
} }
binding.mutedIfRestartItem.setOnClickListener { binding.mutedIfRestartItem.setOnClickListener {
FrameworkTool.mutedErrorsIfRestart(context, appErrorsDisplay.packageName) { FrameworkTool.mutedErrorsIfRestart(context, appErrorsDisplay.packageName) {
toast(LocaleString.muteIfRestartTip(appErrorsDisplay.appName)) toast(locale.muteIfRestartTip(appErrorsDisplay.appName))
cancel() cancel()
} }
} }

View File

@@ -17,17 +17,15 @@
* and eula along with this software. If not, see * and eula along with this software. If not, see
* <https://www.gnu.org/licenses/> * <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 package com.fankes.apperrorstracking.ui.activity.errors
import androidx.core.view.isVisible import androidx.core.view.isVisible
import com.fankes.apperrorstracking.bean.MutedErrorsAppBean import com.fankes.apperrorstracking.bean.MutedErrorsAppBean
import com.fankes.apperrorstracking.databinding.ActivityAppErrorsMutedBinding import com.fankes.apperrorstracking.databinding.ActivityAppErrorsMutedBinding
import com.fankes.apperrorstracking.databinding.AdapterAppErrorsMutedBinding 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.ui.activity.base.BaseActivity
import com.fankes.apperrorstracking.utils.factory.appIconOf import com.fankes.apperrorstracking.utils.factory.appIconOf
import com.fankes.apperrorstracking.utils.factory.appNameOf import com.fankes.apperrorstracking.utils.factory.appNameOf
@@ -47,8 +45,8 @@ class AppErrorsMutedActivity : BaseActivity<ActivityAppErrorsMutedBinding>() {
binding.titleBackIcon.setOnClickListener { onBackPressed() } binding.titleBackIcon.setOnClickListener { onBackPressed() }
binding.unmuteAllIcon.setOnClickListener { binding.unmuteAllIcon.setOnClickListener {
showDialog { showDialog {
title = LocaleString.notice title = locale.notice
msg = LocaleString.areYouSureUnmuteAll msg = locale.areYouSureUnmuteAll
confirmButton { FrameworkTool.unmuteAllErrorsApps(context) { refreshData() } } confirmButton { FrameworkTool.unmuteAllErrorsApps(context) { refreshData() } }
cancelButton() cancelButton()
} }
@@ -61,8 +59,8 @@ class AppErrorsMutedActivity : BaseActivity<ActivityAppErrorsMutedBinding>() {
binding.appIcon.setImageDrawable(appIconOf(bean.packageName)) binding.appIcon.setImageDrawable(appIconOf(bean.packageName))
binding.appNameText.text = appNameOf(bean.packageName).ifBlank { bean.packageName } binding.appNameText.text = appNameOf(bean.packageName).ifBlank { bean.packageName }
binding.muteTypeText.text = when (bean.type) { binding.muteTypeText.text = when (bean.type) {
MutedErrorsAppBean.MuteType.UNTIL_UNLOCKS -> LocaleString.muteIfUnlock MutedErrorsAppBean.MuteType.UNTIL_UNLOCKS -> locale.muteIfUnlock
MutedErrorsAppBean.MuteType.UNTIL_REBOOTS -> LocaleString.muteIfRestart MutedErrorsAppBean.MuteType.UNTIL_REBOOTS -> locale.muteIfRestart
} }
binding.unmuteButton.setOnClickListener { FrameworkTool.unmuteErrorsApp(context, bean) { refreshData() } } binding.unmuteButton.setOnClickListener { FrameworkTool.unmuteErrorsApp(context, bean) { refreshData() } }
} }

View File

@@ -17,7 +17,7 @@
* and eula along with this software. If not, see * and eula along with this software. If not, see
* <https://www.gnu.org/licenses/> * <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") @file:Suppress("DEPRECATION", "OVERRIDE_DEPRECATION", "SetTextI18n")
@@ -31,7 +31,6 @@ import android.view.MenuItem
import android.view.View import android.view.View
import android.widget.AdapterView.AdapterContextMenuInfo import android.widget.AdapterView.AdapterContextMenuInfo
import androidx.core.view.isVisible import androidx.core.view.isVisible
import com.fankes.apperrorstracking.BuildConfig
import com.fankes.apperrorstracking.R import com.fankes.apperrorstracking.R
import com.fankes.apperrorstracking.bean.AppErrorsInfoBean import com.fankes.apperrorstracking.bean.AppErrorsInfoBean
import com.fankes.apperrorstracking.bean.AppFiltersBean 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.ActivityAppErrorsRecordBinding
import com.fankes.apperrorstracking.databinding.AdapterAppErrorsRecordBinding import com.fankes.apperrorstracking.databinding.AdapterAppErrorsRecordBinding
import com.fankes.apperrorstracking.databinding.DiaAppErrorsStatisticsBinding 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.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.FrameworkTool
import com.fankes.apperrorstracking.utils.tool.StackTraceShareHelper
import com.fankes.apperrorstracking.utils.tool.ZipFileTool import com.fankes.apperrorstracking.utils.tool.ZipFileTool
import com.fankes.apperrorstracking.wrapper.BuildConfigWrapper
import java.io.File import java.io.File
import java.io.FileInputStream import java.io.FileInputStream
@@ -58,7 +67,7 @@ class AppErrorsRecordActivity : BaseActivity<ActivityAppErrorsRecordBinding>() {
* 获取 [Intent] * 获取 [Intent]
* @return [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.titleBackIcon.setOnClickListener { onBackPressed() }
binding.appErrorSisIcon.setOnClickListener { binding.appErrorSisIcon.setOnClickListener {
showDialog { showDialog {
title = LocaleString.notice title = locale.notice
progressContent = LocaleString.generatingStatistics progressContent = locale.generatingStatistics
noCancelable() noCancelable()
FrameworkTool.fetchAppListData(context, AppFiltersBean(type = AppFiltersType.ALL)) { FrameworkTool.fetchAppListData(context, AppFiltersBean(type = AppFiltersType.ALL)) {
newThread { newThread {
@@ -92,14 +101,14 @@ class AppErrorsRecordActivity : BaseActivity<ActivityAppErrorsRecordBinding>() {
runOnUiThread { runOnUiThread {
cancel() cancel()
showDialog<DiaAppErrorsStatisticsBinding> { showDialog<DiaAppErrorsStatisticsBinding> {
title = LocaleString.appErrorsStatistics title = locale.appErrorsStatistics
binding.totalErrorsUnitText.text = LocaleString.totalErrorsUnit(listData.size) binding.totalErrorsUnitText.text = locale.totalErrorsUnit(listData.size)
binding.totalAppsUnitText.text = LocaleString.totalAppsUnit(it.size) binding.totalAppsUnitText.text = locale.totalAppsUnit(it.size)
binding.mostErrorsAppIcon.setImageDrawable(appIconOf(mostAppPackageName)) binding.mostErrorsAppIcon.setImageDrawable(appIconOf(mostAppPackageName))
binding.mostErrorsAppText.text = appNameOf(mostAppPackageName).ifBlank { mostAppPackageName } binding.mostErrorsAppText.text = appNameOf(mostAppPackageName).ifBlank { mostAppPackageName }
binding.mostErrorsTypeText.text = mostErrorsType binding.mostErrorsTypeText.text = mostErrorsType
binding.totalPptOfErrorsText.text = "$pptCount%" binding.totalPptOfErrorsText.text = "$pptCount%"
confirmButton(LocaleString.gotIt) confirmButton(locale.gotIt)
} }
} }
} }
@@ -108,12 +117,12 @@ class AppErrorsRecordActivity : BaseActivity<ActivityAppErrorsRecordBinding>() {
} }
binding.clearAllIcon.setOnClickListener { binding.clearAllIcon.setOnClickListener {
showDialog { showDialog {
title = LocaleString.notice title = locale.notice
msg = LocaleString.areYouSureClearErrors msg = locale.areYouSureClearErrors
confirmButton { confirmButton {
FrameworkTool.clearAppErrorsInfoData(context) { FrameworkTool.clearAppErrorsInfoData(context) {
refreshData() refreshData()
toast(LocaleString.allErrorsClearSuccess) toast(locale.allErrorsClearSuccess)
} }
} }
cancelButton() cancelButton()
@@ -121,8 +130,8 @@ class AppErrorsRecordActivity : BaseActivity<ActivityAppErrorsRecordBinding>() {
} }
binding.exportAllIcon.setOnClickListener { binding.exportAllIcon.setOnClickListener {
showDialog { showDialog {
title = LocaleString.notice title = locale.notice
msg = LocaleString.areYouSureExportAllErrors msg = locale.areYouSureExportAllErrors
confirmButton { exportAll() } confirmButton { exportAll() }
cancelButton() cancelButton()
} }
@@ -136,7 +145,7 @@ class AppErrorsRecordActivity : BaseActivity<ActivityAppErrorsRecordBinding>() {
binding.appIcon.setImageDrawable(appIconOf(bean.packageName)) binding.appIcon.setImageDrawable(appIconOf(bean.packageName))
binding.appNameText.text = appNameOf(bean.packageName).ifBlank { bean.packageName } binding.appNameText.text = appNameOf(bean.packageName).ifBlank { bean.packageName }
binding.appUserIdText.isVisible = bean.userId > 0 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.errorsTimeText.text = bean.crossTime
binding.errorTypeIcon.setImageResource(if (bean.isNativeCrash) R.drawable.ic_cpp else R.drawable.ic_java) 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() binding.errorTypeText.text = if (bean.isNativeCrash) "Native crash" else bean.exceptionClassName.simpleThwName()
@@ -152,7 +161,7 @@ class AppErrorsRecordActivity : BaseActivity<ActivityAppErrorsRecordBinding>() {
/** 更新列表数据 */ /** 更新列表数据 */
private fun refreshData() { private fun refreshData() {
FrameworkTool.fetchAppErrorsInfoData(context = this) { FrameworkTool.fetchAppErrorsInfoData(context = this) {
binding.titleCountText.text = LocaleString.recordCount(it.size) binding.titleCountText.text = locale.recordCount(it.size)
binding.listProgressView.isVisible = false binding.listProgressView.isVisible = false
binding.appErrorSisIcon.isVisible = it.size >= 5 binding.appErrorSisIcon.isVisible = it.size >= 5
binding.clearAllIcon.isVisible = it.isNotEmpty() binding.clearAllIcon.isVisible = it.isNotEmpty()
@@ -168,20 +177,24 @@ class AppErrorsRecordActivity : BaseActivity<ActivityAppErrorsRecordBinding>() {
/** 打包导出全部 */ /** 打包导出全部 */
private fun exportAll() { private fun exportAll() {
clearAllExportTemp() clearAllExportTemp()
("${cacheDir.absolutePath}/temp").also { path -> StackTraceShareHelper.showChoose(context = this, locale.exportAll) { sDeviceBrand, sDeviceModel, sDisplay, sPackageName ->
File(path).mkdirs() ("${cacheDir.absolutePath}/temp").also { path ->
listData.takeIf { it.isNotEmpty() }?.forEach { File(path).mkdirs()
File("$path/${it.packageName}_${it.utcTime}.log").writeText(it.stackOutputFileContent) 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_app_info -> openSelfSetting(listData[it.position].packageName)
R.id.aerrors_remove_record -> R.id.aerrors_remove_record ->
showDialog { showDialog {
title = LocaleString.notice title = locale.notice
msg = LocaleString.areYouSureRemoveRecord msg = locale.areYouSureRemoveRecord
confirmButton { FrameworkTool.removeAppErrorsInfoData(context, listData[it.position]) { refreshData() } } confirmButton { FrameworkTool.removeAppErrorsInfoData(context, listData[it.position]) { refreshData() } }
cancelButton() cancelButton()
} }
@@ -227,9 +240,9 @@ class AppErrorsRecordActivity : BaseActivity<ActivityAppErrorsRecordBinding>() {
data?.data?.let { data?.data?.let {
contentResolver?.openOutputStream(it)?.apply { write(FileInputStream(outPutFilePath).readBytes()) }?.close() contentResolver?.openOutputStream(it)?.apply { write(FileInputStream(outPutFilePath).readBytes()) }?.close()
clearAllExportTemp() clearAllExportTemp()
toast(LocaleString.exportAllErrorsSuccess) toast(locale.exportAllErrorsSuccess)
} ?: toast(LocaleString.exportAllErrorsFail) } ?: toast(locale.exportAllErrorsFail)
}.onFailure { toast(LocaleString.exportAllErrorsFail) } }.onFailure { toast(locale.exportAllErrorsFail) }
} }
override fun onResume() { override fun onResume() {

View File

@@ -17,7 +17,7 @@
* and eula along with this software. If not, see * and eula along with this software. If not, see
* <https://www.gnu.org/licenses/> * <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 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.AdapterAppInfoBinding
import com.fankes.apperrorstracking.databinding.DiaAppConfigBinding import com.fankes.apperrorstracking.databinding.DiaAppConfigBinding
import com.fankes.apperrorstracking.databinding.DiaAppsFilterBinding 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.ui.activity.base.BaseActivity
import com.fankes.apperrorstracking.utils.factory.appIconOf import com.fankes.apperrorstracking.utils.factory.appIconOf
import com.fankes.apperrorstracking.utils.factory.bindAdapter import com.fankes.apperrorstracking.utils.factory.bindAdapter
@@ -53,16 +53,16 @@ class ConfigureActivity : BaseActivity<ActivityConfigBinding>() {
override fun onCreate() { override fun onCreate() {
binding.titleBackIcon.setOnClickListener { finish() } binding.titleBackIcon.setOnClickListener { finish() }
binding.globalIcon.setOnClickListener { binding.globalIcon.setOnClickListener {
showAppConfigDialog(LocaleString.globalConfig, isShowGlobalConfig = false) { type -> showAppConfigDialog(locale.globalConfig, isShowGlobalConfig = false) { type ->
AppErrorsConfigData.putAppShowingType(type) AppErrorsConfigData.putAppShowingType(type)
onChanged?.invoke() onChanged?.invoke()
} }
} }
binding.batchIcon.setOnClickListener { binding.batchIcon.setOnClickListener {
showAppConfigDialog(LocaleString.batchOperationsNumber(listData.size), isNotSetDefaultValue = true) { type -> showAppConfigDialog(locale.batchOperationsNumber(listData.size), isNotSetDefaultValue = true) { type ->
showDialog { showDialog {
title = LocaleString.notice title = locale.notice
msg = LocaleString.areYouSureApplySiteApps(listData.size) msg = locale.areYouSureApplySiteApps(listData.size)
confirmButton { confirmButton {
listData.takeIf { it.isNotEmpty() }?.forEach { AppErrorsConfigData.putAppShowingType(type, it.packageName) } listData.takeIf { it.isNotEmpty() }?.forEach { AppErrorsConfigData.putAppShowingType(type, it.packageName) }
onChanged?.invoke() onChanged?.invoke()
@@ -73,7 +73,7 @@ class ConfigureActivity : BaseActivity<ActivityConfigBinding>() {
} }
binding.filterIcon.setOnClickListener { binding.filterIcon.setOnClickListener {
showDialog<DiaAppsFilterBinding> { showDialog<DiaAppsFilterBinding> {
title = LocaleString.filterByCondition title = locale.filterByCondition
binding.filtersRadioUser.isChecked = appFilters.type == AppFiltersType.USER binding.filtersRadioUser.isChecked = appFilters.type == AppFiltersType.USER
binding.filtersRadioSystem.isChecked = appFilters.type == AppFiltersType.SYSTEM binding.filtersRadioSystem.isChecked = appFilters.type == AppFiltersType.SYSTEM
binding.filtersRadioAll.isChecked = appFilters.type == AppFiltersType.ALL binding.filtersRadioAll.isChecked = appFilters.type == AppFiltersType.ALL
@@ -101,7 +101,7 @@ class ConfigureActivity : BaseActivity<ActivityConfigBinding>() {
} }
cancelButton() cancelButton()
if (appFilters.name.isNotBlank()) if (appFilters.name.isNotBlank())
neutralButton(LocaleString.clearFilters) { neutralButton(locale.clearFilters) {
setAppFiltersType() setAppFiltersType()
appFilters.name = "" appFilters.name = ""
refreshData() refreshData()
@@ -116,11 +116,11 @@ class ConfigureActivity : BaseActivity<ActivityConfigBinding>() {
binding.appIcon.setImageDrawable(bean.icon) binding.appIcon.setImageDrawable(bean.icon)
binding.appNameText.text = bean.name binding.appNameText.text = bean.name
binding.configTypeText.text = when { binding.configTypeText.text = when {
AppErrorsConfigData.isAppShowingType(AppErrorsConfigType.GLOBAL, bean.packageName) -> LocaleString.followGlobalConfig AppErrorsConfigData.isAppShowingType(AppErrorsConfigType.GLOBAL, bean.packageName) -> locale.followGlobalConfig
AppErrorsConfigData.isAppShowingType(AppErrorsConfigType.DIALOG, bean.packageName) -> LocaleString.showErrorsDialog AppErrorsConfigData.isAppShowingType(AppErrorsConfigType.DIALOG, bean.packageName) -> locale.showErrorsDialog
AppErrorsConfigData.isAppShowingType(AppErrorsConfigType.NOTIFY, bean.packageName) -> LocaleString.showErrorsNotify AppErrorsConfigData.isAppShowingType(AppErrorsConfigType.NOTIFY, bean.packageName) -> locale.showErrorsNotify
AppErrorsConfigData.isAppShowingType(AppErrorsConfigType.TOAST, bean.packageName) -> LocaleString.showErrorsToast AppErrorsConfigData.isAppShowingType(AppErrorsConfigType.TOAST, bean.packageName) -> locale.showErrorsToast
AppErrorsConfigData.isAppShowingType(AppErrorsConfigType.NOTHING, bean.packageName) -> LocaleString.showNothing AppErrorsConfigData.isAppShowingType(AppErrorsConfigType.NOTHING, bean.packageName) -> locale.showNothing
else -> "Unknown type" else -> "Unknown type"
} }
} }
@@ -138,8 +138,8 @@ class ConfigureActivity : BaseActivity<ActivityConfigBinding>() {
/** 模块未完全激活将显示警告 */ /** 模块未完全激活将显示警告 */
if (MainActivity.isModuleValied.not()) if (MainActivity.isModuleValied.not())
showDialog { showDialog {
title = LocaleString.notice title = locale.notice
msg = LocaleString.moduleNotFullyActivatedTip msg = locale.moduleNotFullyActivatedTip
confirmButton { FrameworkTool.restartSystem(context) } confirmButton { FrameworkTool.restartSystem(context) }
cancelButton() cancelButton()
noCancelable() noCancelable()
@@ -199,7 +199,7 @@ class ConfigureActivity : BaseActivity<ActivityConfigBinding>() {
binding.filterIcon.isVisible = false binding.filterIcon.isVisible = false
binding.listView.isVisible = false binding.listView.isVisible = false
binding.listNoDataView.isVisible = false binding.listNoDataView.isVisible = false
binding.titleCountText.text = LocaleString.loading binding.titleCountText.text = locale.loading
FrameworkTool.fetchAppListData(context = this, appFilters) { FrameworkTool.fetchAppListData(context = this, appFilters) {
/** 设置一个临时变量用于更新列表数据 */ /** 设置一个临时变量用于更新列表数据 */
val tempsData = ArrayList<AppInfoBean>() val tempsData = ArrayList<AppInfoBean>()
@@ -221,7 +221,7 @@ class ConfigureActivity : BaseActivity<ActivityConfigBinding>() {
binding.filterIcon.isVisible = true binding.filterIcon.isVisible = true
binding.listView.isVisible = listData.isNotEmpty() binding.listView.isVisible = listData.isNotEmpty()
binding.listNoDataView.isVisible = listData.isEmpty() binding.listNoDataView.isVisible = listData.isEmpty()
binding.titleCountText.text = LocaleString.resultCount(listData.size) binding.titleCountText.text = locale.resultCount(listData.size)
} else tempsData.clear() } else tempsData.clear()
} }
} }

View File

@@ -17,7 +17,7 @@
* and eula along with this software. If not, see * and eula along with this software. If not, see
* <https://www.gnu.org/licenses/> * <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") @file:Suppress("SetTextI18n")
@@ -25,20 +25,28 @@ package com.fankes.apperrorstracking.ui.activity.main
import android.os.Build import android.os.Build
import androidx.core.view.isVisible import androidx.core.view.isVisible
import com.fankes.apperrorstracking.BuildConfig
import com.fankes.apperrorstracking.R import com.fankes.apperrorstracking.R
import com.fankes.apperrorstracking.const.ModuleVersion
import com.fankes.apperrorstracking.data.ConfigData import com.fankes.apperrorstracking.data.ConfigData
import com.fankes.apperrorstracking.data.factory.bind import com.fankes.apperrorstracking.data.factory.bind
import com.fankes.apperrorstracking.databinding.ActivityMainBinding 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.base.BaseActivity
import com.fankes.apperrorstracking.ui.activity.debug.LoggerActivity import com.fankes.apperrorstracking.ui.activity.debug.LoggerActivity
import com.fankes.apperrorstracking.ui.activity.errors.AppErrorsMutedActivity import com.fankes.apperrorstracking.ui.activity.errors.AppErrorsMutedActivity
import com.fankes.apperrorstracking.ui.activity.errors.AppErrorsRecordActivity 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.AppAnalyticsTool.bindAppAnalytics
import com.fankes.apperrorstracking.utils.tool.FrameworkTool import com.fankes.apperrorstracking.utils.tool.FrameworkTool
import com.fankes.apperrorstracking.utils.tool.GithubReleaseTool import com.fankes.apperrorstracking.utils.tool.GithubReleaseTool
import com.fankes.projectpromote.ProjectPromote
import com.highcapable.yukihookapi.YukiHookAPI import com.highcapable.yukihookapi.YukiHookAPI
class MainActivity : BaseActivity<ActivityMainBinding>() { class MainActivity : BaseActivity<ActivityMainBinding>() {
@@ -55,23 +63,39 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
override fun onCreate() { override fun onCreate() {
checkingTopComponentName() checkingTopComponentName()
/** 检查更新 */ /** 检查更新 */
GithubReleaseTool.checkingForUpdate(context = this, BuildConfig.VERSION_NAME) { version, function -> GithubReleaseTool.checkingForUpdate(context = this, ModuleVersion.NAME) { version, function ->
binding.mainTextReleaseVersion.apply { binding.mainTextReleaseVersion.apply {
text = LocaleString.clickToUpdate(version) text = locale.clickToUpdate(version)
isVisible = true isVisible = true
setOnClickListener { function() } setOnClickListener { function() }
} }
} }
/** 推广、恰饭 */
if (YukiHookAPI.Status.isXposedModuleActive) ProjectPromote.show(activity = this, ModuleVersion.toString())
/** 显示开发者提示 */ /** 显示开发者提示 */
if (ConfigData.isShowDeveloperNotice) if (ConfigData.isShowDeveloperNotice)
showDialog { showDialog {
title = LocaleString.developerNotice title = locale.developerNotice
msg = LocaleString.developerNoticeTip msg = locale.developerNoticeTip
confirmButton(LocaleString.gotIt) { ConfigData.isShowDeveloperNotice = false } confirmButton(locale.gotIt) { ConfigData.isShowDeveloperNotice = false }
noCancelable() noCancelable()
} }
binding.mainTextVersion.text = LocaleString.moduleVersion(BuildConfig.VERSION_NAME) /** 设置 CI 自动构建标识 */
binding.mainTextSystemVersion.text = LocaleString.systemVersion(systemVersion) 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.onlyShowErrorsInFrontSwitch.bind(ConfigData.ENABLE_ONLY_SHOW_ERRORS_IN_FRONT)
binding.onlyShowErrorsInMainProcessSwitch.bind(ConfigData.ENABLE_ONLY_SHOW_ERRORS_IN_MAIN) binding.onlyShowErrorsInMainProcessSwitch.bind(ConfigData.ENABLE_ONLY_SHOW_ERRORS_IN_MAIN)
binding.alwaysShowsReopenAppOptionsSwitch.bind(ConfigData.ENABLE_ALWAYS_SHOWS_REOPEN_APP_OPTIONS) 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.enableMaterial3AppErrorsDialogSwitch.bind(ConfigData.ENABLE_MATERIAL3_STYLE_APP_ERRORS_DIALOG)
/** 设置匿名统计 */ /** 设置匿名统计 */
binding.appAnalyticsConfigItem.isVisible = AppAnalyticsTool.isAvailable
binding.enableAnonymousStatisticsSwitch.bindAppAnalytics() binding.enableAnonymousStatisticsSwitch.bindAppAnalytics()
/** 系统版本点击事件 */ /** 系统版本点击事件 */
binding.mainTextSystemVersion.setOnClickListener { binding.mainTextSystemVersion.setOnClickListener {
showDialog { showDialog {
title = LocaleString.notice title = locale.notice
msg = systemVersion msg = systemVersion
confirmButton(LocaleString.gotIt) confirmButton(locale.gotIt)
} }
} }
/** 管理应用配置模板按钮点击事件 */ /** 管理应用配置模板按钮点击事件 */
@@ -101,6 +126,11 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
binding.titleRestartIcon.setOnClickListener { FrameworkTool.restartSystem(context = this) } binding.titleRestartIcon.setOnClickListener { FrameworkTool.restartSystem(context = this) }
/** 项目地址按钮点击事件 */ /** 项目地址按钮点击事件 */
binding.titleGithubIcon.setOnClickListener { openBrowser(url = "https://github.com/KitsunePie/AppErrorsTracking") } 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.isChecked = isLauncherIconShowing.not()
binding.hideIconInLauncherSwitch.setOnCheckedChangeListener { btn, b -> binding.hideIconInLauncherSwitch.setOnCheckedChangeListener { btn, b ->
@@ -125,9 +155,9 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
} }
) )
binding.mainTextStatus.text = when { binding.mainTextStatus.text = when {
YukiHookAPI.Status.isXposedModuleActive && isModuleValied.not() -> LocaleString.moduleNotFullyActivated YukiHookAPI.Status.isXposedModuleActive && isModuleValied.not() -> locale.moduleNotFullyActivated
YukiHookAPI.Status.isXposedModuleActive -> LocaleString.moduleIsActivated YukiHookAPI.Status.isXposedModuleActive -> locale.moduleIsActivated
else -> LocaleString.moduleNotActivated else -> locale.moduleNotActivated
} }
binding.mainTextApiWay.isVisible = YukiHookAPI.Status.isXposedModuleActive binding.mainTextApiWay.isVisible = YukiHookAPI.Status.isXposedModuleActive
binding.mainTextApiWay.text = "Activated by ${YukiHookAPI.Status.Executor.name} API ${YukiHookAPI.Status.Executor.apiLevel}" binding.mainTextApiWay.text = "Activated by ${YukiHookAPI.Status.Executor.name} API ${YukiHookAPI.Status.Executor.apiLevel}"
@@ -138,7 +168,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
* @param callback 激活后回调 * @param callback 激活后回调
*/ */
private inline fun whenActivated(callback: () -> Unit) { 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() { override fun onResume() {

View File

@@ -17,7 +17,7 @@
* and eula along with this software. If not, see * and eula along with this software. If not, see
* <https://www.gnu.org/licenses/> * <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 package com.fankes.apperrorstracking.ui.widget

View File

@@ -17,7 +17,7 @@
* and eula along with this software. If not, see * and eula along with this software. If not, see
* <https://www.gnu.org/licenses/> * <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") @file:Suppress("SameParameterValue")

View File

@@ -17,7 +17,7 @@
* and eula along with this software. If not, see * and eula along with this software. If not, see
* <https://www.gnu.org/licenses/> * <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 package com.fankes.apperrorstracking.utils.factory

View File

@@ -17,9 +17,9 @@
* and eula along with this software. If not, see * and eula along with this software. If not, see
* <https://www.gnu.org/licenses/> * <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 package com.fankes.apperrorstracking.utils.factory
@@ -33,12 +33,11 @@ import android.widget.LinearLayout
import android.widget.TextView import android.widget.TextView
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.viewbinding.ViewBinding 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.dialog.MaterialAlertDialogBuilder
import com.google.android.material.progressindicator.CircularProgressIndicator import com.google.android.material.progressindicator.CircularProgressIndicator
import com.google.android.material.shape.MaterialShapeDrawable import com.google.android.material.shape.MaterialShapeDrawable
import com.highcapable.yukihookapi.YukiHookAPI import com.highcapable.yukihookapi.YukiHookAPI
import com.highcapable.yukihookapi.annotation.CauseProblemsApi
import com.highcapable.yukihookapi.hook.factory.method import com.highcapable.yukihookapi.hook.factory.method
import com.highcapable.yukihookapi.hook.type.android.LayoutInflaterClass import com.highcapable.yukihookapi.hook.type.android.LayoutInflaterClass
@@ -150,7 +149,7 @@ class DialogBuilder<VB : ViewBinding>(
* @param text 按钮文本内容 * @param text 按钮文本内容
* @param callback 点击事件 * @param callback 点击事件
*/ */
fun confirmButton(text: String = LocaleString.confirm, callback: () -> Unit = {}) { fun confirmButton(text: String = locale.confirm, callback: () -> Unit = {}) {
instance?.setPositiveButton(text) { _, _ -> callback() } instance?.setPositiveButton(text) { _, _ -> callback() }
} }
@@ -159,7 +158,7 @@ class DialogBuilder<VB : ViewBinding>(
* @param text 按钮文本内容 * @param text 按钮文本内容
* @param callback 点击事件 * @param callback 点击事件
*/ */
fun cancelButton(text: String = LocaleString.cancel, callback: () -> Unit = {}) { fun cancelButton(text: String = locale.cancel, callback: () -> Unit = {}) {
instance?.setNegativeButton(text) { _, _ -> callback() } instance?.setNegativeButton(text) { _, _ -> callback() }
} }
@@ -168,7 +167,7 @@ class DialogBuilder<VB : ViewBinding>(
* @param text 按钮文本内容 * @param text 按钮文本内容
* @param callback 点击事件 * @param callback 点击事件
*/ */
fun neutralButton(text: String = LocaleString.more, callback: () -> Unit = {}) { fun neutralButton(text: String = locale.more, callback: () -> Unit = {}) {
instance?.setNeutralButton(text) { _, _ -> callback() } instance?.setNeutralButton(text) { _, _ -> callback() }
} }
@@ -184,7 +183,6 @@ class DialogBuilder<VB : ViewBinding>(
fun cancel() = dialogInstance?.cancel() fun cancel() = dialogInstance?.cancel()
/** 显示对话框 */ /** 显示对话框 */
@CauseProblemsApi
fun show() { fun show() {
/** 若当前自定义 View 的对话框没有调用 [binding] 将会对其手动调用一次以确保显示布局 */ /** 若当前自定义 View 的对话框没有调用 [binding] 将会对其手动调用一次以确保显示布局 */
if (bindingClass != null) binding if (bindingClass != null) binding

View File

@@ -17,14 +17,22 @@
* and eula along with this software. If not, see * and eula along with this software. If not, see
* <https://www.gnu.org/licenses/> * <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") @file:Suppress("unused", "NotificationPermission")
package com.fankes.apperrorstracking.utils.factory package com.fankes.apperrorstracking.utils.factory
import android.app.* import android.app.Activity
import android.content.* 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.PackageInfo
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.content.pm.PackageManager.PackageInfoFlags 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.pm.PackageInfoCompat
import androidx.core.content.res.ResourcesCompat import androidx.core.content.res.ResourcesCompat
import androidx.core.graphics.drawable.IconCompat import androidx.core.graphics.drawable.IconCompat
import com.fankes.apperrorstracking.BuildConfig
import com.fankes.apperrorstracking.R 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.google.android.material.snackbar.Snackbar
import com.highcapable.yukihookapi.hook.factory.field import com.highcapable.yukihookapi.hook.factory.field
import com.highcapable.yukihookapi.hook.factory.method 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.ApplicationInfoClass
import com.highcapable.yukihookapi.hook.type.android.ContextClass import com.highcapable.yukihookapi.hook.type.android.ContextClass
import com.highcapable.yukihookapi.hook.type.android.IntentClass import com.highcapable.yukihookapi.hook.type.android.IntentClass
@@ -58,7 +67,18 @@ import java.io.Serializable
import java.math.RoundingMode import java.math.RoundingMode
import java.text.DecimalFormat import java.text.DecimalFormat
import java.text.SimpleDateFormat 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 * @return [PackageInfo] or null
*/ */
private fun Context.getPackageInfoCompat(packageName: String, flag: Number = 0) = runCatching { private fun Context.getPackageInfoCompat(packageName: String, flag: Number = 0) = runCatching {
@Suppress("DEPRECATION") @Suppress("DEPRECATION", "KotlinRedundantDiagnosticSuppress")
if (Build.VERSION.SDK_INT >= 33) if (Build.VERSION.SDK_INT >= 33)
packageManager?.getPackageInfo(packageName, PackageInfoFlags.of(flag.toLong())) packageManager?.getPackageInfo(packageName, PackageInfoFlags.of(flag.toLong()))
else packageManager?.getPackageInfo(packageName, flag.toInt()) else packageManager?.getPackageInfo(packageName, flag.toInt())
@@ -124,7 +144,7 @@ private val PackageInfo.versionCodeCompat get() = PackageInfoCompat.getLongVersi
* @return [List]<[PackageInfo]> * @return [List]<[PackageInfo]>
*/ */
fun Context.listOfPackages() = runCatching { fun Context.listOfPackages() = runCatching {
@Suppress("DEPRECATION") @Suppress("DEPRECATION", "KotlinRedundantDiagnosticSuppress")
if (Build.VERSION.SDK_INT >= 33) if (Build.VERSION.SDK_INT >= 33)
packageManager?.getInstalledPackages(PackageInfoFlags.of(PackageManager.GET_CONFIGURATIONS.toLong())) packageManager?.getInstalledPackages(PackageInfoFlags.of(PackageManager.GET_CONFIGURATIONS.toLong()))
else packageManager?.getInstalledPackages(PackageManager.GET_CONFIGURATIONS) 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 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 名称 * 获取 APP CPU ABI 名称
* @param packageName APP 包名 - 默认为当前 APP * @param packageName APP 包名 - 默认为当前 APP
@@ -254,7 +288,11 @@ fun Long.toUtcTime() = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS", Locale.ROOT
* 弹出 [Toast] * 弹出 [Toast]
* @param msg 提示内容 * @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] * 弹出 [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 { 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 flags = if (this@navigate !is Activity) Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
else Intent.FLAG_ACTIVITY_NEW_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) initiate(this)
}) })
}.onFailure { toast(msg = "Start ${T::class.java.name} failed") } }.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 { (getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager).apply {
setPrimaryClip(ClipData.newPlainText(null, content)) setPrimaryClip(ClipData.newPlainText(null, content))
(primaryClip?.getItemAt(0)?.text ?: "").also { (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 权限 * 是否有 Root 权限
* @return [Boolean] * @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] 执行结果 * @return [String] 执行结果
*/ */
fun execShell(cmd: String, isSu: Boolean = true) = runCatching { fun execShell(cmd: String, isSu: Boolean = true) = runCatching {
@Suppress("DEPRECATION")
(if (isSu) Shell.su(cmd) else Shell.sh(cmd)).exec().out.let { (if (isSu) Shell.su(cmd) else Shell.sh(cmd)).exec().out.let {
if (it.isNotEmpty()) it[0].trim() else "" if (it.isNotEmpty()) it[0].trim() else ""
} }
@@ -399,7 +441,7 @@ fun execShell(cmd: String, isSu: Boolean = true) = runCatching {
*/ */
fun Context.hideOrShowLauncherIcon(isShow: Boolean) { fun Context.hideOrShowLauncherIcon(isShow: Boolean) {
packageManager?.setComponentEnabledSetting( 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, if (isShow) PackageManager.COMPONENT_ENABLED_STATE_DISABLED else PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
PackageManager.DONT_KILL_APP PackageManager.DONT_KILL_APP
) )
@@ -411,5 +453,5 @@ fun Context.hideOrShowLauncherIcon(isShow: Boolean) {
*/ */
val Context.isLauncherIconShowing val Context.isLauncherIconShowing
get() = packageManager?.getComponentEnabledSetting( get() = packageManager?.getComponentEnabledSetting(
ComponentName(packageName, "${BuildConfig.APPLICATION_ID}.Home") ComponentName(packageName, "${BuildConfigWrapper.APPLICATION_ID}.Home")
) != PackageManager.COMPONENT_ENABLED_STATE_DISABLED ) != PackageManager.COMPONENT_ENABLED_STATE_DISABLED

View File

@@ -17,7 +17,7 @@
* and eula along with this software. If not, see * and eula along with this software. If not, see
* <https://www.gnu.org/licenses/> * <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 package com.fankes.apperrorstracking.utils.factory

View File

@@ -17,7 +17,7 @@
* and eula along with this software. If not, see * and eula along with this software. If not, see
* <https://www.gnu.org/licenses/> * <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 package com.fankes.apperrorstracking.utils.factory

View File

@@ -17,7 +17,7 @@
* and eula along with this software. If not, see * and eula along with this software. If not, see
* <https://www.gnu.org/licenses/> * <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") @file:Suppress("unused")
@@ -25,7 +25,7 @@ package com.fankes.apperrorstracking.utils.tool
import android.app.Application import android.app.Application
import android.widget.CompoundButton 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.factory.prefs
import com.highcapable.yukihookapi.hook.xposed.prefs.data.PrefsData import com.highcapable.yukihookapi.hook.xposed.prefs.data.PrefsData
import com.microsoft.appcenter.AppCenter import com.microsoft.appcenter.AppCenter
@@ -38,7 +38,7 @@ import com.microsoft.appcenter.crashes.Crashes
object AppAnalyticsTool { object AppAnalyticsTool {
/** App Secret */ /** 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) 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) } instance?.prefs()?.edit { put(ENABLE_APP_CENTER_ANALYTICS, value) }
} }
/** 是否可用 */
val isAvailable = APP_CENTER_SECRET.isNotBlank()
/** 绑定到 [CompoundButton] 自动设置选中状态 */ /** 绑定到 [CompoundButton] 自动设置选中状态 */
fun CompoundButton.bindAppAnalytics() { fun CompoundButton.bindAppAnalytics() {
isChecked = isEnableAppCenterAnalytics isChecked = isEnableAppCenterAnalytics
@@ -81,7 +84,7 @@ object AppAnalyticsTool {
*/ */
fun init(instance: Application) { fun init(instance: Application) {
this.instance = instance this.instance = instance
if (isEnableAppCenterAnalytics && APP_CENTER_SECRET.isNotBlank()) if (isEnableAppCenterAnalytics && isAvailable)
AppCenter.start(instance, APP_CENTER_SECRET, Analytics::class.java, Crashes::class.java) AppCenter.start(instance, APP_CENTER_SECRET, Analytics::class.java, Crashes::class.java)
} }
} }

View File

@@ -17,10 +17,8 @@
* and eula along with this software. If not, see * and eula along with this software. If not, see
* <https://www.gnu.org/licenses/> * <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 package com.fankes.apperrorstracking.utils.tool
import android.content.Context 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.AppFiltersBean
import com.fankes.apperrorstracking.bean.AppInfoBean import com.fankes.apperrorstracking.bean.AppInfoBean
import com.fankes.apperrorstracking.bean.MutedErrorsAppBean 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.execShell
import com.fankes.apperrorstracking.utils.factory.isRootAccess import com.fankes.apperrorstracking.utils.factory.isRootAccess
import com.fankes.apperrorstracking.utils.factory.showDialog import com.fankes.apperrorstracking.utils.factory.showDialog
@@ -41,9 +40,6 @@ import com.highcapable.yukihookapi.hook.xposed.channel.data.ChannelData
*/ */
object FrameworkTool { 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_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_APP_ERRORS_DATA_GET = "call_app_errors_data_get"
private const val CALL_MUTED_ERRORS_APP_DATA_GET = "call_muted_app_errors_data_get" private const val CALL_MUTED_ERRORS_APP_DATA_GET = "call_muted_app_errors_data_get"
@@ -215,22 +211,22 @@ object FrameworkTool {
/** 当 Root 权限获取失败时显示对话框 */ /** 当 Root 权限获取失败时显示对话框 */
fun showWhenAccessRootFail() = fun showWhenAccessRootFail() =
context.showDialog { context.showDialog {
title = LocaleString.accessRootFail title = locale.accessRootFail
msg = LocaleString.accessRootFailTip msg = locale.accessRootFailTip
confirmButton(LocaleString.gotIt) confirmButton(locale.gotIt)
} }
context.showDialog { context.showDialog {
title = LocaleString.notice title = locale.notice
msg = LocaleString.areYouSureRestartSystem msg = locale.areYourSureRestartSystem
confirmButton { confirmButton {
if (isRootAccess) if (isRootAccess)
execShell(cmd = "reboot") execShell(cmd = "reboot")
else showWhenAccessRootFail() else showWhenAccessRootFail()
} }
neutralButton(LocaleString.fastRestart) { neutralButton(locale.fastRestart) {
context.showDialog { context.showDialog {
title = LocaleString.warning title = locale.warning
msg = LocaleString.fastRestartProblem msg = locale.fastRestartProblem
confirmButton { confirmButton {
if (isRootAccess) if (isRootAccess)
execShell(cmd = "killall zygote") execShell(cmd = "killall zygote")
@@ -249,13 +245,13 @@ object FrameworkTool {
* @param result 成功后回调 * @param result 成功后回调
*/ */
fun checkingActivated(context: Context, result: (Boolean) -> Unit) = 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 实例 * @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] * 使用系统框架打开 [packageName]
@@ -264,7 +260,7 @@ object FrameworkTool {
* @param userId APP 用户 ID * @param userId APP 用户 ID
*/ */
fun openAppUsedFramework(context: Context, packageName: String, userId: Int) = 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 异常信息 * 获取指定 APP 异常信息
@@ -273,7 +269,7 @@ object FrameworkTool {
* @param result 回调数据 * @param result 回调数据
*/ */
fun fetchAppErrorInfoData(context: Context, pid: Int, result: (AppErrorsInfoBean) -> Unit) { 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) } wait(CALL_APP_ERROR_DATA_GET_RESULT) { result(it) }
put(CALL_APP_ERROR_DATA_GET, pid) put(CALL_APP_ERROR_DATA_GET, pid)
} }
@@ -285,7 +281,7 @@ object FrameworkTool {
* @param result 回调数据 * @param result 回调数据
*/ */
fun fetchAppErrorsInfoData(context: Context, result: (ArrayList<AppErrorsInfoBean>) -> Unit) { 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) } wait(CALL_APP_ERRORS_DATA_GET_RESULT) { result(it) }
put(CALL_APP_ERRORS_DATA_GET) put(CALL_APP_ERRORS_DATA_GET)
} }
@@ -298,7 +294,7 @@ object FrameworkTool {
* @param callback 成功后回调 * @param callback 成功后回调
*/ */
fun removeAppErrorsInfoData(context: Context, appErrorsInfo: AppErrorsInfoBean, callback: () -> Unit) { 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() } wait(CALL_APP_ERRORS_DATA_REMOVE_RESULT) { callback() }
put(CALL_APP_ERRORS_DATA_REMOVE, appErrorsInfo) put(CALL_APP_ERRORS_DATA_REMOVE, appErrorsInfo)
} }
@@ -310,7 +306,7 @@ object FrameworkTool {
* @param callback 成功后回调 * @param callback 成功后回调
*/ */
fun clearAppErrorsInfoData(context: Context, callback: () -> Unit) { 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() } wait(CALL_APP_ERRORS_DATA_CLEAR_RESULT) { callback() }
put(CALL_APP_ERRORS_DATA_CLEAR) put(CALL_APP_ERRORS_DATA_CLEAR)
} }
@@ -323,7 +319,7 @@ object FrameworkTool {
* @param callback 成功后回调 * @param callback 成功后回调
*/ */
fun mutedErrorsIfUnlock(context: Context, packageName: String, callback: () -> Unit) { 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() } wait(CALL_MUTED_ERRORS_IF_UNLOCK_RESULT) { callback() }
put(CALL_MUTED_ERRORS_IF_UNLOCK, packageName) put(CALL_MUTED_ERRORS_IF_UNLOCK, packageName)
} }
@@ -336,7 +332,7 @@ object FrameworkTool {
* @param callback 成功后回调 * @param callback 成功后回调
*/ */
fun mutedErrorsIfRestart(context: Context, packageName: String, callback: () -> Unit) { 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() } wait(CALL_MUTED_ERRORS_IF_RESTART_RESULT) { callback() }
put(CALL_MUTED_ERRORS_IF_RESTART, packageName) put(CALL_MUTED_ERRORS_IF_RESTART, packageName)
} }
@@ -348,7 +344,7 @@ object FrameworkTool {
* @param result 回调数据 * @param result 回调数据
*/ */
fun fetchMutedErrorsAppsData(context: Context, result: (ArrayList<MutedErrorsAppBean>) -> Unit) { 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) } wait(CALL_MUTED_ERRORS_APP_DATA_GET_RESULT) { result(it) }
put(CALL_MUTED_ERRORS_APP_DATA_GET) put(CALL_MUTED_ERRORS_APP_DATA_GET)
} }
@@ -361,7 +357,7 @@ object FrameworkTool {
* @param callback 成功后回调 * @param callback 成功后回调
*/ */
fun unmuteErrorsApp(context: Context, mutedErrorsApp: MutedErrorsAppBean, callback: () -> Unit) { 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() } wait(CALL_UNMUTE_ERRORS_APP_DATA_RESULT) { callback() }
put(CALL_UNMUTE_ERRORS_APP_DATA, mutedErrorsApp) put(CALL_UNMUTE_ERRORS_APP_DATA, mutedErrorsApp)
} }
@@ -373,7 +369,7 @@ object FrameworkTool {
* @param callback 成功后回调 * @param callback 成功后回调
*/ */
fun unmuteAllErrorsApps(context: Context, callback: () -> Unit) { 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() } wait(CALL_UNMUTE_ALL_ERRORS_APPS_DATA_RESULT) { callback() }
put(CALL_UNMUTE_ALL_ERRORS_APPS_DATA) put(CALL_UNMUTE_ALL_ERRORS_APPS_DATA)
} }
@@ -386,7 +382,7 @@ object FrameworkTool {
* @param result 回调数据 * @param result 回调数据
*/ */
fun fetchAppListData(context: Context, appFilters: AppFiltersBean, result: (ArrayList<AppInfoBean>) -> Unit) { 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) } wait(CALL_APP_LIST_DATA_GET_RESULT) { result(it) }
put(CALL_APP_LIST_DATA_GET, appFilters) put(CALL_APP_LIST_DATA_GET, appFilters)
} }

View File

@@ -17,7 +17,7 @@
* and eula along with this software. If not, see * and eula along with this software. If not, see
* <https://www.gnu.org/licenses/> * <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 package com.fankes.apperrorstracking.utils.tool
@@ -26,14 +26,18 @@ import android.content.Context
import android.icu.text.SimpleDateFormat import android.icu.text.SimpleDateFormat
import android.icu.util.Calendar import android.icu.util.Calendar
import android.icu.util.TimeZone 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.openBrowser
import com.fankes.apperrorstracking.utils.factory.showDialog 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 org.json.JSONObject
import java.io.IOException import java.io.IOException
import java.io.Serializable import java.io.Serializable
import java.util.* import java.util.Locale
/** /**
* 获取 GitHub Release 最新版本工具类 * 获取 GitHub Release 最新版本工具类
@@ -70,9 +74,9 @@ object GithubReleaseTool {
date = getString("published_at").localTime() date = getString("published_at").localTime()
).apply { ).apply {
fun showUpdate() = context.showDialog { fun showUpdate() = context.showDialog {
title = LocaleString.latestVersion(name) title = locale.latestVersion(name)
msg = LocaleString.latestVersionTip(date, content) msg = locale.latestVersionTip(date, content)
confirmButton(LocaleString.updateNow) { context.openBrowser(htmlUrl) } confirmButton(locale.updateNow) { context.openBrowser(htmlUrl) }
cancelButton() cancelButton()
} }
if (name != version) (context as? Activity?)?.runOnUiThread { if (name != version) (context as? Activity?)?.runOnUiThread {

View File

@@ -0,0 +1,57 @@
/*
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
* Copyright (C) 2017-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()
}
}
}

View File

@@ -17,13 +17,19 @@
* and eula along with this software. If not, see * and eula along with this software. If not, see
* <https://www.gnu.org/licenses/> * <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") @file:Suppress("unused")
package com.fankes.apperrorstracking.utils.tool 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.ZipEntry
import java.util.zip.ZipInputStream import java.util.zip.ZipInputStream
import java.util.zip.ZipOutputStream import java.util.zip.ZipOutputStream

View File

@@ -0,0 +1,36 @@
/*
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
* Copyright (C) 2017-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