Compare commits

...

762 Commits
v25.2 ... v27.0

Author SHA1 Message Date
topjohnwu
2c6adbc69b Release Magisk v27.0 2024-02-02 22:54:41 -08:00
topjohnwu
5280982363 Add v27.0 changelog 2024-02-02 22:47:35 -08:00
topjohnwu
18c45ae289 Update cxx and Rust dependencies 2024-02-02 14:35:30 -08:00
LoveSy
41fbd2a7be Upgrade gradle 2024-02-02 10:55:11 -08:00
LoveSy
5e45884af4 Use Apple Silicon for CI
https://github.blog/changelog/2024-01-30-github-actions-introducing-the-new-m1-macos-runner-available-to-open-source/
2024-02-01 15:09:42 -08:00
topjohnwu
d78ee171bc Release new canary build 2024-01-30 15:59:02 -08:00
LoveSy
356ee1febd Code clean up 2024-01-30 11:07:37 -08:00
LoveSy
cc044ccc4c Fix zygisk unload 2024-01-30 11:07:37 -08:00
LoveSy
9c638cc463 Remove rust workaround 2024-01-29 15:07:21 -08:00
topjohnwu
df786eb2b6 Separate Linux and other jobs 2024-01-29 01:53:09 -08:00
topjohnwu
8e7186eebb Try out composite actions 2024-01-29 01:36:51 -08:00
topjohnwu
74b7b84561 Test all APIs on Linux 2024-01-28 00:46:03 -08:00
topjohnwu
308c9999fa Properly detect package changes 2024-01-28 00:42:43 -08:00
topjohnwu
930bb8687f Minor zygisk refactoring 2024-01-25 00:17:47 -08:00
topjohnwu
f2c4288d2d Run pthread_atfork only once
Close #7704
2024-01-25 00:17:05 -08:00
topjohnwu
b44141ae39 Run tests on Linux 2024-01-22 18:10:26 -08:00
kam821
86e0020964 Update Polish translation
- Added missing strings, fixed translation, escaped quotas.
- Some context dependent values (like target_pid) may require better translation in the future.
- Also, 'DenyList' translation, although correct, could be replaced in the future by better fitting polish equivalent of 'block list' / 'rejection list'.
2024-01-17 16:13:20 -08:00
残页
94d3daeadf Fix Sony init.real check 2024-01-17 16:09:50 -08:00
LoveSy
79334b7702 One stage zygisk loading 2024-01-11 16:19:39 -08:00
LoveSy
df66458db6 Check full path of init.rc instead of its dir
Some devices has `/system/etc/init/hw` but has no init.rc in it.
2024-01-11 16:18:57 -08:00
LoveSy
97705704e2 install or uninstall apk asynchronously 2024-01-11 16:16:36 -08:00
topjohnwu
1206179580 Update dependencies 2024-01-10 15:46:30 -08:00
topjohnwu
a0b8aa4da6 Release new canary build 2023-12-27 01:42:42 +08:00
topjohnwu
65207f96c8 Create custom cxx binding to Utf8CStr 2023-12-26 23:10:55 +08:00
Abhishek Girish
062e498bdd Update Malayalam translations 2023-12-25 18:46:08 +08:00
topjohnwu
1057cb3e3c Set serial on Rust binding 2023-12-24 04:36:58 +08:00
topjohnwu
2dd23b2518 Update system_properties 2023-12-24 04:36:58 +08:00
RafaeloxMC
8cab12998c Update strings.xml / German translation 2023-12-23 16:49:47 +08:00
topjohnwu
48b1c26dc8 Prevent race condition in wait 2023-12-23 06:33:12 +08:00
topjohnwu
f1e0bc3e4a Use platform implementation if possible 2023-12-23 06:24:20 +08:00
topjohnwu
38527cd58f Slightly change wait usage and API 2023-12-23 06:23:29 +08:00
LoveSy
e94d65b4b2 Add resetprop -w for waiting property change
It's very easy to wait for property change both in Java and C++,
but it's not the case in shell script. With this patch, developers
can now easily to wait for property change, just like what we have
in `.rc` files, and to wait for boot complete.
2023-12-23 00:12:42 +08:00
LoveSy
27ece3c7df Keep mirror shared before magic mount
This allows mounting during post-fs-data be kept after magic mount
2023-12-22 21:39:03 +08:00
LoveSy
06687abffc Fix magisk --stop by making mirror shared
Previously mirror is private and then unshared to zygote, which
makes magisk --stop cannot propagate umount mirror to zygote.
2023-12-22 21:39:03 +08:00
vvb2060
deedb462a0 Hide magisk internal mount point 2023-12-22 21:38:15 +08:00
igor
c48962bdf7 Update Portuguese translation 2023-12-22 01:31:55 +08:00
Wang Han
1ef3f6e13b Remove useless rule for prctl PR_SET_MM
* There is no use-case for it now.
2023-12-22 00:36:06 +08:00
topjohnwu
83a34a9004 Update emulator 2023-12-21 21:30:35 +08:00
topjohnwu
e30bda6c8d Rebase libsepol to AOSP main 2023-12-21 19:23:02 +08:00
vvb2060
00e9d76a5a Revert "Avoid doing any unmounts for SysUI" 2023-12-20 17:23:17 +08:00
LoveSy
6cda6c2fae Upgrade github action deps 2023-12-18 16:25:56 +08:00
VD $ VD171 @ Priv8
6dfda6dc39 Update Portuguese Translation 2023-12-18 16:24:14 +08:00
LoveSy
f41994cb52 Skip svc for ro properties
ro properties' triggers should only be triggered once, otherwise it
may undefined behaviour.
This patch avoids triggering ro properties' actions again when using
resetprop to modify them.

Co-authored-by: 5ec1cff <ewtqyqyewtqyqy@gmail.com>
2023-12-18 16:21:08 +08:00
topjohnwu
a003336497 Update system_properties for pre Android 10 2023-12-18 16:21:08 +08:00
LoveSy
401090d6fe Avoid zygiskd restarts when boot-complete 2023-12-18 16:21:08 +08:00
LoveSy
90dcc1cd30 Do not always zero initialize for rust resize vec 2023-12-18 16:21:08 +08:00
LoveSy
2ac464b186 Only compress regular file 2023-12-18 16:21:08 +08:00
LoveSy
8b7fae278b Support compressing during cpio backup 2023-12-18 16:21:08 +08:00
topjohnwu
d73c2daf6d Use special emulator to make tests less flaky 2023-12-16 15:50:53 +08:00
topjohnwu
ca25935de3 Release new canary build 2023-12-14 03:21:22 +08:00
LoveSy
d7750b7220 uiautomator dump to /data/local/tmp 2023-12-13 03:28:30 +08:00
LoveSy
98861f0b5a Clone dir attr for tmpfs in advance 2023-12-13 03:28:30 +08:00
topjohnwu
e35925d520 Properly version zygisk APIs 2023-12-13 03:27:38 +08:00
Kieron Quinn
685a2d2101 Fixes for Android 14 QPR2 B2
Added new method signatures and arguments
2023-12-13 00:16:54 +08:00
LoveSy
f7e471616d Fix clone_attr for newly created dirs 2023-12-10 23:37:47 +08:00
残页
c013a349af Update install guide
- Remove boot vbmeta patching because the checkbox is removed in b1363ee
- Remove meaningless slot argument from `fastboot flash` as it will automatically flash the active slot. Fix #7571
2023-12-10 23:37:02 +08:00
topjohnwu
61ea59a27b API 34 AOSP ATD image is released 2023-12-08 17:59:24 +08:00
VD $ VD171 @ Priv8
e55f338367 Update Portuguese Translation 2023-12-08 17:03:48 +08:00
VD $ VD171 @ Priv8
1425cf4105 Update Portuguese Translation 2023-12-08 17:03:48 +08:00
topjohnwu
b493a985b0 Update dependencies 2023-12-08 17:03:18 +08:00
canyie
1fe9ede940 Update selinux to disable validation for policydb 2023-12-08 16:50:45 +08:00
LoveSy
1fd49e4987 Make tmpfs mount of magic mount atomic
This avoid system libraries disappear temporarily during magic mount,
which causes some dynamic executables fails to run during post-fs-data.
2023-12-08 13:59:02 +08:00
LoveSy
d49b02b274 Fix zygiskd not restart when zygote restarts 2023-12-07 20:44:44 +08:00
LoveSy
d47e70cfaa Fix native symbol strips
`ndkVersion` is also needed by app for striping native symbols.
Set it in `setupCommon` instead.
2023-12-04 00:37:09 +08:00
topjohnwu
40cb031af5 Release new canary build 2023-12-04 00:30:46 +08:00
topjohnwu
1dcf325547 Minor cleanup 2023-12-03 19:32:58 +08:00
LoveSy
4e99997013 Upgrade AGP 2023-12-02 15:25:58 +08:00
LoveSy
334554697d Enable rust parallel front-end
See https://blog.rust-lang.org/2023/11/09/parallel-rustc.html
2023-12-02 15:25:41 +08:00
LoveSy
e77cbd0c15 Upgrade gradle 2023-11-30 11:49:40 +08:00
topjohnwu
46ba008b9d Disable SCCACHE_DIRECT 2023-11-30 01:55:38 +08:00
LoveSy
58aded31c2 Enable iter_intersperse 2023-11-29 23:47:51 +08:00
LoveSy
6f6b0ade06 Correct cpio's norm_path 2023-11-29 23:47:51 +08:00
topjohnwu
d9b67a207b Update ONDK 2023-11-27 17:41:11 +08:00
topjohnwu
c7083659aa Directly guard boot state with mutex 2023-11-27 17:40:58 +08:00
topjohnwu
a6d1803105 Update dependencies 2023-11-26 23:09:20 +08:00
Re*Index. (ot_inc)
66eef75673 Update strings.xml 2023-11-26 22:54:53 +08:00
Alessandro Sangiorgi
367f5f7b44 Update italian translation
Co-authored-by: Francesco Saltori <francescosaltori@gmail.com>
2023-11-26 22:54:33 +08:00
topjohnwu
0edcb03c45 Update test API levels 2023-11-26 21:41:43 +08:00
canyie
63eef153de Warn about unsupported installation methods 2023-11-17 13:58:41 -08:00
canyie
68442f38ac Misc changes
- actions: Update all actions/checkout references to v4
- magiskboot: Add missing new line to dtb help message
- docs: Update documents, fix some errors and remove outdated info
2023-11-17 13:58:41 -08:00
topjohnwu
8d5b9e5329 C++/Rust 2 way binding for MagiskD 2023-11-17 13:35:50 -08:00
topjohnwu
6c0966b795 Move some global state into Rust 2023-11-16 15:38:38 -08:00
topjohnwu
7c2e93d266 Introduce owned_fd 2023-11-16 15:38:38 -08:00
topjohnwu
1ff7b9055f Add LSPosed launch test 2023-11-16 15:38:38 -08:00
topjohnwu
49f241b77c Allow running scripts with incomplete env 2023-11-10 00:55:05 -08:00
topjohnwu
cfb20b0f86 Zygisk refactoring part 2 2023-11-09 20:55:58 -08:00
topjohnwu
6d6f14fcb3 Use bitflags 2023-11-09 14:35:49 -08:00
topjohnwu
977c981265 Make sure native bridge is restored on daemon restart 2023-11-08 17:55:25 -08:00
topjohnwu
ef48abf19d Reorganize zygisk code 2023-11-08 17:46:39 -08:00
topjohnwu
65c18f9c09 Restructure project files 2023-11-08 01:46:02 -08:00
残页
ecb31eed40 Prevent Zygisk from closing new fds created by Zygote itself 2023-11-08 00:34:38 -08:00
topjohnwu
a80cadf587 Refactor hookJniNativeMethods
Utilize NativeBridgeRuntimeCallbacks we obtained from native bridge
to directly fetch and modify registered native JNI methods.
By doing so, we do not need to keep a copy of every single
JNINativeMethod registered in order to provide JNI hooking
functionality.

Co-authored-by: LoveSy <shana@zju.edu.cn>
2023-11-07 23:57:55 -08:00
LoveSy
fce1bf2365 Obtain NativeBridgeRuntimeCallbacks for future use
NativeBridgeRuntimeCallbacks can be used for better JNI method hooking

Co-authored-by: topjohnwu <topjohnwu@gmail.com>
2023-11-07 16:56:40 -08:00
LoveSy
cbc6d40b2c Clean up codes 2023-11-07 14:25:57 -08:00
LoveSy
9fbd079560 Refactor zygisk to use native bridge to inject
Co-authored-by: vvb2060 <vvb2060@gmail.com>
Co-authored-by: topjohnwu <topjohnwu@gmail.com>
2023-11-07 14:25:57 -08:00
LoveSy
42eb928054 Inject zygisk.rc for sync --zygisk-restart 2023-11-06 15:39:48 -08:00
topjohnwu
577483fde1 Release new canary build 2023-11-05 23:49:35 -08:00
topjohnwu
aa6c7c15cc Update README 2023-11-05 23:44:07 -08:00
topjohnwu
6c807d35b2 Release Magisk v26.4 2023-11-05 23:31:15 -08:00
topjohnwu
8ca8cdae97 Add v26.4 release notes 2023-11-05 23:07:53 -08:00
topjohnwu
75e37be6f3 Do not need to check pkg in magisk_env 2023-11-05 23:02:40 -08:00
WindowsFan9600
4985314ca6 Update language "tr" on main application 2023-11-05 22:37:39 -08:00
topjohnwu
ac5ceb18c8 Guard log FIFO with SELinux 2023-11-04 23:59:11 -07:00
topjohnwu
72b39594d3 Always close logd_fd during fork 2023-11-04 02:36:14 -07:00
topjohnwu
16ae4aedf1 Remove usage of MAGISKTMP 2023-11-02 15:50:36 -07:00
topjohnwu
3ba00858e6 Allow avd_magisk on API 28 2023-11-01 09:15:17 -07:00
topjohnwu
489100c755 Fix fd sanitization 2023-11-01 02:01:29 -07:00
topjohnwu
da766f2a4e Do not go through magiskd for getting the log pipe 2023-11-01 02:01:18 -07:00
topjohnwu
c81d7ff76c Remove unnecessary RefCell usage 2023-10-31 18:22:48 -07:00
topjohnwu
a6e50d3648 Make log pipe a FIFO instead of anonymous pipe 2023-10-31 18:05:22 -07:00
topjohnwu
a177846044 Better logging in recv_fds 2023-10-31 17:40:59 -07:00
topjohnwu
19a4e11645 Make tmpfs path static strings 2023-10-29 00:47:28 -07:00
topjohnwu
67cc36268e Simplify zygisk log pipe 2023-10-26 18:13:56 -07:00
topjohnwu
28770b9a32 Support baseline profiles 2023-10-26 15:56:51 -07:00
WindowsFan9600
9f92e1bf15 [STRINGS] Improve Turkish (tr) language 2023-10-26 15:23:35 -07:00
topjohnwu
23fe5d5a19 Update build.yml 2023-10-26 14:50:42 -07:00
LoveSy
9088b584f6 Use official argh 2023-10-25 15:14:16 -07:00
vvb2060
beaf636415 Use ccache for C code 2023-10-25 15:05:41 -07:00
vvb2060
09bb2fe8dc Update dependencies 2023-10-25 14:58:02 -07:00
tzagim
1d6747d90e Update Hebrew translation 2023-10-24 21:06:15 -07:00
南宫雪珊
efadd94de3 Update strings.xml 2023-10-24 21:02:32 -07:00
vvb2060
8c0b4e444a Update zh-rCN translation 2023-10-24 21:02:32 -07:00
Rom
32c7106e40 Update French translation 2023-10-24 21:01:53 -07:00
topjohnwu
d2f2a9e4c8 Make avd_test less flaky 2023-10-24 16:45:24 -07:00
topjohnwu
985454afd4 Better logging 2023-10-24 16:41:49 -07:00
topjohnwu
9e1322de25 Make sure the shared preference is committed 2023-10-24 16:41:38 -07:00
topjohnwu
4e4ec73d94 Make gradle.properties optional 2023-10-19 15:44:34 -07:00
topjohnwu
bb39a524d0 Switch to default images for faster boot time 2023-10-19 05:31:03 -07:00
topjohnwu
196d9af099 Add application and Zygisk tests to avd_test.sh 2023-10-19 05:15:53 -07:00
topjohnwu
1eeb2a34a1 Don't support alternative binary paths
The Magisk app will guide users through repair setup
2023-10-19 05:11:43 -07:00
Arbri çoçka
cf43c56218 Update strings.xml sq 2023-10-18 14:29:55 -07:00
kubalav
e6c1aec443 Update Slovak translation 2023-10-18 14:29:38 -07:00
topjohnwu
43fd1c4c1b Update stub version 2023-10-17 19:22:53 -07:00
topjohnwu
022caca979 Release new canary build 2023-10-17 19:13:16 -07:00
topjohnwu
0352ea2cca Rename biometrics to user authentication 2023-10-17 18:43:27 -07:00
topjohnwu
e483d6befe Do not go through a fragment for auth 2023-10-17 17:39:31 -07:00
vvb2060
678c07fff5 suBiometric: remove biometric
use device credential to support more devices and second user
2023-10-17 17:39:05 -07:00
topjohnwu
91c92051f1 Simplify C++ SELinux routines 2023-10-17 16:04:59 -07:00
topjohnwu
4b8a0388e7 Make SELinux support a feature 2023-10-17 13:29:15 -07:00
topjohnwu
66788dc58c Cleanup SELinux support 2023-10-16 17:38:44 -07:00
topjohnwu
dd8c28b1cb Upgrade AGP 2023-10-16 17:25:57 -07:00
残页
32c5153e8e Increase boot timeout to 600s 2023-10-16 01:20:25 -07:00
topjohnwu
36de62873a Fix error logging on the C++ side 2023-10-13 16:59:54 -07:00
topjohnwu
51e37880c6 Add repr(transparent) to guarantee soundness 2023-10-12 18:59:16 -07:00
topjohnwu
4b83c1e76c Cleanup messy error messages 2023-10-12 18:54:09 -07:00
topjohnwu
b0b04690d5 Use newer bash version for avd_test.sh 2023-10-12 00:45:53 -07:00
topjohnwu
6d1e8d86cb Cleaner cstr code 2023-10-11 23:53:55 -07:00
topjohnwu
eda8c70a80 Borrow value instead of moving in FsPath::from()
When accepting a value of AsRef<Utf8CStr> in FsPath::from(), the
existing code will move a value of Utf8CStrBufArr, creating a reference
that lives longer than the borrowing value, causing undefined behavior.

The issue is only visible on release builds, as more advanced
optimizations will be more aggressive re-using the stack of variables
that no longer lives.

Fix #7408
2023-10-11 23:48:54 -07:00
topjohnwu
587b6cfd41 Update avd_test.sh 2023-10-11 22:42:45 -07:00
topjohnwu
e774408782 Allow AVD hacks on release builds 2023-10-11 14:17:31 -07:00
canyie
187f583c95 Fix $RECOVERYMODE from config being incorrectly overridden
Move legacy SAR checking logic into mount_partitions, and avoid calling get_flags before check_boot_ramdisk
Fix #7346
2023-10-10 15:53:18 -07:00
topjohnwu
f5d3a71478 Update ONDK to r26.1 2023-10-10 15:52:59 -07:00
残页
d868ff3080 AVD test release builds as well 2023-10-10 15:52:41 -07:00
nkh0472
f80198a669 typo fix 2023-10-09 17:22:48 -07:00
topjohnwu
6076b52c48 Update libcxx 2023-10-03 17:22:25 -07:00
topjohnwu
79a1c39b30 Simplify fd sanitization 2023-09-28 20:38:16 -07:00
topjohnwu
5c92d39498 Enable Zygisk by default in emulators
Make sure CI tests Zygisk
2023-09-28 20:25:26 -07:00
topjohnwu
6e7a995716 Introduce UtfCString 2023-09-27 15:21:24 -07:00
topjohnwu
a55d570213 Move more I/O operations into Rust 2023-09-27 02:28:43 -07:00
topjohnwu
5d07d0b964 Do not support systems without SELinux 2023-09-27 02:28:43 -07:00
Wang Han
ec115cd7e3 Don't skip fd sanitization if fds_to_ignore does not exist 2023-09-25 09:45:03 -07:00
osm0sis
9b3896fd3d Retain PREINITDEVICE during A-only addon.d 2023-09-23 23:51:36 -07:00
topjohnwu
a3f5918d25 Fix bug in libsepol
Fix #7308
2023-09-23 22:34:51 -07:00
topjohnwu
b28326198c Use crates for cpio code 2023-09-22 01:39:21 -07:00
topjohnwu
46275b90c2 Generalize unxz 2023-09-21 05:47:21 -07:00
topjohnwu
15e13a8d8b Organize logging code 2023-09-19 03:02:30 -07:00
topjohnwu
b750c89c87 Address clippy warnings 2023-09-19 01:11:50 -07:00
LoveSy
8d7c7c3dfb Refactor dtb in rust 2023-09-19 00:41:42 -07:00
topjohnwu
8e1a91509c Remove readlink_unsafe 2023-09-19 00:06:21 -07:00
LoveSy
927cd571f8 Fix read_cert crash when receive fd = -1 2023-09-18 22:32:32 -07:00
LoveSy
5fbd3e5c65 Fix buf len update of read_link to Utf8CStrBuf 2023-09-18 22:31:12 -07:00
LoveSy
877aeb66cb Upgrade to Gradle 8.3 2023-09-14 13:16:59 -07:00
topjohnwu
8a88d8465a Prevent OOM
Fix #7341
2023-09-14 13:14:30 -07:00
topjohnwu
dda8cc85c9 Use bytemuck 2023-09-14 13:10:09 -07:00
topjohnwu
6a59939d9a Remove for_all_file 2023-09-13 18:09:16 -07:00
topjohnwu
4745e86c1b Fix #7301 2023-09-13 14:44:20 -07:00
topjohnwu
9aa466c773 Fix genfscon and filename_trans
Fix #7329
2023-09-12 21:31:31 -07:00
LoveSy
0243610c09 No trailing zeros if the signed boot img is larger 2023-09-12 18:09:20 -07:00
topjohnwu
0a2a590ab7 Use Utf8CStr for logging 2023-09-12 17:35:20 -07:00
topjohnwu
89aee6ffa7 Add more to the Utf8CStr family
Better C strings with path operations
2023-09-12 17:35:01 -07:00
topjohnwu
4eaf701cb7 Address clippy warnings 2023-09-06 21:45:12 -07:00
topjohnwu
4fff2aa7d8 Fix proto read and write 2023-09-06 20:45:59 -07:00
topjohnwu
35b3c8ba5c Cleanup persist props code 2023-09-06 15:52:14 -07:00
topjohnwu
1d7cff7703 Update Cargo dependencies 2023-09-06 13:57:43 -07:00
LoveSy
8d81bd0e33 resetprop: replace nanopb with quick-protobuf for persist 2023-09-05 22:20:57 -07:00
topjohnwu
7826d7527f Release new canary build 2023-09-04 00:35:17 -07:00
topjohnwu
d4e552d08b Update README 2023-09-04 00:26:48 -07:00
topjohnwu
5a16418543 Release Magisk v26.3 2023-09-04 00:01:46 -07:00
topjohnwu
7297aba15a Add v26.3 release notes 2023-09-03 23:54:45 -07:00
Ylarod
bc5d5f9502 Update details.md
The `MAGISKTMP` changed to `/debug_ramdisk` since https://github.com/topjohnwu/Magisk/pull/6931
2023-09-03 23:46:25 -07:00
vvb2060
1761986c1b Update zh-rCN translation 2023-09-03 23:43:07 -07:00
topjohnwu
1e034e3e0e Update libsu 2023-09-03 23:41:17 -07:00
topjohnwu
bbf9756bfa Release new canary build 2023-09-02 06:20:10 -07:00
topjohnwu
96e559fb0e Skip cargo build if possible 2023-09-02 06:03:39 -07:00
topjohnwu
4c45775131 Update BusyBox 2023-09-02 05:45:18 -07:00
LoveSy
c072b4254d Wrap rename and renameat 2023-09-02 04:24:24 -07:00
topjohnwu
2dbb812126 Disable stack protector on x86 static executables
Close #7274
2023-09-01 23:04:19 -07:00
topjohnwu
be50f17f55 Update to ONDK r26.0
Close #7264
2023-09-01 23:01:49 -07:00
残页
6f77f190f2 Fix processPayload 2023-08-31 20:30:10 -07:00
topjohnwu
6bdc57cbe4 Release new canary build 2023-08-30 01:22:02 -07:00
残页
de00f1d5a9 Always check mounts to detect legacy SAR on bootmode 2023-08-30 01:02:19 -07:00
残页
e9b9bf987b Fix syntax error in util_functions.sh 2023-08-29 15:33:03 -07:00
topjohnwu
f4b6385f9f Disable boot vbmeta patch when found vbmeta.img 2023-08-28 22:54:55 -07:00
topjohnwu
75d905a56d Fix device detection scripts and logic 2023-08-28 22:13:36 -07:00
topjohnwu
b1363ee479 Do not allow user to configure boot vbmeta patching 2023-08-28 22:13:36 -07:00
topjohnwu
51afe43a30 Cleanup util_functions 2023-08-28 22:13:36 -07:00
残页
189c03c047 Add canyie to developer list 2023-08-28 13:21:58 -07:00
topjohnwu
ae9d270a32 Release new canary build 2023-08-28 01:25:07 -07:00
topjohnwu
e47e869f6b Update full changelog 2023-08-28 01:15:14 -07:00
topjohnwu
c39038a439 Update README 2023-08-28 01:13:25 -07:00
topjohnwu
69174e2c13 Release Magisk v26.2 2023-08-28 01:04:28 -07:00
Chris Renshaw
474268a0af manager.sh: add ro.boot.vbmeta.size + ro.product.ab_ota_partitions to vbmeta check
ro.boot.vbmeta.device doesn't seem to be in use on all A/B devices
2023-08-28 00:45:14 -07:00
topjohnwu
eadb0307fa Add v26.2 release notes 2023-08-27 23:48:49 -07:00
topjohnwu
5a5d0d5d72 Add missing permissions 2023-08-27 23:29:34 -07:00
topjohnwu
a1273bc467 Update dependencies 2023-08-27 22:44:51 -07:00
topjohnwu
0c28a916be Use cxx_name 2023-08-24 00:50:38 -07:00
BlackMesa123
0ba573b789 Additional Samsung devices install guide refactoring
Signed-off-by: BlackMesa123 <giangrecosalvo9@gmail.com>
2023-08-18 17:24:25 -07:00
BlackMesa123
ec42ee152c Refactor Samsung devices install guide
Signed-off-by: BlackMesa123 <giangrecosalvo9@gmail.com>
2023-08-18 17:24:25 -07:00
I3elphegor
abcb487361 Update czech translation of strings.xml
Added and translated new strings.
2023-08-18 17:19:36 -07:00
vvb2060
d12d9e82f1 Force kernel to load rootfs only for legacy SAR devices 2023-08-18 17:18:34 -07:00
topjohnwu
275208e81b Update Rust dependencies 2023-08-17 21:24:29 -07:00
topjohnwu
41226c12b8 Update to ONDK r25.7 2023-08-15 17:10:20 -07:00
topjohnwu
f86c66c99d Officially support API 34 2023-08-11 09:46:45 -07:00
topjohnwu
93876b5fd3 Update dependencies, AGP, and SDK level 2023-08-11 09:38:59 -07:00
topjohnwu
b5b14ce343 Use cxx_name instead of rust_name 2023-08-10 21:22:53 -07:00
topjohnwu
350d0d600c Update build script 2023-08-08 01:05:32 -07:00
topjohnwu
f924ffcbf3 Merge files 2023-08-08 00:57:58 -07:00
VD $ VD171 @ Priv8
0f5963f231 Update PORTUGUESE Translation 2023-08-08 00:54:01 -07:00
Arbri çoçka
1961ff2c40 Update strings.xml Albania 2023-08-08 00:53:38 -07:00
vvb2060
40003691d6 manager.sh: check vbmeta device by getprop 2023-08-08 00:53:21 -07:00
topjohnwu
8290358241 Release new canary build 2023-08-05 23:27:06 -07:00
kubalav
ee34f775c3 Update Slovak translation 2023-08-05 23:19:34 -07:00
vvb2060
feb47cd88c sulog: add migration 2023-08-02 21:18:05 -07:00
vvb2060
c6efb51f61 sulog: add more info 2023-08-02 21:18:05 -07:00
Hen_Ry
a5acf33ccd Update De translation 2023-08-02 21:17:12 -07:00
vvb2060
ab9ee449e4 suBiometric: fix open app on second user will auto disable biometric
second user does not support biometric, but the config of app ignores user
2023-08-02 21:16:06 -07:00
vvb2060
9571b6f9be SuperuserViewModel: fix updatePolicy
Starting biometrics may cause the SuperuserFragment to lost focus. After onResume(), doLoadWork() will refresh the itemsPolicies, so notify property changed will work on wrong items. Fixed by snapshotting items to be refreshed before starting biometrics.
2023-08-02 09:29:14 -07:00
vvb2060
207d7fd3f6 SuRequestViewModel: fix await RootService on the main thread 2023-08-02 09:21:25 -07:00
南宫雪珊
bcdcfa1104 Update scripts/avd_magisk.sh 2023-08-02 09:12:00 -07:00
vvb2060
e0a4230dac avd_magisk: hide stderr 2023-08-02 09:12:00 -07:00
topjohnwu
17ba5cba3e Print permissive rules 2023-08-02 09:11:22 -07:00
topjohnwu
f2e109ad7d Update libselinux and libsepol 2023-08-01 18:07:53 -07:00
topjohnwu
c83e141a1c Support dumping sepolicy rules 2023-08-01 18:03:54 -07:00
topjohnwu
6089cc36de Update xperm parsing 2023-07-31 09:28:27 -07:00
topjohnwu
9638dc0a66 Fix perror 2023-07-25 21:03:04 -07:00
Andrew Gunnerson
b191a14a23 magiskpolicy: Fix old xperms being cleared when adding new xperms
This commit updates sepol_impl::add_xperm_rule() so that it loads the
current xperm bits from the existing avtab entry before setting or
clearing xperm bits. This fixes new allowxperm rules causing old xperm
rules within the same xperm specified/driver to be removed.

Fixes: #7176

Signed-off-by: Andrew Gunnerson <accounts+github@chiller3.com>
2023-07-24 23:52:28 -07:00
topjohnwu
cf1bc82537 Random small refactoring 2023-07-24 23:49:20 -07:00
残页
6141bb5bb3 Fix MagiskInstaller.patchBoot() error catching 2023-07-21 12:05:33 -07:00
topjohnwu
4d2b62da0d Do not override global variables in document 2023-07-21 12:04:42 -07:00
topjohnwu
39383229d1 Update dependencies
Close #7128
2023-07-20 18:35:53 -07:00
topjohnwu
08bfbb154a Release new canary build 2023-07-17 23:20:07 -07:00
残页
d390ca2fdf Avoid using IconCompat.createFromIcon() that doesn't support bitmap icon 2023-07-17 21:46:47 -07:00
topjohnwu
7ad77a14ae Remove unused line 2023-07-17 21:43:09 -07:00
topjohnwu
f33343b4e6 Remove unused code and logic 2023-07-17 18:58:48 -07:00
topjohnwu
af65d07456 Support AVB1.0 signing and verification in magiskboot 2023-07-17 18:57:50 -07:00
topjohnwu
16d728f379 Partially document global variables in scripts 2023-07-17 16:07:16 -07:00
topjohnwu
c97ab690b6 Segment memory mapped boot image region 2023-07-13 21:01:49 -07:00
topjohnwu
4caed73fe0 Always include boot image when processing tar
Credits: @BlackMesa123

Fix #7132, close #7133
2023-07-09 02:04:42 -07:00
BlackMesa123
4856da1584 Ignore userdata.img in Samsung tar firmware packages
Signed-off-by: BlackMesa123 <giangrecosalvo9@gmail.com>
2023-07-06 17:37:12 -07:00
LoveSy
0a07018fec No need to use submodule for argh 2023-07-06 15:28:39 -07:00
LoveSy
64c82e1f2c Refine cpio argh
we can use argh to handle `--help` now
2023-07-06 15:07:06 -07:00
topjohnwu
e8e8afa6c2 Properly handle visibility 2023-07-06 11:12:27 -07:00
LoveSy
af2207433d Fix error logging
ok_or will unconditionally creates a LoggedResult, which prints
an error even it successes. Use ok_or_else which lazily creates
a LoggedResult only if it fails.
2023-07-06 11:01:57 -07:00
LoveSy
75ba62d588 Fix stub resource loading on Android 9, 10
They can only load resources from zip files

Co-authored-by: canyie <a1364259@163.com>
Co-authored-by: 南宫雪珊 <vvb2060@gmail.com>
2023-07-06 04:53:26 -07:00
LoveSy
606d97ae4d Trace location from LoggedError
Co-authored-by: topjohnwu <topjohnwu@gmail.com>
2023-07-05 18:55:23 -07:00
topjohnwu
d778b0b0a7 Custom help message when using argh
Help messages generated from argh is nearly useless and very hard to
customize. Fork argh and disable all code for generating help messages.

