mirror of
https://github.com/topjohnwu/Magisk
synced 2025-10-26 02:22:14 +01:00
Compare commits
426 Commits
manager-v5
...
v18.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b31402766e | ||
|
|
9ab3143bf0 | ||
|
|
81a0cddb9e | ||
|
|
f620ac769f | ||
|
|
dc91041edd | ||
|
|
6ee08b6717 | ||
|
|
5a2cd2ac84 | ||
|
|
2bd8448aaa | ||
|
|
2360adb592 | ||
|
|
c7301a5161 | ||
|
|
72270825c1 | ||
|
|
1e94f0a094 | ||
|
|
e39d2567ea | ||
|
|
949136c92a | ||
|
|
9f456a9b19 | ||
|
|
4cf6ba25ca | ||
|
|
093f971896 | ||
|
|
df38a9da71 | ||
|
|
813814c54a | ||
|
|
68cb32f375 | ||
|
|
93c9590b0f | ||
|
|
619d979c39 | ||
|
|
c30faad838 | ||
|
|
bea0de4980 | ||
|
|
ef5a490415 | ||
|
|
fa404285be | ||
|
|
0e526258ff | ||
|
|
56d2fb9a3b | ||
|
|
7c82690852 | ||
|
|
62acc17e42 | ||
|
|
9fbe5895b7 | ||
|
|
6bbe0f07d4 | ||
|
|
bd3e0b9336 | ||
|
|
699debdaca | ||
|
|
70eba568af | ||
|
|
bb7560e441 | ||
|
|
43c0cac52f | ||
|
|
4b4aa148a9 | ||
|
|
c9c90c4e7f | ||
|
|
99093e9a4c | ||
|
|
2cf33d635d | ||
|
|
d6abaf846e | ||
|
|
4b88131977 | ||
|
|
4520f46a57 | ||
|
|
348d47076a | ||
|
|
6e7b90a184 | ||
|
|
28d7a7a6d2 | ||
|
|
da13b5dbf2 | ||
|
|
a60710e3bb | ||
|
|
7d2a2b9983 | ||
|
|
749df5dacd | ||
|
|
af88b7c807 | ||
|
|
4091687733 | ||
|
|
cfb0a3ba2a | ||
|
|
6c4d082f35 | ||
|
|
262185046a | ||
|
|
da9d00be7d | ||
|
|
454abc388b | ||
|
|
3e9174deed | ||
|
|
4df1047b07 | ||
|
|
60f69feaff | ||
|
|
5df426380d | ||
|
|
976c299657 | ||
|
|
18ab6b51fd | ||
|
|
4be8bd4d18 | ||
|
|
075bc4a6d5 | ||
|
|
1c61feb368 | ||
|
|
d32b788988 | ||
|
|
7565ea2787 | ||
|
|
9275975b2c | ||
|
|
b3e0d5ba58 | ||
|
|
841dee94c6 | ||
|
|
71638191ee | ||
|
|
9d6851cbbd | ||
|
|
d633d05803 | ||
|
|
45d7879d7b | ||
|
|
4a8375355c | ||
|
|
d3ebd763a2 | ||
|
|
b7f69238a1 | ||
|
|
118a9f224e | ||
|
|
a44dc8df37 | ||
|
|
abf19aad74 | ||
|
|
d73127b175 | ||
|
|
00f4242fa4 | ||
|
|
f6a4510659 | ||
|
|
33215424d8 | ||
|
|
6094bc9210 | ||
|
|
a8cd9b3aa9 | ||
|
|
a189dec1c8 | ||
|
|
858216796a | ||
|
|
f24342f117 | ||
|
|
50b55a77de | ||
|
|
fdf167db11 | ||
|
|
a4f8bd4ee0 | ||
|
|
3e4c12cf56 | ||
|
|
03c39e692a | ||
|
|
ab63b0e970 | ||
|
|
6ea42a35a9 | ||
|
|
d366dfc72b | ||
|
|
85042fbe25 | ||
|
|
23e5188422 | ||
|
|
93ee0c8798 | ||
|
|
aa88486f59 | ||
|
|
1d9c441038 | ||
|
|
928c56bda2 | ||
|
|
bc6f37eecc | ||
|
|
ffebff8cab | ||
|
|
6d6bd89d6b | ||
|
|
0a64a7e5d4 | ||
|
|
586488af48 | ||
|
|
7b9a45f1a8 | ||
|
|
4ff70aefac | ||
|
|
d63b5d7014 | ||
|
|
ab5f6bf901 | ||
|
|
04088b34a2 | ||
|
|
3edcd2004e | ||
|
|
7bd52d0245 | ||
|
|
1df65940b9 | ||
|
|
d9ace35c3e | ||
|
|
1fe92cee6f | ||
|
|
267868c3b0 | ||
|
|
6d27eb7f64 | ||
|
|
2e10fa494f | ||
|
|
039be65a89 | ||
|
|
570ecd9987 | ||
|
|
a575180475 | ||
|
|
07d1a20f3d | ||
|
|
76491cbb31 | ||
|
|
bf7d6ddcb2 | ||
|
|
44b969e0b6 | ||
|
|
176e470497 | ||
|
|
646a10d9bf | ||
|
|
52137fd64f | ||
|
|
3ccac8c3b8 | ||
|
|
0be158afa1 | ||
|
|
e6942e0122 | ||
|
|
496b22026f | ||
|
|
bb2df02dff | ||
|
|
4c850ecc31 | ||
|
|
da9c6f6e23 | ||
|
|
58ba0b0b4e | ||
|
|
1d0b87246a | ||
|
|
920b60da19 | ||
|
|
523e66294b | ||
|
|
23f8f35098 | ||
|
|
8d210b5e37 | ||
|
|
3c6c0e6700 | ||
|
|
01344c451f | ||
|
|
2c42c79482 | ||
|
|
75c2cfe7bf | ||
|
|
6c6eeb3f28 | ||
|
|
31053e0cd0 | ||
|
|
aad9aced18 | ||
|
|
dd2c9eeafe | ||
|
|
740d76bc42 | ||
|
|
45f4f5afd9 | ||
|
|
e875de3e98 | ||
|
|
fd7786633d | ||
|
|
bce9cfa39a | ||
|
|
ff3d66a661 | ||
|
|
006d28abd5 | ||
|
|
59b1e63bdf | ||
|
|
eab74ef06b | ||
|
|
89837de9b0 | ||
|
|
b245931c79 | ||
|
|
fd5e42698c | ||
|
|
c75512ba6e | ||
|
|
a22e7aa0b1 | ||
|
|
020dd97f99 | ||
|
|
e9882d9702 | ||
|
|
fd4a27dbf2 | ||
|
|
9c63e31da6 | ||
|
|
c91f809eba | ||
|
|
a54eaf5371 | ||
|
|
8032bd4bb9 | ||
|
|
ea1beec2f7 | ||
|
|
05f2f6820e | ||
|
|
0f5f15a5ce | ||
|
|
14ac37e8a5 | ||
|
|
1fae89cbb6 | ||
|
|
8b4008798f | ||
|
|
fd4faf59b8 | ||
|
|
109891d668 | ||
|
|
bdea796121 | ||
|
|
c8813c05c9 | ||
|
|
1cff08ce5d | ||
|
|
a868118f6f | ||
|
|
e5c62f5750 | ||
|
|
4084e8790b | ||
|
|
8d931dd773 | ||
|
|
25d6366297 | ||
|
|
08cd5b81d1 | ||
|
|
5d3a8a5b1a | ||
|
|
79b84da4b8 | ||
|
|
68b07c5913 | ||
|
|
553db9124d | ||
|
|
b495f37299 | ||
|
|
1915547594 | ||
|
|
03de29164a | ||
|
|
86d8b50547 | ||
|
|
7b04386162 | ||
|
|
07bfdf3e4d | ||
|
|
d510224e2a | ||
|
|
e658f9297d | ||
|
|
2b502e9a0f | ||
|
|
59141f9bbe | ||
|
|
3af66b72f2 | ||
|
|
422c24bd68 | ||
|
|
f0f87c8eb9 | ||
|
|
80dad54119 | ||
|
|
56a76df28e | ||
|
|
ee2c801fe0 | ||
|
|
fc314cc248 | ||
|
|
fe231a4c80 | ||
|
|
2e2bbe0a7f | ||
|
|
857e6e8345 | ||
|
|
3402981ada | ||
|
|
f401e577e5 | ||
|
|
0241a50c6f | ||
|
|
2a2e1236fc | ||
|
|
9b170f2b4f | ||
|
|
51e9ff59de | ||
|
|
2977dbcded | ||
|
|
ac60b51035 | ||
|
|
4c2f33a089 | ||
|
|
3b071116ac | ||
|
|
a9f265a591 | ||
|
|
5b62fc8103 | ||
|
|
0598f5f89a | ||
|
|
f723427b8b | ||
|
|
f69a004c1c | ||
|
|
1134b18a8b | ||
|
|
2e4aa507f7 | ||
|
|
5fb96cdcf4 | ||
|
|
e8cba3524e | ||
|
|
7e6b5363f1 | ||
|
|
29457a1d28 | ||
|
|
731455f164 | ||
|
|
b01a8cace6 | ||
|
|
72db5b4fac | ||
|
|
ddfd42994e | ||
|
|
2a9ff9c5ef | ||
|
|
6d49f05356 | ||
|
|
85a5e62e36 | ||
|
|
e67965a381 | ||
|
|
ec4723096f | ||
|
|
762b678d24 | ||
|
|
38fcc57bbf | ||
|
|
c8c57c74cc | ||
|
|
0784448c69 | ||
|
|
de0064af47 | ||
|
|
baae1fc84f | ||
|
|
2ab999f4ca | ||
|
|
c9f390d6e0 | ||
|
|
af05922ecc | ||
|
|
299edbf3ab | ||
|
|
c8abed9d48 | ||
|
|
3622c49ce1 | ||
|
|
0462e9a7d9 | ||
|
|
c3a6091908 | ||
|
|
ab5fedda0b | ||
|
|
ba70269398 | ||
|
|
77fd5fa7de | ||
|
|
ab74290fe3 | ||
|
|
3aad9d8166 | ||
|
|
572e078d87 | ||
|
|
ee4548230b | ||
|
|
96b93bd876 | ||
|
|
927f69fe30 | ||
|
|
7e9ad5927a | ||
|
|
6d6b07865e | ||
|
|
376e7977f0 | ||
|
|
83ae66daea | ||
|
|
89e0be0099 | ||
|
|
ef40c1212e | ||
|
|
3a2a2a4ffa | ||
|
|
9592a69986 | ||
|
|
89be07e1f2 | ||
|
|
c61c3ae0e9 | ||
|
|
817350c8c5 | ||
|
|
3603b7c82b | ||
|
|
5743c72cca | ||
|
|
4cdd66ceff | ||
|
|
d3947d2cfa | ||
|
|
07718b994a | ||
|
|
ef9d463bd7 | ||
|
|
8745c7884e | ||
|
|
b6965105b7 | ||
|
|
3d269fe8be | ||
|
|
be5f00aa1a | ||
|
|
59ba350f34 | ||
|
|
803c5377a6 | ||
|
|
7c12bf7fa1 | ||
|
|
ca35a9681f | ||
|
|
9fe5f37337 | ||
|
|
0742901cd2 | ||
|
|
5e4d2dedbe | ||
|
|
411ea56a3e | ||
|
|
cda57dd4b4 | ||
|
|
4351de503f | ||
|
|
6339ba6bfb | ||
|
|
ef6677f43d | ||
|
|
a7824af5a8 | ||
|
|
1eb7d7b7a8 | ||
|
|
11c33d4447 | ||
|
|
b8a3cc8b60 | ||
|
|
27c688252d | ||
|
|
3e2afd4b1d | ||
|
|
f45b0686d2 | ||
|
|
1f3f881f81 | ||
|
|
ceb51bb14f | ||
|
|
3e22573d8d | ||
|
|
79418a3767 | ||
|
|
40d4683de1 | ||
|
|
79e5b54ec7 | ||
|
|
bd81923f2f | ||
|
|
69560b8ad7 | ||
|
|
dc413e7b73 | ||
|
|
7fc00c446b | ||
|
|
2efc423cf8 | ||
|
|
8ec3086cdd | ||
|
|
5fc7079023 | ||
|
|
bfbd254be7 | ||
|
|
f8ea43466c | ||
|
|
75ab1fa570 | ||
|
|
bf4a46d57c | ||
|
|
1046dd5eda | ||
|
|
f9e32a119a | ||
|
|
dbb8b8a439 | ||
|
|
2a65c3dc8f | ||
|
|
f17ec9e9d7 | ||
|
|
675d6d8328 | ||
|
|
6dc9ccad75 | ||
|
|
6add02702b | ||
|
|
958d6377e3 | ||
|
|
9954154ca2 | ||
|
|
4ecbf8c12c | ||
|
|
fc8a3c5fb4 | ||
|
|
01e7dff1a0 | ||
|
|
018c0064cd | ||
|
|
c2b016370b | ||
|
|
fc791b4371 | ||
|
|
f76bb009f4 | ||
|
|
8a1292b295 | ||
|
|
d7d80d3fc1 | ||
|
|
41b01003fd | ||
|
|
6557070ae1 | ||
|
|
e7e580e177 | ||
|
|
dd9ddd2019 | ||
|
|
74aae523ba | ||
|
|
48c40f9516 | ||
|
|
e0e7674715 | ||
|
|
e1a65276b9 | ||
|
|
469adc85ad | ||
|
|
e1b181ca4e | ||
|
|
a4f0fbf8b7 | ||
|
|
190cdaddf8 | ||
|
|
5c4ba13839 | ||
|
|
e62630cf3e | ||
|
|
36fe7846c0 | ||
|
|
8d150dd67a | ||
|
|
506df00d81 | ||
|
|
a9121fa28f | ||
|
|
d5a56d9e85 | ||
|
|
acf7c0c665 | ||
|
|
619d48c97a | ||
|
|
2cb198c38c | ||
|
|
e8e39e0f3c | ||
|
|
37860181d4 | ||
|
|
d119dd9a0c | ||
|
|
09ef19f7ec | ||
|
|
6a06c92fa6 | ||
|
|
58ae596b0f | ||
|
|
f1ca21678d | ||
|
|
d7eeef2c8a | ||
|
|
4f626897f2 | ||
|
|
b127e01845 | ||
|
|
2118beeb23 | ||
|
|
5020cd1bbf | ||
|
|
cce636224c | ||
|
|
60b3b8ddce | ||
|
|
41446ec9ba | ||
|
|
df8b047bca | ||
|
|
12ced52012 | ||
|
|
1d53335ae5 | ||
|
|
971a50d290 | ||
|
|
36dd9106a8 | ||
|
|
0a4ee3ffc7 | ||
|
|
cfe32f1a70 | ||
|
|
d877f5d5c6 | ||
|
|
0ab6ffefb4 | ||
|
|
a292a1d23a | ||
|
|
3f87f6aee3 | ||
|
|
04bcd145d3 | ||
|
|
244e811291 | ||
|
|
ac7467fb59 | ||
|
|
2c0436216f | ||
|
|
017fbf267b | ||
|
|
e6afbf2ec0 | ||
|
|
906b4aad9e | ||
|
|
4cf8d41f6a | ||
|
|
47c860142e | ||
|
|
2fba3f213b | ||
|
|
af7c6f9fce | ||
|
|
78534deab6 | ||
|
|
6710314832 | ||
|
|
0cd4fa6fa0 | ||
|
|
065949496e | ||
|
|
39c82576ae | ||
|
|
37221a508d | ||
|
|
6b43a32a10 | ||
|
|
d7cd1ff142 | ||
|
|
659d947863 | ||
|
|
39be7a6288 | ||
|
|
8ac976c579 | ||
|
|
70fd432c57 | ||
|
|
00135f2f49 | ||
|
|
9b944bc29c | ||
|
|
d520b3d2a0 | ||
|
|
6f41d9855b | ||
|
|
2d7c1da741 | ||
|
|
c0f45b6b1e | ||
|
|
7a0025673c | ||
|
|
ad7ec79903 | ||
|
|
0543239cca | ||
|
|
ff3dad2457 |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -3,9 +3,7 @@ out
|
||||
*.jks
|
||||
*.apk
|
||||
config.prop
|
||||
|
||||
# Manually dumped jars
|
||||
snet/libs
|
||||
update.sh
|
||||
|
||||
# Built binaries
|
||||
native/out
|
||||
|
||||
28
README.MD
28
README.MD
@@ -1,28 +1,34 @@
|
||||
# Magisk
|
||||
[Downloads](https://github.com/topjohnwu/Magisk/releases) | [Documentation](https://topjohnwu.github.io/Magisk/) | [XDA Thread](https://forum.xda-developers.com/apps/magisk/official-magisk-v7-universal-systemless-t3473445)
|
||||
|
||||
## Introduction
|
||||
Magisk is a suite of open source tools for customizing Android, supporting devices higher than Android 4.2 (API 17). It covers the fundamental parts for Android customization: root, boot scripts, SELinux patches, AVB2.0 / dm-verity / forceencrypt removals etc.
|
||||
|
||||
Furthermore, Magisk provides a **Systemless Interface** to alter the system (or vendor) arbitrarily while the actual partitions stay completely intact. With its systemless nature along with several other hacks, Magisk can hide modifications from nearly any system integrity verifications used in banking apps, corporation monitoring apps, game cheat detections, and most importantly [Google's SafetyNet API](https://developer.android.com/training/safetynet/index.html).
|
||||
|
||||
## Bug Reports
|
||||
**Make sure to install the latest [Canary Build](https://forum.xda-developers.com/apps/magisk/dev-magisk-canary-channel-bleeding-edge-t3839337) before reporting any bugs!** **DO NOT** report bugs that is already fixed upstream. Follow the instructions in the [Canary Channel XDA Thread](https://forum.xda-developers.com/apps/magisk/dev-magisk-canary-channel-bleeding-edge-t3839337), and report a bug either by opening an issue on GitHub or directly in the thread.
|
||||
|
||||
## Building Environment Requirements
|
||||
|
||||
1. Python 3.5+: run `build.py` script
|
||||
1. Python 3: run `build.py` script
|
||||
2. Java Development Kit (JDK) 8: Compile Magisk Manager and sign zips
|
||||
3. Latest Android SDK: set `ANDROID_HOME` environment variable to the path to Android SDK
|
||||
4. Android NDK: Install NDK along with SDK (`$ANDROID_HOME/ndk-bundle`), or optionally specify a custom path `ANDROID_NDK_HOME`
|
||||
5. (Windows Only) Python package Colorama: Install with `pip install colorama`, used for ANSI color codes
|
||||
|
||||
## Building Notes and Instructions
|
||||
1. Building is supported on macOS, Linux, and Windows using the custom NDK: [FrankeNDK](https://github.com/topjohnwu/FrankeNDK).
|
||||
2. Set configurations in `config.prop`. A sample file `config.prop.sample` is provided as an example.
|
||||
3. Run `build.py` with argument `-h` to see the built-in help message. The `-h` option also works for each supported actions, e.g. `./build.py binary -h`
|
||||
4. By default, `build.py` build binaries and Magisk Manager in debug mode. If you want to build Magisk Manager in release mode (via the `-r, --release` flag), you need a Java Keystore file `release-key.jks` (only `JKS` format is supported) to sign APKs and zips. For more information, check out [Google's Official Documentation](https://developer.android.com/studio/publish/app-signing.html#signing-manually).
|
||||
|
||||
## Documentation
|
||||
[Link to Documentation](docs/README.MD)
|
||||
1. Clone sources with submodules: `git clone --recurse-submodules https://github.com/topjohnwu/Magisk.git`
|
||||
2. Building is supported on macOS, Linux, and Windows. Official releases are built and tested with [FrankeNDK](https://github.com/topjohnwu/FrankeNDK); point `ANDROID_NDK_HOME` to FrankeNDK if you want to use it for compiling.
|
||||
3. Set configurations in `config.prop`. A sample file `config.prop.sample` is provided as an example.
|
||||
4. Run `build.py` with argument `-h` to see the built-in help message. The `-h` option also works for each supported actions, e.g. `./build.py binary -h`
|
||||
5. By default, `build.py` build binaries and Magisk Manager in debug mode. If you want to build Magisk Manager in release mode (via the `-r, --release` flag), you need a Java Keystore file `release-key.jks` (only `JKS` format is supported) to sign APKs and zips. For more information, check out [Google's Official Documentation](https://developer.android.com/studio/publish/app-signing.html#signing-manually).
|
||||
|
||||
## License
|
||||
|
||||
```
|
||||
Magisk, including all git submodules are free software:
|
||||
you can redistribute it and/or modify it under the terms of the
|
||||
GNU General Public License as published by the Free Software Foundation,
|
||||
you can redistribute it and/or modify it under the terms of the
|
||||
GNU 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,
|
||||
|
||||
2
app-core/.gitignore
vendored
Normal file
2
app-core/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/build
|
||||
src/main/res/raw/util_functions.sh
|
||||
21
app-core/build.gradle
Normal file
21
app-core/build.gradle
Normal file
@@ -0,0 +1,21 @@
|
||||
apply plugin: 'com.android.library'
|
||||
|
||||
android {
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
api project(':net')
|
||||
api project(':signing')
|
||||
api 'org.kamranzafar:jtar:2.3'
|
||||
|
||||
def libsuVersion = '2.3.0'
|
||||
api "com.github.topjohnwu.libsu:core:${libsuVersion}"
|
||||
api "com.github.topjohnwu.libsu:io:${libsuVersion}"
|
||||
}
|
||||
21
app-core/proguard-rules.pro
vendored
Normal file
21
app-core/proguard-rules.pro
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
4
app-core/src/main/AndroidManifest.xml
Normal file
4
app-core/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.topjohnwu.magisk.core" >
|
||||
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
|
||||
</manifest>
|
||||
61
app-core/src/main/java/com/topjohnwu/magisk/App.java
Normal file
61
app-core/src/main/java/com/topjohnwu/magisk/App.java
Normal file
@@ -0,0 +1,61 @@
|
||||
package com.topjohnwu.magisk;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.preference.PreferenceManager;
|
||||
|
||||
import com.topjohnwu.magisk.core.BuildConfig;
|
||||
import com.topjohnwu.magisk.database.MagiskDB;
|
||||
import com.topjohnwu.magisk.database.RepoDatabaseHelper;
|
||||
import com.topjohnwu.magisk.utils.LocaleManager;
|
||||
import com.topjohnwu.magisk.utils.RootUtils;
|
||||
import com.topjohnwu.net.Networking;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
|
||||
public class App extends Application {
|
||||
|
||||
public static App self;
|
||||
public static ThreadPoolExecutor THREAD_POOL;
|
||||
|
||||
// Global resources
|
||||
public SharedPreferences prefs;
|
||||
public MagiskDB mDB;
|
||||
public RepoDatabaseHelper repoDB;
|
||||
|
||||
static {
|
||||
Shell.Config.setFlags(Shell.FLAG_MOUNT_MASTER | Shell.FLAG_USE_MAGISK_BUSYBOX);
|
||||
Shell.Config.verboseLogging(BuildConfig.DEBUG);
|
||||
Shell.Config.addInitializers(RootUtils.class);
|
||||
Shell.Config.setTimeout(2);
|
||||
THREAD_POOL = (ThreadPoolExecutor) AsyncTask.THREAD_POOL_EXECUTOR;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void attachBaseContext(Context base) {
|
||||
super.attachBaseContext(base);
|
||||
self = this;
|
||||
|
||||
Context de = this;
|
||||
if (Build.VERSION.SDK_INT >= 24) {
|
||||
de = createDeviceProtectedStorageContext();
|
||||
de.moveSharedPreferencesFrom(this, PreferenceManager.getDefaultSharedPreferencesName(base));
|
||||
}
|
||||
prefs = PreferenceManager.getDefaultSharedPreferences(de);
|
||||
mDB = new MagiskDB(this);
|
||||
|
||||
Networking.init(this);
|
||||
LocaleManager.setLocale(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(Configuration newConfig) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
LocaleManager.setLocale(this);
|
||||
}
|
||||
}
|
||||
372
app-core/src/main/java/com/topjohnwu/magisk/Config.java
Normal file
372
app-core/src/main/java/com/topjohnwu/magisk/Config.java
Normal file
File diff suppressed because it is too large
Load Diff
110
app-core/src/main/java/com/topjohnwu/magisk/Const.java
Normal file
110
app-core/src/main/java/com/topjohnwu/magisk/Const.java
Normal file
@@ -0,0 +1,110 @@
|
||||
package com.topjohnwu.magisk;
|
||||
|
||||
import android.os.Environment;
|
||||
import android.os.Process;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class Const {
|
||||
|
||||
public static final String DEBUG_TAG = "MagiskManager";
|
||||
|
||||
// APK content
|
||||
public static final String ANDROID_MANIFEST = "AndroidManifest.xml";
|
||||
|
||||
public static final String SU_KEYSTORE_KEY = "su_key";
|
||||
|
||||
// Paths
|
||||
public static final String MAGISK_PATH = "/sbin/.magisk/img";
|
||||
public static final File EXTERNAL_PATH;
|
||||
public static File MAGISK_DISABLE_FILE;
|
||||
|
||||
static {
|
||||
MAGISK_DISABLE_FILE = new File("xxx");
|
||||
EXTERNAL_PATH = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
|
||||
EXTERNAL_PATH.mkdirs();
|
||||
}
|
||||
|
||||
public static final String BUSYBOX_PATH = "/sbin/.magisk/busybox";
|
||||
public static final String TMP_FOLDER_PATH = "/dev/tmp";
|
||||
public static final String MAGISK_LOG = "/cache/magisk.log";
|
||||
public static final String MANAGER_CONFIGS = ".tmp.magisk.config";
|
||||
|
||||
// Versions
|
||||
public static final int UPDATE_SERVICE_VER = 1;
|
||||
public static final int MIN_MODULE_VER = 1500;
|
||||
public static final int SNET_EXT_VER = 12;
|
||||
|
||||
/* A list of apps that should not be shown as hide-able */
|
||||
public static final List<String> HIDE_BLACKLIST = Arrays.asList(
|
||||
App.self.getPackageName(),
|
||||
"com.google.android.gms"
|
||||
);
|
||||
|
||||
public static final int USER_ID = Process.myUid() / 100000;
|
||||
|
||||
public static final class MAGISK_VER {
|
||||
public static final int MIN_SUPPORT = 18000;
|
||||
}
|
||||
|
||||
public static class ID {
|
||||
public static final int UPDATE_SERVICE_ID = 1;
|
||||
public static final int FETCH_ZIP = 2;
|
||||
public static final int SELECT_BOOT = 3;
|
||||
public static final int ONBOOT_SERVICE_ID = 6;
|
||||
|
||||
// notifications
|
||||
public static final int MAGISK_UPDATE_NOTIFICATION_ID = 4;
|
||||
public static final int APK_UPDATE_NOTIFICATION_ID = 5;
|
||||
public static final int DTBO_NOTIFICATION_ID = 7;
|
||||
public static final int HIDE_MANAGER_NOTIFICATION_ID = 8;
|
||||
public static final String UPDATE_NOTIFICATION_CHANNEL = "update";
|
||||
public static final String PROGRESS_NOTIFICATION_CHANNEL = "progress";
|
||||
public static final String CHECK_MAGISK_UPDATE_WORKER_ID = "magisk_update";
|
||||
}
|
||||
|
||||
public static class Url {
|
||||
private static String getRaw(String where, String name) {
|
||||
return String.format("https://raw.githubusercontent.com/topjohnwu/magisk_files/%s/%s", where, name);
|
||||
}
|
||||
public static final String STABLE_URL = getRaw("master", "stable.json");
|
||||
public static final String BETA_URL = getRaw("master", "beta.json");
|
||||
public static final String REPO_URL = "https://api.github.com/users/Magisk-Modules-Repo/repos?per_page=100&sort=pushed&page=%d";
|
||||
public static final String FILE_URL = "https://raw.githubusercontent.com/Magisk-Modules-Repo/%s/master/%s";
|
||||
public static final String ZIP_URL = "https://github.com/Magisk-Modules-Repo/%s/archive/master.zip";
|
||||
public static final String PAYPAL_URL = "https://www.paypal.me/topjohnwu";
|
||||
public static final String PATREON_URL = "https://www.patreon.com/topjohnwu";
|
||||
public static final String TWITTER_URL = "https://twitter.com/topjohnwu";
|
||||
public static final String XDA_THREAD = "http://forum.xda-developers.com/showthread.php?t=3432382";
|
||||
public static final String SOURCE_CODE_URL = "https://github.com/topjohnwu/Magisk";
|
||||
public static final String SNET_URL = getRaw("b66b1a914978e5f4c4bbfd74a59f4ad371bac107", "snet.apk");
|
||||
public static final String BOOTCTL_URL = getRaw("9c5dfc1b8245c0b5b524901ef0ff0f8335757b77", "bootctl");
|
||||
}
|
||||
|
||||
public static class Key {
|
||||
// others
|
||||
public static final String LINK_KEY = "Link";
|
||||
public static final String IF_NONE_MATCH = "If-None-Match";
|
||||
// intents
|
||||
public static final String FROM_SPLASH = "splash";
|
||||
public static final String OPEN_SECTION = "section";
|
||||
public static final String INTENT_SET_NAME = "filename";
|
||||
public static final String INTENT_SET_LINK = "link";
|
||||
public static final String FLASH_ACTION = "action";
|
||||
public static final String FLASH_SET_BOOT = "boot";
|
||||
public static final String BROADCAST_MANAGER_UPDATE = "manager_update";
|
||||
public static final String BROADCAST_REBOOT = "reboot";
|
||||
}
|
||||
|
||||
public static class Value {
|
||||
public static final String FLASH_ZIP = "flash";
|
||||
public static final String PATCH_BOOT = "patch";
|
||||
public static final String FLASH_MAGISK = "magisk";
|
||||
public static final String FLASH_INACTIVE_SLOT = "slot";
|
||||
public static final String UNINSTALL = "uninstall";
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,29 +1,68 @@
|
||||
package com.topjohnwu.magisk.container;
|
||||
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.database.Cursor;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public abstract class BaseModule implements Comparable<BaseModule> {
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
private String mId = null, mName, mVersion, mAuthor, mDescription;
|
||||
public abstract class BaseModule implements Comparable<BaseModule>, Parcelable {
|
||||
|
||||
private String mId, mName, mVersion, mAuthor, mDescription;
|
||||
private int mVersionCode = -1, minMagiskVersion = -1;
|
||||
|
||||
protected BaseModule() {}
|
||||
protected BaseModule() {
|
||||
mId = mName = mVersion = mAuthor = mDescription = "";
|
||||
}
|
||||
|
||||
protected BaseModule(Cursor c) {
|
||||
mId = c.getString(c.getColumnIndex("id"));
|
||||
mName = c.getString(c.getColumnIndex("name"));
|
||||
mVersion = c.getString(c.getColumnIndex("version"));
|
||||
mId = nonNull(c.getString(c.getColumnIndex("id")));
|
||||
mName = nonNull(c.getString(c.getColumnIndex("name")));
|
||||
mVersion = nonNull(c.getString(c.getColumnIndex("version")));
|
||||
mVersionCode = c.getInt(c.getColumnIndex("versionCode"));
|
||||
mAuthor = c.getString(c.getColumnIndex("author"));
|
||||
mDescription = c.getString(c.getColumnIndex("description"));
|
||||
mAuthor = nonNull(c.getString(c.getColumnIndex("author")));
|
||||
mDescription = nonNull(c.getString(c.getColumnIndex("description")));
|
||||
minMagiskVersion = c.getInt(c.getColumnIndex("minMagisk"));
|
||||
}
|
||||
|
||||
protected BaseModule(Parcel p) {
|
||||
mId = p.readString();
|
||||
mName = p.readString();
|
||||
mVersion = p.readString();
|
||||
mAuthor = p.readString();
|
||||
mDescription = p.readString();
|
||||
mVersionCode = p.readInt();
|
||||
minMagiskVersion = p.readInt();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NonNull BaseModule module) {
|
||||
return this.getName().toLowerCase().compareTo(module.getName().toLowerCase());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeString(mId);
|
||||
dest.writeString(mName);
|
||||
dest.writeString(mVersion);
|
||||
dest.writeString(mAuthor);
|
||||
dest.writeString(mDescription);
|
||||
dest.writeInt(mVersionCode);
|
||||
dest.writeInt(minMagiskVersion);
|
||||
}
|
||||
|
||||
private String nonNull(String s) {
|
||||
return s == null ? "" : s;
|
||||
}
|
||||
|
||||
public ContentValues getContentValues() {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put("id", mId);
|
||||
@@ -113,9 +152,4 @@ public abstract class BaseModule implements Comparable<BaseModule> {
|
||||
public int getMinMagiskVersion() {
|
||||
return minMagiskVersion;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NonNull BaseModule module) {
|
||||
return this.getName().toLowerCase().compareTo(module.getName().toLowerCase());
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,8 @@
|
||||
package com.topjohnwu.magisk.container;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
import com.topjohnwu.superuser.io.SuFile;
|
||||
|
||||
@@ -11,19 +14,19 @@ public class Module extends BaseModule {
|
||||
public Module(String path) {
|
||||
|
||||
try {
|
||||
parseProps(Shell.Sync.su("dos2unix < " + path + "/module.prop"));
|
||||
parseProps(Shell.su("dos2unix < " + path + "/module.prop").exec().getOut());
|
||||
} catch (NumberFormatException ignored) {}
|
||||
|
||||
mRemoveFile = new SuFile(path, "remove");
|
||||
mDisableFile = new SuFile(path, "disable");
|
||||
mUpdateFile = new SuFile(path, "update");
|
||||
|
||||
if (getId() == null) {
|
||||
if (getId().isEmpty()) {
|
||||
int sep = path.lastIndexOf('/');
|
||||
setId(path.substring(sep + 1));
|
||||
}
|
||||
|
||||
if (getName() == null) {
|
||||
if (getName().isEmpty()) {
|
||||
setName(getId());
|
||||
}
|
||||
|
||||
@@ -32,14 +35,25 @@ public class Module extends BaseModule {
|
||||
mUpdated = mUpdateFile.exists();
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<Module> CREATOR = new Creator<Module>() {
|
||||
/* It won't be used at any place */
|
||||
@Override
|
||||
public Module createFromParcel(Parcel source) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Module[] newArray(int size) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
public void createDisableFile() {
|
||||
mEnable = false;
|
||||
mDisableFile.createNewFile();
|
||||
mEnable = !mDisableFile.createNewFile();
|
||||
}
|
||||
|
||||
public void removeDisableFile() {
|
||||
mEnable = true;
|
||||
mDisableFile.delete();
|
||||
mEnable = mDisableFile.delete();
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
@@ -47,13 +61,11 @@ public class Module extends BaseModule {
|
||||
}
|
||||
|
||||
public void createRemoveFile() {
|
||||
mRemove = true;
|
||||
mRemoveFile.createNewFile();
|
||||
mRemove = mRemoveFile.createNewFile();
|
||||
}
|
||||
|
||||
public void deleteRemoveFile() {
|
||||
mRemove = false;
|
||||
mRemoveFile.delete();
|
||||
mRemove = !mRemoveFile.delete();
|
||||
}
|
||||
|
||||
public boolean willBeRemoved() {
|
||||
@@ -3,8 +3,10 @@ package com.topjohnwu.magisk.container;
|
||||
import android.content.ContentValues;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.database.Cursor;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
|
||||
public class Policy implements Comparable<Policy>{
|
||||
@@ -25,22 +27,22 @@ public class Policy implements Comparable<Policy>{
|
||||
this.uid = uid;
|
||||
packageName = pkgs[0];
|
||||
info = pm.getApplicationInfo(packageName, 0);
|
||||
appName = info.loadLabel(pm).toString();
|
||||
appName = Utils.getAppLabel(info, pm);
|
||||
}
|
||||
|
||||
public Policy(Cursor c, PackageManager pm) throws PackageManager.NameNotFoundException {
|
||||
uid = c.getInt(c.getColumnIndex("uid"));
|
||||
packageName = c.getString(c.getColumnIndex("package_name"));
|
||||
policy = c.getInt(c.getColumnIndex("policy"));
|
||||
until = c.getLong(c.getColumnIndex("until"));
|
||||
logging = c.getInt(c.getColumnIndex("logging")) != 0;
|
||||
notification = c.getInt(c.getColumnIndex("notification")) != 0;
|
||||
public Policy(ContentValues values, PackageManager pm) throws PackageManager.NameNotFoundException {
|
||||
uid = values.getAsInteger("uid");
|
||||
packageName = values.getAsString("package_name");
|
||||
policy = values.getAsInteger("policy");
|
||||
until = values.getAsInteger("until");
|
||||
logging = values.getAsInteger("logging") != 0;
|
||||
notification = values.getAsInteger("notification") != 0;
|
||||
info = pm.getApplicationInfo(packageName, 0);
|
||||
if (info.uid != uid)
|
||||
throw new PackageManager.NameNotFoundException();
|
||||
appName = info.loadLabel(pm).toString();
|
||||
}
|
||||
|
||||
|
||||
public ContentValues getContentValues() {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put("uid", uid);
|
||||
@@ -2,47 +2,66 @@ package com.topjohnwu.magisk.container;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.database.Cursor;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.utils.Download;
|
||||
import com.topjohnwu.magisk.utils.Logger;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.magisk.utils.WebService;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
public class Repo extends BaseModule {
|
||||
|
||||
private String repoName;
|
||||
private Date mLastUpdate;
|
||||
|
||||
public Repo(String name) {
|
||||
repoName = name;
|
||||
public Repo(String id) {
|
||||
setId(id);
|
||||
}
|
||||
|
||||
public Repo(Cursor c) {
|
||||
super(c);
|
||||
repoName = c.getString(c.getColumnIndex("repo_name"));
|
||||
mLastUpdate = new Date(c.getLong(c.getColumnIndex("last_update")));
|
||||
}
|
||||
|
||||
public Repo(Parcel p) {
|
||||
super(p);
|
||||
mLastUpdate = new Date(p.readLong());
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<Repo> CREATOR = new Parcelable.Creator<Repo>() {
|
||||
|
||||
@Override
|
||||
public Repo createFromParcel(Parcel source) {
|
||||
return new Repo(source);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Repo[] newArray(int size) {
|
||||
return new Repo[size];
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
super.writeToParcel(dest, flags);
|
||||
dest.writeLong(mLastUpdate.getTime());
|
||||
}
|
||||
|
||||
public void update() throws IllegalRepoException {
|
||||
String props[] = Utils.dos2unix(WebService.getString(getManifestUrl())).split("\\n");
|
||||
String props[] = Utils.dlString(getPropUrl()).split("\\n");
|
||||
try {
|
||||
parseProps(props);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new IllegalRepoException("Repo [" + repoName + "] parse error: " + e.getMessage());
|
||||
throw new IllegalRepoException("Repo [" + getId() + "] parse error: " + e.getMessage());
|
||||
}
|
||||
|
||||
if (getId() == null) {
|
||||
throw new IllegalRepoException("Repo [" + repoName + "] does not contain id");
|
||||
}
|
||||
if (getVersionCode() < 0) {
|
||||
throw new IllegalRepoException("Repo [" + repoName + "] does not contain versionCode");
|
||||
throw new IllegalRepoException("Repo [" + getId() + "] does not contain versionCode");
|
||||
}
|
||||
if (getMinMagiskVersion() < Const.MIN_MODULE_VER()) {
|
||||
Logger.debug("Repo [" + repoName + "] is outdated");
|
||||
if (getMinMagiskVersion() < Const.MIN_MODULE_VER) {
|
||||
Logger.debug("Repo [" + getId() + "] is outdated");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,25 +73,20 @@ public class Repo extends BaseModule {
|
||||
@Override
|
||||
public ContentValues getContentValues() {
|
||||
ContentValues values = super.getContentValues();
|
||||
values.put("repo_name", repoName);
|
||||
values.put("last_update", mLastUpdate.getTime());
|
||||
return values;
|
||||
}
|
||||
|
||||
public String getRepoName() {
|
||||
return repoName;
|
||||
}
|
||||
|
||||
public String getZipUrl() {
|
||||
return String.format(Const.Url.ZIP_URL, repoName);
|
||||
return String.format(Const.Url.ZIP_URL, getId());
|
||||
}
|
||||
|
||||
public String getManifestUrl() {
|
||||
return String.format(Const.Url.FILE_URL, repoName, "module.prop");
|
||||
public String getPropUrl() {
|
||||
return String.format(Const.Url.FILE_URL, getId(), "module.prop");
|
||||
}
|
||||
|
||||
public String getDetailUrl() {
|
||||
return String.format(Const.Url.FILE_URL, repoName, "README.md");
|
||||
return String.format(Const.Url.FILE_URL, getId(), "README.md");
|
||||
}
|
||||
|
||||
public String getLastUpdateString() {
|
||||
@@ -84,7 +98,7 @@ public class Repo extends BaseModule {
|
||||
}
|
||||
|
||||
public String getDownloadFilename() {
|
||||
return Download.getLegalFilename(getName() + "-" + getVersion() + ".zip");
|
||||
return Utils.getLegalFilename(getName() + "-" + getVersion() + ".zip");
|
||||
}
|
||||
|
||||
public class IllegalRepoException extends Exception {
|
||||
@@ -1,7 +1,6 @@
|
||||
package com.topjohnwu.magisk.container;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.database.Cursor;
|
||||
|
||||
import com.topjohnwu.magisk.utils.LocaleManager;
|
||||
|
||||
@@ -20,17 +19,18 @@ public class SuLogEntry {
|
||||
fromUid = policy.uid;
|
||||
packageName = policy.packageName;
|
||||
appName = policy.appName;
|
||||
action = policy.policy == Policy.ALLOW;
|
||||
}
|
||||
|
||||
public SuLogEntry(Cursor c) {
|
||||
fromUid = c.getInt(c.getColumnIndex("from_uid"));
|
||||
fromPid = c.getInt(c.getColumnIndex("from_pid"));
|
||||
toUid = c.getInt(c.getColumnIndex("to_uid"));
|
||||
packageName = c.getString(c.getColumnIndex("package_name"));
|
||||
appName = c.getString(c.getColumnIndex("app_name"));
|
||||
command = c.getString(c.getColumnIndex("command"));
|
||||
action = c.getInt(c.getColumnIndex("action")) != 0;
|
||||
date = new Date(c.getLong(c.getColumnIndex("time")));
|
||||
public SuLogEntry(ContentValues values) {
|
||||
fromUid = values.getAsInteger("from_uid");
|
||||
packageName = values.getAsString("package_name");
|
||||
appName = values.getAsString("app_name");
|
||||
fromPid = values.getAsInteger("from_pid");
|
||||
command = values.getAsString("command");
|
||||
toUid = values.getAsInteger("to_uid");
|
||||
action = values.getAsInteger("action") != 0;
|
||||
date = new Date(values.getAsLong("time"));
|
||||
}
|
||||
|
||||
public ContentValues getContentValues() {
|
||||
@@ -1,7 +1,5 @@
|
||||
package com.topjohnwu.magisk.container;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
@@ -9,6 +7,8 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public class ValueSortedMap<K, V extends Comparable<? super V>> extends HashMap<K, V> {
|
||||
|
||||
private List<V> sorted = new ArrayList<>();
|
||||
@@ -0,0 +1,186 @@
|
||||
package com.topjohnwu.magisk.database;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.topjohnwu.magisk.Config;
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.container.Policy;
|
||||
import com.topjohnwu.magisk.container.SuLogEntry;
|
||||
import com.topjohnwu.magisk.utils.LocaleManager;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class MagiskDB {
|
||||
|
||||
private static final String POLICY_TABLE = "policies";
|
||||
private static final String LOG_TABLE = "logs";
|
||||
private static final String SETTINGS_TABLE = "settings";
|
||||
private static final String STRINGS_TABLE = "strings";
|
||||
|
||||
private PackageManager pm;
|
||||
|
||||
public MagiskDB(Context context) {
|
||||
pm = context.getPackageManager();
|
||||
}
|
||||
|
||||
public void deletePolicy(Policy policy) {
|
||||
deletePolicy(policy.uid);
|
||||
}
|
||||
|
||||
private List<String> rawSQL(String fmt, Object... args) {
|
||||
return Shell.su("magisk --sqlite '" + Utils.fmt(fmt, args) + "'").exec().getOut();
|
||||
}
|
||||
|
||||
private List<ContentValues> SQL(String fmt, Object... args) {
|
||||
List<ContentValues> list = new ArrayList<>();
|
||||
for (String raw : rawSQL(fmt, args)) {
|
||||
ContentValues values = new ContentValues();
|
||||
String[] cols = raw.split("\\|");
|
||||
for (String col : cols) {
|
||||
String[] pair = col.split("=", 2);
|
||||
if (pair.length != 2)
|
||||
continue;
|
||||
values.put(pair[0], pair[1]);
|
||||
}
|
||||
list.add(values);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
private String toSQL(ContentValues values) {
|
||||
StringBuilder keys = new StringBuilder(), vals = new StringBuilder();
|
||||
keys.append('(');
|
||||
vals.append("VALUES(");
|
||||
boolean first = true;
|
||||
for (Map.Entry<String, Object> entry : values.valueSet()) {
|
||||
if (!first) {
|
||||
keys.append(',');
|
||||
vals.append(',');
|
||||
} else {
|
||||
first = false;
|
||||
}
|
||||
keys.append(entry.getKey());
|
||||
vals.append('"');
|
||||
vals.append(entry.getValue());
|
||||
vals.append('"');
|
||||
}
|
||||
keys.append(')');
|
||||
vals.append(')');
|
||||
keys.append(vals);
|
||||
return keys.toString();
|
||||
}
|
||||
|
||||
public void clearOutdated() {
|
||||
rawSQL(
|
||||
"DELETE FROM %s WHERE until > 0 AND until < %d;" +
|
||||
"DELETE FROM %s WHERE time < %d",
|
||||
POLICY_TABLE, System.currentTimeMillis() / 1000,
|
||||
LOG_TABLE, System.currentTimeMillis() - Config.suLogTimeout * 86400000
|
||||
);
|
||||
}
|
||||
|
||||
public void deletePolicy(String pkg) {
|
||||
rawSQL("DELETE FROM %s WHERE package_name=\"%s\"", POLICY_TABLE, pkg);
|
||||
}
|
||||
|
||||
public void deletePolicy(int uid) {
|
||||
rawSQL("DELETE FROM %s WHERE uid=%d", POLICY_TABLE, uid);
|
||||
}
|
||||
|
||||
public Policy getPolicy(int uid) {
|
||||
List<ContentValues> res =
|
||||
SQL("SELECT * FROM %s WHERE uid=%d", POLICY_TABLE, uid);
|
||||
if (!res.isEmpty()) {
|
||||
try {
|
||||
return new Policy(res.get(0), pm);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
deletePolicy(uid);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void updatePolicy(Policy policy) {
|
||||
rawSQL("REPLACE INTO %s %s", POLICY_TABLE, toSQL(policy.getContentValues()));
|
||||
}
|
||||
|
||||
public List<Policy> getPolicyList() {
|
||||
List<Policy> list = new ArrayList<>();
|
||||
for (ContentValues values : SQL("SELECT * FROM %s WHERE uid/100000=%d", POLICY_TABLE, Const.USER_ID)) {
|
||||
try {
|
||||
list.add(new Policy(values, pm));
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
deletePolicy(values.getAsInteger("uid"));
|
||||
}
|
||||
}
|
||||
Collections.sort(list);
|
||||
return list;
|
||||
}
|
||||
|
||||
public List<List<SuLogEntry>> getLogs() {
|
||||
List<List<SuLogEntry>> ret = new ArrayList<>();
|
||||
List<SuLogEntry> list = null;
|
||||
String dateString = null, newString;
|
||||
for (ContentValues values : SQL("SELECT * FROM %s ORDER BY time DESC", LOG_TABLE)) {
|
||||
Date date = new Date(values.getAsLong("time"));
|
||||
newString = DateFormat.getDateInstance(DateFormat.MEDIUM, LocaleManager.locale).format(date);
|
||||
if (!TextUtils.equals(dateString, newString)) {
|
||||
dateString = newString;
|
||||
list = new ArrayList<>();
|
||||
ret.add(list);
|
||||
}
|
||||
list.add(new SuLogEntry(values));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
public void addLog(SuLogEntry log) {
|
||||
rawSQL("INSERT INTO %s %s", LOG_TABLE, toSQL(log.getContentValues()));
|
||||
}
|
||||
|
||||
public void clearLogs() {
|
||||
rawSQL("DELETE FROM %s", LOG_TABLE);
|
||||
}
|
||||
|
||||
public void setSettings(String key, int value) {
|
||||
ContentValues data = new ContentValues();
|
||||
data.put("key", key);
|
||||
data.put("value", value);
|
||||
rawSQL("REPLACE INTO %s %s", SETTINGS_TABLE, toSQL(data));
|
||||
}
|
||||
|
||||
public int getSettings(String key, int defaultValue) {
|
||||
List<ContentValues> res = SQL("SELECT value FROM %s WHERE key=\"%s\"", SETTINGS_TABLE, key);
|
||||
if (res.isEmpty())
|
||||
return defaultValue;
|
||||
return res.get(0).getAsInteger("value");
|
||||
}
|
||||
|
||||
public void setStrings(String key, String value) {
|
||||
if (value == null) {
|
||||
rawSQL("DELETE FROM %s WHERE key=\"%s\"", STRINGS_TABLE, key);
|
||||
return;
|
||||
}
|
||||
ContentValues data = new ContentValues();
|
||||
data.put("key", key);
|
||||
data.put("value", value);
|
||||
rawSQL("REPLACE INTO %s %s", STRINGS_TABLE, toSQL(data));
|
||||
}
|
||||
|
||||
public String getStrings(String key, String defaultValue) {
|
||||
List<ContentValues> res = SQL("SELECT value FROM %s WHERE key=\"%s\"", STRINGS_TABLE, key);
|
||||
if (res.isEmpty())
|
||||
return defaultValue;
|
||||
return res.get(0).getAsString("value");
|
||||
}
|
||||
}
|
||||
@@ -5,10 +5,8 @@ import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
|
||||
import com.topjohnwu.magisk.Config;
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.adapters.ReposAdapter;
|
||||
import com.topjohnwu.magisk.container.Repo;
|
||||
|
||||
import java.util.HashSet;
|
||||
@@ -16,21 +14,17 @@ import java.util.Set;
|
||||
|
||||
public class RepoDatabaseHelper extends SQLiteOpenHelper {
|
||||
|
||||
private static final int DATABASE_VER = 3;
|
||||
private static final int DATABASE_VER = 4;
|
||||
private static final String TABLE_NAME = "repos";
|
||||
|
||||
private SQLiteDatabase mDb;
|
||||
private MagiskManager mm;
|
||||
private ReposAdapter adapter;
|
||||
|
||||
public RepoDatabaseHelper(Context context) {
|
||||
super(context, "repo.db", null, DATABASE_VER);
|
||||
mm = Data.MM();
|
||||
mDb = getWritableDatabase();
|
||||
|
||||
// Remove outdated repos
|
||||
mDb.delete(TABLE_NAME, "minMagisk<?",
|
||||
new String[] { String.valueOf(Const.MIN_MODULE_VER()) });
|
||||
mDb.delete(TABLE_NAME, "minMagisk<?", new String[] { String.valueOf(Const.MIN_MODULE_VER) });
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -40,21 +34,14 @@ public class RepoDatabaseHelper extends SQLiteOpenHelper {
|
||||
|
||||
@Override
|
||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
try {
|
||||
if (oldVersion < 3) {
|
||||
db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
|
||||
db.execSQL(
|
||||
"CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " " +
|
||||
"(id TEXT, name TEXT, version TEXT, versionCode INT, minMagisk INT, " +
|
||||
"author TEXT, description TEXT, repo_name TEXT, last_update INT, " +
|
||||
"PRIMARY KEY(id))");
|
||||
mm.prefs.edit().remove(Const.Key.ETAG_KEY).apply();
|
||||
oldVersion = 3;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
// Reset database
|
||||
onDowngrade(db, DATABASE_VER, 0);
|
||||
if (oldVersion != newVersion) {
|
||||
// Nuke old DB and create new table
|
||||
db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
|
||||
db.execSQL(
|
||||
"CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " " +
|
||||
"(id TEXT, name TEXT, version TEXT, versionCode INT, minMagisk INT, " +
|
||||
"author TEXT, description TEXT, last_update INT, PRIMARY KEY(id))");
|
||||
Config.remove(Config.Key.ETAG_KEY);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,18 +52,15 @@ public class RepoDatabaseHelper extends SQLiteOpenHelper {
|
||||
|
||||
public void clearRepo() {
|
||||
mDb.delete(TABLE_NAME, null, null);
|
||||
notifyAdapter();
|
||||
}
|
||||
|
||||
|
||||
public void removeRepo(String id) {
|
||||
mDb.delete(TABLE_NAME, "id=?", new String[] { id });
|
||||
notifyAdapter();
|
||||
}
|
||||
|
||||
public void removeRepo(Repo repo) {
|
||||
mDb.delete(TABLE_NAME, "repo_name=?", new String[] { repo.getRepoName() });
|
||||
notifyAdapter();
|
||||
removeRepo(repo.getId());
|
||||
}
|
||||
|
||||
public void removeRepo(Iterable<String> list) {
|
||||
@@ -84,12 +68,10 @@ public class RepoDatabaseHelper extends SQLiteOpenHelper {
|
||||
if (id == null) continue;
|
||||
mDb.delete(TABLE_NAME, "id=?", new String[] { id });
|
||||
}
|
||||
notifyAdapter();
|
||||
}
|
||||
|
||||
public void addRepo(Repo repo) {
|
||||
mDb.replace(TABLE_NAME, null, repo.getContentValues());
|
||||
notifyAdapter();
|
||||
}
|
||||
|
||||
public Repo getRepo(String id) {
|
||||
@@ -107,15 +89,15 @@ public class RepoDatabaseHelper extends SQLiteOpenHelper {
|
||||
|
||||
public Cursor getRepoCursor() {
|
||||
String orderBy = null;
|
||||
switch (Data.repoOrder) {
|
||||
case Const.Value.ORDER_NAME:
|
||||
switch ((int) Config.get(Config.Key.REPO_ORDER)) {
|
||||
case Config.Value.ORDER_NAME:
|
||||
orderBy = "name COLLATE NOCASE";
|
||||
break;
|
||||
case Const.Value.ORDER_DATE:
|
||||
case Config.Value.ORDER_DATE:
|
||||
orderBy = "last_update DESC";
|
||||
}
|
||||
return mDb.query(TABLE_NAME, null, "minMagisk<=? AND minMagisk>=?",
|
||||
new String[] { String.valueOf(Data.magiskVersionCode), String.valueOf(Const.MIN_MODULE_VER()) },
|
||||
new String[] { String.valueOf(Config.magiskVersionCode), String.valueOf(Const.MIN_MODULE_VER) },
|
||||
null, null, orderBy);
|
||||
}
|
||||
|
||||
@@ -128,18 +110,4 @@ public class RepoDatabaseHelper extends SQLiteOpenHelper {
|
||||
}
|
||||
return set;
|
||||
}
|
||||
|
||||
public void registerAdapter(ReposAdapter a) {
|
||||
adapter = a;
|
||||
}
|
||||
|
||||
public void unregisterAdapter() {
|
||||
adapter = null;
|
||||
}
|
||||
|
||||
private void notifyAdapter() {
|
||||
if (adapter != null) {
|
||||
Data.mainHandler.post(adapter::notifyDBChanged);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
package com.topjohnwu.magisk.tasks;
|
||||
|
||||
import android.os.SystemClock;
|
||||
|
||||
import com.topjohnwu.magisk.Config;
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.utils.Topic;
|
||||
import com.topjohnwu.net.Networking;
|
||||
import com.topjohnwu.net.Request;
|
||||
import com.topjohnwu.net.ResponseListener;
|
||||
import com.topjohnwu.superuser.internal.UiThreadHandler;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
public class CheckUpdates {
|
||||
|
||||
private static Request getRequest() {
|
||||
String url;
|
||||
switch ((int) Config.get(Config.Key.UPDATE_CHANNEL)) {
|
||||
case Config.Value.BETA_CHANNEL:
|
||||
url = Const.Url.BETA_URL;
|
||||
break;
|
||||
case Config.Value.CUSTOM_CHANNEL:
|
||||
url = Config.get(Config.Key.CUSTOM_CHANNEL);
|
||||
break;
|
||||
case Config.Value.STABLE_CHANNEL:
|
||||
default:
|
||||
url = Const.Url.STABLE_URL;
|
||||
break;
|
||||
}
|
||||
return Networking.get(url);
|
||||
}
|
||||
|
||||
public static void check() {
|
||||
getRequest().getAsJSONObject(new UpdateListener(null));
|
||||
}
|
||||
|
||||
public static void checkNow(Runnable cb) {
|
||||
JSONObject json = getRequest().execForJSONObject().getResult();
|
||||
if (json != null)
|
||||
new UpdateListener(cb).onResponse(json);
|
||||
}
|
||||
|
||||
private static class UpdateListener implements ResponseListener<JSONObject> {
|
||||
|
||||
private Runnable cb;
|
||||
private long start;
|
||||
|
||||
UpdateListener(Runnable callback) {
|
||||
cb = callback;
|
||||
start = SystemClock.uptimeMillis();
|
||||
}
|
||||
|
||||
private int getInt(JSONObject json, String name, int defValue) {
|
||||
if (json == null)
|
||||
return defValue;
|
||||
try {
|
||||
return json.getInt(name);
|
||||
} catch (JSONException e) {
|
||||
return defValue;
|
||||
}
|
||||
}
|
||||
|
||||
private String getString(JSONObject json, String name, String defValue) {
|
||||
if (json == null)
|
||||
return defValue;
|
||||
try {
|
||||
return json.getString(name);
|
||||
} catch (JSONException e) {
|
||||
return defValue;
|
||||
}
|
||||
}
|
||||
|
||||
private JSONObject getJson(JSONObject json, String name) {
|
||||
try {
|
||||
return json.getJSONObject(name);
|
||||
} catch (JSONException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResponse(JSONObject json) {
|
||||
JSONObject magisk = getJson(json, "magisk");
|
||||
Config.remoteMagiskVersionString = getString(magisk, "version", null);
|
||||
Config.remoteMagiskVersionCode = getInt(magisk, "versionCode", -1);
|
||||
Config.magiskLink = getString(magisk, "link", null);
|
||||
Config.magiskNoteLink = getString(magisk, "note", null);
|
||||
Config.magiskMD5 = getString(magisk, "md5", null);
|
||||
|
||||
JSONObject manager = getJson(json, "app");
|
||||
Config.remoteManagerVersionString = getString(manager, "version", null);
|
||||
Config.remoteManagerVersionCode = getInt(manager, "versionCode", -1);
|
||||
Config.managerLink = getString(manager, "link", null);
|
||||
Config.managerNoteLink = getString(manager, "note", null);
|
||||
|
||||
JSONObject uninstaller = getJson(json, "uninstaller");
|
||||
Config.uninstallerLink = getString(uninstaller, "link", null);
|
||||
|
||||
UiThreadHandler.handler.postAtTime(() -> Topic.publish(Topic.UPDATE_CHECK_DONE),
|
||||
start + 1000 /* Add artificial delay to let UI behave correctly */);
|
||||
|
||||
if (cb != null)
|
||||
cb.run();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package com.topjohnwu.magisk.tasks;
|
||||
|
||||
import android.net.Uri;
|
||||
|
||||
import com.topjohnwu.magisk.App;
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.magisk.utils.ZipUtils;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
import com.topjohnwu.superuser.ShellUtils;
|
||||
import com.topjohnwu.superuser.internal.UiThreadHandler;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.List;
|
||||
|
||||
public abstract class FlashZip {
|
||||
|
||||
private Uri mUri;
|
||||
private File tmpFile;
|
||||
private List<String> console, logs;
|
||||
|
||||
public FlashZip(Uri uri, List<String> out, List<String> err) {
|
||||
mUri = uri;
|
||||
console = out;
|
||||
logs = err;
|
||||
tmpFile = new File(App.self.getCacheDir(), "install.zip");
|
||||
}
|
||||
|
||||
private boolean unzipAndCheck() throws IOException {
|
||||
ZipUtils.unzip(tmpFile, tmpFile.getParentFile(), "META-INF/com/google/android", true);
|
||||
return Shell.su("grep -q '#MAGISK' " + new File(tmpFile.getParentFile(), "updater-script"))
|
||||
.exec().isSuccess();
|
||||
}
|
||||
|
||||
private boolean flash() throws IOException {
|
||||
console.add("- Copying zip to temp directory");
|
||||
try (InputStream in = App.self.getContentResolver().openInputStream(mUri);
|
||||
OutputStream out = new BufferedOutputStream(new FileOutputStream(tmpFile))) {
|
||||
if (in == null) throw new FileNotFoundException();
|
||||
InputStream buf= new BufferedInputStream(in);
|
||||
ShellUtils.pump(buf, out);
|
||||
} catch (FileNotFoundException e) {
|
||||
console.add("! Invalid Uri");
|
||||
throw e;
|
||||
} catch (IOException e) {
|
||||
console.add("! Cannot copy to cache");
|
||||
throw e;
|
||||
}
|
||||
try {
|
||||
if (!unzipAndCheck()) {
|
||||
console.add("! This zip is not a Magisk Module!");
|
||||
return false;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
console.add("! Unzip error");
|
||||
throw e;
|
||||
}
|
||||
console.add("- Installing " + Utils.getNameFromUri(App.self, mUri));
|
||||
return Shell.su("cd " + tmpFile.getParent(),
|
||||
"BOOTMODE=true sh update-binary dummy 1 " + tmpFile)
|
||||
.to(console, logs)
|
||||
.exec().isSuccess();
|
||||
}
|
||||
|
||||
public void exec() {
|
||||
App.THREAD_POOL.execute(() -> {
|
||||
boolean success = false;
|
||||
try {
|
||||
success = flash();
|
||||
} catch (IOException ignored) {}
|
||||
Shell.su("cd /", "rm -rf " + tmpFile.getParent() + " " + Const.TMP_FOLDER_PATH).submit();
|
||||
boolean finalSuccess = success;
|
||||
UiThreadHandler.run(() -> onResult(finalSuccess));
|
||||
});
|
||||
}
|
||||
|
||||
protected abstract void onResult(boolean success);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,176 @@
|
||||
package com.topjohnwu.magisk.tasks;
|
||||
|
||||
import android.database.Cursor;
|
||||
import android.util.Pair;
|
||||
|
||||
import com.topjohnwu.magisk.App;
|
||||
import com.topjohnwu.magisk.Config;
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.container.Repo;
|
||||
import com.topjohnwu.magisk.utils.Logger;
|
||||
import com.topjohnwu.magisk.utils.Topic;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.net.Networking;
|
||||
import com.topjohnwu.net.Request;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.net.HttpURLConnection;
|
||||
import java.text.DateFormat;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
import java.util.Queue;
|
||||
import java.util.Set;
|
||||
import java.util.TimeZone;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
public class UpdateRepos {
|
||||
private static final DateFormat DATE_FORMAT;
|
||||
|
||||
private App app = App.self;
|
||||
private Set<String> cached;
|
||||
private Queue<Pair<String, Date>> moduleQueue;
|
||||
|
||||
static {
|
||||
DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US);
|
||||
DATE_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||
}
|
||||
|
||||
private void runTasks(Runnable task) {
|
||||
Future[] futures = new Future[App.THREAD_POOL.getMaximumPoolSize() - 1];
|
||||
for (int i = 0; i < futures.length; ++i) {
|
||||
futures[i] = App.THREAD_POOL.submit(task);
|
||||
}
|
||||
for (Future f : futures) {
|
||||
while (true) {
|
||||
try {
|
||||
f.get();
|
||||
} catch (InterruptedException e) {
|
||||
continue;
|
||||
} catch (ExecutionException ignored) {}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* We sort repos by last push, which means that we only need to check whether the
|
||||
* first page is updated to determine whether the online repo database is changed
|
||||
*/
|
||||
private boolean parsePage(int page) {
|
||||
Request req = Networking.get(Utils.fmt(Const.Url.REPO_URL, page + 1));
|
||||
if (page == 0) {
|
||||
String etag = Config.get(Config.Key.ETAG_KEY);
|
||||
if (etag != null)
|
||||
req.addHeaders(Const.Key.IF_NONE_MATCH, etag);
|
||||
}
|
||||
Request.Result<JSONArray> res = req.execForJSONArray();
|
||||
// JSON not updated
|
||||
if (res.getCode() == HttpURLConnection.HTTP_NOT_MODIFIED)
|
||||
return false;
|
||||
// Network error
|
||||
if (res.getResult() == null) {
|
||||
cached.clear();
|
||||
return true;
|
||||
}
|
||||
// Current page is the last page
|
||||
if (res.getResult().length() == 0)
|
||||
return true;
|
||||
|
||||
try {
|
||||
for (int i = 0; i < res.getResult().length(); i++) {
|
||||
JSONObject rawRepo = res.getResult().getJSONObject(i);
|
||||
String id = rawRepo.getString("name");
|
||||
Date date = DATE_FORMAT.parse(rawRepo.getString("pushed_at"));
|
||||
moduleQueue.offer(new Pair<>(id, date));
|
||||
}
|
||||
} catch (JSONException | ParseException e) {
|
||||
// Should not happen, but if exception occurs, page load fails
|
||||
return false;
|
||||
}
|
||||
|
||||
// Update ETAG
|
||||
if (page == 0) {
|
||||
String etag = res.getConnection().getHeaderField(Config.Key.ETAG_KEY);
|
||||
if (etag != null) {
|
||||
etag = etag.substring(etag.indexOf('\"'), etag.lastIndexOf('\"') + 1);
|
||||
Config.set(Config.Key.ETAG_KEY, etag);
|
||||
}
|
||||
}
|
||||
|
||||
String links = res.getConnection().getHeaderField(Const.Key.LINK_KEY);
|
||||
return links == null || !links.contains("next") || parsePage(page + 1);
|
||||
}
|
||||
|
||||
private boolean loadPages() {
|
||||
if (!parsePage(0))
|
||||
return false;
|
||||
runTasks(() -> {
|
||||
while (true) {
|
||||
Pair<String, Date> pair = moduleQueue.poll();
|
||||
if (pair == null)
|
||||
return;
|
||||
Repo repo = app.repoDB.getRepo(pair.first);
|
||||
try {
|
||||
if (repo == null)
|
||||
repo = new Repo(pair.first);
|
||||
else
|
||||
cached.remove(pair.first);
|
||||
repo.update(pair.second);
|
||||
app.repoDB.addRepo(repo);
|
||||
} catch (Repo.IllegalRepoException e) {
|
||||
Logger.debug(e.getMessage());
|
||||
app.repoDB.removeRepo(pair.first);
|
||||
}
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
private void fullReload() {
|
||||
Cursor c = app.repoDB.getRawCursor();
|
||||
runTasks(() -> {
|
||||
while (true) {
|
||||
Repo repo;
|
||||
synchronized (c) {
|
||||
if (!c.moveToNext())
|
||||
return;
|
||||
repo = new Repo(c);
|
||||
}
|
||||
try {
|
||||
repo.update();
|
||||
app.repoDB.addRepo(repo);
|
||||
} catch (Repo.IllegalRepoException e) {
|
||||
Logger.debug(e.getMessage());
|
||||
app.repoDB.removeRepo(repo);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void exec(boolean force) {
|
||||
Topic.reset(Topic.REPO_LOAD_DONE);
|
||||
App.THREAD_POOL.execute(() -> {
|
||||
cached = Collections.synchronizedSet(app.repoDB.getRepoIDSet());
|
||||
moduleQueue = new ConcurrentLinkedQueue<>();
|
||||
|
||||
if (loadPages()) {
|
||||
// The leftover cached means they are removed from online repo
|
||||
app.repoDB.removeRepo(cached);
|
||||
} else if (force) {
|
||||
fullReload();
|
||||
}
|
||||
Topic.publish(Topic.REPO_LOAD_DONE);
|
||||
});
|
||||
}
|
||||
|
||||
public void exec() {
|
||||
exec(false);
|
||||
}
|
||||
}
|
||||
@@ -9,9 +9,9 @@ import android.security.keystore.KeyGenParameterSpec;
|
||||
import android.security.keystore.KeyPermanentlyInvalidatedException;
|
||||
import android.security.keystore.KeyProperties;
|
||||
|
||||
import com.topjohnwu.magisk.App;
|
||||
import com.topjohnwu.magisk.Config;
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
|
||||
import java.security.KeyStore;
|
||||
|
||||
@@ -26,18 +26,26 @@ public abstract class FingerprintHelper {
|
||||
private Cipher cipher;
|
||||
private CancellationSignal cancel;
|
||||
|
||||
public static boolean useFingerprint() {
|
||||
boolean fp = Config.get(Config.Key.SU_FINGERPRINT);
|
||||
if (fp && !canUseFingerprint()) {
|
||||
Config.set(Config.Key.SU_FINGERPRINT, false);
|
||||
fp = false;
|
||||
}
|
||||
return fp;
|
||||
}
|
||||
|
||||
public static boolean canUseFingerprint() {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M)
|
||||
return false;
|
||||
MagiskManager mm = Data.MM();
|
||||
KeyguardManager km = mm.getSystemService(KeyguardManager.class);
|
||||
FingerprintManager fm = mm.getSystemService(FingerprintManager.class);
|
||||
KeyguardManager km = App.self.getSystemService(KeyguardManager.class);
|
||||
FingerprintManager fm = App.self.getSystemService(FingerprintManager.class);
|
||||
return km.isKeyguardSecure() && fm != null && fm.isHardwareDetected() && fm.hasEnrolledFingerprints();
|
||||
}
|
||||
|
||||
protected FingerprintHelper() throws Exception {
|
||||
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
|
||||
manager = Data.MM().getSystemService(FingerprintManager.class);
|
||||
manager = App.self.getSystemService(FingerprintManager.class);
|
||||
cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/"
|
||||
+ KeyProperties.BLOCK_MODE_CBC + "/"
|
||||
+ KeyProperties.ENCRYPTION_PADDING_PKCS7);
|
||||
@@ -1,7 +1,5 @@
|
||||
package com.topjohnwu.magisk.utils;
|
||||
|
||||
import android.support.annotation.Keep;
|
||||
|
||||
public interface ISafetyNetHelper {
|
||||
|
||||
int RESPONSE_ERR = 0x01;
|
||||
@@ -10,14 +8,11 @@ public interface ISafetyNetHelper {
|
||||
int BASIC_PASS = 0x10;
|
||||
int CTS_PASS = 0x20;
|
||||
|
||||
@Keep
|
||||
void attest();
|
||||
|
||||
@Keep
|
||||
int getVersion();
|
||||
|
||||
interface Callback {
|
||||
@Keep
|
||||
void onResponse(int responseCode);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
package com.topjohnwu.magisk.utils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.ContextWrapper;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.os.Build;
|
||||
|
||||
import com.topjohnwu.magisk.App;
|
||||
import com.topjohnwu.magisk.Config;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
import com.topjohnwu.superuser.internal.InternalUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import androidx.annotation.StringRes;
|
||||
|
||||
public class LocaleManager {
|
||||
public static Locale locale = Locale.getDefault();
|
||||
public final static Locale defaultLocale = Locale.getDefault();
|
||||
public static List<Locale> locales;
|
||||
|
||||
public static Locale forLanguageTag(String tag) {
|
||||
if (Build.VERSION.SDK_INT >= 21) {
|
||||
return Locale.forLanguageTag(tag);
|
||||
} else {
|
||||
String[] tok = tag.split("-");
|
||||
if (tok.length == 0) {
|
||||
return new Locale("");
|
||||
}
|
||||
String language;
|
||||
switch (tok[0]) {
|
||||
case "und":
|
||||
language = ""; // Undefined
|
||||
break;
|
||||
case "fil":
|
||||
language = "tl"; // Filipino
|
||||
break;
|
||||
default:
|
||||
language = tok[0];
|
||||
}
|
||||
if ((language.length() != 2 && language.length() != 3))
|
||||
return new Locale("");
|
||||
if (tok.length == 1)
|
||||
return new Locale(language);
|
||||
String country = tok[1];
|
||||
if (country.length() != 2 && country.length() != 3)
|
||||
return new Locale(language);
|
||||
return new Locale(language, country);
|
||||
}
|
||||
}
|
||||
|
||||
public static String toLanguageTag(Locale loc) {
|
||||
if (Build.VERSION.SDK_INT >= 21) {
|
||||
return loc.toLanguageTag();
|
||||
} else {
|
||||
String language = loc.getLanguage();
|
||||
String country = loc.getCountry();
|
||||
String variant = loc.getVariant();
|
||||
if (language.isEmpty() || !language.matches("\\p{Alpha}{2,8}")) {
|
||||
language = "und"; // Follow the Locale#toLanguageTag() implementation
|
||||
} else if (language.equals("iw")) {
|
||||
language = "he"; // correct deprecated "Hebrew"
|
||||
} else if (language.equals("in")) {
|
||||
language = "id"; // correct deprecated "Indonesian"
|
||||
} else if (language.equals("ji")) {
|
||||
language = "yi"; // correct deprecated "Yiddish"
|
||||
}
|
||||
// ensure valid country code, if not well formed, it's omitted
|
||||
if (!country.matches("\\p{Alpha}{2}|\\p{Digit}{3}")) {
|
||||
country = "";
|
||||
}
|
||||
|
||||
// variant subtags that begin with a letter must be at least 5 characters long
|
||||
if (!variant.matches("\\p{Alnum}{5,8}|\\p{Digit}\\p{Alnum}{3}")) {
|
||||
variant = "";
|
||||
}
|
||||
StringBuilder tag = new StringBuilder(language);
|
||||
if (!country.isEmpty())
|
||||
tag.append('-').append(country);
|
||||
if (!variant.isEmpty())
|
||||
tag.append('-').append(variant);
|
||||
return tag.toString();
|
||||
}
|
||||
}
|
||||
|
||||
public static void setLocale(ContextWrapper wrapper) {
|
||||
String localeConfig = Config.get(Config.Key.LOCALE);
|
||||
if (localeConfig.isEmpty()) {
|
||||
locale = defaultLocale;
|
||||
} else {
|
||||
locale = forLanguageTag(localeConfig);
|
||||
}
|
||||
Locale.setDefault(locale);
|
||||
InternalUtils.replaceBaseContext(wrapper, getLocaleContext(locale));
|
||||
}
|
||||
|
||||
public static Context getLocaleContext(Context context, Locale locale) {
|
||||
if (Build.VERSION.SDK_INT >= 17) {
|
||||
Configuration config = new Configuration(context.getResources().getConfiguration());
|
||||
config.setLocale(locale);
|
||||
return context.createConfigurationContext(config);
|
||||
} else {
|
||||
return context;
|
||||
}
|
||||
}
|
||||
|
||||
public static Context getLocaleContext(Locale locale) {
|
||||
return getLocaleContext(App.self.getBaseContext(), locale);
|
||||
}
|
||||
|
||||
public static String getString(Locale locale, @StringRes int id) {
|
||||
return getLocaleContext(locale).getString(id);
|
||||
}
|
||||
|
||||
public static void loadAvailableLocales(@StringRes int compareId) {
|
||||
if (Build.VERSION.SDK_INT < 17)
|
||||
return;
|
||||
Shell.EXECUTOR.execute(() -> {
|
||||
locales = new ArrayList<>();
|
||||
HashSet<String> set = new HashSet<>();
|
||||
Resources res = App.self.getResources();
|
||||
Locale locale;
|
||||
|
||||
// Add default locale
|
||||
locales.add(Locale.ENGLISH);
|
||||
set.add(getString(Locale.ENGLISH, compareId));
|
||||
|
||||
// Add some special locales
|
||||
locales.add(Locale.TAIWAN);
|
||||
set.add(getString(Locale.TAIWAN, compareId));
|
||||
locale = new Locale("pt", "BR");
|
||||
locales.add(locale);
|
||||
set.add(getString(locale, compareId));
|
||||
|
||||
// Other locales
|
||||
for (String s : res.getAssets().getLocales()) {
|
||||
locale = forLanguageTag(s);
|
||||
if (set.add(getString(locale, compareId))) {
|
||||
locales.add(locale);
|
||||
}
|
||||
}
|
||||
|
||||
Collections.sort(locales, (a, b) -> a.getDisplayName(a).compareTo(b.getDisplayName(b)));
|
||||
Topic.publish(Topic.LOCALE_FETCH_DONE);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,8 @@ package com.topjohnwu.magisk.utils;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.topjohnwu.magisk.BuildConfig;
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.core.BuildConfig;
|
||||
|
||||
public class Logger {
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.topjohnwu.magisk.utils;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.topjohnwu.magisk.Config;
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.core.R;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
import com.topjohnwu.superuser.ShellUtils;
|
||||
import com.topjohnwu.superuser.io.SuFile;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public class RootUtils extends Shell.Initializer {
|
||||
|
||||
public static void rmAndLaunch(String rm, String launch) {
|
||||
Shell.su(Utils.fmt("(rm_launch %d %s %s)&", Const.USER_ID, rm, launch)).exec();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onInit(Context context, @NonNull Shell shell) {
|
||||
Shell.Job job = shell.newJob();
|
||||
if (shell.isRoot()) {
|
||||
job.add(context.getResources().openRawResource(R.raw.util_functions))
|
||||
.add(context.getResources().openRawResource(R.raw.utils));
|
||||
Const.MAGISK_DISABLE_FILE = new SuFile("/cache/.disable_magisk");
|
||||
Config.loadMagiskInfo();
|
||||
} else {
|
||||
InputStream nonroot = context.getResources().openRawResource(R.raw.nonroot_utils);
|
||||
job.add(nonroot);
|
||||
}
|
||||
|
||||
job.add("mount_partitions", "get_flags", "run_migrations", "export BOOTMODE=true").exec();
|
||||
|
||||
Config.keepVerity = Boolean.parseBoolean(ShellUtils.fastCmd("echo $KEEPVERITY"));
|
||||
Config.keepEnc = Boolean.parseBoolean(ShellUtils.fastCmd("echo $KEEPFORCEENCRYPT"));
|
||||
Config.recovery = Boolean.parseBoolean(ShellUtils.fastCmd("echo $RECOVERYMODE"));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package com.topjohnwu.magisk.utils;
|
||||
|
||||
import android.net.LocalSocket;
|
||||
import android.net.LocalSocketAddress;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
public abstract class SuConnector {
|
||||
|
||||
private LocalSocket socket;
|
||||
protected DataOutputStream out;
|
||||
protected DataInputStream in;
|
||||
|
||||
protected SuConnector(String name) throws IOException {
|
||||
socket = new LocalSocket();
|
||||
socket.connect(new LocalSocketAddress(name, LocalSocketAddress.Namespace.ABSTRACT));
|
||||
out = new DataOutputStream(new BufferedOutputStream(socket.getOutputStream()));
|
||||
in = new DataInputStream(new BufferedInputStream(socket.getInputStream()));
|
||||
}
|
||||
|
||||
private String readString() throws IOException {
|
||||
int len = in.readInt();
|
||||
byte[] buf = new byte[len];
|
||||
in.readFully(buf);
|
||||
return new String(buf, "UTF-8");
|
||||
}
|
||||
|
||||
public Bundle readSocketInput() throws IOException {
|
||||
Bundle bundle = new Bundle();
|
||||
while (true) {
|
||||
String name = readString();
|
||||
if (TextUtils.equals(name, "eof"))
|
||||
break;
|
||||
bundle.putString(name, readString());
|
||||
}
|
||||
return bundle;
|
||||
}
|
||||
|
||||
public void response() {
|
||||
try {
|
||||
onResponse();
|
||||
out.flush();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
try {
|
||||
in.close();
|
||||
out.close();
|
||||
socket.close();
|
||||
} catch (IOException ignored) { }
|
||||
}
|
||||
|
||||
protected abstract void onResponse() throws IOException;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package com.topjohnwu.magisk.utils;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Bundle;
|
||||
import android.os.Process;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.topjohnwu.magisk.App;
|
||||
import com.topjohnwu.magisk.Config;
|
||||
import com.topjohnwu.magisk.container.Policy;
|
||||
import com.topjohnwu.magisk.container.SuLogEntry;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
public abstract class SuLogger {
|
||||
|
||||
public void handleLogs(Intent intent) {
|
||||
|
||||
int fromUid = intent.getIntExtra("from.uid", -1);
|
||||
if (fromUid < 0) return;
|
||||
if (fromUid == Process.myUid()) return;
|
||||
|
||||
App app = App.self;
|
||||
PackageManager pm = app.getPackageManager();
|
||||
Policy policy;
|
||||
|
||||
boolean notify;
|
||||
Bundle data = intent.getExtras();
|
||||
if (data.containsKey("notify")) {
|
||||
notify = data.getBoolean("notify");
|
||||
try {
|
||||
policy = new Policy(fromUid, pm);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// Doesn't report whether notify or not, check database ourselves
|
||||
policy = app.mDB.getPolicy(fromUid);
|
||||
if (policy == null)
|
||||
return;
|
||||
notify = policy.notification;
|
||||
}
|
||||
|
||||
policy.policy = data.getInt("policy", -1);
|
||||
if (policy.policy < 0)
|
||||
return;
|
||||
|
||||
if (notify)
|
||||
handleNotify(policy);
|
||||
|
||||
SuLogEntry log = new SuLogEntry(policy);
|
||||
|
||||
int toUid = intent.getIntExtra("to.uid", -1);
|
||||
if (toUid < 0) return;
|
||||
int pid = intent.getIntExtra("pid", -1);
|
||||
if (pid < 0) return;
|
||||
String command = intent.getStringExtra("command");
|
||||
if (command == null) return;
|
||||
log.toUid = toUid;
|
||||
log.fromPid = pid;
|
||||
log.command = command;
|
||||
log.date = new Date();
|
||||
app.mDB.addLog(log);
|
||||
}
|
||||
|
||||
private void handleNotify(Policy policy) {
|
||||
if (policy.notification &&
|
||||
(int) Config.get(Config.Key.SU_NOTIFICATION) == Config.Value.NOTIFICATION_TOAST)
|
||||
Utils.toast(getMessage(policy), Toast.LENGTH_SHORT);
|
||||
}
|
||||
|
||||
public void handleNotify(Intent intent) {
|
||||
int fromUid = intent.getIntExtra("from.uid", -1);
|
||||
if (fromUid < 0) return;
|
||||
if (fromUid == Process.myUid()) return;
|
||||
try {
|
||||
Policy policy = new Policy(fromUid, App.self.getPackageManager());
|
||||
policy.policy = intent.getIntExtra("policy", -1);
|
||||
if (policy.policy >= 0)
|
||||
handleNotify(policy);
|
||||
} catch (PackageManager.NameNotFoundException ignored) {}
|
||||
}
|
||||
|
||||
public abstract String getMessage(Policy policy);
|
||||
}
|
||||
@@ -1,31 +1,29 @@
|
||||
package com.topjohnwu.magisk.utils;
|
||||
|
||||
import android.support.annotation.IntDef;
|
||||
|
||||
import com.topjohnwu.magisk.Data;
|
||||
import com.topjohnwu.superuser.internal.UiThreadHandler;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import androidx.annotation.IntDef;
|
||||
|
||||
public class Topic {
|
||||
|
||||
public static final int MAGISK_HIDE_DONE = 0;
|
||||
public static final int RELOAD_ACTIVITY = 1;
|
||||
public static final int MODULE_LOAD_DONE = 2;
|
||||
public static final int REPO_LOAD_DONE = 3;
|
||||
public static final int UPDATE_CHECK_DONE = 4;
|
||||
public static final int SNET_CHECK_DONE = 5;
|
||||
public static final int LOCALE_FETCH_DONE = 6;
|
||||
public static final int MODULE_LOAD_DONE = 1;
|
||||
public static final int REPO_LOAD_DONE = 2;
|
||||
public static final int UPDATE_CHECK_DONE = 3;
|
||||
public static final int LOCALE_FETCH_DONE = 4;
|
||||
|
||||
@IntDef({MAGISK_HIDE_DONE, RELOAD_ACTIVITY, MODULE_LOAD_DONE, REPO_LOAD_DONE,
|
||||
UPDATE_CHECK_DONE, SNET_CHECK_DONE, LOCALE_FETCH_DONE})
|
||||
@IntDef({MAGISK_HIDE_DONE, MODULE_LOAD_DONE, REPO_LOAD_DONE,
|
||||
UPDATE_CHECK_DONE, LOCALE_FETCH_DONE})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface TopicID {}
|
||||
|
||||
// We will not dynamically add topics, so use arrays instead of hash tables
|
||||
private static Store[] topicList = new Store[7];
|
||||
private static Store[] topicList = new Store[5];
|
||||
|
||||
public static void subscribe(Subscriber sub, @TopicID int... topics) {
|
||||
for (int topic : topics) {
|
||||
@@ -39,8 +37,7 @@ public class Topic {
|
||||
}
|
||||
|
||||
public static void subscribe(AutoSubscriber sub) {
|
||||
if (sub instanceof Subscriber)
|
||||
subscribe((Subscriber) sub, sub.getSubscribedTopics());
|
||||
subscribe(sub, sub.getSubscribedTopics());
|
||||
}
|
||||
|
||||
public static void unsubscribe(Subscriber sub, @TopicID int... topics) {
|
||||
@@ -52,8 +49,7 @@ public class Topic {
|
||||
}
|
||||
|
||||
public static void unsubscribe(AutoSubscriber sub) {
|
||||
if (sub instanceof Subscriber)
|
||||
unsubscribe((Subscriber) sub, sub.getSubscribedTopics());
|
||||
unsubscribe(sub, sub.getSubscribedTopics());
|
||||
}
|
||||
|
||||
public static void publish(@TopicID int topic, Object... results) {
|
||||
@@ -68,7 +64,7 @@ public class Topic {
|
||||
topicList[topic].published = true;
|
||||
}
|
||||
for (Subscriber sub : topicList[topic].subscribers) {
|
||||
Data.mainHandler.post(() -> sub.onPublish(topic, results));
|
||||
UiThreadHandler.run(() -> sub.onPublish(topic, results));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,6 +87,10 @@ public class Topic {
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean isPublished(AutoSubscriber sub) {
|
||||
return isPublished(sub.getSubscribedTopics());
|
||||
}
|
||||
|
||||
private static class Store {
|
||||
boolean published = false;
|
||||
Set<Subscriber> subscribers = new HashSet<>();
|
||||
@@ -101,7 +101,7 @@ public class Topic {
|
||||
void onPublish(int topic, Object[] result);
|
||||
}
|
||||
|
||||
public interface AutoSubscriber {
|
||||
public interface AutoSubscriber extends Subscriber {
|
||||
@TopicID
|
||||
int[] getSubscribedTopics();
|
||||
}
|
||||
127
app-core/src/main/java/com/topjohnwu/magisk/utils/Utils.java
Normal file
127
app-core/src/main/java/com/topjohnwu/magisk/utils/Utils.java
Normal file
@@ -0,0 +1,127 @@
|
||||
package com.topjohnwu.magisk.utils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.provider.OpenableColumns;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.topjohnwu.magisk.App;
|
||||
import com.topjohnwu.magisk.Config;
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.container.Module;
|
||||
import com.topjohnwu.magisk.container.ValueSortedMap;
|
||||
import com.topjohnwu.net.Networking;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
import com.topjohnwu.superuser.internal.UiThreadHandler;
|
||||
import com.topjohnwu.superuser.io.SuFile;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
public class Utils {
|
||||
|
||||
public static void toast(CharSequence msg, int duration) {
|
||||
UiThreadHandler.run(() -> Toast.makeText(App.self, msg, duration).show());
|
||||
}
|
||||
|
||||
public static void toast(int resId, int duration) {
|
||||
UiThreadHandler.run(() -> Toast.makeText(App.self, resId, duration).show());
|
||||
}
|
||||
|
||||
public static String dlString(String url) {
|
||||
String s = Networking.get(url).execForString().getResult();
|
||||
return s == null ? "" : s;
|
||||
}
|
||||
|
||||
public static int getPrefsInt(SharedPreferences prefs, String key, int def) {
|
||||
return Integer.parseInt(prefs.getString(key, String.valueOf(def)));
|
||||
}
|
||||
|
||||
public static int getPrefsInt(SharedPreferences prefs, String key) {
|
||||
return getPrefsInt(prefs, key, 0);
|
||||
}
|
||||
|
||||
public static String getNameFromUri(Context context, Uri uri) {
|
||||
String name = null;
|
||||
try (Cursor c = context.getContentResolver().query(uri, null, null, null, null)) {
|
||||
if (c != null) {
|
||||
int nameIndex = c.getColumnIndex(OpenableColumns.DISPLAY_NAME);
|
||||
if (nameIndex != -1) {
|
||||
c.moveToFirst();
|
||||
name = c.getString(nameIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (name == null) {
|
||||
int idx = uri.getPath().lastIndexOf('/');
|
||||
name = uri.getPath().substring(idx + 1);
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
public static int dpInPx(int dp) {
|
||||
float scale = App.self.getResources().getDisplayMetrics().density;
|
||||
return (int) (dp * scale + 0.5);
|
||||
}
|
||||
|
||||
public static String fmt(String fmt, Object... args) {
|
||||
return String.format(Locale.US, fmt, args);
|
||||
}
|
||||
|
||||
public static String getAppLabel(ApplicationInfo info, PackageManager pm) {
|
||||
try {
|
||||
if (info.labelRes > 0 && Build.VERSION.SDK_INT >= 17) {
|
||||
Resources res = pm.getResourcesForApplication(info);
|
||||
Configuration config = new Configuration();
|
||||
config.setLocale(LocaleManager.locale);
|
||||
res.updateConfiguration(config, res.getDisplayMetrics());
|
||||
return res.getString(info.labelRes);
|
||||
}
|
||||
} catch (Exception ignored) {}
|
||||
return info.loadLabel(pm).toString();
|
||||
}
|
||||
|
||||
public static String getLegalFilename(CharSequence filename) {
|
||||
return filename.toString().replace(" ", "_").replace("'", "").replace("\"", "")
|
||||
.replace("$", "").replace("`", "").replace("*", "").replace("/", "_")
|
||||
.replace("#", "").replace("@", "").replace("\\", "_");
|
||||
}
|
||||
|
||||
public static void loadModules() {
|
||||
Topic.reset(Topic.MODULE_LOAD_DONE);
|
||||
App.THREAD_POOL.execute(() -> {
|
||||
Map<String, Module> moduleMap = new ValueSortedMap<>();
|
||||
SuFile path = new SuFile(Const.MAGISK_PATH);
|
||||
SuFile[] modules = path.listFiles(
|
||||
(file, name) -> !name.equals("lost+found") && !name.equals(".core"));
|
||||
for (SuFile file : modules) {
|
||||
if (file.isFile()) continue;
|
||||
Module module = new Module(Const.MAGISK_PATH + "/" + file.getName());
|
||||
moduleMap.put(module.getId(), module);
|
||||
}
|
||||
Topic.publish(Topic.MODULE_LOAD_DONE, moduleMap);
|
||||
});
|
||||
}
|
||||
|
||||
public static boolean showSuperUser() {
|
||||
return Shell.rootAccess() && (Const.USER_ID == 0 ||
|
||||
(int) Config.get(Config.Key.SU_MULTIUSER_MODE) !=
|
||||
Config.Value.MULTIUSER_MODE_OWNER_MANAGED);
|
||||
}
|
||||
|
||||
public static Context getDEContext() {
|
||||
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N ?
|
||||
App.self.createDeviceProtectedStorageContext() : App.self;
|
||||
}
|
||||
|
||||
public static void reboot() {
|
||||
Shell.su("/system/bin/reboot" + (Config.recovery ? " recovery" : "")).submit();
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
package com.topjohnwu.magisk.utils;
|
||||
|
||||
import com.topjohnwu.signing.JarMap;
|
||||
import com.topjohnwu.signing.SignAPK;
|
||||
import com.topjohnwu.superuser.ShellUtils;
|
||||
import com.topjohnwu.superuser.io.SuFile;
|
||||
import com.topjohnwu.superuser.io.SuFileOutputStream;
|
||||
import com.topjohnwu.utils.JarMap;
|
||||
import com.topjohnwu.utils.SignAPK;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user