mirror of
https://github.com/KitsunePie/AppErrorsTracking.git
synced 2026-02-04 12:17:22 +08:00
Compare commits
116 Commits
copilot/ad
...
1.0.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
20eb229e02
|
|||
|
60a619ab3e
|
|||
|
447793cc7a
|
|||
|
2468e322e1
|
|||
|
30e2d66be1
|
|||
|
b951ebb820
|
|||
|
9027158047
|
|||
|
f4d68ce9ec
|
|||
|
00af888e0e
|
|||
|
59cd027bbe
|
|||
|
8cb57c805e
|
|||
|
a512e4854a
|
|||
|
6aa9ddbf00
|
|||
|
95751dde2d
|
|||
|
9ddbea5ae7
|
|||
|
|
bd3adbdbd0 | ||
|
d0f976f843
|
|||
|
f5bbc312e5
|
|||
|
62c9982e2e
|
|||
|
3c623365f3
|
|||
|
3be12ef72c
|
|||
|
7024a8c521
|
|||
|
66296f9ed4
|
|||
|
27ec4c0218
|
|||
|
2c4e4f5090
|
|||
|
8635ad3874
|
|||
|
be87f5d529
|
|||
|
26462f4d62
|
|||
|
108fd9e4ea
|
|||
|
973b3aa98f
|
|||
|
5697a7c780
|
|||
|
5c0ef34e49
|
|||
|
1582277ffb
|
|||
|
02d5cf2141
|
|||
|
cf7de4e131
|
|||
|
33cca59afa
|
|||
|
ded9da730f
|
|||
|
698d2dc8e0
|
|||
|
f85dd9ee7b
|
|||
|
75db6839bc
|
|||
|
16d3543696
|
|||
|
bc4813e106
|
|||
|
aa50ccb01a
|
|||
|
f908a03311
|
|||
|
a461010b56
|
|||
|
26712b18e4
|
|||
|
81bf56464a
|
|||
|
3e441468ef
|
|||
|
dbe414b487
|
|||
|
775c28a7aa
|
|||
|
9607eaba73
|
|||
|
24e622d778
|
|||
|
7cf7c2930f
|
|||
|
ead17453a7
|
|||
|
a41337a979
|
|||
|
a36362735e
|
|||
|
|
3473430a84 | ||
|
5d9cd8a239
|
|||
|
|
f653dc01f5 | ||
|
|
db4af5499d | ||
|
208814e110
|
|||
|
266be8786d
|
|||
|
dee843b3db
|
|||
|
|
972996a107 | ||
|
|
b01b93282c | ||
|
|
097b752f48 | ||
|
470333fe8e
|
|||
|
786d46069c
|
|||
|
7611445b56
|
|||
|
762c2aa562
|
|||
|
31451dc5ca
|
|||
|
eb88f41001
|
|||
|
d5880671a1
|
|||
|
288e2904b6
|
|||
|
c4d1c42faa
|
|||
|
7348ceba94
|
|||
|
06d96be187
|
|||
|
|
e321a96ba9 | ||
|
|
8454126b55 | ||
|
443220a078
|
|||
|
d097054cb0
|
|||
|
13b23ad59c
|
|||
|
4988ebbf3b
|
|||
|
9fe224fc3a
|
|||
|
0e8d7bc733
|
|||
|
e386e1fa42
|
|||
|
8cabb24937
|
|||
|
8410576deb
|
|||
|
2a99cd8384
|
|||
|
c20064de96
|
|||
|
e2b6fe8a81
|
|||
|
cac96e188e
|
|||
|
5217146bdd
|
|||
|
7fcf8b36cf
|
|||
|
a0489411c4
|
|||
|
6597cd9bfe
|
|||
|
1e38e57347
|
|||
|
174f7ab697
|
|||
|
b0787c1fa3
|
|||
|
5e9fad6023
|
|||
|
652925297a
|
|||
| 9946d35e57 | |||
| eea67af563 | |||
| d42a691537 | |||
| 36a5eb9ef2 | |||
| e66896b9f9 | |||
|
|
8c59eb9465 | ||
|
|
d2896cf938 | ||
|
|
a08289fe22 | ||
|
|
53b8c2ddd9 | ||
|
|
ee4b3896ef | ||
|
|
5350d720c8 | ||
| 0e8742e83a | |||
|
|
24ce434d9f | ||
|
|
0748395586 | ||
|
|
11e1c5395b |
62
.github/workflows/pr_ci.yml
vendored
Normal file
62
.github/workflows/pr_ci.yml
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
name: main
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- '**.txt'
|
||||
- '.github/**'
|
||||
- '!.github/workflows/**'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Pull request check
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Setup cmake
|
||||
uses: jwlawson/actions-setup-cmake@v1.12
|
||||
with:
|
||||
cmake-version: '3.22.1'
|
||||
- name: Prepare Java 11
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 11
|
||||
java-package: jdk
|
||||
- name: Cache Gradle Dependencies
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
~/.gradle/wrapper
|
||||
!~/.gradle/caches/build-cache-*
|
||||
key: gradle-deps-core-${{ hashFiles('**/build.gradle') }}
|
||||
restore-keys: |
|
||||
gradle-deps
|
||||
- name: Cache Gradle Build
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches/build-cache-*
|
||||
key: gradle-builds-core-${{ github.sha }}
|
||||
restore-keys: |
|
||||
gradle-builds
|
||||
- name: Grant execute permission for gradlew
|
||||
run: chmod +x gradlew
|
||||
- name: Build with Gradle
|
||||
run: |
|
||||
./gradlew :app:assembleRelease
|
||||
./gradlew :demo-app:assembleRelease
|
||||
echo "APK_FILE=$(find app/build/outputs/apk/release -name '*.apk')" >> $GITHUB_ENV
|
||||
echo "DEMO_APK_FILE=$(find demo-app/build/outputs/apk/release -name '*.apk')" >> $GITHUB_ENV
|
||||
- name: Upload Artifacts(module)
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
path: ${{ env.APK_FILE }}
|
||||
name: module-release
|
||||
- name: Upload Artifacts(demo-app)
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
path: ${{ env.DEMO_APK_FILE }}
|
||||
name: demo-release
|
||||
63
.github/workflows/push_ci.yml
vendored
Normal file
63
.github/workflows/push_ci.yml
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
name: main
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: [ master ]
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- '**.txt'
|
||||
- '.github/**'
|
||||
- '!.github/workflows/**'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build CI
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Setup cmake
|
||||
uses: jwlawson/actions-setup-cmake@v1.12
|
||||
with:
|
||||
cmake-version: '3.22.1'
|
||||
- name: Prepare Java 11
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 11
|
||||
java-package: jdk
|
||||
- name: Cache Gradle Dependencies
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
~/.gradle/wrapper
|
||||
!~/.gradle/caches/build-cache-*
|
||||
key: gradle-deps-core-${{ hashFiles('**/build.gradle') }}
|
||||
restore-keys: |
|
||||
gradle-deps
|
||||
- name: Cache Gradle Build
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches/build-cache-*
|
||||
key: gradle-builds-core-${{ github.sha }}
|
||||
restore-keys: |
|
||||
gradle-builds
|
||||
- name: Grant execute permission for gradlew
|
||||
run: chmod +x gradlew
|
||||
- name: Build with Gradle
|
||||
run: |
|
||||
./gradlew :app:assembleRelease
|
||||
./gradlew :demo-app:assembleRelease
|
||||
echo "APK_FILE=$(find app/build/outputs/apk/release -name '*.apk')" >> $GITHUB_ENV
|
||||
echo "DEMO_APK_FILE=$(find demo-app/build/outputs/apk/release -name '*.apk')" >> $GITHUB_ENV
|
||||
- name: Upload Artifacts(module)
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
path: ${{ env.APK_FILE }}
|
||||
name: module-release
|
||||
- name: Upload Artifacts(demo-app)
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
path: ${{ env.DEMO_APK_FILE }}
|
||||
name: demo-release
|
||||
13
.gitignore
vendored
13
.gitignore
vendored
@@ -1,3 +1,14 @@
|
||||
# Project exclude paths
|
||||
/.gradle/
|
||||
*.iml
|
||||
.gradle
|
||||
/local.properties
|
||||
.DS_Store
|
||||
/build
|
||||
/captures
|
||||
.externalNativeBuild
|
||||
.cxx
|
||||
local.properties
|
||||
/app/releaseHasController/
|
||||
/app/debug/
|
||||
/app/release/
|
||||
/.idea/
|
||||
|
||||
15
.idea/.gitignore
generated
vendored
15
.idea/.gitignore
generated
vendored
@@ -1,15 +0,0 @@
|
||||
*.iml
|
||||
.gradle
|
||||
/local.properties
|
||||
/.idea/caches
|
||||
/.idea/libraries
|
||||
/.idea/modules.xml
|
||||
/.idea/workspace.xml
|
||||
/.idea/navEditor.xml
|
||||
/.idea/assetWizardSettings.xml
|
||||
.DS_Store
|
||||
/build
|
||||
/captures
|
||||
.externalNativeBuild
|
||||
.cxx
|
||||
local.properties
|
||||
346
.idea/assetWizardSettings.xml
generated
346
.idea/assetWizardSettings.xml
generated
@@ -1,346 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="WizardSettings">
|
||||
<option name="children">
|
||||
<map>
|
||||
<entry key="imageWizard">
|
||||
<value>
|
||||
<PersistentState>
|
||||
<option name="children">
|
||||
<map>
|
||||
<entry key="confirmationStep">
|
||||
<value>
|
||||
<PersistentState>
|
||||
<option name="values">
|
||||
<map>
|
||||
<entry key="resourceDirectory" value="main" />
|
||||
</map>
|
||||
</option>
|
||||
</PersistentState>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="imageAssetPanel">
|
||||
<value>
|
||||
<PersistentState>
|
||||
<option name="children">
|
||||
<map>
|
||||
<entry key="actionbar">
|
||||
<value>
|
||||
<PersistentState>
|
||||
<option name="children">
|
||||
<map>
|
||||
<entry key="clipArt">
|
||||
<value>
|
||||
<PersistentState>
|
||||
<option name="values">
|
||||
<map>
|
||||
<entry key="color" value="000000" />
|
||||
<entry key="imagePath" value="/private/var/folders/zk/0sxgvrg50kdg3pypqyb1md1m0000gn/T/ic_android_black_24dp.xml" />
|
||||
</map>
|
||||
</option>
|
||||
</PersistentState>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="text">
|
||||
<value>
|
||||
<PersistentState>
|
||||
<option name="values">
|
||||
<map>
|
||||
<entry key="color" value="000000" />
|
||||
</map>
|
||||
</option>
|
||||
</PersistentState>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="textAsset">
|
||||
<value>
|
||||
<PersistentState>
|
||||
<option name="values">
|
||||
<map>
|
||||
<entry key="color" value="000000" />
|
||||
</map>
|
||||
</option>
|
||||
</PersistentState>
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
</PersistentState>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="launcher">
|
||||
<value>
|
||||
<PersistentState>
|
||||
<option name="children">
|
||||
<map>
|
||||
<entry key="foregroundClipArt">
|
||||
<value>
|
||||
<PersistentState>
|
||||
<option name="values">
|
||||
<map>
|
||||
<entry key="imagePath" value="/private/var/folders/zk/0sxgvrg50kdg3pypqyb1md1m0000gn/T/ic_android_black_24dp.xml" />
|
||||
</map>
|
||||
</option>
|
||||
</PersistentState>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="foregroundImage">
|
||||
<value>
|
||||
<PersistentState>
|
||||
<option name="values">
|
||||
<map>
|
||||
<entry key="color" value="000000" />
|
||||
<entry key="imagePath" value="$PROJECT_DIR$/app/src/main/res/drawable/ic_baseline_bug_report.xml" />
|
||||
<entry key="scalingPercent" value="60" />
|
||||
<entry key="trimmed" value="true" />
|
||||
</map>
|
||||
</option>
|
||||
</PersistentState>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="foregroundText">
|
||||
<value>
|
||||
<PersistentState>
|
||||
<option name="values">
|
||||
<map>
|
||||
<entry key="color" value="000000" />
|
||||
</map>
|
||||
</option>
|
||||
</PersistentState>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="foregroundTextAsset">
|
||||
<value>
|
||||
<PersistentState>
|
||||
<option name="values">
|
||||
<map>
|
||||
<entry key="color" value="000000" />
|
||||
</map>
|
||||
</option>
|
||||
</PersistentState>
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
<option name="values">
|
||||
<map>
|
||||
<entry key="backgroundAssetType" value="COLOR" />
|
||||
<entry key="backgroundColor" value="ff4400" />
|
||||
</map>
|
||||
</option>
|
||||
</PersistentState>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="launcherLegacy">
|
||||
<value>
|
||||
<PersistentState>
|
||||
<option name="children">
|
||||
<map>
|
||||
<entry key="clipArt">
|
||||
<value>
|
||||
<PersistentState>
|
||||
<option name="values">
|
||||
<map>
|
||||
<entry key="color" value="000000" />
|
||||
<entry key="imagePath" value="/private/var/folders/zk/0sxgvrg50kdg3pypqyb1md1m0000gn/T/ic_android_black_24dp.xml" />
|
||||
</map>
|
||||
</option>
|
||||
</PersistentState>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="text">
|
||||
<value>
|
||||
<PersistentState>
|
||||
<option name="values">
|
||||
<map>
|
||||
<entry key="color" value="000000" />
|
||||
</map>
|
||||
</option>
|
||||
</PersistentState>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="textAsset">
|
||||
<value>
|
||||
<PersistentState>
|
||||
<option name="values">
|
||||
<map>
|
||||
<entry key="color" value="000000" />
|
||||
</map>
|
||||
</option>
|
||||
</PersistentState>
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
</PersistentState>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="notification">
|
||||
<value>
|
||||
<PersistentState>
|
||||
<option name="children">
|
||||
<map>
|
||||
<entry key="clipArt">
|
||||
<value>
|
||||
<PersistentState>
|
||||
<option name="values">
|
||||
<map>
|
||||
<entry key="color" value="000000" />
|
||||
<entry key="imagePath" value="/private/var/folders/zk/0sxgvrg50kdg3pypqyb1md1m0000gn/T/ic_android_black_24dp.xml" />
|
||||
</map>
|
||||
</option>
|
||||
</PersistentState>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="text">
|
||||
<value>
|
||||
<PersistentState>
|
||||
<option name="values">
|
||||
<map>
|
||||
<entry key="color" value="000000" />
|
||||
</map>
|
||||
</option>
|
||||
</PersistentState>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="textAsset">
|
||||
<value>
|
||||
<PersistentState>
|
||||
<option name="values">
|
||||
<map>
|
||||
<entry key="color" value="000000" />
|
||||
</map>
|
||||
</option>
|
||||
</PersistentState>
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
</PersistentState>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="tvBanner">
|
||||
<value>
|
||||
<PersistentState>
|
||||
<option name="children">
|
||||
<map>
|
||||
<entry key="foregroundText">
|
||||
<value>
|
||||
<PersistentState>
|
||||
<option name="values">
|
||||
<map>
|
||||
<entry key="color" value="000000" />
|
||||
</map>
|
||||
</option>
|
||||
</PersistentState>
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
</PersistentState>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="tvChannel">
|
||||
<value>
|
||||
<PersistentState>
|
||||
<option name="children">
|
||||
<map>
|
||||
<entry key="foregroundClipArt">
|
||||
<value>
|
||||
<PersistentState>
|
||||
<option name="values">
|
||||
<map>
|
||||
<entry key="imagePath" value="/private/var/folders/zk/0sxgvrg50kdg3pypqyb1md1m0000gn/T/ic_android_black_24dp.xml" />
|
||||
</map>
|
||||
</option>
|
||||
</PersistentState>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="foregroundImage">
|
||||
<value>
|
||||
<PersistentState>
|
||||
<option name="values">
|
||||
<map>
|
||||
<entry key="color" value="000000" />
|
||||
</map>
|
||||
</option>
|
||||
</PersistentState>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="foregroundText">
|
||||
<value>
|
||||
<PersistentState>
|
||||
<option name="values">
|
||||
<map>
|
||||
<entry key="color" value="000000" />
|
||||
</map>
|
||||
</option>
|
||||
</PersistentState>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="foregroundTextAsset">
|
||||
<value>
|
||||
<PersistentState>
|
||||
<option name="values">
|
||||
<map>
|
||||
<entry key="color" value="000000" />
|
||||
</map>
|
||||
</option>
|
||||
</PersistentState>
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
</PersistentState>
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
</PersistentState>
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
</PersistentState>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="vectorWizard">
|
||||
<value>
|
||||
<PersistentState>
|
||||
<option name="children">
|
||||
<map>
|
||||
<entry key="vectorAssetStep">
|
||||
<value>
|
||||
<PersistentState>
|
||||
<option name="children">
|
||||
<map>
|
||||
<entry key="clipartAsset">
|
||||
<value>
|
||||
<PersistentState>
|
||||
<option name="values">
|
||||
<map>
|
||||
<entry key="url" value="file:$USER_HOME$/Library/Android/sdk/icons/material/materialicons/info/baseline_info_24.xml" />
|
||||
</map>
|
||||
</option>
|
||||
</PersistentState>
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
<option name="values">
|
||||
<map>
|
||||
<entry key="outputName" value="ic_baseline_info" />
|
||||
</map>
|
||||
</option>
|
||||
</PersistentState>
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
</PersistentState>
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
6
.idea/compiler.xml
generated
6
.idea/compiler.xml
generated
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CompilerConfiguration">
|
||||
<bytecodeTargetLevel target="11" />
|
||||
</component>
|
||||
</project>
|
||||
20
.idea/gradle.xml
generated
20
.idea/gradle.xml
generated
@@ -1,20 +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" />
|
||||
</set>
|
||||
</option>
|
||||
</GradleProjectSettings>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
10
.idea/inspectionProfiles/Project_Default.xml
generated
10
.idea/inspectionProfiles/Project_Default.xml
generated
@@ -1,10 +0,0 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
|
||||
<option name="processCode" value="true" />
|
||||
<option name="processLiterals" value="true" />
|
||||
<option name="processComments" value="true" />
|
||||
</inspection_tool>
|
||||
</profile>
|
||||
</component>
|
||||
@@ -1,15 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="Gradle: androidx.activity:activity:1.2.4@aar">
|
||||
<ANNOTATIONS>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/transforms-3/ff7fddaea4f5dc9e017114fd1f0e5657/transformed/activity-1.2.4/annotations.zip!/" />
|
||||
</ANNOTATIONS>
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/transforms-3/ff7fddaea4f5dc9e017114fd1f0e5657/transformed/activity-1.2.4/jars/classes.jar!/" />
|
||||
<root url="file://$USER_HOME$/.gradle/caches/transforms-3/ff7fddaea4f5dc9e017114fd1f0e5657/transformed/activity-1.2.4/AndroidManifest.xml" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.activity/activity/1.2.4/2572345ff3e71e4e97140b01423cb80765b5b3e5/activity-1.2.4-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</component>
|
||||
@@ -1,11 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="Gradle: androidx.annotation:annotation:1.3.0">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.annotation/annotation/1.3.0/21f49f5f9b85fc49de712539f79123119740595/annotation-1.3.0.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.annotation/annotation/1.3.0/661529c7dfdacef81b03e6dd27e5daa73793a9b2/annotation-1.3.0-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</component>
|
||||
@@ -1,13 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="Gradle: androidx.annotation:annotation-experimental:1.1.0@aar">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/transforms-3/b3bfe042dcb85fbf512d7ae4f3a60502/transformed/annotation-experimental-1.1.0/jars/classes.jar!/" />
|
||||
<root url="file://$USER_HOME$/.gradle/caches/transforms-3/b3bfe042dcb85fbf512d7ae4f3a60502/transformed/annotation-experimental-1.1.0/res" />
|
||||
<root url="file://$USER_HOME$/.gradle/caches/transforms-3/b3bfe042dcb85fbf512d7ae4f3a60502/transformed/annotation-experimental-1.1.0/AndroidManifest.xml" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.annotation/annotation-experimental/1.1.0/238eb640abf1ca6c952c7cd2e9423fe7f715713f/annotation-experimental-1.1.0-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</component>
|
||||
@@ -1,16 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="Gradle: androidx.appcompat:appcompat:1.4.1@aar">
|
||||
<ANNOTATIONS>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/transforms-3/e87bd43d330ada434c250dca1f230975/transformed/appcompat-1.4.1/annotations.zip!/" />
|
||||
</ANNOTATIONS>
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/transforms-3/e87bd43d330ada434c250dca1f230975/transformed/appcompat-1.4.1/jars/classes.jar!/" />
|
||||
<root url="file://$USER_HOME$/.gradle/caches/transforms-3/e87bd43d330ada434c250dca1f230975/transformed/appcompat-1.4.1/res" />
|
||||
<root url="file://$USER_HOME$/.gradle/caches/transforms-3/e87bd43d330ada434c250dca1f230975/transformed/appcompat-1.4.1/AndroidManifest.xml" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.appcompat/appcompat/1.4.1/f9e48179ea0c6ac0080a277b9383f74523f56aa2/appcompat-1.4.1-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</component>
|
||||
@@ -1,13 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="Gradle: androidx.appcompat:appcompat-resources:1.4.1@aar">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/transforms-3/633c5188cc9894c171f04484b6147556/transformed/appcompat-resources-1.4.1/jars/classes.jar!/" />
|
||||
<root url="file://$USER_HOME$/.gradle/caches/transforms-3/633c5188cc9894c171f04484b6147556/transformed/appcompat-resources-1.4.1/res" />
|
||||
<root url="file://$USER_HOME$/.gradle/caches/transforms-3/633c5188cc9894c171f04484b6147556/transformed/appcompat-resources-1.4.1/AndroidManifest.xml" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.appcompat/appcompat-resources/1.4.1/8f78838745a125dea62514f7b4f70e8776a6244e/appcompat-resources-1.4.1-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</component>
|
||||
@@ -1,11 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="Gradle: androidx.arch.core:core-common:2.1.0">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.arch.core/core-common/2.1.0/b3152fc64428c9354344bd89848ecddc09b6f07e/core-common-2.1.0.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.arch.core/core-common/2.1.0/80ac2d7c8e6400ce2fbc663cd1a7e1cbef38c4b8/core-common-2.1.0-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</component>
|
||||
@@ -1,12 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="Gradle: androidx.arch.core:core-runtime:2.0.0@aar">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/transforms-3/3cb87a4d7522372012d361f1f1c3ba48/transformed/core-runtime-2.0.0/jars/classes.jar!/" />
|
||||
<root url="file://$USER_HOME$/.gradle/caches/transforms-3/3cb87a4d7522372012d361f1f1c3ba48/transformed/core-runtime-2.0.0/AndroidManifest.xml" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.arch.core/core-runtime/2.0.0/bc41b287c95bc50a3cd27cb1b7cfb301805ba7f1/core-runtime-2.0.0-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</component>
|
||||
@@ -1,12 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="Gradle: androidx.arch.core:core-runtime:2.1.0@aar">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/transforms-3/aedc6423499d7cac5a50807f9496470e/transformed/core-runtime-2.1.0/jars/classes.jar!/" />
|
||||
<root url="file://$USER_HOME$/.gradle/caches/transforms-3/aedc6423499d7cac5a50807f9496470e/transformed/core-runtime-2.1.0/AndroidManifest.xml" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.arch.core/core-runtime/2.1.0/f19886651c9946b39f83d8c184fd0e2ce9f43c16/core-runtime-2.1.0-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</component>
|
||||
@@ -1,11 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="Gradle: androidx.collection:collection:1.1.0">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.collection/collection/1.1.0/1f27220b47669781457de0d600849a5de0e89909/collection-1.1.0.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.collection/collection/1.1.0/bae67b0019fbb38498198fcc2d0282a340b71c5b/collection-1.1.0-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</component>
|
||||
@@ -1,16 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="Gradle: androidx.core:core:1.7.0@aar">
|
||||
<ANNOTATIONS>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/transforms-3/72b2915e73cf37e5e7463ddcdb0a0107/transformed/core-1.7.0/annotations.zip!/" />
|
||||
</ANNOTATIONS>
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/transforms-3/72b2915e73cf37e5e7463ddcdb0a0107/transformed/core-1.7.0/jars/classes.jar!/" />
|
||||
<root url="file://$USER_HOME$/.gradle/caches/transforms-3/72b2915e73cf37e5e7463ddcdb0a0107/transformed/core-1.7.0/res" />
|
||||
<root url="file://$USER_HOME$/.gradle/caches/transforms-3/72b2915e73cf37e5e7463ddcdb0a0107/transformed/core-1.7.0/AndroidManifest.xml" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.core/core/1.7.0/f1c3685231d49092ae301932de1481d9b7a9e025/core-1.7.0-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</component>
|
||||
@@ -1,12 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="Gradle: androidx.cursoradapter:cursoradapter:1.0.0@aar">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/transforms-3/3c2177ac1e7b2c7e0698186a45040366/transformed/cursoradapter-1.0.0/jars/classes.jar!/" />
|
||||
<root url="file://$USER_HOME$/.gradle/caches/transforms-3/3c2177ac1e7b2c7e0698186a45040366/transformed/cursoradapter-1.0.0/AndroidManifest.xml" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.cursoradapter/cursoradapter/1.0.0/1e323083b41c31fd4d45510dfce50614963c3c6c/cursoradapter-1.0.0-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</component>
|
||||
@@ -1,12 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="Gradle: androidx.customview:customview:1.0.0@aar">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/transforms-3/b3f7f1a2db15549da9a6815cc2327aef/transformed/customview-1.0.0/jars/classes.jar!/" />
|
||||
<root url="file://$USER_HOME$/.gradle/caches/transforms-3/b3f7f1a2db15549da9a6815cc2327aef/transformed/customview-1.0.0/AndroidManifest.xml" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.customview/customview/1.0.0/61f6a717d144dff3a6bda413d9abeeb2bca71581/customview-1.0.0-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</component>
|
||||
@@ -1,15 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="Gradle: androidx.drawerlayout:drawerlayout:1.0.0@aar">
|
||||
<ANNOTATIONS>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/transforms-3/474f450235d7f0ef2d512a6c9fdcebb0/transformed/drawerlayout-1.0.0/annotations.zip!/" />
|
||||
</ANNOTATIONS>
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/transforms-3/474f450235d7f0ef2d512a6c9fdcebb0/transformed/drawerlayout-1.0.0/jars/classes.jar!/" />
|
||||
<root url="file://$USER_HOME$/.gradle/caches/transforms-3/474f450235d7f0ef2d512a6c9fdcebb0/transformed/drawerlayout-1.0.0/AndroidManifest.xml" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.drawerlayout/drawerlayout/1.0.0/9ecd4ecb7da215ba4c5c3e00bf8d290dad6f2bc5/drawerlayout-1.0.0-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</component>
|
||||
@@ -1,16 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="Gradle: androidx.fragment:fragment:1.3.6@aar">
|
||||
<ANNOTATIONS>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/transforms-3/dc4f34dd93938f8cbeba9983c19d2e5d/transformed/fragment-1.3.6/annotations.zip!/" />
|
||||
</ANNOTATIONS>
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/transforms-3/dc4f34dd93938f8cbeba9983c19d2e5d/transformed/fragment-1.3.6/jars/classes.jar!/" />
|
||||
<root url="file://$USER_HOME$/.gradle/caches/transforms-3/dc4f34dd93938f8cbeba9983c19d2e5d/transformed/fragment-1.3.6/res" />
|
||||
<root url="file://$USER_HOME$/.gradle/caches/transforms-3/dc4f34dd93938f8cbeba9983c19d2e5d/transformed/fragment-1.3.6/AndroidManifest.xml" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.fragment/fragment/1.3.6/25ece06338d39da1fdc9d8488aa57b5014866918/fragment-1.3.6-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</component>
|
||||
@@ -1,12 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="Gradle: androidx.interpolator:interpolator:1.0.0@aar">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/transforms-3/f99a7047836f7e8890b3d1788f61d16f/transformed/interpolator-1.0.0/jars/classes.jar!/" />
|
||||
<root url="file://$USER_HOME$/.gradle/caches/transforms-3/f99a7047836f7e8890b3d1788f61d16f/transformed/interpolator-1.0.0/AndroidManifest.xml" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.interpolator/interpolator/1.0.0/fefd5e3cbc479b6b4a9532d05688a1e659e8d3d2/interpolator-1.0.0-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</component>
|
||||
@@ -1,11 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="Gradle: androidx.lifecycle:lifecycle-common:2.4.0">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.lifecycle/lifecycle-common/2.4.0/1fdb7349701e9cf2f0a69fc10642b6fef6bb3e12/lifecycle-common-2.4.0.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.lifecycle/lifecycle-common/2.4.0/29609e3d01eaafd06c688c5366154174cf686c12/lifecycle-common-2.4.0-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</component>
|
||||
@@ -1,12 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="Gradle: androidx.lifecycle:lifecycle-livedata:2.0.0@aar">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/transforms-3/634811c3fe27114dcfddbdfc40697d30/transformed/lifecycle-livedata-2.0.0/jars/classes.jar!/" />
|
||||
<root url="file://$USER_HOME$/.gradle/caches/transforms-3/634811c3fe27114dcfddbdfc40697d30/transformed/lifecycle-livedata-2.0.0/AndroidManifest.xml" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.lifecycle/lifecycle-livedata/2.0.0/740ce61935bd789380c01178bd8ce402402ebd2f/lifecycle-livedata-2.0.0-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</component>
|
||||
@@ -1,12 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="Gradle: androidx.lifecycle:lifecycle-livedata-core:2.3.1@aar">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/transforms-3/63103388a5da8a8773b80940fb1a6306/transformed/lifecycle-livedata-core-2.3.1/jars/classes.jar!/" />
|
||||
<root url="file://$USER_HOME$/.gradle/caches/transforms-3/63103388a5da8a8773b80940fb1a6306/transformed/lifecycle-livedata-core-2.3.1/AndroidManifest.xml" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.lifecycle/lifecycle-livedata-core/2.3.1/38ecd5651d87b6db994df01f93fc72d6e59b846a/lifecycle-livedata-core-2.3.1-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</component>
|
||||
@@ -1,13 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="Gradle: androidx.lifecycle:lifecycle-runtime:2.3.1@aar">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/transforms-3/29d1052046ade280414a278045d70da0/transformed/lifecycle-runtime-2.3.1/jars/classes.jar!/" />
|
||||
<root url="file://$USER_HOME$/.gradle/caches/transforms-3/29d1052046ade280414a278045d70da0/transformed/lifecycle-runtime-2.3.1/res" />
|
||||
<root url="file://$USER_HOME$/.gradle/caches/transforms-3/29d1052046ade280414a278045d70da0/transformed/lifecycle-runtime-2.3.1/AndroidManifest.xml" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.lifecycle/lifecycle-runtime/2.3.1/ae7040cf314de81d20ac69f28f5ab6c9a2c0d1ab/lifecycle-runtime-2.3.1-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</component>
|
||||
@@ -1,13 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="Gradle: androidx.lifecycle:lifecycle-runtime:2.4.0@aar">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/transforms-3/854ea43dad3782672630e1d66ab44386/transformed/lifecycle-runtime-2.4.0/jars/classes.jar!/" />
|
||||
<root url="file://$USER_HOME$/.gradle/caches/transforms-3/854ea43dad3782672630e1d66ab44386/transformed/lifecycle-runtime-2.4.0/res" />
|
||||
<root url="file://$USER_HOME$/.gradle/caches/transforms-3/854ea43dad3782672630e1d66ab44386/transformed/lifecycle-runtime-2.4.0/AndroidManifest.xml" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.lifecycle/lifecycle-runtime/2.4.0/ae7040cf314de81d20ac69f28f5ab6c9a2c0d1ab/lifecycle-runtime-2.4.0-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</component>
|
||||
@@ -1,13 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="Gradle: androidx.lifecycle:lifecycle-viewmodel:2.3.1@aar">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/transforms-3/caf5722f648937a9f70639b3c84f2880/transformed/lifecycle-viewmodel-2.3.1/jars/classes.jar!/" />
|
||||
<root url="file://$USER_HOME$/.gradle/caches/transforms-3/caf5722f648937a9f70639b3c84f2880/transformed/lifecycle-viewmodel-2.3.1/res" />
|
||||
<root url="file://$USER_HOME$/.gradle/caches/transforms-3/caf5722f648937a9f70639b3c84f2880/transformed/lifecycle-viewmodel-2.3.1/AndroidManifest.xml" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.lifecycle/lifecycle-viewmodel/2.3.1/55d6fa3541ca02167b0bd62a16fbdaec2a71622/lifecycle-viewmodel-2.3.1-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</component>
|
||||
@@ -1,12 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="Gradle: androidx.lifecycle:lifecycle-viewmodel-savedstate:2.3.1@aar">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/transforms-3/b60f4d1b13682bb6dc24bbbe1183fff2/transformed/lifecycle-viewmodel-savedstate-2.3.1/jars/classes.jar!/" />
|
||||
<root url="file://$USER_HOME$/.gradle/caches/transforms-3/b60f4d1b13682bb6dc24bbbe1183fff2/transformed/lifecycle-viewmodel-savedstate-2.3.1/AndroidManifest.xml" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.lifecycle/lifecycle-viewmodel-savedstate/2.3.1/411f92301435123d502de27b6f3262c062f3bd7a/lifecycle-viewmodel-savedstate-2.3.1-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</component>
|
||||
@@ -1,12 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="Gradle: androidx.loader:loader:1.0.0@aar">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/transforms-3/45f6e341cdf0c3068d8ba4557e463820/transformed/loader-1.0.0/jars/classes.jar!/" />
|
||||
<root url="file://$USER_HOME$/.gradle/caches/transforms-3/45f6e341cdf0c3068d8ba4557e463820/transformed/loader-1.0.0/AndroidManifest.xml" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.loader/loader/1.0.0/b9ef587f3e46c7fe5b00264989764e43ff45cada/loader-1.0.0-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</component>
|
||||
@@ -1,13 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="Gradle: androidx.savedstate:savedstate:1.1.0@aar">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/transforms-3/cf41aa5d0999ed6188b40660c16ce919/transformed/savedstate-1.1.0/jars/classes.jar!/" />
|
||||
<root url="file://$USER_HOME$/.gradle/caches/transforms-3/cf41aa5d0999ed6188b40660c16ce919/transformed/savedstate-1.1.0/res" />
|
||||
<root url="file://$USER_HOME$/.gradle/caches/transforms-3/cf41aa5d0999ed6188b40660c16ce919/transformed/savedstate-1.1.0/AndroidManifest.xml" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.savedstate/savedstate/1.1.0/73464c2c55129727354a95ffa91dc9c2cf0c78b/savedstate-1.1.0-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</component>
|
||||
@@ -1,14 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="Gradle: androidx.test:core:1.4.0@aar">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/transforms-3/530684afcb063b92856794081c808deb/transformed/core-1.4.0/jars/classes.jar!/" />
|
||||
<root url="file://$USER_HOME$/.gradle/caches/transforms-3/530684afcb063b92856794081c808deb/transformed/core-1.4.0/AndroidManifest.xml" />
|
||||
</CLASSES>
|
||||
<JAVADOC>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.test/core/1.4.0/96b4d161def059d92989a352500d05d215c33d5e/core-1.4.0-javadoc.jar!/" />
|
||||
</JAVADOC>
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.test/core/1.4.0/2ba6b5cb5392cc99bdc6fb1f188c95497358fb62/core-1.4.0-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</component>
|
||||
@@ -1,14 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="Gradle: androidx.test.espresso:espresso-core:3.4.0@aar">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/transforms-3/25b87a74d132ecf259cad4eb1c6e187f/transformed/espresso-core-3.4.0/jars/classes.jar!/" />
|
||||
<root url="file://$USER_HOME$/.gradle/caches/transforms-3/25b87a74d132ecf259cad4eb1c6e187f/transformed/espresso-core-3.4.0/AndroidManifest.xml" />
|
||||
</CLASSES>
|
||||
<JAVADOC>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.test.espresso/espresso-core/3.4.0/527848e2722cd3ada150b991c0e620ec4f0a8b/espresso-core-3.4.0-javadoc.jar!/" />
|
||||
</JAVADOC>
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.test.espresso/espresso-core/3.4.0/8c2dc35c200b749281e0fbdc1262359e19a99cc3/espresso-core-3.4.0-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</component>
|
||||
@@ -1,14 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="Gradle: androidx.test.espresso:espresso-idling-resource:3.4.0@aar">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/transforms-3/4ad575eecb739b04fd0f9363a434919d/transformed/espresso-idling-resource-3.4.0/jars/classes.jar!/" />
|
||||
<root url="file://$USER_HOME$/.gradle/caches/transforms-3/4ad575eecb739b04fd0f9363a434919d/transformed/espresso-idling-resource-3.4.0/AndroidManifest.xml" />
|
||||
</CLASSES>
|
||||
<JAVADOC>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.test.espresso/espresso-idling-resource/3.4.0/69a300fcf8de2c433672d07a6c436a19dfb9b8f9/espresso-idling-resource-3.4.0-javadoc.jar!/" />
|
||||
</JAVADOC>
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.test.espresso/espresso-idling-resource/3.4.0/80fd2aa3695bf9e0383d53f2f39016960a0156a6/espresso-idling-resource-3.4.0-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</component>
|
||||
@@ -1,14 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="Gradle: androidx.test.ext:junit:1.1.3@aar">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/transforms-3/18f92a88878eead949c7f51d69cf0f88/transformed/junit-1.1.3/jars/classes.jar!/" />
|
||||
<root url="file://$USER_HOME$/.gradle/caches/transforms-3/18f92a88878eead949c7f51d69cf0f88/transformed/junit-1.1.3/AndroidManifest.xml" />
|
||||
</CLASSES>
|
||||
<JAVADOC>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.test.ext/junit/1.1.3/f138cf897cc1e024dd714073df04f2425d845104/junit-1.1.3-javadoc.jar!/" />
|
||||
</JAVADOC>
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.test.ext/junit/1.1.3/cdf059d469527681d62d3330131eb875b77911d5/junit-1.1.3-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</component>
|
||||
@@ -1,14 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="Gradle: androidx.test:monitor:1.4.0@aar">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/transforms-3/48bb8bfbe072e4520ed48f531a4f9787/transformed/monitor-1.4.0/jars/classes.jar!/" />
|
||||
<root url="file://$USER_HOME$/.gradle/caches/transforms-3/48bb8bfbe072e4520ed48f531a4f9787/transformed/monitor-1.4.0/AndroidManifest.xml" />
|
||||
</CLASSES>
|
||||
<JAVADOC>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.test/monitor/1.4.0/2770e38fa6d39242c2fa2e9d2ca3275b1d9debd8/monitor-1.4.0-javadoc.jar!/" />
|
||||
</JAVADOC>
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.test/monitor/1.4.0/5d892d39aae695079e3ecc7a841336ff3aeaf40/monitor-1.4.0-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</component>
|
||||
@@ -1,14 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="Gradle: androidx.test:runner:1.4.0@aar">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/transforms-3/deb47eff8fcf0f86502169d2d4a391cc/transformed/runner-1.4.0/jars/classes.jar!/" />
|
||||
<root url="file://$USER_HOME$/.gradle/caches/transforms-3/deb47eff8fcf0f86502169d2d4a391cc/transformed/runner-1.4.0/AndroidManifest.xml" />
|
||||
</CLASSES>
|
||||
<JAVADOC>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.test/runner/1.4.0/aee6a62a26d9b413dadc1732e66cc4ef3bcf9d61/runner-1.4.0-javadoc.jar!/" />
|
||||
</JAVADOC>
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.test/runner/1.4.0/11ef580a9f2ea2fba4d060a3121506197bd6b183/runner-1.4.0-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</component>
|
||||
@@ -1,12 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="Gradle: androidx.test.services:storage:1.4.0@aar">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/transforms-3/2564ef0868cf1d1465af6406d9835a99/transformed/storage-1.4.0/jars/classes.jar!/" />
|
||||
<root url="file://$USER_HOME$/.gradle/caches/transforms-3/2564ef0868cf1d1465af6406d9835a99/transformed/storage-1.4.0/AndroidManifest.xml" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.test.services/storage/1.4.0/27088f46bc0ab94523a20ccc56aa27e142662396/storage-1.4.0-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</component>
|
||||
@@ -1,12 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="Gradle: androidx.vectordrawable:vectordrawable:1.1.0@aar">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/transforms-3/36cd5a67e1376b887bbdf36e81817506/transformed/vectordrawable-1.1.0/jars/classes.jar!/" />
|
||||
<root url="file://$USER_HOME$/.gradle/caches/transforms-3/36cd5a67e1376b887bbdf36e81817506/transformed/vectordrawable-1.1.0/AndroidManifest.xml" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.vectordrawable/vectordrawable/1.1.0/1e0694477eed874c50c54b547cc3e5a62a57a62b/vectordrawable-1.1.0-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</component>
|
||||
@@ -1,12 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="Gradle: androidx.vectordrawable:vectordrawable-animated:1.1.0@aar">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/transforms-3/b1cee440f11994ce6e9f5f15372365ba/transformed/vectordrawable-animated-1.1.0/jars/classes.jar!/" />
|
||||
<root url="file://$USER_HOME$/.gradle/caches/transforms-3/b1cee440f11994ce6e9f5f15372365ba/transformed/vectordrawable-animated-1.1.0/AndroidManifest.xml" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.vectordrawable/vectordrawable-animated/1.1.0/871a7705cd03bc246947638c712cdd11378233ff/vectordrawable-animated-1.1.0-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</component>
|
||||
@@ -1,12 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="Gradle: androidx.versionedparcelable:versionedparcelable:1.1.1@aar">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/transforms-3/e3830581dd8c0d21eab866f373570bc5/transformed/versionedparcelable-1.1.1/jars/classes.jar!/" />
|
||||
<root url="file://$USER_HOME$/.gradle/caches/transforms-3/e3830581dd8c0d21eab866f373570bc5/transformed/versionedparcelable-1.1.1/AndroidManifest.xml" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.versionedparcelable/versionedparcelable/1.1.1/d9085927216387af679d18b6f472bc0fc5c7cc81/versionedparcelable-1.1.1-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</component>
|
||||
@@ -1,12 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="Gradle: androidx.viewpager:viewpager:1.0.0@aar">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/transforms-3/42febb75154f2490d556930cb5ce1693/transformed/viewpager-1.0.0/jars/classes.jar!/" />
|
||||
<root url="file://$USER_HOME$/.gradle/caches/transforms-3/42febb75154f2490d556930cb5ce1693/transformed/viewpager-1.0.0/AndroidManifest.xml" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.viewpager/viewpager/1.0.0/db045f92188b9d247d5f556866f8861ab68528f0/viewpager-1.0.0-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</component>
|
||||
@@ -1,9 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="Gradle: com.google.code.findbugs:jsr305:2.0.1">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.google.code.findbugs/jsr305/2.0.1/516c03b21d50a644d538de0f0369c620989cd8f0/jsr305-2.0.1.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
||||
@@ -1,13 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="Gradle: com.highcapable.yukihookapi:api:1.0.86">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.highcapable.yukihookapi/api/1.0.86/f1152098a27bddbd98e9c2039feaae0b86f8a114/api-1.0.86.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.highcapable.yukihookapi/api/1.0.86/e172b99b8e0f1b8a67cec2c411f015efca3f5802/api-1.0.86-javadoc.jar!/" />
|
||||
</JAVADOC>
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.highcapable.yukihookapi/api/1.0.86/71b0345f5384456654d2767a33b02eeb748f7c26/api-1.0.86-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</component>
|
||||
@@ -1,13 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="Gradle: com.squareup:javawriter:2.1.1">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.squareup/javawriter/2.1.1/67ff45d9ae02e583d0f9b3432a5ebbe05c30c966/javawriter-2.1.1.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.squareup/javawriter/2.1.1/f591a105db78771d0a1e7a277b3747556c528c22/javawriter-2.1.1-javadoc.jar!/" />
|
||||
</JAVADOC>
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.squareup/javawriter/2.1.1/5b31387d839a5cdaf5b6de3990da01f7f2b963c5/javawriter-2.1.1-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</component>
|
||||
@@ -1,11 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="Gradle: de.robv.android.xposed:api:82">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/de.robv.android.xposed/api/82/35866b507b360d4789ff389ad7386b6e8bbf6cc4/api-82.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/de.robv.android.xposed/api/82/2030f71764b06b2f39fa1a85660690aa834cfd84/api-82-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</component>
|
||||
@@ -1,13 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="Gradle: javax.inject:javax.inject:1">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/javax.inject/javax.inject/1/6975da39a7040257bd51d21a231b76c915872d38/javax.inject-1.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/javax.inject/javax.inject/1/70ec961c25111ed9015d1af77772d96383c2d238/javax.inject-1-javadoc.jar!/" />
|
||||
</JAVADOC>
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/javax.inject/javax.inject/1/a00123f261762a7c5e0ec916a2c7c8298d29c400/javax.inject-1-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</component>
|
||||
16
.idea/libraries/Gradle__junit_junit_4_12.xml
generated
16
.idea/libraries/Gradle__junit_junit_4_12.xml
generated
@@ -1,16 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="Gradle: junit:junit:4.12">
|
||||
<ANNOTATIONS>
|
||||
<root url="jar://$USER_HOME$/.m2/repository/org/jetbrains/externalAnnotations/junit/junit/4.12-an1/junit-4.12-an1-annotations.zip!/" />
|
||||
</ANNOTATIONS>
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/junit/junit/4.12/2973d150c0dc1fefe998f834810d68f278ea58ec/junit-4.12.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/junit/junit/4.12/941a8be4506c65f0a9001c08812fb7da1e505e21/junit-4.12-javadoc.jar!/" />
|
||||
</JAVADOC>
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/junit/junit/4.12/a6c32b40bf3d76eca54e3c601e5d1470c86fcdfa/junit-4.12-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</component>
|
||||
16
.idea/libraries/Gradle__junit_junit_4_13_2.xml
generated
16
.idea/libraries/Gradle__junit_junit_4_13_2.xml
generated
@@ -1,16 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="Gradle: junit:junit:4.13.2">
|
||||
<ANNOTATIONS>
|
||||
<root url="jar://$USER_HOME$/.m2/repository/org/jetbrains/externalAnnotations/junit/junit/4.12-an1/junit-4.12-an1-annotations.zip!/" />
|
||||
</ANNOTATIONS>
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/junit/junit/4.13.2/8ac9e16d933b6fb43bc7f576336b8f4d7eb5ba12/junit-4.13.2.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/junit/junit/4.13.2/f2f3f384dacd2ade2ddf7aa7e0f4360dfee38672/junit-4.13.2-javadoc.jar!/" />
|
||||
</JAVADOC>
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/junit/junit/4.13.2/33987872a811fe4d4001ed494b07854822257f42/junit-4.13.2-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</component>
|
||||
@@ -1,13 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="Gradle: org.hamcrest:hamcrest-core:1.3">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.hamcrest/hamcrest-core/1.3/42a25dc3219429f0e5d060061f71acb49bf010a0/hamcrest-core-1.3.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.hamcrest/hamcrest-core/1.3/ad09811315f1d4f5756986575b0ea16b99cd686f/hamcrest-core-1.3-javadoc.jar!/" />
|
||||
</JAVADOC>
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.hamcrest/hamcrest-core/1.3/1dc37250fbc78e23a65a67fbbaf71d2e9cbc3c0b/hamcrest-core-1.3-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</component>
|
||||
@@ -1,13 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="Gradle: org.hamcrest:hamcrest-integration:1.3">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.hamcrest/hamcrest-integration/1.3/5de0c73fef18917cd85d0ab70bb23818685e4dfd/hamcrest-integration-1.3.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.hamcrest/hamcrest-integration/1.3/cc5884d4138d3376f574f6a3992acceedfc37bea/hamcrest-integration-1.3-javadoc.jar!/" />
|
||||
</JAVADOC>
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.hamcrest/hamcrest-integration/1.3/ae7787a563e6a1b1f17fd4ac43be3a3c8830cfda/hamcrest-integration-1.3-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</component>
|
||||
@@ -1,13 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="Gradle: org.hamcrest:hamcrest-library:1.3">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.hamcrest/hamcrest-library/1.3/4785a3c21320980282f9f33d0d1264a69040538f/hamcrest-library-1.3.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.hamcrest/hamcrest-library/1.3/4324046c5f99f3dc91b5370899fa3ae65fd137d2/hamcrest-library-1.3-javadoc.jar!/" />
|
||||
</JAVADOC>
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.hamcrest/hamcrest-library/1.3/47a7ee46628ab7133129cd7cef1e92657bc275e/hamcrest-library-1.3-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</component>
|
||||
@@ -1,13 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="Gradle: org.jetbrains:annotations:13.0">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains/annotations/13.0/919f0dfe192fb4e063e7dacadee7f8bb9a2672a9/annotations-13.0.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains/annotations/13.0/73368c3b0887f3adc2c2730dd1b95d7c3781aaf3/annotations-13.0-javadoc.jar!/" />
|
||||
</JAVADOC>
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains/annotations/13.0/5991ca87ef1fb5544943d9abc5a9a37583fabe03/annotations-13.0-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</component>
|
||||
@@ -1,13 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="Gradle: org.jetbrains.kotlin:kotlin-stdlib:1.6.21">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib/1.6.21/11ef67f1900634fd951bad28c53ec957fabbe5b8/kotlin-stdlib-1.6.21.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib/1.6.21/5b8f86fea035328fc9e8c660773037a3401ce25f/kotlin-stdlib-1.6.21-javadoc.jar!/" />
|
||||
</JAVADOC>
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib/1.6.21/bc58085192d5abb48080e3670915133715a33ce0/kotlin-stdlib-1.6.21-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</component>
|
||||
@@ -1,13 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="Gradle: org.jetbrains.kotlin:kotlin-stdlib-common:1.6.21" type="kotlin.common">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-common/1.6.21/5e5b55c26dbc80372a920aef60eb774b714559b8/kotlin-stdlib-common-1.6.21.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-common/1.6.21/5b8f86fea035328fc9e8c660773037a3401ce25f/kotlin-stdlib-common-1.6.21-javadoc.jar!/" />
|
||||
</JAVADOC>
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-common/1.6.21/4161056305b7cdaf52a6fd0c051b06ad03f9bd49/kotlin-stdlib-common-1.6.21-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</component>
|
||||
@@ -1,13 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="Gradle: org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.6.21">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-jdk7/1.6.21/568c1b78a8e17a4f35b31f0a74e2916095ed74c2/kotlin-stdlib-jdk7-1.6.21.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-jdk7/1.6.21/5b8f86fea035328fc9e8c660773037a3401ce25f/kotlin-stdlib-jdk7-1.6.21-javadoc.jar!/" />
|
||||
</JAVADOC>
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-jdk7/1.6.21/78b7fc534a411952d7579a61d02b70fdd34aa56c/kotlin-stdlib-jdk7-1.6.21-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</component>
|
||||
@@ -1,13 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="Gradle: org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.6.21">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-jdk8/1.6.21/eeb4d60d75e9ea9c11200d52974e522793b14fba/kotlin-stdlib-jdk8-1.6.21.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-jdk8/1.6.21/5b8f86fea035328fc9e8c660773037a3401ce25f/kotlin-stdlib-jdk8-1.6.21-javadoc.jar!/" />
|
||||
</JAVADOC>
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-jdk8/1.6.21/2cb762c1a14a3a958fd18d29f59491fa300e42d4/kotlin-stdlib-jdk8-1.6.21-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</component>
|
||||
22
.idea/misc.xml
generated
22
.idea/misc.xml
generated
@@ -1,22 +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/ic_baseline_bug_report.xml" value="0.2295" />
|
||||
<entry key="app/src/main/res/drawable/ic_baseline_bug_report_24.xml" value="0.2295" />
|
||||
<entry key="app/src/main/res/drawable/ic_baseline_close.xml" value="0.2295" />
|
||||
<entry key="app/src/main/res/drawable/ic_baseline_close_24.xml" value="0.2295" />
|
||||
<entry key="app/src/main/res/drawable/ic_baseline_eject_24.xml" value="0.2295" />
|
||||
<entry key="app/src/main/res/drawable/ic_baseline_feedback_24.xml" value="0.2295" />
|
||||
<entry key="app/src/main/res/drawable/ic_baseline_info.xml" value="0.2295" />
|
||||
<entry key="app/src/main/res/drawable/ic_baseline_info_24.xml" value="0.2295" />
|
||||
<entry key="app/src/main/res/drawable/ic_baseline_refresh.xml" value="0.2295" />
|
||||
<entry key="app/src/main/res/drawable/ic_baseline_refresh_24.xml" value="0.2295" />
|
||||
<entry key="app/src/main/res/drawable/ic_launcher_background.xml" value="0.2295" />
|
||||
<entry key="app/src/main/res/layout/activity_main.xml" value="0.42291666666666666" />
|
||||
</map>
|
||||
</option>
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" project-jdk-name="11" project-jdk-type="JavaSDK" />
|
||||
</project>
|
||||
12
.idea/modules.xml
generated
12
.idea/modules.xml
generated
@@ -1,12 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/AppErrorsTracking.iml" filepath="$PROJECT_DIR$/.idea/modules/AppErrorsTracking.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/app/AppErrorsTracking.app.iml" filepath="$PROJECT_DIR$/.idea/modules/app/AppErrorsTracking.app.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/app/AppErrorsTracking.app.androidTest.iml" filepath="$PROJECT_DIR$/.idea/modules/app/AppErrorsTracking.app.androidTest.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/app/AppErrorsTracking.app.main.iml" filepath="$PROJECT_DIR$/.idea/modules/app/AppErrorsTracking.app.main.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/app/AppErrorsTracking.app.unitTest.iml" filepath="$PROJECT_DIR$/.idea/modules/app/AppErrorsTracking.app.unitTest.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
6
.idea/vcs.xml
generated
6
.idea/vcs.xml
generated
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
184
.idea/workspace.xml
generated
184
.idea/workspace.xml
generated
@@ -1,184 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="AndroidLayouts">
|
||||
<shared>
|
||||
<config />
|
||||
</shared>
|
||||
</component>
|
||||
<component name="AutoImportSettings">
|
||||
<option name="autoReloadType" value="NONE" />
|
||||
</component>
|
||||
<component name="ChangeListManager">
|
||||
<list default="true" id="244dd63c-e260-4e57-8056-1e1bfe6d37b8" name="Changes" comment="first commit" />
|
||||
<option name="SHOW_DIALOG" value="false" />
|
||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
||||
<option name="LAST_RESOLUTION" value="IGNORE" />
|
||||
</component>
|
||||
<component name="ExecutionTargetManager" SELECTED_TARGET="device_and_snapshot_combo_box_target[HA1DS4D7]" />
|
||||
<component name="ExternalProjectsData">
|
||||
<projectState path="$PROJECT_DIR$">
|
||||
<ProjectState />
|
||||
</projectState>
|
||||
</component>
|
||||
<component name="FileTemplateManagerImpl">
|
||||
<option name="RECENT_TEMPLATES">
|
||||
<list>
|
||||
<option value="Interface" />
|
||||
<option value="Class" />
|
||||
<option value="Kotlin Class" />
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
<component name="Git.Settings">
|
||||
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
||||
</component>
|
||||
<component name="GitSEFilterConfiguration">
|
||||
<file-type-list>
|
||||
<filtered-out-file-type name="LOCAL_BRANCH" />
|
||||
<filtered-out-file-type name="REMOTE_BRANCH" />
|
||||
<filtered-out-file-type name="TAG" />
|
||||
<filtered-out-file-type name="COMMIT_BY_MESSAGE" />
|
||||
</file-type-list>
|
||||
</component>
|
||||
<component name="MarkdownSettingsMigration">
|
||||
<option name="stateVersion" value="1" />
|
||||
</component>
|
||||
<component name="ProjectId" id="28g3gU33GmC73nlyOD9ynbhnUEm" />
|
||||
<component name="ProjectLevelVcsManager" settingsEditedManually="true" />
|
||||
<component name="ProjectViewState">
|
||||
<option name="showLibraryContents" value="true" />
|
||||
</component>
|
||||
<component name="PropertiesComponent">
|
||||
<property name="RunOnceActivity.OpenProjectViewOnStart" value="true" />
|
||||
<property name="RunOnceActivity.ShowReadmeOnStart" value="true" />
|
||||
<property name="RunOnceActivity.cidr.known.project.marker" value="true" />
|
||||
<property name="android-custom-view/Users/fankes/Library/Android/sdk/sources/android-31/android/widget/LinearLayout.java_SELECTED" value="LinearLayout" />
|
||||
<property name="cidr.known.project.marker" value="true" />
|
||||
<property name="dart.analysis.tool.window.visible" value="false" />
|
||||
<property name="last_opened_file_path" value="$PROJECT_DIR$/app/src/main/java/com/fankes/apperrorstracking/utils/factory" />
|
||||
<property name="settings.editor.selected.configurable" value="project.propVCSSupport.DirectoryMappings" />
|
||||
<property name="show.migrate.to.gradle.popup" value="false" />
|
||||
</component>
|
||||
<component name="RecentsManager">
|
||||
<key name="CopyFile.RECENT_KEYS">
|
||||
<recent name="$PROJECT_DIR$/app/src/main/java/com/fankes/apperrorstracking/utils/factory" />
|
||||
<recent name="$PROJECT_DIR$/app/src/main/java/com/fankes/apperrorstracking/utils" />
|
||||
<recent name="$PROJECT_DIR$/app/src/main/res/values" />
|
||||
</key>
|
||||
<key name="CopyClassDialog.RECENTS_KEY">
|
||||
<recent name="android.os" />
|
||||
</key>
|
||||
</component>
|
||||
<component name="RunManager">
|
||||
<configuration name="app" type="AndroidRunConfigurationType" factoryName="Android App" activateToolWindowBeforeRun="false">
|
||||
<module name="AppErrorsTracking.app.main" />
|
||||
<option name="DEPLOY" value="true" />
|
||||
<option name="DEPLOY_APK_FROM_BUNDLE" value="false" />
|
||||
<option name="DEPLOY_AS_INSTANT" value="false" />
|
||||
<option name="ARTIFACT_NAME" value="" />
|
||||
<option name="PM_INSTALL_OPTIONS" value="" />
|
||||
<option name="ALL_USERS" value="false" />
|
||||
<option name="ALWAYS_INSTALL_WITH_PM" value="false" />
|
||||
<option name="CLEAR_APP_STORAGE" value="false" />
|
||||
<option name="DYNAMIC_FEATURES_DISABLED_LIST" value="" />
|
||||
<option name="ACTIVITY_EXTRA_FLAGS" value="" />
|
||||
<option name="MODE" value="do_nothing" />
|
||||
<option name="CLEAR_LOGCAT" value="false" />
|
||||
<option name="SHOW_LOGCAT_AUTOMATICALLY" value="false" />
|
||||
<option name="INSPECTION_WITHOUT_ACTIVITY_RESTART" value="false" />
|
||||
<option name="TARGET_SELECTION_MODE" value="DEVICE_AND_SNAPSHOT_COMBO_BOX" />
|
||||
<option name="SELECTED_CLOUD_MATRIX_CONFIGURATION_ID" value="-1" />
|
||||
<option name="SELECTED_CLOUD_MATRIX_PROJECT_ID" value="" />
|
||||
<option name="DEBUGGER_TYPE" value="Auto" />
|
||||
<Auto>
|
||||
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
|
||||
<option name="SHOW_STATIC_VARS" value="true" />
|
||||
<option name="WORKING_DIR" value="" />
|
||||
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
|
||||
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
|
||||
</Auto>
|
||||
<Hybrid>
|
||||
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
|
||||
<option name="SHOW_STATIC_VARS" value="true" />
|
||||
<option name="WORKING_DIR" value="" />
|
||||
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
|
||||
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
|
||||
</Hybrid>
|
||||
<Java />
|
||||
<Native>
|
||||
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
|
||||
<option name="SHOW_STATIC_VARS" value="true" />
|
||||
<option name="WORKING_DIR" value="" />
|
||||
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
|
||||
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
|
||||
</Native>
|
||||
<Profilers>
|
||||
<option name="ADVANCED_PROFILING_ENABLED" value="false" />
|
||||
<option name="STARTUP_PROFILING_ENABLED" value="false" />
|
||||
<option name="STARTUP_CPU_PROFILING_ENABLED" value="false" />
|
||||
<option name="STARTUP_CPU_PROFILING_CONFIGURATION_NAME" value="Java/Kotlin Method Sample (legacy)" />
|
||||
<option name="STARTUP_NATIVE_MEMORY_PROFILING_ENABLED" value="false" />
|
||||
<option name="NATIVE_MEMORY_SAMPLE_RATE_BYTES" value="2048" />
|
||||
<option name="PROFILING_MODE" value="NOT_SET" />
|
||||
</Profilers>
|
||||
<option name="DEEP_LINK" value="" />
|
||||
<option name="ACTIVITY_CLASS" value="" />
|
||||
<option name="SEARCH_ACTIVITY_IN_GLOBAL_SCOPE" value="false" />
|
||||
<option name="SKIP_ACTIVITY_VALIDATION" value="false" />
|
||||
<method v="2">
|
||||
<option name="Android.Gradle.BeforeRunTask" enabled="true" />
|
||||
</method>
|
||||
</configuration>
|
||||
<configuration default="true" type="JetRunConfigurationType">
|
||||
<method v="2">
|
||||
<option name="Make" enabled="true" />
|
||||
</method>
|
||||
</configuration>
|
||||
<configuration default="true" type="KotlinStandaloneScriptRunConfigurationType">
|
||||
<option name="filePath" />
|
||||
<method v="2">
|
||||
<option name="Make" enabled="true" />
|
||||
</method>
|
||||
</configuration>
|
||||
</component>
|
||||
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
|
||||
<component name="TaskManager">
|
||||
<task active="true" id="Default" summary="Default task">
|
||||
<changelist id="244dd63c-e260-4e57-8056-1e1bfe6d37b8" name="Changes" comment="" />
|
||||
<created>1651624512624</created>
|
||||
<option name="number" value="Default" />
|
||||
<option name="presentableId" value="Default" />
|
||||
<updated>1651624512624</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00001" summary="first commit">
|
||||
<created>1651859135240</created>
|
||||
<option name="number" value="00001" />
|
||||
<option name="presentableId" value="LOCAL-00001" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1651859135240</updated>
|
||||
</task>
|
||||
<option name="localTasksCounter" value="2" />
|
||||
<servers />
|
||||
</component>
|
||||
<component name="Vcs.Log.Tabs.Properties">
|
||||
<option name="TAB_STATES">
|
||||
<map>
|
||||
<entry key="MAIN">
|
||||
<value>
|
||||
<State />
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
</component>
|
||||
<component name="VcsManagerConfiguration">
|
||||
<option name="CHECK_CODE_SMELLS_BEFORE_PROJECT_COMMIT" value="false" />
|
||||
<option name="CHECK_NEW_TODO" value="false" />
|
||||
<ignored-roots>
|
||||
<path value="$PROJECT_DIR$" />
|
||||
</ignored-roots>
|
||||
<MESSAGE value="first commit" />
|
||||
<option name="LAST_COMMIT_MESSAGE" value="first commit" />
|
||||
</component>
|
||||
</project>
|
||||
661
LICENSE
Normal file
661
LICENSE
Normal file
@@ -0,0 +1,661 @@
|
||||
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
Version 3, 19 November 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU Affero General Public License is a free, copyleft license for
|
||||
software and other kinds of works, specifically designed to ensure
|
||||
cooperation with the community in the case of network server software.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
our General Public Licenses are intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
Developers that use our General Public Licenses protect your rights
|
||||
with two steps: (1) assert copyright on the software, and (2) offer
|
||||
you this License which gives you legal permission to copy, distribute
|
||||
and/or modify the software.
|
||||
|
||||
A secondary benefit of defending all users' freedom is that
|
||||
improvements made in alternate versions of the program, if they
|
||||
receive widespread use, become available for other developers to
|
||||
incorporate. Many developers of free software are heartened and
|
||||
encouraged by the resulting cooperation. However, in the case of
|
||||
software used on network servers, this result may fail to come about.
|
||||
The GNU General Public License permits making a modified version and
|
||||
letting the public access it on a server without ever releasing its
|
||||
source code to the public.
|
||||
|
||||
The GNU Affero General Public License is designed specifically to
|
||||
ensure that, in such cases, the modified source code becomes available
|
||||
to the community. It requires the operator of a network server to
|
||||
provide the source code of the modified version running there to the
|
||||
users of that server. Therefore, public use of a modified version, on
|
||||
a publicly accessible server, gives the public access to the source
|
||||
code of the modified version.
|
||||
|
||||
An older license, called the Affero General Public License and
|
||||
published by Affero, was designed to accomplish similar goals. This is
|
||||
a different license, not a version of the Affero GPL, but Affero has
|
||||
released a new version of the Affero GPL which permits relicensing under
|
||||
this license.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU Affero General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Remote Network Interaction; Use with the GNU General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, if you modify the
|
||||
Program, your modified version must prominently offer all users
|
||||
interacting with it remotely through a computer network (if your version
|
||||
supports such interaction) an opportunity to receive the Corresponding
|
||||
Source of your version by providing access to the Corresponding Source
|
||||
from a network server at no charge, through some standard or customary
|
||||
means of facilitating copying of software. This Corresponding Source
|
||||
shall include the Corresponding Source for any work covered by version 3
|
||||
of the GNU General Public License that is incorporated pursuant to the
|
||||
following paragraph.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the work with which it is combined will remain governed by version
|
||||
3 of the GNU General Public License.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU Affero General Public License from time to time. Such new versions
|
||||
will be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU Affero General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU Affero General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU Affero General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If your software can interact with users remotely through a computer
|
||||
network, you should also make sure that it provides a way for users to
|
||||
get its source. For example, if your program is a web application, its
|
||||
interface could display a "Source" link that leads users to an archive
|
||||
of the code. There are many ways you could offer source, and different
|
||||
solutions will be better for different programs; see section 13 for the
|
||||
specific requirements.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU AGPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
102
README.md
102
README.md
@@ -1,44 +1,102 @@
|
||||
# AppErrorsTracking
|
||||
|
||||
[](https://github.com/KitsunePie/AppErrorsTracking)
|
||||
[](https://github.com/KitsunePie/AppErrorsTracking/blob/master/LICENSE)
|
||||
[](https://github.com/KitsunePie/AppErrorsTracking/releases)
|
||||
[](https://github.com/KitsunePie/AppErrorsTracking/releases)
|
||||
[](https://github.com/Xposed-Modules-Repo/com.fankes.apperrorstracking/releases)
|
||||
<br/><br/>
|
||||
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 module is specially designed for Android developers.
|
||||
|
||||
When it is possible that the computer cannot be connected and ADB cannot be performed, this module can be used to quickly capture any exception
|
||||
of any installed apps, so as to quickly locate the problem.
|
||||
|
||||
The error log of apps crashing is an invaluable asset for developers. If you are not a developer, you can still install this module to provide
|
||||
developers with more exception information to quickly solve problems.
|
||||
|
||||
> Minimum support Android 8.1
|
||||
|
||||
**应用异常跟踪**
|
||||
|
||||
Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
|
||||
|
||||
为原生 FC 对话框增加更多功能并修复国内定制 ROM 删除 FC 对话框的问题,给 Android 开发者带来更好的体验。
|
||||
|
||||
## Project Reason
|
||||
|
||||
我实在是不能理解,国内 ROM 除了 MIUI(稳定版除外) 都选择了删除应用程序崩溃的对话框(FC 对话框),我曾以为这一直是一个特性,直到我去反编译了系统框架,才确认确实是被删掉了。
|
||||
|
||||
难道产品经理认为,让用户看不到错误,应用直接闪退,逃避就是最好的解决方案吗,还是说<u>另有隐情</u>呢?
|
||||
|
||||
## Feature
|
||||
|
||||
此项目为 Xposed 模块,可用在任何 Android 系统中,目前仅在 **LSPosed** 中测试通过。
|
||||
|
||||
- 重新定制应用崩溃错误对话框
|
||||
此模块专为 Android 开发者而打造。
|
||||
|
||||
- “错误详情”按钮功能,可查看具体的异常堆栈
|
||||
在可能的无法连接电脑,不能进行 ADB 调试的时候,可通过此模块来快速捕获任意已安装应用的任意异常,以便快速定位问题。
|
||||
|
||||
- “应用信息”按钮功能(原生功能),点击可打开当前崩溃的应用详情页面
|
||||
应用发生崩溃的错误日志对开发者来说是无价的财富,若你不是开发者,你依然可以安装此模块,以便给开发者提供更多异常信息快速解决问题。
|
||||
|
||||
- “重新启动”按钮功能(原生功能),在首次崩溃可点击按钮重新启动应用
|
||||
> 最低支持 Android 8.1
|
||||
|
||||
- “屡次停止运行”显示(原生功能)
|
||||
## Project Reason
|
||||
|
||||
- 对话框支持 Android 10 及以上系统的深色模式
|
||||
I really can't understand, except for MIUI (except stable version), Android ROMs in mainland China have chosen to delete the dialog box (FC
|
||||
dialog) of apps crashes. I thought this was always a feature until I decompiled the system. frame, only to confirm that it was indeed deleted.
|
||||
|
||||
## Future
|
||||
Does the product manager think that it is the best solution to let the user not see the error, and the apps will crash and exit directly, or is
|
||||
there another **hidden secret**?
|
||||
|
||||
此项目依然在开发中,现在未解决的问题和包含的问题如下
|
||||
**项目缘由**
|
||||
|
||||
- “错误详情”按钮现在是无效的,还在开发
|
||||
我实在是不能理解,国内 ROM 除了 MIUI(稳定版除外) 都选择了删除应用程序崩溃的对话框(FC 对话框),我曾以为这一直是一个特性,直到我去反编译了系统框架,才确认确实是被删掉了。
|
||||
|
||||
- 后台进程可能依然会弹出崩溃对话框且开发者选项里的设置无效,还在排查
|
||||
难道产品经理认为,让用户看不到错误,应用直接闪退,逃避就是最好的解决方案吗,还是说**另有隐情**呢?
|
||||
|
||||
- 无法启动后台进程和服务,是否在一定条件隐藏“重新打开”按钮的问题
|
||||
## Woking Principle
|
||||
|
||||
- 暂不支持国际化语言,Chinese only
|
||||
Unlike `Thread.UncaughtExceptionHandler`, we use the native method to capture apps errors in all directions by injecting the system framework,
|
||||
without generating additional registration monitoring, which is better than the original exception monitoring in performance.
|
||||
|
||||
At the same time, the system-level exception capture can also capture the `stack trace` of the native platform.
|
||||
|
||||
**工作原理**
|
||||
|
||||
不同于 `Thread.UncaughtExceptionHandler`,我们通过注入系统框架,使用原生方式全方位捕获应用异常,不会产生额外的注册监听,在性能上相比原始的异常监听会更好。
|
||||
|
||||
同时系统级别的异常捕获还可捕获原生层的 `stack trace`。
|
||||
|
||||
## Feature
|
||||
|
||||
- Completely replaces the system's apps errors dialog
|
||||
|
||||
- Logs exceptions for each apps and persists until restart
|
||||
|
||||
- Copy, share, export errors stack trace functions
|
||||
|
||||
- Errors history record function, which can be entered through the notification bar tile "errors history record" and the main interface of the
|
||||
module
|
||||
|
||||
- Apps errors statistics function
|
||||
|
||||
- Errors display function for multi-process apps
|
||||
|
||||
**功能**
|
||||
|
||||
- 完全取代系统的应用错误对话框
|
||||
|
||||
- 记录每个应用的异常,直到重新启动前持续保留
|
||||
|
||||
- 复制、分享、导出异常堆栈功能
|
||||
|
||||
- 异常历史记录功能,可通过通知栏磁贴“异常历史记录”进入和模块主界面进入
|
||||
|
||||
- 应用异常统计功能
|
||||
|
||||
- 多进程应用的异常显示功能
|
||||
|
||||
## Translation contribution
|
||||
|
||||
Contributions to this project are welcome to translate it into your country's language.
|
||||
|
||||
**翻译贡献**
|
||||
|
||||
欢迎为此项目做出贡献,将其翻译为您国家的语言。
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'org.jetbrains.kotlin.android'
|
||||
id 'com.google.devtools.ksp' version '1.6.21-1.0.5'
|
||||
id 'com.google.devtools.ksp' version '1.7.0-1.0.6'
|
||||
}
|
||||
|
||||
android {
|
||||
namespace 'com.fankes.apperrorstracking'
|
||||
compileSdk 31
|
||||
compileSdk 32
|
||||
|
||||
signingConfigs {
|
||||
debug {
|
||||
@@ -21,8 +21,8 @@ android {
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.fankes.apperrorstracking"
|
||||
minSdk 21
|
||||
targetSdk 31
|
||||
minSdk 27
|
||||
targetSdk 32
|
||||
versionCode rootProject.ext.appVersionCode
|
||||
versionName rootProject.ext.appVersionName
|
||||
|
||||
@@ -32,6 +32,8 @@ android {
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled rootProject.ext.enableR8
|
||||
shrinkResources rootProject.ext.enableR8
|
||||
zipAlignEnabled rootProject.ext.enableR8
|
||||
signingConfig signingConfigs.debug
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
@@ -48,28 +50,22 @@ android {
|
||||
'-Xno-receiver-assertions'
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
/** 移除无效耗时 lint Task */
|
||||
tasks.whenTaskAdded {
|
||||
task -> if (task.name == "lintVitalRelease") task.enabled = false
|
||||
}
|
||||
|
||||
/** 移除无效耗时 lint Task */
|
||||
tasks.whenTaskAdded {
|
||||
task -> if (task.name == "lintVitalAnalyzeRelease") task.enabled = false
|
||||
}
|
||||
|
||||
/** 移除无效耗时 lint Task */
|
||||
tasks.whenTaskAdded {
|
||||
task -> if (task.name == "lintVitalReportRelease") task.enabled = false
|
||||
buildFeatures {
|
||||
viewBinding true
|
||||
}
|
||||
lintOptions {
|
||||
checkReleaseBuilds false
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly 'de.robv.android.xposed:api:82'
|
||||
implementation 'com.highcapable.yukihookapi:api:1.0.86'
|
||||
ksp 'com.highcapable.yukihookapi:ksp-xposed:1.0.86'
|
||||
implementation 'androidx.appcompat:appcompat:1.4.1'
|
||||
implementation 'com.highcapable.yukihookapi:api:1.0.92'
|
||||
ksp 'com.highcapable.yukihookapi:ksp-xposed:1.0.92'
|
||||
implementation "com.github.topjohnwu.libsu:core:3.1.2"
|
||||
implementation 'androidx.core:core-ktx:1.8.0'
|
||||
implementation 'androidx.appcompat:appcompat:1.4.2'
|
||||
implementation 'com.google.android.material:material:1.6.1'
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
|
||||
|
||||
4
app/proguard-rules.pro
vendored
4
app/proguard-rules.pro
vendored
@@ -37,4 +37,6 @@
|
||||
-assumenosideeffects class kotlin.jvm.internal.Intrinsics {
|
||||
public static *** throwUninitializedProperty(...);
|
||||
public static *** throwUninitializedPropertyAccessException(...);
|
||||
}
|
||||
}
|
||||
|
||||
-keep class com.fankes.apperrorstracking.databinding**{*;}
|
||||
@@ -1,7 +1,13 @@
|
||||
<?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="android.permission.QUERY_ALL_PACKAGES"
|
||||
tools:ignore="QueryAllPackagesPermission" />
|
||||
|
||||
<application
|
||||
android:name=".application.AppErrorsApplication"
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
@@ -14,12 +20,82 @@
|
||||
android:value="true" />
|
||||
<meta-data
|
||||
android:name="xposeddescription"
|
||||
android:value="为原生 FC 对话框增加更多功能并修复国内定制 ROM 删除 FC 对话框的问题,给 Android 开发者带来更好的体验。\n开发者:酷安 @星夜不荟" />
|
||||
android:value="@string/xposed_desc" />
|
||||
<meta-data
|
||||
android:name="xposedminversion"
|
||||
android:value="93" />
|
||||
<meta-data
|
||||
android:name="xposedscope"
|
||||
android:resource="@array/module_scope" />
|
||||
|
||||
<activity
|
||||
android:name=".ui.activity.main.MainActivity"
|
||||
android:exported="true"
|
||||
android:screenOrientation="behind">
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="de.robv.android.xposed.category.MODULE_SETTINGS" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity-alias
|
||||
android:name=".Home"
|
||||
android:exported="true"
|
||||
android:label="@string/app_name"
|
||||
android:screenOrientation="behind"
|
||||
android:targetActivity=".ui.activity.main.MainActivity">
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity-alias>
|
||||
|
||||
<activity
|
||||
android:name=".ui.activity.main.ConfigureActivity"
|
||||
android:exported="false"
|
||||
android:screenOrientation="behind" />
|
||||
|
||||
<activity
|
||||
android:name=".ui.activity.errors.AppErrorsRecordActivity"
|
||||
android:exported="true"
|
||||
android:screenOrientation="behind" />
|
||||
|
||||
<activity
|
||||
android:name=".ui.activity.errors.AppErrorsMutedActivity"
|
||||
android:exported="false"
|
||||
android:screenOrientation="behind" />
|
||||
|
||||
<activity
|
||||
android:name=".ui.activity.errors.AppErrorsDisplayActivity"
|
||||
android:excludeFromRecents="true"
|
||||
android:exported="true"
|
||||
android:label="@string/empty_lable"
|
||||
android:launchMode="singleTask"
|
||||
android:screenOrientation="behind"
|
||||
android:taskAffinity=":display"
|
||||
android:theme="@style/Theme.AppErrorsTracking.Translucent" />
|
||||
|
||||
<activity
|
||||
android:name=".ui.activity.errors.AppErrorsDetailActivity"
|
||||
android:exported="true"
|
||||
android:launchMode="singleTask"
|
||||
android:screenOrientation="behind"
|
||||
android:taskAffinity=":detail" />
|
||||
|
||||
<service
|
||||
android:name=".service.QuickStartTileService"
|
||||
android:exported="true"
|
||||
android:icon="@drawable/ic_debug"
|
||||
android:label="@string/errors_record"
|
||||
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.service.quicksettings.action.QS_TILE" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
</application>
|
||||
</manifest>
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 8.6 KiB After Width: | Height: | Size: 12 KiB |
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
|
||||
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
|
||||
* https://github.com/KitsunePie/AppErrorsTracking
|
||||
*
|
||||
* This software is non-free but opensource software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Affero General Public License
|
||||
* as published by the Free Software Foundation; either
|
||||
* version 3 of the License, or any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/5/10.
|
||||
*/
|
||||
package com.fankes.apperrorstracking.application
|
||||
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import com.fankes.apperrorstracking.locale.LocaleString
|
||||
import com.highcapable.yukihookapi.hook.xposed.application.ModuleApplication
|
||||
|
||||
class AppErrorsApplication : ModuleApplication() {
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
/** 跟随系统夜间模式 */
|
||||
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
|
||||
/** 绑定 I18n */
|
||||
LocaleString.bind(instance = this)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
|
||||
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
|
||||
* https://github.com/KitsunePie/AppErrorsTracking
|
||||
*
|
||||
* This software is non-free but opensource software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Affero General Public License
|
||||
* as published by the Free Software Foundation; either
|
||||
* version 3 of the License, or any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/6/1.
|
||||
*/
|
||||
package com.fankes.apperrorstracking.bean
|
||||
|
||||
import java.io.Serializable
|
||||
|
||||
/**
|
||||
* 应用异常信息显示 bean
|
||||
* @param packageName APP 包名
|
||||
* @param processName APP 进程名
|
||||
* @param appName APP 名称
|
||||
* @param title 标题
|
||||
* @param isShowAppInfoButton 是否显示应用信息按钮
|
||||
* @param isShowCloseAppButton 是否显示关闭应用按钮
|
||||
* @param isShowReopenButton 是否显示重新打开按钮
|
||||
*/
|
||||
data class AppErrorsDisplayBean(
|
||||
var packageName: String,
|
||||
var processName: String,
|
||||
var appName: String,
|
||||
var title: String,
|
||||
var isShowAppInfoButton: Boolean,
|
||||
var isShowCloseAppButton: Boolean,
|
||||
var isShowReopenButton: Boolean
|
||||
) : Serializable
|
||||
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
|
||||
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
|
||||
* https://github.com/KitsunePie/AppErrorsTracking
|
||||
*
|
||||
* This software is non-free but opensource software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Affero General Public License
|
||||
* as published by the Free Software Foundation; either
|
||||
* version 3 of the License, or any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/5/10.
|
||||
*/
|
||||
package com.fankes.apperrorstracking.bean
|
||||
|
||||
import android.app.ApplicationErrorReport
|
||||
import android.os.Build
|
||||
import com.fankes.apperrorstracking.locale.LocaleString
|
||||
import com.fankes.apperrorstracking.utils.factory.difference
|
||||
import java.io.Serializable
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* 应用异常信息 bean
|
||||
* @param packageName 包名
|
||||
* @param isNativeCrash 是否为原生层异常
|
||||
* @param exceptionClassName 异常类名
|
||||
* @param exceptionMessage 异常信息
|
||||
* @param throwClassName 抛出异常的类名
|
||||
* @param throwFileName 抛出异常的文件名
|
||||
* @param throwMethodName 抛出异常的方法名
|
||||
* @param throwLineNumber 抛出异常的行号
|
||||
* @param stackTrace 异常堆栈
|
||||
* @param timestamp 记录时间戳
|
||||
*/
|
||||
data class AppErrorsInfoBean(
|
||||
var packageName: String,
|
||||
var isNativeCrash: Boolean,
|
||||
var exceptionClassName: String,
|
||||
var exceptionMessage: String,
|
||||
var throwFileName: String,
|
||||
var throwClassName: String,
|
||||
var throwMethodName: String,
|
||||
var throwLineNumber: Int,
|
||||
var stackTrace: String,
|
||||
var timestamp: Long,
|
||||
) : Serializable {
|
||||
|
||||
companion object {
|
||||
|
||||
/**
|
||||
* 从 [ApplicationErrorReport.CrashInfo] 克隆
|
||||
* @param packageName APP 包名
|
||||
* @param crashInfo [ApplicationErrorReport.CrashInfo]
|
||||
* @return [AppErrorsInfoBean]
|
||||
*/
|
||||
fun clone(packageName: String?, crashInfo: ApplicationErrorReport.CrashInfo?) =
|
||||
(crashInfo?.exceptionClassName?.lowercase() == "native crash").let { isNativeCrash ->
|
||||
AppErrorsInfoBean(
|
||||
packageName = packageName ?: "null",
|
||||
isNativeCrash = isNativeCrash,
|
||||
exceptionClassName = crashInfo?.exceptionClassName ?: "null",
|
||||
exceptionMessage = if (isNativeCrash) crashInfo?.stackTrace.let {
|
||||
if (it?.contains(other = "Abort message: '") == true)
|
||||
runCatching { it.split("Abort message: '")[1].split("'")[0] }.getOrNull()
|
||||
?: crashInfo?.exceptionMessage ?: "null"
|
||||
else crashInfo?.exceptionMessage ?: "null"
|
||||
} else crashInfo?.exceptionMessage ?: "null",
|
||||
throwFileName = crashInfo?.throwFileName ?: "null",
|
||||
throwClassName = crashInfo?.throwClassName ?: "null",
|
||||
throwMethodName = crashInfo?.throwMethodName ?: "null",
|
||||
throwLineNumber = crashInfo?.throwLineNumber ?: -1,
|
||||
stackTrace = crashInfo?.stackTrace?.trim() ?: "null",
|
||||
timestamp = System.currentTimeMillis()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取异常本地化经过时间
|
||||
* @return [String]
|
||||
*/
|
||||
val crossTime
|
||||
get() = timestamp.difference(
|
||||
now = LocaleString.momentAgo,
|
||||
second = LocaleString.secondAgo,
|
||||
minute = LocaleString.minuteAgo,
|
||||
hour = LocaleString.hourAgo,
|
||||
day = LocaleString.dayAgo,
|
||||
month = LocaleString.monthAgo,
|
||||
year = LocaleString.yearAgo
|
||||
)
|
||||
|
||||
/**
|
||||
* 获取异常本地化量化时间
|
||||
* @return [String]
|
||||
*/
|
||||
val dateTime get() = SimpleDateFormat.getDateTimeInstance().format(Date(timestamp)) ?: "DateTime not found"
|
||||
|
||||
/**
|
||||
* 获取异常堆栈分享模板
|
||||
* @return [String]
|
||||
*/
|
||||
val stackOutputShareContent
|
||||
get() = "Generated by AppErrorsTracking\n" +
|
||||
"Project Url: https://github.com/KitsunePie/AppErrorsTracking\n" +
|
||||
"===============\n" +
|
||||
"[Device Brand]: ${Build.BRAND}\n" +
|
||||
"[Device Model]: ${Build.MODEL}\n" +
|
||||
"[Display]: ${Build.DISPLAY}\n" +
|
||||
"[Android Version]: ${Build.VERSION.RELEASE}\n" +
|
||||
"[API Version]: ${Build.VERSION.SDK_INT}\n" +
|
||||
"[Package Name]: $packageName\n" +
|
||||
"[Error Type]: ${if (isNativeCrash) "Native" else "Jvm"}\n" +
|
||||
"[Crash Time]: $dateTime\n" +
|
||||
"[Stack Trace]:\n" +
|
||||
stackTrace
|
||||
|
||||
/**
|
||||
* 获取异常堆栈文件模板
|
||||
* @return [String]
|
||||
*/
|
||||
val stackOutputFileContent
|
||||
get() = "================================================================\n" +
|
||||
" Generated by AppErrorsTracking\n" +
|
||||
" Project Url: https://github.com/KitsunePie/AppErrorsTracking\n" +
|
||||
"================================================================\n" +
|
||||
"[Device Brand]: ${Build.BRAND}\n" +
|
||||
"[Device Model]: ${Build.MODEL}\n" +
|
||||
"[Display]: ${Build.DISPLAY}\n" +
|
||||
"[Android Version]: ${Build.VERSION.RELEASE}\n" +
|
||||
"[API Version]: ${Build.VERSION.SDK_INT}\n" +
|
||||
"[Package Name]: $packageName\n" +
|
||||
"[Error Type]: ${if (isNativeCrash) "Native" else "Jvm"}\n" +
|
||||
"[Crash Time]: $dateTime\n" +
|
||||
"[Stack Trace]:\n" +
|
||||
stackTrace
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
|
||||
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
|
||||
* https://github.com/KitsunePie/AppErrorsTracking
|
||||
*
|
||||
* This software is non-free but opensource software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Affero General Public License
|
||||
* as published by the Free Software Foundation; either
|
||||
* version 3 of the License, or any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/6/4.
|
||||
*/
|
||||
package com.fankes.apperrorstracking.bean
|
||||
|
||||
import java.io.Serializable
|
||||
|
||||
/**
|
||||
* 应用过滤条件 bean
|
||||
* @param name 名称或包名
|
||||
* @param isContainsSystem 是否包含系统应用
|
||||
*/
|
||||
data class AppFiltersBean(var name: String = "", var isContainsSystem: Boolean = false) : Serializable
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
|
||||
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
|
||||
* https://github.com/KitsunePie/AppErrorsTracking
|
||||
*
|
||||
* This software is non-free but opensource software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Affero General Public License
|
||||
* as published by the Free Software Foundation; either
|
||||
* version 3 of the License, or any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/6/8.
|
||||
*/
|
||||
package com.fankes.apperrorstracking.bean
|
||||
|
||||
import android.graphics.drawable.Drawable
|
||||
import java.io.Serializable
|
||||
|
||||
/**
|
||||
* 应用信息 bean
|
||||
* @param icon 图标
|
||||
* @param name APP 名称
|
||||
* @param packageName APP 包名
|
||||
*/
|
||||
data class AppInfoBean(var icon: Drawable? = null, var name: String, var packageName: String) : Serializable
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
|
||||
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
|
||||
* https://github.com/KitsunePie/AppErrorsTracking
|
||||
*
|
||||
* This software is non-free but opensource software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Affero General Public License
|
||||
* as published by the Free Software Foundation; either
|
||||
* version 3 of the License, or any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/6/3.
|
||||
*/
|
||||
package com.fankes.apperrorstracking.bean
|
||||
|
||||
import java.io.Serializable
|
||||
|
||||
/**
|
||||
* 已忽略异常的应用 bean
|
||||
* @param type 类型
|
||||
* @param packageName 包名
|
||||
*/
|
||||
data class MutedErrorsAppBean(var type: MuteType, var packageName: String) : Serializable {
|
||||
|
||||
/**
|
||||
* 已忽略的异常类型
|
||||
*/
|
||||
enum class MuteType { UNTIL_UNLOCKS, UNTIL_REBOOTS }
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
|
||||
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
|
||||
* https://github.com/KitsunePie/AppErrorsTracking
|
||||
*
|
||||
* This software is non-free but opensource software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Affero General Public License
|
||||
* as published by the Free Software Foundation; either
|
||||
* version 3 of the License, or any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/5/14.
|
||||
*/
|
||||
package com.fankes.apperrorstracking.data
|
||||
|
||||
import com.highcapable.yukihookapi.hook.xposed.prefs.data.PrefsData
|
||||
|
||||
object DataConst {
|
||||
|
||||
val SHOW_DEVELOPER_NOTICE = PrefsData("_show_developer_notice", true)
|
||||
|
||||
val ENABLE_HIDE_ICON = PrefsData("_hide_icon", false)
|
||||
val ENABLE_ONLY_SHOW_ERRORS_IN_FRONT = PrefsData("_enable_only_show_errors_in_front", false)
|
||||
val ENABLE_ONLY_SHOW_ERRORS_IN_MAIN = PrefsData("_enable_only_show_errors_in_main", false)
|
||||
val ENABLE_ALWAYS_SHOWS_REOPEN_APP_OPTIONS = PrefsData("_enable_always_shows_reopen_app_options", false)
|
||||
val ENABLE_APP_CONFIG_TEMPLATE = PrefsData("_enable_app_config_template", false)
|
||||
}
|
||||
@@ -22,18 +22,25 @@
|
||||
package com.fankes.apperrorstracking.hook
|
||||
|
||||
import com.fankes.apperrorstracking.hook.entity.FrameworkHooker
|
||||
import com.fankes.apperrorstracking.locale.LocaleString
|
||||
import com.highcapable.yukihookapi.annotation.xposed.InjectYukiHookWithXposed
|
||||
import com.highcapable.yukihookapi.hook.factory.configs
|
||||
import com.highcapable.yukihookapi.hook.factory.encase
|
||||
import com.highcapable.yukihookapi.hook.xposed.proxy.IYukiHookXposedInit
|
||||
|
||||
@InjectYukiHookWithXposed(entryClassName = "AppErrorsTracking")
|
||||
@InjectYukiHookWithXposed(entryClassName = "AppErrorsTracking", isUsingResourcesHook = false)
|
||||
class HookEntry : IYukiHookXposedInit {
|
||||
|
||||
override fun onInit() = configs {
|
||||
debugTag = "AppErrorsTracking"
|
||||
isDebug = false
|
||||
isEnableModulePrefsCache = false
|
||||
}
|
||||
|
||||
override fun onHook() = encase { loadSystem(FrameworkHooker()) }
|
||||
override fun onHook() = encase {
|
||||
loadSystem {
|
||||
LocaleString.bind(instance = this)
|
||||
loadHooker(FrameworkHooker)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,83 +19,126 @@
|
||||
*
|
||||
* This file is Created by fankes on 2022/5/7.
|
||||
*/
|
||||
@file:Suppress("DEPRECATION", "UseCompatLoadingForDrawables")
|
||||
@file:Suppress("UseCompatLoadingForDrawables")
|
||||
|
||||
package com.fankes.apperrorstracking.hook.entity
|
||||
|
||||
import android.app.AlertDialog
|
||||
import android.app.Dialog
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.graphics.Color
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import android.os.Message
|
||||
import android.view.Gravity
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.WindowManager
|
||||
import android.widget.ImageView
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import androidx.core.graphics.drawable.IconCompat
|
||||
import androidx.core.graphics.drawable.toBitmap
|
||||
import com.fankes.apperrorstracking.BuildConfig
|
||||
import com.fankes.apperrorstracking.R
|
||||
import com.fankes.apperrorstracking.utils.drawable.drawabletoolbox.DrawableBuilder
|
||||
import com.fankes.apperrorstracking.utils.factory.dp
|
||||
import com.fankes.apperrorstracking.utils.factory.isSystemInDarkMode
|
||||
import com.fankes.apperrorstracking.utils.factory.openApp
|
||||
import com.fankes.apperrorstracking.utils.factory.openSelfSetting
|
||||
import com.fankes.apperrorstracking.bean.AppErrorsDisplayBean
|
||||
import com.fankes.apperrorstracking.bean.AppErrorsInfoBean
|
||||
import com.fankes.apperrorstracking.bean.AppInfoBean
|
||||
import com.fankes.apperrorstracking.bean.MutedErrorsAppBean
|
||||
import com.fankes.apperrorstracking.data.DataConst
|
||||
import com.fankes.apperrorstracking.hook.factory.isAppShowErrorsNotify
|
||||
import com.fankes.apperrorstracking.hook.factory.isAppShowErrorsToast
|
||||
import com.fankes.apperrorstracking.hook.factory.isAppShowNothing
|
||||
import com.fankes.apperrorstracking.locale.LocaleString
|
||||
import com.fankes.apperrorstracking.ui.activity.errors.AppErrorsDisplayActivity
|
||||
import com.fankes.apperrorstracking.ui.activity.errors.AppErrorsRecordActivity
|
||||
import com.fankes.apperrorstracking.utils.factory.*
|
||||
import com.fankes.apperrorstracking.utils.tool.FrameworkTool
|
||||
import com.highcapable.yukihookapi.hook.bean.VariousClass
|
||||
import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker
|
||||
import com.highcapable.yukihookapi.hook.factory.field
|
||||
import com.highcapable.yukihookapi.hook.factory.hasMethod
|
||||
import com.highcapable.yukihookapi.hook.factory.method
|
||||
import com.highcapable.yukihookapi.hook.log.loggerE
|
||||
import com.highcapable.yukihookapi.hook.type.android.BundleClass
|
||||
import com.highcapable.yukihookapi.hook.type.android.MessageClass
|
||||
|
||||
class FrameworkHooker : YukiBaseHooker() {
|
||||
object FrameworkHooker : YukiBaseHooker() {
|
||||
|
||||
companion object {
|
||||
private const val ActivityManagerServiceClass = "com.android.server.am.ActivityManagerService"
|
||||
private const val UserControllerClass = "com.android.server.am.UserController"
|
||||
private const val AppErrorsClass = "com.android.server.am.AppErrors"
|
||||
private const val AppErrorDialogClass = "com.android.server.am.AppErrorDialog"
|
||||
private const val AppErrorDialog_DataClass = "com.android.server.am.AppErrorDialog\$Data"
|
||||
private const val ProcessRecordClass = "com.android.server.am.ProcessRecord"
|
||||
private const val ActivityTaskManagerService_LocalServiceClass = "com.android.server.wm.ActivityTaskManagerService\$LocalService"
|
||||
|
||||
private const val AppErrorsClass = "com.android.server.am.AppErrors"
|
||||
private val PackageListClass = VariousClass(
|
||||
"com.android.server.am.ProcessRecord\$PackageList",
|
||||
"com.android.server.am.PackageList"
|
||||
)
|
||||
|
||||
private const val AppErrorResultClass = "com.android.server.am.AppErrorResult"
|
||||
private val ErrorDialogControllerClass = VariousClass(
|
||||
"com.android.server.am.ProcessRecord\$ErrorDialogController",
|
||||
"com.android.server.am.ErrorDialogController"
|
||||
)
|
||||
|
||||
private const val AppErrorDialog_DataClass = "com.android.server.am.AppErrorDialog\$Data"
|
||||
/** 已忽略错误的 APP 数组 - 直到重新解锁 */
|
||||
private var mutedErrorsIfUnlockApps = HashSet<String>()
|
||||
|
||||
private const val ProcessRecordClass = "com.android.server.am.ProcessRecord"
|
||||
/** 已忽略错误的 APP 数组 - 直到重新启动 */
|
||||
private var mutedErrorsIfRestartApps = HashSet<String>()
|
||||
|
||||
private val ErrorDialogControllerClass = VariousClass(
|
||||
"com.android.server.am.ProcessRecord\$ErrorDialogController",
|
||||
"com.android.server.am.ErrorDialogController"
|
||||
)
|
||||
/** 已记录的 APP 异常信息数组 - 直到重新启动 */
|
||||
private val appErrorsRecords = ArrayList<AppErrorsInfoBean>()
|
||||
|
||||
/** 注册 */
|
||||
private fun register() {
|
||||
onAppLifecycle {
|
||||
/** 解锁后清空已记录的忽略错误 APP */
|
||||
registerReceiver(Intent.ACTION_USER_PRESENT) { _, _ -> mutedErrorsIfUnlockApps.clear() }
|
||||
/** 刷新模块 Resources 缓存 */
|
||||
registerReceiver(Intent.ACTION_LOCALE_CHANGED) { _, _ -> refreshModuleAppResources() }
|
||||
}
|
||||
FrameworkTool.Host.with(instance = this) {
|
||||
onOpenAppUsedFramework { appContext.openApp(it) }
|
||||
onPushAppErrorsInfoData { appErrorsRecords }
|
||||
onRemoveAppErrorsInfoData { appErrorsRecords.remove(it) }
|
||||
onClearAppErrorsInfoData { appErrorsRecords.clear() }
|
||||
onMutedErrorsIfUnlock { mutedErrorsIfUnlockApps.add(it) }
|
||||
onMutedErrorsIfRestart { mutedErrorsIfRestartApps.add(it) }
|
||||
onPushMutedErrorsAppsData {
|
||||
arrayListOf<MutedErrorsAppBean>().apply {
|
||||
mutedErrorsIfUnlockApps.takeIf { it.isNotEmpty() }
|
||||
?.forEach { add(MutedErrorsAppBean(MutedErrorsAppBean.MuteType.UNTIL_UNLOCKS, it)) }
|
||||
mutedErrorsIfRestartApps.takeIf { it.isNotEmpty() }
|
||||
?.forEach { add(MutedErrorsAppBean(MutedErrorsAppBean.MuteType.UNTIL_REBOOTS, it)) }
|
||||
}
|
||||
}
|
||||
onUnmuteErrorsApp {
|
||||
when (it.type) {
|
||||
MutedErrorsAppBean.MuteType.UNTIL_UNLOCKS -> mutedErrorsIfUnlockApps.remove(it.packageName)
|
||||
MutedErrorsAppBean.MuteType.UNTIL_REBOOTS -> mutedErrorsIfRestartApps.remove(it.packageName)
|
||||
}
|
||||
}
|
||||
onUnmuteAllErrorsApps {
|
||||
mutedErrorsIfUnlockApps.clear()
|
||||
mutedErrorsIfRestartApps.clear()
|
||||
}
|
||||
onPushAppListData { filters ->
|
||||
arrayListOf<AppInfoBean>().apply {
|
||||
appContext.packageManager.getInstalledPackages(PackageManager.GET_CONFIGURATIONS).also { info ->
|
||||
(if (filters.name.isNotBlank())
|
||||
info.filter { it.packageName.contains(filters.name) || appContext.appName(it.packageName).contains(filters.name) }
|
||||
else info).let { result ->
|
||||
if (filters.isContainsSystem.not()) result.filter { (it.applicationInfo.flags and ApplicationInfo.FLAG_SYSTEM) == 0 }
|
||||
else result
|
||||
}.sortedByDescending { it.lastUpdateTime }
|
||||
.forEach { add(AppInfoBean(name = appContext.appName(it.packageName), packageName = it.packageName)) }
|
||||
/** 移除模块自身 */
|
||||
removeIf { it.packageName == BuildConfig.APPLICATION_ID }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建对话框按钮
|
||||
* @param context 实例
|
||||
* @param drawableId 按钮图标
|
||||
* @param content 按钮文本
|
||||
* @param it 点击事件回调
|
||||
* @return [LinearLayout]
|
||||
*/
|
||||
private fun createButtonItem(context: Context, drawableId: Int, content: String, it: () -> Unit) =
|
||||
LinearLayout(context).apply {
|
||||
background = DrawableBuilder().rounded().cornerRadius(15.dp(context)).ripple().rippleColor(0xFFAAAAAA.toInt()).build()
|
||||
gravity = Gravity.CENTER or Gravity.START
|
||||
layoutParams =
|
||||
ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
|
||||
addView(ImageView(context).apply {
|
||||
setImageDrawable(moduleAppResources.getDrawable(drawableId))
|
||||
layoutParams = ViewGroup.LayoutParams(25.dp(context), 25.dp(context))
|
||||
setColorFilter(if (context.isSystemInDarkMode) Color.WHITE else Color.BLACK)
|
||||
})
|
||||
addView(View(context).apply { layoutParams = ViewGroup.LayoutParams(15.dp(context), 0) })
|
||||
addView(TextView(context).apply {
|
||||
text = content
|
||||
textSize = 16f
|
||||
setTextColor(if (context.isSystemInDarkMode) 0xFFDDDDDD.toInt() else 0xFF777777.toInt())
|
||||
})
|
||||
setPadding(19.dp(context), 16.dp(context), 19.dp(context), 16.dp(context))
|
||||
setOnClickListener { it() }
|
||||
}
|
||||
|
||||
override fun onHook() {
|
||||
/** 注册 */
|
||||
register()
|
||||
/** 干掉原生错误对话框 - 如果有 */
|
||||
ErrorDialogControllerClass.hook {
|
||||
injectMember {
|
||||
@@ -112,6 +155,26 @@ class FrameworkHooker : YukiBaseHooker() {
|
||||
}
|
||||
intercept()
|
||||
}
|
||||
}.ignoredHookClassNotFoundFailure()
|
||||
/** 干掉原生错误对话框 - API 30 以下 */
|
||||
ActivityTaskManagerService_LocalServiceClass.hook {
|
||||
injectMember {
|
||||
method {
|
||||
name = "canShowErrorDialogs"
|
||||
emptyParam()
|
||||
}
|
||||
replaceToFalse()
|
||||
}
|
||||
}.by { Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q }
|
||||
/** 干掉原生错误对话框 - 如果上述方法全部失效则直接结束对话框 */
|
||||
AppErrorDialogClass.hook {
|
||||
injectMember {
|
||||
method {
|
||||
name = "onCreate"
|
||||
param(BundleClass)
|
||||
}
|
||||
afterHook { instance<Dialog>().cancel() }
|
||||
}
|
||||
}
|
||||
/** 注入自定义错误对话框 */
|
||||
AppErrorsClass.hook {
|
||||
@@ -127,54 +190,120 @@ class FrameworkHooker : YukiBaseHooker() {
|
||||
/** 错误数据 */
|
||||
val errData = args().first().cast<Message>()?.obj
|
||||
|
||||
/** 错误结果 */
|
||||
val errResult = AppErrorResultClass.clazz.method {
|
||||
name = "get"
|
||||
emptyParam()
|
||||
}.get(AppErrorDialog_DataClass.clazz.field {
|
||||
name = "result"
|
||||
}.get(errData).any()).int()
|
||||
/** 当前进程信息 */
|
||||
val proc = AppErrorDialog_DataClass.clazz.field { name = "proc" }.get(errData).any()
|
||||
|
||||
/** 当前 UserId 信息 */
|
||||
val userId = ProcessRecordClass.clazz.field { name = "userId" }.get(proc).int()
|
||||
|
||||
/** 当前 APP 信息 */
|
||||
val appInfo = ProcessRecordClass.clazz.field { name = "info" }
|
||||
.get(AppErrorDialog_DataClass.clazz.field { name = "proc" }
|
||||
.get(errData).any()).cast<ApplicationInfo>() ?: ApplicationInfo()
|
||||
val appInfo = ProcessRecordClass.clazz.field { name = "info" }.get(proc).cast<ApplicationInfo>()
|
||||
|
||||
/** 当前进程名称 */
|
||||
val processName = ProcessRecordClass.clazz.field { name = "processName" }.get(proc).string()
|
||||
|
||||
/** 当前 APP、进程 包名 */
|
||||
val packageName = appInfo?.packageName ?: processName
|
||||
|
||||
/** 当前 APP 名称 */
|
||||
val appName = appInfo?.let { context.appName(it.packageName) } ?: packageName
|
||||
|
||||
/** 是否为 APP */
|
||||
val isApp = (PackageListClass.clazz.method {
|
||||
name = "size"
|
||||
emptyParam()
|
||||
}.get(if (ProcessRecordClass.clazz.hasMethod {
|
||||
name = "getPkgList"
|
||||
emptyParam()
|
||||
}) ProcessRecordClass.clazz.method {
|
||||
name = "getPkgList"
|
||||
emptyParam()
|
||||
}.get(proc).call() else ProcessRecordClass.clazz.field {
|
||||
name = "pkgList"
|
||||
}.get(proc).self).int() == 1 && appInfo != null)
|
||||
|
||||
/** 是否为主进程 */
|
||||
val isMainProcess = packageName == processName
|
||||
|
||||
/** 是否为后台进程 */
|
||||
val isBackgroundProcess = UserControllerClass.clazz.method { name = "getCurrentProfileIds" }
|
||||
.get(ActivityManagerServiceClass.clazz.field { name = "mUserController" }
|
||||
.get(field { name = "mService" }.get(instance).any()).any())
|
||||
.invoke<IntArray>()?.takeIf { it.isNotEmpty() }?.any { it != userId } ?: false
|
||||
|
||||
/** 是否短时内重复错误 */
|
||||
val isRepeating = AppErrorDialog_DataClass.clazz.field { name = "repeating" }.get(errData).boolean()
|
||||
/** 判断在后台就不显示对话框 */
|
||||
if (errResult == -2) return@afterHook
|
||||
/** 创建自定义对话框 */
|
||||
AlertDialog.Builder(
|
||||
context, if (context.isSystemInDarkMode)
|
||||
android.R.style.Theme_Material_Dialog
|
||||
else android.R.style.Theme_Material_Light_Dialog
|
||||
).create().apply {
|
||||
setTitle("${appInfo.loadLabel(context.packageManager)} ${if (isRepeating) "屡次停止运行" else "已停止运行"}")
|
||||
setView(LinearLayout(context).apply {
|
||||
orientation = LinearLayout.VERTICAL
|
||||
addView(createButtonItem(context, R.drawable.ic_baseline_info, content = "应用信息") {
|
||||
cancel()
|
||||
context.openSelfSetting(packageName = appInfo.packageName)
|
||||
})
|
||||
if (isRepeating)
|
||||
addView(createButtonItem(context, R.drawable.ic_baseline_close, content = "关闭应用") { cancel() })
|
||||
else addView(createButtonItem(context, R.drawable.ic_baseline_refresh, content = "重新打开") {
|
||||
cancel()
|
||||
context.openApp(appInfo.packageName)
|
||||
})
|
||||
addView(createButtonItem(context, R.drawable.ic_baseline_bug_report, content = "错误详情") {
|
||||
// TODO 待开发
|
||||
})
|
||||
setPadding(6.dp(context), 15.dp(context), 6.dp(context), 6.dp(context))
|
||||
})
|
||||
/** 只有 SystemUid 才能响应系统级别的对话框 */
|
||||
window?.setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT)
|
||||
}.show()
|
||||
|
||||
/** 崩溃标题 */
|
||||
val errorTitle = if (isRepeating) LocaleString.aerrRepeatedTitle(appName) else LocaleString.aerrTitle(appName)
|
||||
|
||||
/** 是否始终显示重新打开按钮 */
|
||||
val isAlwaysShowsReopenApp = prefs.get(DataConst.ENABLE_ALWAYS_SHOWS_REOPEN_APP_OPTIONS)
|
||||
/** 打印错误日志 */
|
||||
loggerE(msg = "Process \"${appInfo.packageName}\" has crashed, isRepeating --> $isRepeating")
|
||||
if (isApp) loggerE(
|
||||
msg = "App \"$packageName\"${if (packageName != processName) " --process \"$processName\"" else ""}" +
|
||||
" has crashed${if (isRepeating) " again" else ""}"
|
||||
) else loggerE(msg = "Process \"$processName\" has crashed${if (isRepeating) " again" else ""}")
|
||||
/** 判断是否为模块自身崩溃 */
|
||||
if (packageName == BuildConfig.APPLICATION_ID) {
|
||||
context.toast(msg = "AppErrorsTracking has crashed, please see the log in console")
|
||||
return@afterHook
|
||||
}
|
||||
/** 判断是否为已忽略的 APP */
|
||||
if (mutedErrorsIfUnlockApps.contains(packageName) || mutedErrorsIfRestartApps.contains(packageName)) return@afterHook
|
||||
/** 判断配置模块启用状态 */
|
||||
if (prefs.get(DataConst.ENABLE_APP_CONFIG_TEMPLATE)) {
|
||||
if (isAppShowNothing(packageName)) return@afterHook
|
||||
if (isAppShowErrorsNotify(packageName)) {
|
||||
context.pushNotify(
|
||||
channelId = "APPS_ERRORS",
|
||||
channelName = LocaleString.appName,
|
||||
title = errorTitle,
|
||||
content = LocaleString.appErrorsTip,
|
||||
icon = IconCompat.createWithBitmap(R.mipmap.ic_notify.drawableOf(moduleAppResources).toBitmap()),
|
||||
color = 0xFFFF6200.toInt(),
|
||||
intent = AppErrorsRecordActivity.intent()
|
||||
)
|
||||
return@afterHook
|
||||
}
|
||||
if (isAppShowErrorsToast(packageName)) {
|
||||
context.toast(errorTitle)
|
||||
return@afterHook
|
||||
}
|
||||
}
|
||||
/** 判断是否为后台进程 */
|
||||
if ((isBackgroundProcess || context.isAppCanOpened(packageName).not())
|
||||
&& prefs.get(DataConst.ENABLE_ONLY_SHOW_ERRORS_IN_FRONT)
|
||||
) return@afterHook
|
||||
/** 判断是否为主进程 */
|
||||
if (isMainProcess.not() && prefs.get(DataConst.ENABLE_ONLY_SHOW_ERRORS_IN_MAIN)) return@afterHook
|
||||
/** 启动错误对话框显示窗口 */
|
||||
AppErrorsDisplayActivity.start(
|
||||
context, AppErrorsDisplayBean(
|
||||
packageName = packageName,
|
||||
processName = processName,
|
||||
appName = appName,
|
||||
title = errorTitle,
|
||||
isShowAppInfoButton = isApp,
|
||||
isShowReopenButton = isApp && (isRepeating.not() || isAlwaysShowsReopenApp)
|
||||
&& context.isAppCanOpened(packageName) && isMainProcess,
|
||||
isShowCloseAppButton = isApp
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
injectMember {
|
||||
method {
|
||||
name = "crashApplication"
|
||||
paramCount = 2
|
||||
}
|
||||
afterHook {
|
||||
/** 当前 APP 信息 */
|
||||
val appInfo = ProcessRecordClass.clazz.field { name = "info" }.get(args().first().any()).cast<ApplicationInfo>()
|
||||
/** 添加当前异常信息到第一位 */
|
||||
appErrorsRecords.add(0, AppErrorsInfoBean.clone(appInfo?.packageName, args().last().cast()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
|
||||
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
|
||||
* https://github.com/KitsunePie/AppErrorsTracking
|
||||
*
|
||||
* This software is non-free but opensource software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Affero General Public License
|
||||
* as published by the Free Software Foundation; either
|
||||
* version 3 of the License, or any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/6/8.
|
||||
*/
|
||||
@file:Suppress("unused")
|
||||
|
||||
package com.fankes.apperrorstracking.hook.factory
|
||||
|
||||
import android.content.Context
|
||||
import com.highcapable.yukihookapi.hook.factory.modulePrefs
|
||||
import com.highcapable.yukihookapi.hook.param.PackageParam
|
||||
|
||||
/**
|
||||
* 获取此 APP 是否配置显示错误对话框
|
||||
* @param packageName APP 包名
|
||||
*/
|
||||
fun PackageParam.isAppShowErrorsDialog(packageName: String) = prefs.getBoolean("${packageName}_show_errors_dialog", true)
|
||||
|
||||
/**
|
||||
* 获取此 APP 是否配置显示错误通知推送
|
||||
* @param packageName APP 包名
|
||||
*/
|
||||
fun PackageParam.isAppShowErrorsNotify(packageName: String) = prefs.getBoolean("${packageName}_show_errors_notify", false)
|
||||
|
||||
/**
|
||||
* 获取此 APP 是否配置显示错误 Toast 提示
|
||||
* @param packageName APP 包名
|
||||
*/
|
||||
fun PackageParam.isAppShowErrorsToast(packageName: String) = prefs.getBoolean("${packageName}_show_errors_toast", false)
|
||||
|
||||
/**
|
||||
* 获取此 APP 是否配置不显示任何提示
|
||||
* @param packageName APP 包名
|
||||
*/
|
||||
fun PackageParam.isAppShowNothing(packageName: String) = prefs.getBoolean("${packageName}_show_nothing", false)
|
||||
|
||||
/**
|
||||
* 获取此 APP 是否配置显示错误对话框
|
||||
* @param packageName APP 包名
|
||||
*/
|
||||
fun Context.isAppShowErrorsDialog(packageName: String) = modulePrefs.getBoolean("${packageName}_show_errors_dialog", true)
|
||||
|
||||
/**
|
||||
* 获取此 APP 是否配置显示错误通知推送
|
||||
* @param packageName APP 包名
|
||||
*/
|
||||
fun Context.isAppShowErrorsNotify(packageName: String) = modulePrefs.getBoolean("${packageName}_show_errors_notify", false)
|
||||
|
||||
/**
|
||||
* 获取此 APP 是否配置显示错误 Toast 提示
|
||||
* @param packageName APP 包名
|
||||
*/
|
||||
fun Context.isAppShowErrorsToast(packageName: String) = modulePrefs.getBoolean("${packageName}_show_errors_toast", false)
|
||||
|
||||
/**
|
||||
* 获取此 APP 是否配置不显示任何提示
|
||||
* @param packageName APP 包名
|
||||
*/
|
||||
fun Context.isAppShowNothing(packageName: String) = modulePrefs.getBoolean("${packageName}_show_nothing", false)
|
||||
|
||||
/**
|
||||
* 设置此 APP 是否配置显示错误对话框
|
||||
* @param packageName APP 包名
|
||||
* @param isApply 是否设置
|
||||
*/
|
||||
fun Context.putAppShowErrorsDialog(packageName: String, isApply: Boolean) = modulePrefs.putBoolean("${packageName}_show_errors_dialog", isApply)
|
||||
|
||||
/**
|
||||
* 设置此 APP 是否配置显示错误通知推送
|
||||
* @param packageName APP 包名
|
||||
* @param isApply 是否设置
|
||||
*/
|
||||
fun Context.putAppShowErrorsNotify(packageName: String, isApply: Boolean) = modulePrefs.putBoolean("${packageName}_show_errors_notify", isApply)
|
||||
|
||||
/**
|
||||
* 设置此 APP 是否配置显示错误 Toast 提示
|
||||
* @param packageName APP 包名
|
||||
* @param isApply 是否设置
|
||||
*/
|
||||
fun Context.putAppShowErrorsToast(packageName: String, isApply: Boolean) = modulePrefs.putBoolean("${packageName}_show_errors_toast", isApply)
|
||||
|
||||
/**
|
||||
* 设置此 APP 是否配置不显示任何提示
|
||||
* @param packageName APP 包名
|
||||
* @param isApply 是否设置
|
||||
*/
|
||||
fun Context.putAppShowNothing(packageName: String, isApply: Boolean) = modulePrefs.putBoolean("${packageName}_show_nothing", isApply)
|
||||
@@ -0,0 +1,457 @@
|
||||
/*
|
||||
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
|
||||
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
|
||||
* https://github.com/KitsunePie/AppErrorsTracking
|
||||
*
|
||||
* This software is non-free but opensource software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Affero General Public License
|
||||
* as published by the Free Software Foundation; either
|
||||
* version 3 of the License, or any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/5/11.
|
||||
*/
|
||||
@file:Suppress("MemberVisibilityCanBePrivate", "StaticFieldLeak", "unused")
|
||||
|
||||
package com.fankes.apperrorstracking.locale
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.Resources
|
||||
import com.fankes.apperrorstracking.R
|
||||
import com.highcapable.yukihookapi.hook.param.PackageParam
|
||||
|
||||
/**
|
||||
* I18n 字符串实例
|
||||
*/
|
||||
object LocaleString {
|
||||
|
||||
/** 当前的 [Context] */
|
||||
private var baseContext: Context? = null
|
||||
|
||||
/** 当前的 [PackageParam] */
|
||||
private var basePackageParam: PackageParam? = null
|
||||
|
||||
/** 当前的 [Resources] */
|
||||
private var baseResources: Resources? = null
|
||||
|
||||
/**
|
||||
* 当前的 [Resources]
|
||||
* @return [Resources]
|
||||
* @throws IllegalStateException 如果 [LocaleString] 没有被绑定
|
||||
*/
|
||||
private val resources
|
||||
get() = baseContext?.resources ?: basePackageParam?.moduleAppResources ?: baseResources
|
||||
?: error("LocaleString must bind an instance first")
|
||||
|
||||
/**
|
||||
* 绑定并初始化
|
||||
* @param instance 可以是 [Context]、[PackageParam]、[Resources]
|
||||
*/
|
||||
fun bind(instance: Any) {
|
||||
when (instance) {
|
||||
is Context -> baseContext = instance
|
||||
is PackageParam -> basePackageParam = instance
|
||||
is Resources -> baseResources = instance
|
||||
else -> error("LocaleString bind an unknown instance")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据资源 Id 获取字符串
|
||||
* @param objArrs 格式化实例
|
||||
* @return [String]
|
||||
*/
|
||||
private fun Int.bind(vararg objArrs: Any) = resources.getString(this, *objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val appName get() = appName()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun appName(vararg objArrs: Any) = R.string.app_name.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val copied get() = copied()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun copied(vararg objArrs: Any) = R.string.copied.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val copyFail get() = copyFail()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun copyFail(vararg objArrs: Any) = R.string.copy_fail.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val printToLogcatSuccess get() = printToLogcatSuccess()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun printToLogcatSuccess(vararg objArrs: Any) = R.string.print_to_logcat_success.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val outputStackSuccess get() = outputStackSuccess()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun outputStackSuccess(vararg objArrs: Any) = R.string.output_stack_success.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val outputStackFail get() = outputStackFail()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun outputStackFail(vararg objArrs: Any) = R.string.output_stack_fail.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val aerrTitle get() = aerrTitle()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun aerrTitle(vararg objArrs: Any) = R.string.aerr_title.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val aerrRepeatedTitle get() = aerrRepeatedTitle()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun aerrRepeatedTitle(vararg objArrs: Any) = R.string.aerr_repeated_title.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val appInfo get() = appInfo()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun appInfo(vararg objArrs: Any) = R.string.app_info.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val closeApp get() = closeApp()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun closeApp(vararg objArrs: Any) = R.string.close_app.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val reopenApp get() = reopenApp()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun reopenApp(vararg objArrs: Any) = R.string.reopen_app.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val errorDetail get() = errorDetail()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun errorDetail(vararg objArrs: Any) = R.string.error_detail.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val muteIfUnlock get() = muteIfUnlock()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun muteIfUnlock(vararg objArrs: Any) = R.string.mute_if_unlock.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val muteIfRestart get() = muteIfRestart()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun muteIfRestart(vararg objArrs: Any) = R.string.mute_if_restart.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val muteIfUnlockTip get() = muteIfUnlockTip()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun muteIfUnlockTip(vararg objArrs: Any) = R.string.mute_if_unlock_tip.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val muteIfRestartTip get() = muteIfRestartTip()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun muteIfRestartTip(vararg objArrs: Any) = R.string.mute_if_restart_tip.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val confirm get() = confirm()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun confirm(vararg objArrs: Any) = R.string.confirm.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val cancel get() = cancel()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun cancel(vararg objArrs: Any) = R.string.cancel.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val more get() = more()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun more(vararg objArrs: Any) = R.string.more.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val notice get() = notice()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun notice(vararg objArrs: Any) = R.string.notice.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val areYouSureClearErrors get() = areYouSureClearErrors()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun areYouSureClearErrors(vararg objArrs: Any) = R.string.are_you_sure_clear_errors.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val allErrorsClearSuccess get() = allErrorsClearSuccess()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun allErrorsClearSuccess(vararg objArrs: Any) = R.string.all_errors_clear_success.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val areYouSureExportAllErrors get() = areYouSureExportAllErrors()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun areYouSureExportAllErrors(vararg objArrs: Any) = R.string.are_you_sure_export_all_errors.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val exportAllErrorsSuccess get() = exportAllErrorsSuccess()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun exportAllErrorsSuccess(vararg objArrs: Any) = R.string.export_all_errors_success.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val exportAllErrorsFail get() = exportAllErrorsFail()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun exportAllErrorsFail(vararg objArrs: Any) = R.string.export_all_errors_fail.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val noCpuAbi get() = noCpuAbi()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun noCpuAbi(vararg objArrs: Any) = R.string.no_cpu_abi.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val areYouSureRemoveRecord get() = areYouSureRemoveRecord()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun areYouSureRemoveRecord(vararg objArrs: Any) = R.string.are_you_sure_remove_record.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val gotIt get() = gotIt()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun gotIt(vararg objArrs: Any) = R.string.got_it.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val moduleVersion get() = moduleVersion()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun moduleVersion(vararg objArrs: Any) = R.string.module_version.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val systemVersion get() = systemVersion()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun systemVersion(vararg objArrs: Any) = R.string.system_version.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val areYouSureRestartSystem get() = areYouSureRestartSystem()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun areYouSureRestartSystem(vararg objArrs: Any) = R.string.are_your_sure_restart_system.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val fastRestart get() = fastRestart()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun fastRestart(vararg objArrs: Any) = R.string.fast_restart.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val accessRootFail get() = accessRootFail()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun accessRootFail(vararg objArrs: Any) = R.string.access_root_fail.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val moduleNotActivated get() = moduleNotActivated()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun moduleNotActivated(vararg objArrs: Any) = R.string.module_not_activated.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val moduleIsActivated get() = moduleIsActivated()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun moduleIsActivated(vararg objArrs: Any) = R.string.module_is_activated.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val moduleNotFullyActivated get() = moduleNotFullyActivated()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun moduleNotFullyActivated(vararg objArrs: Any) = R.string.module_not_fully_activated.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val momentAgo get() = momentAgo()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun momentAgo(vararg objArrs: Any) = R.string.moment_ago.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val secondAgo get() = secondAgo()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun secondAgo(vararg objArrs: Any) = R.string.second_ago.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val minuteAgo get() = minuteAgo()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun minuteAgo(vararg objArrs: Any) = R.string.minute_ago.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val hourAgo get() = hourAgo()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun hourAgo(vararg objArrs: Any) = R.string.hour_ago.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val dayAgo get() = dayAgo()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun dayAgo(vararg objArrs: Any) = R.string.day_ago.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val monthAgo get() = monthAgo()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun monthAgo(vararg objArrs: Any) = R.string.month_ago.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val yearAgo get() = yearAgo()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun yearAgo(vararg objArrs: Any) = R.string.year_ago.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val crashProcess get() = crashProcess()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun crashProcess(vararg objArrs: Any) = R.string.crash_process.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val shareErrorStack get() = shareErrorStack()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun shareErrorStack(vararg objArrs: Any) = R.string.share_error_stack.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val areYouSureUnmuteAll get() = areYouSureUnmuteAll()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun areYouSureUnmuteAll(vararg objArrs: Any) = R.string.are_you_sure_unmute_all.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val filterByCondition get() = filterByCondition()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun filterByCondition(vararg objArrs: Any) = R.string.filter_by_condition.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val clearFilters get() = clearFilters()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun clearFilters(vararg objArrs: Any) = R.string.clear_filters.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val resultCount get() = resultCount()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun resultCount(vararg objArrs: Any) = R.string.result_count.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val loading get() = loading()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun loading(vararg objArrs: Any) = R.string.loading.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val showErrorsDialog get() = showErrorsDialog()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun showErrorsDialog(vararg objArrs: Any) = R.string.show_errors_dialog.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val showErrorsToast get() = showErrorsToast()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun showErrorsToast(vararg objArrs: Any) = R.string.show_errors_toast.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val showNothing get() = showNothing()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun showNothing(vararg objArrs: Any) = R.string.show_nothing.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val appErrorsStatistics get() = appErrorsStatistics()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun appErrorsStatistics(vararg objArrs: Any) = R.string.app_errors_statistics.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val totalErrorsUnit get() = totalErrorsUnit()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun totalErrorsUnit(vararg objArrs: Any) = R.string.total_errors_unit.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val totalAppsUnit get() = totalAppsUnit()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun totalAppsUnit(vararg objArrs: Any) = R.string.total_apps_unit.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val generatingStatistics get() = generatingStatistics()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun generatingStatistics(vararg objArrs: Any) = R.string.generating_statistics.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val moduleNotFullyActivatedTip get() = moduleNotFullyActivatedTip()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun moduleNotFullyActivatedTip(vararg objArrs: Any) = R.string.module_not_fully_activated_tip.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val showErrorsNotify get() = showErrorsNotify()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun showErrorsNotify(vararg objArrs: Any) = R.string.show_errors_notify.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val appErrorsTip get() = appErrorsTip()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun appErrorsTip(vararg objArrs: Any) = R.string.app_errors_tip.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val batchOperations get() = batchOperations()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun batchOperations(vararg objArrs: Any) = R.string.batch_operations.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val areYouSureApplySiteApps get() = areYouSureApplySiteApps()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun areYouSureApplySiteApps(vararg objArrs: Any) = R.string.are_you_sure_apply_site_apps.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val developerNoticeTip get() = developerNoticeTip()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun developerNoticeTip(vararg objArrs: Any) = R.string.developer_notice_tip.bind(*objArrs)
|
||||
|
||||
/** @string Automatic generated */
|
||||
val developerNotice get() = developerNotice()
|
||||
|
||||
/** @string Automatic generated */
|
||||
fun developerNotice(vararg objArrs: Any) = R.string.developer_notice.bind(*objArrs)
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
|
||||
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
|
||||
* https://github.com/KitsunePie/AppErrorsTracking
|
||||
*
|
||||
* This software is non-free but opensource software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Affero General Public License
|
||||
* as published by the Free Software Foundation; either
|
||||
* version 3 of the License, or any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/5/11.
|
||||
*/
|
||||
package com.fankes.apperrorstracking.service
|
||||
|
||||
import android.service.quicksettings.TileService
|
||||
import com.fankes.apperrorstracking.ui.activity.errors.AppErrorsRecordActivity
|
||||
import com.fankes.apperrorstracking.utils.factory.navigate
|
||||
|
||||
class QuickStartTileService : TileService() {
|
||||
|
||||
override fun onClick() {
|
||||
super.onClick()
|
||||
/** 启动异常历史记录窗口 */
|
||||
navigate<AppErrorsRecordActivity>()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
|
||||
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
|
||||
* https://github.com/KitsunePie/AppErrorsTracking
|
||||
*
|
||||
* This software is non-free but opensource software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Affero General Public License
|
||||
* as published by the Free Software Foundation; either
|
||||
* version 3 of the License, or any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/5/7.
|
||||
*/
|
||||
@file:Suppress("UNCHECKED_CAST")
|
||||
|
||||
package com.fankes.apperrorstracking.ui.activity.base
|
||||
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.viewbinding.ViewBinding
|
||||
import com.fankes.apperrorstracking.R
|
||||
import com.fankes.apperrorstracking.utils.factory.isNotSystemInDarkMode
|
||||
import com.fankes.apperrorstracking.utils.factory.toast
|
||||
import com.highcapable.yukihookapi.hook.factory.method
|
||||
import com.highcapable.yukihookapi.hook.type.android.LayoutInflaterClass
|
||||
import java.lang.reflect.ParameterizedType
|
||||
|
||||
abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
|
||||
|
||||
/** 获取绑定布局对象 */
|
||||
lateinit var binding: VB
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
javaClass.genericSuperclass.also { type ->
|
||||
if (type is ParameterizedType) {
|
||||
binding = (type.actualTypeArguments[0] as Class<VB>).method {
|
||||
name = "inflate"
|
||||
param(LayoutInflaterClass)
|
||||
}.get().invoke<VB>(layoutInflater) ?: error("binding failed")
|
||||
setContentView(binding.root)
|
||||
} else error("binding but got wrong type")
|
||||
}
|
||||
/** 隐藏系统的标题栏 */
|
||||
supportActionBar?.hide()
|
||||
/** 初始化沉浸状态栏 */
|
||||
WindowCompat.getInsetsController(window, window.decorView).apply {
|
||||
isAppearanceLightStatusBars = isNotSystemInDarkMode
|
||||
isAppearanceLightNavigationBars = isNotSystemInDarkMode
|
||||
}
|
||||
ResourcesCompat.getColor(resources, R.color.colorThemeBackground, null).also {
|
||||
window?.statusBarColor = it
|
||||
window?.navigationBarColor = it
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) window?.navigationBarDividerColor = it
|
||||
}
|
||||
/** 装载子类 */
|
||||
onCreate()
|
||||
}
|
||||
|
||||
/** 回调 [onCreate] 方法 */
|
||||
abstract fun onCreate()
|
||||
|
||||
/**
|
||||
* 弹出提示并退出
|
||||
* @param name 名称
|
||||
*/
|
||||
fun toastAndFinish(name: String) {
|
||||
toast(msg = "Invalid $name, exit")
|
||||
finish()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
|
||||
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
|
||||
* https://github.com/KitsunePie/AppErrorsTracking
|
||||
*
|
||||
* This software is non-free but opensource software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Affero General Public License
|
||||
* as published by the Free Software Foundation; either
|
||||
* version 3 of the License, or any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/5/7.
|
||||
*/
|
||||
@file:Suppress("DEPRECATION", "OVERRIDE_DEPRECATION")
|
||||
|
||||
package com.fankes.apperrorstracking.ui.activity.errors
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import androidx.core.view.isGone
|
||||
import com.fankes.apperrorstracking.R
|
||||
import com.fankes.apperrorstracking.bean.AppErrorsInfoBean
|
||||
import com.fankes.apperrorstracking.databinding.ActivityAppErrorsDetailBinding
|
||||
import com.fankes.apperrorstracking.locale.LocaleString
|
||||
import com.fankes.apperrorstracking.ui.activity.base.BaseActivity
|
||||
import com.fankes.apperrorstracking.utils.factory.*
|
||||
import com.highcapable.yukihookapi.hook.log.loggerE
|
||||
|
||||
class AppErrorsDetailActivity : BaseActivity<ActivityAppErrorsDetailBinding>() {
|
||||
|
||||
companion object {
|
||||
|
||||
/** 请求保存文件回调标识 */
|
||||
private const val WRITE_REQUEST_CODE = 0
|
||||
|
||||
/** [AppErrorsInfoBean] 传值 */
|
||||
private const val EXTRA_APP_ERRORS_INFO = "app_errors_info_extra"
|
||||
|
||||
/**
|
||||
* 启动 [AppErrorsDetailActivity]
|
||||
* @param context 实例
|
||||
* @param appErrorsInfo 应用异常信息
|
||||
*/
|
||||
fun start(context: Context, appErrorsInfo: AppErrorsInfoBean) =
|
||||
context.navigate<AppErrorsDetailActivity> { putExtra(EXTRA_APP_ERRORS_INFO, appErrorsInfo) }
|
||||
}
|
||||
|
||||
/** 预导出的异常堆栈 */
|
||||
private var stackTrace = ""
|
||||
|
||||
override fun onCreate() {
|
||||
val appErrorsInfo = runCatching { intent?.getSerializableExtra(EXTRA_APP_ERRORS_INFO) as? AppErrorsInfoBean }.getOrNull()
|
||||
?: return toastAndFinish(name = "AppErrorsInfo")
|
||||
binding.appInfoItem.setOnClickListener { openSelfSetting(appErrorsInfo.packageName) }
|
||||
binding.titleBackIcon.setOnClickListener { onBackPressed() }
|
||||
binding.printIcon.setOnClickListener {
|
||||
loggerE(msg = appErrorsInfo.stackTrace)
|
||||
toast(LocaleString.printToLogcatSuccess)
|
||||
}
|
||||
binding.copyIcon.setOnClickListener { copyToClipboard(appErrorsInfo.stackOutputShareContent) }
|
||||
binding.exportIcon.setOnClickListener {
|
||||
stackTrace = appErrorsInfo.stackOutputFileContent
|
||||
runCatching {
|
||||
startActivityForResult(Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
|
||||
addCategory(Intent.CATEGORY_OPENABLE)
|
||||
type = "*/application"
|
||||
putExtra(Intent.EXTRA_TITLE, "${appErrorsInfo.packageName}_${appErrorsInfo.timestamp}.log")
|
||||
}, WRITE_REQUEST_CODE)
|
||||
}.onFailure { toast(msg = "Start Android SAF failed") }
|
||||
}
|
||||
binding.shareIcon.setOnClickListener {
|
||||
startActivity(Intent.createChooser(Intent(Intent.ACTION_SEND).apply {
|
||||
type = "text/plain"
|
||||
putExtra(Intent.EXTRA_TEXT, appErrorsInfo.stackOutputShareContent)
|
||||
}, LocaleString.shareErrorStack))
|
||||
}
|
||||
binding.appIcon.setImageDrawable(appIcon(appErrorsInfo.packageName))
|
||||
binding.appNameText.text = appName(appErrorsInfo.packageName)
|
||||
binding.appVersionText.text = appVersion(appErrorsInfo.packageName)
|
||||
binding.appAbiText.text = appCpuAbi(appErrorsInfo.packageName).ifBlank { LocaleString.noCpuAbi }
|
||||
binding.jvmErrorPanel.isGone = appErrorsInfo.isNativeCrash
|
||||
binding.errorTypeIcon.setImageResource(if (appErrorsInfo.isNativeCrash) R.drawable.ic_cpp else R.drawable.ic_java)
|
||||
binding.errorInfoText.text = appErrorsInfo.exceptionMessage
|
||||
binding.errorTypeText.text = appErrorsInfo.exceptionClassName
|
||||
binding.errorFileNameText.text = appErrorsInfo.throwFileName
|
||||
binding.errorThrowClassText.text = appErrorsInfo.throwClassName
|
||||
binding.errorThrowMethodText.text = appErrorsInfo.throwMethodName
|
||||
binding.errorLineNumberText.text = appErrorsInfo.throwLineNumber.toString()
|
||||
binding.errorRecordTimeText.text = appErrorsInfo.dateTime
|
||||
binding.errorStackText.text = appErrorsInfo.stackTrace
|
||||
binding.appPanelScrollView.setOnScrollChangeListener { _, _, y, _, _ ->
|
||||
binding.detailTitleText.text = if (y >= 30.dp(context = this)) appName(appErrorsInfo.packageName) else LocaleString.appName
|
||||
}
|
||||
binding.detailTitleText.setOnClickListener { binding.appPanelScrollView.smoothScrollTo(0, 0) }
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
if (requestCode == WRITE_REQUEST_CODE && resultCode == Activity.RESULT_OK) runCatching {
|
||||
data?.data?.let {
|
||||
contentResolver?.openOutputStream(it)?.apply { write(stackTrace.toByteArray()) }?.close()
|
||||
toast(LocaleString.outputStackSuccess)
|
||||
} ?: toast(LocaleString.outputStackFail)
|
||||
}.onFailure { toast(LocaleString.outputStackFail) }
|
||||
}
|
||||
|
||||
override fun onBackPressed() {
|
||||
intent?.removeExtra(EXTRA_APP_ERRORS_INFO)
|
||||
finish()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
|
||||
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
|
||||
* https://github.com/KitsunePie/AppErrorsTracking
|
||||
*
|
||||
* This software is non-free but opensource software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Affero General Public License
|
||||
* as published by the Free Software Foundation; either
|
||||
* version 3 of the License, or any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/6/1.
|
||||
*/
|
||||
package com.fankes.apperrorstracking.ui.activity.errors
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.view.isVisible
|
||||
import com.fankes.apperrorstracking.bean.AppErrorsDisplayBean
|
||||
import com.fankes.apperrorstracking.databinding.ActivityAppErrorsDisplayBinding
|
||||
import com.fankes.apperrorstracking.databinding.DiaAppErrorsDisplayBinding
|
||||
import com.fankes.apperrorstracking.locale.LocaleString
|
||||
import com.fankes.apperrorstracking.ui.activity.base.BaseActivity
|
||||
import com.fankes.apperrorstracking.utils.factory.navigate
|
||||
import com.fankes.apperrorstracking.utils.factory.openSelfSetting
|
||||
import com.fankes.apperrorstracking.utils.factory.showDialog
|
||||
import com.fankes.apperrorstracking.utils.factory.toast
|
||||
import com.fankes.apperrorstracking.utils.tool.FrameworkTool
|
||||
|
||||
class AppErrorsDisplayActivity : BaseActivity<ActivityAppErrorsDisplayBinding>() {
|
||||
|
||||
companion object {
|
||||
|
||||
/** 当前实例 - 单例运行 */
|
||||
private var instance: AppErrorsDisplayActivity? = null
|
||||
|
||||
/** [AppErrorsDisplayBean] 传值 */
|
||||
private const val EXTRA_APP_ERRORS_DISPLAY = "app_errors_display_extra"
|
||||
|
||||
/**
|
||||
* 启动 [AppErrorsDisplayActivity]
|
||||
* @param context 实例
|
||||
* @param appErrorsDisplay 应用异常信息显示
|
||||
*/
|
||||
fun start(context: Context, appErrorsDisplay: AppErrorsDisplayBean) =
|
||||
context.navigate<AppErrorsDisplayActivity>(isOutSide = true) { putExtra(EXTRA_APP_ERRORS_DISPLAY, appErrorsDisplay) }
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
instance?.finish()
|
||||
instance = this
|
||||
val appErrorsDisplay = runCatching { intent?.getSerializableExtra(EXTRA_APP_ERRORS_DISPLAY) as? AppErrorsDisplayBean }.getOrNull()
|
||||
?: return toastAndFinish(name = "AppErrorsDisplay")
|
||||
/** 显示对话框 */
|
||||
showDialog<DiaAppErrorsDisplayBinding> {
|
||||
title = appErrorsDisplay.title
|
||||
binding.processNameText.isVisible = appErrorsDisplay.packageName != appErrorsDisplay.processName
|
||||
binding.appInfoItem.isVisible = appErrorsDisplay.isShowAppInfoButton
|
||||
binding.closeAppItem.isVisible = appErrorsDisplay.isShowReopenButton.not() && appErrorsDisplay.isShowCloseAppButton
|
||||
binding.reopenAppItem.isVisible = appErrorsDisplay.isShowReopenButton
|
||||
binding.processNameText.text = LocaleString.crashProcess(appErrorsDisplay.processName)
|
||||
binding.appInfoItem.setOnClickListener {
|
||||
cancel()
|
||||
openSelfSetting(appErrorsDisplay.packageName)
|
||||
}
|
||||
binding.closeAppItem.setOnClickListener { cancel() }
|
||||
binding.reopenAppItem.setOnClickListener {
|
||||
FrameworkTool.openAppUsedFramework(context, appErrorsDisplay.packageName)
|
||||
cancel()
|
||||
}
|
||||
binding.errorDetailItem.setOnClickListener {
|
||||
FrameworkTool.fetchAppErrorsInfoData(context) { appErrorsInfos ->
|
||||
appErrorsInfos.takeIf { it.isNotEmpty() }
|
||||
?.filter { it.packageName == appErrorsDisplay.packageName }
|
||||
?.takeIf { it.isNotEmpty() }?.get(0)?.let {
|
||||
AppErrorsDetailActivity.start(context, it)
|
||||
cancel()
|
||||
} ?: toast(msg = "No errors founded")
|
||||
}
|
||||
}
|
||||
binding.mutedIfUnlockItem.setOnClickListener {
|
||||
FrameworkTool.mutedErrorsIfUnlock(context, appErrorsDisplay.packageName) {
|
||||
toast(LocaleString.muteIfUnlockTip(appErrorsDisplay.appName))
|
||||
cancel()
|
||||
}
|
||||
}
|
||||
binding.mutedIfRestartItem.setOnClickListener {
|
||||
FrameworkTool.mutedErrorsIfRestart(context, appErrorsDisplay.packageName) {
|
||||
toast(LocaleString.muteIfRestartTip(appErrorsDisplay.appName))
|
||||
cancel()
|
||||
}
|
||||
}
|
||||
onCancel { finish() }
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
instance = null
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
|
||||
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
|
||||
* https://github.com/KitsunePie/AppErrorsTracking
|
||||
*
|
||||
* This software is non-free but opensource software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Affero General Public License
|
||||
* as published by the Free Software Foundation; either
|
||||
* version 3 of the License, or any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/6/3.
|
||||
*/
|
||||
@file:Suppress("DEPRECATION", "OVERRIDE_DEPRECATION")
|
||||
|
||||
package com.fankes.apperrorstracking.ui.activity.errors
|
||||
|
||||
import androidx.core.view.isVisible
|
||||
import com.fankes.apperrorstracking.bean.MutedErrorsAppBean
|
||||
import com.fankes.apperrorstracking.databinding.ActivityAppErrorsMutedBinding
|
||||
import com.fankes.apperrorstracking.databinding.AdapterAppErrorsMutedBinding
|
||||
import com.fankes.apperrorstracking.locale.LocaleString
|
||||
import com.fankes.apperrorstracking.ui.activity.base.BaseActivity
|
||||
import com.fankes.apperrorstracking.utils.factory.appIcon
|
||||
import com.fankes.apperrorstracking.utils.factory.appName
|
||||
import com.fankes.apperrorstracking.utils.factory.bindAdapter
|
||||
import com.fankes.apperrorstracking.utils.factory.showDialog
|
||||
import com.fankes.apperrorstracking.utils.tool.FrameworkTool
|
||||
|
||||
class AppErrorsMutedActivity : BaseActivity<ActivityAppErrorsMutedBinding>() {
|
||||
|
||||
/** 回调适配器改变 */
|
||||
private var onChanged: (() -> Unit)? = null
|
||||
|
||||
/** 全部的已忽略异常的 APP 信息 */
|
||||
private val listData = ArrayList<MutedErrorsAppBean>()
|
||||
|
||||
override fun onCreate() {
|
||||
binding.titleBackIcon.setOnClickListener { onBackPressed() }
|
||||
binding.unmuteAllIcon.setOnClickListener {
|
||||
showDialog {
|
||||
title = LocaleString.notice
|
||||
msg = LocaleString.areYouSureUnmuteAll
|
||||
confirmButton { FrameworkTool.unmuteAllErrorsApps(context) { refreshData() } }
|
||||
cancelButton()
|
||||
}
|
||||
}
|
||||
/** 设置列表元素和 Adapter */
|
||||
binding.listView.bindAdapter {
|
||||
onBindDatas { listData }
|
||||
onBindViews<AdapterAppErrorsMutedBinding> { binding, position ->
|
||||
listData[position].also { bean ->
|
||||
binding.appIcon.setImageDrawable(appIcon(bean.packageName))
|
||||
binding.appNameText.text = appName(bean.packageName)
|
||||
binding.muteTypeText.text = when (bean.type) {
|
||||
MutedErrorsAppBean.MuteType.UNTIL_UNLOCKS -> LocaleString.muteIfUnlock
|
||||
MutedErrorsAppBean.MuteType.UNTIL_REBOOTS -> LocaleString.muteIfRestart
|
||||
}
|
||||
binding.unmuteButton.setOnClickListener { FrameworkTool.unmuteErrorsApp(context, bean) { refreshData() } }
|
||||
}
|
||||
}
|
||||
}.apply { onChanged = { notifyDataSetChanged() } }
|
||||
}
|
||||
|
||||
/** 更新列表数据 */
|
||||
private fun refreshData() {
|
||||
FrameworkTool.fetchMutedErrorsAppsData(context = this) {
|
||||
listData.clear()
|
||||
it.takeIf { e -> e.isNotEmpty() }?.forEach { e -> listData.add(e) }
|
||||
onChanged?.invoke()
|
||||
binding.unmuteAllIcon.isVisible = listData.isNotEmpty()
|
||||
binding.listView.isVisible = listData.isNotEmpty()
|
||||
binding.listNoDataView.isVisible = listData.isEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
/** 执行更新 */
|
||||
refreshData()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
/*
|
||||
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
|
||||
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
|
||||
* https://github.com/KitsunePie/AppErrorsTracking
|
||||
*
|
||||
* This software is non-free but opensource software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Affero General Public License
|
||||
* as published by the Free Software Foundation; either
|
||||
* version 3 of the License, or any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/5/11.
|
||||
*/
|
||||
@file:Suppress("DEPRECATION", "OVERRIDE_DEPRECATION", "SetTextI18n")
|
||||
|
||||
package com.fankes.apperrorstracking.ui.activity.errors
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.ComponentName
|
||||
import android.content.Intent
|
||||
import android.view.ContextMenu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.widget.AdapterView.AdapterContextMenuInfo
|
||||
import androidx.core.view.isVisible
|
||||
import com.fankes.apperrorstracking.BuildConfig
|
||||
import com.fankes.apperrorstracking.R
|
||||
import com.fankes.apperrorstracking.bean.AppErrorsInfoBean
|
||||
import com.fankes.apperrorstracking.bean.AppFiltersBean
|
||||
import com.fankes.apperrorstracking.databinding.ActivityAppErrorsRecordBinding
|
||||
import com.fankes.apperrorstracking.databinding.AdapterAppErrorsRecordBinding
|
||||
import com.fankes.apperrorstracking.databinding.DiaAppErrorsStatisticsBinding
|
||||
import com.fankes.apperrorstracking.locale.LocaleString
|
||||
import com.fankes.apperrorstracking.ui.activity.base.BaseActivity
|
||||
import com.fankes.apperrorstracking.utils.factory.*
|
||||
import com.fankes.apperrorstracking.utils.tool.FrameworkTool
|
||||
import com.fankes.apperrorstracking.utils.tool.ZipFileTool
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
|
||||
class AppErrorsRecordActivity : BaseActivity<ActivityAppErrorsRecordBinding>() {
|
||||
|
||||
companion object {
|
||||
|
||||
/** 请求保存文件回调标识 */
|
||||
private const val WRITE_REQUEST_CODE = 0
|
||||
|
||||
/**
|
||||
* 获取 [Intent]
|
||||
* @return [Intent]
|
||||
*/
|
||||
fun intent() = Intent().apply { component = ComponentName(BuildConfig.APPLICATION_ID, AppErrorsRecordActivity::class.java.name) }
|
||||
}
|
||||
|
||||
/** 当前导出文件的路径 */
|
||||
private var outPutFilePath = ""
|
||||
|
||||
/** 回调适配器改变 */
|
||||
private var onChanged: (() -> Unit)? = null
|
||||
|
||||
/** 全部的 APP 异常信息 */
|
||||
private val listData = ArrayList<AppErrorsInfoBean>()
|
||||
|
||||
override fun onCreate() {
|
||||
binding.titleBackIcon.setOnClickListener { onBackPressed() }
|
||||
binding.appErrorSisIcon.setOnClickListener {
|
||||
showDialog {
|
||||
title = LocaleString.notice
|
||||
progressContent = LocaleString.generatingStatistics
|
||||
noCancelable()
|
||||
FrameworkTool.fetchAppListData(context, AppFiltersBean(isContainsSystem = true)) {
|
||||
Thread {
|
||||
val errorsApps = listData.groupBy { it.packageName }
|
||||
.map { it.key to it.value.size }
|
||||
.sortedByDescending { it.second }
|
||||
.takeIf { it.isNotEmpty() }
|
||||
val mostAppPackageName = errorsApps?.get(0)?.first ?: ""
|
||||
val mostErrorsType = listData.groupBy { it.exceptionClassName }
|
||||
.map { it.key to it.value.size }
|
||||
.sortedByDescending { it.second }
|
||||
.takeIf { it.isNotEmpty() }?.get(0)?.first?.simpleThwName() ?: ""
|
||||
val pptCount = (((errorsApps?.size?.toFloat() ?: 0f) * 100f) / it.size.toFloat()).decimal()
|
||||
runOnUiThread {
|
||||
cancel()
|
||||
showDialog<DiaAppErrorsStatisticsBinding> {
|
||||
title = LocaleString.appErrorsStatistics
|
||||
binding.totalErrorsUnitText.text = LocaleString.totalErrorsUnit(listData.size)
|
||||
binding.totalAppsUnitText.text = LocaleString.totalAppsUnit(it.size)
|
||||
binding.mostErrorsAppIcon.setImageDrawable(appIcon(mostAppPackageName))
|
||||
binding.mostErrorsAppText.text = appName(mostAppPackageName)
|
||||
binding.mostErrorsTypeText.text = mostErrorsType
|
||||
binding.totalPptOfErrorsText.text = "$pptCount%"
|
||||
confirmButton(LocaleString.gotIt)
|
||||
}
|
||||
}
|
||||
}.start()
|
||||
}
|
||||
}
|
||||
}
|
||||
binding.clearAllIcon.setOnClickListener {
|
||||
showDialog {
|
||||
title = LocaleString.notice
|
||||
msg = LocaleString.areYouSureClearErrors
|
||||
confirmButton {
|
||||
FrameworkTool.clearAppErrorsInfoData(context) {
|
||||
refreshData()
|
||||
toast(LocaleString.allErrorsClearSuccess)
|
||||
}
|
||||
}
|
||||
cancelButton()
|
||||
}
|
||||
}
|
||||
binding.exportAllIcon.setOnClickListener {
|
||||
showDialog {
|
||||
title = LocaleString.notice
|
||||
msg = LocaleString.areYouSureExportAllErrors
|
||||
confirmButton { exportAll() }
|
||||
cancelButton()
|
||||
}
|
||||
}
|
||||
/** 设置列表元素和 Adapter */
|
||||
binding.listView.apply {
|
||||
bindAdapter {
|
||||
onBindDatas { listData }
|
||||
onBindViews<AdapterAppErrorsRecordBinding> { binding, position ->
|
||||
listData[position].also { bean ->
|
||||
binding.appIcon.setImageDrawable(appIcon(bean.packageName))
|
||||
binding.appNameText.text = appName(bean.packageName)
|
||||
binding.errorsTimeText.text = bean.crossTime
|
||||
binding.errorTypeIcon.setImageResource(if (bean.isNativeCrash) R.drawable.ic_cpp else R.drawable.ic_java)
|
||||
binding.errorTypeText.text = if (bean.isNativeCrash) "Native crash" else bean.exceptionClassName.simpleThwName()
|
||||
binding.errorMsgText.text = bean.exceptionMessage
|
||||
}
|
||||
}
|
||||
}.apply { onChanged = { notifyDataSetChanged() } }
|
||||
registerForContextMenu(this)
|
||||
setOnItemClickListener { _, _, p, _ -> AppErrorsDetailActivity.start(context, listData[p]) }
|
||||
}
|
||||
}
|
||||
|
||||
/** 更新列表数据 */
|
||||
private fun refreshData() {
|
||||
FrameworkTool.fetchAppErrorsInfoData(context = this) {
|
||||
listData.clear()
|
||||
it.takeIf { e -> e.isNotEmpty() }?.forEach { e -> listData.add(e) }
|
||||
onChanged?.invoke()
|
||||
binding.appErrorSisIcon.isVisible = listData.size >= 5
|
||||
binding.clearAllIcon.isVisible = listData.isNotEmpty()
|
||||
binding.exportAllIcon.isVisible = listData.isNotEmpty()
|
||||
binding.listView.isVisible = listData.isNotEmpty()
|
||||
binding.listNoDataView.isVisible = listData.isEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
/** 打包导出全部 */
|
||||
private fun exportAll() {
|
||||
clearAllExportTemp()
|
||||
("${cacheDir.absolutePath}/temp").also { path ->
|
||||
File(path).mkdirs()
|
||||
listData.takeIf { it.isNotEmpty() }?.forEach {
|
||||
File("$path/${it.packageName}_${it.timestamp}.log").writeText(it.stackOutputFileContent)
|
||||
}
|
||||
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()}.zip")
|
||||
}, WRITE_REQUEST_CODE)
|
||||
}.onFailure { toast(msg = "Start Android SAF failed") }
|
||||
}
|
||||
}
|
||||
|
||||
/** 清空导出的临时文件 */
|
||||
private fun clearAllExportTemp() {
|
||||
cacheDir.deleteRecursively()
|
||||
cacheDir.mkdirs()
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取异常的精简名称
|
||||
* @return [String]
|
||||
*/
|
||||
private fun String.simpleThwName() =
|
||||
let { text -> if (text.contains(other = ".")) text.split(".").let { e -> e[e.lastIndex] } else text }
|
||||
|
||||
override fun onCreateContextMenu(menu: ContextMenu?, v: View?, menuInfo: ContextMenu.ContextMenuInfo?) {
|
||||
menuInflater.inflate(R.menu.menu_list_detail_action, menu)
|
||||
super.onCreateContextMenu(menu, v, menuInfo)
|
||||
}
|
||||
|
||||
override fun onContextItemSelected(item: MenuItem): Boolean {
|
||||
if (item.menuInfo is AdapterContextMenuInfo)
|
||||
(item.menuInfo as? AdapterContextMenuInfo?)?.also {
|
||||
when (item.itemId) {
|
||||
R.id.aerrors_view_detail -> AppErrorsDetailActivity.start(context = this, listData[it.position])
|
||||
R.id.aerrors_app_info -> openSelfSetting(listData[it.position].packageName)
|
||||
R.id.aerrors_remove_record ->
|
||||
showDialog {
|
||||
title = LocaleString.notice
|
||||
msg = LocaleString.areYouSureRemoveRecord
|
||||
confirmButton { FrameworkTool.removeAppErrorsInfoData(context, listData[it.position]) { refreshData() } }
|
||||
cancelButton()
|
||||
}
|
||||
}
|
||||
}
|
||||
return super.onContextItemSelected(item)
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
if (requestCode == WRITE_REQUEST_CODE && resultCode == Activity.RESULT_OK) runCatching {
|
||||
data?.data?.let {
|
||||
contentResolver?.openOutputStream(it)?.apply { write(FileInputStream(outPutFilePath).readBytes()) }?.close()
|
||||
clearAllExportTemp()
|
||||
toast(LocaleString.exportAllErrorsSuccess)
|
||||
} ?: toast(LocaleString.exportAllErrorsFail)
|
||||
}.onFailure { toast(LocaleString.exportAllErrorsFail) }
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
/** 执行更新 */
|
||||
refreshData()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
/*
|
||||
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
|
||||
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
|
||||
* https://github.com/KitsunePie/AppErrorsTracking
|
||||
*
|
||||
* This software is non-free but opensource software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Affero General Public License
|
||||
* as published by the Free Software Foundation; either
|
||||
* version 3 of the License, or any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/6/4.
|
||||
*/
|
||||
package com.fankes.apperrorstracking.ui.activity.main
|
||||
|
||||
import androidx.core.view.isVisible
|
||||
import com.fankes.apperrorstracking.bean.AppFiltersBean
|
||||
import com.fankes.apperrorstracking.bean.AppInfoBean
|
||||
import com.fankes.apperrorstracking.databinding.ActivityConfigBinding
|
||||
import com.fankes.apperrorstracking.databinding.AdapterAppInfoBinding
|
||||
import com.fankes.apperrorstracking.databinding.DiaAppConfigBinding
|
||||
import com.fankes.apperrorstracking.databinding.DiaAppsFilterBinding
|
||||
import com.fankes.apperrorstracking.hook.factory.*
|
||||
import com.fankes.apperrorstracking.locale.LocaleString
|
||||
import com.fankes.apperrorstracking.ui.activity.base.BaseActivity
|
||||
import com.fankes.apperrorstracking.utils.factory.appIcon
|
||||
import com.fankes.apperrorstracking.utils.factory.bindAdapter
|
||||
import com.fankes.apperrorstracking.utils.factory.showDialog
|
||||
import com.fankes.apperrorstracking.utils.tool.FrameworkTool
|
||||
|
||||
class ConfigureActivity : BaseActivity<ActivityConfigBinding>() {
|
||||
|
||||
/** 过滤条件 */
|
||||
private var appFilters = AppFiltersBean()
|
||||
|
||||
/** 回调适配器改变 */
|
||||
private var onChanged: (() -> Unit)? = null
|
||||
|
||||
/** 全部的 APP 信息 */
|
||||
private val listData = ArrayList<AppInfoBean>()
|
||||
|
||||
override fun onCreate() {
|
||||
binding.titleBackIcon.setOnClickListener { onBackPressed() }
|
||||
binding.batchIcon.setOnClickListener {
|
||||
showDialog<DiaAppConfigBinding> {
|
||||
title = LocaleString.batchOperations
|
||||
confirmButton {
|
||||
val config0 = binding.configRadio0.isChecked
|
||||
val config1 = binding.configRadio1.isChecked
|
||||
val config2 = binding.configRadio2.isChecked
|
||||
val config3 = binding.configRadio3.isChecked
|
||||
showDialog {
|
||||
title = LocaleString.notice
|
||||
msg = LocaleString.areYouSureApplySiteApps(listData.size)
|
||||
confirmButton {
|
||||
listData.takeIf { it.isNotEmpty() }?.forEach {
|
||||
putAppShowErrorsDialog(it.packageName, config0)
|
||||
putAppShowErrorsNotify(it.packageName, config1)
|
||||
putAppShowErrorsToast(it.packageName, config2)
|
||||
putAppShowNothing(it.packageName, config3)
|
||||
}
|
||||
onChanged?.invoke()
|
||||
}
|
||||
cancelButton()
|
||||
}
|
||||
}
|
||||
cancelButton()
|
||||
}
|
||||
}
|
||||
binding.filterIcon.setOnClickListener {
|
||||
showDialog<DiaAppsFilterBinding> {
|
||||
title = LocaleString.filterByCondition
|
||||
binding.containsSystemSwitch.isChecked = appFilters.isContainsSystem
|
||||
binding.appFiltersEdit.apply {
|
||||
requestFocus()
|
||||
invalidate()
|
||||
if (appFilters.name.isNotBlank()) {
|
||||
setText(appFilters.name)
|
||||
setSelection(appFilters.name.length)
|
||||
}
|
||||
}
|
||||
confirmButton {
|
||||
appFilters.isContainsSystem = binding.containsSystemSwitch.isChecked
|
||||
appFilters.name = binding.appFiltersEdit.text.toString().trim()
|
||||
refreshData()
|
||||
}
|
||||
cancelButton()
|
||||
if (appFilters.name.isNotBlank())
|
||||
neutralButton(LocaleString.clearFilters) {
|
||||
appFilters.isContainsSystem = binding.containsSystemSwitch.isChecked
|
||||
appFilters.name = ""
|
||||
refreshData()
|
||||
}
|
||||
}
|
||||
}
|
||||
binding.listView.apply {
|
||||
bindAdapter {
|
||||
onBindDatas { listData }
|
||||
onBindViews<AdapterAppInfoBinding> { binding, position ->
|
||||
listData[position].also { bean ->
|
||||
binding.appIcon.setImageDrawable(bean.icon)
|
||||
binding.appNameText.text = bean.name
|
||||
binding.configTypeText.text = when {
|
||||
isAppShowErrorsDialog(bean.packageName) -> LocaleString.showErrorsDialog
|
||||
isAppShowErrorsNotify(bean.packageName) -> LocaleString.showErrorsNotify
|
||||
isAppShowErrorsToast(bean.packageName) -> LocaleString.showErrorsToast
|
||||
isAppShowNothing(bean.packageName) -> LocaleString.showNothing
|
||||
else -> "Unknown type"
|
||||
}
|
||||
}
|
||||
}
|
||||
}.apply { onChanged = { notifyDataSetChanged() } }
|
||||
setOnItemClickListener { _, _, p, _ ->
|
||||
listData[p].also { bean ->
|
||||
showDialog<DiaAppConfigBinding> {
|
||||
title = bean.name
|
||||
binding.configRadio0.isChecked = isAppShowErrorsDialog(bean.packageName)
|
||||
binding.configRadio1.isChecked = isAppShowErrorsNotify(bean.packageName)
|
||||
binding.configRadio2.isChecked = isAppShowErrorsToast(bean.packageName)
|
||||
binding.configRadio3.isChecked = isAppShowNothing(bean.packageName)
|
||||
confirmButton {
|
||||
putAppShowErrorsDialog(bean.packageName, binding.configRadio0.isChecked)
|
||||
putAppShowErrorsNotify(bean.packageName, binding.configRadio1.isChecked)
|
||||
putAppShowErrorsToast(bean.packageName, binding.configRadio2.isChecked)
|
||||
putAppShowNothing(bean.packageName, binding.configRadio3.isChecked)
|
||||
onChanged?.invoke()
|
||||
}
|
||||
cancelButton()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/** 模块未完全激活将显示警告 */
|
||||
if (MainActivity.isModuleValied.not())
|
||||
showDialog {
|
||||
title = LocaleString.notice
|
||||
msg = LocaleString.moduleNotFullyActivatedTip
|
||||
confirmButton { FrameworkTool.restartSystem(context) }
|
||||
cancelButton()
|
||||
noCancelable()
|
||||
}
|
||||
/** 开始刷新数据 */
|
||||
refreshData()
|
||||
}
|
||||
|
||||
/** 刷新列表数据 */
|
||||
private fun refreshData() {
|
||||
binding.listProgressView.isVisible = true
|
||||
binding.batchIcon.isVisible = false
|
||||
binding.filterIcon.isVisible = false
|
||||
binding.listView.isVisible = false
|
||||
binding.listNoDataView.isVisible = false
|
||||
binding.titleCountText.text = LocaleString.loading
|
||||
FrameworkTool.fetchAppListData(context = this, appFilters) {
|
||||
listData.clear()
|
||||
Thread {
|
||||
it.takeIf { e -> e.isNotEmpty() }?.forEach { e ->
|
||||
listData.add(e)
|
||||
e.icon = appIcon(e.packageName)
|
||||
}
|
||||
runOnUiThread {
|
||||
onChanged?.invoke()
|
||||
binding.listView.post { binding.listView.setSelection(0) }
|
||||
binding.listProgressView.isVisible = false
|
||||
binding.batchIcon.isVisible = listData.isNotEmpty()
|
||||
binding.filterIcon.isVisible = true
|
||||
binding.listView.isVisible = listData.isNotEmpty()
|
||||
binding.listNoDataView.isVisible = listData.isEmpty()
|
||||
binding.titleCountText.text = LocaleString.resultCount(listData.size)
|
||||
}
|
||||
}.start()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
|
||||
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
|
||||
* https://github.com/KitsunePie/AppErrorsTracking
|
||||
*
|
||||
* This software is non-free but opensource software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Affero General Public License
|
||||
* as published by the Free Software Foundation; either
|
||||
* version 3 of the License, or any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/5/14.
|
||||
*/
|
||||
@file:Suppress("SetTextI18n")
|
||||
|
||||
package com.fankes.apperrorstracking.ui.activity.main
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import androidx.core.view.isVisible
|
||||
import com.fankes.apperrorstracking.BuildConfig
|
||||
import com.fankes.apperrorstracking.R
|
||||
import com.fankes.apperrorstracking.data.DataConst
|
||||
import com.fankes.apperrorstracking.databinding.ActivityMainBinding
|
||||
import com.fankes.apperrorstracking.locale.LocaleString
|
||||
import com.fankes.apperrorstracking.ui.activity.base.BaseActivity
|
||||
import com.fankes.apperrorstracking.ui.activity.errors.AppErrorsMutedActivity
|
||||
import com.fankes.apperrorstracking.ui.activity.errors.AppErrorsRecordActivity
|
||||
import com.fankes.apperrorstracking.utils.factory.navigate
|
||||
import com.fankes.apperrorstracking.utils.factory.openBrowser
|
||||
import com.fankes.apperrorstracking.utils.factory.showDialog
|
||||
import com.fankes.apperrorstracking.utils.factory.toast
|
||||
import com.fankes.apperrorstracking.utils.tool.FrameworkTool
|
||||
import com.highcapable.yukihookapi.YukiHookAPI
|
||||
import com.highcapable.yukihookapi.hook.factory.modulePrefs
|
||||
|
||||
class MainActivity : BaseActivity<ActivityMainBinding>() {
|
||||
|
||||
companion object {
|
||||
|
||||
/** 模块是否有效 */
|
||||
var isModuleValied = false
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
binding.mainTextVersion.text = LocaleString.moduleVersion(BuildConfig.VERSION_NAME)
|
||||
binding.mainTextSystemVersion.text =
|
||||
LocaleString.systemVersion("${Build.VERSION.RELEASE} (API ${Build.VERSION.SDK_INT}) ${Build.DISPLAY}")
|
||||
binding.onlyShowErrorsInFrontSwitch.isChecked = modulePrefs.get(DataConst.ENABLE_ONLY_SHOW_ERRORS_IN_FRONT)
|
||||
binding.onlyShowErrorsInMainProcessSwitch.isChecked = modulePrefs.get(DataConst.ENABLE_ONLY_SHOW_ERRORS_IN_MAIN)
|
||||
binding.alwaysShowsReopenAppOptionsSwitch.isChecked = modulePrefs.get(DataConst.ENABLE_ALWAYS_SHOWS_REOPEN_APP_OPTIONS)
|
||||
binding.enableAppsConfigsTemplateSwitch.isChecked = modulePrefs.get(DataConst.ENABLE_APP_CONFIG_TEMPLATE)
|
||||
binding.hideIconInLauncherSwitch.isChecked = modulePrefs.get(DataConst.ENABLE_HIDE_ICON)
|
||||
binding.mgrAppsConfigsTemplateButton.isVisible = modulePrefs.get(DataConst.ENABLE_APP_CONFIG_TEMPLATE)
|
||||
binding.hideIconInLauncherSwitch.setOnCheckedChangeListener { btn, b ->
|
||||
if (btn.isPressed.not()) return@setOnCheckedChangeListener
|
||||
modulePrefs.put(DataConst.ENABLE_HIDE_ICON, b)
|
||||
packageManager.setComponentEnabledSetting(
|
||||
ComponentName(packageName, "${BuildConfig.APPLICATION_ID}.Home"),
|
||||
if (b) PackageManager.COMPONENT_ENABLED_STATE_DISABLED else PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
|
||||
PackageManager.DONT_KILL_APP
|
||||
)
|
||||
}
|
||||
binding.onlyShowErrorsInFrontSwitch.setOnCheckedChangeListener { btn, b ->
|
||||
if (btn.isPressed.not()) return@setOnCheckedChangeListener
|
||||
modulePrefs.put(DataConst.ENABLE_ONLY_SHOW_ERRORS_IN_FRONT, b)
|
||||
}
|
||||
binding.onlyShowErrorsInMainProcessSwitch.setOnCheckedChangeListener { btn, b ->
|
||||
if (btn.isPressed.not()) return@setOnCheckedChangeListener
|
||||
modulePrefs.put(DataConst.ENABLE_ONLY_SHOW_ERRORS_IN_MAIN, b)
|
||||
}
|
||||
binding.alwaysShowsReopenAppOptionsSwitch.setOnCheckedChangeListener { btn, b ->
|
||||
if (btn.isPressed.not()) return@setOnCheckedChangeListener
|
||||
modulePrefs.put(DataConst.ENABLE_ALWAYS_SHOWS_REOPEN_APP_OPTIONS, b)
|
||||
}
|
||||
binding.enableAppsConfigsTemplateSwitch.setOnCheckedChangeListener { btn, b ->
|
||||
if (btn.isPressed.not()) return@setOnCheckedChangeListener
|
||||
binding.mgrAppsConfigsTemplateButton.isVisible = b
|
||||
modulePrefs.put(DataConst.ENABLE_APP_CONFIG_TEMPLATE, b)
|
||||
}
|
||||
/** 管理应用配置模板按钮点击事件 */
|
||||
binding.mgrAppsConfigsTemplateButton.setOnClickListener { whenActivated { navigate<ConfigureActivity>() } }
|
||||
/** 功能管理按钮点击事件 */
|
||||
binding.viewErrorsRecordButton.setOnClickListener { whenActivated { navigate<AppErrorsRecordActivity>() } }
|
||||
binding.viewMutedErrorsAppsButton.setOnClickListener { whenActivated { navigate<AppErrorsMutedActivity>() } }
|
||||
/** 重启按钮点击事件 */
|
||||
binding.titleRestartIcon.setOnClickListener { FrameworkTool.restartSystem(context = this) }
|
||||
/** 项目地址按钮点击事件 */
|
||||
binding.titleGithubIcon.setOnClickListener { openBrowser(url = "https://github.com/KitsunePie/AppErrorsTracking") }
|
||||
/** 显示开发者提示 */
|
||||
if (modulePrefs.get(DataConst.SHOW_DEVELOPER_NOTICE))
|
||||
showDialog {
|
||||
title = LocaleString.developerNotice
|
||||
msg = LocaleString.developerNoticeTip
|
||||
confirmButton(LocaleString.gotIt) { modulePrefs.put(DataConst.SHOW_DEVELOPER_NOTICE, value = false) }
|
||||
noCancelable()
|
||||
}
|
||||
}
|
||||
|
||||
/** 刷新模块状态 */
|
||||
private fun refreshModuleStatus() {
|
||||
binding.mainLinStatus.setBackgroundResource(
|
||||
when {
|
||||
YukiHookAPI.Status.isXposedModuleActive && isModuleValied.not() -> R.drawable.bg_yellow_round
|
||||
YukiHookAPI.Status.isXposedModuleActive -> R.drawable.bg_green_round
|
||||
else -> R.drawable.bg_dark_round
|
||||
}
|
||||
)
|
||||
binding.mainImgStatus.setImageResource(
|
||||
when {
|
||||
YukiHookAPI.Status.isXposedModuleActive -> R.mipmap.ic_success
|
||||
else -> R.mipmap.ic_warn
|
||||
}
|
||||
)
|
||||
binding.mainTextStatus.text =
|
||||
when {
|
||||
YukiHookAPI.Status.isXposedModuleActive && isModuleValied.not() -> LocaleString.moduleNotFullyActivated
|
||||
YukiHookAPI.Status.isXposedModuleActive -> LocaleString.moduleIsActivated
|
||||
else -> LocaleString.moduleNotActivated
|
||||
}
|
||||
binding.mainTextApiWay.isVisible = YukiHookAPI.Status.isXposedModuleActive
|
||||
binding.mainTextApiWay.text = "Activated by ${YukiHookAPI.Status.executorName} API ${YukiHookAPI.Status.executorVersion}"
|
||||
}
|
||||
|
||||
/**
|
||||
* 当模块激活后才能执行相应功能
|
||||
* @param callback 激活后回调
|
||||
*/
|
||||
private inline fun whenActivated(callback: () -> Unit) {
|
||||
if (YukiHookAPI.Status.isXposedModuleActive) callback() else toast(LocaleString.moduleNotActivated)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
/** 刷新模块状态 */
|
||||
refreshModuleStatus()
|
||||
/** 检查模块激活状态 */
|
||||
FrameworkTool.checkingActivated(context = this) { isValied ->
|
||||
isModuleValied = isValied
|
||||
refreshModuleStatus()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
|
||||
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
|
||||
* https://github.com/KitsunePie/AppErrorsTracking
|
||||
*
|
||||
* This software is non-free but opensource software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Affero General Public License
|
||||
* as published by the Free Software Foundation; either
|
||||
* version 3 of the License, or any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/6/1.
|
||||
*/
|
||||
package com.fankes.apperrorstracking.ui.view
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.Gravity
|
||||
import android.widget.LinearLayout
|
||||
import com.fankes.apperrorstracking.utils.drawable.drawabletoolbox.DrawableBuilder
|
||||
import com.fankes.apperrorstracking.utils.factory.dp
|
||||
|
||||
class ItemLinearLayout(context: Context, attrs: AttributeSet?) : LinearLayout(context, attrs) {
|
||||
|
||||
init {
|
||||
gravity = Gravity.CENTER or Gravity.START
|
||||
background = DrawableBuilder()
|
||||
.rounded()
|
||||
.cornerRadius(15.dp(context))
|
||||
.ripple()
|
||||
.rippleColor(0xFFAAAAAA.toInt())
|
||||
.build()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
|
||||
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
|
||||
* https://github.com/KitsunePie/AppErrorsTracking
|
||||
*
|
||||
* This software is non-free but opensource software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Affero General Public License
|
||||
* as published by the Free Software Foundation; either
|
||||
* version 3 of the License, or any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/5/14.
|
||||
*/
|
||||
@file:Suppress("SameParameterValue")
|
||||
|
||||
package com.fankes.apperrorstracking.ui.view
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.ColorStateList
|
||||
import android.graphics.Color
|
||||
import android.text.TextUtils
|
||||
import android.util.AttributeSet
|
||||
import androidx.appcompat.widget.SwitchCompat
|
||||
import com.fankes.apperrorstracking.utils.drawable.drawabletoolbox.DrawableBuilder
|
||||
import com.fankes.apperrorstracking.utils.factory.dp
|
||||
import com.fankes.apperrorstracking.utils.factory.isSystemInDarkMode
|
||||
|
||||
class MaterialSwitch(context: Context, attrs: AttributeSet?) : SwitchCompat(context, attrs) {
|
||||
|
||||
private fun toColors(selected: Int, pressed: Int, normal: Int): ColorStateList {
|
||||
val colors = intArrayOf(selected, pressed, normal)
|
||||
val states = arrayOfNulls<IntArray>(3)
|
||||
states[0] = intArrayOf(android.R.attr.state_checked)
|
||||
states[1] = intArrayOf(android.R.attr.state_pressed)
|
||||
states[2] = intArrayOf()
|
||||
return ColorStateList(states, colors)
|
||||
}
|
||||
|
||||
private val thumbColor get() = if (context.isSystemInDarkMode) 0xFF7C7C7C else 0xFFCCCCCC
|
||||
|
||||
init {
|
||||
trackDrawable = DrawableBuilder()
|
||||
.rectangle()
|
||||
.rounded()
|
||||
.solidColor(0xFF656565.toInt())
|
||||
.height(20.dp(context))
|
||||
.cornerRadius(15.dp(context))
|
||||
.build()
|
||||
thumbDrawable = DrawableBuilder()
|
||||
.rectangle()
|
||||
.rounded()
|
||||
.solidColor(Color.WHITE)
|
||||
.size(20.dp(context), 20.dp(context))
|
||||
.cornerRadius(20.dp(context))
|
||||
.strokeWidth(8.dp(context))
|
||||
.strokeColor(Color.TRANSPARENT)
|
||||
.build()
|
||||
trackTintList = toColors(
|
||||
0xFF656565.toInt(),
|
||||
thumbColor.toInt(),
|
||||
thumbColor.toInt()
|
||||
)
|
||||
isSingleLine = true
|
||||
ellipsize = TextUtils.TruncateAt.END
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,7 @@
|
||||
*
|
||||
* This file is Created by fankes on 2022/5/7.
|
||||
*/
|
||||
@file:Suppress("DEPRECATION", "CanvasSize")
|
||||
@file:Suppress("DEPRECATION", "CanvasSize", "OVERRIDE_DEPRECATION")
|
||||
|
||||
package com.fankes.apperrorstracking.utils.drawable.drawabletoolbox
|
||||
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
|
||||
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
|
||||
* https://github.com/KitsunePie/AppErrorsTracking
|
||||
*
|
||||
* This software is non-free but opensource software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Affero General Public License
|
||||
* as published by the Free Software Foundation; either
|
||||
* version 3 of the License, or any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/6/3.
|
||||
*/
|
||||
package com.fankes.apperrorstracking.utils.factory
|
||||
|
||||
import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.BaseAdapter
|
||||
import android.widget.ListView
|
||||
import androidx.viewbinding.ViewBinding
|
||||
import com.highcapable.yukihookapi.hook.factory.method
|
||||
import com.highcapable.yukihookapi.hook.type.android.LayoutInflaterClass
|
||||
|
||||
/**
|
||||
* 绑定 [BaseAdapter] 到 [ListView]
|
||||
* @param initiate 方法体
|
||||
* @return [BaseAdapter]
|
||||
*/
|
||||
inline fun ListView.bindAdapter(initiate: BaseAdapterCreater.() -> Unit) =
|
||||
BaseAdapterCreater(context).apply(initiate).baseAdapter?.apply { adapter = this } ?: error("BaseAdapter not binded")
|
||||
|
||||
/**
|
||||
* [BaseAdapter] 创建类
|
||||
* @param context 实例
|
||||
*/
|
||||
class BaseAdapterCreater(val context: Context) {
|
||||
|
||||
/** 当前 [List] 回调 */
|
||||
var listDataCallback: (() -> List<*>)? = null
|
||||
|
||||
/** 当前 [BaseAdapter] */
|
||||
var baseAdapter: BaseAdapter? = null
|
||||
|
||||
/**
|
||||
* 绑定 [List] 到 [ListView]
|
||||
* @param result 回调数据
|
||||
*/
|
||||
fun onBindDatas(result: (() -> List<*>)) {
|
||||
listDataCallback = result
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定 [BaseAdapter] 到 [ListView]
|
||||
* @param bindViews 回调 - ([VB] 每项,[Int] 下标)
|
||||
*/
|
||||
inline fun <reified VB : ViewBinding> onBindViews(crossinline bindViews: (binding: VB, position: Int) -> Unit) {
|
||||
baseAdapter = object : BaseAdapter() {
|
||||
override fun getCount() = listDataCallback?.let { it() }?.size ?: 0
|
||||
override fun getItem(position: Int) = listDataCallback?.let { it() }?.get(position)
|
||||
override fun getItemId(position: Int) = position.toLong()
|
||||
override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
|
||||
var holderView = convertView
|
||||
val holder: VB
|
||||
if (convertView == null) {
|
||||
holder = VB::class.java.method {
|
||||
name = "inflate"
|
||||
param(LayoutInflaterClass)
|
||||
}.get().invoke<VB>(LayoutInflater.from(context)) ?: error("ViewHolder binding failed")
|
||||
holderView = holder.root.apply { tag = holder }
|
||||
} else holder = convertView.tag as VB
|
||||
bindViews(holder, position)
|
||||
return holderView ?: error("ViewHolder binding failed")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,229 @@
|
||||
/*
|
||||
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
|
||||
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
|
||||
* https://github.com/KitsunePie/AppErrorsTracking
|
||||
*
|
||||
* This software is non-free but opensource software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Affero General Public License
|
||||
* as published by the Free Software Foundation; either
|
||||
* version 3 of the License, or any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/5/12.
|
||||
*/
|
||||
@file:Suppress("unused", "DEPRECATION", "OPT_IN_USAGE", "EXPERIMENTAL_API_USAGE")
|
||||
|
||||
package com.fankes.apperrorstracking.utils.factory
|
||||
|
||||
import android.app.Dialog
|
||||
import android.content.Context
|
||||
import android.graphics.Color
|
||||
import android.graphics.drawable.GradientDrawable
|
||||
import android.graphics.drawable.InsetDrawable
|
||||
import android.view.Gravity
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.ProgressBar
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.viewbinding.ViewBinding
|
||||
import com.fankes.apperrorstracking.locale.LocaleString
|
||||
import com.fankes.apperrorstracking.ui.activity.errors.AppErrorsDisplayActivity
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.shape.MaterialShapeDrawable
|
||||
import com.highcapable.yukihookapi.annotation.CauseProblemsApi
|
||||
import com.highcapable.yukihookapi.hook.factory.method
|
||||
import com.highcapable.yukihookapi.hook.type.android.LayoutInflaterClass
|
||||
|
||||
/**
|
||||
* 构造 [VB] 自定义 View 对话框
|
||||
* @param initiate 对话框方法体
|
||||
*/
|
||||
@JvmName(name = "showDialog-VB")
|
||||
inline fun <reified VB : ViewBinding> Context.showDialog(initiate: DialogBuilder<VB>.() -> Unit) =
|
||||
DialogBuilder<VB>(context = this, VB::class.java).apply(initiate).show()
|
||||
|
||||
/**
|
||||
* 构造对话框
|
||||
* @param initiate 对话框方法体
|
||||
*/
|
||||
inline fun Context.showDialog(initiate: DialogBuilder<*>.() -> Unit) = DialogBuilder<ViewBinding>(context = this).apply(initiate).show()
|
||||
|
||||
/**
|
||||
* 对话框构造器
|
||||
* @param context 实例
|
||||
* @param bindingClass [ViewBinding] 的 [Class] 实例 or null
|
||||
*/
|
||||
class DialogBuilder<VB : ViewBinding>(val context: Context, private val bindingClass: Class<*>? = null) {
|
||||
|
||||
private var instanceAndroidX: androidx.appcompat.app.AlertDialog.Builder? = null // 实例对象
|
||||
private var instanceAndroid: android.app.AlertDialog.Builder? = null // 实例对象
|
||||
|
||||
private var onCancel: (() -> Unit)? = null // 对话框取消监听
|
||||
private var dialogInstance: Dialog? = null // 对话框实例
|
||||
private var customLayoutView: View? = null // 自定义布局
|
||||
|
||||
/**
|
||||
* 获取 [DialogBuilder] 绑定布局对象
|
||||
* @return [VB]
|
||||
*/
|
||||
val binding by lazy {
|
||||
bindingClass?.method {
|
||||
name = "inflate"
|
||||
param(LayoutInflaterClass)
|
||||
}?.get()?.invoke<VB>(LayoutInflater.from(context))?.apply {
|
||||
customLayoutView = root
|
||||
} ?: error("This dialog maybe not a custom view dialog")
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否需要使用 AndroidX 风格对话框
|
||||
* @return [Boolean]
|
||||
*/
|
||||
private val isUsingAndroidX get() = runCatching { context is AppCompatActivity }.getOrNull() ?: false
|
||||
|
||||
init {
|
||||
if (isUsingAndroidX) runCatching {
|
||||
instanceAndroidX = MaterialAlertDialogBuilder(context).also { builder ->
|
||||
if (context is AppErrorsDisplayActivity)
|
||||
builder.background = (builder.background as MaterialShapeDrawable).apply { setCornerSize(15.dpFloat(context)) }
|
||||
}
|
||||
} else runCatching {
|
||||
instanceAndroid = android.app.AlertDialog.Builder(
|
||||
context,
|
||||
if (context.isSystemInDarkMode) android.R.style.Theme_Material_Dialog else android.R.style.Theme_Material_Light_Dialog
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** 设置对话框不可关闭 */
|
||||
fun noCancelable() {
|
||||
if (isUsingAndroidX)
|
||||
runCatching { instanceAndroidX?.setCancelable(false) }
|
||||
else runCatching { instanceAndroid?.setCancelable(false) }
|
||||
}
|
||||
|
||||
/** 设置对话框标题 */
|
||||
var title
|
||||
get() = ""
|
||||
set(value) {
|
||||
if (isUsingAndroidX)
|
||||
runCatching { instanceAndroidX?.setTitle(value) }
|
||||
else runCatching { instanceAndroid?.setTitle(value) }
|
||||
}
|
||||
|
||||
/** 设置对话框消息内容 */
|
||||
var msg
|
||||
get() = ""
|
||||
set(value) {
|
||||
if (isUsingAndroidX)
|
||||
runCatching { instanceAndroidX?.setMessage(value) }
|
||||
else runCatching { instanceAndroid?.setMessage(value) }
|
||||
}
|
||||
|
||||
/** 设置进度条对话框消息内容 */
|
||||
var progressContent
|
||||
get() = ""
|
||||
set(value) {
|
||||
if (customLayoutView == null)
|
||||
customLayoutView = LinearLayout(context).apply {
|
||||
orientation = LinearLayout.HORIZONTAL
|
||||
gravity = Gravity.CENTER or Gravity.START
|
||||
addView(ProgressBar(context))
|
||||
addView(View(context).apply { layoutParams = ViewGroup.LayoutParams(20.dp(context), 5) })
|
||||
addView(TextView(context).apply {
|
||||
tag = "progressContent"
|
||||
text = value
|
||||
})
|
||||
setPadding(20.dp(context), 20.dp(context), 20.dp(context), 20.dp(context))
|
||||
}
|
||||
else customLayoutView?.findViewWithTag<TextView>("progressContent")?.text = value
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置对话框确定按钮
|
||||
* @param text 按钮文本内容
|
||||
* @param callback 点击事件
|
||||
*/
|
||||
fun confirmButton(text: String = LocaleString.confirm, callback: () -> Unit = {}) {
|
||||
if (isUsingAndroidX)
|
||||
runCatching { instanceAndroidX?.setPositiveButton(text) { _, _ -> callback() } }
|
||||
else runCatching { instanceAndroid?.setPositiveButton(text) { _, _ -> callback() } }
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置对话框取消按钮
|
||||
* @param text 按钮文本内容
|
||||
* @param callback 点击事件
|
||||
*/
|
||||
fun cancelButton(text: String = LocaleString.cancel, callback: () -> Unit = {}) {
|
||||
if (isUsingAndroidX)
|
||||
runCatching { instanceAndroidX?.setNegativeButton(text) { _, _ -> callback() } }
|
||||
else runCatching { instanceAndroid?.setNegativeButton(text) { _, _ -> callback() } }
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置对话框第三个按钮
|
||||
* @param text 按钮文本内容
|
||||
* @param callback 点击事件
|
||||
*/
|
||||
fun neutralButton(text: String = LocaleString.more, callback: () -> Unit = {}) {
|
||||
if (isUsingAndroidX)
|
||||
runCatching { instanceAndroidX?.setNeutralButton(text) { _, _ -> callback() } }
|
||||
else runCatching { instanceAndroid?.setNeutralButton(text) { _, _ -> callback() } }
|
||||
}
|
||||
|
||||
/**
|
||||
* 当对话框关闭时
|
||||
* @param callback 回调
|
||||
*/
|
||||
fun onCancel(callback: () -> Unit) {
|
||||
onCancel = callback
|
||||
}
|
||||
|
||||
/** 取消对话框 */
|
||||
fun cancel() = dialogInstance?.cancel()
|
||||
|
||||
/** 显示对话框 */
|
||||
@CauseProblemsApi
|
||||
fun show() {
|
||||
/** 若当前自定义 View 的对话框没有调用 [binding] 将会对其手动调用一次以确保显示布局 */
|
||||
if (bindingClass != null) binding
|
||||
if (isUsingAndroidX) runCatching {
|
||||
instanceAndroidX?.create()?.apply {
|
||||
customLayoutView?.let { setView(it) }
|
||||
dialogInstance = this
|
||||
setOnCancelListener { onCancel?.invoke() }
|
||||
}?.show()
|
||||
} else runCatching {
|
||||
instanceAndroid?.create()?.apply {
|
||||
customLayoutView?.let { setView(it) }
|
||||
window?.setBackgroundDrawable(
|
||||
InsetDrawable(
|
||||
GradientDrawable(
|
||||
GradientDrawable.Orientation.TOP_BOTTOM,
|
||||
if (context.isSystemInDarkMode) intArrayOf(0xFF2D2D2D.toInt(), 0xFF2D2D2D.toInt())
|
||||
else intArrayOf(Color.WHITE, Color.WHITE)
|
||||
).apply {
|
||||
shape = GradientDrawable.RECTANGLE
|
||||
gradientType = GradientDrawable.LINEAR_GRADIENT
|
||||
cornerRadius = 15.dpFloat(this@DialogBuilder.context)
|
||||
}, 30.dp(context), 0, 30.dp(context), 0
|
||||
)
|
||||
)
|
||||
dialogInstance = this
|
||||
setOnCancelListener { onCancel?.invoke() }
|
||||
}?.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,12 +23,29 @@
|
||||
|
||||
package com.fankes.apperrorstracking.utils.factory
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.app.*
|
||||
import android.content.*
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.res.Configuration
|
||||
import android.content.res.Resources
|
||||
import android.graphics.Color
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.net.Uri
|
||||
import android.provider.Settings
|
||||
import android.widget.Toast
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.content.getSystemService
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import androidx.core.graphics.drawable.IconCompat
|
||||
import com.fankes.apperrorstracking.BuildConfig
|
||||
import com.fankes.apperrorstracking.R
|
||||
import com.fankes.apperrorstracking.locale.LocaleString
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.highcapable.yukihookapi.hook.factory.field
|
||||
import com.highcapable.yukihookapi.hook.type.android.ApplicationInfoClass
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import java.math.RoundingMode
|
||||
import java.text.DecimalFormat
|
||||
|
||||
/**
|
||||
* 系统深色模式是否开启
|
||||
@@ -56,6 +73,178 @@ fun Number.dp(context: Context) = dpFloat(context).toInt()
|
||||
*/
|
||||
fun Number.dpFloat(context: Context) = toFloat() * context.resources.displayMetrics.density
|
||||
|
||||
/**
|
||||
* 从 [Int] resId 获取 [Drawable]
|
||||
* @param resources 使用的 [Resources]
|
||||
* @return [Drawable]
|
||||
*/
|
||||
fun Int.drawableOf(resources: Resources) = ResourcesCompat.getDrawable(resources, this, null) ?: error("Invalid resources")
|
||||
|
||||
/**
|
||||
* 获取 APP 名称
|
||||
* @param packageName 包名
|
||||
* @return [String]
|
||||
*/
|
||||
fun Context.appName(packageName: String) =
|
||||
runCatching {
|
||||
packageManager.getPackageInfo(packageName, PackageManager.GET_META_DATA)
|
||||
.applicationInfo.loadLabel(packageManager).toString()
|
||||
}.getOrNull() ?: packageName
|
||||
|
||||
/**
|
||||
* 获取 APP 完整版本
|
||||
* @param packageName 包名
|
||||
* @return [String]
|
||||
*/
|
||||
fun Context.appVersion(packageName: String) =
|
||||
runCatching {
|
||||
packageManager.getPackageInfo(packageName, PackageManager.GET_META_DATA)?.let { "${it.versionName} (${it.versionCode})" }
|
||||
}.getOrNull() ?: "<unknown>"
|
||||
|
||||
/**
|
||||
* 获取 APP CPU ABI 名称
|
||||
* @param packageName 包名
|
||||
* @return [String]
|
||||
*/
|
||||
fun Context.appCpuAbi(packageName: String) =
|
||||
runCatching {
|
||||
ApplicationInfoClass.field { name = "primaryCpuAbi" }
|
||||
.get(packageManager.getPackageInfo(packageName, PackageManager.GET_META_DATA)?.applicationInfo).string()
|
||||
}.getOrNull() ?: ""
|
||||
|
||||
/**
|
||||
* 获取 APP 图标
|
||||
* @param packageName 包名
|
||||
* @return [Drawable]
|
||||
*/
|
||||
fun Context.appIcon(packageName: String) =
|
||||
runCatching {
|
||||
packageManager.getPackageInfo(packageName, PackageManager.GET_META_DATA)
|
||||
.applicationInfo.loadIcon(packageManager)
|
||||
}.getOrNull() ?: R.drawable.ic_android.drawableOf(resources)
|
||||
|
||||
/**
|
||||
* 计算与当前时间戳相差的友好时间
|
||||
* @param now 刚刚
|
||||
* @param second 秒前
|
||||
* @param minute 分钟前
|
||||
* @param hour 小时前
|
||||
* @param day 天前
|
||||
* @param month 月前
|
||||
* @param year 年前
|
||||
* @return [String] 友好时间
|
||||
*/
|
||||
fun Long.difference(now: String, second: String, minute: String, hour: String, day: String, month: String, year: String) =
|
||||
((System.currentTimeMillis() - this) / 1000).toInt().let { diff ->
|
||||
when (diff) {
|
||||
in 0..10 -> now
|
||||
in 11..20 -> "10 $second"
|
||||
in 21..30 -> "20 $second"
|
||||
in 31..40 -> "30 $second"
|
||||
in 41..50 -> "40 $second"
|
||||
in 51..59 -> "50 $second"
|
||||
in 60..3599 -> "${(diff / 60).coerceAtLeast(1)} $minute"
|
||||
in 3600..86399 -> "${diff / 3600} $hour"
|
||||
in 86400..2591999 -> "${diff / 86400} $day"
|
||||
in 2592000..31103999 -> "${diff / 2592000} $month"
|
||||
else -> "${diff / 31104000} $year"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保留小数
|
||||
* @param count 要保留的位数 - 默认 2 位 - 最多 7 位
|
||||
* @return [String] 得到的字符串数字 - 格式化失败返回原始数字的字符串
|
||||
*/
|
||||
fun Number.decimal(count: Int = 2) = runCatching {
|
||||
DecimalFormat(
|
||||
when (count) {
|
||||
0 -> "0"
|
||||
1 -> "0.0"
|
||||
2 -> "0.00"
|
||||
3 -> "0.000"
|
||||
4 -> "0.0000"
|
||||
5 -> "0.00000"
|
||||
6 -> "0.000000"
|
||||
7 -> "0.0000000"
|
||||
else -> "0.0"
|
||||
}
|
||||
).apply { roundingMode = RoundingMode.HALF_UP }.format(this) ?: toString()
|
||||
}.getOrNull() ?: this
|
||||
|
||||
/**
|
||||
* 弹出 [Toast]
|
||||
* @param msg 提示内容
|
||||
*/
|
||||
fun Context.toast(msg: String) = Toast.makeText(this, msg, Toast.LENGTH_SHORT).show()
|
||||
|
||||
/**
|
||||
* 弹出 [Snackbar]
|
||||
* @param msg 提示内容
|
||||
* @param actionText 按钮文本 - 不写默认取消按钮
|
||||
* @param callback 按钮事件回调
|
||||
*/
|
||||
fun Context.snake(msg: String, actionText: String = "", callback: () -> Unit = {}) =
|
||||
Snackbar.make((this as Activity).findViewById(android.R.id.content), msg, Snackbar.LENGTH_LONG).apply {
|
||||
if (actionText.isBlank()) return@apply
|
||||
setActionTextColor(if (isSystemInDarkMode) Color.BLACK else Color.WHITE)
|
||||
setAction(actionText) { callback() }
|
||||
}.show()
|
||||
|
||||
/**
|
||||
* 推送通知
|
||||
* @param channelId 渠道 Id
|
||||
* @param channelName 渠道名称
|
||||
* @param title 标题
|
||||
* @param content 内容
|
||||
* @param icon 图标
|
||||
* @param color 颜色
|
||||
* @param intent [Intent]
|
||||
*/
|
||||
fun Context.pushNotify(channelId: String, channelName: String, title: String, content: String, icon: IconCompat, color: Int, intent: Intent) {
|
||||
getSystemService<NotificationManager>()?.apply {
|
||||
createNotificationChannel(NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_HIGH))
|
||||
notify((0..999).random(), NotificationCompat.Builder(this@pushNotify, channelId).apply {
|
||||
this.color = color
|
||||
setAutoCancel(true)
|
||||
setContentTitle(title)
|
||||
setContentText(content)
|
||||
setSmallIcon(icon)
|
||||
setContentIntent(PendingIntent.getActivity(this@pushNotify, (0..999).random(), intent, PendingIntent.FLAG_IMMUTABLE))
|
||||
setDefaults(NotificationCompat.DEFAULT_ALL)
|
||||
}.build())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 跳转到指定页面
|
||||
*
|
||||
* [T] 为指定的 [Activity]
|
||||
* @param isOutSide 是否从外部启动
|
||||
* @param initiate [Intent] 方法体
|
||||
*/
|
||||
inline fun <reified T : Activity> Context.navigate(isOutSide: Boolean = false, initiate: Intent.() -> Unit = {}) = runCatching {
|
||||
startActivity((if (isOutSide) Intent() else Intent(if (this is Service) applicationContext else this, T::class.java)).apply {
|
||||
flags = if (this@navigate !is Activity) Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||
else Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
if (isOutSide) component = ComponentName(BuildConfig.APPLICATION_ID, T::class.java.name)
|
||||
initiate(this)
|
||||
})
|
||||
}.onFailure { toast(msg = "Start ${T::class.java.name} failed") }
|
||||
|
||||
/**
|
||||
* 复制到剪贴板
|
||||
* @param content 要复制的文本
|
||||
*/
|
||||
fun Context.copyToClipboard(content: String) = runCatching {
|
||||
(getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager).apply {
|
||||
setPrimaryClip(ClipData.newPlainText(null, content))
|
||||
(primaryClip?.getItemAt(0)?.text ?: "").also {
|
||||
if (it != content) toast(LocaleString.copyFail) else toast(LocaleString.copied)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 跳转 APP 自身设置界面
|
||||
* @param packageName 包名
|
||||
@@ -66,7 +255,32 @@ fun Context.openSelfSetting(packageName: String = this.packageName) = runCatchin
|
||||
action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS
|
||||
data = Uri.fromParts("package", packageName, null)
|
||||
})
|
||||
}.onFailure { Toast.makeText(this, "无法打开 $packageName 的设置界面", Toast.LENGTH_SHORT).show() }
|
||||
}.onFailure { toast(msg = "Cannot open '$packageName'") }
|
||||
|
||||
/**
|
||||
* 启动系统浏览器
|
||||
* @param url 网址
|
||||
* @param packageName 指定包名 - 可不填
|
||||
*/
|
||||
fun Context.openBrowser(url: String, packageName: String = "") = runCatching {
|
||||
startActivity(Intent().apply {
|
||||
if (packageName.isNotBlank()) setPackage(packageName)
|
||||
action = Intent.ACTION_VIEW
|
||||
data = Uri.parse(url)
|
||||
/** 防止顶栈一样重叠在自己的 APP 中 */
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
})
|
||||
}.onFailure {
|
||||
if (packageName.isNotBlank()) snake(msg = "Cannot start '$packageName'")
|
||||
else snake(msg = "Start system browser failed")
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前 APP 是否可被启动
|
||||
* @param packageName 包名
|
||||
*/
|
||||
fun Context.isAppCanOpened(packageName: String = this.packageName) =
|
||||
runCatching { packageManager?.getLaunchIntentForPackage(packageName) != null }.getOrNull() ?: false
|
||||
|
||||
/**
|
||||
* 启动指定 APP
|
||||
@@ -76,4 +290,22 @@ fun Context.openApp(packageName: String = this.packageName) = runCatching {
|
||||
startActivity(packageManager.getLaunchIntentForPackage(packageName)?.apply {
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
})
|
||||
}.onFailure { Toast.makeText(this, "无法启动 $packageName", Toast.LENGTH_SHORT).show() }
|
||||
}.onFailure { toast(msg = "Cannot start '$packageName'") }
|
||||
|
||||
/**
|
||||
* 是否有 Root 权限
|
||||
* @return [Boolean]
|
||||
*/
|
||||
val isRootAccess get() = runCatching { Shell.rootAccess() }.getOrNull() ?: false
|
||||
|
||||
/**
|
||||
* 执行命令
|
||||
* @param cmd 命令
|
||||
* @param isSu 是否使用 Root 权限执行 - 默认:是
|
||||
* @return [String] 执行结果
|
||||
*/
|
||||
fun execShell(cmd: String, isSu: Boolean = true) = runCatching {
|
||||
(if (isSu) Shell.su(cmd) else Shell.sh(cmd)).exec().out.let {
|
||||
if (it.isNotEmpty()) it[0].trim() else ""
|
||||
}
|
||||
}.getOrNull() ?: ""
|
||||
@@ -0,0 +1,342 @@
|
||||
/*
|
||||
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
|
||||
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
|
||||
* https://github.com/KitsunePie/AppErrorsTracking
|
||||
*
|
||||
* This software is non-free but opensource software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Affero General Public License
|
||||
* as published by the Free Software Foundation; either
|
||||
* version 3 of the License, or any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/5/12.
|
||||
*/
|
||||
@file:Suppress("UNCHECKED_CAST")
|
||||
|
||||
package com.fankes.apperrorstracking.utils.tool
|
||||
|
||||
import android.content.Context
|
||||
import com.fankes.apperrorstracking.bean.AppErrorsInfoBean
|
||||
import com.fankes.apperrorstracking.bean.AppFiltersBean
|
||||
import com.fankes.apperrorstracking.bean.AppInfoBean
|
||||
import com.fankes.apperrorstracking.bean.MutedErrorsAppBean
|
||||
import com.fankes.apperrorstracking.locale.LocaleString
|
||||
import com.fankes.apperrorstracking.utils.factory.execShell
|
||||
import com.fankes.apperrorstracking.utils.factory.isRootAccess
|
||||
import com.fankes.apperrorstracking.utils.factory.showDialog
|
||||
import com.fankes.apperrorstracking.utils.factory.snake
|
||||
import com.highcapable.yukihookapi.hook.factory.dataChannel
|
||||
import com.highcapable.yukihookapi.hook.param.PackageParam
|
||||
import com.highcapable.yukihookapi.hook.xposed.channel.data.ChannelData
|
||||
|
||||
/**
|
||||
* 系统框架控制工具
|
||||
*/
|
||||
object FrameworkTool {
|
||||
|
||||
/** 系统框架包名 */
|
||||
private const val SYSTEM_FRAMEWORK_NAME = "android"
|
||||
|
||||
private const val CALL_APP_ERRORS_DATA_GET = "call_app_errors_data_get"
|
||||
private const val CALL_MUTED_ERRORS_APP_DATA_GET = "call_muted_app_errors_data_get"
|
||||
private const val CALL_APP_ERRORS_DATA_CLEAR = "call_app_errors_data_clear"
|
||||
private const val CALL_UNMUTE_ALL_ERRORS_APPS_DATA = "call_unmute_all_errors_apps_data"
|
||||
private const val CALL_APP_ERRORS_DATA_REMOVE_RESULT = "call_app_errors_data_remove_result"
|
||||
private const val CALL_APP_ERRORS_DATA_CLEAR_RESULT = "call_app_errors_data_clear_result"
|
||||
private const val CALL_MUTED_ERRORS_IF_UNLOCK_RESULT = "call_muted_errors_if_unlock_result"
|
||||
private const val CALL_MUTED_ERRORS_IF_RESTART_RESULT = "call_muted_errors_if_restart_result"
|
||||
private const val CALL_UNMUTE_ERRORS_APP_DATA_RESULT = "call_unmute_errors_app_data_result"
|
||||
private const val CALL_UNMUTE_ALL_ERRORS_APPS_DATA_RESULT = "call_unmute_all_errors_apps_data_result"
|
||||
|
||||
private val CALL_OPEN_SPECIFY_APP = ChannelData<String>("call_open_specify_app")
|
||||
private val CALL_APP_LIST_DATA_GET = ChannelData<AppFiltersBean>("call_app_info_list_data_get")
|
||||
private val CALL_APP_ERRORS_DATA_REMOVE = ChannelData<AppErrorsInfoBean>("call_app_errors_data_remove")
|
||||
private val CALL_APP_LIST_DATA_GET_RESULT = ChannelData<ArrayList<AppInfoBean>>("call_app_info_list_data_get_result")
|
||||
private val CALL_APP_ERRORS_DATA_GET_RESULT = ChannelData<ArrayList<AppErrorsInfoBean>>("call_app_errors_data_get_result")
|
||||
private val CALL_MUTED_ERRORS_APP_DATA_GET_RESULT = ChannelData<ArrayList<MutedErrorsAppBean>>("call_muted_app_errors_data_get_result")
|
||||
private val CALL_UNMUTE_ERRORS_APP_DATA = ChannelData<MutedErrorsAppBean>("call_unmute_errors_app_data")
|
||||
private val CALL_MUTED_ERRORS_IF_UNLOCK = ChannelData<String>("call_muted_errors_if_unlock")
|
||||
private val CALL_MUTED_ERRORS_IF_RESTART = ChannelData<String>("call_muted_errors_if_restart")
|
||||
|
||||
/**
|
||||
* 宿主注册监听
|
||||
*/
|
||||
object Host {
|
||||
|
||||
/** [PackageParam] 实例 */
|
||||
private var instance: PackageParam? = null
|
||||
|
||||
/**
|
||||
* 注册监听
|
||||
* @param instance 实例
|
||||
* @param initiate 实例方法体
|
||||
* @return [Host]
|
||||
*/
|
||||
fun with(instance: PackageParam, initiate: Host.() -> Unit) = apply { this.instance = instance }.apply(initiate)
|
||||
|
||||
/**
|
||||
* 监听使用系统框架打开 APP
|
||||
* @param result 回调包名
|
||||
*/
|
||||
fun onOpenAppUsedFramework(result: (String) -> Unit) = instance?.dataChannel?.wait(CALL_OPEN_SPECIFY_APP) { result(it) }
|
||||
|
||||
/**
|
||||
* 监听发送 APP 异常信息数组
|
||||
* @param result 回调数据
|
||||
*/
|
||||
fun onPushAppErrorsInfoData(result: () -> ArrayList<AppErrorsInfoBean>) {
|
||||
instance?.dataChannel?.with { wait(CALL_APP_ERRORS_DATA_GET) { put(CALL_APP_ERRORS_DATA_GET_RESULT, result()) } }
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听移除指定 APP 异常信息
|
||||
* @param result 回调数据
|
||||
*/
|
||||
fun onRemoveAppErrorsInfoData(result: (AppErrorsInfoBean) -> Unit) {
|
||||
instance?.dataChannel?.with {
|
||||
wait(CALL_APP_ERRORS_DATA_REMOVE) {
|
||||
result(it)
|
||||
put(CALL_APP_ERRORS_DATA_REMOVE_RESULT)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听清空 APP 异常信息数组
|
||||
* @param callback 回调
|
||||
*/
|
||||
fun onClearAppErrorsInfoData(callback: () -> Unit) {
|
||||
instance?.dataChannel?.with {
|
||||
wait(CALL_APP_ERRORS_DATA_CLEAR) {
|
||||
callback()
|
||||
put(CALL_APP_ERRORS_DATA_CLEAR_RESULT)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听忽略 APP 的错误直到设备重新解锁
|
||||
* @param result 回调包名
|
||||
*/
|
||||
fun onMutedErrorsIfUnlock(result: (String) -> Unit) {
|
||||
instance?.dataChannel?.with {
|
||||
wait(CALL_MUTED_ERRORS_IF_UNLOCK) {
|
||||
result(it)
|
||||
put(CALL_MUTED_ERRORS_IF_UNLOCK_RESULT)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听忽略 APP 的错误直到设备重新启动
|
||||
* @param result 回调包名
|
||||
*/
|
||||
fun onMutedErrorsIfRestart(result: (String) -> Unit) {
|
||||
instance?.dataChannel?.with {
|
||||
wait(CALL_MUTED_ERRORS_IF_RESTART) {
|
||||
result(it)
|
||||
put(CALL_MUTED_ERRORS_IF_RESTART_RESULT)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听发送已忽略异常的 APP 信息数组
|
||||
* @param result 回调数据
|
||||
*/
|
||||
fun onPushMutedErrorsAppsData(result: () -> ArrayList<MutedErrorsAppBean>) {
|
||||
instance?.dataChannel?.with { wait(CALL_MUTED_ERRORS_APP_DATA_GET) { put(CALL_MUTED_ERRORS_APP_DATA_GET_RESULT, result()) } }
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听取消指定已忽略异常的 APP
|
||||
* @param result 回调数据
|
||||
*/
|
||||
fun onUnmuteErrorsApp(result: (MutedErrorsAppBean) -> Unit) {
|
||||
instance?.dataChannel?.with {
|
||||
wait(CALL_UNMUTE_ERRORS_APP_DATA) {
|
||||
result(it)
|
||||
put(CALL_UNMUTE_ERRORS_APP_DATA_RESULT)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听取消全部已忽略异常的 APP
|
||||
* @param callback 回调
|
||||
*/
|
||||
fun onUnmuteAllErrorsApps(callback: () -> Unit) {
|
||||
instance?.dataChannel?.with {
|
||||
wait(CALL_UNMUTE_ALL_ERRORS_APPS_DATA) {
|
||||
callback()
|
||||
put(CALL_UNMUTE_ALL_ERRORS_APPS_DATA_RESULT)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听发送已安装 APP 列表数组
|
||||
* @param result 回调数据
|
||||
*/
|
||||
fun onPushAppListData(result: (AppFiltersBean) -> ArrayList<AppInfoBean>) {
|
||||
instance?.dataChannel?.with { wait(CALL_APP_LIST_DATA_GET) { put(CALL_APP_LIST_DATA_GET_RESULT, result(it)) } }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重启系统
|
||||
* @param context 实例
|
||||
*/
|
||||
fun restartSystem(context: Context) =
|
||||
context.showDialog {
|
||||
title = LocaleString.notice
|
||||
msg = LocaleString.areYouSureRestartSystem
|
||||
confirmButton {
|
||||
if (isRootAccess)
|
||||
execShell(cmd = "reboot")
|
||||
else context.snake(LocaleString.accessRootFail)
|
||||
}
|
||||
neutralButton(LocaleString.fastRestart) {
|
||||
if (isRootAccess)
|
||||
execShell(cmd = "killall zygote")
|
||||
else context.snake(LocaleString.accessRootFail)
|
||||
}
|
||||
cancelButton()
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查模块是否激活
|
||||
* @param context 实例
|
||||
* @param result 成功后回调
|
||||
*/
|
||||
fun checkingActivated(context: Context, result: (Boolean) -> Unit) = context.dataChannel(SYSTEM_FRAMEWORK_NAME).checkingVersionEquals(result)
|
||||
|
||||
/**
|
||||
* 使用系统框架打开 [packageName]
|
||||
* @param context 实例
|
||||
* @param packageName APP 包名
|
||||
*/
|
||||
fun openAppUsedFramework(context: Context, packageName: String) =
|
||||
context.dataChannel(SYSTEM_FRAMEWORK_NAME).put(CALL_OPEN_SPECIFY_APP, packageName)
|
||||
|
||||
/**
|
||||
* 获取 APP 异常信息数组
|
||||
* @param context 实例
|
||||
* @param result 回调数据
|
||||
*/
|
||||
fun fetchAppErrorsInfoData(context: Context, result: (ArrayList<AppErrorsInfoBean>) -> Unit) {
|
||||
context.dataChannel(SYSTEM_FRAMEWORK_NAME).with {
|
||||
wait(CALL_APP_ERRORS_DATA_GET_RESULT) { result(it) }
|
||||
put(CALL_APP_ERRORS_DATA_GET)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除指定 APP 异常信息
|
||||
* @param context 实例
|
||||
* @param appErrorsInfo 指定 APP 异常信息
|
||||
* @param callback 成功后回调
|
||||
*/
|
||||
fun removeAppErrorsInfoData(context: Context, appErrorsInfo: AppErrorsInfoBean, callback: () -> Unit) {
|
||||
context.dataChannel(SYSTEM_FRAMEWORK_NAME).with {
|
||||
wait(CALL_APP_ERRORS_DATA_REMOVE_RESULT) { callback() }
|
||||
put(CALL_APP_ERRORS_DATA_REMOVE, appErrorsInfo)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空 APP 异常信息数组
|
||||
* @param context 实例
|
||||
* @param callback 成功后回调
|
||||
*/
|
||||
fun clearAppErrorsInfoData(context: Context, callback: () -> Unit) {
|
||||
context.dataChannel(SYSTEM_FRAMEWORK_NAME).with {
|
||||
wait(CALL_APP_ERRORS_DATA_CLEAR_RESULT) { callback() }
|
||||
put(CALL_APP_ERRORS_DATA_CLEAR)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 忽略 [packageName] 的错误直到设备重新解锁
|
||||
* @param context 实例
|
||||
* @param packageName APP 包名
|
||||
* @param callback 成功后回调
|
||||
*/
|
||||
fun mutedErrorsIfUnlock(context: Context, packageName: String, callback: () -> Unit) {
|
||||
context.dataChannel(SYSTEM_FRAMEWORK_NAME).with {
|
||||
wait(CALL_MUTED_ERRORS_IF_UNLOCK_RESULT) { callback() }
|
||||
put(CALL_MUTED_ERRORS_IF_UNLOCK, packageName)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 忽略 [packageName] 的错误直到设备重新启动
|
||||
* @param context 实例
|
||||
* @param packageName APP 包名
|
||||
* @param callback 成功后回调
|
||||
*/
|
||||
fun mutedErrorsIfRestart(context: Context, packageName: String, callback: () -> Unit) {
|
||||
context.dataChannel(SYSTEM_FRAMEWORK_NAME).with {
|
||||
wait(CALL_MUTED_ERRORS_IF_RESTART_RESULT) { callback() }
|
||||
put(CALL_MUTED_ERRORS_IF_RESTART, packageName)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取已忽略异常的 APP 信息数组
|
||||
* @param context 实例
|
||||
* @param result 回调数据
|
||||
*/
|
||||
fun fetchMutedErrorsAppsData(context: Context, result: (ArrayList<MutedErrorsAppBean>) -> Unit) {
|
||||
context.dataChannel(SYSTEM_FRAMEWORK_NAME).with {
|
||||
wait(CALL_MUTED_ERRORS_APP_DATA_GET_RESULT) { result(it) }
|
||||
put(CALL_MUTED_ERRORS_APP_DATA_GET)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消指定已忽略异常的 APP
|
||||
* @param context 实例
|
||||
* @param mutedErrorsApp 指定已忽略异常的 APP 信息
|
||||
* @param callback 成功后回调
|
||||
*/
|
||||
fun unmuteErrorsApp(context: Context, mutedErrorsApp: MutedErrorsAppBean, callback: () -> Unit) {
|
||||
context.dataChannel(SYSTEM_FRAMEWORK_NAME).with {
|
||||
wait(CALL_UNMUTE_ERRORS_APP_DATA_RESULT) { callback() }
|
||||
put(CALL_UNMUTE_ERRORS_APP_DATA, mutedErrorsApp)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消全部已忽略异常的 APP
|
||||
* @param context 实例
|
||||
* @param callback 成功后回调
|
||||
*/
|
||||
fun unmuteAllErrorsApps(context: Context, callback: () -> Unit) {
|
||||
context.dataChannel(SYSTEM_FRAMEWORK_NAME).with {
|
||||
wait(CALL_UNMUTE_ALL_ERRORS_APPS_DATA_RESULT) { callback() }
|
||||
put(CALL_UNMUTE_ALL_ERRORS_APPS_DATA)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取已安装 APP 列表数组
|
||||
* @param context 实例
|
||||
* @param appFilters 过滤条件
|
||||
* @param result 回调数据
|
||||
*/
|
||||
fun fetchAppListData(context: Context, appFilters: AppFiltersBean, result: (ArrayList<AppInfoBean>) -> Unit) {
|
||||
context.dataChannel(SYSTEM_FRAMEWORK_NAME).with {
|
||||
wait(CALL_APP_LIST_DATA_GET_RESULT) { result(it) }
|
||||
put(CALL_APP_LIST_DATA_GET, appFilters)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
|
||||
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
|
||||
* https://github.com/KitsunePie/AppErrorsTracking
|
||||
*
|
||||
* This software is non-free but opensource software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Affero General Public License
|
||||
* as published by the Free Software Foundation; either
|
||||
* version 3 of the License, or any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* and eula along with this software. If not, see
|
||||
* <https://www.gnu.org/licenses/>
|
||||
*
|
||||
* This file is Created by fankes on 2022/5/13.
|
||||
*/
|
||||
@file:Suppress("unused")
|
||||
|
||||
package com.fankes.apperrorstracking.utils.tool
|
||||
|
||||
import java.io.*
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipInputStream
|
||||
import java.util.zip.ZipOutputStream
|
||||
|
||||
/**
|
||||
* 处理压缩文件
|
||||
*/
|
||||
object ZipFileTool {
|
||||
|
||||
private const val BUFF_SIZE = 2048
|
||||
|
||||
/**
|
||||
* 压缩整个文件夹中的所有文件 - 生成指定名称的 Zip 压缩包
|
||||
* @param filePath 文件所在目录
|
||||
* @param zipPath 压缩后 Zip 文件名称
|
||||
* @param isDirFlag Zip 文件中第一层是否包含一级目录 - true 包含 false没有
|
||||
*/
|
||||
fun zipMultiFile(filePath: String, zipPath: String, isDirFlag: Boolean = false) {
|
||||
runCatching {
|
||||
val file = File(filePath)
|
||||
val zipFile = File(zipPath)
|
||||
val zipOut = ZipOutputStream(FileOutputStream(zipFile))
|
||||
if (file.isDirectory) {
|
||||
val files = file.listFiles() ?: emptyArray()
|
||||
for (fileSec in files)
|
||||
if (isDirFlag) recursionZip(zipOut, fileSec, file.name + File.separator)
|
||||
else recursionZip(zipOut, fileSec, "")
|
||||
}
|
||||
zipOut.close()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解压文件
|
||||
* @param unZipPath 解压后的目录
|
||||
* @param zipPath 压缩文件目录
|
||||
*/
|
||||
fun unZipFile(unZipPath: String, zipPath: String) {
|
||||
runCatching {
|
||||
unZipFileByInput(unZipPath, FileInputStream(zipPath))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解压文件
|
||||
* @param unZipPath 解压后的目录
|
||||
* @param zips 压缩文件流
|
||||
*/
|
||||
private fun unZipFileByInput(unZipPath: String, zips: FileInputStream) {
|
||||
val path = createSeparator(unZipPath)
|
||||
var bos: BufferedOutputStream? = null
|
||||
var zis: ZipInputStream? = null
|
||||
try {
|
||||
var filename: String
|
||||
zis = ZipInputStream(BufferedInputStream(zips))
|
||||
var ze: ZipEntry
|
||||
val buffer = ByteArray(BUFF_SIZE)
|
||||
var count: Int
|
||||
while (zis.nextEntry.also { ze = it } != null) {
|
||||
filename = ze.name
|
||||
createSubFolders(filename, path)
|
||||
if (ze.isDirectory) {
|
||||
val fmd = File(path + filename)
|
||||
fmd.mkdirs()
|
||||
continue
|
||||
}
|
||||
bos = BufferedOutputStream(FileOutputStream(path + filename))
|
||||
while (zis.read(buffer).also { count = it } != -1) bos.write(buffer, 0, count)
|
||||
bos.flush()
|
||||
bos.close()
|
||||
}
|
||||
} catch (_: IOException) {
|
||||
} finally {
|
||||
runCatching {
|
||||
if (zis != null) {
|
||||
zis.closeEntry()
|
||||
zis.close()
|
||||
}
|
||||
bos?.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @base code */
|
||||
private fun recursionZip(zipOut: ZipOutputStream, file: File, baseDir: String) {
|
||||
if (file.isDirectory) {
|
||||
val files = file.listFiles() ?: emptyArray()
|
||||
for (fileSec in files) recursionZip(zipOut, fileSec, baseDir + file.name + File.separator)
|
||||
} else {
|
||||
val buf = ByteArray(1024)
|
||||
val input: InputStream = FileInputStream(file)
|
||||
zipOut.putNextEntry(ZipEntry(baseDir + file.name))
|
||||
var len: Int
|
||||
while (input.read(buf).also { len = it } != -1) {
|
||||
zipOut.write(buf, 0, len)
|
||||
}
|
||||
input.close()
|
||||
}
|
||||
}
|
||||
|
||||
/** @base code */
|
||||
private fun createSubFolders(filename: String, path: String) {
|
||||
val subFolders = filename.split("/").toTypedArray()
|
||||
if (subFolders.size <= 1) return
|
||||
var pathNow = path
|
||||
for (i in 0 until subFolders.size - 1) {
|
||||
pathNow = pathNow + subFolders[i] + "/"
|
||||
val fmd = File(pathNow)
|
||||
if (fmd.exists()) continue
|
||||
fmd.mkdirs()
|
||||
}
|
||||
}
|
||||
|
||||
/** @base code */
|
||||
private fun createSeparator(path: String): String {
|
||||
val dir = File(path)
|
||||
if (!dir.exists()) dir.mkdirs()
|
||||
return if (path.endsWith("/")) path else "$path/"
|
||||
}
|
||||
}
|
||||
6
app/src/main/res/drawable-night/bg_dark_round.xml
Executable file
6
app/src/main/res/drawable-night/bg_dark_round.xml
Executable file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="#66A6A6A6" />
|
||||
<corners android:radius="15dp" />
|
||||
</shape>
|
||||
10
app/src/main/res/drawable-night/bg_permotion_ripple.xml
Normal file
10
app/src/main/res/drawable-night/bg_permotion_ripple.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:color="#777777">
|
||||
<item>
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="#666E6E6E" />
|
||||
<corners android:radius="15dp" />
|
||||
</shape>
|
||||
</item>
|
||||
</ripple>
|
||||
6
app/src/main/res/drawable-night/bg_permotion_round.xml
Normal file
6
app/src/main/res/drawable-night/bg_permotion_round.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="#666E6E6E" />
|
||||
<corners android:radius="15dp" />
|
||||
</shape>
|
||||
6
app/src/main/res/drawable/bg_black_round.xml
Normal file
6
app/src/main/res/drawable/bg_black_round.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="#292929" />
|
||||
<corners android:radius="5dp" />
|
||||
</shape>
|
||||
10
app/src/main/res/drawable/bg_button_round.xml
Normal file
10
app/src/main/res/drawable/bg_button_round.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:color="#777777">
|
||||
<item>
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="#66DAD9D9" />
|
||||
<corners android:radius="15dp" />
|
||||
</shape>
|
||||
</item>
|
||||
</ripple>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user