Use a closure to print the help message when handling EarlyExit.
2023-07-05 17:05:39 -07:00
topjohnwu
5ee6daf126 Handle cpio commands properly 2023-07-03 21:57:28 -07:00
Fs00
43b9a09c9b Update Italian app strings 2023-06-30 15:57:56 -07:00
Fs00
8475a2bb94 Update Italian stub strings 2023-06-30 15:57:56 -07:00
Rom
d8692de2f4 Update French translation 2023-06-30 15:57:37 -07:00
LoveSy
33a9abc946 Fix backup fails when ramdisk does not exist 2023-06-30 15:57:09 -07:00
topjohnwu
ee943afbc9 Cleanup SHA hash implementation 2023-06-30 15:50:52 -07:00
LoveSy
1f7c3e9f14 Use rust to calculate sha 2023-06-30 14:06:02 -07:00
topjohnwu
46770db18b Rename stuffs 2023-06-30 03:03:51 -07:00
vvb2060
92f980601c Fix close 2023-06-30 02:43:38 -07:00
vvb2060
d0b8c16651 Fix file permission 2023-06-30 02:43:38 -07:00
LoveSy
a470ee6f93 Fix mmap block device 2023-06-30 01:06:51 -07:00
vvb2060
ff1c56683d Skip magisk32 for 64bit only avd 2023-06-29 20:45:51 -07:00
topjohnwu
4ee4cbada6 Standardize logging and error handling
- Introduce new types: LoggedResult and LoggedError
- Introduce new extension methods to log and add additional msgs
- Never exit when logging error messages in Rust (all errors should be
  handled by using Result and Rust's error propagation)
- Remove all usages of anyhow as it doesn't fit Magisk's use cases
2023-06-29 17:14:53 -07:00
topjohnwu
dbc2236dd2 Release new canary build 2023-06-23 02:39:07 -07:00
topjohnwu
a8c4a33e91 Avoid using trait object 2023-06-23 02:32:29 -07:00
topjohnwu
279f955a84 Minor changes 2023-06-23 01:50:33 -07:00
topjohnwu
fbd1dbb20c Manage MenuProvider with lifecycle state 2023-06-22 16:12:35 -07:00
topjohnwu
6c09fc2e64 Move addMenuProvider into onStart 2023-06-22 15:47:12 -07:00
LoveSy
f3304b482c Fix sulog prompt always shows 2023-06-22 15:27:34 -07:00
LoveSy
0a85ef61c3 Call removeMenuProvider on Fragment::onStop 2023-06-22 15:27:23 -07:00
topjohnwu
dc26ad7125 Address clippy warnings 2023-06-22 02:36:31 -07:00
LoveSy
24b1c607f3 Replace clap with argh 2023-06-22 02:36:31 -07:00
topjohnwu
732a161b67 Minor cleanup 2023-06-22 02:23:27 -07:00
topjohnwu
9c7cf340a1 Move pattern matching to Rust 2023-06-21 16:47:20 -07:00
topjohnwu
399b9e5eba Move hexpatch to Rust 2023-06-20 18:17:26 -07:00
topjohnwu
5805573625 Update clean operation 2023-06-20 14:50:02 -07:00
topjohnwu
a6b1149b9f Minor cleanup 2023-06-20 14:36:07 -07:00
LoveSy
51e985ae7f Use quick-protobuf 2023-06-20 14:36:07 -07:00
vvb2060
9929b25339 Move su request path to magisk tmp 2023-06-20 03:29:06 -07:00
topjohnwu
2359cfc480 Small refactor 2023-06-20 00:21:51 -07:00
topjohnwu
81493475f9 Directly use rust::Vec 2023-06-20 00:21:51 -07:00
Arbri çoçka
0493829231 Update strings.xml sq 2023-06-16 14:15:31 -07:00
VD $ VD171 @ Priv8
e2d1952ad9 Update PORTUGUESE Translation 2023-06-16 14:14:46 -07:00
LoveSy
7450965458 Update Chinese translation
Co-authored-by: 南宫雪珊 <vvb2060@gmail.com>
2023-06-16 14:13:46 -07:00
Vladimír Kubala
f45384685b Update Slovak translation 2023-06-16 14:13:01 -07:00
topjohnwu
8abcccc262 Fix typo 2023-06-16 01:49:44 -07:00
LoveSy
a9c89cbbbb Read certificate in Rust
Co-authored-by: topjohnwu <topjohnwu@gmail.com>
2023-06-16 01:49:44 -07:00
topjohnwu
d2eaa6e6c1 Fix scripts on Windows 2023-06-15 05:57:19 -07:00
LoveSy
53257b6ea1 Fix find_apk_path 2023-06-15 04:09:45 -07:00
LoveSy
c874391be4 Box CpioEntry 2023-06-15 04:09:17 -07:00
LoveSy
7e8e013832 Fix two typo 2023-06-15 04:09:17 -07:00
topjohnwu
037f46f7f0 Fix copy_cstr 2023-06-15 04:00:32 -07:00
topjohnwu
d3e1c496ca Upgrade ONDK to r25.6 2023-06-15 01:26:54 -07:00
topjohnwu
d7d0a44693 Remove randomness from Magisk 2023-06-14 17:05:49 -07:00
topjohnwu
9d6f6764cb Use Metadata instead of direct stat syscall 2023-06-12 14:58:13 -07:00
topjohnwu
cb3ab63815 Replace all CStr usage to Utf8CStr 2023-06-12 13:57:15 -07:00
topjohnwu
caae932117 Remove unnecessary lifetime markers 2023-06-12 13:56:20 -07:00
LoveSy
e9cf27eb5a Fix map_file 2023-06-12 13:55:58 -07:00
LoveSy
6ee6685f4c AVD test on API 34 2023-06-12 03:23:27 -07:00
LoveSy
d15017b777 Add arg requirement for cpio extract 2023-06-12 02:40:50 -07:00
LoveSy
a9387e63e1 Fix Utf8CStr::as_ref() -> OsStr 2023-06-12 02:40:50 -07:00
topjohnwu
23c1f0111b Improve Rust implementation
- Move mmap_file implementation into Rust
- Introduce Utf8CStr as the better c-string type to use
2023-06-12 02:40:50 -07:00
LoveSy
866386e21f Use to_string instead of to_owned 2023-06-12 02:40:50 -07:00
LoveSy
bf10496fa9 Add log for restore 2023-06-12 02:40:50 -07:00
LoveSy
607e6547a7 No check rm -r 2023-06-12 02:40:50 -07:00
topjohnwu
6b21091fe2 Fix compile errors and cleanup 2023-06-12 02:40:50 -07:00
topjohnwu
e58f98e844 Update cargo files 2023-06-12 02:40:50 -07:00
LoveSy
b8cb9cd84d Refactor magiskboot cpio 2023-06-12 02:40:50 -07:00
LoveSy
c1038ac6f9 Remove permissve update_engine 2023-06-10 13:17:37 -07:00
LoveSy
c556dd0aac Increase sccache hit rate 2023-06-10 13:17:16 -07:00
LoveSy
d2fbcd07b7 Use sccache on non CI env 2023-06-10 13:14:12 -07:00
LoveSy
bf6359abaa Fix release build 2023-06-10 13:10:54 -07:00
topjohnwu
d1621845b8 Fix typo 2023-06-10 01:50:18 -07:00
topjohnwu
f33f1d25d0 Move find_apk_path to Rust 2023-06-10 01:40:45 -07:00
topjohnwu
40f25f4d56 Introduce directory traversal 2023-06-09 02:00:37 -07:00
topjohnwu
e13775ec2c Directly use memmem in contains 2023-06-07 16:52:52 -07:00
topjohnwu
ee4dad7a13 Bridge C++ bytes with Rust &[u8] 2023-06-07 16:49:40 -07:00
topjohnwu
5e2ef1b7f4 Better bytes support in C++ 2023-06-06 17:11:42 -07:00
topjohnwu
f8c38eab74 Proper Windows support 2023-06-05 02:27:02 -07:00
topjohnwu
305e8b3d14 Improve bootimg const correctness 2023-06-03 05:10:22 -07:00
topjohnwu
2a654e5d7f Improve byte_data const correctness 2023-06-03 03:16:03 -07:00
topjohnwu
57afae3425 Cleanup cpio codebase 2023-06-03 00:31:20 -07:00
topjohnwu
feb44f875e Migrate PREINITDEVICE in recovery mode
Close #6917
2023-06-02 16:49:04 -07:00
topjohnwu
7eebe62bb6 Do not realpath ANDROID_SDK_ROOT 2023-06-02 15:36:45 -07:00
topjohnwu
9ea9f01933 Resolve clippy errors and warnings 2023-05-31 01:08:33 -07:00
topjohnwu
665c6bdc4b Provide easy access to the cargo command 2023-05-31 01:08:33 -07:00
topjohnwu
c79bc83275 Update dependencies 2023-05-30 01:32:43 -07:00
topjohnwu
c30fbdf145 Simplify logging code 2023-05-29 01:27:40 -07:00
topjohnwu
f12951bd1d Fix typo 2023-05-29 00:30:55 -07:00
nikk gitanes
52f2e8c4a0 allow fast switch access with d-pad on superuser tab 2023-05-28 23:51:37 -07:00
nikk gitanes
1b2af1ed6d correlate nextFocusRight 2023-05-28 23:51:18 -07:00
nikk gitanes
0f9b2a7df8 make module card clickable and highlight when focused 2023-05-28 23:51:18 -07:00
topjohnwu
f2846694e1 Cleanup some code 2023-05-28 23:50:52 -07:00
topjohnwu
e668dbf6f7 Update AGP 2023-05-28 17:57:53 -07:00
topjohnwu
d77a368176 Move dependency version into workspace 2023-05-28 17:30:33 -07:00
topjohnwu
ad0da08610 Update native clean operation 2023-05-28 17:30:20 -07:00
topjohnwu
0c52385ad4 Update to use ONDK r25.4 2023-05-27 01:57:02 -07:00
topjohnwu
5b8b48ccc1 Properly support streamable input 2023-05-26 14:07:11 -07:00
topjohnwu
659b9c6fee Support extracting any partition from payload.bin 2023-05-26 13:36:47 -07:00
LoveSy
ec31cab5a7 Add zip and payload.bin support to Magisk app 2023-05-26 13:36:47 -07:00
vvb2060
dd93556ad8 Faster get magisk tmpfs path 2023-05-25 01:03:27 -07:00
topjohnwu
533aeadd38 Update cstr macro 2023-05-25 01:03:04 -07:00
topjohnwu
18d0cedbe2 Parse rule files with Rust 2023-05-24 19:11:56 -07:00
topjohnwu
5a94ef9106 Fix init rust code setup 2023-05-23 21:50:13 -07:00
topjohnwu
8e8f01f8b5 Move project common code into include 2023-05-23 21:30:30 -07:00
topjohnwu
7087badf87 Release new canary build 2023-05-23 21:02:33 -07:00
topjohnwu
47d2d4e3a5 Update su cmdline parsing 2023-05-23 20:51:23 -07:00
topjohnwu
6c47d8f556 Support 32 bit only AVD patch
Close #7010
2023-05-23 18:12:06 -07:00
topjohnwu
8c9d0314fb Use sccache for all native builds in CI 2023-05-23 17:52:10 -07:00
topjohnwu
69144942e3 Fix fortify
Close #7009, fix #7003
2023-05-23 16:31:24 -07:00
topjohnwu
5627053b74 Move su folder into core 2023-05-23 01:36:25 -07:00
topjohnwu
0f666de5e6 Organize headers 2023-05-22 21:36:15 -07:00
LoveSy
eddc862fa3 Use POSIX format 2023-05-22 18:14:59 -07:00
LoveSy
4327682120 Add mnt ns attach support for su 2023-05-22 18:14:59 -07:00
LoveSy
af5bdee78f Reimplement su -z 2023-05-22 18:14:59 -07:00
LoveSy
0e36e86dbf Support settings gids of su 2023-05-22 18:14:59 -07:00
LoveSy
f95478f1f1 Truncate file only if needed 2023-05-22 00:51:42 -07:00
topjohnwu
9fe8741a02 Export get_prop to Rust 2023-05-21 23:51:30 -07:00
topjohnwu
a5768e02ea Cleanup byte_channel implementation 2023-05-20 14:19:40 -07:00
topjohnwu
f5aaff2b1e Cleanup filter_out_stream implementation 2023-05-20 01:28:10 -07:00
topjohnwu
655f778171 Better cxx binding codegen 2023-05-19 15:59:40 -07:00
topjohnwu
2e77a426b2 Fix build script 2023-05-19 15:32:14 -07:00
topjohnwu
2bcf2e76f1 Generate cxx binding in build.rs 2023-05-19 15:16:54 -07:00
topjohnwu
57bd450798 Split input and output streams 2023-05-19 04:19:43 -07:00
topjohnwu
582cad1b8d Cleanup libc hacks 2023-05-19 03:23:43 -07:00
topjohnwu
6ca2a3d841 Update libsystemproperties 2023-05-19 03:22:50 -07:00
topjohnwu
91773c3311 Support only read properties from storage 2023-05-19 01:53:40 -07:00
topjohnwu
dc61033b2c Support persist props bypassing property_service 2023-05-18 23:36:46 -07:00
topjohnwu
f8d62a4b6c Move resetprop under core 2023-05-18 22:15:49 -07:00
topjohnwu
1d2145b1b7 Improve argument parsing and help message 2023-05-18 21:54:54 -07:00
topjohnwu
1f7f84b74a Remove unnecessary class 2023-05-18 20:38:33 -07:00
topjohnwu
cd7a335d0f Cleanup implementation 2023-05-18 20:26:20 -07:00
topjohnwu
17569005a4 Remove sysprop fallback
The library now supports mapping as ro
2023-05-18 15:47:50 -07:00
topjohnwu
f36b21bae5 Support get property context
Co-authored-by: canyie <a1364259@163.com>
Co-authored-by: vvb2060 <vvb2060@gmail.com>
2023-05-18 14:46:36 -07:00
topjohnwu
fe1ca52f6d Simplify prop_cb 2023-05-16 02:41:39 -07:00
topjohnwu
1be647a279 Put all FFI into same module 2023-05-16 02:41:39 -07:00
topjohnwu
2ea1a47bec Fix color printing 2023-05-16 02:41:39 -07:00
Ernest
2d799dae0d Update app/src/main/res/values-lt/strings.xml
Co-authored-by: LoveSy <631499712@qq.com>
2023-05-14 23:54:57 -07:00
Ernest
c6408babac Update strings.xml
Updated all strings.
2023-05-14 23:54:57 -07:00
topjohnwu
a8c1ed8795 Update development docs 2023-05-13 02:38:03 -07:00
topjohnwu
e3cb5f8ddd Support setting ANDROID_STUDIO 2023-05-13 02:38:03 -07:00
topjohnwu
e160e211dd Format build.py with black 2023-05-13 02:38:03 -07:00
topjohnwu
22d05ca399 Update time handling code 2023-05-13 02:38:03 -07:00
vvb2060
bd2651057d Simplify prefs migration 2023-05-11 16:29:01 -07:00
topjohnwu
1610092ec4 Increase wait timeout 2023-05-10 16:13:45 -07:00
LoveSy
b9e6937996 Make magisk node ro as well 2023-05-10 00:13:18 -07:00
topjohnwu
a207f03952 Run tests in parallel 2023-05-10 00:10:19 -07:00
topjohnwu
851153dd7c Fix avd_test.sh 2023-05-09 23:11:11 -07:00
topjohnwu
583ffc8177 Reduce cpp logging overhead 2023-05-09 19:14:08 -07:00
topjohnwu
7518092ad2 Implement logging purely in Rust 2023-05-09 18:54:38 -07:00
topjohnwu
8c2ad3883a Update avd_magisk.sh 2023-05-09 17:33:20 -07:00
topjohnwu
d364554425 Remove unused code 2023-05-06 01:48:47 -07:00
vvb2060
726ffdcd98 Fix meizu rootfs type 2023-05-06 00:06:59 -07:00
vvb2060
f9d22cf8ee New magisk tmp dir: /debug_ramdisk
Co-authored-by: LoveSy <shana@zju.edu.cn>
2023-05-06 00:04:11 -07:00
vvb2060
ee50da566f Cancel recursive bind 2023-05-06 00:04:11 -07:00
vvb2060
9f7d410959 Use pathname local socket 2023-05-06 00:04:11 -07:00
vvb2060
bc94ea4334 Update SELinux policy 2023-05-06 00:04:11 -07:00
topjohnwu
c0c9204848 Add ResultExt 2023-05-05 23:57:34 -07:00
topjohnwu
c0d1bf63bc Clean up logging on C++ side 2023-05-05 01:14:56 -07:00
StoyanDimitrov
bbda0cdffe Update strings.xml 2023-05-05 00:39:19 -07:00
topjohnwu
7b5ff99cd1 Reorganize code 2023-05-04 21:37:08 -07:00
topjohnwu
21ddb26db8 Perform proto codegen in build script 2023-05-04 21:37:08 -07:00
LoveSy
7bf2e3875f Support extract boot image from payload.bin 2023-05-04 21:37:08 -07:00
topjohnwu
b136aba1e2 Implement magiskinit logging in Rust 2023-05-02 16:49:43 -07:00
topjohnwu
0d84f80b3c Update AGP 2023-05-02 16:28:14 -07:00
topjohnwu
af45aeb771 Extract busybox from APK for AVD 2023-05-02 16:28:02 -07:00
topjohnwu
1c5a435e1f Update cxx-rs 2023-05-01 14:53:07 -07:00
Soo-Hwan Na
0ea1257dcd Update Korean translation 2023-05-01 12:08:12 -07:00
Mohamadreza Nakhleh
4c92677b5a (translate)
update some Persian (Farsi,فارسی) translations and add more Persian equivalent
2023-05-01 12:07:50 -07:00
fadlyas07
979260bd62 app: l10n: Update Indonesian translations
* Added new strings based on the latest source.
* Rephrase "bisa" (informal) to "dapat" (formal).

Change-Id: I7c29951adff7e5dc086e6044571ff4cdb6366966
2023-05-01 12:07:21 -07:00
topjohnwu
f7de649a36 Update ODNK requirement to r25.3 2023-04-29 15:12:04 -07:00
topjohnwu
0cf0d2b821 Move avd_hack boolean out of init class 2023-04-25 23:34:45 -07:00
vvb2060
3733c9a091 CI: add avd test 2023-04-25 23:00:59 -07:00
vvb2060
e9f32e4f68 Set text and background color 2023-04-25 23:00:59 -07:00
vvb2060
68c2817d40 Enable avd hack for debug build 2023-04-25 23:00:59 -07:00
naxitoo
83d837d868 Update/refine Spanish translations 2023-04-18 11:53:22 -04:00
I3elphegor
093eb15ee1 Update strings.xml
Incorrectly placed punctuation marks have been removed. Word order and some expressions have been corrected. The style of the menu headings and descriptions has been unified in the settings.
2023-04-18 11:22:44 -04:00
VD $ VD171 @ Priv8
c6412c1b1b Update PORTUGUESE translation 2023-04-18 11:22:03 -04:00
serkanege
1151393d74 tr language update 2023-04-18 11:21:32 -04:00
topjohnwu
468f3efb13 Update dependencies 2023-04-13 14:19:34 -07:00
LoveSy
d6b19b9d4c Upgrade gradle 2023-04-13 14:19:30 -07:00
Ilya Kushnir
709f25f600 Fix changelog index 2023-04-12 02:46:21 -07:00
topjohnwu
4b16e4b026 Update README 2023-04-11 12:51:22 -07:00
topjohnwu
cdfbc02922 Release new canary build 2023-04-11 02:04:15 -07:00
topjohnwu
d0c9384233 Release Magisk v26.1 2023-04-11 01:57:30 -07:00
topjohnwu
2488668b06 Add v26.1 release notes 2023-04-11 01:52:45 -07:00
LoveSy
52a98cbd51 Temp workaround for module file context 2023-04-10 19:30:37 -07:00
serkanege
1840c4c486 Update strings.xml 2023-04-10 19:30:11 -07:00
serkanege
34080f3958 Update strings.xml 2023-04-10 19:30:11 -07:00
topjohnwu
e9b76b6aa5 Add monochrome adaptive icon support
Close #6867
2023-04-10 19:29:32 -07:00
Jakub K
b7799b53d9 Updated Czech translation
Added missing strings and updated few.
2023-04-09 21:24:55 -07:00
Lishoo
1e206515c7 Update PL strings
Update PL strings
2023-04-08 21:15:19 -07:00
sn-o-w
6bb313184d Update Romanian 2023-04-08 21:15:02 -07:00
l3ng
2763992434 Update Azerbaijani
Co-authored-by: LoveSy <631499712@qq.com>
2023-04-08 21:14:41 -07:00
osm0sis
18fe0e6442 Fix scripts
manager.sh + boot_patch.sh:
- all listed files from boot_patch.sh header are required for boot patching, but stub.apk was being removed so install_magisk via addon.d.sh would fail without it; leave it in place

addon.d.sh:
- remove old redundant recovery_actions call (it's also performed by setup_flashable in initialize)
- print ABI to match flash_script.sh output

boot_patch.sh:
- catch and abort on any errors from ramdisk.cpio patching in the future

util_functions.sh:
- fix hiding of mount_partitions /system_root umount stderr
- quote mount_apex .pb DEST name parsing charset for safety even though both work

Fixes #6828
2023-04-08 21:13:40 -07:00
zjw
a70c73bffd Fix config file path
$MAGISKTMP was redefined in commit 4e2b88b
2023-04-08 21:10:54 -07:00
topjohnwu
b4ae3493a6 Use ext4 partitions for preinit first
Fix #6841, close #6847
2023-04-08 20:30:40 -07:00
残页
1a16004b20 Add help message for magisk --preinit-device 2023-04-08 18:32:34 -07:00
topjohnwu
56707b8119 Make FilterList more accurate 2023-04-08 18:32:00 -07:00
LoveSy
c3f9533ddc Fix inconsistency of FilterableDiffObservableList
`update` should also update sublist
2023-04-08 18:32:00 -07:00
Rom
3b3abd63cc Update FR translation 2023-04-07 03:08:27 -07:00
Hen_Ry
411d3ed4e9 Update DE strings 2023-04-07 03:07:46 -07:00
LoveSy
f29cc26103 Correctly get displayName of live uri 2023-04-06 02:03:09 -07:00
Ilya Kushnir
1cd595a598 Update RU strings 2023-04-06 01:10:48 -07:00
topjohnwu
22e023b58d Set notes on main thread 2023-04-06 00:53:06 -07:00
topjohnwu
7be958e35d Fix crash when revoke root permission 2023-04-06 00:40:26 -07:00
topjohnwu
69b66ef637 Make core package more self contained 2023-04-05 23:04:33 -07:00
topjohnwu
daf8653c38 Release new canary build 2023-04-05 11:13:51 -07:00
topjohnwu
e2545e57cf Update README badges 2023-04-05 11:07:40 -07:00
topjohnwu
7cb0909c70 Release Magisk v26.0 2023-04-05 11:02:17 -07:00
topjohnwu
cc5ff36165 Revert "Cancel recursive bind"
This reverts commit a18a440236.
2023-04-05 10:47:13 -07:00
topjohnwu
18b1ef6c29 Only restore JNIEnv in constructor 2023-04-05 10:13:50 -07:00
LoveSy
7fe012347a Restore JNIEnv in advance for clean env to modules 2023-04-05 09:29:59 -07:00
vvb2060
5c165c9bb0 Fix avd hack 2023-04-05 04:01:32 -07:00
topjohnwu
6c3519923d Make things more obvious 2023-04-05 04:01:07 -07:00
topjohnwu
9ea859810d Update api.hpp copyright notice 2023-04-05 01:54:56 -07:00
LoveSy
8dae7b5451 Update installation guide 2023-04-05 01:50:45 -07:00
vvb2060
f827755aaf Skip getSessionInfo 2023-04-05 01:46:33 -07:00
topjohnwu
637a8af234 Add v26.0 release notes 2023-04-05 01:42:42 -07:00
LoveSy
b0fc580860 Avoid crash when calling abandonSession 2023-04-05 00:36:51 -07:00
vvb2060
9279f30e89 Upload mapping and native debug symbols 2023-04-05 00:14:51 -07:00
LoveSy
b505819ca2 Fix a typo 2023-04-04 12:28:08 -07:00
topjohnwu
39d1d23909 Release new canary build 2023-04-04 03:00:50 -07:00
vvb2060
69529ac59c Fix restorecon 2023-04-04 02:34:16 -07:00
vvb2060
a18a440236 Cancel recursive bind 2023-04-04 02:12:07 -07:00
LoveSy
aa7846c1c0 No need to mount ROOTMIR if tmp_dir != /sbin 2023-04-04 02:12:07 -07:00
topjohnwu
24ba4ab95b Better AVD support 2023-04-04 02:04:49 -07:00
topjohnwu
762b70ba9d Better string split implementation 2023-04-03 18:50:36 -07:00
topjohnwu
41b77e4f25 Make base as template argument for optimization 2023-04-03 18:32:11 -07:00
topjohnwu
2087e47300 Get random separately 2023-04-03 18:32:11 -07:00
vvb2060
46ce765860 Use stable random seed 2023-04-03 18:32:11 -07:00
topjohnwu
5117dc1a31 Reorganize code 2023-04-03 17:47:07 -07:00
Arbri çoçka
620fd7d124 Update sq strings.xml 2023-04-03 16:54:11 -07:00
kubalav
3e991dc003 Update Slovak translation 2023-04-03 16:53:59 -07:00
LoveSy
15cab86152 Make module mirror read only 2023-04-02 22:03:02 -07:00
LoveSy
aa785b5845 Show confirm dialog when installing local module
It can avoid miss click

Co-authored-by: 南宫雪珊 <vvb2060@gmail.com>
2023-04-02 21:33:13 -07:00
LoveSy
97731a519a Update zygisk API to avoid mem leak 2023-04-02 03:54:33 -07:00
残页
b696dae808 Specify foregroundServiceType for DownloadService 2023-04-02 02:20:49 -07:00
topjohnwu
732a8260c2 Update dependencies 2023-04-02 02:13:47 -07:00
LoveSy
4ff60ef9a9 No more patching libc.a on 64bit platforms 2023-04-01 03:51:57 -07:00
topjohnwu
23b1b69110 Consolidate zygisk cleanup routines 2023-04-01 03:37:17 -07:00
LoveSy
3a4fe53f27 New way to unload zygisk
Co-authored-by: 残页 <a1364259@163.com>
2023-04-01 03:37:17 -07:00
LoveSy
e48afff5e8 Compress jniLibs 2023-04-01 01:54:10 -07:00
topjohnwu
3f4f4598e8 Better AVD support 2023-03-31 17:21:12 -07:00
LoveSy
3921e9cb1b Fix release build 2023-03-31 00:06:55 -07:00
topjohnwu
0b987dd0b0 Cleanup more databinding implementation 2023-03-31 00:05:33 -07:00
Ilya Kushnir
1620e15f99 Update RU strings 2023-03-30 13:45:13 -07:00
topjohnwu
b089511e91 Update Android Studio 2023-03-30 13:35:13 -07:00
Arbri çoçka
958788c1aa Update Albania 2023-03-30 13:33:26 -07:00
LoveSy
b5a8a27296 Update Chinese translation 2023-03-30 13:33:04 -07:00
kubalav
98123775ad Update Slovak translation 2023-03-30 13:32:40 -07:00
Thonsi
c7133974be Clean up some codes 2023-03-26 20:48:23 -07:00
LoveSy
04324a7ebe Upgrade LSPlt to bypass a bionic bug 2023-03-23 20:56:35 -07:00
vvb2060
f54daa3469 Force ramdisk format to lz4_legacy for v4 2023-03-23 20:50:27 -07:00
LoveSy
07c22ccd39 Use app_dir to detect systemui instead
uid may be shared by other apps (e.g., in MIUI, systemui's uid
is 1000 and shared by many system apps).
2023-03-23 16:17:13 -07:00
LoveSy
e893c13cf1 Unlink preinit device if bind mount fails 2023-03-23 00:40:13 -07:00
LoveSy
dba5020e4f Refactor magiskrc 2023-03-22 17:53:15 -07:00
LoveSy
87e036a190 Update LSPlt to avoid crash when hooking libc 2023-03-22 12:02:33 -07:00
LoveSy
3dd94672b0 Fix preinit scripts 2023-03-22 03:07:34 -07:00
LoveSy
004b193f69 Fix installation crash 2023-03-22 02:44:14 -07:00
topjohnwu
4417997749 Make sure ro mmap region is not overwritten 2023-03-21 15:50:43 -07:00
LoveSy
2eef542054 Add amonet microloader support 2023-03-21 15:50:43 -07:00
LoveSy
a07d4080b6 Upgrade termux-elf-cleaner to strip DT_PREINIT_ARRAY 2023-03-21 02:17:21 -07:00
LoveSy
b9d0a3b3d4 Use partition name or devpath's name 2023-03-21 00:40:11 -07:00
topjohnwu
76405bd984 Add more comments 2023-03-21 00:40:11 -07:00
topjohnwu
4e2b88b3d0 Rename rules to preinit
It is possible that we will allow more preinit files for modules.
Rename the partition and folders from rules to preinit.
2023-03-21 00:40:11 -07:00
LoveSy
7048aa1014 Rename sepolicy.rules -> rules 2023-03-21 00:40:11 -07:00
LoveSy
1c2fcd14b5 Mount sepolicy.rules for migration 2023-03-21 00:40:11 -07:00
vvb2060
84e1bd7bc3 Refactor sepolicy.rules resolve app 2023-03-21 00:40:11 -07:00
vvb2060
362eea741f Refactor sepolicy.rules resolve native
Co-authored-by: LoveSy <shana@zju.edu.cn>
2023-03-21 00:40:11 -07:00
LoveSy
4de93cfd4b Use RV to show Magisk logs 2023-03-19 23:47:29 -07:00
LoveSy
03cee0b8d4 Remove meaningless umount in magiskinit
This is no longer required since we redirect to /data/magiskinit
2023-03-19 23:35:18 -07:00
LoveSy
54ecc001f4 Clean up more codes 2023-03-19 23:20:19 -07:00
LoveSy
5c325d9466 Fix su log with long command 2023-03-19 23:20:04 -07:00
topjohnwu
0e851cdcf8 Always fetch network state on process onStart 2023-03-19 04:11:59 -07:00
topjohnwu
af054e4e31 Bump minSdk to 23 2023-03-17 04:24:26 -07:00
Chris Renshaw
33fb4653f0 Sanitize any bad chars from mount_apex apex_manifest.pb string parsing
For example, Lineage's com.android.ondevicepersonalization apex_manifest.pb has a # char, which strings keeps in its output, and breaks the mount for that apex before this fix
2023-03-17 02:44:36 -07:00
LoveSy
d9f0aed571 Fix unpack -n and repack -n of ZIMAGE kernel 2023-03-17 02:00:46 -07:00
LoveSy
98813c24fb Drop trailing garbage of gzip decompress
I previously refered to minigzip from libz which copies all trailing
data to the output when decompressing. However, gzip, on the other
hand, drop trailing garbage by default. Consider ZIMAGE append
the kernel size with zero padding, we should drop trailing garbage
as well.
2023-03-17 02:00:25 -07:00
topjohnwu
3cc81bb3fd Cleanup ObservableList implementation 2023-03-17 01:40:28 -07:00
topjohnwu
366dd52419 Update AGP 2023-03-16 04:18:03 -07:00
topjohnwu
fe6b658c02 Use MenuProvider 2023-03-09 18:05:37 -08:00
LoveSy
3cf66d1c57 Fix currentFocus
Looks like currentFocus does not always exist even after setContentView,
so I hereby use another way to check if setContentView is called
2023-03-09 17:52:38 -08:00
topjohnwu
382568bd3c Cleanup filterable list implementation 2023-03-09 17:45:00 -08:00
LoveSy
d130aa02a1 Do not always create new adapter 2023-03-09 16:00:08 -08:00
LoveSy
1a1646795f Support untrusted_app_32 2023-03-09 02:17:30 -08:00
LoveSy
d52ea1b068 Postpone showMainUI when activity has stopped
Co-authored-by: topjohnwu <topjohnwu@gmail.com>
2023-03-07 21:04:04 -08:00
LoveSy
e14f7b6908 No KeyDispatch or OnBackPress when no currentFocus 2023-03-07 20:20:35 -08:00
南宫雪珊
4709a32641 Fix mkdir
Co-authored-by: LoveSy <shana@zju.edu.cn>
2023-03-07 01:47:26 -08:00
topjohnwu
71b7f52663 Release new canary build 2023-03-06 05:32:08 -08:00
LoveSy
981ccabbef No support for partitions w/o symlink in /system 2023-03-06 05:23:40 -08:00
vvb2060
9e07eb592c Fix make private before remount 2023-03-06 05:18:16 -08:00
LoveSy
9555380818 Replace parse_mnt with parse_mount_info 2023-03-06 05:09:12 -08:00
topjohnwu
f80d5d858e Update AGP 2023-03-06 05:08:58 -08:00
topjohnwu
a1ce6f5f12 Fix race condition when switching root manager
Before this change, the root manager package name is only written into
the database after the repackaged APK is installed. In the time between
the repackaged APK being installed and the package name being written
into the database, if some operation calls `get_manager`, the Magisk
daemon will cache this result and ignore the repackaged APK, even if
the package name is set afterwards, because the cache won't be
invalidated. The result is that the repackaged manager APK will not be
recognized as the root manager, breaking the hide manager feature.

This race condition is more likely to happen when Zygisk is enabled,
because `get_manager` is called with a very high frequency in that case.

To fix the issue, we have to set the new package name into the database
BEFORE installing the repackaged APK. We also stop pruning the
database if the repackaged manager is not found, moving this logic into
the Magisk app. By doing so, we can guarantee that the instant after
the repackaged manager APK is installed, the Magisk daemon will
immediately pick it up and treat it as the root manager.

Another small optimization: when the requester is root, simply bypass
the whole database + manager package check. Since the Magisk app hiding
APK installation proces will call `su` several times to run `pm` under
different UIDs, doing this opimization will reduce the amount of
unnecessary database query + filesystem traversals.
2023-03-06 03:58:58 -08:00
LoveSy
1aade8f8a8 No greedy match to find parent mount point
This fixes /sys is considered as a parent mount point of /system
2023-03-03 11:09:03 -08:00
LoveSy
b9213b7043 Remove redundant stub.apk 2023-03-03 11:07:36 -08:00
LoveSy
4af72324f4 Fix gradle cache 2023-03-03 02:39:22 -08:00
LoveSy
b6ea5b8984 Fix SYSTEM_ROOT not passed to boot patch 2023-03-02 23:39:50 -08:00
topjohnwu
c279e08c88 Release new canary build 2023-03-02 21:35:14 -08:00
topjohnwu
2717feac21 Fix stub APK building in app 2023-03-02 21:27:48 -08:00
topjohnwu
8adf27859d Build script adjustments 2023-03-02 20:32:46 -08:00
LoveSy
307cf87215 Fix build script 2023-03-02 20:32:46 -08:00
Takeda-senpai
ca31412c05 Update strings.xml 2023-03-02 14:34:08 -08:00
LoveSy
f59fbd5dca Filter duplicate mount points
This prevents umounting existing overlay mount points
2023-03-02 14:33:50 -08:00
topjohnwu
2285f5e888 Fix build script 2023-03-02 03:02:10 -08:00
LoveSy
da36e5bcd5 Make worker private 2023-03-02 02:25:44 -08:00
Prithvi
4ed9f57fdc Update ota.md (#6374)
Specified the steps of the OTA install process so there is less confusion about what step 1 and 2 entail.
2023-03-02 02:23:19 -08:00
Daki Carnhof
ea7be6162f install.md: Mention Heimdall beside Odin
Proven to work with Magisk 25.2, LineageOS 18.1, SM-A520F.
2023-03-02 02:21:14 -08:00
南宫雪珊
3726eb6032 Deny init relabel to adb_data_file
Co-authored-by: 残页 <a1364259@163.com>
Co-authored-by: LoveSy <shana@zju.edu.cn>
2023-03-02 02:20:38 -08:00
vvb2060
6e918ffd68 Remove fetchCustomUpdate 2023-03-02 02:05:38 -08:00
vvb2060
4772868d6a Move REMOUNT_ROOT 2023-03-02 02:05:38 -08:00
vvb2060
78df677a42 Use /data/adb/modules directly 2023-03-02 02:05:38 -08:00
vvb2060
85a4b249b3 Skip copy old rule 2023-03-02 02:05:38 -08:00
vvb2060
d06e9a0b51 Allow R8 to delete fetchCanary 2023-03-02 02:05:38 -08:00
vvb2060
5eb774a2ad Fix typo 2023-03-02 02:05:38 -08:00
topjohnwu
cbbbbab483 Release new canary build 2023-02-27 23:16:54 -08:00
LoveSy
e5641d5bdb Fix avd-magisk 2023-02-27 23:00:42 -08:00
topjohnwu
a721206c6f Update items in the right thread 2023-02-27 23:00:21 -08:00
LoveSy
c7a27481f9 Update proguard rules to fix module page crash 2023-02-27 20:50:24 -08:00
LoveSy
594c304634 Fix release build 2023-02-26 22:35:20 -08:00
topjohnwu
d0ec387c28 Release new canary build 2023-02-26 15:06:33 -08:00
vvb2060
7dbfba76bf Umount by tmpfs id 2023-02-26 14:23:11 -08:00
vvb2060
2a4aa95a6f Identify tmpfs used by magisk 2023-02-26 14:23:11 -08:00
vvb2060
5520f0fbf7 Add stub version to apk comment 2023-02-26 14:23:02 -08:00
LoveSy
a1a87c9956 Get rid of vtable hook 2023-02-26 04:59:21 -08:00
vvb2060
2c53356bfd Remove unexpected files 2023-02-26 03:47:38 -08:00
topjohnwu
85d9756f62 Update Zygisk API documentation 2023-02-26 03:16:13 -08:00
LoveSy
79586ece4c Update install.md 2023-02-26 01:17:11 -08:00
AndroPlus
6851d11a8e Update Japanese translation 2023-02-26 01:16:23 -08:00
LoveSy
996a857096 Upgrade kotlin 2023-02-26 01:15:32 -08:00
LoveSy
d7158131e4 No need to manually parse mount flags 2023-02-26 01:15:18 -08:00
topjohnwu
3d3082bc82 Minor optimizations 2023-02-26 01:14:10 -08:00
topjohnwu
744ebca206 Don't let inter_node upgrade to module_node 2023-02-25 22:05:50 -08:00
topjohnwu
92077ebe53 Refactor module and node implementation 2023-02-25 18:19:46 -08:00
LoveSy
78ca682bc5 Always mount tmpfs for dirs
https://android-review.googlesource.com/c/platform/system/core/+/928592
2023-02-25 18:19:46 -08:00
LoveSy
af01a36296 Refactor magic mount to support overlayfs
Previously, magic mount creates its own mirror devices and mount
mirror mount points. With these mirror mount points, magic mount
can get the original files and directory trees. However, some
devices use overlayfs to modify some mount points, and thus after
magic mount, the overlayed files are missing because the mirror
mount points do not contain the overlayed files. To address this
issue and make magic mount more compatible, this patch refactors
how magic mount works.

The new workflows are as follows:
1. make MAGISKTMP a private mount point so that we can create the
   private mount points there
2. for mirror mount points, we instead of creating our own mirror
   devices and mount the mirror mount points, we "copy" the
   original mount points by recursively mounting /
3. to prevent magic mount affecting the mirror mount points, we
   recursively set the mirror mount points private
4. to trace the mount points we created for reverting mounts, we
   again make the mirror mount points shared, and by this way we
   create a new peer group for each mirror mount points
5. as for tracing the newly created tmpfs mount point by magic
   mount, we create a dedicated tmpfs mount point for them, namely
   worker mount point, and obviously, it is shared as in a newly
   created peer group for tracing
6. when reverting mount points by magic mount, we can then trace
   the peer group id and unmount the mount points whose peer group
   ids are created by us

The advantages are as follows:
1. it is more compatible, (e.g., with overlayfs, fix #2359)
2. it can mount more partitions for which previous implementation
   cannot create mirror mount points (fix #3338)
2023-02-25 18:19:46 -08:00
LoveSy
97ed1b16d0 Fix gzip decompression 2023-02-20 18:26:04 -08:00
LoveSy
508a001753 Remove obsolete link 2023-02-20 17:25:32 -08:00
vvb2060
c1909d520b Fix gradle build cache 2023-02-20 01:23:56 -08:00
topjohnwu
9b1e173373 Update AGP 2023-02-20 01:03:35 -08:00
LoveSy
4ba365565f Upgrade gradle 2023-02-20 00:08:23 -08:00
残页
ae34659b26 No kernel repack if it isn't patched at all
It turns out that decompressing and recompressing the kernel is enough to break booting on many devices that use MT6763.
Fix #5124, fix #6204, fix #6566 

Co-authored-by: LoveSy <shana@zju.edu.cn>
Co-authored-by: 南宫雪珊 <vvb2060@gmail.com>
2023-02-17 15:00:14 -08:00
LoveSy
79a85f5937 Use mountinfo for revert unmount 2023-02-17 12:36:19 -08:00
LoveSy
b249832571 Use statfs to check rootfs
This helps support adb remount
2023-02-12 22:49:27 -08:00
LoveSy
577b5912af Fix SKIP_FD_SANITIZATION false positive
Fix #6523
2023-02-12 00:40:09 -08:00
LoveSy
9e8c68af12 Refactor sepolicy.rules resolve
We resolve available partitions for sepolicy.rules when patching
boot and bind mount the partition by magiskinit.

For older devices, the previous logic won't work because the part name
is never readable.

Co-authored-by: topjohnwu <topjohnwu@gmail.com>
2023-02-12 00:36:38 -08:00
shìwēi nguyen
03418ddcbf run module uninstall.sh on Magisk uninstallation 2023-02-09 20:36:58 -08:00
LoveSy
220a1c84ce Zygisk v4 module's plt commit should not use regex 2023-02-09 20:19:53 -08:00
南宫雪珊
9a4458ffac Update appcompat 2023-02-09 20:13:40 -08:00
vvb2060
7a9e6d2ad2 Remove unexpected /sbin/overlay.d 2023-02-09 20:08:59 -08:00
LoveSy
9656cf2f86 Refine 2023-02-09 20:08:44 -08:00
BlackMesa123
584bad5314 Add init_boot.img patching for Samsung tar firmware packages
Signed-off-by: BlackMesa123 <giangrecosalvo9@gmail.com>
2023-02-09 20:08:44 -08:00
topjohnwu
459088024f Update dependencies 2023-02-09 19:53:40 -08:00
Chris Renshaw
d740bbe058 Ignore AMLogic "normal" slot suffix in scripts as well
Fixes #6572
2023-02-03 10:44:43 -08:00
canyie
6ecc04a4df Fix auto install stub 2023-01-23 01:55:28 +08:00
canyie
15a7e9af57 Fix ResourcesProvider being closed 2023-01-20 19:32:22 +08:00
LoveSy
0329f00129 Upgrade LSPlt
Fix #6533
2023-01-20 19:29:55 +08:00
topjohnwu
cd8a2edefb Reduce unnecessary formatting 2023-01-20 14:41:34 +08:00
LoveSy
4318ab5cd2 Reuse tmpfs for magic mount
As we already have a tmpfs (magisktmp), we can reuse them for
magic mount
2023-01-20 03:49:40 +08:00
topjohnwu
3517e6d752 Handle nullptr char* in Rust 2023-01-20 03:45:16 +08:00
LoveSy
67845f9c21 Clear sepolicy rules when disable/remove modules
Co-authored-by: topjohnwu <topjohnwu@gmail.com>
2023-01-19 04:25:44 +08:00
Kian-Meng Ang
f562710438 Fix typos
Found via `codespell -S *.xml,*.kt,./native/src/external -L crate,bu`
2023-01-18 16:35:37 +08:00
vvb2060
e836909c50 umount old hijack binary 2023-01-18 13:06:17 +08:00
vvb2060
7769ba5f54 Remove READ_EXTERNAL_STORAGE permission added by AGP 1.8 2023-01-18 12:52:24 +08:00
topjohnwu
7fe9db90a1 Update AGP 2023-01-18 12:52:04 +08:00
topjohnwu
8f7d6dfb77 Cleanup unused functions 2023-01-12 23:17:41 +08:00
canyie
2839978cc1 Use null oat dir in root service only 2023-01-12 22:38:14 +08:00
canyie
e73f87b758 Update LSPlt 2023-01-12 01:18:56 +08:00
canyie
bd0409fd15 Fix busybox filename 2023-01-12 01:18:41 +08:00
canyie
babdfe80cb Fix stub resources load on Android 5 2023-01-12 01:12:56 +08:00
topjohnwu
636223b289 Cleanup APIs 2023-01-10 02:42:45 +08:00
LoveSy
aa0a2f77cf Add inode plt hook APIs 2023-01-10 02:42:45 +08:00
topjohnwu
e38f35eab2 Update libsu
Fix #6488
2023-01-09 03:16:11 +08:00
canyie
cb39514705 Fix NotificationService implementation
- Fix #6385. (Maybe the reason is, the call to stopForeground() with STOP_FOREGROUND_DETACH ensures the notification is shown so it reposts the notification?)
- Use FOREGROUND_SERVICE_IMMEDIATE on Android 12+ to make sure the downloading notification always shows immediately
2023-01-09 00:10:52 +08:00
topjohnwu
78a444d601 Wait for root service to bind 2022-12-30 15:52:41 -08:00
LoveSy
37b81ad1f6 Refine module preparation return value 2022-12-26 17:17:41 -08:00
vvb2060
7871c2f595 Update deps 2022-12-26 17:16:26 -08:00
topjohnwu
57d83635c6 Check stub.xz existence 2022-12-26 16:07:04 -08:00
topjohnwu
76fbf4634a Update scripts 2022-12-26 16:07:04 -08:00
topjohnwu
7ce4bd3330 Copy stub APK into output directory 2022-12-26 16:07:04 -08:00
vvb2060
ad0e6511e1 Stop embedding stub.apk in magiskinit 2022-12-26 16:07:04 -08:00
vvb2060
a4a734458b Fix network capabilities 2022-12-26 13:48:17 -08:00
Brian Kepha
f989756b93 Added Swahili Translation 2022-12-26 03:30:44 -08:00
LoveSy
5763a3d908 Support replacing existing .rc by overlay.d
Co-authored-by: topjohnwu <topjohnwu@gmail.com>
2022-12-26 03:28:10 -08:00
topjohnwu
1b745ae1a0 Use latest build tools 2022-12-26 02:02:21 -08:00
topjohnwu
b6d50bea2c Release new canary build 2022-12-26 00:33:02 -08:00
topjohnwu
831a398bf1 Check Python 3.8+ 2022-12-26 00:09:27 -08:00
topjohnwu
a848783b97 Guard boot stages more precisely
Close #6468, fix #6148
2022-12-26 00:04:58 -08:00
LoveSy
4d876f0145 Support detecting safemode by ro.sys.safemode 2022-12-24 15:16:53 -08:00
LoveSy
bdfedea4e0 Close missing fd
Fix #6463
2022-12-24 15:16:27 -08:00
LoveSy
ea0e3a09ef Update install.md 2022-12-23 17:21:44 -08:00
topjohnwu
dadae20960 Remove unused implementations 2022-12-23 17:20:39 -08:00
LoveSy
4ed34cd648 Eliminate unnecessarily copy on magiskinit
This patch reuses the abused /data tmpfs for magisktmp
2022-12-23 17:03:16 -08:00
osm0sis
0d38c94c9c scripts: fix root loss until reboot after Magisk addon.d-v2
- /system/bin/su was being removed in error from the live system so update remove_system_su to be aware of a running A/B OTA and generalize/simplify removal logic with relative paths to correctly run on the updated system slot
2022-12-23 16:36:44 -08:00
vvb2060
2a2a452bd4 CI uses java 17 2022-12-13 14:13:25 -08:00
vvb2060
13c2695e98 simplify denylist rm 2022-12-13 14:11:32 -08:00
fadlyas07
3ff60ed49f app: l10n: Update Indonesian translations
* Added new strings based on the recent source.

Change-Id: I40d6e0374a0356d93c61acace7ab48c3649e85e8
2022-12-13 14:05:54 -08:00
VD $ VD171 @ Priv8
bbb1786ec3 Fix typo PT translation
Fix typo PT translation
2022-12-13 14:05:40 -08:00
Davy Defaud
4bfd2dac54 Fix gender of ”install” as an adjective in French
In French, install as an adjective depends on the gender of its related noun. For instance, “Magisk installed” is translated “Magisk installé” (masculine form), whereas “Application installed” is translated “Application installée” (feminine form).
By using “Version installée”, “installed” is related to “version” which is feminine. For consistency of the GUI, I’m also changing "home_latest_version" string to ”Dernière version” instead of “Dernière”.
2022-12-13 14:05:24 -08:00
ysard
857c12372a Update French translations
Just a misspelling fix
2022-12-13 14:05:09 -08:00
残页
33f5154269 Inject binaries into /system if sbin not accessible
Some Android 11+ devices have the /sbin partition but not accessible by the global shell (`PATH` doesn't contain `/sbin`). Not only custom ROMs but also some stock ROMs have the same behavior so I believe it is something we need to deal with.
Fix #6427, fix #4309, fix #5728, fix #3593
2022-12-13 13:54:55 -08:00
topjohnwu
ed37ddd570 Stricter validation 2022-11-22 14:47:37 -08:00
LoveSy
cd5384f13e Fix crashes whenever a zygisk module has ver > 4 2022-11-22 14:47:37 -08:00
LoveSy
11b2ddbad8 Fix zygisk v4 ApiTable abi
Also refactor some code to let the compiler check the abi

Co-authored-by: topjohnwu <topjohnwu@gmail.com>
2022-11-22 11:49:31 -08:00
topjohnwu
cf9957ce4d Properly detect SysUI appId
Fix #6322
2022-11-01 02:04:50 -07:00
topjohnwu
44643ad7b3 Restrict pointer aliasing
Close #6354, close #6353
2022-10-31 16:35:33 -07:00
topjohnwu
1e53a5555e Update AGP 2022-10-31 16:00:42 -07:00
topjohnwu
616adc22e1 Support Linux < 3.6 2022-10-31 16:00:42 -07:00
akhilkedia
916e373edb Update README.md to specify python version 3.8+
Current readme suggests python 3.6+
However, the file `build.py` on executing `build.py ndk` runs the command `shutil.copytree(src_dir, lib_dir, copy_function=cp, dirs_exist_ok=True)`
This command errors out on python 3.7, because the `dirs_exist_ok` parameter is new in Python 3.8 (https://docs.python.org/3/library/shutil.html#shutil.copytree)

So the README should suggest python 3.8+
2022-10-20 17:21:08 -07:00
Hen_Ry
021ae15395 Update German translation 2022-10-12 13:08:12 -07:00
vvb2060
52cf72002a Update resources load
addAssetPath supports apk and directory
2022-10-12 13:07:30 -07:00
topjohnwu
68874bf571 Release new canary build 2022-10-11 13:33:08 -07:00
残页
a468fd946d Fix #6314 2022-10-11 13:01:34 -07:00
topjohnwu
e327565434 Release new canary build 2022-10-10 21:44:32 -07:00
topjohnwu
c3b4678f6e Properly detect SysUI 2022-10-10 21:28:13 -07:00
vvb2060
978216eade local module: filter hidden dirs 2022-10-03 14:20:09 -07:00
残页
44cfe94e4d Always cleanup init LD_PRELOAD hooks
Fix #6296
2022-10-03 08:26:33 -07:00
Nitrovenom
f9e82c9e8a Update Bengali translation 2022-09-26 23:11:00 -07:00
theunknownKiran
25b4b107d3 Create strings.xml
Added Malayalam translation
2022-09-26 23:10:48 -07:00
theunknownKiran
db651fa9ec Create strings.xml
Added Malayalam translation
2022-09-26 23:10:34 -07:00
LoveSy
23ad611566 Use sccache for rust build 2022-09-26 01:35:52 +02:00
topjohnwu
095d821240 Don't use xopen in readlink 2022-09-25 16:35:28 -07:00
topjohnwu
e23f23a8b7 Update AGP 2022-09-21 03:09:53 +02:00
topjohnwu
48f829b76e Minor refactoring 2022-09-21 03:09:46 +02:00
topjohnwu
0b82fe197c Update avd_test.sh 2022-09-15 19:45:58 -07:00
topjohnwu
af99c1b843 Don't crash when nullptr paired with len = 0 2022-09-15 16:56:22 -07:00
topjohnwu
c6646efe68 Move all xwrap to Rust 2022-09-15 01:17:05 -07:00
Nitrovenom
66a7ef5615 Update Bengali translation 2022-09-13 04:30:01 -07:00
canyie
9474750bdf Close fd of erroneous daemon socket connections 2022-09-13 04:29:29 -07:00
LoveSy
e86db0bd61 Reset stack guard after fork from Zygote 2022-09-13 04:18:34 -07:00
topjohnwu
a29fc11798 Update libsu
Fix #6255
2022-09-13 04:17:19 -07:00
topjohnwu
a66a3b7438 Make sure logs are always ended with newline 2022-09-09 04:29:50 -07:00
topjohnwu
44029875a6 Add new API exemptFd 2022-09-09 03:27:19 -07:00
topjohnwu
ccf21b0992 Zygisk code refactor 2022-09-07 13:48:20 -07:00
topjohnwu
4e14dab60a Specialize does not need to close logd_fd 2022-09-06 03:01:39 -07:00
topjohnwu
6e299018a4 Preserve logd_fd after specialization
Also add more comments regarding FD checks
2022-09-02 01:49:17 -07:00
topjohnwu
555a54ec53 Avoid doing any unmounts for SysUI 2022-08-31 00:15:15 -07:00
topjohnwu
1565bf5442 Make Zygisk API 0BSD 2022-08-30 01:58:40 -07:00
topjohnwu
14b830027b Cleanup zygisk headers 2022-08-30 01:40:14 -07:00
topjohnwu
38325e708e Make private applets hidden 2022-08-27 14:50:28 -07:00
topjohnwu
646260ad6d Fix typo 2022-08-27 05:27:23 -07:00
topjohnwu
d1d26f4481 Fix building individual applet 2022-08-27 05:06:28 -07:00
topjohnwu
357d913f18 Dynamically generate component names at runtime 2022-08-26 06:31:51 -07:00
topjohnwu
71b0c8b42b Make stub patching 100% offline 2022-08-26 06:31:51 -07:00
topjohnwu
cdc66c1ac8 Move patching logic out of AXML 2022-08-26 06:31:51 -07:00
topjohnwu
e9af773901 Remove usage of AndroidX CoreComponentFactory 2022-08-26 06:31:51 -07:00
Rom
eadf6e8b96 Update French translation 2022-08-25 02:27:35 -07:00
topjohnwu
87bec70d9f Setup notification on app init
Close #6216
2022-08-25 02:24:30 -07:00
Ilya Kushnir
3668b28f62 Update RU strings 2022-08-24 03:20:55 -07:00
Arbri çoçka
933e4bd163 update Albania strings 2022-08-24 03:20:33 -07:00
vvb2060
e3ab9e9a1e Update zh-rCN translation 2022-08-24 03:20:19 -07:00
VD $ VD171 @ Priv8
58ad2c1416 Update Portuguese & Portuguese Brazilian Translations 2022-08-24 03:19:31 -07:00
kubalav
c5291ad33b Update Slovak translation 2022-08-24 03:19:09 -07:00
vvb2060
77d8445bfd Avoid hardcode package name 2022-08-24 03:18:55 -07:00
topjohnwu
f8395a7dc6 Make sure UI and state is in sync 2022-08-23 05:38:19 -07:00
topjohnwu
727c70005e Release new canary build 2022-08-23 05:20:44 -07:00
topjohnwu
38ab6858f0 Do not crash root service in stub 2022-08-23 05:10:18 -07:00
topjohnwu
a54114f149 Pre-grant permissions if possible 2022-08-23 05:09:50 -07:00
topjohnwu
7a4a5c8992 Ensure JobService is rescheduled 2022-08-23 04:14:06 -07:00
topjohnwu
928a16d8cc Update app to target API 33
Close #6206
2022-08-23 03:59:09 -07:00
topjohnwu
3f7f6e619a Use VERSION_CODES instead of raw numbers 2022-08-23 01:43:53 -07:00
vvb2060
c2f96975ce Pre grant as much as possible 2022-08-23 01:33:10 -07:00
vvb2060
8bd4760b00 Fix build 2022-08-23 01:32:32 -07:00
vvb2060
4f4aeb893d Update deps 2022-08-23 01:31:48 -07:00
canyie
fed4f1b50f Collect kernel version and environment variables in log 2022-08-22 12:52:13 -07:00
vvb2060
e11087cd1a Update kotlin R8 rules 2022-08-22 12:51:54 -07:00
南宫雪珊
e6eb51551c Fix ci text color 2022-08-22 12:51:36 -07:00
topjohnwu
c5c608f0d3 Release new canary build 2022-08-20 18:31:18 -07:00
topjohnwu
4737c5117a Update to ONDK r25.2 2022-08-19 16:26:25 -07:00
topjohnwu
9806b38d8e Introduce zygisk loader
Use a separate library for 1st stage
2022-08-19 04:49:19 -07:00
topjohnwu
6bfe34e5a8 Support testing magiskinit on arm64 AVD 2022-08-19 02:40:33 -07:00
topjohnwu
34dd9eb7d6 More Rust 2022-08-19 02:21:52 -07:00
topjohnwu
2d8beabbd4 Better build script 2022-08-17 01:59:23 -07:00
topjohnwu
4d9b7e7114 More Rust 2022-08-15 11:53:51 -07:00
topjohnwu
40aab13601 Make IDE recognize we are targeting Android 2022-08-09 14:09:39 -07:00
topjohnwu
4c0f72f68f Move part of libbase to Rust 2022-08-08 22:53:37 -07:00
vvb2060
dd565a11ea Fix outdated state 2022-08-08 13:59:56 -07:00
残页
1735a713cb Use ANDROID_DLEXT_FORCE_LOAD to load second stage if possible
Fix #6095
2022-08-08 02:43:19 -07:00
残页
52ba6d11bc Don't let remote errors crash Zygisk
Fix #6095
2022-08-07 05:09:46 -07:00
topjohnwu
7357a35f8d Fix build errors 2022-08-07 05:03:18 -07:00
Acetylcholine
aeb7fd7cb3 Fix denylist add_list
Signed-off-by: ACh Sulfate <xenonhydride@gmail.com>
Co-authored-by: John Wu <topjohnwu@gmail.com>
2022-08-07 04:48:47 -07:00
topjohnwu
1b4a6850b8 Ensure parent folders exist before extract 2022-08-07 04:06:18 -07:00
Cristian Silaghi
07b45f39df Update Romanian 2022-08-07 03:38:57 -07:00
canyie
1d0b873950 Fix sepolicy attribute rule parsing
Fix #6166
2022-08-07 03:35:50 -07:00
topjohnwu
d449f49d73 Update AGP 2022-08-05 15:47:46 -07:00
canyie
e8787b5cfd Fix UB when remote process died
If remote process died, `xreadlink` fails and leaves `buf` uninitialized. Then the daemon calls `str_ends`, creates a temp `std::string_view` with the uninitialized buffer and undefined behavior occurs.
2022-08-02 12:54:54 -07:00
topjohnwu
d17ed2b979 Support patching AVD with release builds 2022-08-02 03:37:04 -07:00
topjohnwu
b496923cbb Update Cargo.toml 2022-07-24 06:14:49 -07:00
topjohnwu
759d196aad Update cxx.rs 2022-07-24 05:45:23 -07:00
topjohnwu
a7ab8216ce Proper build scripts 2022-07-24 05:39:14 -07:00
topjohnwu
b9e89a1a2d Restructure the native module
Consolidate all code into the src folder
2022-07-23 13:51:56 -07:00
vvb2060
c7c9fb9576 Restore context before copy
fix magiskpolicy context
2022-07-23 03:57:43 -07:00
vvb2060
8b095de04d Fix app_zygote context 2022-07-23 03:14:44 -07:00
vvb2060
468325b51a Fix CRLF 2022-07-23 03:14:44 -07:00
gidano
e5058bfb8b Hungarian translation 2022-07-23 02:43:00 -07:00
vvb2060
d4b9ef736d Check magisk32 exists 2022-07-23 02:41:36 -07:00
vvb2060
00d3cb0908 magisk_node: check target exists 2022-07-23 02:40:13 -07:00
vvb2060
d35072d4e6 Match app_zygote by context 2022-07-23 02:39:28 -07:00
canyie
1a964e78dd Support 32-bit emulators
This would be helpful when debugging arch-related bugs, there is no reason to prevent it.
2022-07-23 02:27:50 -07:00
topjohnwu
4264ae49c0 Format with rustfmt 2022-07-22 03:56:09 -07:00
topjohnwu
f08712cd0a Update to ONDK r25.1 2022-07-22 03:56:09 -07:00
LoveSy
3906fe75dc Clean up code 2022-07-21 00:52:28 -07:00
topjohnwu
2497e548c9 Update to ONDK r25.0 2022-07-21 00:07:09 -07:00
topjohnwu
e4635684e9 Release new canary build 2022-07-20 20:21:49 -07:00
topjohnwu
9b61bdfc9a Update README 2022-07-20 20:17:02 -07:00
552 changed files with 22722 additions and 24365 deletions

44
.github/actions/setup/action.yml vendored Normal file
View File

@@ -0,0 +1,44 @@
name: Magisk Setup
runs:
using: "composite"
steps:
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
distribution: "temurin"
java-version: "17"
- name: Set up Python 3
uses: actions/setup-python@v5
with:
python-version: "3.x"
- name: Set up sccache
uses: hendrikmuhs/ccache-action@v1.2
with:
variant: sccache
key: ${{ runner.os }}-${{ github.sha }}
restore-keys: ${{ runner.os }}
max-size: 10000M
- name: Cache Gradle dependencies
uses: actions/cache@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
!~/.gradle/caches/build-cache-*
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle.kts') }}
restore-keys: ${{ runner.os }}-gradle-
- name: Cache build cache
uses: actions/cache@v4
with:
path: |
~/.gradle/caches/build-cache-*
key: ${{ runner.os }}-build-cache-${{ github.sha }}
restore-keys: ${{ runner.os }}-build-cache-
- name: Set up NDK
run: python build.py -v ndk
shell: bash

19
.github/ccache.sh vendored
View File

@@ -1,19 +0,0 @@
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'

View File

@@ -4,88 +4,114 @@ on:
push:
branches: [master]
paths:
- 'app/**'
- 'native/**'
- 'stub/**'
- 'buildSrc/**'
- 'build.py'
- 'gradle.properties'
- '.github/workflows/build.yml'
- "app/**"
- "native/**"
- "stub/**"
- "buildSrc/**"
- "build.py"
- "gradle.properties"
- ".github/workflows/build.yml"
pull_request:
branches: [master]
workflow_dispatch:
jobs:
build:
name: Build on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
name: Build Magisk artifacts
runs-on: ubuntu-latest
env:
SCCACHE_DIRECT: false
strategy:
fail-fast: false
matrix:
os: [ ubuntu-latest, windows-latest, macos-latest ]
env:
NDK_CCACHE: ${{ github.workspace }}/ccache
CCACHE_DIR: ${{ github.workspace }}/.ccache
steps:
- name: Check out
uses: actions/checkout@v2
uses: actions/checkout@v4
with:
submodules: 'recursive'
submodules: "recursive"
fetch-depth: 0
- name: Set up JDK 11
uses: actions/setup-java@v1
with:
java-version: '11'
- name: Set up Python 3
uses: actions/setup-python@v2
with:
python-version: '3.x'
- name: Set up ccache
run: bash .github/ccache.sh
- 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 build cache
uses: actions/cache@v2
with:
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
run: python build.py -v ndk
- name: Setup environment
uses: ./.github/actions/setup
- name: Build release
run: |
./ccache -zp
python build.py -vr all
run: ./build.py -vr all
- name: Build debug
run: |
python build.py -v all
./ccache -s
run: ./build.py -v all
- name: Stop gradle daemon
run: ./gradlew --stop
# Only upload artifacts built on Linux
- name: Upload build artifact
if: runner.os == 'Linux'
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v4
with:
name: ${{ github.sha }}
path: out
compression-level: 9
- name: Upload mapping and native debug symbols
uses: actions/upload-artifact@v4
with:
name: ${{ github.sha }}-symbols
path: app/build/outputs
compression-level: 9
test-build:
name: Test building on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
env:
SCCACHE_DIRECT: false
strategy:
fail-fast: false
matrix:
os: [windows-latest, macos-14]
steps:
- name: Check out
uses: actions/checkout@v4
with:
submodules: "recursive"
fetch-depth: 0
- name: Setup environment
uses: ./.github/actions/setup
- name: Build debug
run: python build.py -v all
- name: Stop gradle daemon
run: ./gradlew --stop
test:
name: Test on API ${{ matrix.api }}
runs-on: ubuntu-latest
needs: build
strategy:
fail-fast: false
matrix:
api: [23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34]
steps:
- name: Check out
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Python 3
uses: actions/setup-python@v5
with:
python-version: "3.x"
- name: Download build artifacts
uses: actions/download-artifact@v4
with:
name: ${{ github.sha }}
path: out
- name: Enable KVM group perms
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm
- name: AVD test
run: scripts/avd_test.sh ${{ matrix.api }}

46
.gitmodules vendored
View File

@@ -1,45 +1,39 @@
[submodule "selinux"]
path = native/jni/external/selinux
path = native/src/external/selinux
url = https://github.com/topjohnwu/selinux.git
[submodule "busybox"]
path = native/jni/external/busybox
path = native/src/external/busybox
url = https://github.com/topjohnwu/ndk-busybox.git
[submodule "dtc"]
path = native/jni/external/dtc
url = https://github.com/dgibson/dtc.git
[submodule "lz4"]
path = native/jni/external/lz4
path = native/src/external/lz4
url = https://github.com/lz4/lz4.git
[submodule "bzip2"]
path = native/jni/external/bzip2
path = native/src/external/bzip2
url = https://github.com/nemequ/bzip2.git
[submodule "xz"]
path = native/jni/external/xz
path = native/src/external/xz
url = https://github.com/xz-mirror/xz.git
[submodule "nanopb"]
path = native/jni/external/nanopb
url = https://github.com/nanopb/nanopb.git
[submodule "mincrypt"]
path = native/jni/external/mincrypt
url = https://github.com/topjohnwu/mincrypt.git
[submodule "pcre"]
path = native/jni/external/pcre
path = native/src/external/pcre
url = https://android.googlesource.com/platform/external/pcre
[submodule "libcxx"]
path = native/jni/external/libcxx
path = native/src/external/libcxx
url = https://github.com/topjohnwu/libcxx.git
[submodule "zlib"]
path = native/jni/external/zlib
path = native/src/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 "zopfli"]
path = native/src/external/zopfli
url = https://github.com/google/zopfli.git
[submodule "cxx-rs"]
path = native/src/external/cxx-rs
url = https://github.com/topjohnwu/cxx.git
[submodule "lsplt"]
path = native/src/external/lsplt
url = https://github.com/LSPosed/LSPlt.git
[submodule "system_properties"]
path = native/src/external/system_properties
url = https://github.com/topjohnwu/system_properties.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
[submodule "cxx-rs"]
path = native/jni/external/cxx-rs
url = https://github.com/topjohnwu/cxx.git

View File

@@ -6,7 +6,7 @@
## Introduction
Magisk is a suite of open source software for customizing Android, supporting devices higher than Android 5.0.<br>
Magisk is a suite of open source software for customizing Android, supporting devices higher than Android 6.0.<br>
Some highlight features:
- **MagiskSU**: Provide root access for applications
@@ -18,16 +18,16 @@ Some highlight features:
[Github](https://github.com/topjohnwu/Magisk/) is the only source where you can get official Magisk information and downloads.
[![](https://img.shields.io/badge/Magisk-v25.1-blue)](https://github.com/topjohnwu/Magisk/releases/tag/v25.1)
[![](https://img.shields.io/badge/Magisk%20Beta-v25.1-blue)](https://github.com/topjohnwu/Magisk/releases/tag/v25.1)
[![](https://img.shields.io/badge/Magisk-v26.4-blue)](https://github.com/topjohnwu/Magisk/releases/tag/v26.4)
[![](https://img.shields.io/badge/Magisk%20Beta-v26.4-blue)](https://github.com/topjohnwu/Magisk/releases/tag/v26.4)
[![](https://img.shields.io/badge/Magisk-Canary-red)](https://raw.githubusercontent.com/topjohnwu/magisk-files/canary/app-release.apk)
[![](https://img.shields.io/badge/Magisk-Debug-red)](https://raw.githubusercontent.com/topjohnwu/magisk-files/canary/app-debug.apk)
## Useful Links
- [Installation Instruction](https://topjohnwu.github.io/Magisk/install.html)
- [Building and Development](https://topjohnwu.github.io/Magisk/build.html)
- [Magisk Documentation](https://topjohnwu.github.io/Magisk/)
- [Magisk Troubleshoot Wiki](https://www.didgeridoohan.com/magisk/HomePage) (by [@Didgeridoohan](https://github.com/Didgeridoohan))
## Bug Reports
@@ -37,30 +37,6 @@ For installation issues, upload both boot image and install logs.<br>
For Magisk issues, upload boot logcat or dmesg.<br>
For Magisk app crashes, record and upload the logcat when the crash occurs.
## Building and Development
- 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)
- Configure to use the JDK bundled in Android Studio:
- 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`)
- To start development, open the project with Android Studio. The IDE can be used for both app (Kotlin/Java) and native sources.
- Optionally, set custom configs with `config.prop`. A sample `config.prop.sample` is provided.
## Signing and Distribution
- The certificate of the key used to sign the final Magisk APK product is also directly embedded into some executables. In release builds, Magisk's root daemon will enforce this certificate check and reject and forcefully uninstall any non-matching Magisk apps to protect users from malicious and unverified Magisk APKs.
- To do any development on Magisk itself, switch to an **official debug build and reinstall Magisk** to bypass the signature check.
- To distribute your own Magisk builds signed with your own keys, set your signing configs in `config.prop`.
- Check [Google's Documentation](https://developer.android.com/studio/publish/app-signing.html#generate-key) for more details on generating your own key.
## Translation Contributions
Default string resources for the Magisk app and its stub APK are located here:

7
app/.gitignore vendored
View File

@@ -3,10 +3,9 @@
/local.properties
.idea/
/build
app/release
*.hprof
.externalNativeBuild/
*.apk
src/main/assets
src/main/jniLibs
src/main/resources
src/*/assets
src/*/jniLibs
src/*/resources

View File

@@ -26,7 +26,10 @@ android {
vectorDrawables.useSupportLibrary = true
versionName = Config.version
versionCode = Config.versionCode
ndk.abiFilters += listOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64")
ndk {
abiFilters += listOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64")
debugSymbolLevel = "FULL"
}
}
buildTypes {
@@ -39,11 +42,13 @@ android {
buildFeatures {
dataBinding = true
aidl = true
}
packagingOptions {
packaging {
resources {
excludes += "/META-INF/*"
excludes += "/META-INF/versions/**"
excludes += "/org/bouncycastle/**"
excludes += "/kotlin/**"
excludes += "/kotlinx/**"
@@ -52,9 +57,6 @@ android {
excludes += "/*.bin"
excludes += "/*.json"
}
jniLibs {
keepDebugSymbols += "**/*.so"
}
}
}
@@ -72,13 +74,13 @@ dependencies {
implementation("com.github.topjohnwu:indeterminate-checkbox:1.0.7")
implementation("com.github.topjohnwu:lz4-java:1.7.1")
implementation("com.jakewharton.timber:timber:5.0.1")
implementation("org.bouncycastle:bcpkix-jdk18on:1.71")
implementation("dev.rikka.rikkax.layoutinflater:layoutinflater:1.2.0")
implementation("dev.rikka.rikkax.insets:insets:1.2.0")
implementation("dev.rikka.rikkax.recyclerview:recyclerview-ktx:1.3.1")
implementation("org.bouncycastle:bcpkix-jdk18on:1.77")
implementation("dev.rikka.rikkax.layoutinflater:layoutinflater:1.3.0")
implementation("dev.rikka.rikkax.insets:insets:1.3.0")
implementation("dev.rikka.rikkax.recyclerview:recyclerview-ktx:1.3.2")
implementation("io.noties.markwon:core:4.6.2")
val vLibsu = "5.0.2"
val vLibsu = "5.2.2"
implementation("com.github.topjohnwu.libsu:core:${vLibsu}")
implementation("com.github.topjohnwu.libsu:service:${vLibsu}")
implementation("com.github.topjohnwu.libsu:nio:${vLibsu}")
@@ -88,33 +90,32 @@ dependencies {
implementation("com.squareup.retrofit2:converter-moshi:${vRetrofit}")
implementation("com.squareup.retrofit2:converter-scalars:${vRetrofit}")
val vOkHttp = "4.9.3"
val vOkHttp = "4.12.0"
implementation("com.squareup.okhttp3:okhttp:${vOkHttp}")
implementation("com.squareup.okhttp3:logging-interceptor:${vOkHttp}")
implementation("com.squareup.okhttp3:okhttp-dnsoverhttps:${vOkHttp}")
val vMoshi = "1.13.0"
val vMoshi = "1.15.0"
implementation("com.squareup.moshi:moshi:${vMoshi}")
kapt("com.squareup.moshi:moshi-kotlin-codegen:${vMoshi}")
val vRoom = "2.5.0-alpha02"
val vRoom = "2.6.1"
implementation("androidx.room:room-runtime:${vRoom}")
implementation("androidx.room:room-ktx:${vRoom}")
kapt("androidx.room:room-compiler:${vRoom}")
val vNav = "2.5.0-rc01"
val vNav = "2.7.6"
implementation("androidx.navigation:navigation-fragment-ktx:${vNav}")
implementation("androidx.navigation:navigation-ui-ktx:${vNav}")
implementation("androidx.biometric:biometric:1.1.0")
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
implementation("androidx.appcompat:appcompat:1.4.2")
implementation("androidx.preference:preference:1.2.0")
implementation("androidx.recyclerview:recyclerview:1.2.1")
implementation("androidx.fragment:fragment-ktx:1.4.1")
implementation("androidx.appcompat:appcompat:1.6.1")
implementation("androidx.recyclerview:recyclerview:1.3.2")
implementation("androidx.fragment:fragment-ktx:1.6.2")
implementation("androidx.transition:transition:1.4.1")
implementation("androidx.core:core-ktx:1.8.0")
implementation("androidx.core:core-splashscreen:1.0.0-rc01")
implementation("com.google.android.material:material:1.6.1")
implementation("androidx.core:core-ktx:1.12.0")
implementation("androidx.core:core-splashscreen:1.0.1")
implementation("androidx.profileinstaller:profileinstaller:1.3.1")
implementation("com.google.android.material:material:1.11.0")
}

View File

@@ -11,12 +11,15 @@
-assumenosideeffects class java.util.Objects {
public static ** requireNonNull(...);
}
-assumenosideeffects public class kotlin.coroutines.jvm.internal.DebugMetadataKt {
private static ** getDebugMetadataAnnotation(...) return null;
}
# Stub
-keep class com.topjohnwu.magisk.core.App { <init>(java.lang.Object); }
-keepclassmembers class androidx.appcompat.app.AppCompatDelegateImpl {
boolean mActivityHandlesUiModeChecked;
boolean mActivityHandlesUiMode;
boolean mActivityHandlesConfigFlagsChecked;
int mActivityHandlesConfigFlags;
}
# main
@@ -30,6 +33,17 @@
public void d(**);
}
# https://github.com/square/retrofit/issues/3751#issuecomment-1192043644
# Keep generic signature of Call, Response (R8 full mode strips signatures from non-kept items).
-keep,allowobfuscation,allowshrinking interface retrofit2.Call
-keep,allowobfuscation,allowshrinking class retrofit2.Response
# With R8 full mode generic signatures are stripped for classes that are not
# kept. Suspend functions are wrapped in continuations where the type argument
# is used.
-keep,allowobfuscation,allowshrinking class kotlin.coroutines.Continuation
# Excessive obfuscation
-repackageclasses 'a'
-allowaccessmodification

View File

@@ -7,7 +7,3 @@ setupCommon()
android {
namespace = "com.topjohnwu.shared"
}
dependencies {
api("io.michaelrocks:paranoid-core:0.3.7")
}

View File

@@ -1,14 +1,18 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.topjohnwu.shared"
android:installLocation="internalOnly">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
<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.POST_NOTIFICATIONS" />
<uses-permission
android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="29" />
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="29"

View File

@@ -2,9 +2,6 @@ package com.topjohnwu.magisk;
import android.content.Context;
import io.michaelrocks.paranoid.Obfuscate;
@Obfuscate
public class ProviderInstaller {
public static boolean install(Context context) {

View File

@@ -3,6 +3,7 @@ package com.topjohnwu.magisk;
import static android.os.Build.VERSION.SDK_INT;
import static android.os.ParcelFileDescriptor.MODE_READ_ONLY;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
@@ -11,6 +12,7 @@ import android.content.res.AssetManager;
import android.content.res.Resources;
import android.content.res.loader.ResourcesLoader;
import android.content.res.loader.ResourcesProvider;
import android.os.Build;
import android.os.ParcelFileDescriptor;
import java.io.File;
@@ -18,9 +20,6 @@ import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Map;
import io.michaelrocks.paranoid.Obfuscate;
@Obfuscate
public class StubApk {
private static File dynDir;
private static Method addAssetPath;
@@ -28,7 +27,7 @@ public class StubApk {
private static File getDynDir(ApplicationInfo info) {
if (dynDir == null) {
final String dataDir;
if (SDK_INT >= 24) {
if (SDK_INT >= Build.VERSION_CODES.N) {
// Use device protected path to allow directBootAware
dataDir = info.deviceProtectedDataDir;
} else {
@@ -56,12 +55,24 @@ public class StubApk {
return new File(getDynDir(info), "update.apk");
}
public static void addAssetPath(Resources res, String path) {
if (SDK_INT >= 30) {
try (var fd = ParcelFileDescriptor.open(new File(path), MODE_READ_ONLY)) {
@TargetApi(Build.VERSION_CODES.R)
private static ResourcesLoader getResourcesLoader(File path) throws IOException {
var loader = new ResourcesLoader();
loader.addProvider(ResourcesProvider.loadFromApk(fd));
res.addLoaders(loader);
ResourcesProvider provider;
if (path.isDirectory()) {
provider = ResourcesProvider.loadFromDirectory(path.getPath(), null);
} else {
var fd = ParcelFileDescriptor.open(path, MODE_READ_ONLY);
provider = ResourcesProvider.loadFromApk(fd);
}
loader.addProvider(provider);
return loader;
}
public static void addAssetPath(Resources res, String path) {
if (SDK_INT >= Build.VERSION_CODES.R) {
try {
res.addLoaders(getResourcesLoader(new File(path)));
} catch (IOException ignored) {}
} else {
AssetManager asset = res.getAssets();

View File

@@ -25,9 +25,6 @@ import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import io.michaelrocks.paranoid.Obfuscate;
@Obfuscate
public final class APKInstall {
public static void transfer(InputStream in, OutputStream out) throws IOException {
@@ -39,6 +36,16 @@ public final class APKInstall {
}
}
public static void registerReceiver(
Context context, BroadcastReceiver receiver, IntentFilter filter) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// noinspection InlinedApi
context.registerReceiver(receiver, filter, Context.RECEIVER_NOT_EXPORTED);
} else {
context.registerReceiver(receiver, filter);
}
}
public static Session startSession(Context context) {
return startSession(context, null, null, null);
}
@@ -51,9 +58,9 @@ public final class APKInstall {
// If pkg is not null, look for package added event
var filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
filter.addDataScheme("package");
context.registerReceiver(receiver, filter);
registerReceiver(context, receiver, filter);
}
context.registerReceiver(receiver, new IntentFilter(receiver.sessionId));
registerReceiver(context, receiver, new IntentFilter(receiver.sessionId));
return receiver;
}
@@ -94,28 +101,26 @@ public final class APKInstall {
} else if (sessionId.equals(intent.getAction())) {
int status = intent.getIntExtra(EXTRA_STATUS, STATUS_FAILURE_INVALID);
switch (status) {
case STATUS_PENDING_USER_ACTION:
case STATUS_PENDING_USER_ACTION ->
userAction = intent.getParcelableExtra(Intent.EXTRA_INTENT);
break;
case STATUS_SUCCESS:
case STATUS_SUCCESS -> {
if (packageName == null) {
onSuccess(context);
}
break;
default:
int id = intent.getIntExtra(EXTRA_SESSION_ID, 0);
if (id > 0) {
var installer = context.getPackageManager().getPackageInstaller();
var info = installer.getSessionInfo(id);
if (info != null) {
installer.abandonSession(info.getSessionId());
}
default -> {
int id = intent.getIntExtra(EXTRA_SESSION_ID, 0);
var installer = context.getPackageManager().getPackageInstaller();
try {
installer.abandonSession(id);
} catch (SecurityException ignored) {
}
if (onFailure != null) {
onFailure.run();
}
context.getApplicationContext().unregisterReceiver(this);
}
}
latch.countDown();
}
}

View File

@@ -1,5 +1,7 @@
package com.topjohnwu.magisk.utils;
import android.os.Process;
import java.io.File;
import java.io.IOException;
import java.net.URL;
@@ -14,8 +16,8 @@ public class DynamicClassLoader extends BaseDexClassLoader {
}
public DynamicClassLoader(File apk, ClassLoader parent) {
// Set optimizedDirectory to null to bypass DexFile's security checks
super(apk.getPath(), null, null, parent);
// Set optimizedDirectory to null for RootService to bypass DexFile's security checks
super(apk.getPath(), Process.myUid() == 0 ? null : apk.getParentFile(), null, parent);
}
@Override

View File

@@ -2,12 +2,21 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<permission
android:name="${applicationId}.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION"
android:protectionLevel="signature"
tools:node="remove" />
<uses-permission
android:name="${applicationId}.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION"
tools:node="remove" />
<application
android:name=".core.App"
android:extractNativeLibs="true"
android:icon="@drawable/ic_launcher"
android:multiArch="true"
tools:ignore="UnusedAttribute,GoogleAppIndexingWarning">
tools:ignore="UnusedAttribute,GoogleAppIndexingWarning"
tools:remove="android:appComponentFactory">
<activity
android:name=".ui.MainActivity"
@@ -53,7 +62,8 @@
<service
android:name=".core.download.DownloadService"
android:exported="false" />
android:exported="false"
android:foregroundServiceType="dataSync" />
<service
android:name=".core.JobService"
@@ -72,11 +82,15 @@
android:name="androidx.room.MultiInstanceInvalidationService"
tools:node="remove" />
<!-- We don't need emoji compat -->
<!-- We handle initialization ourselves -->
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
android:exported="false"
tools:node="remove" />
<!-- We handle profile installation ourselves -->
<receiver
android:name="androidx.profileinstaller.ProfileInstallReceiver"
tools:node="remove" />
</application>

View File

@@ -5,13 +5,14 @@ import android.view.KeyEvent
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.MenuProvider
import androidx.databinding.DataBindingUtil
import androidx.databinding.OnRebindCallback
import androidx.databinding.ViewDataBinding
import androidx.fragment.app.Fragment
import androidx.lifecycle.Lifecycle
import androidx.navigation.NavDirections
import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.ktx.startAnimations
abstract class BaseFragment<Binding : ViewDataBinding> : Fragment(), ViewModelHolder {
@@ -37,6 +38,9 @@ abstract class BaseFragment<Binding : ViewDataBinding> : Fragment(), ViewModelHo
it.setVariable(BR.viewModel, viewModel)
it.lifecycleOwner = viewLifecycleOwner
}
if (this is MenuProvider) {
activity?.addMenuProvider(this, viewLifecycleOwner, Lifecycle.State.STARTED)
}
savedInstanceState?.let { viewModel.onRestoreState(it) }
return binding.root
}
@@ -89,5 +93,4 @@ abstract class BaseFragment<Binding : ViewDataBinding> : Fragment(), ViewModelHo
fun NavDirections.navigate() {
navigation?.currentDestination?.getAction(actionId)?.let { navigation!!.navigate(this) }
}
}

View File

@@ -1,18 +1,18 @@
package com.topjohnwu.magisk.arch
import android.Manifest.permission.REQUEST_INSTALL_PACKAGES
import android.Manifest.permission.WRITE_EXTERNAL_STORAGE
import android.Manifest.permission.*
import android.annotation.SuppressLint
import android.os.Bundle
import androidx.databinding.PropertyChangeRegistry
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.navigation.NavDirections
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.databinding.ObservableHost
import com.topjohnwu.magisk.events.BackPressEvent
import com.topjohnwu.magisk.events.DialogBuilder
import com.topjohnwu.magisk.events.DialogEvent
import com.topjohnwu.magisk.events.NavigationEvent
import com.topjohnwu.magisk.events.PermissionEvent
import com.topjohnwu.magisk.events.SnackbarEvent
@@ -53,15 +53,25 @@ abstract class BaseViewModel : ViewModel(), ObservableHost {
}
}
@SuppressLint("InlinedApi")
inline fun withPostNotificationPermission(crossinline callback: () -> Unit) {
withPermission(POST_NOTIFICATIONS) {
if (!it) {
SnackbarEvent(R.string.post_notifications_denied).publish()
} else {
callback()
}
}
}
fun back() = BackPressEvent().publish()
fun <Event : ViewEvent> Event.publish() {
fun ViewEvent.publish() {
_viewEvents.postValue(this)
}
fun <Event : ViewEventWithScope> Event.publish() {
scope = viewModelScope
_viewEvents.postValue(this)
fun DialogBuilder.show() {
DialogEvent(this).publish()
}
fun NavDirections.navigate(pop: Boolean = false) {

View File

@@ -20,14 +20,16 @@ abstract class NavigationActivity<Binding : ViewDataBinding> : UIActivity<Bindin
val navigation: NavController get() = navHostFragment.navController
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
return currentFragment?.onKeyEvent(event) == true || super.dispatchKeyEvent(event)
return if (binded && currentFragment?.onKeyEvent(event) == true) true else super.dispatchKeyEvent(event)
}
override fun onBackPressed() {
if (currentFragment?.onBackPressed()?.not() == true) {
if (binded) {
if (currentFragment?.onBackPressed() == false) {
super.onBackPressed()
}
}
}
fun NavDirections.navigate() {
navigation.navigate(this)

View File

@@ -5,16 +5,20 @@ import android.graphics.Color
import android.os.Build
import android.os.Bundle
import android.view.View
import android.view.ViewGroup
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.interpolator.view.animation.FastOutSlowInInterpolator
import androidx.transition.AutoTransition
import androidx.transition.TransitionManager
import com.google.android.material.snackbar.Snackbar
import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.core.base.BaseActivity
import com.topjohnwu.magisk.widget.Pre23CardViewBackgroundColorFixLayoutInflaterListener
import rikka.insets.WindowInsetsHelper
import rikka.layoutinflater.view.LayoutInflaterFactory
@@ -23,6 +27,8 @@ abstract class UIActivity<Binding : ViewDataBinding> : BaseActivity(), ViewModel
protected lateinit var binding: Binding
protected abstract val layoutRes: Int
protected val binded get() = ::binding.isInitialized
open val snackbarView get() = binding.root
open val snackbarAnchorView: View? get() = null
@@ -33,11 +39,6 @@ abstract class UIActivity<Binding : ViewDataBinding> : BaseActivity(), ViewModel
override fun onCreate(savedInstanceState: Bundle?) {
layoutInflater.factory2 = LayoutInflaterFactory(delegate)
.addOnViewCreatedListener(WindowInsetsHelper.LISTENER)
.apply {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
this.addOnViewCreatedListener(Pre23CardViewBackgroundColorFixLayoutInflaterListener.getInstance())
}
}
super.onCreate(savedInstanceState)
@@ -101,3 +102,14 @@ abstract class UIActivity<Binding : ViewDataBinding> : BaseActivity(), ViewModel
else -> Unit
}
}
fun ViewGroup.startAnimations() {
val transition = AutoTransition()
.setInterpolator(FastOutSlowInInterpolator())
.setDuration(400)
.excludeTarget(R.id.main_toolbar, true)
TransitionManager.beginDelayedTransition(
this,
transition
)
}

View File

@@ -1,7 +1,6 @@
package com.topjohnwu.magisk.arch
import android.content.Context
import kotlinx.coroutines.CoroutineScope
/**
* Class for passing events from ViewModels to Activities/Fragments
@@ -9,10 +8,6 @@ import kotlinx.coroutines.CoroutineScope
*/
abstract class ViewEvent
abstract class ViewEventWithScope: ViewEvent() {
lateinit var scope: CoroutineScope
}
interface ContextExecutor {
operator fun invoke(context: Context)
}

View File

@@ -34,7 +34,8 @@ object VMFactory : ViewModelProvider.Factory {
HomeViewModel::class.java -> HomeViewModel(ServiceLocator.networkService)
LogViewModel::class.java -> LogViewModel(ServiceLocator.logRepo)
SuperuserViewModel::class.java -> SuperuserViewModel(ServiceLocator.policyDB)
InstallViewModel::class.java -> InstallViewModel(ServiceLocator.networkService)
InstallViewModel::class.java ->
InstallViewModel(ServiceLocator.networkService, ServiceLocator.markwon)
SuRequestViewModel::class.java ->
SuRequestViewModel(ServiceLocator.policyDB, ServiceLocator.timeoutPrefs)
else -> modelClass.newInstance()

View File

@@ -5,14 +5,25 @@ import android.app.Application
import android.content.Context
import android.content.res.Configuration
import android.os.Bundle
import androidx.profileinstaller.ProfileInstaller
import com.topjohnwu.magisk.BuildConfig
import com.topjohnwu.magisk.StubApk
import com.topjohnwu.magisk.core.di.ServiceLocator
import com.topjohnwu.magisk.core.utils.*
import com.topjohnwu.magisk.core.utils.DispatcherExecutor
import com.topjohnwu.magisk.core.utils.NetworkObserver
import com.topjohnwu.magisk.core.utils.ProcessLifecycle
import com.topjohnwu.magisk.core.utils.RootUtils
import com.topjohnwu.magisk.core.utils.ShellInit
import com.topjohnwu.magisk.core.utils.refreshLocale
import com.topjohnwu.magisk.core.utils.setConfig
import com.topjohnwu.magisk.ui.surequest.SuRequestActivity
import com.topjohnwu.magisk.view.Notifications
import com.topjohnwu.superuser.Shell
import com.topjohnwu.superuser.internal.UiThreadHandler
import com.topjohnwu.superuser.ipc.RootService
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import timber.log.Timber
import java.lang.ref.WeakReference
import kotlin.system.exitProcess
@@ -70,6 +81,18 @@ open class App() : Application() {
refreshLocale()
resources.patch()
Notifications.setup()
}
override fun onCreate() {
super.onCreate()
ProcessLifecycle.init(this)
NetworkObserver.init(this)
if (!BuildConfig.DEBUG && !isRunningAsStub) {
GlobalScope.launch(Dispatchers.IO) {
ProfileInstaller.writeProfile(this@App)
}
}
}
override fun onConfigurationChanged(newConfig: Configuration) {

View File

@@ -1,21 +1,19 @@
package com.topjohnwu.magisk.core
import android.annotation.SuppressLint
import android.content.SharedPreferences
import android.util.Xml
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.content.edit
import com.topjohnwu.magisk.BuildConfig
import com.topjohnwu.magisk.core.di.AppContext
import com.topjohnwu.magisk.core.di.ServiceLocator
import com.topjohnwu.magisk.core.ktx.writeTo
import com.topjohnwu.magisk.core.repository.BoolDBPropertyNoWrite
import com.topjohnwu.magisk.core.repository.DBConfig
import com.topjohnwu.magisk.core.repository.PreferenceConfig
import com.topjohnwu.magisk.core.utils.refreshLocale
import com.topjohnwu.magisk.ui.theme.Theme
import kotlinx.coroutines.GlobalScope
import org.xmlpull.v1.XmlPullParser
import java.io.File
import java.io.InputStream
object Config : PreferenceConfig, DBConfig {
@@ -24,13 +22,12 @@ object Config : PreferenceConfig, DBConfig {
override val context get() = ServiceLocator.deContext
override val coroutineScope get() = GlobalScope
@get:SuppressLint("ApplySharedPref")
val prefsFile: File get() {
// Flush prefs to disk
prefs.edit().apply {
remove(Key.ASKED_HOME)
}.commit()
return File("${context.filesDir.parent}/shared_prefs", "${fileName}.xml")
private val prefsFile = File("${context.filesDir.parent}/shared_prefs", "${fileName}.xml")
@SuppressLint("ApplySharedPref")
fun getPrefsFile(): File {
prefs.edit().remove(Key.ASKED_HOME).commit()
return prefsFile
}
object Key {
@@ -117,7 +114,6 @@ object Config : PreferenceConfig, DBConfig {
@JvmField var keepVerity = false
@JvmField var keepEnc = false
@JvmField var patchVbmeta = false
@JvmField var recovery = false
var bootId by preference(Key.BOOT_ID, "")
@@ -136,7 +132,15 @@ object Config : PreferenceConfig, DBConfig {
var themeOrdinal by preference(Key.THEME_ORDINAL, Theme.Piplup.ordinal)
var suReAuth by preference(Key.SU_REAUTH, false)
var suTapjack by preference(Key.SU_TAPJACK, true)
var checkUpdate by preference(Key.CHECK_UPDATES, true)
private var checkUpdatePrefs by preference(Key.CHECK_UPDATES, true)
var checkUpdate
get() = checkUpdatePrefs
set(value) {
if (checkUpdatePrefs != value) {
checkUpdatePrefs = value
JobService.schedule(AppContext)
}
}
var doh by preference(Key.DOH, false)
var showSystemApp by preference(Key.SHOW_SYSTEM_APP, false)
@@ -152,7 +156,12 @@ object Config : PreferenceConfig, DBConfig {
var rootMode by dbSettings(Key.ROOT_ACCESS, Value.ROOT_ACCESS_APPS_AND_ADB)
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)
private var suBiometric by dbSettings(Key.SU_BIOMETRIC, false)
var userAuth
get() = Info.isDeviceSecure && suBiometric
set(value) {
suBiometric = value
}
var zygisk by dbSettings(Key.ZYGISK, false)
var denyList by BoolDBPropertyNoWrite(Key.DENYLIST, false)
var suManager by dbStrings(Key.SU_MANAGER, "", true)
@@ -163,9 +172,8 @@ object Config : PreferenceConfig, DBConfig {
fun load(pkg: String?) {
// Only try to load prefs when fresh install and a previous package name is set
if (pkg != null && prefs.all.isEmpty()) runCatching {
context.contentResolver.openInputStream(Provider.preferencesUri(pkg))?.use {
prefs.edit { parsePrefs(it) }
}
context.contentResolver.openInputStream(Provider.preferencesUri(pkg))?.writeTo(prefsFile)
return
}
prefs.edit {
@@ -182,52 +190,4 @@ object Config : PreferenceConfig, DBConfig {
}
}
}
private fun SharedPreferences.Editor.parsePrefs(input: InputStream) {
runCatching {
val parser = Xml.newPullParser()
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false)
parser.setInput(input, "UTF-8")
parser.nextTag()
parser.require(XmlPullParser.START_TAG, null, "map")
while (parser.next() != XmlPullParser.END_TAG) {
if (parser.eventType != XmlPullParser.START_TAG)
continue
val key: String = parser.getAttributeValue(null, "name")
fun value() = parser.getAttributeValue(null, "value")!!
when (parser.name) {
"string" -> {
parser.require(XmlPullParser.START_TAG, null, "string")
putString(key, parser.nextText())
parser.require(XmlPullParser.END_TAG, null, "string")
}
"boolean" -> {
parser.require(XmlPullParser.START_TAG, null, "boolean")
putBoolean(key, value().toBoolean())
parser.nextTag()
parser.require(XmlPullParser.END_TAG, null, "boolean")
}
"int" -> {
parser.require(XmlPullParser.START_TAG, null, "int")
putInt(key, value().toInt())
parser.nextTag()
parser.require(XmlPullParser.END_TAG, null, "int")
}
"long" -> {
parser.require(XmlPullParser.START_TAG, null, "long")
putLong(key, value().toLong())
parser.nextTag()
parser.require(XmlPullParser.END_TAG, null, "long")
}
"float" -> {
parser.require(XmlPullParser.START_TAG, null, "int")
putFloat(key, value().toFloat())
parser.nextTag()
parser.require(XmlPullParser.END_TAG, null, "int")
}
else -> parser.next()
}
}
}
}
}

View File

@@ -15,8 +15,7 @@ object Const {
else Build.SUPPORTED_32_BIT_ABIS.firstOrNull()
// Paths
lateinit var MAGISKTMP: String
val MAGISK_PATH get() = "$MAGISKTMP/modules"
const val MAGISK_PATH = "/data/adb/modules"
const val TMPDIR = "/dev/tmp"
const val MAGISK_LOG = "/cache/magisk.log"

View File

@@ -13,8 +13,8 @@ import android.util.DisplayMetrics
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.StubApk
import com.topjohnwu.magisk.core.di.AppContext
import com.topjohnwu.magisk.core.ktx.unwrap
import com.topjohnwu.magisk.core.utils.syncLocale
import com.topjohnwu.magisk.ktx.unwrap
lateinit var AppApkPath: String

View File

@@ -1,14 +1,12 @@
package com.topjohnwu.magisk.core
import android.os.Build
import androidx.lifecycle.LiveData
import android.app.KeyguardManager
import androidx.lifecycle.MutableLiveData
import com.topjohnwu.magisk.StubApk
import com.topjohnwu.magisk.core.di.AppContext
import com.topjohnwu.magisk.core.ktx.getProperty
import com.topjohnwu.magisk.core.model.UpdateInfo
import com.topjohnwu.magisk.core.repository.NetworkService
import com.topjohnwu.magisk.core.utils.net.NetworkObserver
import com.topjohnwu.magisk.ktx.getProperty
import com.topjohnwu.superuser.ShellUtils.fastCmd
val isRunningAsStub get() = Info.stub != null
@@ -28,30 +26,31 @@ object Info {
// Device state
@JvmStatic val env by lazy { loadState() }
@JvmField var isSAR = false
var legacySAR = false
var isAB = false
@JvmField val isZygiskEnabled = System.getenv("ZYGISK_ENABLED") == "1"
@JvmStatic val isFDE get() = crypto == "block"
@JvmField var ramdisk = false
@JvmField var vbmeta = false
var patchBootVbmeta = false
var crypto = ""
var noDataExec = false
var isRooted = 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: LiveData<Boolean> by lazy {
MutableLiveData(false).also { field ->
NetworkObserver.observe(AppContext) {
remote = EMPTY_REMOTE
field.postValue(it)
}
}
val isConnected = MutableLiveData(false)
val showSuperUser: Boolean get() {
return env.isActive && (Const.USER_ID == 0
|| Config.suMultiuserMode == Config.Value.MULTIUSER_MODE_USER)
}
val isDeviceSecure get() =
AppContext.getSystemService(KeyguardManager::class.java).isDeviceSecure
private fun loadState(): Env {
val v = fastCmd("magisk -v").split(":".toRegex())
return Env(
@@ -67,9 +66,10 @@ object Info {
) {
val versionCode = when {
code < Const.Version.MIN_VERCODE -> -1
else -> if (isRooted) code else -1
isRooted -> code
else -> -1
}
val isUnsupported = code > 0 && code < Const.Version.MIN_VERCODE
val isActive = versionCode >= 0
val isActive = versionCode > 0
}
}

View File

@@ -33,7 +33,7 @@ class JobService : BaseJobService() {
svc.fetchUpdate()?.let {
Info.remote = it
if (Info.env.isActive && BuildConfig.VERSION_CODE < it.magisk.versionCode)
Notifications.updateAvailable(this)
Notifications.updateAvailable()
}
}

View File

@@ -6,17 +6,23 @@ import android.os.ParcelFileDescriptor
import android.os.ParcelFileDescriptor.MODE_READ_ONLY
import com.topjohnwu.magisk.core.base.BaseProvider
import com.topjohnwu.magisk.core.su.SuCallbackHandler
import com.topjohnwu.magisk.core.su.TestHandler
class Provider : BaseProvider() {
override fun call(method: String, arg: String?, extras: Bundle?): Bundle? {
return when (method) {
SuCallbackHandler.LOG, SuCallbackHandler.NOTIFY -> {
SuCallbackHandler.run(context!!, method, extras)
return Bundle.EMPTY
Bundle.EMPTY
}
else -> TestHandler.run(method)
}
}
override fun openFile(uri: Uri, mode: String): ParcelFileDescriptor? {
return when (uri.encodedPath ?: return null) {
"/prefs_file" -> ParcelFileDescriptor.open(Config.prefsFile, MODE_READ_ONLY)
"/prefs_file" -> ParcelFileDescriptor.open(Config.getPrefsFile(), MODE_READ_ONLY)
else -> super.openFile(uri, mode)
}
}

View File

@@ -51,7 +51,7 @@ open class Receiver : BaseReceiver() {
@Suppress("DEPRECATION")
val installer = context.packageManager.getInstallerPackageName(context.packageName)
if (installer == context.packageName) {
Notifications.updateDone(context)
Notifications.updateDone()
}
}
}

View File

@@ -1,5 +1,6 @@
package com.topjohnwu.magisk.core.base
import android.Manifest.permission.POST_NOTIFICATIONS
import android.Manifest.permission.REQUEST_INSTALL_PACKAGES
import android.Manifest.permission.WRITE_EXTERNAL_STORAGE
import android.app.Activity
@@ -17,10 +18,11 @@ import androidx.activity.result.contract.ActivityResultContracts.RequestPermissi
import androidx.appcompat.app.AppCompatActivity
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.isRunningAsStub
import com.topjohnwu.magisk.core.ktx.reflectField
import com.topjohnwu.magisk.core.ktx.toast
import com.topjohnwu.magisk.core.utils.RequestAuthentication
import com.topjohnwu.magisk.core.utils.RequestInstall
import com.topjohnwu.magisk.core.wrap
import com.topjohnwu.magisk.ktx.reflectField
import com.topjohnwu.magisk.utils.Utils
interface ContentResultCallback: ActivityResultCallback<Uri>, Parcelable {
fun onActivityLaunch() {}
@@ -35,9 +37,17 @@ abstract class BaseActivity : AppCompatActivity() {
permissionCallback?.invoke(it)
permissionCallback = null
}
private var installCallback: ((Boolean) -> Unit)? = null
private val requestInstall = registerForActivityResult(RequestInstall()) {
permissionCallback?.invoke(it)
permissionCallback = null
installCallback?.invoke(it)
installCallback = null
}
var authenticateCallback: ((Boolean) -> Unit)? = null
val requestAuthenticate = registerForActivityResult(RequestAuthentication()) {
authenticateCallback?.invoke(it)
authenticateCallback = null
}
private var contentCallback: ContentResultCallback? = null
@@ -52,9 +62,7 @@ abstract class BaseActivity : AppCompatActivity() {
val realCallingPackage: String? get() {
callingPackage?.let { return it }
if (Build.VERSION.SDK_INT >= 22) {
mReferrerField.get(this)?.let { return it as String }
}
return null
}
@@ -67,8 +75,8 @@ abstract class BaseActivity : AppCompatActivity() {
// Overwrite private members to avoid nasty "false" stack traces being logged
val delegate = delegate
val clz = delegate.javaClass
clz.reflectField("mActivityHandlesUiModeChecked").set(delegate, true)
clz.reflectField("mActivityHandlesUiMode").set(delegate, false)
clz.reflectField("mActivityHandlesConfigFlagsChecked").set(delegate, true)
clz.reflectField("mActivityHandlesConfigFlags").set(delegate, 0)
}
contentCallback = savedInstanceState?.getParcelable(CONTENT_CALLBACK_KEY)
super.onCreate(savedInstanceState)
@@ -82,15 +90,23 @@ abstract class BaseActivity : AppCompatActivity() {
}
fun withPermission(permission: String, callback: (Boolean) -> Unit) {
if (permission == WRITE_EXTERNAL_STORAGE && Build.VERSION.SDK_INT >= 30) {
// We do not need external rw on 30+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R &&
permission == WRITE_EXTERNAL_STORAGE) {
// We do not need external rw on R+
callback(true)
return
}
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU &&
permission == POST_NOTIFICATIONS) {
// All apps have notification permissions before T
callback(true)
return
}
permissionCallback = callback
if (permission == REQUEST_INSTALL_PACKAGES) {
installCallback = callback
requestInstall.launch(Unit)
} else {
permissionCallback = callback
requestPermission.launch(permission)
}
}
@@ -101,7 +117,7 @@ abstract class BaseActivity : AppCompatActivity() {
getContent.launch(type)
callback.onActivityLaunch()
} catch (e: ActivityNotFoundException) {
Utils.toast(R.string.app_not_found, Toast.LENGTH_SHORT)
toast(R.string.app_not_found, Toast.LENGTH_SHORT)
}
}

View File

@@ -12,15 +12,12 @@ private const val FILE = "file"
interface GithubPageServices {
@GET("{$FILE}")
suspend fun fetchUpdateJSON(@Path(FILE) file: String): UpdateInfo
@GET
suspend fun fetchUpdateJSON(@Url file: String): UpdateInfo
}
interface RawServices {
@GET
suspend fun fetchCustomUpdate(@Url url: String): UpdateInfo
@GET
@Streaming
suspend fun fetchFile(@Url url: String): ResponseBody

View File

@@ -1,15 +1,27 @@
package com.topjohnwu.magisk.core.data
import androidx.room.*
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
import com.topjohnwu.magisk.core.model.su.SuLog
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.util.*
@Database(version = 1, entities = [SuLog::class], exportSchema = false)
@Database(version = 2, entities = [SuLog::class], exportSchema = false)
abstract class SuLogDatabase : RoomDatabase() {
abstract fun suLogDao(): SuLogDao
companion object {
val MIGRATION_1_2 = object : Migration(1, 2) {
override fun migrate(database: SupportSQLiteDatabase) = with(database) {
execSQL("ALTER TABLE logs ADD COLUMN target INTEGER NOT NULL DEFAULT -1")
execSQL("ALTER TABLE logs ADD COLUMN context TEXT NOT NULL DEFAULT ''")
execSQL("ALTER TABLE logs ADD COLUMN gids TEXT NOT NULL DEFAULT ''")
}
}
}
}
@Dao

View File

@@ -1,6 +1,6 @@
package com.topjohnwu.magisk.core.data.magiskdb
import com.topjohnwu.magisk.ktx.await
import com.topjohnwu.magisk.core.ktx.await
import com.topjohnwu.superuser.Shell
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext

View File

@@ -9,9 +9,9 @@ import com.topjohnwu.magisk.core.data.SuLogDatabase
import com.topjohnwu.magisk.core.data.magiskdb.PolicyDao
import com.topjohnwu.magisk.core.data.magiskdb.SettingsDao
import com.topjohnwu.magisk.core.data.magiskdb.StringDao
import com.topjohnwu.magisk.core.ktx.deviceProtectedContext
import com.topjohnwu.magisk.core.repository.LogRepository
import com.topjohnwu.magisk.core.repository.NetworkService
import com.topjohnwu.magisk.ktx.deviceProtectedContext
import io.noties.markwon.Markwon
import io.noties.markwon.utils.NoCopySpannableFactory
@@ -39,13 +39,13 @@ object ServiceLocator {
NetworkService(
createApiService(retrofit, Const.Url.GITHUB_PAGE_URL),
createApiService(retrofit, Const.Url.GITHUB_RAW_URL),
createApiService(retrofit, Const.Url.GITHUB_API_URL)
)
}
}
private fun createSuLogDatabase(context: Context) =
Room.databaseBuilder(context, SuLogDatabase::class.java, "sulogs.db")
.addMigrations(SuLogDatabase.MIGRATION_1_2)
.fallbackToDestructiveMigration()
.build()

View File

@@ -1,5 +1,6 @@
package com.topjohnwu.magisk.core.download
import android.Manifest
import android.annotation.SuppressLint
import android.app.PendingIntent
import android.app.PendingIntent.*
@@ -13,26 +14,26 @@ import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.StubApk
import com.topjohnwu.magisk.core.ActivityTracker
import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.base.BaseActivity
import com.topjohnwu.magisk.core.intent
import com.topjohnwu.magisk.core.isRunningAsStub
import com.topjohnwu.magisk.core.ktx.*
import com.topjohnwu.magisk.core.tasks.HideAPK
import com.topjohnwu.magisk.core.utils.MediaStoreUtils
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream
import com.topjohnwu.magisk.ktx.copyAndClose
import com.topjohnwu.magisk.ktx.forEach
import com.topjohnwu.magisk.ktx.withStreams
import com.topjohnwu.magisk.ktx.writeTo
import com.topjohnwu.magisk.utils.APKInstall
import com.topjohnwu.magisk.view.Notifications
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import timber.log.Timber
import java.io.ByteArrayInputStream
import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
import java.util.Properties
import java.util.zip.ZipEntry
import java.util.zip.ZipFile
import java.util.zip.ZipInputStream
import java.util.zip.ZipOutputStream
@@ -46,14 +47,12 @@ class DownloadService : NotificationService() {
}
override fun onDestroy() {
super.onDestroy()
job.cancel()
}
private fun download(subject: Subject) {
update(subject.notifyId)
val coroutineScope = CoroutineScope(job + Dispatchers.IO)
coroutineScope.launch {
notifyUpdate(subject.notifyId)
CoroutineScope(job + Dispatchers.IO).launch {
try {
val stream = service.fetchFile(subject.url).toProgressStream(subject)
when (subject) {
@@ -62,7 +61,7 @@ class DownloadService : NotificationService() {
}
val activity = ActivityTracker.foreground
if (activity != null && subject.autoLaunch) {
remove(subject.notifyId)
notifyRemove(subject.notifyId)
subject.pendingIntent(activity)?.send()
} else {
notifyFinish(subject)
@@ -77,7 +76,7 @@ class DownloadService : NotificationService() {
}
}
private suspend fun handleApp(stream: InputStream, subject: Subject.App) {
private fun handleApp(stream: InputStream, subject: Subject.App) {
fun writeTee(output: OutputStream) {
val uri = MediaStoreUtils.getFile("${subject.title}.apk").uri
val external = uri.outputStream()
@@ -90,35 +89,34 @@ class DownloadService : NotificationService() {
// Download full APK to stub update path
writeTee(updateApk.outputStream())
if (Info.stub!!.version < subject.stub.versionCode) {
val zf = ZipFile(updateApk)
val prop = Properties()
prop.load(ByteArrayInputStream(zf.comment.toByteArray()))
val stubVersion = prop.getProperty("stubVersion").toIntOrNull() ?: -1
if (Info.stub!!.version < stubVersion) {
// Also upgrade stub
update(subject.notifyId) {
notifyUpdate(subject.notifyId) {
it.setProgress(0, 0, true)
.setContentTitle(getString(R.string.hide_app_title))
.setContentText("")
}
// Download
// Extract stub
val apk = subject.file.toFile()
service.fetchFile(subject.stub.link).byteStream().writeTo(apk)
zf.getInputStream(zf.getEntry("assets/stub.apk")).writeTo(apk)
zf.close()
// Patch and install
val session = APKInstall.startSession(this)
session.openStream(this).use {
val label = applicationInfo.nonLocalizedLabel
if (!HideAPK.patch(this, apk, it, packageName, label)) {
throw IOException("HideAPK patch error")
}
}
subject.intent = HideAPK.upgrade(this, apk)
?: throw IOException("HideAPK patch error")
apk.delete()
subject.intent = session.waitIntent()
} else {
ActivityTracker.foreground?.let {
// Relaunch the process if we are foreground
StubApk.restartProcess(it)
} ?: run {
// Or else kill the current process after posting notification
subject.intent = Notifications.selfLaunchIntent(this)
subject.intent = selfLaunchIntent()
subject.postDownload = { Runtime.getRuntime().exit(0) }
}
return
@@ -199,16 +197,19 @@ class DownloadService : NotificationService() {
fun getPendingIntent(context: Context, subject: Subject): PendingIntent {
val flag = FLAG_IMMUTABLE or FLAG_UPDATE_CURRENT or FLAG_ONE_SHOT
val intent = intent(context, subject)
return if (Build.VERSION.SDK_INT >= 26) {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
getForegroundService(context, REQUEST_CODE, intent, flag)
} else {
getService(context, REQUEST_CODE, intent, flag)
}
}
fun start(context: Context, subject: Subject) {
val app = context.applicationContext
if (Build.VERSION.SDK_INT >= 26) {
@SuppressLint("InlinedApi")
fun start(activity: BaseActivity, subject: Subject) {
activity.withPermission(Manifest.permission.POST_NOTIFICATIONS) {
// Always download regardless of notification permission status
val app = activity.applicationContext
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
app.startForegroundService(intent(app, subject))
} else {
app.startService(intent(app, subject))
@@ -216,3 +217,4 @@ class DownloadService : NotificationService() {
}
}
}
}

View File

@@ -2,12 +2,13 @@ package com.topjohnwu.magisk.core.download
import android.app.Notification
import android.content.Intent
import android.os.Build
import androidx.lifecycle.MutableLiveData
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.base.BaseService
import com.topjohnwu.magisk.core.di.ServiceLocator
import com.topjohnwu.magisk.core.ktx.synchronized
import com.topjohnwu.magisk.core.utils.ProgressInputStream
import com.topjohnwu.magisk.ktx.synchronized
import com.topjohnwu.magisk.view.Notifications
import okhttp3.ResponseBody
import java.io.InputStream
@@ -19,6 +20,8 @@ open class NotificationService : BaseService() {
protected val service get() = ServiceLocator.networkService
private var attachedNotificationId = 0
override fun onTaskRemoved(rootIntent: Intent?) {
super.onTaskRemoved(rootIntent)
notifications.forEach { Notifications.mgr.cancel(it.key) }
@@ -30,11 +33,11 @@ open class NotificationService : BaseService() {
val total = max.toFloat() / 1048576
val id = subject.notifyId
update(id) { it.setContentTitle(subject.title) }
notifyUpdate(id) { it.setContentTitle(subject.title) }
return ProgressInputStream(byteStream()) {
val progress = it.toFloat() / 1048576
update(id) { notification ->
notifyUpdate(id) { notification ->
if (max > 0) {
broadcast(progress / total, subject)
notification
@@ -49,7 +52,7 @@ open class NotificationService : BaseService() {
}
private fun finalNotify(id: Int, editor: (Notification.Builder) -> Unit): Int {
val notification = remove(id)?.also(editor) ?: return -1
val notification = notifyRemove(id)?.also(editor) ?: return -1
val newId = Notifications.nextId()
Notifications.mgr.notify(newId, notification.build())
return newId
@@ -73,28 +76,43 @@ open class NotificationService : BaseService() {
subject.pendingIntent(this)?.let { intent -> it.setContentIntent(intent) }
}
private fun create() = Notifications.progress(this, "")
private fun attachNotification(id: Int, notification: Notification) {
attachedNotificationId = id
startForeground(id, notification)
}
private fun updateForeground() {
private fun maybeDetachNotification(id: Int) : Boolean {
if (attachedNotificationId != id) return false
if (hasNotifications) {
val (id, notification) = notifications.entries.first()
startForeground(id, notification.build())
val (anotherId, notification) = notifications.entries.first()
// Attaching a new notification will remove the current showing one
attachNotification(anotherId, notification.build())
return true
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
stopForeground(STOP_FOREGROUND_REMOVE)
} else {
stopForeground(false)
@Suppress("DEPRECATION")
stopForeground(true)
}
attachedNotificationId = 0
return true
}
protected fun update(id: Int, editor: (Notification.Builder) -> Unit = {}) {
protected fun notifyUpdate(id: Int, editor: (Notification.Builder) -> Unit = {}) {
fun create() = Notifications.startProgress("")
val wasEmpty = !hasNotifications
val notification = notifications.getOrPut(id, ::create).also(editor)
val notification = notifications.getOrPut(id, ::create).also(editor).build()
if (wasEmpty)
updateForeground()
attachNotification(id, notification)
else
Notifications.mgr.notify(id, notification.build())
Notifications.mgr.notify(id, notification)
}
protected fun remove(id: Int): Notification.Builder? {
val n = notifications.remove(id)?.also { updateForeground() }
protected fun notifyRemove(id: Int): Notification.Builder? {
val n = notifications.remove(id)
if (n == null || !maybeDetachNotification(id))
Notifications.mgr.cancel(id)
return n
}

View File

@@ -9,11 +9,10 @@ import android.os.Parcelable
import androidx.core.net.toUri
import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.di.AppContext
import com.topjohnwu.magisk.core.ktx.cachedFile
import com.topjohnwu.magisk.core.model.MagiskJson
import com.topjohnwu.magisk.core.model.StubJson
import com.topjohnwu.magisk.core.model.module.OnlineModule
import com.topjohnwu.magisk.core.utils.MediaStoreUtils
import com.topjohnwu.magisk.ktx.cachedFile
import com.topjohnwu.magisk.ui.flash.FlashFragment
import com.topjohnwu.magisk.view.Notifications
import kotlinx.parcelize.IgnoredOnParcel
@@ -59,7 +58,6 @@ sealed class Subject : Parcelable {
@Parcelize
class App(
private val json: MagiskJson = Info.remote.magisk,
val stub: StubJson = Info.remote.stub,
override val notifyId: Int = Notifications.nextId()
) : Subject() {
override val title: String get() = "Magisk-${json.version}(${json.versionCode})"

View File

@@ -0,0 +1,152 @@
package com.topjohnwu.magisk.core.ktx
import android.annotation.SuppressLint
import android.app.Activity
import android.content.*
import android.content.pm.ApplicationInfo
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.content.res.Configuration
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.drawable.AdaptiveIconDrawable
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.LayerDrawable
import android.os.Build
import android.os.Build.VERSION.SDK_INT
import android.os.Process
import android.view.View
import android.view.inputmethod.InputMethodManager
import android.widget.Toast
import androidx.appcompat.content.res.AppCompatResources
import androidx.core.content.getSystemService
import com.topjohnwu.magisk.core.utils.RootUtils
import com.topjohnwu.magisk.core.utils.currentLocale
import com.topjohnwu.magisk.utils.APKInstall
import com.topjohnwu.superuser.internal.UiThreadHandler
import java.io.File
import kotlin.String
fun Context.rawResource(id: Int) = resources.openRawResource(id)
fun Context.getBitmap(id: Int): Bitmap {
var drawable = AppCompatResources.getDrawable(this, id)!!
if (drawable is BitmapDrawable)
return drawable.bitmap
if (SDK_INT >= Build.VERSION_CODES.O && drawable is AdaptiveIconDrawable) {
drawable = LayerDrawable(arrayOf(drawable.background, drawable.foreground))
}
val bitmap = Bitmap.createBitmap(
drawable.intrinsicWidth, drawable.intrinsicHeight,
Bitmap.Config.ARGB_8888
)
val canvas = Canvas(bitmap)
drawable.setBounds(0, 0, canvas.width, canvas.height)
drawable.draw(canvas)
return bitmap
}
val Context.deviceProtectedContext: Context get() =
if (SDK_INT >= Build.VERSION_CODES.N) {
createDeviceProtectedStorageContext()
} else { this }
fun Context.cachedFile(name: String) = File(cacheDir, name)
fun ApplicationInfo.getLabel(pm: PackageManager): String {
runCatching {
if (labelRes > 0) {
val res = pm.getResourcesForApplication(this)
val config = Configuration()
config.setLocale(currentLocale)
res.updateConfiguration(config, res.displayMetrics)
return res.getString(labelRes)
}
}
return loadLabel(pm).toString()
}
fun Context.unwrap(): Context {
var context = this
while (context is ContextWrapper)
context = context.baseContext
return context
}
fun Activity.hideKeyboard() {
val view = currentFocus ?: return
getSystemService<InputMethodManager>()
?.hideSoftInputFromWindow(view.windowToken, 0)
view.clearFocus()
}
val View.activity: Activity get() {
var context = context
while(true) {
if (context !is ContextWrapper)
error("View is not attached to activity")
if (context is Activity)
return context
context = context.baseContext
}
}
@SuppressLint("PrivateApi")
fun getProperty(key: String, def: String): String {
runCatching {
val clazz = Class.forName("android.os.SystemProperties")
val get = clazz.getMethod("get", String::class.java, String::class.java)
return get.invoke(clazz, key, def) as String
}
return def
}
@SuppressLint("InlinedApi")
@Throws(PackageManager.NameNotFoundException::class)
fun PackageManager.getPackageInfo(uid: Int, pid: Int): PackageInfo? {
val flag = PackageManager.MATCH_UNINSTALLED_PACKAGES
val pkgs = getPackagesForUid(uid) ?: throw PackageManager.NameNotFoundException()
if (pkgs.size > 1) {
if (pid <= 0) {
return null
}
// Try to find package name from PID
val proc = RootUtils.obj?.getAppProcess(pid)
if (proc == null) {
if (uid == Process.SHELL_UID) {
// It is possible that some apps installed are sharing UID with shell.
// We will not be able to find a package from the active process list,
// because the client is forked from ADB shell, not any app process.
return getPackageInfo("com.android.shell", flag)
}
} else if (uid == proc.uid) {
return getPackageInfo(proc.pkgList[0], flag)
}
return null
}
if (pkgs.size == 1) {
return getPackageInfo(pkgs[0], flag)
}
throw PackageManager.NameNotFoundException()
}
fun Context.registerRuntimeReceiver(receiver: BroadcastReceiver, filter: IntentFilter) {
APKInstall.registerReceiver(this, receiver, filter)
}
fun Context.selfLaunchIntent(): Intent {
val pm = packageManager
val intent = pm.getLaunchIntentForPackage(packageName)!!
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
return intent
}
fun Context.toast(msg: CharSequence, duration: Int) {
UiThreadHandler.run { Toast.makeText(this, msg, duration).show() }
}
fun Context.toast(resId: Int, duration: Int) {
UiThreadHandler.run { Toast.makeText(this, resId, duration).show() }
}

View File

@@ -1,4 +1,4 @@
package com.topjohnwu.magisk.ktx
package com.topjohnwu.magisk.core.ktx
import androidx.collection.SparseArrayCompat
import com.topjohnwu.magisk.core.utils.currentLocale
@@ -11,7 +11,7 @@ import java.io.OutputStream
import java.lang.reflect.Field
import java.text.DateFormat
import java.text.SimpleDateFormat
import java.util.*
import java.util.Collections
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream

View File

@@ -1,8 +1,6 @@
package com.topjohnwu.magisk.ktx
package com.topjohnwu.magisk.core.ktx
import android.content.Context
import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.core.Const
import com.topjohnwu.superuser.Shell
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@@ -15,12 +13,4 @@ fun reboot(reason: String = if (Config.recovery) "recovery" else "") {
Shell.cmd("/system/bin/svc power reboot $reason || /system/bin/reboot $reason").submit()
}
fun relaunchApp(context: Context) {
val intent = context.packageManager.getLaunchIntentForPackage(context.packageName) ?: return
val args = mutableListOf("am", "start", "--user", Const.USER_ID.toString())
val cmd = intent.toCommand(args).joinToString(separator = " ")
Shell.cmd("run_delay 1 \"$cmd\"").exec()
Runtime.getRuntime().exit(0)
}
suspend fun Shell.Job.await() = withContext(Dispatchers.IO) { exec() }

View File

@@ -7,7 +7,6 @@ import kotlinx.parcelize.Parcelize
@JsonClass(generateAdapter = true)
data class UpdateInfo(
val magisk: MagiskJson = MagiskJson(),
val stub: StubJson = StubJson()
)
@Parcelize
@@ -19,13 +18,6 @@ data class MagiskJson(
val note: String = ""
) : Parcelable
@Parcelize
@JsonClass(generateAdapter = true)
data class StubJson(
val versionCode: Int = -1,
val link: String = ""
) : Parcelable
@JsonClass(generateAdapter = true)
data class ModuleJson(
val version: String,

View File

@@ -43,10 +43,10 @@ data class LocalModule(
set(enable) {
if (enable) {
disableFile.delete()
Shell.cmd("copy_sepolicy_rules").submit()
Shell.cmd("copy_preinit_files").submit()
} else {
!disableFile.createNewFile()
Shell.cmd("copy_sepolicy_rules").submit()
Shell.cmd("copy_preinit_files").submit()
}
}
@@ -56,10 +56,10 @@ data class LocalModule(
if (remove) {
if (updateFile.exists()) return
removeFile.createNewFile()
Shell.cmd("copy_sepolicy_rules").submit()
Shell.cmd("copy_preinit_files").submit()
} else {
removeFile.delete()
Shell.cmd("copy_sepolicy_rules").submit()
Shell.cmd("copy_preinit_files").submit()
}
}
@@ -122,15 +122,13 @@ data class LocalModule(
companion object {
private val PERSIST get() = "${Const.MAGISKTMP}/mirror/persist/magisk"
fun loaded() = RootUtils.fs.getFile(Const.MAGISK_PATH).exists()
suspend fun installed() = withContext(Dispatchers.IO) {
RootUtils.fs.getFile(Const.MAGISK_PATH)
.listFiles()
.orEmpty()
.filter { !it.isFile }
.filter { !it.isFile && !it.isHidden }
.map { LocalModule("${Const.MAGISK_PATH}/${it.name}") }
.sortedBy { it.name.lowercase(Locale.ROOT) }
}

View File

@@ -4,7 +4,7 @@ import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import androidx.room.Entity
import androidx.room.PrimaryKey
import com.topjohnwu.magisk.ktx.getLabel
import com.topjohnwu.magisk.core.ktx.getLabel
@Entity(tableName = "logs")
class SuLog(
@@ -14,7 +14,10 @@ class SuLog(
val packageName: String,
val appName: String,
val command: String,
val action: Boolean,
val action: Int,
val target: Int,
val context: String,
val gids: String,
val time: Long = System.currentTimeMillis()
) {
@PrimaryKey(autoGenerate = true) var id: Int = 0
@@ -25,7 +28,10 @@ fun PackageManager.createSuLog(
toUid: Int,
fromPid: Int,
command: String,
policy: Int
policy: Int,
target: Int,
context: String,
gids: String,
): SuLog {
val appInfo = info.applicationInfo
return SuLog(
@@ -35,7 +41,10 @@ fun PackageManager.createSuLog(
packageName = getNameForUid(appInfo.uid)!!,
appName = appInfo.getLabel(this),
command = command,
action = policy == SuPolicy.ALLOW
action = policy,
target = target,
context = context,
gids = gids,
)
}
@@ -44,7 +53,10 @@ fun createSuLog(
toUid: Int,
fromPid: Int,
command: String,
policy: Int
policy: Int,
target: Int,
context: String,
gids: String,
): SuLog {
return SuLog(
fromUid = fromUid,
@@ -53,6 +65,9 @@ fun createSuLog(
packageName = "[UID] $fromUid",
appName = "[UID] $fromUid",
command = command,
action = policy == SuPolicy.ALLOW
action = policy,
target = target,
context = context,
gids = gids,
)
}

View File

@@ -3,8 +3,8 @@ package com.topjohnwu.magisk.core.repository
import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.data.SuLogDao
import com.topjohnwu.magisk.core.ktx.await
import com.topjohnwu.magisk.core.model.su.SuLog
import com.topjohnwu.magisk.ktx.await
import com.topjohnwu.superuser.Shell

View File

@@ -8,7 +8,6 @@ import com.topjohnwu.magisk.core.Config.Value.DEBUG_CHANNEL
import com.topjohnwu.magisk.core.Config.Value.DEFAULT_CHANNEL
import com.topjohnwu.magisk.core.Config.Value.STABLE_CHANNEL
import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.data.GithubApiServices
import com.topjohnwu.magisk.core.data.GithubPageServices
import com.topjohnwu.magisk.core.data.RawServices
import retrofit2.HttpException
@@ -17,8 +16,7 @@ import java.io.IOException
class NetworkService(
private val pages: GithubPageServices,
private val raw: RawServices,
private val api: GithubApiServices
private val raw: RawServices
) {
suspend fun fetchUpdate() = safe {
var info = when (Config.updateChannel) {
@@ -42,7 +40,7 @@ class NetworkService(
private suspend fun fetchBetaUpdate() = pages.fetchUpdateJSON("beta.json")
private suspend fun fetchCanaryUpdate() = pages.fetchUpdateJSON("canary.json")
private suspend fun fetchDebugUpdate() = pages.fetchUpdateJSON("debug.json")
private suspend fun fetchCustomUpdate(url: String) = raw.fetchCustomUpdate(url)
private suspend fun fetchCustomUpdate(url: String) = pages.fetchUpdateJSON(url)
private inline fun <T> safe(factory: () -> T): T? {
return try {

View File

@@ -7,11 +7,11 @@ import com.topjohnwu.magisk.BuildConfig
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.core.di.ServiceLocator
import com.topjohnwu.magisk.core.ktx.getLabel
import com.topjohnwu.magisk.core.ktx.getPackageInfo
import com.topjohnwu.magisk.core.ktx.toast
import com.topjohnwu.magisk.core.model.su.SuPolicy
import com.topjohnwu.magisk.core.model.su.createSuLog
import com.topjohnwu.magisk.ktx.getLabel
import com.topjohnwu.magisk.ktx.getPackageInfo
import com.topjohnwu.magisk.utils.Utils
import kotlinx.coroutines.runBlocking
import timber.log.Timber
@@ -57,17 +57,20 @@ object SuCallbackHandler {
val toUid = data.getIntComp("to.uid", -1)
val pid = data.getIntComp("pid", -1)
val command = data.getString("command", "")
val target = data.getIntComp("target", -1)
val seContext = data.getString("context", "")
val gids = data.getString("gids", "")
val pm = context.packageManager
val log = runCatching {
pm.getPackageInfo(fromUid, pid)?.let {
pm.createSuLog(it, toUid, pid, command, policy)
pm.createSuLog(it, toUid, pid, command, policy, target, seContext, gids)
}
}.getOrNull() ?: createSuLog(fromUid, toUid, pid, command, policy)
}.getOrNull() ?: createSuLog(fromUid, toUid, pid, command, policy, target, seContext, gids)
if (notify)
notify(context, log.action, log.appName)
notify(context, log.action == SuPolicy.ALLOW, log.appName)
runBlocking { ServiceLocator.logRepo.insert(log) }
}
@@ -93,7 +96,7 @@ object SuCallbackHandler {
else
R.string.su_deny_toast
Utils.toast(context.getString(resId, appName), Toast.LENGTH_SHORT)
context.toast(context.getString(resId, appName), Toast.LENGTH_SHORT)
}
}
}

View File

@@ -6,13 +6,14 @@ import android.content.pm.PackageManager
import com.topjohnwu.magisk.BuildConfig
import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.core.data.magiskdb.PolicyDao
import com.topjohnwu.magisk.core.ktx.getPackageInfo
import com.topjohnwu.magisk.core.model.su.SuPolicy
import com.topjohnwu.magisk.ktx.getPackageInfo
import com.topjohnwu.superuser.Shell
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import timber.log.Timber
import java.io.DataOutputStream
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.util.concurrent.TimeUnit
@@ -22,7 +23,7 @@ class SuRequestHandler(
private val policyDB: PolicyDao
) {
private lateinit var output: DataOutputStream
private lateinit var output: File
private lateinit var policy: SuPolicy
lateinit var pkgInfo: PackageInfo
private set
@@ -52,37 +53,32 @@ class SuRequestHandler(
return true
}
private fun close() {
if (::output.isInitialized)
runCatching { output.close() }
}
private suspend fun init(intent: Intent) = withContext(Dispatchers.IO) {
try {
val fifo = intent.getStringExtra("fifo") ?: throw IOException("fifo == null")
output = DataOutputStream(FileOutputStream(fifo))
private suspend fun init(intent: Intent): Boolean {
val uid = intent.getIntExtra("uid", -1)
if (uid <= 0) {
throw IOException("uid == $uid")
}
policy = SuPolicy(uid)
val pid = intent.getIntExtra("pid", -1)
val fifo = intent.getStringExtra("fifo")
if (uid <= 0 || pid <= 0 || fifo == null) {
Timber.e("Unexpected extras: uid=[${uid}], pid=[${pid}], fifo=[${fifo}]")
return false
}
output = File(fifo)
policy = SuPolicy(uid)
try {
pkgInfo = pm.getPackageInfo(uid, pid) ?: PackageInfo().apply {
val name = pm.getNameForUid(uid) ?: throw PackageManager.NameNotFoundException()
// We only fill in sharedUserId and leave other fields uninitialized
sharedUserId = name.split(":")[0]
}
return@withContext true
} catch (e: PackageManager.NameNotFoundException) {
respond(SuPolicy.DENY, -1)
return@withContext false
}
} catch (e: IOException) {
Timber.e(e)
close()
return@withContext false
respond(SuPolicy.DENY, -1)
return false
}
if (!output.canWrite()) {
Timber.e("Cannot write to $output")
return false
}
return true
}
suspend fun respond(action: Int, time: Int) {
@@ -97,13 +93,14 @@ class SuRequestHandler(
withContext(Dispatchers.IO) {
try {
output.writeInt(policy.policy)
output.flush()
DataOutputStream(FileOutputStream(output)).use {
it.writeInt(policy.policy)
it.flush()
}
} catch (e: IOException) {
Timber.e(e)
} finally {
close()
if (until >= 0)
}
if (until >= 0) {
policyDB.update(policy)
}
}

View File

@@ -0,0 +1,68 @@
package com.topjohnwu.magisk.core.su
import android.os.Bundle
import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.di.ServiceLocator
import com.topjohnwu.magisk.core.tasks.MagiskInstaller
import com.topjohnwu.magisk.core.utils.RootUtils
import com.topjohnwu.superuser.Shell
import com.topjohnwu.superuser.internal.NOPList
import kotlinx.coroutines.runBlocking
object TestHandler {
fun run(method: String): Bundle {
val r = Bundle()
fun setup(): Boolean {
val nop = NOPList.getInstance()
return runBlocking {
MagiskInstaller.Emulator(nop, nop).exec()
}
}
fun test(): Boolean {
// Make sure Zygisk works correctly
if (!Info.isZygiskEnabled) {
r.putString("reason", "zygisk not enabled")
return false
}
// Make sure the Magisk app can get root
val shell = Shell.getShell()
if (!shell.isRoot) {
r.putString("reason", "shell not root")
return false
}
// Make sure the root service is running
RootUtils.Connection.await()
// Clear existing grant for ADB shell
runBlocking {
ServiceLocator.policyDB.delete(2000)
Config.suAutoResponse = Config.Value.SU_AUTO_ALLOW
Config.prefs.edit().commit()
}
return true
}
val b = runCatching {
when (method) {
"setup" -> setup()
"test" -> test()
else -> {
r.putString("reason", "unknown method")
false
}
}
}.getOrElse {
r.putString("reason", it.stackTraceToString())
false
}
r.putBoolean("result", b)
return r
}
}

View File

@@ -4,10 +4,10 @@ import android.net.Uri
import androidx.core.net.toFile
import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.di.AppContext
import com.topjohnwu.magisk.core.ktx.writeTo
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.displayName
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.inputStream
import com.topjohnwu.magisk.core.utils.unzip
import com.topjohnwu.magisk.ktx.writeTo
import com.topjohnwu.superuser.Shell
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext

View File

@@ -4,22 +4,21 @@ import android.app.Activity
import android.content.Context
import android.content.Intent
import android.widget.Toast
import androidx.annotation.WorkerThread
import com.topjohnwu.magisk.BuildConfig.APPLICATION_ID
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.StubApk
import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.Provider
import com.topjohnwu.magisk.core.di.ServiceLocator
import com.topjohnwu.magisk.core.ktx.await
import com.topjohnwu.magisk.core.ktx.toast
import com.topjohnwu.magisk.core.ktx.writeTo
import com.topjohnwu.magisk.core.utils.AXML
import com.topjohnwu.magisk.core.utils.Keygen
import com.topjohnwu.magisk.ktx.await
import com.topjohnwu.magisk.ktx.writeTo
import com.topjohnwu.magisk.signing.JarMap
import com.topjohnwu.magisk.signing.SignApk
import com.topjohnwu.magisk.utils.APKInstall
import com.topjohnwu.magisk.utils.Utils
import com.topjohnwu.superuser.Shell
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Runnable
@@ -30,6 +29,7 @@ import java.io.FileOutputStream
import java.io.IOException
import java.io.OutputStream
import java.security.SecureRandom
import kotlin.random.asKotlinRandom
object HideAPK {
@@ -39,8 +39,7 @@ object HideAPK {
// Some arbitrary limit
const val MAX_LABEL_LENGTH = 32
private val svc get() = ServiceLocator.networkService
const val PLACEHOLDER = "COMPONENT_PLACEHOLDER"
private fun genPackageName(): String {
val random = SecureRandom()
@@ -65,20 +64,87 @@ object HideAPK {
return builder.toString()
}
fun patch(
private fun classNameGenerator() = sequence {
val c1 = mutableListOf<String>()
val c2 = mutableListOf<String>()
val c3 = mutableListOf<String>()
val random = SecureRandom()
val kRandom = random.asKotlinRandom()
fun <T> chain(vararg iters: Iterable<T>) = sequence {
iters.forEach { it.forEach { v -> yield(v) } }
}
for (a in chain('a'..'z', 'A'..'Z')) {
if (a != 'a' && a != 'A') {
c1.add("$a")
}
for (b in chain('a'..'z', 'A'..'Z', '0'..'9')) {
c2.add("$a$b")
for (c in chain('a'..'z', 'A'..'Z', '0'..'9')) {
c3.add("$a$b$c")
}
}
}
c1.shuffle(random)
c2.shuffle(random)
c3.shuffle(random)
fun notJavaKeyword(name: String) = when (name) {
"do", "if", "for", "int", "new", "try" -> false
else -> true
}
fun List<String>.process() = asSequence().filter(::notJavaKeyword)
val names = mutableListOf<String>()
names.addAll(c1)
names.addAll(c2.process().take(30))
names.addAll(c3.process().take(30))
while (true) {
val seg = 2 + random.nextInt(4)
val cls = StringBuilder()
for (i in 0 until seg) {
cls.append(names.random(kRandom))
if (i != seg - 1)
cls.append('.')
}
// Old Android does not support capitalized package names
// Check Android 7.0.0 PackageParser#buildClassName
cls[0] = cls[0].lowercaseChar()
yield(cls.toString())
}
}.distinct().iterator()
private fun patch(
context: Context,
apk: File, out: OutputStream,
pkg: String, label: CharSequence
): Boolean {
val info = context.packageManager.getPackageArchiveInfo(apk.path, 0) ?: return false
val name = info.applicationInfo.nonLocalizedLabel.toString()
val origLabel = info.applicationInfo.nonLocalizedLabel.toString()
try {
JarMap.open(apk, true).use { jar ->
val je = jar.getJarEntry(ANDROID_MANIFEST)
val xml = AXML(jar.getRawData(je))
val generator = classNameGenerator()
if (!xml.findAndPatch(APPLICATION_ID to pkg, name to label.toString()))
if (!xml.patchStrings {
for (i in it.indices) {
val s = it[i]
if (s.contains(APPLICATION_ID)) {
it[i] = s.replace(APPLICATION_ID, pkg)
} else if (s.contains(PLACEHOLDER)) {
it[i] = generator.next()
} else if (s == origLabel) {
it[i] = label.toString()
}
}
}) {
return false
}
// Write apk changes
jar.getOutputStream(je).use { it.write(xml.bytes) }
@@ -94,7 +160,6 @@ object HideAPK {
private fun launchApp(activity: Activity, pkg: String) {
val intent = activity.packageManager.getLaunchIntentForPackage(pkg) ?: return
Config.suManager = if (pkg == APPLICATION_ID) "" else pkg
val self = activity.packageName
val flag = Intent.FLAG_GRANT_READ_URI_PERMISSION
activity.grantUriPermission(pkg, Provider.preferencesUri(self), flag)
@@ -103,16 +168,12 @@ object HideAPK {
activity.finish()
}
@Suppress("BlockingMethodInNonBlockingContext")
private suspend fun patchAndHide(activity: Activity, label: String, onFailure: Runnable): Boolean {
private fun patchAndHide(activity: Activity, label: String, onFailure: Runnable): Boolean {
val stub = File(activity.cacheDir, "stub.apk")
try {
svc.fetchFile(Info.remote.stub.link).byteStream().writeTo(stub)
activity.assets.open("stub.apk").writeTo(stub)
} catch (e: IOException) {
Timber.e(e)
stub.createNewFile()
val cmd = "\$MAGISKBIN/magiskinit -x manager ${stub.path}"
if (!Shell.cmd(cmd).exec().isSuccess)
return false
}
@@ -129,7 +190,8 @@ object HideAPK {
launchApp(activity, pkg)
}
val cmd = "adb_pm_install $repack ${activity.applicationInfo.uid}"
Config.suManager = pkg
val cmd = "adb_pm_install $repack $pkg"
if (Shell.cmd(cmd).exec().isSuccess) return true
try {
@@ -152,7 +214,7 @@ object HideAPK {
}
val onFailure = Runnable {
dialog.dismiss()
Utils.toast(R.string.failure, Toast.LENGTH_LONG)
activity.toast(R.string.failure, Toast.LENGTH_LONG)
}
val success = withContext(Dispatchers.IO) {
patchAndHide(activity, label, onFailure)
@@ -170,14 +232,15 @@ object HideAPK {
}
val onFailure = Runnable {
dialog.dismiss()
Utils.toast(R.string.failure, Toast.LENGTH_LONG)
activity.toast(R.string.failure, Toast.LENGTH_LONG)
}
val apk = StubApk.current(activity)
val session = APKInstall.startSession(activity, APPLICATION_ID, onFailure) {
launchApp(activity, APPLICATION_ID)
dialog.dismiss()
}
val cmd = "adb_pm_install $apk ${activity.applicationInfo.uid}"
Config.suManager = ""
val cmd = "adb_pm_install $apk $APPLICATION_ID"
if (Shell.cmd(cmd).await().isSuccess) return
val success = withContext(Dispatchers.IO) {
try {
@@ -191,4 +254,17 @@ object HideAPK {
}
if (!success) onFailure.run()
}
@WorkerThread
fun upgrade(context: Context, apk: File): Intent? {
val label = context.applicationInfo.nonLocalizedLabel
val pkg = context.packageName
val session = APKInstall.startSession(context)
session.openStream(context).use {
if (!patch(context, apk, it, pkg, label)) {
return null
}
}
return session.waitIntent()
}
}

View File

@@ -1,24 +1,30 @@
package com.topjohnwu.magisk.core.tasks
import android.net.Uri
import android.system.ErrnoException
import android.system.Os
import android.system.OsConstants
import android.system.OsConstants.O_WRONLY
import android.widget.Toast
import androidx.annotation.WorkerThread
import androidx.core.os.postDelayed
import com.topjohnwu.magisk.BuildConfig
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.StubApk
import com.topjohnwu.magisk.core.*
import com.topjohnwu.magisk.core.AppApkPath
import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.di.ServiceLocator
import com.topjohnwu.magisk.core.isRunningAsStub
import com.topjohnwu.magisk.core.ktx.copyAndClose
import com.topjohnwu.magisk.core.ktx.reboot
import com.topjohnwu.magisk.core.ktx.toast
import com.topjohnwu.magisk.core.ktx.writeTo
import com.topjohnwu.magisk.core.utils.MediaStoreUtils
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.inputStream
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream
import com.topjohnwu.magisk.core.utils.RootUtils
import com.topjohnwu.magisk.ktx.reboot
import com.topjohnwu.magisk.ktx.withStreams
import com.topjohnwu.magisk.ktx.writeTo
import com.topjohnwu.magisk.signing.SignBoot
import com.topjohnwu.magisk.utils.Utils
import com.topjohnwu.superuser.Shell
import com.topjohnwu.superuser.ShellUtils
import com.topjohnwu.superuser.internal.NOPList
@@ -40,6 +46,7 @@ import java.util.*
import java.util.concurrent.atomic.AtomicBoolean
import java.util.zip.ZipEntry
import java.util.zip.ZipFile
import java.util.zip.ZipInputStream
abstract class MagiskInstallImpl protected constructor(
protected val console: MutableList<String> = NOPList.getInstance(),
@@ -111,7 +118,9 @@ abstract class MagiskInstallImpl protected constructor(
val name = n.substring(3, n.length - 3)
val dest = File(installDir, name)
zf.getInputStream(it).writeTo(dest)
dest.setExecutable(true)
}
zf.close()
} else {
val info = context.applicationInfo
var libs = File(info.nativeLibraryDir).listFiles { _, name ->
@@ -119,7 +128,8 @@ abstract class MagiskInstallImpl protected constructor(
} ?: emptyArray()
// Also symlink magisk32 on non 64-bit only 64-bit devices
val lib32 = info.javaClass.getDeclaredField("secondaryNativeLibraryDir").get(info) as String?
val lib32 = info.javaClass.getDeclaredField("secondaryNativeLibraryDir")
.get(info) as String?
if (lib32 != null) {
libs += File(lib32, "libmagisk32.so")
}
@@ -131,7 +141,7 @@ abstract class MagiskInstallImpl protected constructor(
}
// Extract scripts
for (script in listOf("util_functions.sh", "boot_patch.sh", "addon.d.sh")) {
for (script in listOf("util_functions.sh", "boot_patch.sh", "addon.d.sh", "stub.apk")) {
val dest = File(installDir, script)
context.assets.open(script).writeTo(dest)
}
@@ -164,38 +174,41 @@ abstract class MagiskInstallImpl protected constructor(
return true
}
private fun InputStream.cleanPump(out: OutputStream) = withStreams(this, out) { src, _ ->
src.copyTo(out)
}
private fun InputStream.copyAndCloseOut(out: OutputStream) = out.use { copyTo(it) }
private fun newTarEntry(name: String, size: Long): TarEntry {
console.add("-- Writing: $name")
return TarEntry(TarHeader.createHeader(name, size, 0, false, 420 /* 0644 */))
}
private class NoAvailableStream(s: InputStream) : FilterInputStream(s) {
// Make sure available is never called on the actual stream and always return 0
// 1. Workaround bug in LZ4FrameInputStream
// 2. Reduce max buffer size to prevent OOM
override fun available() = 0
}
private class NoBootException : IOException()
@Throws(IOException::class)
private fun processTar(input: InputStream, output: OutputStream): OutputStream {
private fun processTar(tarIn: TarInputStream, tarOut: TarOutputStream): ExtendedFile {
console.add("- Processing tar file")
val tarOut = TarOutputStream(output)
TarInputStream(input).use { tarIn ->
lateinit var entry: TarEntry
fun decompressedStream(): InputStream {
val src = if (entry.name.endsWith(".lz4")) LZ4FrameInputStream(tarIn) else tarIn
return object : FilterInputStream(src) {
override fun available() = 0 /* Workaround bug in LZ4FrameInputStream */
override fun close() { /* Never close src stream */ }
}
val stream = if (entry.name.endsWith(".lz4")) LZ4FrameInputStream(tarIn) else tarIn
return NoAvailableStream(stream)
}
while (tarIn.nextEntry?.let { entry = it } != null) {
if (entry.name.startsWith("boot.img") ||
entry.name.startsWith("init_boot.img") ||
(Config.recovery && entry.name.contains("recovery.img"))) {
val name = entry.name.replace(".lz4", "")
console.add("-- Extracting: $name")
val extract = installDir.getChildFile(name)
decompressedStream().cleanPump(extract.newOutputStream())
decompressedStream().copyAndCloseOut(extract.newOutputStream())
} else if (entry.name.contains("vbmeta.img")) {
val rawData = decompressedStream().readBytes()
// Valid vbmeta.img should be at least 256 bytes
@@ -208,18 +221,33 @@ abstract class MagiskInstallImpl protected constructor(
ByteBuffer.wrap(rawData).putInt(120, 3)
tarOut.putNextEntry(newTarEntry("vbmeta.img", rawData.size.toLong()))
tarOut.write(rawData)
// vbmeta partition exist, disable boot vbmeta patch
Info.patchBootVbmeta = false
} else if (entry.name.contains("userdata.img")) {
continue
} else {
console.add("-- Copying: ${entry.name}")
tarOut.putNextEntry(entry)
tarIn.copyTo(tarOut, bufferSize = 1024 * 1024)
}
}
}
val boot = installDir.getChildFile("boot.img")
val initBoot = installDir.getChildFile("init_boot.img")
val recovery = installDir.getChildFile("recovery.img")
if (Config.recovery && recovery.exists() && boot.exists()) {
// Install to recovery
srcBoot = recovery
fun ExtendedFile.copyToTar() {
newInputStream().use {
tarOut.putNextEntry(newTarEntry(name, length()))
it.copyTo(tarOut)
}
delete()
}
// Patch priority: recovery > init_boot > boot
return when {
recovery.exists() -> {
if (boot.exists()) {
// Repack boot image to prevent auto restore
arrayOf(
"cd $installDir",
@@ -230,31 +258,134 @@ abstract class MagiskInstallImpl protected constructor(
"./magiskboot cleanup",
"rm -f new-boot.img",
"cd /").sh()
boot.newInputStream().use {
tarOut.putNextEntry(newTarEntry("boot.img", boot.length()))
it.copyTo(tarOut)
boot.copyToTar()
}
boot.delete()
recovery
}
initBoot.exists() -> {
if (boot.exists())
boot.copyToTar()
initBoot
}
boot.exists() -> boot
else -> throw NoBootException()
}
}
@Throws(IOException::class)
private fun processZip(zipIn: ZipInputStream): ExtendedFile {
console.add("- Processing zip file")
val boot = installDir.getChildFile("boot.img")
val initBoot = installDir.getChildFile("init_boot.img")
lateinit var entry: ZipEntry
while (zipIn.nextEntry?.also { entry = it } != null) {
if (entry.isDirectory) continue
when (entry.name.substringAfterLast('/')) {
"payload.bin" -> {
try {
return processPayload(zipIn)
} catch (e: IOException) {
// No boot image in payload.bin, continue to find boot images
}
}
"init_boot.img" -> {
console.add("- Extracting init_boot.img")
zipIn.copyAndCloseOut(initBoot.newOutputStream())
return initBoot
}
"boot.img" -> {
console.add("- Extracting boot.img")
zipIn.copyAndCloseOut(boot.newOutputStream())
// Don't return here since there might be an init_boot.img
}
}
}
if (boot.exists()) {
return boot
} else {
if (!boot.exists()) {
console.add("! No boot image found")
throw NoBootException()
}
}
@Throws(IOException::class)
private fun processPayload(input: InputStream): ExtendedFile {
var fifo: File? = null
try {
console.add("- Processing payload.bin")
fifo = File.createTempFile("payload-fifo-", null, installDir)
fifo.delete()
Os.mkfifo(fifo.path, 420 /* 0644 */)
// Enqueue the shell command first, or the subsequent FIFO open will block
val future = arrayOf(
"cd $installDir",
"./magiskboot extract $fifo",
"cd /"
).eq()
val fd = Os.open(fifo.path, O_WRONLY, 0)
try {
val bufSize = 1024 * 1024
val buf = ByteBuffer.allocate(bufSize)
buf.position(input.read(buf.array()).coerceAtLeast(0)).flip()
while (buf.hasRemaining()) {
try {
Os.write(fd, buf)
} catch (e: ErrnoException) {
if (e.errno != OsConstants.EPIPE)
throw e
// If SIGPIPE, then the other side is closed, we're done
break
}
if (!buf.hasRemaining()) {
buf.limit(bufSize)
buf.position(input.read(buf.array()).coerceAtLeast(0)).flip()
}
}
} finally {
Os.close(fd)
}
val success = try { future.get().isSuccess } catch (e: Exception) { false }
if (!success) {
console.add("! Error while extracting payload.bin")
throw IOException()
}
srcBoot = boot
val boot = installDir.getChildFile("boot.img")
val initBoot = installDir.getChildFile("init_boot.img")
return when {
initBoot.exists() -> {
console.add("-- Extract init_boot.img")
initBoot
}
boot.exists() -> {
console.add("-- Extract boot.img")
boot
}
else -> {
throw NoBootException()
}
}
} catch (e: ErrnoException) {
throw IOException(e)
} finally {
fifo?.delete()
}
return tarOut
}
private fun handleFile(uri: Uri): Boolean {
val outStream: OutputStream
var outFile: MediaStoreUtils.UriFile? = null
val outFile: MediaStoreUtils.UriFile
// Process input file
try {
uri.inputStream().buffered().use { src ->
src.mark(500)
val magic = ByteArray(5)
if (src.skip(257) != 257L || src.read(magic) != magic.size) {
val magic = ByteArray(4)
val tarMagic = ByteArray(5)
if (src.read(magic) != magic.size || src.skip(253) != 253L ||
src.read(tarMagic) != tarMagic.size
) {
console.add("! Invalid input file")
return false
}
@@ -270,29 +401,52 @@ abstract class MagiskInstallImpl protected constructor(
toString()
}
outStream = if (magic.contentEquals("ustar".toByteArray())) {
srcBoot = if (tarMagic.contentEquals("ustar".toByteArray())) {
// tar file
outFile = MediaStoreUtils.getFile("$filename.tar", true)
processTar(src, outFile!!.uri.outputStream())
outStream = TarOutputStream(outFile.uri.outputStream())
try {
processTar(TarInputStream(src), outStream)
} catch (e: IOException) {
outStream.close()
outFile.delete()
throw e
}
} else {
// raw image
srcBoot = installDir.getChildFile("boot.img")
console.add("- Copying image to cache")
src.cleanPump(srcBoot.newOutputStream())
outFile = MediaStoreUtils.getFile("$filename.img", true)
outFile!!.uri.outputStream()
outStream = outFile.uri.outputStream()
try {
if (magic.contentEquals("CrAU".toByteArray())) {
processPayload(src)
} else if (magic.contentEquals("PK\u0003\u0004".toByteArray())) {
processZip(ZipInputStream(src))
} else {
console.add("- Copying image to cache")
installDir.getChildFile("boot.img").also {
src.copyAndCloseOut(it.newOutputStream())
}
}
} catch (e: IOException) {
outStream.close()
outFile.delete()
throw e
}
}
}
} catch (e: IOException) {
if (e is NoBootException)
console.add("! No boot image found")
console.add("! Process error")
outFile?.delete()
Timber.e(e)
return false
}
// Patch file
if (!patchBoot()) {
outFile!!.delete()
outFile.delete()
return false
}
@@ -300,10 +454,16 @@ abstract class MagiskInstallImpl protected constructor(
try {
val newBoot = installDir.getChildFile("new-boot.img")
if (outStream is TarOutputStream) {
val name = if (srcBoot.path.contains("recovery")) "recovery.img" else "boot.img"
val name = with(srcBoot.path) {
when {
contains("recovery") -> "recovery.img"
contains("init_boot") -> "init_boot.img"
else -> "boot.img"
}
}
outStream.putNextEntry(newTarEntry(name, newBoot.length()))
}
newBoot.newInputStream().cleanPump(outStream)
newBoot.newInputStream().copyAndClose(outStream)
newBoot.delete()
console.add("")
@@ -313,7 +473,7 @@ abstract class MagiskInstallImpl protected constructor(
console.add("****************************")
} catch (e: IOException) {
console.add("! Failed to output to $outFile")
outFile!!.delete()
outFile.delete()
Timber.e(e)
return false
}
@@ -326,22 +486,6 @@ abstract class MagiskInstallImpl protected constructor(
}
private fun patchBoot(): Boolean {
var isSigned = false
if (!srcBoot.isCharacter) {
try {
srcBoot.newInputStream().use {
if (SignBoot.verifySignature(it, null)) {
isSigned = true
console.add("- Boot image is signed with AVB 1.0")
}
}
} catch (e: IOException) {
console.add("! Unable to check signature")
Timber.e(e)
return false
}
}
val newBoot = installDir.getChildFile("new-boot.img")
if (!useRootDir) {
// Create output files before hand
@@ -353,33 +497,15 @@ abstract class MagiskInstallImpl protected constructor(
"cd $installDir",
"KEEPFORCEENCRYPT=${Config.keepEnc} " +
"KEEPVERITY=${Config.keepVerity} " +
"PATCHVBMETAFLAG=${Config.patchVbmeta} " +
"PATCHVBMETAFLAG=${Info.patchBootVbmeta} " +
"RECOVERYMODE=${Config.recovery} " +
"LEGACYSAR=${Info.legacySAR} " +
"sh boot_patch.sh $srcBoot")
val isSuccess = cmds.sh().isSuccess
if (!cmds.sh().isSuccess)
return false
shell.newJob().add("./magiskboot cleanup", "cd /").exec()
val job = shell.newJob().add("./magiskboot cleanup", "cd /")
if (isSigned) {
console.add("- Signing boot image with verity keys")
val signed = File.createTempFile("signed", ".img", context.cacheDir)
try {
val src = newBoot.newInputStream().buffered()
val out = signed.outputStream().buffered()
withStreams(src, out) { _, _ ->
SignBoot.doSignature(null, null, src, out, "/boot")
}
} catch (e: IOException) {
console.add("! Unable to sign image")
Timber.e(e)
return false
}
job.add("cat $signed > $newBoot", "rm -f $signed")
}
job.exec()
return true
return isSuccess
}
private fun flashBoot() = "direct_install $installDir $srcBoot".sh().isSuccess
@@ -401,6 +527,7 @@ abstract class MagiskInstallImpl protected constructor(
return true
}
private fun Array<String>.eq() = shell.newJob().add(*this).to(console, logs).enqueue()
private fun String.sh() = shell.newJob().add(this).to(console, logs).exec()
private fun Array<String>.sh() = shell.newJob().add(*this).to(console, logs).exec()
private fun String.fsh() = ShellUtils.fastCmd(shell, this)
@@ -501,7 +628,7 @@ abstract class MagiskInstaller(
override suspend fun exec(): Boolean {
val success = super.exec()
callback()
Utils.toast(
context.toast(
if (success) R.string.reboot_delay_toast else R.string.setup_fail,
Toast.LENGTH_LONG
)

View File

@@ -29,7 +29,7 @@ class AXML(b: ByteArray) {
* Followed by an array of uint32_t with size = number of strings
* Each entry points to an offset into the string data
*/
fun findAndPatch(vararg patterns: Pair<String, String>): Boolean {
fun patchStrings(patchFn: (Array<String>) -> Unit): Boolean {
val buffer = ByteBuffer.wrap(bytes).order(LITTLE_ENDIAN)
fun findStringPool(): Int {
@@ -42,7 +42,6 @@ class AXML(b: ByteArray) {
return -1
}
var patch = false
val start = findStringPool()
if (start < 0)
return false
@@ -57,34 +56,26 @@ class AXML(b: ByteArray) {
val dataOff = start + intBuf.get()
intBuf.get()
val strings = ArrayList<String>(count)
// Read and patch all strings
loop@ for (i in 0 until count) {
val strList = ArrayList<String>(count)
// Collect all strings in the pool
for (i in 0 until count) {
val off = dataOff + intBuf.get()
val len = buffer.getShort(off)
val str = String(bytes, off + 2, len * 2, UTF_16LE)
for ((from, to) in patterns) {
if (str.contains(from)) {
strings.add(str.replace(from, to))
patch = true
continue@loop
}
}
strings.add(str)
strList.add(String(bytes, off + 2, len * 2, UTF_16LE))
}
if (!patch)
return false
val strArr = strList.toTypedArray()
patchFn(strArr)
// Write everything before string data, will patch values later
val baos = RawByteStream()
baos.write(bytes, 0, dataOff)
// Write string data
val strList = IntArray(count)
val offList = IntArray(count)
for (i in 0 until count) {
strList[i] = baos.size() - dataOff
val str = strings[i]
offList[i] = baos.size() - dataOff
val str = strArr[i]
baos.write(str.length.toShortBytes())
baos.write(str.toByteArray(UTF_16LE))
// Null terminate
@@ -103,7 +94,7 @@ class AXML(b: ByteArray) {
// Patch index table
newBuffer.position(start + STRING_INDICES_OFF)
val newIntBuf = newBuffer.asIntBuffer()
strList.forEach { newIntBuf.put(it) }
offList.forEach { newIntBuf.put(it) }
// Write the rest of the chunks
val nextOff = start + size

View File

@@ -1,59 +0,0 @@
package com.topjohnwu.magisk.core.utils
import androidx.biometric.BiometricManager
import androidx.biometric.BiometricPrompt
import androidx.core.content.ContextCompat
import androidx.fragment.app.FragmentActivity
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.core.di.AppContext
object BiometricHelper {
private val mgr by lazy { BiometricManager.from(AppContext) }
val isSupported get() = when (mgr.canAuthenticate()) {
BiometricManager.BIOMETRIC_SUCCESS -> true
else -> false
}
val isEnabled: Boolean get() {
val enabled = Config.suBiometric
if (enabled && !isSupported) {
Config.suBiometric = false
return false
}
return enabled
}
fun authenticate(
activity: FragmentActivity,
onError: () -> Unit = {},
onSuccess: () -> Unit): BiometricPrompt {
val prompt = BiometricPrompt(activity,
ContextCompat.getMainExecutor(activity),
object : BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
onError()
}
override fun onAuthenticationFailed() {
onError()
}
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
onSuccess()
}
}
)
val info = BiometricPrompt.PromptInfo.Builder()
.setConfirmationRequired(true)
.setDeviceCredentialAllowed(false)
.setTitle(activity.getString(R.string.authenticate))
.setNegativeButtonText(activity.getString(android.R.string.cancel))
.build()
prompt.authenticate(info)
return prompt
}
}

View File

@@ -1,6 +1,10 @@
package com.topjohnwu.magisk.core.utils
import kotlinx.coroutines.*
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Runnable
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import java.util.concurrent.AbstractExecutorService
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit

View File

@@ -14,7 +14,9 @@ import java.security.KeyPairGenerator
import java.security.KeyStore
import java.security.PrivateKey
import java.security.cert.X509Certificate
import java.util.*
import java.util.Calendar
import java.util.Locale
import java.util.Random
import java.util.zip.GZIPInputStream
import java.util.zip.GZIPOutputStream

View File

@@ -87,7 +87,7 @@ object MediaStoreUtils {
@Throws(IOException::class)
fun getFile(displayName: String, skipQuery: Boolean = false): UriFile {
if (Build.VERSION.SDK_INT < 30) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
// Fallback to file based I/O pre Android 11
val parent = File(Environment.getExternalStorageDirectory(), relativePath)
parent.mkdirs()
@@ -102,6 +102,8 @@ object MediaStoreUtils {
fun Uri.outputStream() = cr.openOutputStream(this, "rwt") ?: throw FileNotFoundException()
fun Uri.fileDescriptor(mode: String) = cr.openFileDescriptor(this, mode) ?: throw FileNotFoundException()
val Uri.displayName: String get() {
if (scheme == "file") {
// Simple uri wrapper over file, directly get file name

View File

@@ -0,0 +1,80 @@
package com.topjohnwu.magisk.core.utils
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.net.ConnectivityManager
import android.net.Network
import android.net.NetworkCapabilities
import android.net.NetworkRequest
import android.os.PowerManager
import androidx.collection.ArraySet
import androidx.core.content.getSystemService
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.ProcessLifecycleOwner
import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.ktx.registerRuntimeReceiver
class NetworkObserver(context: Context): DefaultLifecycleObserver {
private val manager = context.getSystemService<ConnectivityManager>()!!
private val networkCallback = object : ConnectivityManager.NetworkCallback() {
private val activeList = ArraySet<Network>()
override fun onAvailable(network: Network) {
activeList.add(network)
postValue(true)
}
override fun onLost(network: Network) {
activeList.remove(network)
postValue(!activeList.isEmpty())
}
}
private val receiver = object : BroadcastReceiver() {
private fun Context.isIdleMode(): Boolean {
val pwm = getSystemService<PowerManager>() ?: return true
val isIgnoringOptimizations = pwm.isIgnoringBatteryOptimizations(packageName)
return pwm.isDeviceIdleMode && !isIgnoringOptimizations
}
override fun onReceive(context: Context, intent: Intent) {
if (context.isIdleMode()) {
postValue(false)
} else {
postCurrentState()
}
}
}
init {
val request = NetworkRequest.Builder()
.addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
.build()
manager.registerNetworkCallback(request, networkCallback)
val filter = IntentFilter(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED)
context.applicationContext.registerRuntimeReceiver(receiver, filter)
ProcessLifecycleOwner.get().lifecycle.addObserver(this)
}
override fun onStart(owner: LifecycleOwner) {
postCurrentState()
}
private fun postCurrentState() {
postValue(manager.getNetworkCapabilities(manager.activeNetwork)
?.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) ?: false)
}
private fun postValue(b: Boolean) {
Info.remote = Info.EMPTY_REMOTE
Info.isConnected.postValue(b)
}
companion object {
fun init(context: Context): NetworkObserver {
return NetworkObserver(context).apply { postCurrentState() }
}
}
}

View File

@@ -0,0 +1,15 @@
package com.topjohnwu.magisk.core.utils;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.lifecycle.LifecycleDispatcher;
import androidx.lifecycle.ProcessLifecycleOwner;
// Use Java to bypass Kotlin internal visibility modifier
public class ProcessLifecycle {
public static void init(@NonNull Context context) {
LifecycleDispatcher.init(context);
ProcessLifecycleOwner.init$lifecycle_process_release(context);
}
}

View File

@@ -0,0 +1,17 @@
package com.topjohnwu.magisk.core.utils
import android.app.Activity
import android.app.KeyguardManager
import android.content.Context
import android.content.Intent
import androidx.activity.result.contract.ActivityResultContract
class RequestAuthentication: ActivityResultContract<Unit, Boolean>() {
override fun createIntent(context: Context, input: Unit) =
context.getSystemService(KeyguardManager::class.java)
.createConfirmDeviceCredentialIntent(null, null)
override fun parseResult(resultCode: Int, intent: Intent?) =
resultCode == Activity.RESULT_OK
}

View File

@@ -25,7 +25,7 @@ class RequestInstall : ActivityResultContract<Unit, Boolean>() {
context: Context,
input: Unit
): SynchronousResult<Boolean>? {
if (Build.VERSION.SDK_INT < 26)
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O)
return SynchronousResult(true)
if (context.packageManager.canRequestPackageInstalls())
return SynchronousResult(true)

View File

@@ -111,15 +111,23 @@ class RootUtils(stub: Any?) : RootService() {
}
fun await() {
// We cannot await on the main thread
if (Info.isRooted && !ShellUtils.onMainThread())
if (!Info.isRooted)
return
if (!ShellUtils.onMainThread()) {
acquireSharedInterruptibly(1)
} else if (state != 0) {
throw IllegalStateException("Cannot await on the main thread")
}
}
}
companion object {
var bindTask: Shell.Task? = null
var fs = FileSystemManager.getLocal()
var fs: FileSystemManager = FileSystemManager.getLocal()
get() {
Connection.await()
return field
}
private set
var obj: IRootUtils? = null
get() {

View File

@@ -7,10 +7,10 @@ import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.isRunningAsStub
import com.topjohnwu.magisk.ktx.cachedFile
import com.topjohnwu.magisk.ktx.deviceProtectedContext
import com.topjohnwu.magisk.ktx.rawResource
import com.topjohnwu.magisk.ktx.writeTo
import com.topjohnwu.magisk.core.ktx.cachedFile
import com.topjohnwu.magisk.core.ktx.deviceProtectedContext
import com.topjohnwu.magisk.core.ktx.rawResource
import com.topjohnwu.magisk.core.ktx.writeTo
import com.topjohnwu.superuser.Shell
import com.topjohnwu.superuser.ShellUtils
import java.io.File
@@ -41,7 +41,7 @@ class ShellInit : Shell.Initializer() {
}
if (shell.isRoot) {
add("export MAGISKTMP=\$(magisk --path)/.magisk")
add("export MAGISKTMP=\$(magisk --path)")
// Test if we can properly execute stuff in /data
Info.noDataExec = !shell.newJob().add("$localBB sh -c \"$localBB true\"").exec().isSuccess
}
@@ -49,12 +49,12 @@ class ShellInit : Shell.Initializer() {
if (Info.noDataExec) {
// Copy it out of /data to workaround Samsung bullshit
add(
"if [ -x \$MAGISKTMP/busybox/busybox ]; then",
" cp -af $localBB \$MAGISKTMP/busybox/busybox",
" exec \$MAGISKTMP/busybox/busybox sh",
"if [ -x \$MAGISKTMP/.magisk/busybox/busybox ]; then",
" cp -af $localBB \$MAGISKTMP/.magisk/busybox/busybox",
" exec \$MAGISKTMP/.magisk/busybox/busybox sh",
"else",
" cp -af $localBB /dev/.busybox",
" exec /dev/.busybox sh",
" cp -af $localBB /dev/busybox",
" exec /dev/busybox sh",
"fi"
)
} else {
@@ -73,18 +73,17 @@ class ShellInit : Shell.Initializer() {
fun getVar(name: String) = fastCmd("echo \$$name")
fun getBool(name: String) = getVar(name).toBoolean()
Const.MAGISKTMP = getVar("MAGISKTMP")
Info.isSAR = getBool("SYSTEM_ROOT")
Info.isSAR = getBool("SYSTEM_AS_ROOT")
Info.ramdisk = getBool("RAMDISKEXIST")
Info.vbmeta = getBool("VBMETAEXIST")
Info.isAB = getBool("ISAB")
Info.crypto = getVar("CRYPTOTYPE")
Info.patchBootVbmeta = getBool("PATCHVBMETAFLAG")
Info.legacySAR = getBool("LEGACYSAR")
// Default presets
Config.recovery = getBool("RECOVERYMODE")
Config.keepVerity = getBool("KEEPVERITY")
Config.keepEnc = getBool("KEEPFORCEENCRYPT")
Config.patchVbmeta = getBool("PATCHVBMETAFLAG")
return true
}

View File

@@ -1,49 +0,0 @@
package com.topjohnwu.magisk.core.utils.net
import android.annotation.TargetApi
import android.content.Context
import android.net.ConnectivityManager
import android.net.Network
import android.net.NetworkCapabilities
import android.net.NetworkRequest
import androidx.collection.ArraySet
@TargetApi(21)
open class LollipopNetworkObserver(
context: Context,
callback: ConnectionCallback
): NetworkObserver(context, callback) {
private val networkCallback = NetCallback()
init {
val request = NetworkRequest.Builder()
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.build()
manager.registerNetworkCallback(request, networkCallback)
}
@Suppress("DEPRECATION")
override fun getCurrentState() {
callback(manager.activeNetworkInfo?.isConnected ?: false)
}
override fun stopObserving() {
manager.unregisterNetworkCallback(networkCallback)
}
private inner class NetCallback : ConnectivityManager.NetworkCallback() {
private val activeList = ArraySet<Network>()
override fun onAvailable(network: Network) {
activeList.add(network)
callback(true)
}
override fun onLost(network: Network) {
activeList.remove(network)
callback(!activeList.isEmpty())
}
}
}

View File

@@ -1,53 +0,0 @@
@file:Suppress("DEPRECATION")
package com.topjohnwu.magisk.core.utils.net
import android.annotation.TargetApi
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.net.NetworkCapabilities
import android.os.PowerManager
import androidx.core.content.getSystemService
@TargetApi(23)
class MarshmallowNetworkObserver(
context: Context,
callback: ConnectionCallback
): LollipopNetworkObserver(context, callback) {
private val receiver = IdleBroadcastReceiver()
init {
val filter = IntentFilter(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED)
app.registerReceiver(receiver, filter)
}
override fun stopObserving() {
super.stopObserving()
app.unregisterReceiver(receiver)
}
override fun getCurrentState() {
callback(manager.getNetworkCapabilities(manager.activeNetwork)
?.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) ?: false)
}
private inner class IdleBroadcastReceiver: BroadcastReceiver() {
private fun Context.isIdleMode(): Boolean {
val pwm = getSystemService<PowerManager>() ?: return true
val isIgnoringOptimizations = pwm.isIgnoringBatteryOptimizations(packageName)
return pwm.isDeviceIdleMode && !isIgnoringOptimizations
}
override fun onReceive(context: Context, intent: Intent) {
if (context.isIdleMode()) {
callback(false)
} else {
getCurrentState()
}
}
}
}

View File

@@ -1,29 +0,0 @@
package com.topjohnwu.magisk.core.utils.net
import android.content.Context
import android.net.ConnectivityManager
import android.os.Build
import androidx.core.content.getSystemService
typealias ConnectionCallback = (Boolean) -> Unit
abstract class NetworkObserver(
context: Context,
protected val callback: ConnectionCallback
) {
protected val app: Context = context.applicationContext
protected val manager = context.getSystemService<ConnectivityManager>()!!
protected abstract fun stopObserving()
protected abstract fun getCurrentState()
companion object {
fun observe(context: Context, callback: ConnectionCallback): NetworkObserver {
val observer: NetworkObserver = if (Build.VERSION.SDK_INT >= 23)
MarshmallowNetworkObserver(context, callback)
else LollipopNetworkObserver(context, callback)
return observer.apply { getCurrentState() }
}
}
}

View File

@@ -1,37 +1,53 @@
package com.topjohnwu.magisk.databinding
import androidx.annotation.MainThread
import androidx.annotation.WorkerThread
import androidx.databinding.ListChangeRegistry
import androidx.databinding.ObservableList
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListUpdateCallback
import java.util.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.util.AbstractList
/**
* @param callback The callback that controls the behavior of the DiffObservableList.
* @param detectMoves True if DiffUtil should try to detect moved items, false otherwise.
*/
open class DiffObservableList<T>(
private val callback: Callback<T>,
private val detectMoves: Boolean = true
) : AbstractList<T>(), ObservableList<T> {
// Only expose the immutable List types
interface DiffList<T : DiffItem<*>> : List<T> {
fun calculateDiff(newItems: List<T>): DiffUtil.DiffResult
protected var list: MutableList<T> = ArrayList()
@MainThread
fun update(newItems: List<T>, diffResult: DiffUtil.DiffResult)
@WorkerThread
suspend fun update(newItems: List<T>)
}
interface FilterList<T : DiffItem<*>> : List<T> {
fun filter(filter: (T) -> Boolean)
@MainThread
fun set(newItems: List<T>)
}
fun <T : DiffItem<*>> diffList(): DiffList<T> = DiffObservableList()
fun <T : DiffItem<*>> filterList(scope: CoroutineScope): FilterList<T> =
FilterableDiffObservableList(scope)
private open class DiffObservableList<T : DiffItem<*>>
: AbstractList<T>(), ObservableList<T>, DiffList<T>, ListUpdateCallback {
protected var list: List<T> = emptyList()
private val listeners = ListChangeRegistry()
protected val listCallback = ObservableListUpdateCallback()
override val size: Int get() = list.size
/**
* Calculates the list of update operations that can convert this list into the given one.
*
* @param newItems The items that this list will be set to.
* @return A DiffResult that contains the information about the edit sequence to covert this
* list into the given one.
*/
fun calculateDiff(newItems: List<T>): DiffUtil.DiffResult {
val frozenList = ArrayList(list)
return doCalculateDiff(frozenList, newItems)
override fun get(index: Int) = list[index]
override fun calculateDiff(newItems: List<T>): DiffUtil.DiffResult {
return doCalculateDiff(list, newItems)
}
protected fun doCalculateDiff(oldItems: List<T>, newItems: List<T>): DiffUtil.DiffResult {
@@ -40,48 +56,35 @@ open class DiffObservableList<T>(
override fun getNewListSize() = newItems.size
@Suppress("UNCHECKED_CAST")
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
val oldItem = oldItems[oldItemPosition]
val newItem = newItems[newItemPosition]
return callback.areItemsTheSame(oldItem, newItem)
return (oldItem as DiffItem<Any>).itemSameAs(newItem)
}
@Suppress("UNCHECKED_CAST")
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
val oldItem = oldItems[oldItemPosition]
val newItem = newItems[newItemPosition]
return callback.areContentsTheSame(oldItem, newItem)
return (oldItem as DiffItem<Any>).contentSameAs(newItem)
}
}, detectMoves)
}, true)
}
/**
* Updates the contents of this list to the given one using the DiffResults to dispatch change
* notifications.
*
* @param newItems The items to set this list to.
* @param diffResult The diff results to dispatch change notifications.
*/
@MainThread
fun update(newItems: List<T>, diffResult: DiffUtil.DiffResult) {
list = newItems.toMutableList()
diffResult.dispatchUpdatesTo(listCallback)
override fun update(newItems: List<T>, diffResult: DiffUtil.DiffResult) {
list = ArrayList(newItems)
diffResult.dispatchUpdatesTo(this)
}
/**
* Sets this list to the given items. This is a convenience method for calling [ ][.calculateDiff] followed by [.update].
*
*
* **Warning!** If the lists are large this operation may be too slow for the main thread. In
* that case, you should call [.calculateDiff] on a background thread and then
* [.update] on the main thread.
*
* @param newItems The items to set this list to.
*/
@MainThread
fun update(newItems: List<T>) {
val diffResult = doCalculateDiff(list, newItems)
@WorkerThread
override suspend fun update(newItems: List<T>) {
val diffResult = calculateDiff(newItems)
withContext(Dispatchers.Main) {
update(newItems, diffResult)
}
}
override fun addOnListChangedCallback(listener: ObservableList.OnListChangedCallback<out ObservableList<T>>) {
listeners.add(listener)
@@ -91,113 +94,63 @@ open class DiffObservableList<T>(
listeners.remove(listener)
}
override fun get(index: Int) = list[index]
override fun add(index: Int, element: T) {
list.add(index, element)
notifyAdd(index, 1)
}
override fun addAll(elements: Collection<T>) = addAll(size, elements)
override fun addAll(index: Int, elements: Collection<T>): Boolean {
val added = list.addAll(index, elements)
if (added) {
notifyAdd(index, elements.size)
}
return added
}
override fun clear() {
val oldSize = size
list.clear()
if (oldSize != 0) {
notifyRemove(0, oldSize)
}
}
override fun remove(element: T): Boolean {
val index = indexOf(element)
return if (index >= 0) {
removeAt(index)
true
} else {
false
}
}
override fun removeAt(index: Int): T {
val element = list.removeAt(index)
notifyRemove(index, 1)
return element
}
override fun set(index: Int, element: T): T {
val old = list.set(index, element)
listeners.notifyChanged(this, index, 1)
return old
}
private fun notifyAdd(start: Int, count: Int) {
listeners.notifyInserted(this, start, count)
}
private fun notifyRemove(start: Int, count: Int) {
listeners.notifyRemoved(this, start, count)
}
/**
* A Callback class used by DiffUtil while calculating the diff between two lists.
*/
interface Callback<T> {
/**
* Called by the DiffUtil to decide whether two object represent the same Item.
*
*
* For example, if your items have unique ids, this method should check their id equality.
*
* @param oldItem The old item.
* @param newItem The new item.
* @return True if the two items represent the same object or false if they are different.
*/
fun areItemsTheSame(oldItem: T, newItem: T): Boolean
/**
* Called by the DiffUtil when it wants to check whether two items have the same data.
* DiffUtil uses this information to detect if the contents of an item has changed.
*
*
* DiffUtil uses this method to check equality instead of [Object.equals] so
* that you can change its behavior depending on your UI.
*
*
* This method is called only if [.areItemsTheSame] returns `true` for
* these items.
*
* @param oldItem The old item.
* @param newItem The new item which replaces the old item.
* @return True if the contents of the items are the same or false if they are different.
*/
fun areContentsTheSame(oldItem: T, newItem: T): Boolean
}
inner class ObservableListUpdateCallback : ListUpdateCallback {
override fun onChanged(position: Int, count: Int, payload: Any?) {
listeners.notifyChanged(this@DiffObservableList, position, count)
listeners.notifyChanged(this, position, count)
}
override fun onMoved(fromPosition: Int, toPosition: Int) {
listeners.notifyMoved(this@DiffObservableList, fromPosition, toPosition, 1)
listeners.notifyMoved(this, fromPosition, toPosition, 1)
}
override fun onInserted(position: Int, count: Int) {
modCount += 1
listeners.notifyInserted(this@DiffObservableList, position, count)
listeners.notifyInserted(this, position, count)
}
override fun onRemoved(position: Int, count: Int) {
modCount += 1
listeners.notifyRemoved(this@DiffObservableList, position, count)
listeners.notifyRemoved(this, position, count)
}
}
private class FilterableDiffObservableList<T : DiffItem<*>>(
private val scope: CoroutineScope
) : DiffObservableList<T>(), FilterList<T> {
private var sublist: List<T> = emptyList()
private var job: Job? = null
private var lastFilter: ((T) -> Boolean)? = null
// ---
override fun filter(filter: (T) -> Boolean) {
lastFilter = filter
job?.cancel()
job = scope.launch(Dispatchers.Default) {
val oldList = sublist
val newList = list.filter(filter)
val diff = doCalculateDiff(oldList, newList)
withContext(Dispatchers.Main) {
sublist = newList
diff.dispatchUpdatesTo(this@FilterableDiffObservableList)
}
}
}
// ---
override fun get(index: Int): T {
return sublist[index]
}
override val size: Int
get() = sublist.size
@MainThread
override fun set(newItems: List<T>) {
onRemoved(0, sublist.size)
list = newItems
sublist = emptyList()
lastFilter?.let { filter(it) }
}
}

View File

@@ -1,85 +0,0 @@
package com.topjohnwu.magisk.databinding
import android.os.Handler
import android.os.HandlerThread
import android.os.Looper
import java.util.*
class FilterableDiffObservableList<T>(
callback: Callback<T>
) : DiffObservableList<T>(callback) {
var filter: ((T) -> Boolean)? = null
set(value) {
field = value
queueUpdate()
}
@Volatile
private var sublist: MutableList<T> = super.list
// ---
private val ui by lazy { Handler(Looper.getMainLooper()) }
private val handler = Handler(HandlerThread("List${hashCode()}").apply { start() }.looper)
private val updater = Runnable {
val filter = filter ?: { true }
val newList = super.list.filter(filter)
val diff = synchronized(this) { doCalculateDiff(sublist, newList) }
ui.post {
sublist = Collections.synchronizedList(newList)
diff.dispatchUpdatesTo(listCallback)
}
}
private fun queueUpdate() {
handler.removeCallbacks(updater)
handler.post(updater)
}
fun hasFilter() = filter != null
fun filter(switch: (T) -> Boolean) {
filter = switch
}
fun reset() {
filter = null
}
// ---
override fun get(index: Int): T {
return sublist.get(index)
}
override fun add(element: T): Boolean {
return sublist.add(element)
}
override fun add(index: Int, element: T) {
sublist.add(index, element)
}
override fun addAll(elements: Collection<T>): Boolean {
return sublist.addAll(elements)
}
override fun addAll(index: Int, elements: Collection<T>): Boolean {
return sublist.addAll(index, elements)
}
override fun remove(element: T): Boolean {
return sublist.remove(element)
}
override fun removeAt(index: Int): T {
return sublist.removeAt(index)
}
override fun set(index: Int, element: T): T {
return sublist.set(index, element)
}
override val size: Int
get() = sublist.size
}

View File

@@ -1,7 +0,0 @@
package com.topjohnwu.magisk.databinding
fun <T : AnyDiffRvItem> diffListOf() =
DiffObservableList(DiffRvItem.callback<T>())
fun <T : AnyDiffRvItem> filterableListOf() =
FilterableDiffObservableList(DiffRvItem.callback<T>())

View File

@@ -46,11 +46,11 @@ class MergeObservableList<T> : AbstractList<T>(), ObservableList<T> {
return this
}
fun insertList(list: ObservableList<out T>): MergeObservableList<T> {
fun insertList(list: List<T>): MergeObservableList<T> {
val idx = size
lists.add(list)
++modCount
(list as ObservableList<T>).addOnListChangedCallback(callback)
(list as? ObservableList<T>)?.addOnListChangedCallback(callback)
if (list.isNotEmpty())
listeners.notifyInserted(this, idx, list.size)
return this
@@ -72,11 +72,11 @@ class MergeObservableList<T> : AbstractList<T>(), ObservableList<T> {
return false
}
fun removeList(listToRemove: ObservableList<out T>): Boolean {
fun removeList(listToRemove: List<T>): Boolean {
var idx = 0
for ((i, list) in lists.withIndex()) {
if (listToRemove === list) {
(list as ObservableList<T>).removeOnListChangedCallback(callback)
(list as? ObservableList<T>)?.removeOnListChangedCallback(callback)
lists.removeAt(i)
++modCount
listeners.notifyRemoved(this, idx, list.size)
@@ -90,8 +90,8 @@ class MergeObservableList<T> : AbstractList<T>(), ObservableList<T> {
override fun clear() {
val sz = size
for (list in lists) {
if (list is ObservableList<*>) {
(list as ObservableList<T>).removeOnListChangedCallback(callback)
if (list is ObservableList) {
list.removeOnListChangedCallback(callback)
}
}
++modCount

View File

@@ -8,60 +8,28 @@ abstract class RvItem {
abstract val layoutRes: Int
}
interface RvContainer<E> {
val item: E
}
interface ViewAwareRvItem {
fun onBind(binding: ViewDataBinding, recyclerView: RecyclerView)
}
interface ComparableRv<T> : Comparable<T> {
@Suppress("UNCHECKED_CAST")
fun comparableEqual(o: Any?) =
o != null && o::class == this::class && compareTo(o as T) == 0
}
abstract class DiffRvItem<T> : RvItem() {
// Defer to contentSameAs by default
open fun itemSameAs(other: T) = true
open fun contentSameAs(other: T) =
when (this) {
is RvContainer<*> -> item == (other as RvContainer<*>).item
is ComparableRv<*> -> comparableEqual(other)
else -> this == other
}
companion object {
private val callback = object : DiffObservableList.Callback<DiffRvItem<Any>> {
override fun areItemsTheSame(
oldItem: DiffRvItem<Any>,
newItem: DiffRvItem<Any>
): Boolean {
return oldItem::class == newItem::class && oldItem.itemSameAs(newItem)
}
override fun areContentsTheSame(
oldItem: DiffRvItem<Any>,
newItem: DiffRvItem<Any>
): Boolean {
return oldItem.contentSameAs(newItem)
}
}
@Suppress("UNCHECKED_CAST")
fun <T : AnyDiffRvItem> callback() = callback as DiffObservableList.Callback<T>
}
}
typealias AnyDiffRvItem = DiffRvItem<*>
abstract class ObservableDiffRvItem<T> : DiffRvItem<T>(), ObservableHost {
override var callbacks: PropertyChangeRegistry? = null
}
abstract class ObservableRvItem : RvItem(), ObservableHost {
override var callbacks: PropertyChangeRegistry? = null
}
interface ItemWrapper<E> {
val item: E
}
interface ViewAwareItem {
fun onBind(binding: ViewDataBinding, recyclerView: RecyclerView)
}
interface DiffItem<T : Any> {
fun itemSameAs(other: T): Boolean {
if (this === other) return true
return when (this) {
is ItemWrapper<*> -> item == (other as ItemWrapper<*>).item
is Comparable<*> -> compareValues(this, other as Comparable<*>) == 0
else -> this == other
}
}
fun contentSameAs(other: T) = true
}

View File

@@ -15,8 +15,8 @@ import androidx.recyclerview.widget.RecyclerView
import com.topjohnwu.magisk.BR
class RvItemAdapter<T: RvItem>(
private val items: List<T>,
private val extraBindings: SparseArray<*>?
val items: List<T>,
val extraBindings: SparseArray<*>?
) : RecyclerView.Adapter<RvItemAdapter.ViewHolder>() {
private var lifecycleOwner: LifecycleOwner? = null
@@ -53,7 +53,7 @@ class RvItemAdapter<T: RvItem>(
holder.binding.lifecycleOwner = lifecycleOwner
holder.binding.executePendingBindings()
recyclerView?.let {
if (item is ViewAwareRvItem)
if (item is ViewAwareItem)
item.onBind(holder.binding, it)
}
}
@@ -113,6 +113,9 @@ inline fun bindExtra(body: (SparseArray<Any?>) -> Unit) = SparseArray<Any?>().al
@BindingAdapter("items", "extraBindings", requireAll = false)
fun <T: RvItem> RecyclerView.setAdapter(items: List<T>?, extraBindings: SparseArray<*>?) {
if (items != null) {
val rva = (adapter as? RvItemAdapter<*>)
if (rva == null || rva.items !== items || rva.extraBindings !== extraBindings) {
adapter = RvItemAdapter(items, extraBindings)
}
}
}

View File

@@ -1,13 +1,14 @@
package com.topjohnwu.magisk.events.dialog
package com.topjohnwu.magisk.dialog
import android.app.Activity
import androidx.appcompat.app.AppCompatDelegate
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.arch.UIActivity
import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.events.DialogBuilder
import com.topjohnwu.magisk.view.MagiskDialog
class DarkThemeDialog : DialogEvent() {
class DarkThemeDialog : DialogBuilder {
override fun build(dialog: MagiskDialog) {
val activity = dialog.ownerActivity!!

View File

@@ -1,4 +1,4 @@
package com.topjohnwu.magisk.events.dialog
package com.topjohnwu.magisk.dialog
import androidx.lifecycle.lifecycleScope
import com.topjohnwu.magisk.BuildConfig
@@ -6,11 +6,12 @@ import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.base.BaseActivity
import com.topjohnwu.magisk.core.tasks.MagiskInstaller
import com.topjohnwu.magisk.events.DialogBuilder
import com.topjohnwu.magisk.ui.home.HomeViewModel
import com.topjohnwu.magisk.view.MagiskDialog
import kotlinx.coroutines.launch
class EnvFixDialog(private val vm: HomeViewModel) : DialogEvent() {
class EnvFixDialog(private val vm: HomeViewModel, private val code: Int) : DialogBuilder {
override fun build(dialog: MagiskDialog) {
dialog.apply {
@@ -38,8 +39,10 @@ class EnvFixDialog(private val vm: HomeViewModel) : DialogEvent() {
}
}
if (Info.env.versionCode != BuildConfig.VERSION_CODE ||
if (code == 2 || // No rules block, module policy not loaded
Info.env.versionCode != BuildConfig.VERSION_CODE ||
Info.env.versionString != BuildConfig.VERSION_NAME) {
dialog.setMessage(R.string.env_full_fix_msg)
dialog.setButton(MagiskDialog.ButtonType.POSITIVE) {
text = android.R.string.ok
onClick {

View File

@@ -0,0 +1,33 @@
package com.topjohnwu.magisk.dialog
import android.net.Uri
import com.topjohnwu.magisk.MainDirections
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.events.DialogBuilder
import com.topjohnwu.magisk.ui.module.ModuleViewModel
import com.topjohnwu.magisk.view.MagiskDialog
class LocalModuleInstallDialog(
private val viewModel: ModuleViewModel,
private val uri: Uri,
private val displayName: String
) : DialogBuilder {
override fun build(dialog: MagiskDialog) {
dialog.apply {
setTitle(R.string.confirm_install_title)
setMessage(context.getString(R.string.confirm_install, displayName))
setButton(MagiskDialog.ButtonType.POSITIVE) {
text = android.R.string.ok
onClick {
viewModel.apply {
MainDirections.actionFlashFragment(Const.Value.FLASH_ZIP, uri).navigate()
}
}
}
setButton(MagiskDialog.ButtonType.NEGATIVE) {
text = android.R.string.cancel
}
}
}
}

View File

@@ -1,4 +1,4 @@
package com.topjohnwu.magisk.events.dialog
package com.topjohnwu.magisk.dialog
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.Info
@@ -29,7 +29,7 @@ class ManagerInstallDialog : MarkDownDialog() {
setCancelable(true)
setButton(MagiskDialog.ButtonType.POSITIVE) {
text = R.string.install
onClick { DownloadService.start(context, Subject.App()) }
onClick { DownloadService.start(activity, Subject.App()) }
}
setButton(MagiskDialog.ButtonType.NEGATIVE) {
text = android.R.string.cancel

View File

@@ -1,12 +1,12 @@
package com.topjohnwu.magisk.events.dialog
package com.topjohnwu.magisk.dialog
import android.view.LayoutInflater
import android.widget.TextView
import androidx.annotation.CallSuper
import androidx.lifecycle.lifecycleScope
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.base.BaseActivity
import com.topjohnwu.magisk.core.di.ServiceLocator
import com.topjohnwu.magisk.events.DialogBuilder
import com.topjohnwu.magisk.view.MagiskDialog
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@@ -14,7 +14,7 @@ import kotlinx.coroutines.withContext
import timber.log.Timber
import java.io.IOException
abstract class MarkDownDialog : DialogEvent() {
abstract class MarkDownDialog : DialogBuilder {
abstract suspend fun getMarkdownText(): String
@@ -24,7 +24,7 @@ abstract class MarkDownDialog : DialogEvent() {
val view = LayoutInflater.from(context).inflate(R.layout.markdown_window_md2, null)
setView(view)
val tv = view.findViewById<TextView>(R.id.md_txt)
(ownerActivity as BaseActivity).lifecycleScope.launch {
activity.lifecycleScope.launch {
try {
val text = withContext(Dispatchers.IO) { getMarkdownText() }
ServiceLocator.markwon.setMarkdown(tv, text)

View File

@@ -1,4 +1,4 @@
package com.topjohnwu.magisk.events.dialog
package com.topjohnwu.magisk.dialog
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.di.ServiceLocator
@@ -8,7 +8,7 @@ import com.topjohnwu.magisk.core.download.Subject
import com.topjohnwu.magisk.core.model.module.OnlineModule
import com.topjohnwu.magisk.view.MagiskDialog
class ModuleInstallDialog(private val item: OnlineModule) : MarkDownDialog() {
class OnlineModuleInstallDialog(private val item: OnlineModule) : MarkDownDialog() {
private val svc get() = ServiceLocator.networkService
@@ -24,7 +24,7 @@ class ModuleInstallDialog(private val item: OnlineModule) : MarkDownDialog() {
fun download(install: Boolean) {
val action = if (install) Action.Flash else Action.Download
val subject = Subject.Module(item, action)
DownloadService.start(context, subject)
DownloadService.start(activity, subject)
}
val title = context.getString(R.string.repo_install_title,

View File

@@ -1,9 +1,10 @@
package com.topjohnwu.magisk.events.dialog
package com.topjohnwu.magisk.dialog
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.events.DialogBuilder
import com.topjohnwu.magisk.view.MagiskDialog
class SecondSlotWarningDialog : DialogEvent() {
class SecondSlotWarningDialog : DialogBuilder {
override fun build(dialog: MagiskDialog) {
dialog.apply {

View File

@@ -0,0 +1,25 @@
package com.topjohnwu.magisk.dialog
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.events.DialogBuilder
import com.topjohnwu.magisk.view.MagiskDialog
class SuperuserRevokeDialog(
private val appName: String,
private val onSuccess: () -> Unit
) : DialogBuilder {
override fun build(dialog: MagiskDialog) {
dialog.apply {
setTitle(R.string.su_revoke_title)
setMessage(R.string.su_revoke_msg, appName)
setButton(MagiskDialog.ButtonType.POSITIVE) {
text = android.R.string.ok
onClick { onSuccess() }
}
setButton(MagiskDialog.ButtonType.NEGATIVE) {
text = android.R.string.cancel
}
}
}
}

View File

@@ -1,16 +1,17 @@
package com.topjohnwu.magisk.events.dialog
package com.topjohnwu.magisk.dialog
import android.app.ProgressDialog
import android.content.Context
import android.widget.Toast
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.arch.NavigationActivity
import com.topjohnwu.magisk.core.ktx.toast
import com.topjohnwu.magisk.events.DialogBuilder
import com.topjohnwu.magisk.ui.flash.FlashFragment
import com.topjohnwu.magisk.utils.Utils
import com.topjohnwu.magisk.view.MagiskDialog
import com.topjohnwu.superuser.Shell
class UninstallDialog : DialogEvent() {
class UninstallDialog : DialogBuilder {
override fun build(dialog: MagiskDialog) {
dialog.apply {
@@ -37,9 +38,9 @@ class UninstallDialog : DialogEvent() {
Shell.cmd("restore_imgs").submit { result ->
dialog.dismiss()
if (result.isSuccess) {
Utils.toast(R.string.restore_done, Toast.LENGTH_SHORT)
context.toast(R.string.restore_done, Toast.LENGTH_SHORT)
} else {
Utils.toast(R.string.restore_fail, Toast.LENGTH_LONG)
context.toast(R.string.restore_fail, Toast.LENGTH_LONG)
}
}
}

View File

@@ -5,10 +5,15 @@ import android.view.View
import androidx.annotation.StringRes
import androidx.navigation.NavDirections
import com.google.android.material.snackbar.Snackbar
import com.topjohnwu.magisk.arch.*
import com.topjohnwu.magisk.arch.ActivityExecutor
import com.topjohnwu.magisk.arch.ContextExecutor
import com.topjohnwu.magisk.arch.NavigationActivity
import com.topjohnwu.magisk.arch.UIActivity
import com.topjohnwu.magisk.arch.ViewEvent
import com.topjohnwu.magisk.core.base.ContentResultCallback
import com.topjohnwu.magisk.utils.TextHolder
import com.topjohnwu.magisk.utils.asText
import com.topjohnwu.magisk.view.MagiskDialog
import com.topjohnwu.magisk.view.Shortcuts
class PermissionEvent(
@@ -46,6 +51,16 @@ class RecreateEvent : ViewEvent(), ActivityExecutor {
}
}
class AuthEvent(
private val callback: () -> Unit
) : ViewEvent(), ActivityExecutor {
override fun invoke(activity: UIActivity<*>) {
activity.authenticateCallback = { if (it) callback() }
activity.requestAuthenticate.launch(Unit)
}
}
class GetContentEvent(
private val type: String,
private val callback: ContentResultCallback
@@ -95,3 +110,15 @@ class SnackbarEvent(
activity.showSnackbar(msg.getText(activity.resources), length, builder)
}
}
class DialogEvent(
private val builder: DialogBuilder
) : ViewEvent(), ActivityExecutor {
override fun invoke(activity: UIActivity<*>) {
MagiskDialog(activity).apply(builder::build).show()
}
}
interface DialogBuilder {
fun build(dialog: MagiskDialog)
}

View File

@@ -1,38 +0,0 @@
package com.topjohnwu.magisk.events.dialog
import com.topjohnwu.magisk.arch.ActivityExecutor
import com.topjohnwu.magisk.arch.UIActivity
import com.topjohnwu.magisk.arch.ViewEvent
import com.topjohnwu.magisk.core.utils.BiometricHelper
class BiometricEvent(
builder: Builder.() -> Unit
) : ViewEvent(), ActivityExecutor {
private var listenerOnFailure: GenericDialogListener = {}
private var listenerOnSuccess: GenericDialogListener = {}
init {
builder(Builder())
}
override fun invoke(activity: UIActivity<*>) {
BiometricHelper.authenticate(
activity,
onError = listenerOnFailure,
onSuccess = listenerOnSuccess
)
}
inner class Builder internal constructor() {
fun onFailure(listener: GenericDialogListener) {
listenerOnFailure = listener
}
fun onSuccess(listener: GenericDialogListener) {
listenerOnSuccess = listener
}
}
}

View File

@@ -1,20 +0,0 @@
package com.topjohnwu.magisk.events.dialog
import com.topjohnwu.magisk.arch.ActivityExecutor
import com.topjohnwu.magisk.arch.UIActivity
import com.topjohnwu.magisk.arch.ViewEvent
import com.topjohnwu.magisk.view.MagiskDialog
abstract class DialogEvent : ViewEvent(), ActivityExecutor {
override fun invoke(activity: UIActivity<*>) {
MagiskDialog(activity)
.apply { setOwnerActivity(activity) }
.apply(this::build).show()
}
abstract fun build(dialog: MagiskDialog)
}
typealias GenericDialogListener = () -> Unit

View File

@@ -1,35 +0,0 @@
package com.topjohnwu.magisk.events.dialog
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.view.MagiskDialog
class SuperuserRevokeDialog(
builder: Builder.() -> Unit
) : DialogEvent() {
private val callbacks = Builder().apply(builder)
override fun build(dialog: MagiskDialog) {
dialog.apply {
setTitle(R.string.su_revoke_title)
setMessage(R.string.su_revoke_msg, callbacks.appName)
setButton(MagiskDialog.ButtonType.POSITIVE) {
text = android.R.string.ok
onClick { callbacks.listenerOnSuccess() }
}
setButton(MagiskDialog.ButtonType.NEGATIVE) {
text = android.R.string.cancel
}
}
}
inner class Builder internal constructor() {
var appName: String = ""
internal var listenerOnSuccess: GenericDialogListener = {}
fun onSuccess(listener: GenericDialogListener) {
listenerOnSuccess = listener
}
}
}

View File

@@ -1,267 +0,0 @@
package com.topjohnwu.magisk.ktx
import android.annotation.SuppressLint
import android.app.Activity
import android.content.ComponentName
import android.content.Context
import android.content.ContextWrapper
import android.content.Intent
import android.content.pm.ApplicationInfo
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.content.pm.PackageManager.PERMISSION_GRANTED
import android.content.res.Configuration
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.drawable.AdaptiveIconDrawable
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.LayerDrawable
import android.net.Uri
import android.os.Build.VERSION.SDK_INT
import android.os.Process
import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.InputMethodManager
import androidx.appcompat.content.res.AppCompatResources
import androidx.core.content.ContextCompat
import androidx.core.content.getSystemService
import androidx.interpolator.view.animation.FastOutSlowInInterpolator
import androidx.transition.AutoTransition
import androidx.transition.TransitionManager
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.utils.RootUtils
import com.topjohnwu.magisk.core.utils.currentLocale
import com.topjohnwu.superuser.Shell
import java.io.File
import kotlin.Array
import kotlin.String
import java.lang.reflect.Array as JArray
fun Context.rawResource(id: Int) = resources.openRawResource(id)
fun Context.getBitmap(id: Int): Bitmap {
var drawable = AppCompatResources.getDrawable(this, id)!!
if (drawable is BitmapDrawable)
return drawable.bitmap
if (SDK_INT >= 26 && drawable is AdaptiveIconDrawable) {
drawable = LayerDrawable(arrayOf(drawable.background, drawable.foreground))
}
val bitmap = Bitmap.createBitmap(
drawable.intrinsicWidth, drawable.intrinsicHeight,
Bitmap.Config.ARGB_8888
)
val canvas = Canvas(bitmap)
drawable.setBounds(0, 0, canvas.width, canvas.height)
drawable.draw(canvas)
return bitmap
}
val Context.deviceProtectedContext: Context get() =
if (SDK_INT >= 24) {
createDeviceProtectedStorageContext()
} else { this }
fun Intent.startActivityWithRoot() {
val args = mutableListOf("am", "start", "--user", Const.USER_ID.toString())
val cmd = toCommand(args).joinToString(" ")
Shell.cmd(cmd).submit()
}
fun Intent.toCommand(args: MutableList<String> = mutableListOf()): MutableList<String> {
action?.also {
args.add("-a")
args.add(it)
}
component?.also {
args.add("-n")
args.add(it.flattenToString())
}
data?.also {
args.add("-d")
args.add(it.toString())
}
categories?.also {
for (cat in it) {
args.add("-c")
args.add(cat)
}
}
type?.also {
args.add("-t")
args.add(it)
}
extras?.also {
loop@ for (key in it.keySet()) {
val v = it[key] ?: continue
var value: Any = v
val arg: String
when {
v is String -> arg = "--es"
v is Boolean -> arg = "--ez"
v is Int -> arg = "--ei"
v is Long -> arg = "--el"
v is Float -> arg = "--ef"
v is Uri -> arg = "--eu"
v is ComponentName -> {
arg = "--ecn"
value = v.flattenToString()
}
v is List<*> -> {
if (v.isEmpty())
continue@loop
arg = if (v[0] is Int)
"--eial"
else if (v[0] is Long)
"--elal"
else if (v[0] is Float)
"--efal"
else if (v[0] is String)
"--esal"
else
continue@loop /* Unsupported */
val sb = StringBuilder()
for (o in v) {
sb.append(o.toString().replace(",", "\\,"))
sb.append(',')
}
// Remove trailing comma
sb.deleteCharAt(sb.length - 1)
value = sb
}
v.javaClass.isArray -> {
arg = if (v is IntArray)
"--eia"
else if (v is LongArray)
"--ela"
else if (v is FloatArray)
"--efa"
else if (v is Array<*> && v.isArrayOf<String>())
"--esa"
else
continue@loop /* Unsupported */
val sb = StringBuilder()
val len = JArray.getLength(v)
for (i in 0 until len) {
sb.append(JArray.get(v, i)!!.toString().replace(",", "\\,"))
sb.append(',')
}
// Remove trailing comma
sb.deleteCharAt(sb.length - 1)
value = sb
}
else -> continue@loop
} /* Unsupported */
args.add(arg)
args.add(key)
args.add(value.toString())
}
}
args.add("-f")
args.add(flags.toString())
return args
}
fun Context.cachedFile(name: String) = File(cacheDir, name)
fun ApplicationInfo.getLabel(pm: PackageManager): String {
runCatching {
if (labelRes > 0) {
val res = pm.getResourcesForApplication(this)
val config = Configuration()
config.setLocale(currentLocale)
res.updateConfiguration(config, res.displayMetrics)
return res.getString(labelRes)
}
}
return loadLabel(pm).toString()
}
fun Context.unwrap(): Context {
var context = this
while (true) {
if (context is ContextWrapper)
context = context.baseContext
else
break
}
return context
}
fun Context.hasPermissions(vararg permissions: String) = permissions.all {
ContextCompat.checkSelfPermission(this, it) == PERMISSION_GRANTED
}
fun Activity.hideKeyboard() {
val view = currentFocus ?: return
getSystemService<InputMethodManager>()
?.hideSoftInputFromWindow(view.windowToken, 0)
view.clearFocus()
}
fun ViewGroup.startAnimations() {
val transition = AutoTransition()
.setInterpolator(FastOutSlowInInterpolator())
.setDuration(400)
.excludeTarget(R.id.main_toolbar, true)
TransitionManager.beginDelayedTransition(
this,
transition
)
}
val View.activity: Activity get() {
var context = context
while(true) {
if (context !is ContextWrapper)
error("View is not attached to activity")
if (context is Activity)
return context
context = context.baseContext
}
}
@SuppressLint("PrivateApi")
fun getProperty(key: String, def: String): String {
runCatching {
val clazz = Class.forName("android.os.SystemProperties")
val get = clazz.getMethod("get", String::class.java, String::class.java)
return get.invoke(clazz, key, def) as String
}
return def
}
@SuppressLint("InlinedApi")
@Throws(PackageManager.NameNotFoundException::class)
fun PackageManager.getPackageInfo(uid: Int, pid: Int): PackageInfo? {
val flag = PackageManager.MATCH_UNINSTALLED_PACKAGES
val pkgs = getPackagesForUid(uid) ?: throw PackageManager.NameNotFoundException()
if (pkgs.size > 1) {
if (pid <= 0) {
return null
}
// Try to find package name from PID
val proc = RootUtils.obj?.getAppProcess(pid)
if (proc == null) {
if (uid == Process.SHELL_UID) {
// It is possible that some apps installed are sharing UID with shell.
// We will not be able to find a package from the active process list,
// because the client is forked from ADB shell, not any app process.
return getPackageInfo("com.android.shell", flag)
}
} else if (uid == proc.uid) {
return getPackageInfo(proc.pkgList[0], flag)
}
return null
}
if (pkgs.size == 1) {
return getPackageInfo(pkgs[0], flag)
}
throw PackageManager.NameNotFoundException()
}

View File

@@ -680,7 +680,7 @@ public abstract class ApkSignerV2 {
return "SHA-512";
default:
throw new IllegalArgumentException(
"Unknown content digest algorthm: " + digestAlgorithm);
"Unknown content digest algorithm: " + digestAlgorithm);
}
}
@@ -692,7 +692,7 @@ public abstract class ApkSignerV2 {
return 512 / 8;
default:
throw new IllegalArgumentException(
"Unknown content digest algorthm: " + digestAlgorithm);
"Unknown content digest algorithm: " + digestAlgorithm);
}
}

View File

@@ -1,115 +0,0 @@
package com.topjohnwu.magisk.signing;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.GeneralSecurityException;
import java.security.Key;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.ECPrivateKeySpec;
import java.security.spec.ECPublicKeySpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;
public class CryptoUtils {
static final Map<String, String> ID_TO_ALG;
static final Map<String, String> ALG_TO_ID;
static {
ID_TO_ALG = new HashMap<>();
ALG_TO_ID = new HashMap<>();
ID_TO_ALG.put(X9ObjectIdentifiers.ecdsa_with_SHA256.getId(), "SHA256withECDSA");
ID_TO_ALG.put(X9ObjectIdentifiers.ecdsa_with_SHA384.getId(), "SHA384withECDSA");
ID_TO_ALG.put(X9ObjectIdentifiers.ecdsa_with_SHA512.getId(), "SHA512withECDSA");
ID_TO_ALG.put(PKCSObjectIdentifiers.sha1WithRSAEncryption.getId(), "SHA1withRSA");
ID_TO_ALG.put(PKCSObjectIdentifiers.sha256WithRSAEncryption.getId(), "SHA256withRSA");
ID_TO_ALG.put(PKCSObjectIdentifiers.sha512WithRSAEncryption.getId(), "SHA512withRSA");
ALG_TO_ID.put("SHA256withECDSA", X9ObjectIdentifiers.ecdsa_with_SHA256.getId());
ALG_TO_ID.put("SHA384withECDSA", X9ObjectIdentifiers.ecdsa_with_SHA384.getId());
ALG_TO_ID.put("SHA512withECDSA", X9ObjectIdentifiers.ecdsa_with_SHA512.getId());
ALG_TO_ID.put("SHA1withRSA", PKCSObjectIdentifiers.sha1WithRSAEncryption.getId());
ALG_TO_ID.put("SHA256withRSA", PKCSObjectIdentifiers.sha256WithRSAEncryption.getId());
ALG_TO_ID.put("SHA512withRSA", PKCSObjectIdentifiers.sha512WithRSAEncryption.getId());
}
static String getSignatureAlgorithm(Key key) throws Exception {
if ("EC".equals(key.getAlgorithm())) {
int curveSize;
KeyFactory factory = KeyFactory.getInstance("EC");
if (key instanceof PublicKey) {
ECPublicKeySpec spec = factory.getKeySpec(key, ECPublicKeySpec.class);
curveSize = spec.getParams().getCurve().getField().getFieldSize();
} else if (key instanceof PrivateKey) {
ECPrivateKeySpec spec = factory.getKeySpec(key, ECPrivateKeySpec.class);
curveSize = spec.getParams().getCurve().getField().getFieldSize();
} else {
throw new InvalidKeySpecException();
}
if (curveSize <= 256) {
return "SHA256withECDSA";
} else if (curveSize <= 384) {
return "SHA384withECDSA";
} else {
return "SHA512withECDSA";
}
} else if ("RSA".equals(key.getAlgorithm())) {
return "SHA256withRSA";
} else {
throw new IllegalArgumentException("Unsupported key type " + key.getAlgorithm());
}
}
static AlgorithmIdentifier getSignatureAlgorithmIdentifier(Key key) throws Exception {
String id = ALG_TO_ID.get(getSignatureAlgorithm(key));
if (id == null) {
throw new IllegalArgumentException("Unsupported key type " + key.getAlgorithm());
}
return new AlgorithmIdentifier(new ASN1ObjectIdentifier(id));
}
public static X509Certificate readCertificate(InputStream input)
throws IOException, GeneralSecurityException {
try {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
return (X509Certificate) cf.generateCertificate(input);
} finally {
input.close();
}
}
/** Read a PKCS#8 format private key. */
public static PrivateKey readPrivateKey(InputStream input)
throws IOException, GeneralSecurityException {
try {
ByteArrayStream buf = new ByteArrayStream();
buf.readFrom(input);
byte[] bytes = buf.toByteArray();
/* Check to see if this is in an EncryptedPrivateKeyInfo structure. */
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(bytes);
/*
* Now it's in a PKCS#8 PrivateKeyInfo structure. Read its Algorithm
* OID and use that to construct a KeyFactory.
*/
ASN1InputStream bIn = new ASN1InputStream(new ByteArrayInputStream(spec.getEncoded()));
PrivateKeyInfo pki = PrivateKeyInfo.getInstance(bIn.readObject());
String algOid = pki.getPrivateKeyAlgorithm().getAlgorithm().getId();
return KeyFactory.getInstance(algOid).generatePrivate(spec);
} finally {
input.close();
}
}
}

View File

@@ -1,382 +0,0 @@
package com.topjohnwu.magisk.signing;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.bouncycastle.asn1.ASN1Encodable;
import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1Integer;
import org.bouncycastle.asn1.ASN1Object;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.ASN1Primitive;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.DERPrintableString;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Arrays;
public class SignBoot {
private static final int BOOT_IMAGE_HEADER_V1_RECOVERY_DTBO_SIZE_OFFSET = 1632;
private static final int BOOT_IMAGE_HEADER_V2_DTB_SIZE_OFFSET = 1648;
// Arbitrary maximum header version value; when greater assume the field is dt/extra size
private static final int BOOT_IMAGE_HEADER_VERSION_MAXIMUM = 8;
// Maximum header size byte value to read (currently the bootimg minimum page size)
private static final int BOOT_IMAGE_HEADER_SIZE_MAXIMUM = 2048;
private static class PushBackRWStream extends FilterInputStream {
private OutputStream out;
private int pos = 0;
private byte[] backBuf;
PushBackRWStream(InputStream in, OutputStream o) {
super(in);
out = o;
}
@Override
public int read() throws IOException {
int b;
if (backBuf != null && backBuf.length > pos) {
b = backBuf[pos++];
} else {
b = super.read();
out.write(b);
}
return b;
}
@Override
public int read(byte[] bytes, int off, int len) throws IOException {
int read = 0;
if (backBuf != null && backBuf.length > pos) {
read = Math.min(len, backBuf.length - pos);
System.arraycopy(backBuf, pos, bytes, off, read);
pos += read;
off += read;
len -= read;
}
if (len > 0) {
int ar = super.read(bytes, off, len);
read += ar;
out.write(bytes, off, ar);
}
return read;
}
void unread(byte[] buf) {
backBuf = buf;
}
}
private static int fullRead(InputStream in, byte[] b) throws IOException {
return fullRead(in, b, 0, b.length);
}
private static int fullRead(InputStream in, byte[] b, int off, int len) throws IOException {
int n = 0;
while (n < len) {
int count = in.read(b, off + n, len - n);
if (count <= 0)
break;
n += count;
}
return n;
}
public static boolean doSignature(
@Nullable X509Certificate cert, @Nullable PrivateKey key,
@NonNull InputStream imgIn, @NonNull OutputStream imgOut, @NonNull String target
) {
try {
PushBackRWStream in = new PushBackRWStream(imgIn, imgOut);
byte[] hdr = new byte[BOOT_IMAGE_HEADER_SIZE_MAXIMUM];
// First read the header
fullRead(in, hdr);
int signableSize = getSignableImageSize(hdr);
// Unread header
in.unread(hdr);
BootSignature bootsig = new BootSignature(target, signableSize);
if (cert == null) {
cert = CryptoUtils.readCertificate(
new ByteArrayInputStream(KeyData.verityCert()));
}
bootsig.setCertificate(cert);
if (key == null) {
key = CryptoUtils.readPrivateKey(
new ByteArrayInputStream(KeyData.verityKey()));
}
byte[] sig = bootsig.sign(key, in, signableSize);
bootsig.setSignature(sig, CryptoUtils.getSignatureAlgorithmIdentifier(key));
byte[] encoded_bootsig = bootsig.getEncoded();
imgOut.write(encoded_bootsig);
imgOut.flush();
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
public static boolean verifySignature(InputStream imgIn, X509Certificate cert) {
try {
// Read the header for size
byte[] hdr = new byte[BOOT_IMAGE_HEADER_SIZE_MAXIMUM];
if (fullRead(imgIn, hdr) != hdr.length) {
System.err.println("Unable to read image header");
return false;
}
int signableSize = getSignableImageSize(hdr);
// Read the rest of the image
byte[] rawImg = Arrays.copyOf(hdr, signableSize);
int remain = signableSize - hdr.length;
if (fullRead(imgIn, rawImg, hdr.length, remain) != remain) {
System.err.println("Unable to read image");
return false;
}
// Read footer, which contains the signature
byte[] signature = new byte[4096];
if (imgIn.read(signature) == -1 || Arrays.equals(signature, new byte [signature.length])) {
System.err.println("Invalid image: not signed");
return false;
}
BootSignature bootsig = new BootSignature(signature);
if (cert != null) {
bootsig.setCertificate(cert);
}
if (bootsig.verify(rawImg, signableSize)) {
System.err.println("Signature is VALID");
return true;
} else {
System.err.println("Signature is INVALID");
}
} catch (Exception e) {
e.printStackTrace();
return false;
}
return false;
}
public static int getSignableImageSize(byte[] data) throws Exception {
if (!Arrays.equals(Arrays.copyOfRange(data, 0, 8),
"ANDROID!".getBytes("US-ASCII"))) {
throw new IllegalArgumentException("Invalid image header: missing magic");
}
ByteBuffer image = ByteBuffer.wrap(data);
image.order(ByteOrder.LITTLE_ENDIAN);
image.getLong(); // magic
int kernelSize = image.getInt();
image.getInt(); // kernel_addr
int ramdskSize = image.getInt();
image.getInt(); // ramdisk_addr
int secondSize = image.getInt();
image.getLong(); // second_addr + tags_addr
int pageSize = image.getInt();
if (pageSize >= 0x02000000) {
throw new IllegalArgumentException("Invalid image header: PXA header detected");
}
int length = pageSize // include the page aligned image header
+ ((kernelSize + pageSize - 1) / pageSize) * pageSize
+ ((ramdskSize + pageSize - 1) / pageSize) * pageSize
+ ((secondSize + pageSize - 1) / pageSize) * pageSize;
int headerVersion = image.getInt(); // boot image header version or dt/extra size
if (headerVersion > 0 && headerVersion < BOOT_IMAGE_HEADER_VERSION_MAXIMUM) {
image.position(BOOT_IMAGE_HEADER_V1_RECOVERY_DTBO_SIZE_OFFSET);
int recoveryDtboLength = image.getInt();
length += ((recoveryDtboLength + pageSize - 1) / pageSize) * pageSize;
image.getLong(); // recovery_dtbo address
int headerSize = image.getInt();
if (headerVersion == 2) {
image.position(BOOT_IMAGE_HEADER_V2_DTB_SIZE_OFFSET);
int dtbLength = image.getInt();
length += ((dtbLength + pageSize - 1) / pageSize) * pageSize;
image.getLong(); // dtb address
}
if (image.position() != headerSize) {
throw new IllegalArgumentException("Invalid image header: invalid header length");
}
} else {
// headerVersion is 0 or actually dt/extra size in this case
length += ((headerVersion + pageSize - 1) / pageSize) * pageSize;
}
length = ((length + pageSize - 1) / pageSize) * pageSize;
if (length <= 0) {
throw new IllegalArgumentException("Invalid image header: invalid length");
}
return length;
}
static class BootSignature extends ASN1Object {
private ASN1Integer formatVersion;
private ASN1Encodable certificate;
private AlgorithmIdentifier algId;
private DERPrintableString target;
private ASN1Integer length;
private DEROctetString signature;
private PublicKey publicKey;
private static final int FORMAT_VERSION = 1;
/**
* Initializes the object for signing an image file
* @param target Target name, included in the signed data
* @param length Length of the image, included in the signed data
*/
public BootSignature(String target, int length) {
this.formatVersion = new ASN1Integer(FORMAT_VERSION);
this.target = new DERPrintableString(target);
this.length = new ASN1Integer(length);
}
/**
* Initializes the object for verifying a signed image file
* @param signature Signature footer
*/
public BootSignature(byte[] signature) throws Exception {
ASN1InputStream stream = new ASN1InputStream(signature);
ASN1Sequence sequence = (ASN1Sequence) stream.readObject();
formatVersion = (ASN1Integer) sequence.getObjectAt(0);
if (formatVersion.getValue().intValue() != FORMAT_VERSION) {
throw new IllegalArgumentException("Unsupported format version");
}
certificate = sequence.getObjectAt(1);
byte[] encoded = ((ASN1Object) certificate).getEncoded();
ByteArrayInputStream bis = new ByteArrayInputStream(encoded);
CertificateFactory cf = CertificateFactory.getInstance("X.509");
X509Certificate c = (X509Certificate) cf.generateCertificate(bis);
publicKey = c.getPublicKey();
ASN1Sequence algId = (ASN1Sequence) sequence.getObjectAt(2);
this.algId = new AlgorithmIdentifier((ASN1ObjectIdentifier) algId.getObjectAt(0));
ASN1Sequence attrs = (ASN1Sequence) sequence.getObjectAt(3);
target = (DERPrintableString) attrs.getObjectAt(0);
length = (ASN1Integer) attrs.getObjectAt(1);
this.signature = (DEROctetString) sequence.getObjectAt(4);
}
public ASN1Object getAuthenticatedAttributes() {
ASN1EncodableVector attrs = new ASN1EncodableVector();
attrs.add(target);
attrs.add(length);
return new DERSequence(attrs);
}
public byte[] getEncodedAuthenticatedAttributes() throws IOException {
return getAuthenticatedAttributes().getEncoded();
}
public void setSignature(byte[] sig, AlgorithmIdentifier algId) {
this.algId = algId;
signature = new DEROctetString(sig);
}
public void setCertificate(X509Certificate cert)
throws CertificateEncodingException, IOException {
ASN1InputStream s = new ASN1InputStream(cert.getEncoded());
certificate = s.readObject();
publicKey = cert.getPublicKey();
}
public byte[] sign(PrivateKey key, InputStream is, int len) throws Exception {
Signature signer = Signature.getInstance(CryptoUtils.getSignatureAlgorithm(key));
signer.initSign(key);
int read;
byte buffer[] = new byte[4096];
while ((read = is.read(buffer, 0, Math.min(len, buffer.length))) > 0) {
signer.update(buffer, 0, read);
len -= read;
}
signer.update(getEncodedAuthenticatedAttributes());
return signer.sign();
}
public boolean verify(byte[] image, int length) throws Exception {
if (this.length.getValue().intValue() != length) {
throw new IllegalArgumentException("Invalid image length");
}
String algName = CryptoUtils.ID_TO_ALG.get(algId.getAlgorithm().getId());
if (algName == null) {
throw new IllegalArgumentException("Unsupported algorithm " + algId.getAlgorithm());
}
Signature verifier = Signature.getInstance(algName);
verifier.initVerify(publicKey);
verifier.update(image, 0, length);
verifier.update(getEncodedAuthenticatedAttributes());
return verifier.verify(signature.getOctets());
}
@Override
public ASN1Primitive toASN1Primitive() {
ASN1EncodableVector v = new ASN1EncodableVector();
v.add(formatVersion);
v.add(certificate);
v.add(algId);
v.add(getAuthenticatedAttributes());
v.add(signature);
return new DERSequence(v);
}
}
public static void main(String[] args) throws Exception {
if (args.length > 0 && "-verify".equals(args[0])) {
X509Certificate cert = null;
if (args.length >= 2) {
// args[1] is the path to a public key certificate
cert = CryptoUtils.readCertificate(new FileInputStream(args[1]));
}
boolean signed = SignBoot.verifySignature(System.in, cert);
System.exit(signed ? 0 : 1);
} else if (args.length > 0 && "-sign".equals(args[0])) {
X509Certificate cert = null;
PrivateKey key = null;
String name = "/boot";
if (args.length >= 3) {
cert = CryptoUtils.readCertificate(new FileInputStream(args[1]));
key = CryptoUtils.readPrivateKey(new FileInputStream(args[2]));
}
if (args.length == 2) {
name = args[1];
} else if (args.length >= 4) {
name = args[3];
}
boolean result = SignBoot.doSignature(cert, key, System.in, System.out, name);
System.exit(result ? 0 : 1);
} else {
System.err.println(
"BootSigner <actions> [args]\n" +
"Input from stdin, output to stdout\n" +
"\n" +
"Actions:\n" +
" -verify [x509.pem]\n" +
" verify image. cert is optional.\n" +
" -sign [x509.pem] [pk8] [name]\n" +
" sign image. name and the cert/key pair are optional.\n" +
" name should be either /boot (default) or /recovery.\n"
);
}
}
}

View File

@@ -1,5 +1,7 @@
package com.topjohnwu.magisk.ui
import android.Manifest
import android.annotation.SuppressLint
import android.content.Intent
import android.content.pm.ApplicationInfo
import android.os.Bundle
@@ -10,22 +12,25 @@ import androidx.core.content.pm.ShortcutManagerCompat
import androidx.core.view.forEach
import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import androidx.navigation.NavDirections
import com.topjohnwu.magisk.MainDirections
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.arch.BaseViewModel
import com.topjohnwu.magisk.arch.startAnimations
import com.topjohnwu.magisk.arch.viewModel
import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.isRunningAsStub
import com.topjohnwu.magisk.core.model.module.LocalModule
import com.topjohnwu.magisk.core.tasks.HideAPK
import com.topjohnwu.magisk.databinding.ActivityMainMd2Binding
import com.topjohnwu.magisk.ktx.startAnimations
import com.topjohnwu.magisk.ui.home.HomeFragmentDirections
import com.topjohnwu.magisk.utils.Utils
import com.topjohnwu.magisk.view.MagiskDialog
import com.topjohnwu.magisk.view.Shortcuts
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.io.File
class MainViewModel : BaseViewModel()
@@ -52,10 +57,19 @@ class MainActivity : SplashActivity<ActivityMainMd2Binding>() {
private var isRootFragment = true
@SuppressLint("InlinedApi")
override fun showMainUI(savedInstanceState: Bundle?) {
setContentView()
showUnsupportedMessage()
askForHomeShortcut()
checkStubComponent()
// Ask permission to post notifications for background update check
if (Config.checkUpdate) {
withPermission(Manifest.permission.POST_NOTIFICATIONS) {
Config.checkUpdate = it
}
}
window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
@@ -88,7 +102,7 @@ class MainActivity : SplashActivity<ActivityMainMd2Binding>() {
// https://issuetracker.google.com/issues/124538620
}
binding.mainNavigation.menu.apply {
findItem(R.id.superuserFragment)?.isEnabled = Utils.showSuperUser()
findItem(R.id.superuserFragment)?.isEnabled = Info.showSuperUser
findItem(R.id.modulesFragment)?.isEnabled = Info.env.isActive && LocalModule.loaded()
}
@@ -217,4 +231,22 @@ class MainActivity : SplashActivity<ActivityMainMd2Binding>() {
}.show()
}
}
@SuppressLint("InlinedApi")
private fun checkStubComponent() {
if (intent.component?.className?.contains(HideAPK.PLACEHOLDER) == true) {
// The stub APK was not properly patched, re-apply our changes
withPermission(Manifest.permission.REQUEST_INSTALL_PACKAGES) { granted ->
if (granted) {
lifecycleScope.launch(Dispatchers.IO) {
val apk = File(applicationInfo.sourceDir)
HideAPK.upgrade(this@MainActivity, apk)?.let {
startActivity(it)
}
}
}
}
}
}
}

View File

@@ -6,6 +6,7 @@ import android.os.Bundle
import android.widget.Toast
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.databinding.ViewDataBinding
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import com.topjohnwu.magisk.BuildConfig.APPLICATION_ID
import com.topjohnwu.magisk.R
@@ -16,12 +17,11 @@ import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.JobService
import com.topjohnwu.magisk.core.di.ServiceLocator
import com.topjohnwu.magisk.core.isRunningAsStub
import com.topjohnwu.magisk.core.ktx.toast
import com.topjohnwu.magisk.core.tasks.HideAPK
import com.topjohnwu.magisk.core.utils.RootUtils
import com.topjohnwu.magisk.ui.theme.Theme
import com.topjohnwu.magisk.utils.Utils
import com.topjohnwu.magisk.view.MagiskDialog
import com.topjohnwu.magisk.view.Notifications
import com.topjohnwu.magisk.view.Shortcuts
import com.topjohnwu.superuser.Shell
import kotlinx.coroutines.launch
@@ -30,13 +30,15 @@ import kotlinx.coroutines.launch
abstract class SplashActivity<Binding : ViewDataBinding> : NavigationActivity<Binding>() {
companion object {
private var skipSplash = false
private var splashShown = false
}
private var needShowMainUI = false
override fun onCreate(savedInstanceState: Bundle?) {
setTheme(Theme.selected.themeRes)
if (isRunningAsStub && !skipSplash) {
if (isRunningAsStub && !splashShown) {
// Manually apply splash theme for stub
theme.applyStyle(R.style.StubSplashTheme, true)
}
@@ -45,11 +47,11 @@ abstract class SplashActivity<Binding : ViewDataBinding> : NavigationActivity<Bi
if (!isRunningAsStub) {
val splashScreen = installSplashScreen()
splashScreen.setKeepOnScreenCondition { !skipSplash }
splashScreen.setKeepOnScreenCondition { !splashShown }
}
if (skipSplash) {
showMainUI(savedInstanceState)
if (splashShown) {
doShowMainUI(savedInstanceState)
} else {
Shell.getShell(Shell.EXECUTOR) {
if (isRunningAsStub && !it.isRoot) {
@@ -61,6 +63,11 @@ abstract class SplashActivity<Binding : ViewDataBinding> : NavigationActivity<Bi
}
}
private fun doShowMainUI(savedInstanceState: Bundle?) {
needShowMainUI = false
showMainUI(savedInstanceState)
}
abstract fun showMainUI(savedInstanceState: Bundle?)
@SuppressLint("InlinedApi")
@@ -73,7 +80,7 @@ abstract class SplashActivity<Binding : ViewDataBinding> : NavigationActivity<Bi
onClick {
withPermission(REQUEST_INSTALL_PACKAGES) {
if (!it) {
Utils.toast(R.string.install_unknown_denied, Toast.LENGTH_SHORT)
toast(R.string.install_unknown_denied, Toast.LENGTH_SHORT)
showInvalidStateMessage()
} else {
lifecycleScope.launch {
@@ -88,6 +95,13 @@ abstract class SplashActivity<Binding : ViewDataBinding> : NavigationActivity<Bi
}
}
override fun onResume() {
super.onResume()
if (needShowMainUI) {
doShowMainUI(null)
}
}
private fun preLoad(savedState: Bundle?) {
val prevPkg = intent.getStringExtra(Const.Key.PREV_PKG)?.let {
// Make sure the calling package matches (prevent DoS)
@@ -107,7 +121,6 @@ abstract class SplashActivity<Binding : ViewDataBinding> : NavigationActivity<Bi
return
}
Notifications.setup(this)
JobService.schedule(this)
Shortcuts.setupDynamic(this)
@@ -118,12 +131,16 @@ abstract class SplashActivity<Binding : ViewDataBinding> : NavigationActivity<Bi
RootUtils.Connection.await()
runOnUiThread {
skipSplash = true
splashShown = true
if (isRunningAsStub) {
// Re-launch main activity without splash theme
relaunch()
} else {
showMainUI(savedState)
if (lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) {
doShowMainUI(savedState)
} else {
needShowMainUI = true
}
}
}
}
@@ -136,11 +153,10 @@ abstract class SplashActivity<Binding : ViewDataBinding> : NavigationActivity<Bi
Shell.cmd("(pm uninstall $APPLICATION_ID)& >/dev/null 2>&1").exec()
}
} else {
if (!Const.Version.atLeast_25_0() && Config.suManager.isNotEmpty())
if (Config.suManager.isNotEmpty())
Config.suManager = ""
pkg ?: return
Shell.cmd("(pm uninstall $pkg)& >/dev/null 2>&1").exec()
}
}
}

View File

@@ -7,10 +7,11 @@ import android.content.pm.PackageManager
import android.content.pm.PackageManager.*
import android.content.pm.ServiceInfo
import android.graphics.drawable.Drawable
import android.os.Build
import android.os.Build.VERSION.SDK_INT
import androidx.core.os.ProcessCompat
import com.topjohnwu.magisk.core.ktx.getLabel
import com.topjohnwu.magisk.core.utils.currentLocale
import com.topjohnwu.magisk.ktx.getLabel
import java.util.*
class CmdlineListItem(line: String) {
@@ -67,7 +68,8 @@ class AppProcessInfo(
val proc = info.processName ?: info.packageName
createProcess("${proc}_zygote")
} else {
val proc = if (SDK_INT >= 29) "${it.getProcName()}:${it.name}" else it.getProcName()
val proc = if (SDK_INT >= Build.VERSION_CODES.Q)
"${it.getProcName()}:${it.name}" else it.getProcName()
createProcess(proc, ISOLATED_MAGIC)
}
} else {

View File

@@ -1,33 +1,32 @@
package com.topjohnwu.magisk.ui.deny
import android.content.Context
import android.os.Bundle
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import androidx.appcompat.widget.SearchView
import androidx.core.view.MenuProvider
import androidx.recyclerview.widget.RecyclerView
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.arch.BaseFragment
import com.topjohnwu.magisk.arch.viewModel
import com.topjohnwu.magisk.core.ktx.hideKeyboard
import com.topjohnwu.magisk.databinding.FragmentDenyMd2Binding
import com.topjohnwu.magisk.ktx.hideKeyboard
import rikka.recyclerview.addEdgeSpacing
import rikka.recyclerview.addItemSpacing
import rikka.recyclerview.fixEdgeEffect
class DenyListFragment : BaseFragment<FragmentDenyMd2Binding>() {
class DenyListFragment : BaseFragment<FragmentDenyMd2Binding>(), MenuProvider {
override val layoutRes = R.layout.fragment_deny_md2
override val viewModel by viewModel<DenyListViewModel>()
private lateinit var searchView: SearchView
override fun onAttach(context: Context) {
super.onAttach(context)
override fun onStart() {
super.onStart()
activity?.setTitle(R.string.denylist)
setHasOptionsMenu(true)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@@ -56,7 +55,7 @@ class DenyListFragment : BaseFragment<FragmentDenyMd2Binding>() {
return super.onBackPressed()
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
override fun onCreateMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.menu_deny_md2, menu)
searchView = menu.findItem(R.id.action_search).actionView as SearchView
searchView.queryHint = searchView.context.getString(R.string.hide_filter_hint)
@@ -73,7 +72,7 @@ class DenyListFragment : BaseFragment<FragmentDenyMd2Binding>() {
})
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
override fun onMenuItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.action_show_system -> {
val check = !item.isChecked
@@ -91,7 +90,7 @@ class DenyListFragment : BaseFragment<FragmentDenyMd2Binding>() {
return super.onOptionsItemSelected(item)
}
override fun onPrepareOptionsMenu(menu: Menu) {
override fun onPrepareMenu(menu: Menu) {
val showSystem = menu.findItem(R.id.action_show_system)
val showOS = menu.findItem(R.id.action_show_OS)
showOS.isEnabled = showSystem.isChecked

View File

@@ -5,17 +5,17 @@ import android.view.ViewGroup
import androidx.databinding.Bindable
import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.databinding.ComparableRv
import com.topjohnwu.magisk.databinding.ObservableDiffRvItem
import com.topjohnwu.magisk.arch.startAnimations
import com.topjohnwu.magisk.databinding.DiffItem
import com.topjohnwu.magisk.databinding.ObservableRvItem
import com.topjohnwu.magisk.databinding.addOnPropertyChangedCallback
import com.topjohnwu.magisk.databinding.set
import com.topjohnwu.magisk.ktx.startAnimations
import com.topjohnwu.superuser.Shell
import kotlin.math.roundToInt
class DenyListRvItem(
val info: AppProcessInfo
) : ObservableDiffRvItem<DenyListRvItem>(), ComparableRv<DenyListRvItem> {
) : ObservableRvItem(), DiffItem<DenyListRvItem>, Comparable<DenyListRvItem> {
override val layoutRes get() = R.layout.item_hide_md2
@@ -44,9 +44,18 @@ class DenyListRvItem(
processes
.filterNot { it.isEnabled }
.filter { isExpanded || it.defaultSelection }
.forEach { it.toggle() }
} else {
processes.filter { it.isEnabled }
}.forEach { it.toggle() }
Shell.cmd("magisk --denylist rm ${info.packageName}").submit()
processes.filter { it.isEnabled }.forEach {
if (it.process.isIsolated) {
it.toggle()
} else {
it.isEnabled = !it.isEnabled
notifyPropertyChanged(BR.enabled)
}
}
}
}
init {
@@ -91,7 +100,7 @@ class DenyListRvItem(
class ProcessRvItem(
val process: ProcessInfo
) : ObservableDiffRvItem<ProcessRvItem>() {
) : ObservableRvItem(), DiffItem<ProcessRvItem> {
override val layoutRes get() = R.layout.item_hide_process_md2
@@ -113,10 +122,9 @@ class ProcessRvItem(
val defaultSelection get() =
process.isIsolated || process.isAppZygote || process.name == process.packageName
override fun contentSameAs(other: ProcessRvItem) =
process.isEnabled == other.process.isEnabled
override fun itemSameAs(other: ProcessRvItem) =
process.name == other.process.name && process.packageName == other.process.packageName
override fun contentSameAs(other: ProcessRvItem) =
process.isEnabled == other.process.isEnabled
}

View File

@@ -3,13 +3,14 @@ package com.topjohnwu.magisk.ui.deny
import android.annotation.SuppressLint
import android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES
import androidx.databinding.Bindable
import androidx.lifecycle.viewModelScope
import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.arch.AsyncLoadViewModel
import com.topjohnwu.magisk.core.di.AppContext
import com.topjohnwu.magisk.core.ktx.concurrentMap
import com.topjohnwu.magisk.databinding.bindExtra
import com.topjohnwu.magisk.databinding.filterableListOf
import com.topjohnwu.magisk.databinding.filterList
import com.topjohnwu.magisk.databinding.set
import com.topjohnwu.magisk.ktx.concurrentMap
import com.topjohnwu.superuser.Shell
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.asFlow
@@ -22,22 +23,22 @@ class DenyListViewModel : AsyncLoadViewModel() {
var isShowSystem = false
set(value) {
field = value
query()
doQuery(query)
}
var isShowOS = false
set(value) {
field = value
query()
doQuery(query)
}
var query = ""
set(value) {
field = value
query()
doQuery(value)
}
val items = filterableListOf<DenyListRvItem>()
val items = filterList<DenyListRvItem>(viewModelScope)
val extraBindings = bindExtra {
it.put(BR.viewModel, this)
}
@@ -49,7 +50,7 @@ class DenyListViewModel : AsyncLoadViewModel() {
@SuppressLint("InlinedApi")
override suspend fun doLoadWork() {
loading = true
val (apps, diff) = withContext(Dispatchers.Default) {
val apps = withContext(Dispatchers.Default) {
val pm = AppContext.packageManager
val denyList = Shell.cmd("magisk --denylist ls").exec().out
.map { CmdlineListItem(it) }
@@ -62,22 +63,22 @@ class DenyListViewModel : AsyncLoadViewModel() {
.toCollection(ArrayList(size))
}
apps.sort()
apps to items.calculateDiff(apps)
apps
}
items.update(apps, diff)
query()
items.set(apps)
doQuery(query)
}
fun query() {
private fun doQuery(s: String) {
items.filter {
fun filterSystem() = isShowSystem || !it.info.isSystemApp()
fun filterOS() = (isShowSystem && isShowOS) || it.info.isApp()
fun filterQuery(): Boolean {
fun inName() = it.info.label.contains(query, true)
fun inPackage() = it.info.packageName.contains(query, true)
fun inProcesses() = it.processes.any { p -> p.process.name.contains(query, true) }
fun inName() = it.info.label.contains(s, true)
fun inPackage() = it.info.packageName.contains(s, true)
fun inProcesses() = it.processes.any { p -> p.process.name.contains(s, true) }
return inName() || inPackage() || inProcesses()
}

View File

@@ -6,14 +6,15 @@ import androidx.core.view.updateLayoutParams
import androidx.databinding.ViewDataBinding
import androidx.recyclerview.widget.RecyclerView
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.databinding.DiffRvItem
import com.topjohnwu.magisk.databinding.RvContainer
import com.topjohnwu.magisk.databinding.ViewAwareRvItem
import com.topjohnwu.magisk.databinding.DiffItem
import com.topjohnwu.magisk.databinding.ItemWrapper
import com.topjohnwu.magisk.databinding.RvItem
import com.topjohnwu.magisk.databinding.ViewAwareItem
import kotlin.math.max
class ConsoleItem(
override val item: String
) : DiffRvItem<ConsoleItem>(), ViewAwareRvItem, RvContainer<String> {
) : RvItem(), ViewAwareItem, DiffItem<ConsoleItem>, ItemWrapper<String> {
override val layoutRes = R.layout.item_console_md2
private var parentWidth = -1

View File

@@ -6,6 +6,7 @@ import android.content.pm.ActivityInfo
import android.net.Uri
import android.os.Bundle
import android.view.*
import androidx.core.view.MenuProvider
import androidx.core.view.isVisible
import androidx.navigation.NavDeepLinkBuilder
import com.topjohnwu.magisk.MainDirections
@@ -17,7 +18,7 @@ import com.topjohnwu.magisk.core.cmp
import com.topjohnwu.magisk.databinding.FragmentFlashMd2Binding
import com.topjohnwu.magisk.ui.MainActivity
class FlashFragment : BaseFragment<FragmentFlashMd2Binding>() {
class FlashFragment : BaseFragment<FragmentFlashMd2Binding>(), MenuProvider {
override val layoutRes = R.layout.fragment_flash_md2
override val viewModel by viewModel<FlashViewModel>()
@@ -34,7 +35,6 @@ class FlashFragment : BaseFragment<FragmentFlashMd2Binding>() {
override fun onStart() {
super.onStart()
setHasOptionsMenu(true)
activity?.setTitle(R.string.flash_screen_title)
viewModel.state.observe(this) {
@@ -54,11 +54,11 @@ class FlashFragment : BaseFragment<FragmentFlashMd2Binding>() {
}
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
override fun onCreateMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.menu_flash, menu)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
override fun onMenuItemSelected(item: MenuItem): Boolean {
return viewModel.onMenuItemClicked(item)
}

View File

@@ -2,26 +2,26 @@ package com.topjohnwu.magisk.ui.flash
import android.view.MenuItem
import androidx.databinding.Bindable
import androidx.databinding.ObservableArrayList
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Transformations
import androidx.lifecycle.map
import androidx.lifecycle.viewModelScope
import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.arch.BaseViewModel
import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.ktx.reboot
import com.topjohnwu.magisk.core.ktx.synchronized
import com.topjohnwu.magisk.core.ktx.timeFormatStandard
import com.topjohnwu.magisk.core.ktx.toTime
import com.topjohnwu.magisk.core.tasks.FlashZip
import com.topjohnwu.magisk.core.tasks.MagiskInstaller
import com.topjohnwu.magisk.core.utils.MediaStoreUtils
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream
import com.topjohnwu.magisk.databinding.diffListOf
import com.topjohnwu.magisk.databinding.set
import com.topjohnwu.magisk.events.SnackbarEvent
import com.topjohnwu.magisk.ktx.reboot
import com.topjohnwu.magisk.ktx.synchronized
import com.topjohnwu.magisk.ktx.timeFormatStandard
import com.topjohnwu.magisk.ktx.toTime
import com.topjohnwu.superuser.CallbackList
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@@ -34,13 +34,13 @@ class FlashViewModel : BaseViewModel() {
private val _state = MutableLiveData(State.FLASHING)
val state: LiveData<State> get() = _state
val flashing = Transformations.map(state) { it == State.FLASHING }
val flashing = state.map { it == State.FLASHING }
@get:Bindable
var showReboot = Info.isRooted
set(value) = set(value, field, { field = it }, BR.showReboot)
val items = diffListOf<ConsoleItem>()
val items = ObservableArrayList<ConsoleItem>()
lateinit var args: FlashFragmentArgs
private val logItems = mutableListOf<String>().synchronized()

View File

@@ -24,6 +24,10 @@ private interface RikkaImpl : Dev {
override val name get() = "RikkaW"
}
private interface CanyieImpl : Dev {
override val name get() = "canyie"
}
sealed class DeveloperItem : Dev {
abstract val items: List<IconLink>
@@ -61,6 +65,14 @@ sealed class DeveloperItem : Dev {
object : IconLink.Github.User(), RikkaImpl {}
)
}
object Canyie : DeveloperItem(), CanyieImpl {
override val items =
listOf<IconLink>(
object : IconLink.Twitter() { override val name = "canyie2977" },
object : IconLink.Github.User(), CanyieImpl {}
)
}
}
sealed class IconLink : RvItem() {

View File

@@ -1,26 +1,30 @@
package com.topjohnwu.magisk.ui.home
import android.os.Bundle
import android.view.*
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.core.view.MenuProvider
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.arch.BaseFragment
import com.topjohnwu.magisk.arch.viewModel
import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.download.DownloadService
import com.topjohnwu.magisk.databinding.FragmentHomeMd2Binding
import com.topjohnwu.magisk.events.RebootEvent
class HomeFragment : BaseFragment<FragmentHomeMd2Binding>() {
class HomeFragment : BaseFragment<FragmentHomeMd2Binding>(), MenuProvider {
override val layoutRes = R.layout.fragment_home_md2
override val viewModel by viewModel<HomeViewModel>()
override fun onStart() {
super.onStart()
activity?.title = resources.getString(R.string.section_home)
setHasOptionsMenu(true)
activity?.setTitle(R.string.section_home)
DownloadService.observeProgress(this, viewModel::onProgressUpdate)
}
@@ -54,17 +58,17 @@ class HomeFragment : BaseFragment<FragmentHomeMd2Binding>() {
return binding.root
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
override fun onCreateMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.menu_home_md2, menu)
if (!Info.isRooted)
menu.removeItem(R.id.action_reboot)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
override fun onMenuItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.action_settings ->
HomeFragmentDirections.actionHomeFragmentToSettingsFragment().navigate()
R.id.action_reboot -> activity?.let { RebootEvent.inflateMenu(it).show() }
R.id.action_reboot -> activity?.let { RebootMenu.inflate(it).show() }
else -> return super.onOptionsItemSelected(item)
}
return true

View File

@@ -1,25 +1,32 @@
package com.topjohnwu.magisk.ui.home
import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent
import android.widget.Toast
import androidx.core.net.toUri
import androidx.databinding.Bindable
import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.BuildConfig
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.arch.*
import com.topjohnwu.magisk.arch.ActivityExecutor
import com.topjohnwu.magisk.arch.AsyncLoadViewModel
import com.topjohnwu.magisk.arch.ContextExecutor
import com.topjohnwu.magisk.arch.UIActivity
import com.topjohnwu.magisk.arch.ViewEvent
import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.download.Subject
import com.topjohnwu.magisk.core.download.Subject.App
import com.topjohnwu.magisk.core.ktx.await
import com.topjohnwu.magisk.core.ktx.toast
import com.topjohnwu.magisk.core.repository.NetworkService
import com.topjohnwu.magisk.databinding.bindExtra
import com.topjohnwu.magisk.databinding.set
import com.topjohnwu.magisk.dialog.EnvFixDialog
import com.topjohnwu.magisk.dialog.ManagerInstallDialog
import com.topjohnwu.magisk.dialog.UninstallDialog
import com.topjohnwu.magisk.events.SnackbarEvent
import com.topjohnwu.magisk.events.dialog.EnvFixDialog
import com.topjohnwu.magisk.events.dialog.ManagerInstallDialog
import com.topjohnwu.magisk.events.dialog.UninstallDialog
import com.topjohnwu.magisk.ktx.await
import com.topjohnwu.magisk.utils.Utils
import com.topjohnwu.magisk.utils.asText
import com.topjohnwu.superuser.Shell
import kotlin.math.roundToInt
@@ -43,6 +50,7 @@ class HomeViewModel(
val magiskState
get() = when {
Info.isRooted && Info.env.isUnsupported -> State.OUTDATED
!Info.env.isActive -> State.INVALID
Info.env.versionCode < BuildConfig.VERSION_CODE -> State.OUTDATED
else -> State.UP_TO_DATE
@@ -66,7 +74,6 @@ class HomeViewModel(
val managerInstalledVersion
get() = "${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})" +
Info.stub?.let { " (${it.version})" }.orEmpty() +
if (BuildConfig.DEBUG) " (D)" else ""
@get:Bindable
@@ -91,7 +98,7 @@ class HomeViewModel(
val isDebug = Config.updateChannel == Config.Value.DEBUG_CHANNEL
managerRemoteVersion =
("${magisk.version} (${magisk.versionCode}) (${stub.versionCode})" +
("${magisk.version} (${magisk.versionCode})" +
if (isDebug) " (D)" else "").asText()
} ?: run {
appState = State.INVALID
@@ -108,17 +115,25 @@ class HomeViewModel(
}
fun onLinkPressed(link: String) = object : ViewEvent(), ContextExecutor {
override fun invoke(context: Context) = Utils.openLink(context, link.toUri())
override fun invoke(context: Context) {
val intent = Intent(Intent.ACTION_VIEW, link.toUri())
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
try {
context.startActivity(intent)
} catch (e: ActivityNotFoundException) {
context.toast(R.string.open_link_failed_toast, Toast.LENGTH_SHORT)
}
}
}.publish()
fun onDeletePressed() = UninstallDialog().publish()
fun onDeletePressed() = UninstallDialog().show()
fun onManagerPressed() = when (appState) {
State.LOADING -> SnackbarEvent(R.string.loading).publish()
State.INVALID -> SnackbarEvent(R.string.no_connection).publish()
else -> withExternalRW {
withInstallPermission {
ManagerInstallDialog().publish()
ManagerInstallDialog().show()
}
}
}
@@ -135,8 +150,9 @@ class HomeViewModel(
private suspend fun ensureEnv() {
if (magiskState == State.INVALID || checkedEnv) return
val cmd = "env_check ${Info.env.versionString} ${Info.env.versionCode}"
if (!Shell.cmd(cmd).await().isSuccess) {
EnvFixDialog(this).publish()
val code = Shell.cmd(cmd).await().code
if (code != 0) {
EnvFixDialog(this, code).show()
}
checkedEnv = true
}

Some files were not shown because too many files have changed in this diff Show More