mirror of
https://github.com/topjohnwu/Magisk
synced 2025-10-26 02:22:14 +01:00
Compare commits
441 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
32fc34f922 | ||
|
|
b82a393692 | ||
|
|
3c7e792167 | ||
|
|
0ad66875ab | ||
|
|
1191ac2671 | ||
|
|
928b3425e3 | ||
|
|
0726a00e3b | ||
|
|
5a88984d34 | ||
|
|
18de60f68c | ||
|
|
1893359142 | ||
|
|
f5e5ab2436 | ||
|
|
ff5ea1a70d | ||
|
|
54ee63a409 | ||
|
|
f095606b50 | ||
|
|
e8f31c78d7 | ||
|
|
b34c477d5e | ||
|
|
28611304f7 | ||
|
|
76af9e6e1f | ||
|
|
7b3b965ed7 | ||
|
|
567b905ef1 | ||
|
|
a94268329c | ||
|
|
a11a18686a | ||
|
|
c58e3a99ee | ||
|
|
b166663e89 | ||
|
|
ac13ac14f6 | ||
|
|
06531f6d06 | ||
|
|
f6274d94f6 | ||
|
|
2b303a7e23 | ||
|
|
2bb074a5ad | ||
|
|
3b2db56243 | ||
|
|
45483fde74 | ||
|
|
d742cfa48f | ||
|
|
95353ce9eb | ||
|
|
ab2cc72814 | ||
|
|
5c54a2c008 | ||
|
|
2fe3082518 | ||
|
|
5a889d28c8 | ||
|
|
45e7c1c030 | ||
|
|
c6dcff0ae7 | ||
|
|
b791dc5e1a | ||
|
|
46db281006 | ||
|
|
636479b15b | ||
|
|
dcbb4eabb5 | ||
|
|
068cedaa84 | ||
|
|
02dd962601 | ||
|
|
256d715648 | ||
|
|
cbe97cdfde | ||
|
|
407dfc7547 | ||
|
|
a8e4e077ec | ||
|
|
3d06ba1878 | ||
|
|
8a23d1da58 | ||
|
|
d3eb61e0e4 | ||
|
|
7cdf2d244d | ||
|
|
c59a41a607 | ||
|
|
e0410b6f10 | ||
|
|
8eac6c0b48 | ||
|
|
bf8b74e996 | ||
|
|
691e41e22e | ||
|
|
15e91d42ee | ||
|
|
5e8e94fd0f | ||
|
|
5313a46aa2 | ||
|
|
761a8dde65 | ||
|
|
a73acfb9c2 | ||
|
|
fbe17dde03 | ||
|
|
a01a3404fe | ||
|
|
454e5dfc5d | ||
|
|
47545b45b8 | ||
|
|
7c9908d953 | ||
|
|
5f4cd50cc4 | ||
|
|
b0fba6ce5b | ||
|
|
1f5992f2c2 | ||
|
|
abfd3c3e5d | ||
|
|
97da7f9691 | ||
|
|
2752083d29 | ||
|
|
c826318da4 | ||
|
|
6582a4abd9 | ||
|
|
a699dab5b3 | ||
|
|
21c8ad5b9e | ||
|
|
195d885887 | ||
|
|
519bd2f30f | ||
|
|
20ef724fad | ||
|
|
f443cbaa2b | ||
|
|
dbf45da8ab | ||
|
|
6b67902d53 | ||
|
|
0ad0ef485c | ||
|
|
7dfe3e53d5 | ||
|
|
5be3bd1e64 | ||
|
|
bc0c1980db | ||
|
|
2997258fd0 | ||
|
|
11600fc116 | ||
|
|
a8640f52ef | ||
|
|
0f4e44c38f | ||
|
|
053f4d481d | ||
|
|
f466c27da9 | ||
|
|
bfe6bc3095 | ||
|
|
ff8f3e766e | ||
|
|
6635ea3e29 | ||
|
|
591788c0df | ||
|
|
571b8986a4 | ||
|
|
bb7a74e4b4 | ||
|
|
76ddfeb93a | ||
|
|
c38b826abf | ||
|
|
21d7db0959 | ||
|
|
d7b51d2807 | ||
|
|
a7af8b5722 | ||
|
|
9c93fe6003 | ||
|
|
21505a7470 | ||
|
|
ba6e6cc15a | ||
|
|
fd7bf2bc3a | ||
|
|
b2cd24ed1b | ||
|
|
66cf2c984a | ||
|
|
de1b2b19b0 | ||
|
|
e31583485d | ||
|
|
490e51c1d7 | ||
|
|
1df2a04713 | ||
|
|
42804d5314 | ||
|
|
558710bbdd | ||
|
|
f4926cb822 | ||
|
|
1e77e0862a | ||
|
|
8c696cb8ca | ||
|
|
62ef8ade8f | ||
|
|
3d88dd3123 | ||
|
|
880b348ce6 | ||
|
|
31fe3a1cd8 | ||
|
|
19182ffddf | ||
|
|
afcc60066e | ||
|
|
d3ade06421 | ||
|
|
f1a3ef9590 | ||
|
|
d1d73f11a5 | ||
|
|
05697372f8 | ||
|
|
0c1f68816e | ||
|
|
92546e8a74 | ||
|
|
a4faa3f392 | ||
|
|
df191cd2b5 | ||
|
|
baa19f0ccf | ||
|
|
5a49bd3ac9 | ||
|
|
b37d7e0500 | ||
|
|
f4ed6274a4 | ||
|
|
56eb1a1cf9 | ||
|
|
a7c156a9e3 | ||
|
|
d81ca77231 | ||
|
|
bf013f6ebb | ||
|
|
dd8116e285 | ||
|
|
b5d80a88d1 | ||
|
|
7f4f95cf83 | ||
|
|
87c2f6ad14 | ||
|
|
ad47dba064 | ||
|
|
41b701846f | ||
|
|
5c42830328 | ||
|
|
69617309f8 | ||
|
|
48e2d6a8da | ||
|
|
b4120cddfb | ||
|
|
54e3f1998a | ||
|
|
edcf9f1b0c | ||
|
|
de3747d65e | ||
|
|
b76a3614da | ||
|
|
94cc64c51b | ||
|
|
0f71edee96 | ||
|
|
e097c097fe | ||
|
|
1443a5b175 | ||
|
|
2d82ad93dd | ||
|
|
384c257a74 | ||
|
|
49dfa2c3a0 | ||
|
|
7bd3e768db | ||
|
|
65224ed22b | ||
|
|
0a28dfe1e2 | ||
|
|
1c8ebfacb0 | ||
|
|
5d6d241791 | ||
|
|
4f116d15b9 | ||
|
|
228570640e | ||
|
|
65a79610aa | ||
|
|
24984ea4f2 | ||
|
|
048b2af0fc | ||
|
|
449989ddd9 | ||
|
|
01ebe5724a | ||
|
|
95fb230b8c | ||
|
|
632971af15 | ||
|
|
5787aa1078 | ||
|
|
d8b9265484 | ||
|
|
9ea3169ca9 | ||
|
|
aebf2672cd | ||
|
|
68ac409bfd | ||
|
|
fef44bd24f | ||
|
|
e4a7617dde | ||
|
|
4dfb193d10 | ||
|
|
c248d94995 | ||
|
|
d4ac458d17 | ||
|
|
93e443c4ad | ||
|
|
4b3988cef9 | ||
|
|
4eb5ee17b4 | ||
|
|
e1b63d7dec | ||
|
|
4b5651bd6f | ||
|
|
50515d9128 | ||
|
|
28b5faab0c | ||
|
|
82a01c22d3 | ||
|
|
be9b0c2e8f | ||
|
|
b6affe06a5 | ||
|
|
1e05f8c646 | ||
|
|
7e9d4512b6 | ||
|
|
5fa127c415 | ||
|
|
ac26681fe7 | ||
|
|
3c62636133 | ||
|
|
ca874fa12c | ||
|
|
c3508bbb99 | ||
|
|
6935033db5 | ||
|
|
421277d730 | ||
|
|
56988944b5 | ||
|
|
528601d25a | ||
|
|
ddd153c00d | ||
|
|
b8c1588284 | ||
|
|
4dac9e40bd | ||
|
|
def1811d48 | ||
|
|
c53e507713 | ||
|
|
e0ea777249 | ||
|
|
4c1962f3c7 | ||
|
|
258e89c964 | ||
|
|
3d3bfb42e5 | ||
|
|
6dbd8baa7e | ||
|
|
e660fabc57 | ||
|
|
2115bcd8b0 | ||
|
|
1bdd6e1a9d | ||
|
|
98deec232b | ||
|
|
022c217cfe | ||
|
|
81f57949ed | ||
|
|
fca5eb083f | ||
|
|
a3695cc66b | ||
|
|
6723d20616 | ||
|
|
627ec91687 | ||
|
|
9126cf0c73 | ||
|
|
16322ab30c | ||
|
|
5682917356 | ||
|
|
c91ccc8b4e | ||
|
|
63f670fc36 | ||
|
|
e20b07fa24 | ||
|
|
472656517f | ||
|
|
d232cba02d | ||
|
|
e49d29a914 | ||
|
|
3aa1a68cdc | ||
|
|
f94452083f | ||
|
|
ce1ee5cb9d | ||
|
|
48df6b8485 | ||
|
|
ae23ae2d37 | ||
|
|
e34e04af04 | ||
|
|
ff3f377911 | ||
|
|
18065826b9 | ||
|
|
84e19ceef0 | ||
|
|
59161efd08 | ||
|
|
6663fd3526 | ||
|
|
2c44e1bb93 | ||
|
|
e3f6399473 | ||
|
|
89c2c21774 | ||
|
|
2954eb4bdc | ||
|
|
e08de91666 | ||
|
|
a170acb9d7 | ||
|
|
6a086bb222 | ||
|
|
b2f152e641 | ||
|
|
6c5b261804 | ||
|
|
8bd0c44e83 | ||
|
|
34c36984e9 | ||
|
|
8bd6aca0dd | ||
|
|
983b74be77 | ||
|
|
a3eafdd2c6 | ||
|
|
ea75a09f95 | ||
|
|
4c747c4148 | ||
|
|
49abfcafed | ||
|
|
50710c72ad | ||
|
|
2e299b3814 | ||
|
|
43d11d877d | ||
|
|
d7e7df3bd9 | ||
|
|
8d8ba11221 | ||
|
|
2536a18c00 | ||
|
|
11728b2b15 | ||
|
|
627501b9ba | ||
|
|
3599384b38 | ||
|
|
4b307cad2c | ||
|
|
7496d51580 | ||
|
|
4194ac894c | ||
|
|
ffb5d9ea9c | ||
|
|
770b28ca30 | ||
|
|
62e464f706 | ||
|
|
8d0dc37ec0 | ||
|
|
fe41df87bb | ||
|
|
8276a0775d | ||
|
|
abfb3bb3bb | ||
|
|
e184eb4a23 | ||
|
|
d0fc372ecd | ||
|
|
6f54c57647 | ||
|
|
e8ae103d5f | ||
|
|
b0198dab6c | ||
|
|
b75ec09998 | ||
|
|
c8ac6c07b0 | ||
|
|
27814e3015 | ||
|
|
f59309a445 | ||
|
|
b0292d7319 | ||
|
|
7f18616cc0 | ||
|
|
2fef98a5af | ||
|
|
36765caedc | ||
|
|
f7aed10ea2 | ||
|
|
410bbb8285 | ||
|
|
f56ea52932 | ||
|
|
cb4361b7b7 | ||
|
|
ecd332c573 | ||
|
|
a0fe78a728 | ||
|
|
49cc9c529e | ||
|
|
7635b2c33f | ||
|
|
50c26d33ab | ||
|
|
f642fb3b99 | ||
|
|
e68dd866a3 | ||
|
|
73d36fdff0 | ||
|
|
5561cd3c77 | ||
|
|
32a9acb913 | ||
|
|
f7f23c6e77 | ||
|
|
3d4edbd9dc | ||
|
|
bdf385f374 | ||
|
|
9f78c3e64b | ||
|
|
f370052815 | ||
|
|
9df4b10067 | ||
|
|
d20517483e | ||
|
|
713ce4719b | ||
|
|
f3d39e7515 | ||
|
|
61783ffc82 | ||
|
|
05c4ad01d5 | ||
|
|
12647dcf30 | ||
|
|
da38f59e62 | ||
|
|
cf4ef54dc5 | ||
|
|
12e9873514 | ||
|
|
f7c0e407ca | ||
|
|
82c7662cdf | ||
|
|
4f0bced53e | ||
|
|
f1b6c9f4aa | ||
|
|
0ab31ab0df | ||
|
|
46e8f0779f | ||
|
|
3fb72a4d20 | ||
|
|
db20f65d7c | ||
|
|
63cfe7b47b | ||
|
|
db590091b3 | ||
|
|
7b25e74418 | ||
|
|
82f303e1c6 | ||
|
|
c038683b54 | ||
|
|
3a37ed6b60 | ||
|
|
706a492218 | ||
|
|
c0be5383de | ||
|
|
3b8ce85092 | ||
|
|
b6298f8602 | ||
|
|
abfec57972 | ||
|
|
470fc97d1f | ||
|
|
8d59caf635 | ||
|
|
acf25aa4d3 | ||
|
|
16de4674ec | ||
|
|
65b0ea792e | ||
|
|
fc6b02f607 | ||
|
|
136d8c39d9 | ||
|
|
24a8b41182 | ||
|
|
810cf4dee8 | ||
|
|
9bf835e810 | ||
|
|
eca37bce38 | ||
|
|
3ee6a2baf2 | ||
|
|
69fa7f238d | ||
|
|
de2306bd12 | ||
|
|
714feeb9a7 | ||
|
|
ca99808fd2 | ||
|
|
f8f8c28fec | ||
|
|
f497867ba5 | ||
|
|
383192784d | ||
|
|
605189bc6e | ||
|
|
c0a2e3674c | ||
|
|
76f0602684 | ||
|
|
477ff12cde | ||
|
|
9c09ad3b62 | ||
|
|
a967afc629 | ||
|
|
dcc1fd3ee4 | ||
|
|
933f020b3c | ||
|
|
f5c02be5bf | ||
|
|
68fbdd474c | ||
|
|
2cbc048352 | ||
|
|
e990ffd4a0 | ||
|
|
743c7c9326 | ||
|
|
067248da75 | ||
|
|
f5c982355a | ||
|
|
f98c68a280 | ||
|
|
773bf0c6bc | ||
|
|
080ab6032c | ||
|
|
350144df29 | ||
|
|
9ac0f11d9a | ||
|
|
8079d456ab | ||
|
|
acf166cf9d | ||
|
|
439d497a13 | ||
|
|
0580932610 | ||
|
|
85399f609c | ||
|
|
4bcfee397b | ||
|
|
34bcb1dd26 | ||
|
|
117d1ed080 | ||
|
|
f324252681 | ||
|
|
0dad06cdfe | ||
|
|
9396288ca2 | ||
|
|
f89f08833e | ||
|
|
79e8962854 | ||
|
|
34e5a7cd24 | ||
|
|
7343c195b7 | ||
|
|
0af041b54e | ||
|
|
92a8a3e91f | ||
|
|
f41575d8b0 | ||
|
|
d93c4a5103 | ||
|
|
6fe9b69aad | ||
|
|
5d162f81c4 | ||
|
|
4771c2810b | ||
|
|
0cd99712fa | ||
|
|
b591af7803 | ||
|
|
171d68ca72 | ||
|
|
bade4f2c6a | ||
|
|
5754782a4e | ||
|
|
decdd54c19 | ||
|
|
ffe47300a1 | ||
|
|
6f9c3c4ff3 | ||
|
|
9b3efffba9 | ||
|
|
003fea52b1 | ||
|
|
2b17c77195 | ||
|
|
c252a50fd7 | ||
|
|
cf8f042a20 | ||
|
|
844bc2d808 | ||
|
|
27f7fa7153 | ||
|
|
b325aa4555 | ||
|
|
c2c3bf0ba4 | ||
|
|
0d977b54f7 | ||
|
|
20860da4b4 | ||
|
|
3ea10b7cf9 | ||
|
|
1ec33863bc | ||
|
|
a260e99090 | ||
|
|
25efdd3d6f | ||
|
|
00a1e18959 | ||
|
|
c59f8adc4a | ||
|
|
1eb83ad812 | ||
|
|
7717f0a6b0 | ||
|
|
5e1fba3603 | ||
|
|
66cc9bc545 | ||
|
|
12aa5838d9 | ||
|
|
4f73534837 | ||
|
|
c4d145835c | ||
|
|
f822ca5b23 | ||
|
|
8aaa45c62a | ||
|
|
2f4f257070 |
19
.github/ccache.sh
vendored
Normal file
19
.github/ccache.sh
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
OS=$(uname)
|
||||
CCACHE_VER=4.4
|
||||
|
||||
case $OS in
|
||||
Darwin )
|
||||
brew install ccache
|
||||
ln -s $(which ccache) ./ccache
|
||||
;;
|
||||
Linux )
|
||||
sudo apt-get install -y ccache
|
||||
ln -s $(which ccache) ./ccache
|
||||
;;
|
||||
* )
|
||||
curl -OL https://github.com/ccache/ccache/releases/download/v${CCACHE_VER}/ccache-${CCACHE_VER}-windows-64.zip
|
||||
unzip -j ccache-*-windows-64.zip '*/ccache.exe'
|
||||
;;
|
||||
esac
|
||||
mkdir ./.ccache
|
||||
./ccache -o compiler_check='%compiler% -dumpmachine; %compiler% -dumpversion'
|
||||
53
.github/workflows/build.yml
vendored
53
.github/workflows/build.yml
vendored
@@ -10,6 +10,7 @@ on:
|
||||
- 'buildSrc/**'
|
||||
- 'build.py'
|
||||
- 'gradle.properties'
|
||||
- '.github/workflows/build.yml'
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
workflow_dispatch:
|
||||
@@ -21,7 +22,10 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ ubuntu-latest, windows-latest, macOS-latest ]
|
||||
os: [ ubuntu-latest, windows-latest, macos-latest ]
|
||||
env:
|
||||
NDK_CCACHE: ${{ github.workspace }}/ccache
|
||||
CCACHE_DIR: ${{ github.workspace }}/.ccache
|
||||
|
||||
steps:
|
||||
- name: Check out
|
||||
@@ -40,50 +44,43 @@ jobs:
|
||||
with:
|
||||
python-version: '3.x'
|
||||
|
||||
- name: Set up GitHub env (Windows)
|
||||
if: runner.os == 'Windows'
|
||||
run: |
|
||||
$ndk_ver = Select-String -Path "gradle.properties" -Pattern "^magisk.fullNdkVersion=" | % { $_ -replace ".*=" }
|
||||
echo "ANDROID_SDK_ROOT=$env:ANDROID_SDK_ROOT" >> $env:GITHUB_ENV
|
||||
echo "MAGISK_NDK_VERSION=$ndk_ver" >> $env:GITHUB_ENV
|
||||
echo "GRADLE_OPTS=-Dorg.gradle.daemon=false" >> $env:GITHUB_ENV
|
||||
- name: Set up ccache
|
||||
run: bash .github/ccache.sh
|
||||
|
||||
- name: Set up GitHub env (Unix)
|
||||
if: runner.os != 'Windows'
|
||||
run: |
|
||||
ndk_ver=$(sed -n 's/^magisk.fullNdkVersion=//p' gradle.properties)
|
||||
echo ANDROID_SDK_ROOT=$ANDROID_SDK_ROOT >> $GITHUB_ENV
|
||||
echo MAGISK_NDK_VERSION=$ndk_ver >> $GITHUB_ENV
|
||||
|
||||
- name: Cache Gradle
|
||||
- name: Cache Gradle dependencies
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
~/.gradle/wrapper
|
||||
!~/.gradle/caches/build-cache-*
|
||||
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle.kts') }}
|
||||
restore-keys: ${{ runner.os }}-gradle-
|
||||
|
||||
- name: Cache NDK
|
||||
id: ndk-cache
|
||||
- name: Cache build cache
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ${{ env.ANDROID_SDK_ROOT }}/ndk/magisk
|
||||
key: ${{ runner.os }}-ndk-${{ env.MAGISK_NDK_VERSION }}
|
||||
path: |
|
||||
${{ github.workspace }}/.ccache
|
||||
~/.gradle/caches/build-cache-*
|
||||
key: ${{ runner.os }}-build-cache-${{ github.sha }}
|
||||
restore-keys: ${{ runner.os }}-build-cache-
|
||||
|
||||
- name: Set up NDK
|
||||
if: steps.ndk-cache.outputs.cache-hit != 'true'
|
||||
run: python build.py ndk
|
||||
run: python build.py -v ndk
|
||||
|
||||
- name: Build release
|
||||
run: python build.py -vr all
|
||||
|
||||
- name: Refresh flag
|
||||
run: touch gradle.properties
|
||||
shell: bash
|
||||
run: |
|
||||
./ccache -zp
|
||||
python build.py -vr all
|
||||
|
||||
- name: Build debug
|
||||
run: python build.py -v all
|
||||
run: |
|
||||
python build.py -v all
|
||||
./ccache -s
|
||||
|
||||
- name: Stop gradle daemon
|
||||
run: ./gradlew --stop
|
||||
|
||||
# Only upload artifacts built on Linux
|
||||
- name: Upload build artifact
|
||||
|
||||
26
.github/workflows/issues.yml
vendored
26
.github/workflows/issues.yml
vendored
@@ -1,26 +0,0 @@
|
||||
name: Check Issues
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened]
|
||||
jobs:
|
||||
check:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out
|
||||
uses: actions/checkout@v2
|
||||
- name: Read latest version code
|
||||
run: |
|
||||
ver=$(sed -n 's/^magisk.versionCode=//p' gradle.properties)
|
||||
echo MAGISK_VERSION_CODE=$ver >> $GITHUB_ENV
|
||||
- if: contains(github.event.issue.body, format('Magisk version code{0} ', ':')) != true
|
||||
id: close
|
||||
name: Close Issue(template)
|
||||
uses: peter-evans/close-issue@v1
|
||||
with:
|
||||
comment: This issue is being automatically closed because it does not follow the issue template.
|
||||
- if: steps.close.conclusion == 'skipped' && contains(github.event.issue.body, format('Magisk version code{0} {1}', ':', env.MAGISK_VERSION_CODE)) != true
|
||||
name: Close Issue(latest canary)
|
||||
uses: peter-evans/close-issue@v1
|
||||
with:
|
||||
comment: This issue is being automatically closed because latest canary Magisk version code is ${{ env.MAGISK_VERSION_CODE }}.
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -4,6 +4,7 @@ out
|
||||
*.apk
|
||||
/config.prop
|
||||
/update.sh
|
||||
/dict.txt
|
||||
|
||||
# Built binaries
|
||||
native/out
|
||||
|
||||
6
.gitmodules
vendored
6
.gitmodules
vendored
@@ -34,6 +34,12 @@
|
||||
[submodule "zlib"]
|
||||
path = native/jni/external/zlib
|
||||
url = https://android.googlesource.com/platform/external/zlib
|
||||
[submodule "parallel-hashmap"]
|
||||
path = native/jni/external/parallel-hashmap
|
||||
url = https://github.com/greg7mdp/parallel-hashmap.git
|
||||
[submodule "termux-elf-cleaner"]
|
||||
path = tools/termux-elf-cleaner
|
||||
url = https://github.com/termux/termux-elf-cleaner.git
|
||||
[submodule "zopfli"]
|
||||
path = native/jni/external/zopfli
|
||||
url = https://github.com/google/zopfli.git
|
||||
|
||||
21
README.MD
21
README.MD
@@ -2,28 +2,29 @@
|
||||
|
||||
[](https://raw.githubusercontent.com/topjohnwu/magisk-files/count/count.json)
|
||||
|
||||
#### This is not an officially supported Google product
|
||||
|
||||
## Introduction
|
||||
|
||||
Magisk is a suite of open source software for customizing Android, supporting devices higher than Android 5.0.<br>
|
||||
Here are some feature highlights:
|
||||
Some highlight features:
|
||||
|
||||
- **MagiskSU**: Provide root access for applications
|
||||
- **Magisk Modules**: Modify read-only partitions by installing modules
|
||||
- **MagiskHide**: Hide Magisk from root detections / system integrity checks
|
||||
- **MagiskBoot**: The most complete tool for unpacking and repacking Android boot images
|
||||
- **Zygisk**: Run code in every Android applications' processes
|
||||
|
||||
## Downloads
|
||||
|
||||
[Github](https://github.com/topjohnwu/Magisk/) is the only source where you can get official Magisk information and downloads.
|
||||
|
||||
[](https://github.com/topjohnwu/Magisk/releases/tag/v22.1)
|
||||
[](https://github.com/topjohnwu/Magisk/releases/tag/v22.1)
|
||||
[](https://github.com/topjohnwu/Magisk/releases/tag/v23.0)
|
||||
[](https://github.com/topjohnwu/Magisk/releases/tag/v24.0)
|
||||
[](https://raw.githubusercontent.com/topjohnwu/magisk-files/canary/app-debug.apk)
|
||||
|
||||
## Useful Links
|
||||
|
||||
- [Installation Instruction](https://topjohnwu.github.io/Magisk/install.html)
|
||||
- [Frequently Asked Questions](https://topjohnwu.github.io/Magisk/faq.html)
|
||||
- [Magisk Documentation](https://topjohnwu.github.io/Magisk/)
|
||||
- [Magisk Troubleshoot Wiki](https://www.didgeridoohan.com/magisk/HomePage) (by [@Didgeridoohan](https://github.com/Didgeridoohan))
|
||||
|
||||
@@ -40,15 +41,15 @@ For Magisk app crashes, record and upload the logcat when the crash occurs.
|
||||
- Magisk builds on any OS Android Studio supports. Install Android Studio and do the initial setups.
|
||||
- Clone sources: `git clone --recurse-submodules https://github.com/topjohnwu/Magisk.git`
|
||||
- Install Python 3.6+ \
|
||||
(Windows only: select **'Add Python to PATH'** in installer, and run `pip install colorama` after install)
|
||||
(Windows only: select **'Add Python to PATH'** in installer, and run `pip install colorama` after install)
|
||||
- Configure to use the JDK bundled in Android Studio:
|
||||
- macOS: `export JAVA_HOME="/Applications/Android Studio.app/Contents/jre/jdk/Contents/Home"`
|
||||
- Linux: `export PATH="/path/to/androidstudio/jre/bin:$PATH"`
|
||||
- Windows: Add `C:\Path\To\Android Studio\jre\bin` to environment variable `PATH`
|
||||
- macOS: `export JAVA_HOME="/Applications/Android Studio.app/Contents/jre/Contents/Home"`
|
||||
- Linux: `export PATH="/path/to/androidstudio/jre/bin:$PATH"`
|
||||
- Windows: Add `C:\Path\To\Android Studio\jre\bin` to environment variable `PATH`
|
||||
- Set environment variable `ANDROID_SDK_ROOT` to the Android SDK folder (can be found in Android Studio settings)
|
||||
- Run `./build.py ndk` to let the script download and install NDK for you
|
||||
- To start building, run `build.py` to see your options. \
|
||||
For each action, use `-h` to access help (e.g. `./build.py all -h`)
|
||||
For each action, use `-h` to access help (e.g. `./build.py all -h`)
|
||||
- To start development, open the project with Android Studio. The IDE can be used for both app (Kotlin/Java) and native (C++/C) sources.
|
||||
- Optionally, set custom configs with `config.prop`. A sample `config.prop.sample` is provided.
|
||||
- To sign APKs and zips with your own private keys, set signing configs in `config.prop`. For more info, check [Google's Documentation](https://developer.android.com/studio/publish/app-signing.html#generate-key).
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
14
app/proguard-rules.pro
vendored
14
app/proguard-rules.pro
vendored
@@ -16,17 +16,17 @@
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Parcelable
|
||||
-keepclassmembers class * implements android.os.Parcelable {
|
||||
public static final ** CREATOR;
|
||||
}
|
||||
|
||||
# Kotlin
|
||||
-assumenosideeffects class kotlin.jvm.internal.Intrinsics {
|
||||
public static void check*(...);
|
||||
public static void throw*(...);
|
||||
}
|
||||
|
||||
# Snet
|
||||
-keepclassmembers class com.topjohnwu.magisk.ui.safetynet.SafetyNetHelper { *; }
|
||||
-keep,allowobfuscation interface com.topjohnwu.magisk.ui.safetynet.SafetyNetHelper$Callback
|
||||
-keepclassmembers class * implements com.topjohnwu.magisk.ui.safetynet.SafetyNetHelper$Callback { *; }
|
||||
|
||||
# Stub
|
||||
-keep class com.topjohnwu.magisk.core.App { <init>(java.lang.Object); }
|
||||
-keepclassmembers class androidx.appcompat.app.AppCompatDelegateImpl {
|
||||
@@ -44,6 +44,10 @@
|
||||
-repackageclasses 'a'
|
||||
-allowaccessmodification
|
||||
|
||||
-obfuscationdictionary ../dict.txt
|
||||
-classobfuscationdictionary ../dict.txt
|
||||
-packageobfuscationdictionary ../dict.txt
|
||||
|
||||
-dontwarn org.bouncycastle.jsse.BCSSLParameters
|
||||
-dontwarn org.bouncycastle.jsse.BCSSLSocket
|
||||
-dontwarn org.bouncycastle.jsse.provider.BouncyCastleJsseProvider
|
||||
|
||||
@@ -2,13 +2,14 @@ plugins {
|
||||
id("com.android.library")
|
||||
}
|
||||
|
||||
setupCommon()
|
||||
|
||||
android {
|
||||
defaultConfig {
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
consumerProguardFiles("proguard-rules.pro")
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar"))))
|
||||
api("io.michaelrocks:paranoid-core:0.3.7")
|
||||
}
|
||||
|
||||
8
app/shared/src/debug/AndroidManifest.xml
Normal file
8
app/shared/src/debug/AndroidManifest.xml
Normal file
@@ -0,0 +1,8 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<application
|
||||
android:usesCleartextTraffic="true"
|
||||
tools:ignore="UnusedAttribute" />
|
||||
|
||||
</manifest>
|
||||
@@ -7,6 +7,8 @@
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
||||
<uses-permission android:name="android.permission.HIDE_OVERLAY_WINDOWS" />
|
||||
<uses-permission android:name="android.permission.UPDATE_PACKAGES_WITHOUT_USER_ACTION" />
|
||||
<uses-permission
|
||||
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="29"
|
||||
@@ -20,8 +22,6 @@
|
||||
android:label="Magisk"
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@android:style/Theme.Translucent.NoTitleBar"
|
||||
android:usesCleartextTraffic="true"
|
||||
tools:ignore="UnusedAttribute" />
|
||||
android:theme="@android:style/Theme.Translucent.NoTitleBar" />
|
||||
|
||||
</manifest>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.topjohnwu.magisk;
|
||||
|
||||
import static android.os.Build.VERSION.SDK_INT;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.AssetManager;
|
||||
|
||||
@@ -7,14 +9,10 @@ import java.io.File;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Map;
|
||||
|
||||
import static android.os.Build.VERSION.SDK_INT;
|
||||
import io.michaelrocks.paranoid.Obfuscate;
|
||||
|
||||
@Obfuscate
|
||||
public class DynAPK {
|
||||
|
||||
// Indices of the object array
|
||||
private static final int STUB_VERSION_ENTRY = 0;
|
||||
private static final int CLASS_COMPONENT_MAP = 1;
|
||||
|
||||
private static File dynDir;
|
||||
private static Method addAssetPath;
|
||||
|
||||
@@ -38,21 +36,6 @@ public class DynAPK {
|
||||
return new File(getDynDir(c), "update.apk");
|
||||
}
|
||||
|
||||
public static Data load(Object o) {
|
||||
Object[] arr = (Object[]) o;
|
||||
Data data = new Data();
|
||||
data.version = (int) arr[STUB_VERSION_ENTRY];
|
||||
data.classToComponent = (Map<String, String>) arr[CLASS_COMPONENT_MAP];
|
||||
return data;
|
||||
}
|
||||
|
||||
public static Object pack(Data data) {
|
||||
Object[] arr = new Object[2];
|
||||
arr[STUB_VERSION_ENTRY] = data.version;
|
||||
arr[CLASS_COMPONENT_MAP] = data.classToComponent;
|
||||
return arr;
|
||||
}
|
||||
|
||||
public static void addAssetPath(AssetManager asset, String path) {
|
||||
try {
|
||||
if (addAssetPath == null)
|
||||
@@ -62,7 +45,28 @@ public class DynAPK {
|
||||
}
|
||||
|
||||
public static class Data {
|
||||
public int version;
|
||||
public Map<String, String> classToComponent;
|
||||
// Indices of the object array
|
||||
private static final int STUB_VERSION = 0;
|
||||
private static final int CLASS_COMPONENT_MAP = 1;
|
||||
private static final int ROOT_SERVICE = 2;
|
||||
private static final int ARR_SIZE = 3;
|
||||
|
||||
private final Object[] arr;
|
||||
|
||||
public Data() { arr = new Object[ARR_SIZE]; }
|
||||
public Data(Object o) { arr = (Object[]) o; }
|
||||
public Object getObject() { return arr; }
|
||||
|
||||
public int getVersion() { return (int) arr[STUB_VERSION]; }
|
||||
public void setVersion(int version) { arr[STUB_VERSION] = version; }
|
||||
public Map<String, String> getClassToComponent() {
|
||||
// noinspection unchecked
|
||||
return (Map<String, String>) arr[CLASS_COMPONENT_MAP];
|
||||
}
|
||||
public void setClassToComponent(Map<String, String> map) {
|
||||
arr[CLASS_COMPONENT_MAP] = map;
|
||||
}
|
||||
public Class<?> getRootService() { return (Class<?>) arr[ROOT_SERVICE]; }
|
||||
public void setRootService(Class<?> service) { arr[ROOT_SERVICE] = service; }
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,6 +2,9 @@ package com.topjohnwu.magisk;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import io.michaelrocks.paranoid.Obfuscate;
|
||||
|
||||
@Obfuscate
|
||||
public class ProviderInstaller {
|
||||
|
||||
public static boolean install(Context context) {
|
||||
|
||||
@@ -1,48 +1,121 @@
|
||||
package com.topjohnwu.magisk.utils;
|
||||
|
||||
import android.app.Activity;
|
||||
import static android.content.pm.PackageInstaller.EXTRA_STATUS;
|
||||
import static android.content.pm.PackageInstaller.STATUS_FAILURE_INVALID;
|
||||
import static android.content.pm.PackageInstaller.STATUS_PENDING_USER_ACTION;
|
||||
import static android.content.pm.PackageInstaller.STATUS_SUCCESS;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.pm.PackageInstaller.Session;
|
||||
import android.content.pm.PackageInstaller.SessionParams;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
|
||||
import com.topjohnwu.magisk.FileProvider;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class APKInstall {
|
||||
import io.michaelrocks.paranoid.Obfuscate;
|
||||
|
||||
public static Intent installIntent(Context c, File apk) {
|
||||
Intent intent = new Intent(Intent.ACTION_INSTALL_PACKAGE);
|
||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
if (Build.VERSION.SDK_INT >= 24) {
|
||||
intent.setData(FileProvider.getUriForFile(c, c.getPackageName() + ".provider", apk));
|
||||
} else {
|
||||
//noinspection ResultOfMethodCallIgnored SetWorldReadable
|
||||
apk.setReadable(true, false);
|
||||
intent.setData(Uri.fromFile(apk));
|
||||
@Obfuscate
|
||||
public final class APKInstall {
|
||||
// @WorkerThread
|
||||
public static void installapk(Context context, File apk) {
|
||||
//noinspection InlinedApi
|
||||
var flag = PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE;
|
||||
var action = APKInstall.class.getName();
|
||||
var intent = new Intent(action).setPackage(context.getPackageName());
|
||||
var pending = PendingIntent.getBroadcast(context, 0, intent, flag);
|
||||
|
||||
var installer = context.getPackageManager().getPackageInstaller();
|
||||
var params = new SessionParams(SessionParams.MODE_FULL_INSTALL);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
params.setRequireUserAction(SessionParams.USER_ACTION_NOT_REQUIRED);
|
||||
}
|
||||
try (Session session = installer.openSession(installer.createSession(params))) {
|
||||
OutputStream out = session.openWrite(apk.getName(), 0, apk.length());
|
||||
try (var in = new FileInputStream(apk); out) {
|
||||
transfer(in, out);
|
||||
}
|
||||
session.commit(pending.getIntentSender());
|
||||
} catch (IOException e) {
|
||||
Log.e(APKInstall.class.getSimpleName(), "", e);
|
||||
}
|
||||
return intent;
|
||||
}
|
||||
|
||||
public static void install(Context c, File apk) {
|
||||
c.startActivity(installIntent(c, apk));
|
||||
public static void transfer(InputStream in, OutputStream out) throws IOException {
|
||||
int size = 8192;
|
||||
var buffer = new byte[size];
|
||||
int read;
|
||||
while ((read = in.read(buffer, 0, size)) >= 0) {
|
||||
out.write(buffer, 0, read);
|
||||
}
|
||||
}
|
||||
|
||||
public static void registerInstallReceiver(Context c, BroadcastReceiver r) {
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
|
||||
filter.addAction(Intent.ACTION_PACKAGE_ADDED);
|
||||
public static InstallReceiver register(Context context, String packageName, Runnable onSuccess) {
|
||||
var receiver = new InstallReceiver(context, packageName, onSuccess);
|
||||
var filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
|
||||
filter.addDataScheme("package");
|
||||
c.getApplicationContext().registerReceiver(r, filter);
|
||||
context.registerReceiver(receiver, filter);
|
||||
context.registerReceiver(receiver, new IntentFilter(APKInstall.class.getName()));
|
||||
return receiver;
|
||||
}
|
||||
|
||||
public static void installHideResult(Activity c, File apk) {
|
||||
Intent intent = installIntent(c, apk);
|
||||
intent.putExtra(Intent.EXTRA_RETURN_RESULT, true);
|
||||
c.startActivityForResult(intent, 0); // Ignore result, use install receiver
|
||||
public static class InstallReceiver extends BroadcastReceiver {
|
||||
private final Context context;
|
||||
private final String packageName;
|
||||
private final Runnable onSuccess;
|
||||
private final CountDownLatch latch = new CountDownLatch(1);
|
||||
private Intent intent = null;
|
||||
|
||||
private InstallReceiver(Context context, String packageName, Runnable onSuccess) {
|
||||
this.context = context;
|
||||
this.packageName = packageName;
|
||||
this.onSuccess = onSuccess;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceive(Context c, Intent i) {
|
||||
if (Intent.ACTION_PACKAGE_ADDED.equals(i.getAction())) {
|
||||
Uri data = i.getData();
|
||||
if (data == null || onSuccess == null) return;
|
||||
String pkg = data.getSchemeSpecificPart();
|
||||
if (pkg.equals(packageName)) {
|
||||
onSuccess.run();
|
||||
context.unregisterReceiver(this);
|
||||
}
|
||||
return;
|
||||
}
|
||||
int status = i.getIntExtra(EXTRA_STATUS, STATUS_FAILURE_INVALID);
|
||||
switch (status) {
|
||||
case STATUS_PENDING_USER_ACTION:
|
||||
intent = i.getParcelableExtra(Intent.EXTRA_INTENT);
|
||||
break;
|
||||
case STATUS_SUCCESS:
|
||||
if (onSuccess != null) onSuccess.run();
|
||||
default:
|
||||
context.unregisterReceiver(this);
|
||||
}
|
||||
latch.countDown();
|
||||
}
|
||||
|
||||
// @WorkerThread @Nullable
|
||||
public Intent waitIntent() {
|
||||
try {
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
latch.await(5, TimeUnit.SECONDS);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
return intent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,9 +12,8 @@
|
||||
android:multiArch="true"
|
||||
tools:ignore="UnusedAttribute,GoogleAppIndexingWarning">
|
||||
|
||||
<!-- Splash -->
|
||||
<activity
|
||||
android:name=".core.SplashActivity"
|
||||
android:name=".ui.MainActivity"
|
||||
android:exported="true"
|
||||
android:theme="@style/SplashTheme">
|
||||
<intent-filter>
|
||||
@@ -27,10 +26,6 @@
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<!-- Main -->
|
||||
<activity android:name=".ui.MainActivity" />
|
||||
|
||||
<!-- Superuser -->
|
||||
<activity
|
||||
android:name=".ui.surequest.SuRequestActivity"
|
||||
android:directBootAware="true"
|
||||
@@ -43,7 +38,6 @@
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<!-- Receiver -->
|
||||
<receiver
|
||||
android:name=".core.Receiver"
|
||||
android:directBootAware="true"
|
||||
@@ -54,15 +48,21 @@
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.PACKAGE_REPLACED" />
|
||||
<action android:name="android.intent.action.PACKAGE_FULLY_REMOVED" />
|
||||
|
||||
<data android:scheme="package" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<!-- DownloadService -->
|
||||
<service android:name=".core.download.DownloadService" />
|
||||
<service
|
||||
android:name=".core.download.DownloadService"
|
||||
android:exported="false" />
|
||||
|
||||
<service
|
||||
android:name=".core.JobService"
|
||||
android:exported="false"
|
||||
android:permission="android.permission.BIND_JOB_SERVICE" />
|
||||
|
||||
<!-- FileProvider -->
|
||||
<provider
|
||||
android:name=".core.Provider"
|
||||
android:authorities="${applicationId}.provider"
|
||||
@@ -70,23 +70,18 @@
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true" />
|
||||
|
||||
<!-- Hardcode GMS version -->
|
||||
<meta-data
|
||||
android:name="com.google.android.gms.version"
|
||||
android:value="12451000" />
|
||||
|
||||
<!-- Initialize WorkManager on-demand -->
|
||||
<provider
|
||||
android:name="androidx.work.impl.WorkManagerInitializer"
|
||||
android:authorities="${applicationId}.workmanager-init"
|
||||
tools:node="remove"
|
||||
tools:ignore="ExportedContentProvider" />
|
||||
|
||||
<!-- We don't invalidate Room -->
|
||||
<service
|
||||
android:name="androidx.room.MultiInstanceInvalidationService"
|
||||
tools:node="remove" />
|
||||
|
||||
<!-- We don't need emoji compat -->
|
||||
<provider
|
||||
android:name="androidx.startup.InitializationProvider"
|
||||
android:authorities="${applicationId}.androidx-startup"
|
||||
android:exported="false"
|
||||
tools:node="remove" />
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
||||
@@ -13,15 +13,14 @@ import androidx.navigation.NavDirections
|
||||
import com.topjohnwu.magisk.BR
|
||||
import com.topjohnwu.magisk.ktx.startAnimations
|
||||
|
||||
abstract class BaseUIFragment<VM : BaseViewModel, Binding : ViewDataBinding> :
|
||||
Fragment(), BaseUIComponent<VM> {
|
||||
abstract class BaseFragment<Binding : ViewDataBinding> : Fragment(), ViewModelHolder {
|
||||
|
||||
val activity get() = requireActivity() as BaseUIActivity<*, *>
|
||||
val activity get() = getActivity() as? NavigationActivity<*>
|
||||
protected lateinit var binding: Binding
|
||||
protected abstract val layoutRes: Int
|
||||
|
||||
override val viewRoot: View get() = binding.root
|
||||
private val navigation get() = activity.navigation
|
||||
private val navigation get() = activity?.navigation
|
||||
open val snackbarAnchorView: View? get() = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
@@ -35,19 +34,19 @@ abstract class BaseUIFragment<VM : BaseViewModel, Binding : ViewDataBinding> :
|
||||
): View? {
|
||||
binding = DataBindingUtil.inflate<Binding>(inflater, layoutRes, container, false).also {
|
||||
it.setVariable(BR.viewModel, viewModel)
|
||||
it.lifecycleOwner = this
|
||||
it.lifecycleOwner = viewLifecycleOwner
|
||||
}
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
activity.supportActionBar?.subtitle = null
|
||||
activity?.supportActionBar?.subtitle = null
|
||||
}
|
||||
|
||||
override fun onEventDispatched(event: ViewEvent) = when(event) {
|
||||
is ContextExecutor -> event(requireContext())
|
||||
is ActivityExecutor -> event(activity)
|
||||
is ActivityExecutor -> activity?.let { event(it) } ?: Unit
|
||||
is FragmentExecutor -> event(this)
|
||||
else -> Unit
|
||||
}
|
||||
@@ -63,7 +62,7 @@ abstract class BaseUIFragment<VM : BaseViewModel, Binding : ViewDataBinding> :
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
binding.addOnRebindCallback(object : OnRebindCallback<Binding>() {
|
||||
override fun onPreBind(binding: Binding): Boolean {
|
||||
this@BaseUIFragment.onPreBind(binding)
|
||||
this@BaseFragment.onPreBind(binding)
|
||||
return true
|
||||
}
|
||||
})
|
||||
@@ -83,9 +82,3 @@ abstract class BaseUIFragment<VM : BaseViewModel, Binding : ViewDataBinding> :
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
interface ReselectionTarget {
|
||||
|
||||
fun onReselected()
|
||||
|
||||
}
|
||||
129
app/src/main/java/com/topjohnwu/magisk/arch/BaseMainActivity.kt
Normal file
129
app/src/main/java/com/topjohnwu/magisk/arch/BaseMainActivity.kt
Normal file
@@ -0,0 +1,129 @@
|
||||
package com.topjohnwu.magisk.arch
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import androidx.activity.result.contract.ActivityResultContract
|
||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||
import androidx.databinding.ViewDataBinding
|
||||
import com.topjohnwu.magisk.BuildConfig.APPLICATION_ID
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.core.*
|
||||
import com.topjohnwu.magisk.core.tasks.HideAPK
|
||||
import com.topjohnwu.magisk.di.ServiceLocator
|
||||
import com.topjohnwu.magisk.ui.theme.Theme
|
||||
import com.topjohnwu.magisk.view.MagiskDialog
|
||||
import com.topjohnwu.magisk.view.Notifications
|
||||
import com.topjohnwu.magisk.view.Shortcuts
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import java.util.concurrent.CountDownLatch
|
||||
|
||||
abstract class BaseMainActivity<Binding : ViewDataBinding> : NavigationActivity<Binding>() {
|
||||
|
||||
companion object {
|
||||
private var doPreload = true
|
||||
}
|
||||
|
||||
private val latch = CountDownLatch(1)
|
||||
private val uninstallPkg = registerForActivityResult(UninstallPackage) { latch.countDown() }
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
setTheme(Theme.selected.themeRes)
|
||||
|
||||
if (isRunningAsStub && doPreload) {
|
||||
// Manually apply splash theme for stub
|
||||
theme.applyStyle(R.style.StubSplashTheme, true)
|
||||
}
|
||||
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
if (!isRunningAsStub) {
|
||||
val splashScreen = installSplashScreen()
|
||||
splashScreen.setKeepOnScreenCondition { doPreload }
|
||||
}
|
||||
|
||||
if (doPreload) {
|
||||
Shell.getShell(null) {
|
||||
if (isRunningAsStub && !it.isRoot) {
|
||||
showInvalidStateMessage()
|
||||
return@getShell
|
||||
}
|
||||
preLoad()
|
||||
runOnUiThread {
|
||||
doPreload = false
|
||||
if (isRunningAsStub) {
|
||||
// Re-launch main activity without splash theme
|
||||
relaunch()
|
||||
} else {
|
||||
showMainUI(savedInstanceState)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
showMainUI(savedInstanceState)
|
||||
}
|
||||
}
|
||||
|
||||
abstract fun showMainUI(savedInstanceState: Bundle?)
|
||||
|
||||
private fun showInvalidStateMessage() {
|
||||
runOnUiThread {
|
||||
MagiskDialog(this).apply {
|
||||
setTitle(R.string.unsupport_nonroot_stub_title)
|
||||
setMessage(R.string.unsupport_nonroot_stub_msg)
|
||||
setButton(MagiskDialog.ButtonType.POSITIVE) {
|
||||
text = R.string.install
|
||||
onClick { HideAPK.restore(this@BaseMainActivity) }
|
||||
}
|
||||
setCancelable(false)
|
||||
show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun preLoad() {
|
||||
val prevPkg = intent.getStringExtra(Const.Key.PREV_PKG)
|
||||
|
||||
Config.load(prevPkg)
|
||||
handleRepackage(prevPkg)
|
||||
Notifications.setup(this)
|
||||
JobService.schedule(this)
|
||||
Shortcuts.setupDynamic(this)
|
||||
|
||||
// Pre-fetch network services
|
||||
ServiceLocator.networkService
|
||||
}
|
||||
|
||||
private fun handleRepackage(pkg: String?) {
|
||||
if (packageName != APPLICATION_ID) {
|
||||
runCatching {
|
||||
// Hidden, remove com.topjohnwu.magisk if exist as it could be malware
|
||||
packageManager.getApplicationInfo(APPLICATION_ID, 0)
|
||||
Shell.su("(pm uninstall $APPLICATION_ID)& >/dev/null 2>&1").exec()
|
||||
}
|
||||
} else {
|
||||
if (Config.suManager.isNotEmpty())
|
||||
Config.suManager = ""
|
||||
pkg ?: return
|
||||
if (!Shell.su("(pm uninstall $pkg)& >/dev/null 2>&1").exec().isSuccess) {
|
||||
uninstallPkg.launch(pkg)
|
||||
// Wait for the uninstallation to finish
|
||||
latch.await()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object UninstallPackage : ActivityResultContract<String, Boolean>() {
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
override fun createIntent(context: Context, input: String): Intent {
|
||||
val uri = Uri.Builder().scheme("package").opaquePart(input).build()
|
||||
val intent = Intent(Intent.ACTION_UNINSTALL_PACKAGE, uri)
|
||||
intent.putExtra(Intent.EXTRA_RETURN_RESULT, true)
|
||||
return intent
|
||||
}
|
||||
|
||||
override fun parseResult(resultCode: Int, intent: Intent?) = resultCode == RESULT_OK
|
||||
}
|
||||
}
|
||||
@@ -13,12 +13,12 @@ import androidx.navigation.NavDirections
|
||||
import com.topjohnwu.magisk.BR
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.core.Info
|
||||
import com.topjohnwu.magisk.databinding.ObservableHost
|
||||
import com.topjohnwu.magisk.databinding.set
|
||||
import com.topjohnwu.magisk.events.BackPressEvent
|
||||
import com.topjohnwu.magisk.events.NavigationEvent
|
||||
import com.topjohnwu.magisk.events.PermissionEvent
|
||||
import com.topjohnwu.magisk.events.SnackbarEvent
|
||||
import com.topjohnwu.magisk.utils.ObservableHost
|
||||
import com.topjohnwu.magisk.utils.set
|
||||
import kotlinx.coroutines.Job
|
||||
|
||||
abstract class BaseViewModel(
|
||||
@@ -77,7 +77,7 @@ abstract class BaseViewModel(
|
||||
PermissionEvent(permission, callback).publish()
|
||||
}
|
||||
|
||||
fun withExternalRW(callback: () -> Unit) {
|
||||
inline fun withExternalRW(crossinline callback: () -> Unit) {
|
||||
withPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) {
|
||||
if (!it) {
|
||||
SnackbarEvent(R.string.external_rw_permission_denied).publish()
|
||||
@@ -98,8 +98,8 @@ abstract class BaseViewModel(
|
||||
_viewEvents.postValue(this)
|
||||
}
|
||||
|
||||
fun NavDirections.navigate() {
|
||||
_viewEvents.postValue(NavigationEvent(this))
|
||||
fun NavDirections.navigate(pop: Boolean = false) {
|
||||
_viewEvents.postValue(NavigationEvent(this, pop))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
package com.topjohnwu.magisk.arch
|
||||
|
||||
import androidx.databinding.ViewDataBinding
|
||||
import com.topjohnwu.magisk.databinding.ComparableRvItem
|
||||
import com.topjohnwu.magisk.databinding.RvItem
|
||||
import com.topjohnwu.magisk.utils.DiffObservableList
|
||||
import com.topjohnwu.magisk.utils.FilterableDiffObservableList
|
||||
import me.tatarka.bindingcollectionadapter2.BindingRecyclerViewAdapter
|
||||
import me.tatarka.bindingcollectionadapter2.ItemBinding
|
||||
import me.tatarka.bindingcollectionadapter2.OnItemBind
|
||||
|
||||
fun <T : ComparableRvItem<*>> diffListOf(
|
||||
vararg newItems: T
|
||||
) = diffListOf(newItems.toList())
|
||||
|
||||
fun <T : ComparableRvItem<*>> diffListOf(
|
||||
newItems: List<T>
|
||||
) = DiffObservableList(object : DiffObservableList.Callback<T> {
|
||||
override fun areItemsTheSame(oldItem: T, newItem: T) = oldItem.genericItemSameAs(newItem)
|
||||
override fun areContentsTheSame(oldItem: T, newItem: T) = oldItem.genericContentSameAs(newItem)
|
||||
}).also { it.update(newItems) }
|
||||
|
||||
fun <T : ComparableRvItem<*>> filterableListOf(
|
||||
vararg newItems: T
|
||||
) = FilterableDiffObservableList(object : DiffObservableList.Callback<T> {
|
||||
override fun areItemsTheSame(oldItem: T, newItem: T) = oldItem.genericItemSameAs(newItem)
|
||||
override fun areContentsTheSame(oldItem: T, newItem: T) = oldItem.genericContentSameAs(newItem)
|
||||
}).also { it.update(newItems.toList()) }
|
||||
|
||||
fun <T : RvItem> adapterOf() = object : BindingRecyclerViewAdapter<T>() {
|
||||
override fun onBindBinding(
|
||||
binding: ViewDataBinding,
|
||||
variableId: Int,
|
||||
layoutRes: Int,
|
||||
position: Int,
|
||||
item: T
|
||||
) {
|
||||
super.onBindBinding(binding, variableId, layoutRes, position, item)
|
||||
item.onBindingBound(binding)
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <T : RvItem> itemBindingOf(
|
||||
crossinline body: (ItemBinding<*>) -> Unit = {}
|
||||
) = OnItemBind<T> { itemBinding, _, item ->
|
||||
item.bind(itemBinding)
|
||||
body(itemBinding)
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.topjohnwu.magisk.arch
|
||||
|
||||
import android.view.KeyEvent
|
||||
import androidx.databinding.ViewDataBinding
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.NavDirections
|
||||
import androidx.navigation.fragment.NavHostFragment
|
||||
|
||||
abstract class NavigationActivity<Binding : ViewDataBinding> : UIActivity<Binding>() {
|
||||
|
||||
abstract val navHostId: Int
|
||||
|
||||
private val navHostFragment by lazy {
|
||||
supportFragmentManager.findFragmentById(navHostId) as NavHostFragment
|
||||
}
|
||||
|
||||
protected val currentFragment get() =
|
||||
navHostFragment.childFragmentManager.fragments.getOrNull(0) as? BaseFragment<*>
|
||||
|
||||
val navigation: NavController get() = navHostFragment.navController
|
||||
|
||||
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
|
||||
return currentFragment?.onKeyEvent(event) == true || super.dispatchKeyEvent(event)
|
||||
}
|
||||
|
||||
override fun onBackPressed() {
|
||||
if (currentFragment?.onBackPressed()?.not() == true) {
|
||||
super.onBackPressed()
|
||||
}
|
||||
}
|
||||
|
||||
fun NavDirections.navigate() {
|
||||
navigation.navigate(this)
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package com.topjohnwu.magisk.arch
|
||||
|
||||
import android.os.Handler
|
||||
import androidx.core.os.postDelayed
|
||||
import com.topjohnwu.superuser.internal.UiThreadHandler
|
||||
|
||||
interface Queryable {
|
||||
|
||||
val queryDelay: Long
|
||||
val queryHandler: Handler get() = UiThreadHandler.handler
|
||||
|
||||
fun submitQuery() {
|
||||
queryHandler.postDelayed(queryDelay) { query() }
|
||||
}
|
||||
|
||||
fun query()
|
||||
}
|
||||
@@ -4,39 +4,25 @@ import android.content.res.Resources
|
||||
import android.graphics.Color
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.KeyEvent
|
||||
import android.view.View
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.core.content.res.use
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.databinding.DataBindingUtil
|
||||
import androidx.databinding.ViewDataBinding
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.NavDirections
|
||||
import androidx.navigation.fragment.NavHostFragment
|
||||
import com.topjohnwu.magisk.BR
|
||||
import com.topjohnwu.magisk.core.Config
|
||||
import com.topjohnwu.magisk.core.base.BaseActivity
|
||||
import com.topjohnwu.magisk.ui.inflater.LayoutInflaterFactory
|
||||
import com.topjohnwu.magisk.ui.theme.Theme
|
||||
import rikka.insets.WindowInsetsHelper
|
||||
import rikka.layoutinflater.view.LayoutInflaterFactory
|
||||
|
||||
abstract class BaseUIActivity<VM : BaseViewModel, Binding : ViewDataBinding> :
|
||||
BaseActivity(), BaseUIComponent<VM> {
|
||||
abstract class UIActivity<Binding : ViewDataBinding> : BaseActivity(), ViewModelHolder {
|
||||
|
||||
protected lateinit var binding: Binding
|
||||
protected abstract val layoutRes: Int
|
||||
protected open val themeRes: Int = Theme.selected.themeRes
|
||||
|
||||
private val navHostFragment by lazy {
|
||||
supportFragmentManager.findFragmentById(navHostId) as? NavHostFragment
|
||||
}
|
||||
private val topFragment get() = navHostFragment?.childFragmentManager?.fragments?.getOrNull(0)
|
||||
protected val currentFragment get() = topFragment as? BaseUIFragment<*, *>
|
||||
|
||||
override val viewRoot: View get() = binding.root
|
||||
open val navigation: NavController? get() = navHostFragment?.navController
|
||||
|
||||
open val navHostId: Int = 0
|
||||
open val snackbarView get() = binding.root
|
||||
open val snackbarAnchorView: View? get() = null
|
||||
|
||||
init {
|
||||
val theme = Config.darkTheme
|
||||
@@ -45,8 +31,8 @@ abstract class BaseUIActivity<VM : BaseViewModel, Binding : ViewDataBinding> :
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
layoutInflater.factory2 = LayoutInflaterFactory(delegate)
|
||||
.addOnViewCreatedListener(WindowInsetsHelper.LISTENER)
|
||||
|
||||
setTheme(themeRes)
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
startObserveEvents()
|
||||
@@ -57,17 +43,13 @@ abstract class BaseUIActivity<VM : BaseViewModel, Binding : ViewDataBinding> :
|
||||
.use { it.getDrawable(0) }
|
||||
.also { window.setBackgroundDrawable(it) }
|
||||
|
||||
window?.decorView?.let {
|
||||
it.systemUiVisibility = (it.systemUiVisibility
|
||||
or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
||||
or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
||||
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION)
|
||||
}
|
||||
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
window?.decorView?.post {
|
||||
// If navigation bar is short enough (gesture navigation enabled), make it transparent
|
||||
if (window.decorView.rootWindowInsets?.systemWindowInsetBottom ?: 0 < Resources.getSystem().displayMetrics.density * 40) {
|
||||
if ((window.decorView.rootWindowInsets?.systemWindowInsetBottom
|
||||
?: 0) < Resources.getSystem().displayMetrics.density * 40) {
|
||||
window.navigationBarColor = Color.TRANSPARENT
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
window.navigationBarDividerColor = Color.TRANSPARENT
|
||||
@@ -89,7 +71,7 @@ abstract class BaseUIActivity<VM : BaseViewModel, Binding : ViewDataBinding> :
|
||||
}
|
||||
|
||||
fun setAccessibilityDelegate(delegate: View.AccessibilityDelegate?) {
|
||||
viewRoot.rootView.accessibilityDelegate = delegate
|
||||
binding.root.rootView.accessibilityDelegate = delegate
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
@@ -97,23 +79,9 @@ abstract class BaseUIActivity<VM : BaseViewModel, Binding : ViewDataBinding> :
|
||||
viewModel.requestRefresh()
|
||||
}
|
||||
|
||||
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
|
||||
return currentFragment?.onKeyEvent(event) == true || super.dispatchKeyEvent(event)
|
||||
}
|
||||
|
||||
override fun onEventDispatched(event: ViewEvent) = when (event) {
|
||||
is ContextExecutor -> event(this)
|
||||
is ActivityExecutor -> event(this)
|
||||
else -> Unit
|
||||
}
|
||||
|
||||
override fun onBackPressed() {
|
||||
if (navigation == null || currentFragment?.onBackPressed()?.not() == true) {
|
||||
super.onBackPressed()
|
||||
}
|
||||
}
|
||||
|
||||
fun NavDirections.navigate() {
|
||||
navigation?.navigate(this)
|
||||
}
|
||||
}
|
||||
@@ -18,9 +18,9 @@ interface ContextExecutor {
|
||||
}
|
||||
|
||||
interface ActivityExecutor {
|
||||
operator fun invoke(activity: BaseUIActivity<*, *>)
|
||||
operator fun invoke(activity: UIActivity<*>)
|
||||
}
|
||||
|
||||
interface FragmentExecutor {
|
||||
operator fun invoke(fragment: BaseUIFragment<*, *>)
|
||||
operator fun invoke(fragment: BaseFragment<*>)
|
||||
}
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
package com.topjohnwu.magisk.arch
|
||||
|
||||
import android.view.View
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
|
||||
interface BaseUIComponent<VM : BaseViewModel> : LifecycleOwner {
|
||||
interface ViewModelHolder : LifecycleOwner {
|
||||
|
||||
val viewRoot: View
|
||||
val viewModel: VM
|
||||
val viewModel: BaseViewModel
|
||||
|
||||
fun startObserveEvents() {
|
||||
viewModel.viewEvents.observe(this) {
|
||||
@@ -6,34 +6,28 @@ import android.app.Application
|
||||
import android.content.Context
|
||||
import android.content.res.Configuration
|
||||
import android.os.Bundle
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.work.WorkManager
|
||||
import com.topjohnwu.magisk.DynAPK
|
||||
import com.topjohnwu.magisk.core.utils.AppShellInit
|
||||
import com.topjohnwu.magisk.core.utils.BusyBoxInit
|
||||
import com.topjohnwu.magisk.core.utils.IODispatcherExecutor
|
||||
import com.topjohnwu.magisk.core.utils.updateConfig
|
||||
import com.topjohnwu.magisk.core.utils.*
|
||||
import com.topjohnwu.magisk.di.ServiceLocator
|
||||
import com.topjohnwu.magisk.ktx.unwrap
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import com.topjohnwu.superuser.internal.UiThreadHandler
|
||||
import com.topjohnwu.superuser.ipc.RootService
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
open class App() : Application() {
|
||||
|
||||
constructor(o: Any) : this() {
|
||||
Info.stub = DynAPK.load(o)
|
||||
val data = DynAPK.Data(o)
|
||||
// Add the root service name mapping
|
||||
data.classToComponent[RootRegistry::class.java.name] = data.rootService.name
|
||||
// Send back the actual root service class
|
||||
data.rootService = RootRegistry::class.java
|
||||
Info.stub = data
|
||||
}
|
||||
|
||||
init {
|
||||
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)
|
||||
Shell.setDefaultBuilder(Shell.Builder.create()
|
||||
.setFlags(Shell.FLAG_MOUNT_MASTER)
|
||||
.setInitializers(BusyBoxInit::class.java, AppShellInit::class.java)
|
||||
.setTimeout(2))
|
||||
Shell.EXECUTOR = IODispatcherExecutor()
|
||||
|
||||
// Always log full stack trace with Timber
|
||||
Timber.plant(Timber.DebugTree())
|
||||
Thread.setDefaultUncaughtExceptionHandler { _, e ->
|
||||
@@ -42,46 +36,57 @@ open class App() : Application() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun attachBaseContext(base: Context) {
|
||||
// Some context magic
|
||||
override fun attachBaseContext(context: Context) {
|
||||
Shell.setDefaultBuilder(Shell.Builder.create()
|
||||
.setFlags(Shell.FLAG_MOUNT_MASTER)
|
||||
.setInitializers(ShellInit::class.java)
|
||||
.setTimeout(2))
|
||||
Shell.EXECUTOR = DispatcherExecutor(Dispatchers.IO)
|
||||
|
||||
// Get the actual ContextImpl
|
||||
val app: Application
|
||||
val impl: Context
|
||||
if (base is Application) {
|
||||
app = base
|
||||
impl = base.baseContext
|
||||
val base: Context
|
||||
if (context is Application) {
|
||||
app = context
|
||||
base = context.baseContext
|
||||
} else {
|
||||
app = this
|
||||
impl = base
|
||||
base = context
|
||||
}
|
||||
val wrapped = impl.wrap()
|
||||
super.attachBaseContext(wrapped)
|
||||
super.attachBaseContext(base)
|
||||
ServiceLocator.context = base
|
||||
|
||||
val info = base.applicationInfo
|
||||
val libDir = runCatching {
|
||||
info.javaClass.getDeclaredField("secondaryNativeLibraryDir").get(info) as String?
|
||||
}.getOrNull() ?: info.nativeLibraryDir
|
||||
Const.NATIVE_LIB_DIR = File(libDir)
|
||||
refreshLocale()
|
||||
AppApkPath = if (isRunningAsStub) {
|
||||
DynAPK.current(base).path
|
||||
} else {
|
||||
base.packageResourcePath
|
||||
}
|
||||
|
||||
ServiceLocator.context = wrapped
|
||||
AssetHack.init(impl)
|
||||
app.registerActivityLifecycleCallbacks(ForegroundTracker)
|
||||
WorkManager.initialize(impl.wrapJob(), androidx.work.Configuration.Builder().build())
|
||||
base.resources.patch()
|
||||
app.registerActivityLifecycleCallbacks(ActivityTracker)
|
||||
}
|
||||
|
||||
// This is required as some platforms expect ContextImpl
|
||||
override fun getBaseContext(): Context {
|
||||
return super.getBaseContext().unwrap()
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
RootRegistry.bindTask = RootService.createBindTask(
|
||||
intent<RootRegistry>(),
|
||||
UiThreadHandler.executor,
|
||||
RootRegistry.Connection
|
||||
)
|
||||
}
|
||||
|
||||
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||
resources.updateConfig(newConfig)
|
||||
if (resources.configuration.diff(newConfig) != 0) {
|
||||
resources.setConfig(newConfig)
|
||||
}
|
||||
if (!isRunningAsStub)
|
||||
super.onConfigurationChanged(newConfig)
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
object ForegroundTracker : Application.ActivityLifecycleCallbacks {
|
||||
object ActivityTracker : Application.ActivityLifecycleCallbacks {
|
||||
|
||||
@Volatile
|
||||
var foreground: Activity? = null
|
||||
|
||||
@@ -6,9 +6,9 @@ import android.util.Xml
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.core.content.edit
|
||||
import com.topjohnwu.magisk.BuildConfig
|
||||
import com.topjohnwu.magisk.core.utils.BiometricHelper
|
||||
import com.topjohnwu.magisk.core.utils.refreshLocale
|
||||
import com.topjohnwu.magisk.data.preference.PreferenceModel
|
||||
import com.topjohnwu.magisk.data.repository.DBBoolSettingsNoWrite
|
||||
import com.topjohnwu.magisk.data.repository.DBConfig
|
||||
import com.topjohnwu.magisk.di.ServiceLocator
|
||||
import com.topjohnwu.magisk.ui.theme.Theme
|
||||
@@ -37,6 +37,8 @@ object Config : PreferenceModel, DBConfig {
|
||||
const val SU_MULTIUSER_MODE = "multiuser_mode"
|
||||
const val SU_MNT_NS = "mnt_ns"
|
||||
const val SU_BIOMETRIC = "su_biometric"
|
||||
const val ZYGISK = "zygisk"
|
||||
const val DENYLIST = "denylist"
|
||||
const val SU_MANAGER = "requester"
|
||||
const val KEYSTORE = "keystore"
|
||||
|
||||
@@ -59,9 +61,6 @@ object Config : PreferenceModel, DBConfig {
|
||||
const val BOOT_ID = "boot_id"
|
||||
const val ASKED_HOME = "asked_home"
|
||||
const val DOH = "doh"
|
||||
|
||||
// system state
|
||||
const val MAGISKHIDE = "magiskhide"
|
||||
}
|
||||
|
||||
object Value {
|
||||
@@ -111,9 +110,10 @@ object Config : PreferenceModel, DBConfig {
|
||||
else
|
||||
Value.DEFAULT_CHANNEL
|
||||
|
||||
@JvmStatic var keepVerity = false
|
||||
@JvmStatic var keepEnc = false
|
||||
@JvmStatic var recovery = false
|
||||
@JvmField var keepVerity = false
|
||||
@JvmField var keepEnc = false
|
||||
@JvmField var patchVbmeta = false
|
||||
@JvmField var recovery = false
|
||||
|
||||
var bootId by preference(Key.BOOT_ID, "")
|
||||
var askedHome by preference(Key.ASKED_HOME, false)
|
||||
@@ -133,7 +133,6 @@ object Config : PreferenceModel, DBConfig {
|
||||
var suTapjack by preference(Key.SU_TAPJACK, true)
|
||||
var checkUpdate by preference(Key.CHECK_UPDATES, true)
|
||||
var doh by preference(Key.DOH, false)
|
||||
var magiskHide by preference(Key.MAGISKHIDE, true)
|
||||
var showSystemApp by preference(Key.SHOW_SYSTEM_APP, false)
|
||||
|
||||
var customChannelUrl by preference(Key.CUSTOM_CHANNEL, "")
|
||||
@@ -149,6 +148,8 @@ object Config : PreferenceModel, DBConfig {
|
||||
var suMntNamespaceMode by dbSettings(Key.SU_MNT_NS, Value.NAMESPACE_MODE_REQUESTER)
|
||||
var suMultiuserMode by dbSettings(Key.SU_MULTIUSER_MODE, Value.MULTIUSER_MODE_OWNER_ONLY)
|
||||
var suBiometric by dbSettings(Key.SU_BIOMETRIC, false)
|
||||
var zygisk by dbSettings(Key.ZYGISK, false)
|
||||
var denyList by DBBoolSettingsNoWrite(Key.DENYLIST, false)
|
||||
var suManager by dbStrings(Key.SU_MANAGER, "", true)
|
||||
var keyStoreRaw by dbStrings(Key.KEYSTORE, "", true)
|
||||
|
||||
@@ -173,12 +174,6 @@ object Config : PreferenceModel, DBConfig {
|
||||
else if (it.toInt() > Value.CANARY_CHANNEL)
|
||||
putString(Key.UPDATE_CHANNEL, Value.CANARY_CHANNEL.toString())
|
||||
}
|
||||
|
||||
// Write database configs
|
||||
putString(Key.ROOT_ACCESS, rootMode.toString())
|
||||
putString(Key.SU_MNT_NS, suMntNamespaceMode.toString())
|
||||
putString(Key.SU_MULTIUSER_MODE, suMultiuserMode.toString())
|
||||
putBoolean(Key.SU_BIOMETRIC, BiometricHelper.isEnabled)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,58 +3,55 @@ package com.topjohnwu.magisk.core
|
||||
import android.os.Build
|
||||
import android.os.Process
|
||||
import com.topjohnwu.magisk.BuildConfig
|
||||
import java.io.File
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
object Const {
|
||||
|
||||
val CPU_ABI: String = Build.SUPPORTED_ABIS[0]
|
||||
val CPU_ABI_32: String = Build.SUPPORTED_32_BIT_ABIS.firstOrNull() ?: CPU_ABI
|
||||
val CPU_ABI: String get() = Build.SUPPORTED_ABIS[0]
|
||||
|
||||
// Null if 32-bit only or 64-bit only
|
||||
val CPU_ABI_32 =
|
||||
if (Build.SUPPORTED_64_BIT_ABIS.isEmpty()) null
|
||||
else Build.SUPPORTED_32_BIT_ABIS.firstOrNull()
|
||||
|
||||
// Paths
|
||||
lateinit var MAGISKTMP: String
|
||||
lateinit var NATIVE_LIB_DIR: File
|
||||
val MAGISK_PATH get() = "$MAGISKTMP/modules"
|
||||
const val TMPDIR = "/dev/tmp"
|
||||
const val MAGISK_LOG = "/cache/magisk.log"
|
||||
|
||||
// Versions
|
||||
const val SNET_EXT_VER = 17
|
||||
const val SNET_REVISION = "23.0"
|
||||
const val BOOTCTL_REVISION = "22.0"
|
||||
|
||||
// Misc
|
||||
val USER_ID = Process.myUid() / 100000
|
||||
val APP_IS_CANARY get() = Version.isCanary(BuildConfig.VERSION_CODE)
|
||||
|
||||
object Version {
|
||||
const val MIN_VERSION = "v20.4"
|
||||
const val MIN_VERCODE = 20400
|
||||
const val MIN_VERSION = "v21.0"
|
||||
const val MIN_VERCODE = 21000
|
||||
|
||||
fun atLeast_21_0() = Info.env.magiskVersionCode >= 21000 || isCanary()
|
||||
fun atLeast_21_2() = Info.env.magiskVersionCode >= 21200 || isCanary()
|
||||
fun isCanary() = Info.env.magiskVersionCode % 100 != 0
|
||||
fun atLeast_21_2() = Info.env.versionCode >= 21200 || isCanary()
|
||||
fun atLeast_24_0() = Info.env.versionCode >= 24000 || isCanary()
|
||||
fun isCanary() = isCanary(Info.env.versionCode)
|
||||
|
||||
fun isCanary(ver: Int) = ver > 0 && ver % 100 != 0
|
||||
}
|
||||
|
||||
object ID {
|
||||
// notifications
|
||||
const val APK_UPDATE_NOTIFICATION_ID = 5
|
||||
const val JOB_SERVICE_ID = 7
|
||||
const val UPDATE_NOTIFICATION_CHANNEL = "update"
|
||||
const val PROGRESS_NOTIFICATION_CHANNEL = "progress"
|
||||
const val CHECK_MAGISK_UPDATE_WORKER_ID = "magisk_update"
|
||||
}
|
||||
|
||||
object Url {
|
||||
const val PATREON_URL = "https://www.patreon.com/topjohnwu"
|
||||
const val SOURCE_CODE_URL = "https://github.com/topjohnwu/Magisk"
|
||||
|
||||
val CHANGELOG_URL = if (BuildConfig.VERSION_CODE % 100 != 0) Info.remote.magisk.note
|
||||
val CHANGELOG_URL = if (APP_IS_CANARY) Info.remote.magisk.note
|
||||
else "https://topjohnwu.github.io/Magisk/releases/${BuildConfig.VERSION_CODE}.md"
|
||||
|
||||
const val GITHUB_RAW_URL = "https://raw.githubusercontent.com/"
|
||||
const val GITHUB_API_URL = "https://api.github.com/"
|
||||
const val GITHUB_PAGE_URL = "https://topjohnwu.github.io/magisk-files/"
|
||||
const val JS_DELIVR_URL = "https://cdn.jsdelivr.net/gh/"
|
||||
const val OFFICIAL_REPO = "https://magisk-modules-repo.github.io/submission/modules.json"
|
||||
}
|
||||
|
||||
object Key {
|
||||
@@ -74,7 +71,6 @@ object Const {
|
||||
object Nav {
|
||||
const val HOME = "home"
|
||||
const val SETTINGS = "settings"
|
||||
const val HIDE = "hide"
|
||||
const val MODULES = "modules"
|
||||
const val SUPERUSER = "superuser"
|
||||
}
|
||||
|
||||
@@ -2,11 +2,7 @@
|
||||
|
||||
package com.topjohnwu.magisk.core
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.app.job.JobInfo
|
||||
import android.app.job.JobScheduler
|
||||
import android.app.job.JobWorkItem
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.ContextWrapper
|
||||
@@ -15,33 +11,38 @@ import android.content.res.AssetManager
|
||||
import android.content.res.Configuration
|
||||
import android.content.res.Resources
|
||||
import android.util.DisplayMetrics
|
||||
import androidx.annotation.RequiresApi
|
||||
import com.topjohnwu.magisk.DynAPK
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.core.utils.refreshLocale
|
||||
import com.topjohnwu.magisk.core.utils.updateConfig
|
||||
import com.topjohnwu.magisk.core.utils.syncLocale
|
||||
import com.topjohnwu.magisk.di.AppContext
|
||||
|
||||
fun AssetManager.addAssetPath(path: String) {
|
||||
DynAPK.addAssetPath(this, path)
|
||||
lateinit var AppApkPath: String
|
||||
|
||||
fun AssetManager.addAssetPath(path: String) = DynAPK.addAssetPath(this, path)
|
||||
|
||||
fun Context.wrap(): Context = if (this is PatchedContext) this else PatchedContext(this)
|
||||
|
||||
private class PatchedContext(base: Context) : ContextWrapper(base) {
|
||||
init { base.resources.patch() }
|
||||
override fun getClassLoader() = javaClass.classLoader!!
|
||||
override fun createConfigurationContext(config: Configuration) =
|
||||
super.createConfigurationContext(config).wrap()
|
||||
}
|
||||
|
||||
fun Context.wrap(inject: Boolean = false): Context =
|
||||
if (inject) ReInjectedContext(this) else InjectedContext(this)
|
||||
fun Resources.patch(): Resources {
|
||||
syncLocale()
|
||||
if (isRunningAsStub)
|
||||
assets.addAssetPath(AppApkPath)
|
||||
return this
|
||||
}
|
||||
|
||||
fun Context.wrapJob(): Context = object : InjectedContext(this) {
|
||||
|
||||
override fun getApplicationContext() = this
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
override fun getSystemService(name: String): Any? {
|
||||
return super.getSystemService(name).let {
|
||||
when {
|
||||
!isRunningAsStub -> it
|
||||
name == JOB_SCHEDULER_SERVICE -> JobSchedulerWrapper(it as JobScheduler)
|
||||
else -> it
|
||||
}
|
||||
}
|
||||
}
|
||||
fun createNewResources(): Resources {
|
||||
val asset = AssetManager::class.java.newInstance()
|
||||
asset.addAssetPath(AppApkPath)
|
||||
val config = Configuration(AppContext.resources.configuration)
|
||||
val metrics = DisplayMetrics()
|
||||
metrics.setTo(AppContext.resources.displayMetrics)
|
||||
return Resources(asset, metrics, config)
|
||||
}
|
||||
|
||||
fun Class<*>.cmp(pkg: String) =
|
||||
@@ -53,74 +54,6 @@ inline fun <reified T> Activity.redirect() = Intent(intent)
|
||||
|
||||
inline fun <reified T> Context.intent() = Intent().setComponent(T::class.java.cmp(packageName))
|
||||
|
||||
private open class InjectedContext(base: Context) : ContextWrapper(base) {
|
||||
open val res: Resources get() = AssetHack.resource
|
||||
override fun getAssets(): AssetManager = res.assets
|
||||
override fun getResources() = res
|
||||
override fun getClassLoader() = javaClass.classLoader!!
|
||||
override fun createConfigurationContext(config: Configuration): Context {
|
||||
return super.createConfigurationContext(config).wrap(true)
|
||||
}
|
||||
}
|
||||
|
||||
private class ReInjectedContext(base: Context) : InjectedContext(base) {
|
||||
override val res by lazy { base.resources.patch() }
|
||||
private fun Resources.patch(): Resources {
|
||||
updateConfig()
|
||||
if (isRunningAsStub)
|
||||
assets.addAssetPath(AssetHack.apk)
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
||||
object AssetHack {
|
||||
|
||||
lateinit var resource: Resources
|
||||
lateinit var apk: String
|
||||
|
||||
fun init(context: Context) {
|
||||
resource = context.resources
|
||||
refreshLocale()
|
||||
if (isRunningAsStub) {
|
||||
apk = DynAPK.current(context).path
|
||||
resource.assets.addAssetPath(apk)
|
||||
} else {
|
||||
apk = context.packageResourcePath
|
||||
}
|
||||
}
|
||||
|
||||
fun newResource(): Resources {
|
||||
val asset = AssetManager::class.java.newInstance()
|
||||
asset.addAssetPath(apk)
|
||||
val config = Configuration(resource.configuration)
|
||||
val metrics = DisplayMetrics()
|
||||
metrics.setTo(resource.displayMetrics)
|
||||
return Resources(asset, metrics, config)
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(28)
|
||||
private class JobSchedulerWrapper(private val base: JobScheduler) : JobScheduler() {
|
||||
override fun schedule(job: JobInfo) = base.schedule(job.patch())
|
||||
override fun enqueue(job: JobInfo, work: JobWorkItem) = base.enqueue(job.patch(), work)
|
||||
override fun cancel(jobId: Int) = base.cancel(jobId)
|
||||
override fun cancelAll() = base.cancelAll()
|
||||
override fun getAllPendingJobs(): List<JobInfo> = base.allPendingJobs
|
||||
override fun getPendingJob(jobId: Int) = base.getPendingJob(jobId)
|
||||
private fun JobInfo.patch(): JobInfo {
|
||||
// Swap out the service of JobInfo
|
||||
val component = service.run {
|
||||
ComponentName(packageName,
|
||||
Info.stub?.classToComponent?.get(className) ?: className)
|
||||
}
|
||||
javaClass.getDeclaredField("service").apply {
|
||||
isAccessible = true
|
||||
}.set(this, component)
|
||||
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
||||
// Keep a reference to these resources to prevent it from
|
||||
// being removed when running "remove unused resources"
|
||||
val shouldKeepResources = listOf(
|
||||
@@ -128,9 +61,11 @@ val shouldKeepResources = listOf(
|
||||
R.string.release_notes,
|
||||
R.string.invalid_update_channel,
|
||||
R.string.update_available,
|
||||
R.string.safetynet_api_error,
|
||||
R.drawable.ic_device,
|
||||
R.drawable.ic_hide_select_md2,
|
||||
R.drawable.ic_more,
|
||||
R.drawable.ic_magisk_delete
|
||||
R.drawable.ic_magisk_delete,
|
||||
R.drawable.ic_refresh_data_md2,
|
||||
R.drawable.ic_order_date,
|
||||
R.drawable.ic_order_name,
|
||||
R.array.allow_timeout,
|
||||
)
|
||||
|
||||
@@ -11,8 +11,6 @@ import com.topjohnwu.magisk.ktx.getProperty
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import com.topjohnwu.superuser.ShellUtils.fastCmd
|
||||
import com.topjohnwu.superuser.internal.UiThreadHandler
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
|
||||
val isRunningAsStub get() = Info.stub != null
|
||||
|
||||
@@ -31,16 +29,20 @@ object Info {
|
||||
// Device state
|
||||
@JvmStatic val env by lazy { loadState() }
|
||||
@JvmField var isSAR = false
|
||||
@JvmField var isAB = false
|
||||
@JvmField val isVirtualAB = getProperty("ro.virtual_ab.enabled", "false") == "true"
|
||||
var isAB = false
|
||||
@JvmField val isZygiskEnabled = System.getenv("ZYGISK_ENABLED") == "1"
|
||||
@JvmStatic val isFDE get() = crypto == "block"
|
||||
@JvmField var ramdisk = false
|
||||
@JvmField var hasGMS = true
|
||||
@JvmField val isPixel = Build.BRAND == "google"
|
||||
@JvmField val isEmulator = getProperty("ro.kernel.qemu", "0") == "1"
|
||||
@JvmField var vbmeta = false
|
||||
var crypto = ""
|
||||
var noDataExec = false
|
||||
|
||||
@JvmField var hasGMS = true
|
||||
val isSamsung = Build.MANUFACTURER.equals("samsung", ignoreCase = true)
|
||||
@JvmField val isEmulator =
|
||||
getProperty("ro.kernel.qemu", "0") == "1" ||
|
||||
getProperty("ro.boot.qemu", "0") == "1"
|
||||
|
||||
val isConnected by lazy {
|
||||
ObservableBoolean(false).also { field ->
|
||||
NetworkObserver.observe(AppContext) {
|
||||
@@ -49,41 +51,20 @@ object Info {
|
||||
}
|
||||
}
|
||||
|
||||
val isNewReboot by lazy {
|
||||
try {
|
||||
val id = File("/proc/sys/kernel/random/boot_id").readText()
|
||||
if (id != Config.bootId) {
|
||||
Config.bootId = id
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadState() = Env(
|
||||
fastCmd("magisk -v").split(":".toRegex())[0],
|
||||
runCatching { fastCmd("magisk -V").toInt() }.getOrDefault(-1),
|
||||
Shell.su("magiskhide status").exec().isSuccess
|
||||
runCatching { fastCmd("magisk -V").toInt() }.getOrDefault(-1)
|
||||
)
|
||||
|
||||
class Env(
|
||||
val magiskVersionString: String = "",
|
||||
code: Int = -1,
|
||||
hide: Boolean = false
|
||||
val versionString: String = "",
|
||||
code: Int = -1
|
||||
) {
|
||||
val magiskHide get() = Config.magiskHide
|
||||
val magiskVersionCode = when {
|
||||
val versionCode = when {
|
||||
code < Const.Version.MIN_VERCODE -> -1
|
||||
else -> if (Shell.rootAccess()) code else -1
|
||||
}
|
||||
val isUnsupported = code > 0 && code < Const.Version.MIN_VERCODE
|
||||
val isActive = magiskVersionCode >= 0
|
||||
|
||||
init {
|
||||
Config.magiskHide = hide
|
||||
}
|
||||
val isActive = versionCode >= 0
|
||||
}
|
||||
}
|
||||
|
||||
57
app/src/main/java/com/topjohnwu/magisk/core/JobService.kt
Normal file
57
app/src/main/java/com/topjohnwu/magisk/core/JobService.kt
Normal file
@@ -0,0 +1,57 @@
|
||||
package com.topjohnwu.magisk.core
|
||||
|
||||
import android.app.job.JobInfo
|
||||
import android.app.job.JobParameters
|
||||
import android.app.job.JobScheduler
|
||||
import android.content.Context
|
||||
import androidx.core.content.getSystemService
|
||||
import com.topjohnwu.magisk.BuildConfig
|
||||
import com.topjohnwu.magisk.core.base.BaseJobService
|
||||
import com.topjohnwu.magisk.di.ServiceLocator
|
||||
import com.topjohnwu.magisk.view.Notifications
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class JobService : BaseJobService() {
|
||||
|
||||
private val job = Job()
|
||||
private val svc get() = ServiceLocator.networkService
|
||||
|
||||
override fun onStartJob(params: JobParameters): Boolean {
|
||||
val coroutineScope = CoroutineScope(Dispatchers.IO + job)
|
||||
coroutineScope.launch {
|
||||
svc.fetchUpdate()?.run {
|
||||
Info.remote = this
|
||||
if (Info.env.isActive && BuildConfig.VERSION_CODE < magisk.versionCode)
|
||||
Notifications.managerUpdate(this@JobService)
|
||||
}
|
||||
jobFinished(params, false)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onStopJob(params: JobParameters): Boolean {
|
||||
job.cancel()
|
||||
return false
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun schedule(context: Context) {
|
||||
val scheduler = context.getSystemService<JobScheduler>() ?: return
|
||||
if (Config.checkUpdate) {
|
||||
val cmp = JobService::class.java.cmp(context.packageName)
|
||||
val info = JobInfo.Builder(Const.ID.JOB_SERVICE_ID, cmp)
|
||||
.setPeriodic(TimeUnit.HOURS.toMillis(12))
|
||||
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
|
||||
.setRequiresDeviceIdle(true)
|
||||
.build()
|
||||
scheduler.schedule(info)
|
||||
} else {
|
||||
scheduler.cancel(Const.ID.JOB_SERVICE_ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,23 +1,25 @@
|
||||
package com.topjohnwu.magisk.core
|
||||
|
||||
import android.content.ContentProvider
|
||||
import android.content.ContentValues
|
||||
import android.content.Context
|
||||
import android.content.pm.ProviderInfo
|
||||
import android.database.Cursor
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.os.ParcelFileDescriptor
|
||||
import android.os.ParcelFileDescriptor.MODE_READ_ONLY
|
||||
import com.topjohnwu.magisk.FileProvider
|
||||
import com.topjohnwu.magisk.core.su.SuCallbackHandler
|
||||
import java.io.File
|
||||
|
||||
open class Provider : FileProvider() {
|
||||
class Provider : ContentProvider() {
|
||||
|
||||
override fun attachInfo(context: Context, info: ProviderInfo?) {
|
||||
override fun attachInfo(context: Context, info: ProviderInfo) {
|
||||
super.attachInfo(context.wrap(), info)
|
||||
}
|
||||
|
||||
override fun call(method: String, arg: String?, extras: Bundle?): Bundle? {
|
||||
SuCallbackHandler(context!!, method, extras)
|
||||
SuCallbackHandler.run(context!!, method, extras)
|
||||
return Bundle.EMPTY
|
||||
}
|
||||
|
||||
@@ -36,4 +38,11 @@ open class Provider : FileProvider() {
|
||||
fun PREFS_URI(pkg: String) =
|
||||
Uri.Builder().scheme("content").authority("$pkg.provider").path("prefs_file").build()
|
||||
}
|
||||
|
||||
override fun onCreate() = true
|
||||
override fun getType(uri: Uri): String? = null
|
||||
override fun insert(uri: Uri, values: ContentValues?): Uri? = null
|
||||
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?) = 0
|
||||
override fun update(uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array<out String>?) = 0
|
||||
override fun query(uri: Uri, projection: Array<out String>?, selection: String?, selectionArgs: Array<out String>?, sortOrder: String?): Cursor? = null
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user