Compare commits

...

264 Commits

Author SHA1 Message Date
topjohnwu
97c1e181c5 Remove unused file 2021-05-11 21:47:46 -07:00
topjohnwu
ea80cddd57 Switch to official snet.jar link 2021-05-11 21:42:58 -07:00
topjohnwu
09a294c219 Fix release builds 2021-05-11 18:40:45 -07:00
bela333
408399eae0 Update install.md 2021-05-11 11:46:23 -07:00
Davy Defaud
391852a102 Various fixes in the French translation 2021-05-11 11:45:31 -07:00
topjohnwu
5b37de8fe5 Build our own zlib 2021-05-10 18:46:03 -07:00
topjohnwu
7df23ceb74 Prevent undefined behavior in magiskboot 2021-05-10 18:38:30 -07:00
topjohnwu
6099f3b015 Always resolve to canonical path 2021-05-10 01:14:53 -07:00
topjohnwu
a5cc31783c Release new canary build 2021-05-10 00:02:07 -07:00
topjohnwu
6b34ec3ab9 Fix #4194 2021-05-09 22:56:54 -07:00
topjohnwu
5c333dec33 Minor changes 2021-05-09 20:45:53 -07:00
topjohnwu
775d095b3c Update busybox
Fix #4225
2021-05-08 16:45:31 -07:00
GithubUser699
7679b5d516 Removed two "the"
At least I couldn't find a Magisk app named "The magisk app", so I removed the two "the".
2021-05-06 19:03:34 -07:00
topjohnwu
7702094053 Update dependencies 2021-05-06 11:37:21 -07:00
Wang Han
3798d50457 Kill processes with SIGKILL rather than SIGTERM 2021-05-04 22:14:46 -07:00
Shaka Huang
95e1e57407 Fix #4140 2021-05-04 22:12:18 -07:00
vvb2060
93ba4cca68 Fix copy sepolicy rules when install module 2021-05-04 22:11:10 -07:00
jenslody
fe4981da21 Fix strings fallback in find_manager_apk
There is no preceding character (at least on some devices).
This regex should work in any cases, with and without preceding character.
2021-04-23 18:10:02 -07:00
jenslody
e4f94c4c52 Adapt find_magisk_apk for A11
Add a fallback for Android 11's new app location.
2021-04-23 18:10:02 -07:00
vvb2060
708fe514f8 Always use mirror path 2021-04-23 16:56:23 -07:00
vvb2060
11c882380f Add warning for custom recovery users 2021-04-23 16:56:23 -07:00
vvb2060
fb93af665d Remove obsolete SDK_INT check 2021-04-23 16:56:23 -07:00
topjohnwu
0db405f2cc Release new canary build 2021-04-20 03:45:40 -07:00
topjohnwu
fb8000b58b Handle invalid SafetyNet results
Fix #4253
2021-04-20 03:39:47 -07:00
topjohnwu
1b9d8e068a Remove/move unused files 2021-04-18 05:04:14 -07:00
topjohnwu
038f73a5f7 Remove Koin
Non static DI is bad
2021-04-18 04:46:11 -07:00
topjohnwu
649b49ff45 Don't hold resources in Settings objects 2021-04-18 04:14:43 -07:00
topjohnwu
1418bc454d Don't hold resources in ViewModels 2021-04-18 02:12:53 -07:00
vvb2060
29cc372bfa Fix proguard rules 2021-04-17 23:44:19 -07:00
vvb2060
69b00d3782 Update dependencies
Jcenter will sunset
2021-04-17 23:44:19 -07:00
topjohnwu
a328e2bf3c Hide annoying stack traces when hidden 2021-04-17 22:35:36 -07:00
topjohnwu
4c1ea0e421 Update stub implementation
Prevent some potential issues
2021-04-17 22:14:54 -07:00
topjohnwu
7e01f9c95e Minor changes 2021-04-17 19:57:47 -07:00
topjohnwu
8b28baabd7 Release new canary build 2021-04-15 23:58:38 -07:00
Clement
f49966d86e Update french translations 2021-04-15 23:09:45 -07:00
vvb2060
f4ac7c8e7c Ignore validating class name of isolated process name
Fix #4176

Co-authored-by: topjohnwu <topjohnwu@gmail.com>
2021-04-15 23:08:51 -07:00
Arbri çoçka
2b65e1ffc2 Update strings-sq 2021-04-15 05:02:12 -07:00
tzagim
c81a3fa286 Update HE translation 2021-04-15 05:01:39 -07:00
Wang Han
44f005077d Don't copy sepolicy.rule to /persist on boot
* This seems to be a logic that has been abandoned for a
   long time. Now we automatically choose which partition
   to store sepolicy.rule. Furthermore, touching /persist is
   what we should avoid doing whenever possible.
2021-04-15 05:01:03 -07:00
LoveSy
013b6e68ec Fix perfect forwarding 2021-04-15 04:58:30 -07:00
LoveSy
95c964673d Initialized _root properly
Fix #4204

`_root` is uninitialized for non-root nodes. And it will cause `module_node::mount` fail because it uses `root()`. Once the bug is triggered, signal 11 is received but Magisk catch all signals and therefore stuck forever.
2021-04-15 04:58:30 -07:00
topjohnwu
94ec11db58 Update snet.jar extension
The existing API key was revoked for some reason.
Release an updated extension jar with a new API key.

In addition, add some offline signature verification and change how
results are parsed to workaround some dumbass Xposed module "faking"
success results, since many users really don't know better.
2021-04-15 04:47:57 -07:00
topjohnwu
c4e8dda37c Release new canary build 2021-04-09 21:47:58 -07:00
Wang Han
e136fb3a4f Remove outdated sepolicies
* Support deodexed ROM: This should not be done and dexpreopt is mandatory since P
   Xposed: Xposed handles them just fine, at least in the latest version 89.3
   suMiscL6: For whatever audio mods, a leftover of phh time
   Liveboot and suBackL6: Was for CF.lumen and LiveBoot, not needed now

 * Also cleanup binder sepolicies since we allow all binder transactions.
2021-04-09 21:34:51 -07:00
topjohnwu
01b985eded Remove more pre SDK 21 stuffs 2021-04-09 21:29:42 -07:00
topjohnwu
1f0a35f073 Set minSdkVersion to 21 2021-04-09 20:01:32 -07:00
topjohnwu
2b9b019093 It's 2021 already 2021-04-09 03:51:54 -07:00
vvb2060
10186a9e3d Refresh flag 2021-04-09 03:30:55 -07:00
topjohnwu
89d8fea7d2 Release new canary build 2021-04-09 03:28:13 -07:00
topjohnwu
f623b98858 Update README 2021-04-09 03:23:52 -07:00
topjohnwu
632cee1613 Release Magisk v22.1 2021-04-09 03:05:57 -07:00
topjohnwu
c0f2164bc5 Magisk v22.1 release notes 2021-04-09 02:50:41 -07:00
Wang Han
f6e4a27fdd Don't export $API when initializing shell
* This becomes meanless after 9c0e189.
2021-04-09 01:47:52 -07:00
topjohnwu
257ceb99f7 SDK < 21 is EOL 2021-04-09 01:40:08 -07:00
topjohnwu
706d53065b Rename TransitiveText 2021-04-09 01:32:37 -07:00
topjohnwu
0f95a7babe Do not hold resources in SuperuserViewModel 2021-04-09 01:00:26 -07:00
topjohnwu
7cb2806878 Release new canary build 2021-04-06 04:13:41 -07:00
topjohnwu
9c0e18975c Fallback to getprop when reading system props
Close #4153
2021-04-06 03:56:49 -07:00
Shaka Huang
3da318b48e Fix random return value of faccessat() in x86
faccessat() should return 0 when success, but it returns random number with errno == 0 in x86 platform.

It’s a side effect of commit bf80b08b5f when magisk binaries ‘corretly’ linked with library of API16 .. lol

Co-authored-by: John Wu <topjohnwu@gmail.com>
2021-04-04 03:04:09 -07:00
Shaka Huang
dfe1f2c108 Call freecon() when fgetfilecon() succeeds 2021-04-04 01:58:59 -07:00
Thomas Bertels
f42a87b51a Fix spelling in French translation 2021-03-29 09:15:23 -07:00
ahmouse15
ab25857176 Update docs to use the Magisk Manager's revised name 2021-03-29 09:14:21 -07:00
topjohnwu
7da36079c1 Always delete existing ro props at setprop
Close #4113
2021-03-29 04:16:18 -07:00
topjohnwu
2bef967af1 Make systemproperties more match AOSP 2021-03-29 03:46:07 -07:00
topjohnwu
7e4194418a Update libcxx 2021-03-28 04:55:56 -07:00
topjohnwu
aa02057895 Do not use -f flag for readlink
Close #4104, fix #4098
2021-03-28 04:47:13 -07:00
topjohnwu
fb8dc07599 Release new canary build 2021-03-25 02:09:51 -07:00
topjohnwu
66e30a7723 Build libc++ ourselves 2021-03-25 01:00:10 -07:00
vvb2060
0298ab99c4 Update AGP 2021-03-24 04:43:45 -07:00
vvb2060
d11358671e Fix isolated process display 2021-03-24 04:43:45 -07:00
vvb2060
8b5cb4c7b0 Fix #3735 2021-03-24 04:43:45 -07:00
vvb2060
aad52ae743 Fix UID removed action 2021-03-24 04:43:45 -07:00
vvb2060
8ddab84745 Don't auto hide microG
close #3559
2021-03-24 04:43:45 -07:00
vvb2060
6865652125 Fix process name in MagiskHide
close #3997
2021-03-24 04:43:45 -07:00
topjohnwu
ed4d0867e8 Make sure navigation happens on main thread
Fix #4044
2021-03-24 03:23:11 -07:00
Kazuki H
1c71e02454 Update Japanese translations 2021-03-24 03:10:21 -07:00
Matthew Mirvish
f332e87cab Ensure the installer knows the API version when running from addon.d 2021-03-24 03:08:59 -07:00
osm0sis
023dbc6cb5 scripts: fix empty module cleanup
- should be sufficient for all basic modules, see https://github.com/topjohnwu/Magisk/issues/3119#issuecomment-704000783 for ideas for fixing it further on the daemon module-processing side

Fixes #3119
2021-03-24 03:06:57 -07:00
osm0sis
4dd3f55407 App: add versionCode to magisk_patched.img filenames 2021-03-24 03:06:57 -07:00
osm0sis
7b9a71c9af scripts: improve boot_patch 64bit detection
- use existing api_level_arch_detect function

Fixes #3961
2021-03-24 03:06:57 -07:00
osm0sis
901d22cdfa scripts: add boot_patch unpack error catching
- failure to unpack wasn't being caught so boot_patch would continue to build a new cpio out of nothing and attempt to repack it (identified in #4049)
2021-03-24 03:06:57 -07:00
osm0sis
93e1266ee7 scripts: fix find_boot_image using wrong partition list on non-slot
- revert logic changes introduced by ec8fffe61c which break find_boot_image on NAND devices and any others using non-standard naming supported by the A-only device boot partition name list
- despite being accepted equivalents in modern shells -n does not work on Android in some shell/env scenarios where ! -z always does
2021-03-24 03:06:57 -07:00
osm0sis
0a4e7eea41 scripts: clean up remaining Manager references 2021-03-24 03:06:57 -07:00
Shaka Huang
e3801d6965 Fix overflow
`totalsize` might be a big (invalid) number so instead of checking the end address we check the size of the image.

Fix #4049
2021-03-24 03:02:46 -07:00
topjohnwu
336f1687c1 Be more careful with signals
Fix #4040
2021-03-18 03:28:02 -07:00
topjohnwu
d4e2f2df6e Release new canary build 2021-03-16 05:47:29 -07:00
topjohnwu
f152b4c26e Make LiveData nullable 2021-03-16 05:34:54 -07:00
topjohnwu
bd935b0553 Cleanup fragment navigations 2021-03-16 04:58:02 -07:00
topjohnwu
a9b3b7a359 Update dependencies 2021-03-16 03:44:25 -07:00
vvb2060
7a007b342a Correct comment
For file-based encryption, /data/adb is always required to encrypt
https://android.googlesource.com/platform/system/extras/+/refs/tags/android-7.0.0_r36/ext4_utils/ext4_crypt_init_extensions.cpp
68258e8444%5E%21/
2021-03-13 21:10:02 -08:00
vvb2060
0783f3d5b6 Fix mount rules dir
close #4006
2021-03-13 21:10:02 -08:00
Rikka
afe3c2bc1b Fix "rm_rf" in build.py on Windows
prebuilt/windows-x86_64/bin/libpython2.7.dll
prebuilt/windows-x86_64/lib/python2.7/config/libpython2.7.dll.a

These two files in NDK has read-only attribute on Windows, remove these files with Python will get "WindowsError: [Error 5] Access is denied". It will finally make "build.py ndk" unable to remove the "magisk" folder.

This commit add a onerror callback for "shutil.rmtree" which clear the "read-only" attribute and retry.
2021-03-13 17:51:39 -08:00
topjohnwu
82f8948fd4 Separate setting log functions and starting log daemon 2021-03-13 17:50:48 -08:00
Shaka Huang
b9cdc755d1 Returned fds[0] in socketpair() might be STDOUT
* There will be garbage output when executing `su` (#4016)
* Failed to check root status and showing N/A in status (#4005)

Signed-off-by: Shaka Huang <shakalaca@gmail.com>
2021-03-13 17:50:48 -08:00
topjohnwu
a6f81c66e5 Bypass stdio 2021-03-13 16:17:28 -08:00
topjohnwu
1ff45ac5f5 Proper pattern matching
Fix #3998
2021-03-09 04:08:34 -08:00
Alexandru Scurtu
48bde7375f uninstaller: consistency improvements
since there's no more "Magisk Manager"
2021-03-09 03:05:47 -08:00
topjohnwu
0601fa3b3d Release new canary build 2021-03-09 02:59:07 -08:00
vvb2060
d0d3c8dbfd Disable blank issues 2021-03-09 02:51:20 -08:00
vvb2060
8057de1973 Auto close issues 2021-03-09 02:51:20 -08:00
topjohnwu
43c1105d62 Use dedicated thread for writing logfile 2021-03-09 02:40:12 -08:00
topjohnwu
6adf516b30 Release new canary build 2021-03-07 04:39:47 -08:00
topjohnwu
bf80b08b5f Fix build script 2021-03-07 04:34:06 -08:00
topjohnwu
3e0b1df46d Update README 2021-03-07 04:12:32 -08:00
topjohnwu
84811c80b6 Release new canary build 2021-03-07 02:51:10 -08:00
LLZN
45e0df9c57 Update strings.xml 2021-03-07 01:56:02 -08:00
vvb2060
bc51ce7c7b Fix reboot menu 2021-03-07 01:55:19 -08:00
vvb2060
b693d13b93 Proper implementation of cgroup migration
https://www.kernel.org/doc/Documentation/admin-guide/cgroup-v1/cgroups.rst
https://www.kernel.org/doc/Documentation/admin-guide/cgroup-v2.rst
2021-03-07 01:55:19 -08:00
topjohnwu
39982d57ef Fix logging implementation
- Block signals in logging routine (fix #3976)
- Prevent possible deadlock after fork (stdio locks internally)
  by creating a new FILE pointer per logging call (thread/stack local)
2021-03-06 13:55:30 -08:00
topjohnwu
15e27e54fb Migrate to new endpoints 2021-03-05 05:09:25 -08:00
topjohnwu
851404205b Update NDK to r21e 2021-03-02 23:18:44 -08:00
topjohnwu
117ae71025 Use custom class instead of std::map 2021-03-02 23:16:10 -08:00
topjohnwu
027ec70262 Patch AVB structures
Disable vbmeta verification in flags
2021-03-02 20:37:37 -08:00
topjohnwu
55fdee4d65 Use memmem for searching byte patterns 2021-02-28 14:37:12 -08:00
topjohnwu
0d42f937dd Refactor magiskboot 2021-02-28 14:37:12 -08:00
vvb2060
ac8372dd26 Add cgroup2 path
https://android-review.googlesource.com/c/platform/system/core/+/1585101
2021-02-26 21:36:58 -08:00
vvb2060
5e56a6bbee Fix isolated process name before Android 10 2021-02-26 21:36:58 -08:00
etmatrix
3c6c409df0 Fix #3916 2021-02-25 21:25:21 -08:00
vvb2060
d05408c89f Delete outdated policies when refresh 2021-02-25 20:08:42 -08:00
vvb2060
ee0ec3fbfa Use UID_REMOVED action for multi-user and shared user id compatibility 2021-02-25 20:08:42 -08:00
vvb2060
122a73e086 Always show hidden apps 2021-02-25 20:08:42 -08:00
omerakgoz34
29a9b18c4c Update Turkish translation 2021-02-25 19:56:05 -08:00
孟武.尼德霍格.龍
1561272109 更新繁體中文
更新並改善繁體中文的翻譯
2021-02-25 19:55:25 -08:00
Ilya Kushnir
3e61ab0d25 Update RU strings 2021-02-25 19:54:58 -08:00
Francesco Saltori
a49dc6ccb7 Update Italian translation 2021-02-25 19:54:21 -08:00
topjohnwu
60f3d62f00 Proper synchronization 2021-02-24 02:50:55 -08:00
topjohnwu
e613855a4f Do not check PXA header signatures
Fix #3876
2021-02-24 02:27:42 -08:00
sn-o-w
22662d7e03 Update Romanian 2021-02-24 02:08:46 -08:00
Arbri çoçka
6e7e5be1a2 Update values-sq 2021-02-24 02:06:42 -08:00
vvb2060
8b2ab778c9 Fix show canary channel on stable build 2021-02-24 02:06:20 -08:00
vvb2060
35f3766ecf Update zh-rCN translation 2021-02-24 02:05:33 -08:00
Rom
995304dabb Update French translation 2021-02-24 02:05:16 -08:00
topjohnwu
803982a271 Prevent multiple installation sessions running in parallel 2021-02-24 01:45:10 -08:00
topjohnwu
9164bf22c2 Update terminology 2021-02-23 23:56:58 -08:00
topjohnwu
911a576893 Publish new canary build 2021-02-23 04:36:49 -08:00
topjohnwu
79ee85c0f9 Update README 2021-02-23 04:22:32 -08:00
topjohnwu
483dbcdc40 Release v22.0 2021-02-23 04:09:26 -08:00
topjohnwu
a1096b5bf0 Do not run pm install on main thread 2021-02-23 04:09:13 -08:00
Chris Renshaw
5ac0e64edb Update guides.md for system_ext 2021-02-23 03:27:36 -08:00
Lishoo
60b2624607 Update polish translations
Add missing strings
2021-02-23 03:26:47 -08:00
topjohnwu
d2e2847b03 Fix stub 2021-02-23 03:24:51 -08:00
topjohnwu
b9669f54f7 Update docs 2021-02-23 03:06:00 -08:00
topjohnwu
8c7bd77d33 Do not wrap twice 2021-02-23 01:49:15 -08:00
Shaka Huang
ba1ce16b8b Fix error in pure 64-bit environment
In Android S preview, there’s no 32-bit libraries in x86_64 system image for emulator.

Signed-off-by: Shaka Huang <shakalaca@gmail.com>
2021-02-22 03:28:54 -08:00
topjohnwu
68090943f4 Several changes
- Change error message strings
- Move non-root stub error to SplashActivity
- Skip shell init in non-root stub
2021-02-22 03:28:19 -08:00
vvb2060
a4fb1297b0 Fix crash in pure 64-bit devices 2021-02-22 03:08:51 -08:00
vvb2060
860a05abf2 Simplify UpdateChannel 2021-02-22 03:08:51 -08:00
vvb2060
8bb2f356c0 Allow offline restore app 2021-02-22 03:08:51 -08:00
vvb2060
4950020635 Prevent dot in the first position again 2021-02-22 03:08:51 -08:00
vvb2060
0a6140c6eb Try install with root first 2021-02-22 03:08:51 -08:00
vvb2060
bba2ac8817 Add unsupport env check 2021-02-22 03:08:51 -08:00
topjohnwu
331b1f542f Use standard Android APIs for install and launch 2021-02-20 20:12:35 -08:00
topjohnwu
ccb55205e6 Fix pre 21 support 2021-02-20 03:38:39 -08:00
topjohnwu
9cc91b30b3 Fix #3871 2021-02-20 02:49:43 -08:00
grmasa
e836caf31e Update Greek translation 2021-02-20 01:51:39 -08:00
Lishoo
beaa1e5be2 Add missing strings and small updates. 2021-02-20 01:51:02 -08:00
Lishoo
ea545bae26 Update polish translations 2021-02-20 01:50:44 -08:00
topjohnwu
1c9ec2df45 Publish new canary build 2021-02-14 13:43:24 -08:00
vvb2060
b76c80e2ce Fix apex path 2021-02-14 13:37:38 -08:00
topjohnwu
236990f4a3 Fix stub app crashing 2021-02-14 13:37:13 -08:00
topjohnwu
1ed32df20d Publish new canary build 2021-02-13 17:26:53 -08:00
topjohnwu
8476eb9f4b Avoid patching vendor_boot.img 2021-02-13 17:15:04 -08:00
JoanVC100
735af7843b Add new ca-strings 2021-02-13 17:09:46 -08:00
MC Naveen
ded73e958b Added Tamil Translation 2021-02-13 17:09:28 -08:00
Ooggle
6dcb84d4f4 French translation of newest commit 2021-02-13 17:08:53 -08:00
topjohnwu
501bc9f438 Restore init from backup rather than symlink
Because of course Samsung don't follow AOSP norms.
I mean, why would they?
2021-02-13 16:43:06 -08:00
topjohnwu
f88e812b63 Move behavior to XML 2021-02-13 15:26:32 -08:00
Tornike Khintibidze
be6386c410 Updated Georgian translation 2021-02-12 03:59:35 -08:00
Didgeridoohan
2af4fd17c4 Minor fixes and changes to Swedish transaltions 2021-02-12 03:59:07 -08:00
Mikael Bjurström
f870418bd0 Update Swedish translation 2021-02-12 00:07:40 -08:00
vvb2060
00659e4795 Hide OTA option on virtual A/B devices 2021-02-12 00:07:15 -08:00
Jose Manuel Estrada-Nora Muñoz
cdda10207e Spanish strings 2021-02-11 23:32:24 -08:00
Ilya Kushnir
701700279f Update RU strings 2021-02-11 23:32:04 -08:00
alex26052005
a9d804724a Update strings.xml
Updated German language
2021-02-11 23:31:04 -08:00
DanGLES3
e033a9ab47 Update Portugues Brazilian translation 2021-02-11 23:30:35 -08:00
kubalav
059e5fb8aa Update Slovak translation 2021-02-11 23:28:49 -08:00
vvb2060
a78f255928 Update zh-rCN translation 2021-02-11 23:25:24 -08:00
AndroPlus
1d10e69288 Update Japanese translation 2021-02-11 23:23:36 -08:00
topjohnwu
63590d379c Update hide icon strategy 2021-02-11 22:38:41 -08:00
topjohnwu
5f63e88984 Hide icons when things don't fit 2021-02-11 05:08:40 -08:00
topjohnwu
75584e2b19 App string resources overhaul 2021-02-11 02:34:27 -08:00
vvb2060
1426ee2ebd Fix Android build version sdk in script 2021-02-10 22:22:50 -08:00
topjohnwu
b6643b7bfc Publish new canary build 2021-02-07 21:39:00 -08:00
Hen Ry
721dfdf553 Added translation of new strings 2021-02-07 17:42:33 -08:00
topjohnwu
2963747d14 Fix LZ4_LG format decompression
Fix #3802, fix #3722, fix #3770, fix #3635, fix #3787, close #3812
2021-02-07 17:40:59 -08:00
topjohnwu
e7350d5041 Fix unable to patch images when app is hidden 2021-02-07 06:42:06 -08:00
topjohnwu
f37e8f4ca8 Fix boot image patching 2021-02-07 01:54:08 -08:00
topjohnwu
594c2accc0 Update dependencies 2021-02-05 04:41:01 -08:00
topjohnwu
7acfac6a91 Publish new canary build 2021-01-30 12:54:51 -08:00
Laz M
0646f48e14 [README] Warn users that the official website is github
Google puts a number of cheeky looking websites in the results for Magisk.

I only found out they were unofficial is though issue #3435. This deserves to be shown more prominently.
2021-01-30 11:59:39 -08:00
tzagim
37565fd067 Fix TYPOs 2021-01-30 11:58:43 -08:00
vvb2060
26b2e7dc5d Care version code changes 2021-01-30 11:58:10 -08:00
vvb2060
c3313623e4 Fix release build 2021-01-30 11:58:10 -08:00
topjohnwu
2089223690 Fix #3785 2021-01-30 11:51:15 -08:00
topjohnwu
52e1b84d41 Symlink pre API 21 2021-01-30 01:12:49 -08:00
topjohnwu
8794141b7f Support super old emulators 2021-01-30 00:56:16 -08:00
topjohnwu
f6126dd20e Support Shortcuts pre API 26
Close #3778
2021-01-29 23:16:09 -08:00
topjohnwu
18acfda99b Remove Windows NDK symlink
https://github.com/actions/virtual-environments/pull/2343
2021-01-29 05:43:41 -08:00
topjohnwu
bec5edca84 Avoiding using shell I/O 2021-01-29 05:15:22 -08:00
topjohnwu
6fb20b3ee5 Proper proguard rules 2021-01-27 04:56:39 -08:00
topjohnwu
eaf4d8064b Also download to external storage 2021-01-27 04:09:07 -08:00
topjohnwu
2a5f5b1bba Workaround zip extraction bug on older devices 2021-01-27 03:00:09 -08:00
topjohnwu
c538a77937 Tweak build configs and scripts 2021-01-27 02:36:32 -08:00
sominn
aa9e7b1ed1 Update strings.xml
CS string update
2021-01-27 01:00:10 -08:00
Arbri çoçka
a3066eddab Fix string in values-sq 2021-01-27 00:59:49 -08:00
Arbri çoçka
d1729fa787 Fix string in values-sq 2021-01-27 00:59:49 -08:00
vvb2060
93961dde2c Fix version on continuous build 2021-01-27 00:54:11 -08:00
topjohnwu
1024e68eb6 Remove class mapping in full APK 2021-01-26 07:27:35 -08:00
topjohnwu
6ae2c9387d Use stub APK hiding method for Android 5.0+
At the same time, disable app hiding on devices lower than 5.0
to simplify the logic in the app. By doing so, a hidden app always
implies running as stub.
2021-01-26 07:27:35 -08:00
topjohnwu
fba83e2330 Support stub APK loading down to Android 5.0 2021-01-26 07:27:35 -08:00
topjohnwu
f1295cb7d6 Fix root on Android 7.0 and lower 2021-01-26 02:16:11 -08:00
topjohnwu
dc61dfbde6 Cache update check results 2021-01-25 04:13:08 -08:00
topjohnwu
21466426da Some code cleanup 2021-01-25 03:44:38 -08:00
topjohnwu
3f0136362b Move nand flash handling into boot_patch.sh 2021-01-25 03:37:41 -08:00
topjohnwu
e92d77bbec Some optimizations 2021-01-25 03:02:43 -08:00
topjohnwu
07bd36c94b Fix patching files
Fix #3765
2021-01-25 02:24:12 -08:00
topjohnwu
b1dbbdef12 Remove unneeded busybox redirection 2021-01-25 00:23:42 -08:00
topjohnwu
3e479726ec Fix legacy rootfs devices 2021-01-25 00:19:10 -08:00
vvb2060
4cc41eccb3 Skip download notes when loading notes url 2021-01-24 21:02:51 -08:00
vvb2060
8f08ae59ac Fix permission 2021-01-24 21:02:43 -08:00
vvb2060
e8d4e492d6 Fix CHANGELOG_URL 2021-01-24 21:02:37 -08:00
topjohnwu
b8090a8e18 Ensure cwd is writable in module scripts
Close #3763
2021-01-24 20:58:30 -08:00
topjohnwu
c609a01e55 Proper shortcut name 2021-01-24 08:00:17 -08:00
Wagg13
c97fb385cd New update values-pt-rBR
update brazilian strings.xml
2021-01-24 07:36:07 -08:00
LLZN
da6c57750e correction czech translat
change and fix some strings after trying a new version of the application (v8.0.6)
2021-01-24 07:35:49 -08:00
topjohnwu
6951d926f7 Rename app name to just Magisk 2021-01-24 07:35:00 -08:00
topjohnwu
6623195bd5 Cleanup scripts 2021-01-24 07:24:13 -08:00
topjohnwu
ec31bb9a82 Rename scripts 2021-01-24 07:18:14 -08:00
vvb2060
8618cc383a Fix install modules
Fix #3759
2021-01-24 07:03:19 -08:00
vvb2060
4b01e3a3c7 Cleanup more kotlin stuffs 2021-01-24 07:03:06 -08:00
vvb2060
7f748c23c1 Use Java debugger 2021-01-24 07:02:44 -08:00
vvb2060
963d248cc7 Rename apk to be uninstaller 2021-01-24 07:02:36 -08:00
topjohnwu
657056e636 Cache changelog files 2021-01-24 06:55:43 -08:00
topjohnwu
9d5efea66e Remove ManagerJson
Everything is now Magisk
2021-01-24 05:14:46 -08:00
topjohnwu
658d74e026 Update home fragment 2021-01-24 00:02:49 -08:00
vvb2060
5113f6d375 Fix stop magiskhide 2021-01-23 18:13:15 -08:00
vvb2060
96405c26d0 writeTo has closed InputStream 2021-01-23 18:12:19 -08:00
vvb2060
4ea5f34bf3 Remove unused action 2021-01-23 18:11:08 -08:00
vvb2060
dbd13a2019 Clean code 2021-01-23 18:10:26 -08:00
vvb2060
06773235da Fix Windows build 2021-01-23 18:06:01 -08:00
vvb2060
e57556a8af Use ro.kernel.qemu to check emulator 2021-01-23 18:05:38 -08:00
vvb2060
b54b78c29d Fix prevent dot in the first position 2021-01-23 17:31:18 -08:00
vvb2060
317336f771 Add isolated processes log 2021-01-23 17:31:11 -08:00
topjohnwu
b4e52f6135 Better development workflow 2021-01-23 16:50:55 -08:00
topjohnwu
f2ca042915 Fix script for handling .apex files 2021-01-23 16:09:30 -08:00
topjohnwu
1060dd2906 Random refactoring 2021-01-23 13:26:28 -08:00
topjohnwu
2e0f7a82fa More complete stub sources 2021-01-22 20:45:37 -08:00
topjohnwu
5798536559 Remove unnecessary hacks 2021-01-22 20:25:37 -08:00
topjohnwu
ab9a83c82f Bump target SDK to 30 2021-01-22 05:03:33 -08:00
topjohnwu
c87fdbea0f Fix erroneous stream close 2021-01-22 03:07:39 -08:00
topjohnwu
ec8fffe61c Merge Magisk install zip into Magisk Manager
Distribute Magisk directly with Magisk Manager APK. The APK will
contain all required binaries and scripts for installation and
uninstallation. App versions will now align with Magisk releases.

Extra effort is spent to make the APK itself also a flashable zip that
can be used in custom recoveries, so those still prefer to install
Magisk with recoveries will not be affected with this change.

As a bonus, this makes the whole installation and uninstallation
process 100% offline. The existing Magisk Manager was not really
functional without an Internet connection, as the installation process
was highly tied to zips hosted on the server.

An additional bonus: since all binaries are now shipped as "native
libraries" of the APK, we can finally bump the target SDK version
higher than 28. The target SDK version was stuck at 28 for a long time
because newer SELinux restricts running executables from internal
storage. More details can be found here: https://github.com/termux/termux-app/issues/1072
The target SDK bump will be addressed in a future commit.

Co-authored with @vvb2060
2021-01-22 02:29:54 -08:00
topjohnwu
61d52991f1 Update BusyBox 2021-01-21 00:35:22 -08:00
topjohnwu
9100186dce Make emulator direct install env fix 2021-01-18 13:32:10 -08:00
topjohnwu
d2bc2cfcf8 Install both 32 and 64 bit binaries 2021-01-18 12:37:08 -08:00
topjohnwu
5a71998b4e Stop embedding magisk in magiskinit 2021-01-18 04:25:26 -08:00
topjohnwu
42278f12ff Fix typo in init daemon 2021-01-18 04:13:54 -08:00
topjohnwu
f5593e051c Update README 2021-01-17 06:19:56 -08:00
topjohnwu
a27e30cf54 Update release notes 2021-01-17 06:08:15 -08:00
topjohnwu
79140c7636 Proper xxread and xwrite implementation 2021-01-17 01:42:45 -08:00
topjohnwu
1f4c595cd3 Revert to old su -c behavior 2021-01-16 23:59:31 -08:00
topjohnwu
b5b62e03af Fix copySepolicyRules logic 2021-01-16 21:45:45 -08:00
topjohnwu
67e2a4720e Fix xxread false negatives
Fix #3710
2021-01-16 21:43:53 -08:00
topjohnwu
f5c2d72429 Also log pid and tid 2021-01-16 16:10:47 -08:00
topjohnwu
2f5331ab48 Update README 2021-01-16 05:02:39 -08:00
366 changed files with 5295 additions and 5813 deletions

View File

@@ -7,15 +7,17 @@ assignees: ''
--- ---
<!--
## READ BEFORE OPENING ISSUES ## READ BEFORE OPENING ISSUES
All bug reports require you to **USE CANARY BUILDS**. Please include the version name and version code in the bug report. All bug reports require you to **USE CANARY BUILDS**. Please include the version name and version code in the bug report.
If you experience a bootloop, attach a `dmesg` (kernel logs) when the device refuse to boot. This may very likely require a custom kernel on some devices as `last_kmsg` or `pstore ramoops` are usually not enabled by default. In addition, please also upload the result of `cat /proc/mounts` when your device is working correctly **WITHOUT ROOT**. If you experience a bootloop, attach a `dmesg` (kernel logs) when the device refuse to boot. This may very likely require a custom kernel on some devices as `last_kmsg` or `pstore ramoops` are usually not enabled by default. In addition, please also upload the result of `cat /proc/mounts` when your device is working correctly **WITHOUT ROOT**.
If you experience issues during installation, in recovery, upload the recovery logs, or in Magisk Manager, upload the install logs. Please also upload the `boot.img` or `recovery.img` that you are using for patching. If you experience issues during installation, in recovery, upload the recovery logs, or in Magisk, upload the install logs. Please also upload the `boot.img` or `recovery.img` that you are using for patching.
If you experience a crash of Magisk Manager, dump the full `logcat` **when the crash happens**. **DO NOT** upload `magisk.log`. If you experience a crash of Magisk app, dump the full `logcat` **when the crash happens**.
If you experience other issues related to Magisk, upload `magisk.log`, and preferably also include a boot `logcat` (start dumping `logcat` when the device boots up) If you experience other issues related to Magisk, upload `magisk.log`, and preferably also include a boot `logcat` (start dumping `logcat` when the device boots up)
@@ -26,3 +28,10 @@ If you experience other issues related to Magisk, upload `magisk.log`, and prefe
**DO NOT** report issues if you have any modules installed. **DO NOT** report issues if you have any modules installed.
Without following the rules above, your issue will be closed without explanation. Without following the rules above, your issue will be closed without explanation.
-->
Device:
Android version:
Magisk version name:
Magisk version code:

6
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@@ -0,0 +1,6 @@
blank_issues_enabled: false
contact_links:
- name: XDA Community Support
url: https://forum.xda-developers.com/f/magisk.5903/
about: Please ask and answer questions here.

View File

@@ -43,12 +43,8 @@ jobs:
- name: Set up GitHub env (Windows) - name: Set up GitHub env (Windows)
if: runner.os == 'Windows' if: runner.os == 'Windows'
run: | run: |
$oldAndroidPath = $env:ANDROID_SDK_ROOT
$sdk_root = "C:\Android"
New-Item -Path $sdk_root -ItemType SymbolicLink -Value $oldAndroidPath
$ndk_ver = Select-String -Path "gradle.properties" -Pattern "^magisk.fullNdkVersion=" | % { $_ -replace ".*=" } $ndk_ver = Select-String -Path "gradle.properties" -Pattern "^magisk.fullNdkVersion=" | % { $_ -replace ".*=" }
echo "ANDROID_SDK_ROOT=$sdk_root" >> $env:GITHUB_ENV echo "ANDROID_SDK_ROOT=$env:ANDROID_SDK_ROOT" >> $env:GITHUB_ENV
echo "ANDROID_HOME=$sdk_root" >> $env:GITHUB_ENV
echo "MAGISK_NDK_VERSION=$ndk_ver" >> $env:GITHUB_ENV echo "MAGISK_NDK_VERSION=$ndk_ver" >> $env:GITHUB_ENV
echo "GRADLE_OPTS=-Dorg.gradle.daemon=false" >> $env:GITHUB_ENV echo "GRADLE_OPTS=-Dorg.gradle.daemon=false" >> $env:GITHUB_ENV
@@ -82,6 +78,10 @@ jobs:
- name: Build release - name: Build release
run: python build.py -vr all run: python build.py -vr all
- name: Refresh flag
run: touch gradle.properties
shell: bash
- name: Build debug - name: Build debug
run: python build.py -v all run: python build.py -v all

26
.github/workflows/issues.yml vendored Normal file
View File

@@ -0,0 +1,26 @@
name: Check Issues
on:
issues:
types: [opened]
jobs:
check:
runs-on: ubuntu-latest
steps:
- name: Check out
uses: actions/checkout@v2
- name: Read latest version code
run: |
ver=$(sed -n 's/^magisk.versionCode=//p' gradle.properties)
echo MAGISK_VERSION_CODE=$ver >> $GITHUB_ENV
- if: contains(github.event.issue.body, format('Magisk version code{0} ', ':')) != true
id: close
name: Close Issue(template)
uses: peter-evans/close-issue@v1
with:
comment: This issue is being automatically closed because it does not follow the issue template.
- if: steps.close.conclusion == 'skipped' && contains(github.event.issue.body, format('Magisk version code{0} {1}', ':', env.MAGISK_VERSION_CODE)) != true
name: Close Issue(latest canary)
uses: peter-evans/close-issue@v1
with:
comment: This issue is being automatically closed because latest canary Magisk version code is ${{ env.MAGISK_VERSION_CODE }}.

8
.gitmodules vendored
View File

@@ -6,7 +6,7 @@
url = https://github.com/topjohnwu/ndk-busybox.git url = https://github.com/topjohnwu/ndk-busybox.git
[submodule "dtc"] [submodule "dtc"]
path = native/jni/external/dtc path = native/jni/external/dtc
url = https://github.com/dgibson/dtc url = https://github.com/dgibson/dtc.git
[submodule "lz4"] [submodule "lz4"]
path = native/jni/external/lz4 path = native/jni/external/lz4
url = https://github.com/lz4/lz4.git url = https://github.com/lz4/lz4.git
@@ -28,6 +28,12 @@
[submodule "xhook"] [submodule "xhook"]
path = native/jni/external/xhook path = native/jni/external/xhook
url = https://github.com/iqiyi/xHook.git url = https://github.com/iqiyi/xHook.git
[submodule "libcxx"]
path = native/jni/external/libcxx
url = https://github.com/topjohnwu/libcxx.git
[submodule "zlib"]
path = native/jni/external/zlib
url = https://android.googlesource.com/platform/external/zlib
[submodule "termux-elf-cleaner"] [submodule "termux-elf-cleaner"]
path = tools/termux-elf-cleaner path = tools/termux-elf-cleaner
url = https://github.com/termux/termux-elf-cleaner.git url = https://github.com/termux/termux-elf-cleaner.git

View File

@@ -1,25 +1,24 @@
![](docs/images/logo.png) ![](docs/images/logo.png)
![ZIP Downloads](https://img.shields.io/badge/dynamic/json?color=blue&label=ZIP%20Downloads&query=magisk&url=https%3A%2F%2Fraw.githubusercontent.com%2Ftopjohnwu%2Fmagisk_files%2Fcount%2Fcount.json&cacheSeconds=1800) [![Downloads](https://img.shields.io/badge/dynamic/json?color=green&label=Downloads&query=totalString&url=https%3A%2F%2Fraw.githubusercontent.com%2Ftopjohnwu%2Fmagisk-files%2Fcount%2Fcount.json&cacheSeconds=1800)](https://raw.githubusercontent.com/topjohnwu/magisk-files/count/count.json)
![APK Downloads](https://img.shields.io/badge/dynamic/json?color=green&label=APK%20Downloads&query=manager&url=https%3A%2F%2Fraw.githubusercontent.com%2Ftopjohnwu%2Fmagisk_files%2Fcount%2Fcount.json&cacheSeconds=1800)
## Introduction ## Introduction
Magisk is a suite of open source tools for customizing Android, supporting devices higher than Android 4.2. It covers fundamental parts of Android customization: root, boot scripts, SELinux patches, AVB2.0 / dm-verity / forceencrypt removals etc. Magisk is a suite of open source software for customizing Android, supporting devices higher than Android 5.0.<br>
Here are some feature highlights: Here are some feature highlights:
- **MagiskSU**: Provide root access to your device - **MagiskSU**: Provide root access for applications
- **Magisk Modules**: Modify read-only partitions by installing modules - **Magisk Modules**: Modify read-only partitions by installing modules
- **MagiskHide**: Hide Magisk from root detections / system integrity checks - **MagiskHide**: Hide Magisk from root detections / system integrity checks
- **MagiskBoot**: The most complete tool for unpacking and repacking Android boot images
## Downloads ## Downloads
[![](https://img.shields.io/badge/Magisk%20Manager-v8.0.5-green)](https://github.com/topjohnwu/Magisk/releases/download/manager-v8.0.5/MagiskManager-v8.0.5.apk) [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%20Manager-Canary-red)](https://raw.githubusercontent.com/topjohnwu/magisk_files/canary/app-debug.apk)
<br> [![](https://img.shields.io/badge/Magisk-v22.1-blue)](https://github.com/topjohnwu/Magisk/releases/tag/v22.1)
[![](https://img.shields.io/badge/Magisk-v21.2-blue)](https://github.com/topjohnwu/Magisk/releases/tag/v21.2) [![](https://img.shields.io/badge/Magisk%20Beta-v22.1-blue)](https://github.com/topjohnwu/Magisk/releases/tag/v22.1)
[![](https://img.shields.io/badge/Magisk%20Beta-v21.2-blue)](https://github.com/topjohnwu/Magisk/releases/tag/v21.2) [![](https://img.shields.io/badge/Magisk-Canary-red)](https://raw.githubusercontent.com/topjohnwu/magisk-files/canary/app-debug.apk)
## Useful Links ## Useful Links
@@ -28,23 +27,13 @@ Here are some feature highlights:
- [Magisk Documentation](https://topjohnwu.github.io/Magisk/) - [Magisk Documentation](https://topjohnwu.github.io/Magisk/)
- [Magisk Troubleshoot Wiki](https://www.didgeridoohan.com/magisk/HomePage) (by [@Didgeridoohan](https://github.com/Didgeridoohan)) - [Magisk Troubleshoot Wiki](https://www.didgeridoohan.com/magisk/HomePage) (by [@Didgeridoohan](https://github.com/Didgeridoohan))
## Android Version Support
- Android 4.2+: MagiskSU and Magisk Modules Only
- Android 4.4+: All core features available
- Android 6.0+: Guaranteed MagiskHide support
- Android 7.0+: Full MagiskHide protection
- Android 9.0+: Magisk Manager stealth mode
## Bug Reports ## Bug Reports
Canary Channels are cutting edge builds for those adventurous. To access canary builds, install the Canary Magisk Manager, switch to the Canary Channel in settings and upgrade.
**Only bug reports from Canary builds will be accepted.** **Only bug reports from Canary builds will be accepted.**
For installation issues, upload both boot image and install logs.<br> For installation issues, upload both boot image and install logs.<br>
For Magisk issues, upload boot logcat or dmesg.<br> For Magisk issues, upload boot logcat or dmesg.<br>
For Magisk Manager crashes, record and upload the logcat when the crash occurs. For Magisk app crashes, record and upload the logcat when the crash occurs.
## Building and Development ## Building and Development
@@ -60,13 +49,13 @@ For Magisk Manager crashes, record and upload the logcat when the crash occurs.
- Run `./build.py ndk` to let the script download and install NDK for you - Run `./build.py ndk` to let the script download and install NDK for you
- To start building, run `build.py` to see your options. \ - To start building, run `build.py` to see your options. \
For each action, use `-h` to access help (e.g. `./build.py all -h`) For each action, use `-h` to access help (e.g. `./build.py all -h`)
- To start development, open the project in Android Studio. Both app (Kotlin/Java) and native (C++/C) source code can be properly developed using the IDE, but *always* use `build.py` for building. - To start development, open the project with Android Studio. The IDE can be used for both app (Kotlin/Java) and native (C++/C) sources.
- Optionally, set custom configs with `config.prop`. A sample `config.prop.sample` is provided. - Optionally, set custom configs with `config.prop`. A sample `config.prop.sample` is provided.
- To sign APKs and zips with your own private keys, set signing configs in `config.prop`. For more info, check [Google's Documentation](https://developer.android.com/studio/publish/app-signing.html#generate-key). - To sign APKs and zips with your own private keys, set signing configs in `config.prop`. For more info, check [Google's Documentation](https://developer.android.com/studio/publish/app-signing.html#generate-key).
## Translation Contributions ## Translation Contributions
Default string resources for Magisk Manager and its stub APK are located here: Default string resources for the Magisk app and its stub APK are located here:
- `app/src/main/res/values/strings.xml` - `app/src/main/res/values/strings.xml`
- `stub/src/main/res/values/strings.xml` - `stub/src/main/res/values/strings.xml`

5
app/.gitignore vendored
View File

@@ -6,6 +6,7 @@
app/release app/release
*.hprof *.hprof
.externalNativeBuild/ .externalNativeBuild/
public.certificate.x509.pem
private.key.pk8
*.apk *.apk
src/main/assets
src/main/jniLibs
src/main/resources

View File

@@ -1,3 +1,4 @@
import org.apache.tools.ant.filters.FixCrLfFilter
import java.io.PrintStream import java.io.PrintStream
plugins { plugins {
@@ -15,19 +16,18 @@ kapt {
javacOptions { javacOptions {
option("-Xmaxerrs", 1000) option("-Xmaxerrs", 1000)
} }
arguments {
arg("room.incremental", "true")
}
} }
android { android {
defaultConfig { defaultConfig {
applicationId = "com.topjohnwu.magisk" applicationId = "com.topjohnwu.magisk"
vectorDrawables.useSupportLibrary = true vectorDrawables.useSupportLibrary = true
multiDexEnabled = true versionName = Config.version
versionName = Config.appVersion versionCode = Config.versionCode
versionCode = Config.appVersionCode ndk.abiFilters("armeabi-v7a", "arm64-v8a", "x86", "x86_64")
javaCompileOptions.annotationProcessorOptions.arguments(
mapOf("room.incremental" to "true")
)
} }
buildTypes { buildTypes {
@@ -51,13 +51,14 @@ android {
} }
packagingOptions { packagingOptions {
exclude("/META-INF/**") exclude("/META-INF/*")
exclude("/org/bouncycastle/**") exclude("/org/bouncycastle/**")
exclude("/kotlin/**") exclude("/kotlin/**")
exclude("/kotlinx/**") exclude("/kotlinx/**")
exclude("/okhttp3/**") exclude("/okhttp3/**")
exclude("/*.txt") exclude("/*.txt")
exclude("/*.bin") exclude("/*.bin")
doNotStrip("**/*.so")
} }
kotlinOptions { kotlinOptions {
@@ -65,10 +66,83 @@ android {
} }
} }
tasks["preBuild"]?.dependsOn(tasks.register("copyUtils", Copy::class) { val syncLibs by tasks.registering(Sync::class) {
from(rootProject.file("scripts/util_functions.sh")) into("src/main/jniLibs")
into("src/main/res/raw") into("armeabi-v7a") {
}) from(rootProject.file("native/out/armeabi-v7a")) {
include("busybox", "magiskboot", "magiskinit", "magisk")
rename { if (it == "magisk") "libmagisk32.so" else "lib$it.so" }
}
from(rootProject.file("native/out/arm64-v8a")) {
include("magisk")
rename { if (it == "magisk") "libmagisk64.so" else "lib$it.so" }
}
}
into("x86") {
from(rootProject.file("native/out/x86")) {
include("busybox", "magiskboot", "magiskinit", "magisk")
rename { if (it == "magisk") "libmagisk32.so" else "lib$it.so" }
}
from(rootProject.file("native/out/x86_64")) {
include("magisk")
rename { if (it == "magisk") "libmagisk64.so" else "lib$it.so" }
}
}
onlyIf {
if (inputs.sourceFiles.files.size != 10)
throw StopExecutionException("Please build binaries first! (./build.py binary)")
true
}
}
val createStubLibs by tasks.registering {
dependsOn(syncLibs)
doLast {
val arm64 = project.file("src/main/jniLibs/arm64-v8a/libstub.so")
arm64.parentFile.mkdirs()
arm64.createNewFile()
val x64 = project.file("src/main/jniLibs/x86_64/libstub.so")
x64.parentFile.mkdirs()
x64.createNewFile()
}
}
val syncAssets by tasks.registering(Sync::class) {
dependsOn(createStubLibs)
inputs.property("version", Config.version)
inputs.property("versionCode", Config.versionCode)
into("src/main/assets")
from(rootProject.file("scripts")) {
include("util_functions.sh", "boot_patch.sh", "uninstaller.sh", "addon.d.sh")
}
into("chromeos") {
from(rootProject.file("tools/futility"))
from(rootProject.file("tools/keys")) {
include("kernel_data_key.vbprivk", "kernel.keyblock")
}
}
filesMatching("**/util_functions.sh") {
filter {
it.replace("#MAGISK_VERSION_STUB",
"MAGISK_VER='${Config.version}'\n" +
"MAGISK_VER_CODE=${Config.versionCode}")
}
filter<FixCrLfFilter>("eol" to FixCrLfFilter.CrLf.newInstance("lf"))
}
}
val syncResources by tasks.registering(Sync::class) {
dependsOn(syncAssets)
into("src/main/resources/META-INF/com/google/android")
from(rootProject.file("scripts/update_binary.sh")) {
rename { "update-binary" }
}
from(rootProject.file("scripts/flash_script.sh")) {
rename { "updater-script" }
}
}
tasks["preBuild"]?.dependsOn(syncResources)
android.applicationVariants.all { android.applicationVariants.all {
val keysDir = rootProject.file("tools/keys") val keysDir = rootProject.file("tools/keys")
@@ -109,6 +183,8 @@ android.applicationVariants.all {
dependencies { dependencies {
implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar")))) implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar"))))
implementation(kotlin("stdlib")) implementation(kotlin("stdlib"))
// Some dependencies request JDK 8 stdlib, specify manually here to prevent version mismatch
implementation(kotlin("stdlib-jdk8"))
implementation(project(":app:shared")) implementation(project(":app:shared"))
implementation("com.github.topjohnwu:jtar:1.0.0") implementation("com.github.topjohnwu:jtar:1.0.0")
@@ -125,40 +201,31 @@ dependencies {
implementation("${bindingAdapter}:${vBAdapt}") implementation("${bindingAdapter}:${vBAdapt}")
implementation("${bindingAdapter}-recyclerview:${vBAdapt}") implementation("${bindingAdapter}-recyclerview:${vBAdapt}")
val vMarkwon = "4.6.0" val vMarkwon = "4.6.2"
implementation("io.noties.markwon:core:${vMarkwon}") implementation("io.noties.markwon:core:${vMarkwon}")
implementation("io.noties.markwon:html:${vMarkwon}") implementation("io.noties.markwon:html:${vMarkwon}")
implementation("io.noties.markwon:image:${vMarkwon}") implementation("io.noties.markwon:image:${vMarkwon}")
implementation("com.caverock:androidsvg:1.4") implementation("com.caverock:androidsvg:1.4")
val vLibsu = "3.0.2" val vLibsu = "3.1.2"
implementation("com.github.topjohnwu.libsu:core:${vLibsu}") implementation("com.github.topjohnwu.libsu:core:${vLibsu}")
implementation("com.github.topjohnwu.libsu:io:${vLibsu}") implementation("com.github.topjohnwu.libsu:io:${vLibsu}")
val vKoin = "2.1.6"
implementation("org.koin:koin-core:${vKoin}")
implementation("org.koin:koin-android:${vKoin}")
implementation("org.koin:koin-androidx-viewmodel:${vKoin}")
val vRetrofit = "2.9.0" val vRetrofit = "2.9.0"
implementation("com.squareup.retrofit2:retrofit:${vRetrofit}") implementation("com.squareup.retrofit2:retrofit:${vRetrofit}")
implementation("com.squareup.retrofit2:converter-moshi:${vRetrofit}") implementation("com.squareup.retrofit2:converter-moshi:${vRetrofit}")
implementation("com.squareup.retrofit2:converter-scalars:${vRetrofit}") implementation("com.squareup.retrofit2:converter-scalars:${vRetrofit}")
val vOkHttp = "3.12.12" val vOkHttp = "4.9.1"
implementation("com.squareup.okhttp3:okhttp") { implementation("com.squareup.okhttp3:okhttp:${vOkHttp}")
version {
strictly(vOkHttp)
}
}
implementation("com.squareup.okhttp3:logging-interceptor:${vOkHttp}") implementation("com.squareup.okhttp3:logging-interceptor:${vOkHttp}")
implementation("com.squareup.okhttp3:okhttp-dnsoverhttps:${vOkHttp}") implementation("com.squareup.okhttp3:okhttp-dnsoverhttps:${vOkHttp}")
val vMoshi = "1.11.0" val vMoshi = "1.12.0"
implementation("com.squareup.moshi:moshi:${vMoshi}") implementation("com.squareup.moshi:moshi:${vMoshi}")
kapt("com.squareup.moshi:moshi-kotlin-codegen:${vMoshi}") kapt("com.squareup.moshi:moshi-kotlin-codegen:${vMoshi}")
val vRoom = "2.3.0-alpha04" val vRoom = "2.3.0"
implementation("androidx.room:room-runtime:${vRoom}") implementation("androidx.room:room-runtime:${vRoom}")
implementation("androidx.room:room-ktx:${vRoom}") implementation("androidx.room:room-ktx:${vRoom}")
kapt("androidx.room:room-compiler:${vRoom}") kapt("androidx.room:room-compiler:${vRoom}")
@@ -167,17 +234,15 @@ dependencies {
implementation("androidx.navigation:navigation-fragment-ktx:${vNav}") implementation("androidx.navigation:navigation-fragment-ktx:${vNav}")
implementation("androidx.navigation:navigation-ui-ktx:${vNav}") implementation("androidx.navigation:navigation-ui-ktx:${vNav}")
implementation("androidx.biometric:biometric:1.0.1") implementation("androidx.biometric:biometric:1.1.0")
implementation("androidx.constraintlayout:constraintlayout:2.0.4") implementation("androidx.constraintlayout:constraintlayout:2.0.4")
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0") implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
implementation("androidx.browser:browser:1.3.0") implementation("androidx.browser:browser:1.3.0")
implementation("androidx.preference:preference:1.1.1") implementation("androidx.preference:preference:1.1.1")
implementation("androidx.recyclerview:recyclerview:1.1.0") implementation("androidx.recyclerview:recyclerview:1.2.0")
implementation("androidx.fragment:fragment-ktx:1.2.5") implementation("androidx.fragment:fragment-ktx:1.3.3")
implementation("androidx.work:work-runtime-ktx:2.4.0") implementation("androidx.work:work-runtime-ktx:2.5.0")
implementation("androidx.transition:transition:1.3.1") implementation("androidx.transition:transition:1.4.1")
implementation("androidx.multidex:multidex:2.0.1")
implementation("androidx.core:core-ktx:1.3.2") implementation("androidx.core:core-ktx:1.3.2")
implementation("androidx.localbroadcastmanager:localbroadcastmanager:1.0.0") implementation("com.google.android.material:material:1.3.0")
implementation("com.google.android.material:material:1.2.1")
} }

View File

@@ -18,34 +18,38 @@
# Kotlin # Kotlin
-assumenosideeffects class kotlin.jvm.internal.Intrinsics { -assumenosideeffects class kotlin.jvm.internal.Intrinsics {
public static void checkExpressionValueIsNotNull(...); public static void check*(...);
public static void checkNotNullExpressionValue(...); public static void throw*(...);
public static void checkReturnedValueIsNotNull(...);
public static void checkFieldIsNotNull(...);
public static void checkParameterIsNotNull(...);
} }
# Stubs
-keep class a.* { *; }
# Snet # Snet
-keepclassmembers class com.topjohnwu.magisk.ui.safetynet.SafetyNetHelper { *; } -keepclassmembers class com.topjohnwu.magisk.ui.safetynet.SafetyNetHelper { *; }
-keep,allowobfuscation interface com.topjohnwu.magisk.ui.safetynet.SafetyNetHelper$Callback -keep,allowobfuscation interface com.topjohnwu.magisk.ui.safetynet.SafetyNetHelper$Callback
-keepclassmembers class * implements com.topjohnwu.magisk.ui.safetynet.SafetyNetHelper$Callback { -keepclassmembers class * implements com.topjohnwu.magisk.ui.safetynet.SafetyNetHelper$Callback { *; }
void onResponse(org.json.JSONObject);
# Stub
-keep class com.topjohnwu.magisk.core.App { <init>(java.lang.Object); }
-keepclassmembers class androidx.appcompat.app.AppCompatDelegateImpl {
boolean mActivityHandlesUiModeChecked;
boolean mActivityHandlesUiMode;
} }
# Strip Timber verbose and debug logging # Strip Timber verbose and debug logging
-assumenosideeffects class timber.log.Timber.Tree { -assumenosideeffects class timber.log.Timber$Tree {
public void v(**); public void v(**);
public void d(**); public void d(**);
} }
# Excessive obfuscation # Excessive obfuscation
-repackageclasses -repackageclasses 'a'
-allowaccessmodification -allowaccessmodification
# QOL -dontwarn org.bouncycastle.jsse.BCSSLParameters
-dontnote ** -dontwarn org.bouncycastle.jsse.BCSSLSocket
-dontwarn com.caverock.androidsvg.** -dontwarn org.bouncycastle.jsse.provider.BouncyCastleJsseProvider
-dontwarn ru.noties.markwon.** -dontwarn org.commonmark.ext.gfm.strikethrough.Strikethrough
-dontwarn org.conscrypt.Conscrypt*
-dontwarn org.conscrypt.ConscryptHostnameVerifier
-dontwarn org.openjsse.javax.net.ssl.SSLParameters
-dontwarn org.openjsse.javax.net.ssl.SSLSocket
-dontwarn org.openjsse.net.ssl.OpenJSSE

View File

@@ -19,7 +19,3 @@
# If you keep the line number information, uncomment this to # If you keep the line number information, uncomment this to
# hide the original source file name. # hide the original source file name.
#-renamesourcefileattribute SourceFile #-renamesourcefileattribute SourceFile
-keepclassmembers class * extends javax.net.ssl.SSLSocketFactory {
** delegate;
}

View File

@@ -1,17 +1,27 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
package="com.topjohnwu.shared"> 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.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" /> <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="29"
tools:ignore="ScopedStorage" />
<uses-permission
android:name="android.permission.QUERY_ALL_PACKAGES"
tools:ignore="QueryAllPackagesPermission" />
<application <application
android:label="Magisk Manager" android:allowBackup="false"
android:installLocation="internalOnly" android:label="Magisk"
android:usesCleartextTraffic="true" android:requestLegacyExternalStorage="true"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@android:style/Theme.Translucent.NoTitleBar" android:theme="@android:style/Theme.Translucent.NoTitleBar"
tools:ignore="UnusedAttribute"> android:usesCleartextTraffic="true"
</application> tools:ignore="UnusedAttribute" />
</manifest> </manifest>

View File

@@ -7,7 +7,6 @@ import android.content.pm.ProviderInfo;
import android.database.Cursor; import android.database.Cursor;
import android.database.MatrixCursor; import android.database.MatrixCursor;
import android.net.Uri; import android.net.Uri;
import android.os.Build;
import android.os.Environment; import android.os.Environment;
import android.os.ParcelFileDescriptor; import android.os.ParcelFileDescriptor;
import android.provider.OpenableColumns; import android.provider.OpenableColumns;
@@ -24,12 +23,11 @@ import java.util.Map;
* Modified from androidx.core.content.FileProvider * Modified from androidx.core.content.FileProvider
*/ */
public class FileProvider extends ContentProvider { public class FileProvider extends ContentProvider {
private static final String[] COLUMNS = { private static final String[] COLUMNS = {OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE};
OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE };
private static final File DEVICE_ROOT = new File("/"); private static final File DEVICE_ROOT = new File("/");
private static HashMap<String, PathStrategy> sCache = new HashMap<>(); private static final HashMap<String, PathStrategy> sCache = new HashMap<>();
private PathStrategy mStrategy; private PathStrategy mStrategy;
@@ -42,7 +40,6 @@ public class FileProvider extends ContentProvider {
public void attachInfo(Context context, ProviderInfo info) { public void attachInfo(Context context, ProviderInfo info) {
super.attachInfo(context, info); super.attachInfo(context, info);
if (info.exported) { if (info.exported) {
throw new SecurityException("Provider must not be exported"); throw new SecurityException("Provider must not be exported");
} }
@@ -50,21 +47,16 @@ public class FileProvider extends ContentProvider {
throw new SecurityException("Provider must grant uri permissions"); throw new SecurityException("Provider must grant uri permissions");
} }
mStrategy = getPathStrategy(context, info.authority); mStrategy = getPathStrategy(context, info.authority.split(";")[0]);
} }
public static Uri getUriForFile(Context context, String authority, File file) {
public static Uri getUriForFile(Context context, String authority,
File file) {
final PathStrategy strategy = getPathStrategy(context, authority); final PathStrategy strategy = getPathStrategy(context, authority);
return strategy.getUriForFile(file); return strategy.getUriForFile(file);
} }
@Override @Override
public Cursor query(Uri uri, String[] projection, String selection, public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
String[] selectionArgs,
String sortOrder) {
final File file = mStrategy.getFileForUri(uri); final File file = mStrategy.getFileForUri(uri);
if (projection == null) { if (projection == null) {
@@ -94,7 +86,6 @@ public class FileProvider extends ContentProvider {
@Override @Override
public String getType(Uri uri) { public String getType(Uri uri) {
final File file = mStrategy.getFileForUri(uri); final File file = mStrategy.getFileForUri(uri);
final int lastDot = file.getName().lastIndexOf('.'); final int lastDot = file.getName().lastIndexOf('.');
@@ -115,23 +106,18 @@ public class FileProvider extends ContentProvider {
} }
@Override @Override
public int update(Uri uri, ContentValues values, String selection, public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
String[] selectionArgs) {
throw new UnsupportedOperationException("No external updates"); throw new UnsupportedOperationException("No external updates");
} }
@Override @Override
public int delete(Uri uri, String selection, public int delete(Uri uri, String selection, String[] selectionArgs) {
String[] selectionArgs) {
final File file = mStrategy.getFileForUri(uri); final File file = mStrategy.getFileForUri(uri);
return file.delete() ? 1 : 0; return file.delete() ? 1 : 0;
} }
@Override @Override
public ParcelFileDescriptor openFile(Uri uri, String mode) public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
throws FileNotFoundException {
final File file = mStrategy.getFileForUri(uri); final File file = mStrategy.getFileForUri(uri);
final int fileMode = modeToMode(mode); final int fileMode = modeToMode(mode);
return ParcelFileDescriptor.open(file, fileMode); return ParcelFileDescriptor.open(file, fileMode);
@@ -156,30 +142,24 @@ public class FileProvider extends ContentProvider {
strat.addRoot("internal_files", buildPath(context.getFilesDir(), ".")); strat.addRoot("internal_files", buildPath(context.getFilesDir(), "."));
strat.addRoot("cache_files", buildPath(context.getCacheDir(), ".")); strat.addRoot("cache_files", buildPath(context.getCacheDir(), "."));
strat.addRoot("external_files", buildPath(Environment.getExternalStorageDirectory(), ".")); strat.addRoot("external_files", buildPath(Environment.getExternalStorageDirectory(), "."));
{
File[] externalFilesDirs = getExternalFilesDirs(context, null); File[] externalFilesDirs = context.getExternalFilesDirs(null);
if (externalFilesDirs.length > 0) { if (externalFilesDirs.length > 0) {
strat.addRoot("external_file_files", buildPath(externalFilesDirs[0], ".")); strat.addRoot("external_file_files", buildPath(externalFilesDirs[0], "."));
}
} }
{ File[] externalCacheDirs = context.getExternalCacheDirs();
File[] externalCacheDirs = getExternalCacheDirs(context); if (externalCacheDirs.length > 0) {
if (externalCacheDirs.length > 0) { strat.addRoot("external_cache_files", buildPath(externalCacheDirs[0], "."));
strat.addRoot("external_cache_files", buildPath(externalCacheDirs[0], "."));
}
} }
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { File[] externalMediaDirs = context.getExternalMediaDirs();
File[] externalMediaDirs = context.getExternalMediaDirs(); if (externalMediaDirs.length > 0) {
if (externalMediaDirs.length > 0) { strat.addRoot("external_media_files", buildPath(externalMediaDirs[0], "."));
strat.addRoot("external_media_files", buildPath(externalMediaDirs[0], "."));
}
} }
return strat; return strat;
} }
interface PathStrategy { interface PathStrategy {
Uri getUriForFile(File file); Uri getUriForFile(File file);
File getFileForUri(Uri uri); File getFileForUri(Uri uri);
@@ -199,7 +179,6 @@ public class FileProvider extends ContentProvider {
} }
try { try {
root = root.getCanonicalFile(); root = root.getCanonicalFile();
} catch (IOException e) { } catch (IOException e) {
throw new IllegalArgumentException( throw new IllegalArgumentException(
@@ -218,7 +197,6 @@ public class FileProvider extends ContentProvider {
throw new IllegalArgumentException("Failed to resolve canonical path for " + file); throw new IllegalArgumentException("Failed to resolve canonical path for " + file);
} }
Map.Entry<String, File> mostSpecific = null; Map.Entry<String, File> mostSpecific = null;
for (Map.Entry<String, File> root : mRoots.entrySet()) { for (Map.Entry<String, File> root : mRoots.entrySet()) {
final String rootPath = root.getValue().getPath(); final String rootPath = root.getValue().getPath();
@@ -233,7 +211,6 @@ public class FileProvider extends ContentProvider {
"Failed to find configured root that contains " + path); "Failed to find configured root that contains " + path);
} }
final String rootPath = mostSpecific.getValue().getPath(); final String rootPath = mostSpecific.getValue().getPath();
if (rootPath.endsWith("/")) { if (rootPath.endsWith("/")) {
path = path.substring(rootPath.length()); path = path.substring(rootPath.length());
@@ -241,7 +218,6 @@ public class FileProvider extends ContentProvider {
path = path.substring(rootPath.length() + 1); path = path.substring(rootPath.length() + 1);
} }
path = Uri.encode(mostSpecific.getKey()) + '/' + Uri.encode(path, "/"); path = Uri.encode(mostSpecific.getKey()) + '/' + Uri.encode(path, "/");
return new Uri.Builder().scheme("content") return new Uri.Builder().scheme("content")
.authority(mAuthority).encodedPath(path).build(); .authority(mAuthority).encodedPath(path).build();
@@ -275,7 +251,6 @@ public class FileProvider extends ContentProvider {
} }
} }
private static int modeToMode(String mode) { private static int modeToMode(String mode) {
int modeBits; int modeBits;
if ("r".equals(mode)) { if ("r".equals(mode)) {
@@ -322,20 +297,4 @@ public class FileProvider extends ContentProvider {
System.arraycopy(original, 0, result, 0, newLength); System.arraycopy(original, 0, result, 0, newLength);
return result; return result;
} }
private static File[] getExternalFilesDirs(Context context, String type) {
if (Build.VERSION.SDK_INT >= 19) {
return context.getExternalFilesDirs(type);
} else {
return new File[] { context.getExternalFilesDir(type) };
}
}
private static File[] getExternalCacheDirs(Context context) {
if (Build.VERSION.SDK_INT >= 19) {
return context.getExternalCacheDirs();
} else {
return new File[] { context.getExternalCacheDir() };
}
}
} }

View File

@@ -0,0 +1,21 @@
package com.topjohnwu.magisk;
import android.content.Context;
public class ProviderInstaller {
public static boolean install(Context context) {
try {
// Try installing new SSL provider from Google Play Service
Context gms = context.createPackageContext("com.google.android.gms",
Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY);
gms.getClassLoader()
.loadClass("com.google.android.gms.common.security.ProviderInstallerImpl")
.getMethod("insertProvider", Context.class)
.invoke(null, gms);
} catch (Exception e) {
return false;
}
return true;
}
}

View File

@@ -1,70 +0,0 @@
package com.topjohnwu.magisk.net;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
public class NoSSLv3SocketFactory extends SSLSocketFactory {
private final static SSLSocketFactory delegate = HttpsURLConnection.getDefaultSSLSocketFactory();
@Override
public String[] getDefaultCipherSuites() {
return delegate.getDefaultCipherSuites();
}
@Override
public String[] getSupportedCipherSuites() {
return delegate.getSupportedCipherSuites();
}
private Socket createSafeSocket(Socket socket) {
if (socket instanceof SSLSocket)
return new SSLSocketWrapper((SSLSocket) socket) {
@Override
public void setEnabledProtocols(String[] protocols) {
List<String> proto = new ArrayList<>(Arrays.asList(getSupportedProtocols()));
proto.remove("SSLv3");
super.setEnabledProtocols(proto.toArray(new String[0]));
}
};
return socket;
}
@Override
public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
return createSafeSocket(delegate.createSocket(s, host, port, autoClose));
}
@Override
public Socket createSocket() throws IOException {
return createSafeSocket(delegate.createSocket());
}
@Override
public Socket createSocket(String host, int port) throws IOException {
return createSafeSocket(delegate.createSocket(host, port));
}
@Override
public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException {
return createSafeSocket(delegate.createSocket(host, port, localHost, localPort));
}
@Override
public Socket createSocket(InetAddress host, int port) throws IOException {
return createSafeSocket(delegate.createSocket(host, port));
}
@Override
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
return createSafeSocket(delegate.createSocket(address, port, localAddress, localPort));
}
}

View File

@@ -1,333 +0,0 @@
package com.topjohnwu.magisk.net;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.SocketAddress;
import java.net.SocketException;
import java.nio.channels.SocketChannel;
import javax.net.ssl.HandshakeCompletedListener;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
class SSLSocketWrapper extends SSLSocket {
private SSLSocket mBase;
SSLSocketWrapper(SSLSocket socket) {
mBase = socket;
}
@Override
public String[] getSupportedCipherSuites() {
return mBase.getSupportedCipherSuites();
}
@Override
public String[] getEnabledCipherSuites() {
return mBase.getEnabledCipherSuites();
}
@Override
public void setEnabledCipherSuites(String[] suites) {
mBase.setEnabledCipherSuites(suites);
}
@Override
public String[] getSupportedProtocols() {
return mBase.getSupportedProtocols();
}
@Override
public String[] getEnabledProtocols() {
return mBase.getEnabledProtocols();
}
@Override
public void setEnabledProtocols(String[] protocols) {
mBase.setEnabledProtocols(protocols);
}
@Override
public SSLSession getSession() {
return mBase.getSession();
}
@Override
public SSLSession getHandshakeSession() {
throw new UnsupportedOperationException();
}
@Override
public void addHandshakeCompletedListener(HandshakeCompletedListener listener) {
mBase.addHandshakeCompletedListener(listener);
}
@Override
public void removeHandshakeCompletedListener(HandshakeCompletedListener listener) {
mBase.removeHandshakeCompletedListener(listener);
}
@Override
public void startHandshake() throws IOException {
mBase.startHandshake();
}
@Override
public void setUseClientMode(boolean mode) {
mBase.setUseClientMode(mode);
}
@Override
public boolean getUseClientMode() {
return mBase.getUseClientMode();
}
@Override
public void setNeedClientAuth(boolean need) {
mBase.setNeedClientAuth(need);
}
@Override
public boolean getNeedClientAuth() {
return mBase.getNeedClientAuth();
}
@Override
public void setWantClientAuth(boolean want) {
mBase.setWantClientAuth(want);
}
@Override
public boolean getWantClientAuth() {
return mBase.getWantClientAuth();
}
@Override
public void setEnableSessionCreation(boolean flag) {
mBase.setEnableSessionCreation(flag);
}
@Override
public boolean getEnableSessionCreation() {
return mBase.getEnableSessionCreation();
}
@Override
public SSLParameters getSSLParameters() {
return mBase.getSSLParameters();
}
@Override
public void setSSLParameters(SSLParameters params) {
mBase.setSSLParameters(params);
}
@Override
public String toString() {
return mBase.toString();
}
@Override
public void connect(SocketAddress endpoint) throws IOException {
mBase.connect(endpoint);
}
@Override
public void connect(SocketAddress endpoint, int timeout) throws IOException {
mBase.connect(endpoint, timeout);
}
@Override
public void bind(SocketAddress bindpoint) throws IOException {
mBase.bind(bindpoint);
}
@Override
public InetAddress getInetAddress() {
return mBase.getInetAddress();
}
@Override
public InetAddress getLocalAddress() {
return mBase.getLocalAddress();
}
@Override
public int getPort() {
return mBase.getPort();
}
@Override
public int getLocalPort() {
return mBase.getLocalPort();
}
@Override
public SocketAddress getRemoteSocketAddress() {
return mBase.getRemoteSocketAddress();
}
@Override
public SocketAddress getLocalSocketAddress() {
return mBase.getLocalSocketAddress();
}
@Override
public SocketChannel getChannel() {
return mBase.getChannel();
}
@Override
public InputStream getInputStream() throws IOException {
return mBase.getInputStream();
}
@Override
public OutputStream getOutputStream() throws IOException {
return mBase.getOutputStream();
}
@Override
public void setTcpNoDelay(boolean on) throws SocketException {
mBase.setTcpNoDelay(on);
}
@Override
public boolean getTcpNoDelay() throws SocketException {
return mBase.getTcpNoDelay();
}
@Override
public void setSoLinger(boolean on, int linger) throws SocketException {
mBase.setSoLinger(on, linger);
}
@Override
public int getSoLinger() throws SocketException {
return mBase.getSoLinger();
}
@Override
public void sendUrgentData(int data) throws IOException {
mBase.sendUrgentData(data);
}
@Override
public void setOOBInline(boolean on) throws SocketException {
mBase.setOOBInline(on);
}
@Override
public boolean getOOBInline() throws SocketException {
return mBase.getOOBInline();
}
@Override
public void setSoTimeout(int timeout) throws SocketException {
mBase.setSoTimeout(timeout);
}
@Override
public int getSoTimeout() throws SocketException {
return mBase.getSoTimeout();
}
@Override
public void setSendBufferSize(int size) throws SocketException {
mBase.setSendBufferSize(size);
}
@Override
public int getSendBufferSize() throws SocketException {
return mBase.getSendBufferSize();
}
@Override
public void setReceiveBufferSize(int size) throws SocketException {
mBase.setReceiveBufferSize(size);
}
@Override
public int getReceiveBufferSize() throws SocketException {
return mBase.getReceiveBufferSize();
}
@Override
public void setKeepAlive(boolean on) throws SocketException {
mBase.setKeepAlive(on);
}
@Override
public boolean getKeepAlive() throws SocketException {
return mBase.getKeepAlive();
}
@Override
public void setTrafficClass(int tc) throws SocketException {
mBase.setTrafficClass(tc);
}
@Override
public int getTrafficClass() throws SocketException {
return mBase.getTrafficClass();
}
@Override
public void setReuseAddress(boolean on) throws SocketException {
mBase.setReuseAddress(on);
}
@Override
public boolean getReuseAddress() throws SocketException {
return mBase.getReuseAddress();
}
@Override
public void close() throws IOException {
mBase.close();
}
@Override
public void shutdownInput() throws IOException {
mBase.shutdownInput();
}
@Override
public void shutdownOutput() throws IOException {
mBase.shutdownOutput();
}
@Override
public boolean isConnected() {
return mBase.isConnected();
}
@Override
public boolean isBound() {
return mBase.isBound();
}
@Override
public boolean isClosed() {
return mBase.isClosed();
}
@Override
public boolean isInputShutdown() {
return mBase.isInputShutdown();
}
@Override
public boolean isOutputShutdown() {
return mBase.isOutputShutdown();
}
@Override
public void setPerformancePreferences(int connectionTime, int latency, int bandwidth) {
mBase.setPerformancePreferences(connectionTime, latency, bandwidth);
}
}

View File

@@ -1,7 +1,10 @@
package com.topjohnwu.magisk.utils; package com.topjohnwu.magisk.utils;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
@@ -10,20 +13,36 @@ import com.topjohnwu.magisk.FileProvider;
import java.io.File; import java.io.File;
public class APKInstall { public class APKInstall {
public static Intent installIntent(Context c, File apk) {
Intent intent = new Intent(Intent.ACTION_INSTALL_PACKAGE);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
if (Build.VERSION.SDK_INT >= 24) {
intent.setData(FileProvider.getUriForFile(c, c.getPackageName() + ".provider", apk));
} else {
//noinspection ResultOfMethodCallIgnored SetWorldReadable
apk.setReadable(true, false);
intent.setData(Uri.fromFile(apk));
}
return intent;
}
public static void install(Context c, File apk) { public static void install(Context c, File apk) {
c.startActivity(installIntent(c, apk)); c.startActivity(installIntent(c, apk));
} }
public static Intent installIntent(Context c, File apk) { public static void registerInstallReceiver(Context c, BroadcastReceiver r) {
Intent install = new Intent(Intent.ACTION_INSTALL_PACKAGE); IntentFilter filter = new IntentFilter();
install.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
install.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); filter.addAction(Intent.ACTION_PACKAGE_ADDED);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { filter.addDataScheme("package");
install.setData(FileProvider.getUriForFile(c, c.getPackageName() + ".provider", apk)); c.getApplicationContext().registerReceiver(r, filter);
} else { }
apk.setReadable(true, false);
install.setData(Uri.fromFile(apk)); public static void installHideResult(Activity c, File apk) {
} Intent intent = installIntent(c, apk);
return install; intent.putExtra(Intent.EXTRA_RETURN_RESULT, true);
c.startActivityForResult(intent, 0); // Ignore result, use install receiver
} }
} }

View File

@@ -9,7 +9,11 @@ import dalvik.system.DexClassLoader;
public class DynamicClassLoader extends DexClassLoader { public class DynamicClassLoader extends DexClassLoader {
private ClassLoader base = Object.class.getClassLoader(); private static final ClassLoader base = Object.class.getClassLoader();
public DynamicClassLoader(File apk) {
super(apk.getPath(), apk.getParent(), null, base);
}
public DynamicClassLoader(File apk, ClassLoader parent) { public DynamicClassLoader(File apk, ClassLoader parent) {
super(apk.getPath(), apk.getParent(), null, parent); super(apk.getPath(), apk.getParent(), null, parent);
@@ -18,7 +22,7 @@ public class DynamicClassLoader extends DexClassLoader {
@Override @Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
// First check if already loaded // First check if already loaded
Class cls = findLoadedClass(name); Class<?> cls = findLoadedClass(name);
if (cls != null) if (cls != null)
return cls; return cls;

View File

@@ -3,21 +3,19 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
package="com.topjohnwu.magisk"> package="com.topjohnwu.magisk">
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="29" />
<application <application
android:name=".core.App"
android:extractNativeLibs="true"
android:icon="@drawable/ic_launcher" android:icon="@drawable/ic_launcher"
android:name="a.e" android:multiArch="true"
android:allowBackup="false"
tools:ignore="UnusedAttribute,GoogleAppIndexingWarning"> tools:ignore="UnusedAttribute,GoogleAppIndexingWarning">
<!-- Splash --> <!-- Splash -->
<activity <activity
android:name="a.c" android:name=".core.SplashActivity"
android:exported="true"
android:theme="@style/SplashTheme"> android:theme="@style/SplashTheme">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
@@ -30,48 +28,47 @@
</activity> </activity>
<!-- Main --> <!-- Main -->
<activity android:name="a.b" /> <activity android:name=".ui.MainActivity" />
<!-- Superuser --> <!-- Superuser -->
<activity <activity
android:name="a.m" android:name=".ui.surequest.SuRequestActivity"
android:directBootAware="true" android:directBootAware="true"
android:excludeFromRecents="true" android:excludeFromRecents="true"
android:exported="false" android:exported="false"
tools:ignore="AppLinkUrlError"> tools:ignore="AppLinkUrlError">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.VIEW"/> <action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT"/> <category android:name="android.intent.category.DEFAULT" />
</intent-filter> </intent-filter>
</activity> </activity>
<!-- Receiver --> <!-- Receiver -->
<receiver <receiver
android:name="a.h" android:name=".core.Receiver"
android:directBootAware="true"> android:directBootAware="true"
android:exported="false">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.REBOOT" />
<action android:name="android.intent.action.LOCALE_CHANGED" /> <action android:name="android.intent.action.LOCALE_CHANGED" />
<action android:name="android.intent.action.UID_REMOVED" />
</intent-filter> </intent-filter>
<intent-filter> <intent-filter>
<action android:name="android.intent.action.PACKAGE_REPLACED" /> <action android:name="android.intent.action.PACKAGE_REPLACED" />
<action android:name="android.intent.action.PACKAGE_FULLY_REMOVED" />
<data android:scheme="package" /> <data android:scheme="package" />
</intent-filter> </intent-filter>
</receiver> </receiver>
<!-- DownloadService --> <!-- DownloadService -->
<service android:name="a.j" /> <service android:name=".core.download.DownloadService" />
<!-- FileProvider --> <!-- FileProvider -->
<provider <provider
android:name="a.p" android:name=".core.Provider"
android:authorities="${applicationId}.provider" android:authorities="${applicationId}.provider"
android:directBootAware="true" android:directBootAware="true"
android:exported="false" android:exported="false"
android:grantUriPermissions="true"> android:grantUriPermissions="true" />
</provider>
<!-- Hardcode GMS version --> <!-- Hardcode GMS version -->
<meta-data <meta-data
@@ -82,16 +79,12 @@
<provider <provider
android:name="androidx.work.impl.WorkManagerInitializer" android:name="androidx.work.impl.WorkManagerInitializer"
android:authorities="${applicationId}.workmanager-init" android:authorities="${applicationId}.workmanager-init"
tools:node="remove" /> tools:node="remove"
tools:ignore="ExportedContentProvider" />
<!-- We don't invalidate Room --> <!-- We don't invalidate Room -->
<service <service
android:name="androidx.room.MultiInstanceInvalidationService" android:name="androidx.room.MultiInstanceInvalidationService"
tools:node="remove"/>
<!-- We don't use Device Credentials -->
<activity
android:name="androidx.biometric.DeviceCredentialHandlerActivity"
tools:node="remove" /> tools:node="remove" />
</application> </application>

View File

@@ -1,32 +0,0 @@
@file:JvmName("a")
package a
import com.topjohnwu.magisk.core.App
import com.topjohnwu.magisk.core.Provider
import com.topjohnwu.magisk.core.Receiver
import com.topjohnwu.magisk.core.SplashActivity
import com.topjohnwu.magisk.core.download.DownloadService
import com.topjohnwu.magisk.ui.MainActivity
import com.topjohnwu.magisk.ui.surequest.SuRequestActivity
import com.topjohnwu.signing.SignBoot
fun main(args: Array<String>) {
SignBoot.main(args)
}
class b : MainActivity()
class c : SplashActivity()
class e : App {
constructor() : super()
constructor(o: Any) : super(o)
}
class h : Receiver()
class j : DownloadService()
class m : SuRequestActivity()
class p : Provider()

View File

@@ -10,7 +10,6 @@ import androidx.appcompat.app.AppCompatDelegate
import androidx.core.content.res.use import androidx.core.content.res.use
import androidx.databinding.DataBindingUtil import androidx.databinding.DataBindingUtil
import androidx.databinding.ViewDataBinding import androidx.databinding.ViewDataBinding
import androidx.lifecycle.MutableLiveData
import androidx.navigation.NavController import androidx.navigation.NavController
import androidx.navigation.NavDirections import androidx.navigation.NavDirections
import androidx.navigation.fragment.NavHostFragment import androidx.navigation.fragment.NavHostFragment
@@ -28,7 +27,7 @@ abstract class BaseUIActivity<VM : BaseViewModel, Binding : ViewDataBinding> :
protected open val themeRes: Int = Theme.selected.themeRes protected open val themeRes: Int = Theme.selected.themeRes
private val navHostFragment by lazy { private val navHostFragment by lazy {
supportFragmentManager.findFragmentById(navHost) as? NavHostFragment supportFragmentManager.findFragmentById(navHostId) as? NavHostFragment
} }
private val topFragment get() = navHostFragment?.childFragmentManager?.fragments?.getOrNull(0) private val topFragment get() = navHostFragment?.childFragmentManager?.fragments?.getOrNull(0)
protected val currentFragment get() = topFragment as? BaseUIFragment<*, *> protected val currentFragment get() = topFragment as? BaseUIFragment<*, *>
@@ -36,7 +35,7 @@ abstract class BaseUIActivity<VM : BaseViewModel, Binding : ViewDataBinding> :
override val viewRoot: View get() = binding.root override val viewRoot: View get() = binding.root
open val navigation: NavController? get() = navHostFragment?.navController open val navigation: NavController? get() = navHostFragment?.navController
open val navHost: Int = 0 open val navHostId: Int = 0
open val snackbarView get() = binding.root open val snackbarView get() = binding.root
init { init {
@@ -58,34 +57,24 @@ abstract class BaseUIActivity<VM : BaseViewModel, Binding : ViewDataBinding> :
.use { it.getDrawable(0) } .use { it.getDrawable(0) }
.also { window.setBackgroundDrawable(it) } .also { window.setBackgroundDrawable(it) }
directionsDispatcher.observe(this) { window?.decorView?.let {
it?.navigate() it.systemUiVisibility = (it.systemUiVisibility
// we don't want the directions to be re-dispatched, so we preemptively set them to null or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
if (it != null) { or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
directionsDispatcher.value = null or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION)
}
} }
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
window?.decorView?.let { window?.decorView?.post {
it.systemUiVisibility = (it.systemUiVisibility // If navigation bar is short enough (gesture navigation enabled), make it transparent
or View.SYSTEM_UI_FLAG_LAYOUT_STABLE if (window.decorView.rootWindowInsets?.systemWindowInsetBottom ?: 0 < Resources.getSystem().displayMetrics.density * 40) {
or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN window.navigationBarColor = Color.TRANSPARENT
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
} window.navigationBarDividerColor = Color.TRANSPARENT
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
window?.decorView?.post { window.isNavigationBarContrastEnforced = false
// If navigation bar is short enough (gesture navigation enabled), make it transparent window.isStatusBarContrastEnforced = false
if (window.decorView.rootWindowInsets?.systemWindowInsetBottom ?: 0 < Resources.getSystem().displayMetrics.density * 40) {
window.navigationBarColor = Color.TRANSPARENT
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
window.navigationBarDividerColor = Color.TRANSPARENT
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
window.isNavigationBarContrastEnforced = false
window.isStatusBarContrastEnforced = false
}
} }
} }
} }
@@ -127,14 +116,4 @@ abstract class BaseUIActivity<VM : BaseViewModel, Binding : ViewDataBinding> :
fun NavDirections.navigate() { fun NavDirections.navigate() {
navigation?.navigate(this) navigation?.navigate(this)
} }
companion object {
private val directionsDispatcher = MutableLiveData<NavDirections?>()
fun postDirections(navDirections: NavDirections) =
directionsDispatcher.postValue(navDirections)
}
} }

View File

@@ -5,7 +5,6 @@ import android.view.KeyEvent
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.graphics.Insets
import androidx.databinding.DataBindingUtil import androidx.databinding.DataBindingUtil
import androidx.databinding.OnRebindCallback import androidx.databinding.OnRebindCallback
import androidx.databinding.ViewDataBinding import androidx.databinding.ViewDataBinding

View File

@@ -1,9 +1,7 @@
package com.topjohnwu.magisk.arch package com.topjohnwu.magisk.arch
import android.Manifest import android.Manifest
import android.os.Build
import androidx.annotation.CallSuper import androidx.annotation.CallSuper
import androidx.core.graphics.Insets
import androidx.databinding.Bindable import androidx.databinding.Bindable
import androidx.databinding.Observable import androidx.databinding.Observable
import androidx.databinding.PropertyChangeRegistry import androidx.databinding.PropertyChangeRegistry
@@ -15,16 +13,17 @@ import androidx.navigation.NavDirections
import com.topjohnwu.magisk.BR import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.Info import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.base.BaseActivity import com.topjohnwu.magisk.events.BackPressEvent
import com.topjohnwu.magisk.events.* import com.topjohnwu.magisk.events.NavigationEvent
import com.topjohnwu.magisk.events.PermissionEvent
import com.topjohnwu.magisk.events.SnackbarEvent
import com.topjohnwu.magisk.utils.ObservableHost import com.topjohnwu.magisk.utils.ObservableHost
import com.topjohnwu.magisk.utils.set import com.topjohnwu.magisk.utils.set
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import org.koin.core.KoinComponent
abstract class BaseViewModel( abstract class BaseViewModel(
initialState: State = State.LOADING initialState: State = State.LOADING
) : ViewModel(), ObservableHost, KoinComponent { ) : ViewModel(), ObservableHost {
override var callbacks: PropertyChangeRegistry? = null override var callbacks: PropertyChangeRegistry? = null
@@ -42,10 +41,6 @@ abstract class BaseViewModel(
val isConnected get() = Info.isConnected val isConnected get() = Info.isConnected
val viewEvents: LiveData<ViewEvent> get() = _viewEvents val viewEvents: LiveData<ViewEvent> get() = _viewEvents
@get:Bindable
var insets = Insets.NONE
set(value) = set(value, field, { field = it }, BR.insets)
var state= initialState var state= initialState
set(value) = set(value, field, { field = it }, BR.loading, BR.loaded, BR.loadFailed) set(value) = set(value, field, { field = it }, BR.loading, BR.loaded, BR.loadFailed)
@@ -78,10 +73,6 @@ abstract class BaseViewModel(
super.onCleared() super.onCleared()
} }
fun withView(action: BaseActivity.() -> Unit) {
ViewActionEvent(action).publish()
}
fun withPermission(permission: String, callback: (Boolean) -> Unit) { fun withPermission(permission: String, callback: (Boolean) -> Unit) {
PermissionEvent(permission, callback).publish() PermissionEvent(permission, callback).publish()
} }
@@ -107,7 +98,7 @@ abstract class BaseViewModel(
_viewEvents.postValue(this) _viewEvents.postValue(this)
} }
fun NavDirections.publish() { fun NavDirections.navigate() {
_viewEvents.postValue(NavigationEvent(this)) _viewEvents.postValue(NavigationEvent(this))
} }

View File

@@ -1,24 +1,23 @@
package com.topjohnwu.magisk.core package com.topjohnwu.magisk.core
import android.annotation.SuppressLint
import android.app.Activity import android.app.Activity
import android.app.Application import android.app.Application
import android.content.Context import android.content.Context
import android.content.res.Configuration import android.content.res.Configuration
import android.os.Bundle import android.os.Bundle
import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.app.AppCompatDelegate
import androidx.multidex.MultiDex
import androidx.work.WorkManager import androidx.work.WorkManager
import com.topjohnwu.magisk.BuildConfig
import com.topjohnwu.magisk.DynAPK import com.topjohnwu.magisk.DynAPK
import com.topjohnwu.magisk.core.utils.AppShellInit
import com.topjohnwu.magisk.core.utils.BusyBoxInit
import com.topjohnwu.magisk.core.utils.IODispatcherExecutor import com.topjohnwu.magisk.core.utils.IODispatcherExecutor
import com.topjohnwu.magisk.core.utils.RootInit
import com.topjohnwu.magisk.core.utils.updateConfig import com.topjohnwu.magisk.core.utils.updateConfig
import com.topjohnwu.magisk.di.koinModules import com.topjohnwu.magisk.di.ServiceLocator
import com.topjohnwu.magisk.ktx.unwrap import com.topjohnwu.magisk.ktx.unwrap
import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.Shell
import org.koin.android.ext.koin.androidContext
import org.koin.core.context.startKoin
import timber.log.Timber import timber.log.Timber
import java.io.File
import kotlin.system.exitProcess import kotlin.system.exitProcess
open class App() : Application() { open class App() : Application() {
@@ -31,7 +30,7 @@ open class App() : Application() {
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true) AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)
Shell.setDefaultBuilder(Shell.Builder.create() Shell.setDefaultBuilder(Shell.Builder.create()
.setFlags(Shell.FLAG_MOUNT_MASTER) .setFlags(Shell.FLAG_MOUNT_MASTER)
.setInitializers(RootInit::class.java) .setInitializers(BusyBoxInit::class.java, AppShellInit::class.java)
.setTimeout(2)) .setTimeout(2))
Shell.EXECUTOR = IODispatcherExecutor() Shell.EXECUTOR = IODispatcherExecutor()
@@ -44,10 +43,6 @@ open class App() : Application() {
} }
override fun attachBaseContext(base: Context) { override fun attachBaseContext(base: Context) {
// Basic setup
if (BuildConfig.DEBUG)
MultiDex.install(base)
// Some context magic // Some context magic
val app: Application val app: Application
val impl: Context val impl: Context
@@ -61,12 +56,14 @@ open class App() : Application() {
val wrapped = impl.wrap() val wrapped = impl.wrap()
super.attachBaseContext(wrapped) super.attachBaseContext(wrapped)
// Normal startup val info = base.applicationInfo
startKoin { val libDir = runCatching {
androidContext(wrapped) info.javaClass.getDeclaredField("secondaryNativeLibraryDir").get(info) as String?
modules(koinModules) }.getOrNull() ?: info.nativeLibraryDir
} Const.NATIVE_LIB_DIR = File(libDir)
ResMgr.init(impl)
ServiceLocator.context = wrapped
AssetHack.init(impl)
app.registerActivityLifecycleCallbacks(ForegroundTracker) app.registerActivityLifecycleCallbacks(ForegroundTracker)
WorkManager.initialize(impl.wrapJob(), androidx.work.Configuration.Builder().build()) WorkManager.initialize(impl.wrapJob(), androidx.work.Configuration.Builder().build())
} }
@@ -83,6 +80,7 @@ open class App() : Application() {
} }
} }
@SuppressLint("StaticFieldLeak")
object ForegroundTracker : Application.ActivityLifecycleCallbacks { object ForegroundTracker : Application.ActivityLifecycleCallbacks {
@Volatile @Volatile

View File

@@ -1,20 +1,16 @@
package com.topjohnwu.magisk.core package com.topjohnwu.magisk.core
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Context
import android.content.SharedPreferences import android.content.SharedPreferences
import android.util.Xml import android.util.Xml
import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.app.AppCompatDelegate
import androidx.core.content.edit import androidx.core.content.edit
import com.topjohnwu.magisk.BuildConfig import com.topjohnwu.magisk.BuildConfig
import com.topjohnwu.magisk.core.magiskdb.SettingsDao
import com.topjohnwu.magisk.core.magiskdb.StringDao
import com.topjohnwu.magisk.core.utils.BiometricHelper import com.topjohnwu.magisk.core.utils.BiometricHelper
import com.topjohnwu.magisk.core.utils.refreshLocale import com.topjohnwu.magisk.core.utils.refreshLocale
import com.topjohnwu.magisk.data.preference.PreferenceModel import com.topjohnwu.magisk.data.preference.PreferenceModel
import com.topjohnwu.magisk.data.repository.DBConfig import com.topjohnwu.magisk.data.repository.DBConfig
import com.topjohnwu.magisk.di.Protected import com.topjohnwu.magisk.di.ServiceLocator
import com.topjohnwu.magisk.ktx.inject
import com.topjohnwu.magisk.ui.theme.Theme import com.topjohnwu.magisk.ui.theme.Theme
import org.xmlpull.v1.XmlPullParser import org.xmlpull.v1.XmlPullParser
import java.io.File import java.io.File
@@ -22,9 +18,9 @@ import java.io.InputStream
object Config : PreferenceModel, DBConfig { object Config : PreferenceModel, DBConfig {
override val stringDao: StringDao by inject() override val stringDB get() = ServiceLocator.stringDB
override val settingsDao: SettingsDao by inject() override val settingsDB get() = ServiceLocator.settingsDB
override val context: Context by inject(Protected) override val context get() = ServiceLocator.deContext
@get:SuppressLint("ApplySharedPref") @get:SuppressLint("ApplySharedPref")
val prefsFile: File get() { val prefsFile: File get() {

View File

@@ -1,41 +1,42 @@
package com.topjohnwu.magisk.core package com.topjohnwu.magisk.core
import android.os.Build
import android.os.Process import android.os.Process
import com.topjohnwu.magisk.BuildConfig
import java.io.File
@Suppress("DEPRECATION")
object Const { object Const {
val CPU_ABI: String = Build.SUPPORTED_ABIS[0]
val CPU_ABI_32: String = Build.SUPPORTED_32_BIT_ABIS.firstOrNull() ?: CPU_ABI
// Paths // Paths
lateinit var MAGISKTMP: String lateinit var MAGISKTMP: String
lateinit var NATIVE_LIB_DIR: File
val MAGISK_PATH get() = "$MAGISKTMP/modules" val MAGISK_PATH get() = "$MAGISKTMP/modules"
const val TMP_FOLDER_PATH = "/dev/tmp" const val TMPDIR = "/dev/tmp"
const val MAGISK_LOG = "/cache/magisk.log" const val MAGISK_LOG = "/cache/magisk.log"
// Versions // Versions
const val SNET_EXT_VER = 15 const val SNET_EXT_VER = 17
const val SNET_REVISION = "18ab78817087c337ae0edd1ecac38aec49217880" const val SNET_REVISION = "23.0"
const val BOOTCTL_REVISION = "18ab78817087c337ae0edd1ecac38aec49217880" const val BOOTCTL_REVISION = "22.0"
// Misc // Misc
val USER_ID = Process.myUid() / 100000 val USER_ID = Process.myUid() / 100000
object Version { object Version {
const val MIN_VERSION = "v19.0" const val MIN_VERSION = "v20.4"
const val MIN_VERCODE = 19000 const val MIN_VERCODE = 20400
fun atLeast_20_2() = Info.env.magiskVersionCode >= 20200 || isCanary()
fun atLeast_20_4() = Info.env.magiskVersionCode >= 20400 || isCanary()
fun atLeast_21_0() = Info.env.magiskVersionCode >= 21000 || isCanary() fun atLeast_21_0() = Info.env.magiskVersionCode >= 21000 || isCanary()
fun atLeast_21_2() = Info.env.magiskVersionCode >= 21200 || isCanary() fun atLeast_21_2() = Info.env.magiskVersionCode >= 21200 || isCanary()
fun isCanary() = Info.env.magiskVersionCode % 100 != 0 fun isCanary() = Info.env.magiskVersionCode % 100 != 0
} }
object ID { object ID {
const val FETCH_ZIP = 2
const val SELECT_FILE = 3
const val MAX_ACTIVITY_RESULT = 10
// notifications // notifications
const val MAGISK_UPDATE_NOTIFICATION_ID = 4
const val APK_UPDATE_NOTIFICATION_ID = 5 const val APK_UPDATE_NOTIFICATION_ID = 5
const val UPDATE_NOTIFICATION_CHANNEL = "update" const val UPDATE_NOTIFICATION_CHANNEL = "update"
const val PROGRESS_NOTIFICATION_CHANNEL = "progress" const val PROGRESS_NOTIFICATION_CHANNEL = "progress"
@@ -46,9 +47,12 @@ object Const {
const val PATREON_URL = "https://www.patreon.com/topjohnwu" const val PATREON_URL = "https://www.patreon.com/topjohnwu"
const val SOURCE_CODE_URL = "https://github.com/topjohnwu/Magisk" const val SOURCE_CODE_URL = "https://github.com/topjohnwu/Magisk"
val CHANGELOG_URL = if (BuildConfig.VERSION_CODE % 100 != 0) Info.remote.magisk.note
else "https://topjohnwu.github.io/Magisk/releases/${BuildConfig.VERSION_CODE}.md"
const val GITHUB_RAW_URL = "https://raw.githubusercontent.com/" const val GITHUB_RAW_URL = "https://raw.githubusercontent.com/"
const val GITHUB_API_URL = "https://api.github.com/" const val GITHUB_API_URL = "https://api.github.com/"
const val GITHUB_PAGE_URL = "https://topjohnwu.github.io/magisk_files/" const val GITHUB_PAGE_URL = "https://topjohnwu.github.io/magisk-files/"
const val JS_DELIVR_URL = "https://cdn.jsdelivr.net/gh/" const val JS_DELIVR_URL = "https://cdn.jsdelivr.net/gh/"
const val OFFICIAL_REPO = "https://magisk-modules-repo.github.io/submission/modules.json" const val OFFICIAL_REPO = "https://magisk-modules-repo.github.io/submission/modules.json"
} }

View File

@@ -14,44 +14,38 @@ import android.content.Intent
import android.content.res.AssetManager import android.content.res.AssetManager
import android.content.res.Configuration import android.content.res.Configuration
import android.content.res.Resources import android.content.res.Resources
import android.util.DisplayMetrics
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import com.topjohnwu.magisk.DynAPK import com.topjohnwu.magisk.DynAPK
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.download.DownloadService
import com.topjohnwu.magisk.core.utils.refreshLocale import com.topjohnwu.magisk.core.utils.refreshLocale
import com.topjohnwu.magisk.core.utils.updateConfig import com.topjohnwu.magisk.core.utils.updateConfig
import com.topjohnwu.magisk.ktx.forceGetDeclaredField
import com.topjohnwu.magisk.ui.MainActivity
import com.topjohnwu.magisk.ui.surequest.SuRequestActivity
fun AssetManager.addAssetPath(path: String) { fun AssetManager.addAssetPath(path: String) {
DynAPK.addAssetPath(this, path) DynAPK.addAssetPath(this, path)
} }
fun Context.wrap(global: Boolean = true): Context = fun Context.wrap(inject: Boolean = false): Context =
if (global) GlobalResContext(this) else ResContext(this) if (inject) ReInjectedContext(this) else InjectedContext(this)
fun Context.wrapJob(): Context = object : GlobalResContext(this) { fun Context.wrapJob(): Context = object : InjectedContext(this) {
override fun getApplicationContext(): Context { override fun getApplicationContext() = this
return this
}
@SuppressLint("NewApi") @SuppressLint("NewApi")
override fun getSystemService(name: String): Any? { override fun getSystemService(name: String): Any? {
return if (!isRunningAsStub) super.getSystemService(name) else return super.getSystemService(name).let {
when (name) { when {
Context.JOB_SCHEDULER_SERVICE -> !isRunningAsStub -> it
JobSchedulerWrapper(super.getSystemService(name) as JobScheduler) name == JOB_SCHEDULER_SERVICE -> JobSchedulerWrapper(it as JobScheduler)
else -> super.getSystemService(name) else -> it
} }
}
} }
} }
fun Class<*>.cmp(pkg: String): ComponentName { fun Class<*>.cmp(pkg: String) =
val name = ClassMap[this].name ComponentName(pkg, Info.stub?.classToComponent?.get(name) ?: name)
return ComponentName(pkg, Info.stub?.classToComponent?.get(name) ?: name)
}
inline fun <reified T> Activity.redirect() = Intent(intent) inline fun <reified T> Activity.redirect() = Intent(intent)
.setComponent(T::class.java.cmp(packageName)) .setComponent(T::class.java.cmp(packageName))
@@ -59,34 +53,27 @@ inline fun <reified T> Activity.redirect() = Intent(intent)
inline fun <reified T> Context.intent() = Intent().setComponent(T::class.java.cmp(packageName)) inline fun <reified T> Context.intent() = Intent().setComponent(T::class.java.cmp(packageName))
private open class GlobalResContext(base: Context) : ContextWrapper(base) { private open class InjectedContext(base: Context) : ContextWrapper(base) {
open val mRes: Resources get() = ResMgr.resource open val res: Resources get() = AssetHack.resource
override fun getAssets(): AssetManager = res.assets
override fun getResources(): Resources { override fun getResources() = res
return mRes override fun getClassLoader() = javaClass.classLoader!!
}
override fun getClassLoader(): ClassLoader {
return javaClass.classLoader!!
}
override fun createConfigurationContext(config: Configuration): Context { override fun createConfigurationContext(config: Configuration): Context {
return ResContext(super.createConfigurationContext(config)) return super.createConfigurationContext(config).wrap(true)
} }
} }
private class ResContext(base: Context) : GlobalResContext(base) { private class ReInjectedContext(base: Context) : InjectedContext(base) {
override val mRes by lazy { base.resources.patch() } override val res by lazy { base.resources.patch() }
private fun Resources.patch(): Resources { private fun Resources.patch(): Resources {
updateConfig() updateConfig()
if (isRunningAsStub) if (isRunningAsStub)
assets.addAssetPath(ResMgr.apk) assets.addAssetPath(AssetHack.apk)
return this return this
} }
} }
object ResMgr { object AssetHack {
lateinit var resource: Resources lateinit var resource: Resources
lateinit var apk: String lateinit var apk: String
@@ -101,62 +88,39 @@ object ResMgr {
apk = context.packageResourcePath apk = context.packageResourcePath
} }
} }
fun newResource(): Resources {
val asset = AssetManager::class.java.newInstance()
asset.addAssetPath(apk)
val config = Configuration(resource.configuration)
val metrics = DisplayMetrics()
metrics.setTo(resource.displayMetrics)
return Resources(asset, metrics, config)
}
} }
@RequiresApi(28) @RequiresApi(28)
private class JobSchedulerWrapper(private val base: JobScheduler) : JobScheduler() { private class JobSchedulerWrapper(private val base: JobScheduler) : JobScheduler() {
override fun schedule(job: JobInfo) = base.schedule(job.patch())
override fun schedule(job: JobInfo): Int { override fun enqueue(job: JobInfo, work: JobWorkItem) = base.enqueue(job.patch(), work)
return base.schedule(job.patch()) override fun cancel(jobId: Int) = base.cancel(jobId)
} override fun cancelAll() = base.cancelAll()
override fun getAllPendingJobs(): List<JobInfo> = base.allPendingJobs
override fun enqueue(job: JobInfo, work: JobWorkItem): Int { override fun getPendingJob(jobId: Int) = base.getPendingJob(jobId)
return base.enqueue(job.patch(), work)
}
override fun cancel(jobId: Int) {
base.cancel(jobId)
}
override fun cancelAll() {
base.cancelAll()
}
override fun getAllPendingJobs(): List<JobInfo> {
return base.allPendingJobs
}
override fun getPendingJob(jobId: Int): JobInfo? {
return base.getPendingJob(jobId)
}
private fun JobInfo.patch(): JobInfo { private fun JobInfo.patch(): JobInfo {
// We need to swap out the service of JobInfo // Swap out the service of JobInfo
val name = service.className val component = service.run {
val component = ComponentName( ComponentName(packageName,
service.packageName, Info.stub?.classToComponent?.get(className) ?: className)
Info.stubChk.classToComponent[name] ?: name }
) javaClass.getDeclaredField("service").apply {
isAccessible = true
}.set(this, component)
javaClass.forceGetDeclaredField("service")?.set(this, component)
return this return this
} }
} }
private object ClassMap {
private val map = mapOf(
App::class.java to a.e::class.java,
MainActivity::class.java to a.b::class.java,
SplashActivity::class.java to a.c::class.java,
Receiver::class.java to a.h::class.java,
DownloadService::class.java to a.j::class.java,
SuRequestActivity::class.java to a.m::class.java
)
operator fun get(c: Class<*>) = map.getOrElse(c) { c }
}
// Keep a reference to these resources to prevent it from // Keep a reference to these resources to prevent it from
// being removed when running "remove unused resources" // being removed when running "remove unused resources"
val shouldKeepResources = listOf( val shouldKeepResources = listOf(
@@ -165,7 +129,6 @@ val shouldKeepResources = listOf(
R.string.invalid_update_channel, R.string.invalid_update_channel,
R.string.update_available, R.string.update_available,
R.string.safetynet_api_error, R.string.safetynet_api_error,
R.raw.changelog,
R.drawable.ic_device, R.drawable.ic_device,
R.drawable.ic_hide_select_md2, R.drawable.ic_hide_select_md2,
R.drawable.ic_more, R.drawable.ic_more,

View File

@@ -1,45 +1,49 @@
package com.topjohnwu.magisk.core package com.topjohnwu.magisk.core
import android.os.Build
import androidx.databinding.ObservableBoolean import androidx.databinding.ObservableBoolean
import com.topjohnwu.magisk.DynAPK import com.topjohnwu.magisk.DynAPK
import com.topjohnwu.magisk.core.model.UpdateInfo import com.topjohnwu.magisk.core.model.UpdateInfo
import com.topjohnwu.magisk.core.utils.net.NetworkObserver import com.topjohnwu.magisk.core.utils.net.NetworkObserver
import com.topjohnwu.magisk.ktx.get import com.topjohnwu.magisk.data.repository.NetworkService
import com.topjohnwu.magisk.utils.CachedValue import com.topjohnwu.magisk.di.AppContext
import com.topjohnwu.magisk.ktx.getProperty
import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.Shell
import com.topjohnwu.superuser.ShellUtils.fastCmd import com.topjohnwu.superuser.ShellUtils.fastCmd
import com.topjohnwu.superuser.internal.UiThreadHandler import com.topjohnwu.superuser.internal.UiThreadHandler
import java.io.FileInputStream import java.io.File
import java.io.IOException import java.io.IOException
import java.util.*
val isRunningAsStub get() = Info.stub != null val isRunningAsStub get() = Info.stub != null
object Info { object Info {
val envRef = CachedValue { loadState() }
@JvmStatic val env by envRef
var stub: DynAPK.Data? = null var stub: DynAPK.Data? = null
val stubChk: DynAPK.Data
get() = stub as DynAPK.Data
var remote = UpdateInfo() val EMPTY_REMOTE = UpdateInfo()
var remote = EMPTY_REMOTE
suspend fun getRemote(svc: NetworkService): UpdateInfo? {
return if (remote === EMPTY_REMOTE) {
svc.fetchUpdate()?.apply { remote = this }
} else remote
}
// Device state // Device state
var crypto = "" @JvmStatic val env by lazy { loadState() }
@JvmStatic var isSAR = false @JvmField var isSAR = false
@JvmStatic var isAB = false @JvmField var isAB = false
@JvmField val isVirtualAB = getProperty("ro.virtual_ab.enabled", "false") == "true"
@JvmStatic val isFDE get() = crypto == "block" @JvmStatic val isFDE get() = crypto == "block"
@JvmStatic var ramdisk = false @JvmField var ramdisk = false
@JvmStatic var hasGMS = true @JvmField var hasGMS = true
@JvmStatic var isPixel = false @JvmField val isPixel = Build.BRAND == "google"
@JvmStatic val cryptoText get() = crypto.capitalize(Locale.US) @JvmField val isEmulator = getProperty("ro.kernel.qemu", "0") == "1"
var crypto = ""
var noDataExec = false
val isConnected by lazy { val isConnected by lazy {
ObservableBoolean(false).also { field -> ObservableBoolean(false).also { field ->
NetworkObserver.observe(get()) { NetworkObserver.observe(AppContext) {
UiThreadHandler.run { field.set(it) } UiThreadHandler.run { field.set(it) }
} }
} }
@@ -47,14 +51,12 @@ object Info {
val isNewReboot by lazy { val isNewReboot by lazy {
try { try {
FileInputStream("/proc/sys/kernel/random/boot_id").bufferedReader().use { val id = File("/proc/sys/kernel/random/boot_id").readText()
val id = it.readLine() if (id != Config.bootId) {
if (id != Config.bootId) { Config.bootId = id
Config.bootId = id true
true } else {
} else { false
false
}
} }
} catch (e: IOException) { } catch (e: IOException) {
false false
@@ -64,7 +66,7 @@ object Info {
private fun loadState() = Env( private fun loadState() = Env(
fastCmd("magisk -v").split(":".toRegex())[0], fastCmd("magisk -v").split(":".toRegex())[0],
runCatching { fastCmd("magisk -V").toInt() }.getOrDefault(-1), runCatching { fastCmd("magisk -V").toInt() }.getOrDefault(-1),
Shell.su("magiskhide --status").exec().isSuccess Shell.su("magiskhide status").exec().isSuccess
) )
class Env( class Env(
@@ -73,8 +75,8 @@ object Info {
hide: Boolean = false hide: Boolean = false
) { ) {
val magiskHide get() = Config.magiskHide val magiskHide get() = Config.magiskHide
val magiskVersionCode = when (code) { val magiskVersionCode = when {
in Int.MIN_VALUE..Const.Version.MIN_VERCODE -> -1 code < Const.Version.MIN_VERCODE -> -1
else -> if (Shell.rootAccess()) code else -1 else -> if (Shell.rootAccess()) code else -1
} }
val isUnsupported = code > 0 && code < Const.Version.MIN_VERCODE val isUnsupported = code > 0 && code < Const.Version.MIN_VERCODE

View File

@@ -1,44 +1,46 @@
package com.topjohnwu.magisk.core package com.topjohnwu.magisk.core
import android.annotation.SuppressLint
import android.content.ContextWrapper import android.content.ContextWrapper
import android.content.Intent import android.content.Intent
import com.topjohnwu.magisk.core.base.BaseReceiver import com.topjohnwu.magisk.core.base.BaseReceiver
import com.topjohnwu.magisk.core.magiskdb.PolicyDao import com.topjohnwu.magisk.di.ServiceLocator
import com.topjohnwu.magisk.core.su.SuCallbackHandler
import com.topjohnwu.magisk.view.Shortcuts import com.topjohnwu.magisk.view.Shortcuts
import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.Shell
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.koin.core.inject
open class Receiver : BaseReceiver() { open class Receiver : BaseReceiver() {
private val policyDB: PolicyDao by inject() private val policyDB get() = ServiceLocator.policyDB
private fun getPkg(intent: Intent): String { @SuppressLint("InlinedApi")
return intent.data?.encodedSchemeSpecificPart.orEmpty() private fun getPkg(intent: Intent): String? {
val pkg = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME)
return pkg ?: intent.data?.schemeSpecificPart
}
private fun getUid(intent: Intent): Int? {
val uid = intent.getIntExtra(Intent.EXTRA_UID, -1)
return if (uid == -1) null else uid
} }
override fun onReceive(context: ContextWrapper, intent: Intent?) { override fun onReceive(context: ContextWrapper, intent: Intent?) {
intent ?: return intent ?: return
fun rmPolicy(pkg: String) = GlobalScope.launch { fun rmPolicy(uid: Int) = GlobalScope.launch {
policyDB.delete(pkg) policyDB.delete(uid)
} }
when (intent.action ?: return) { when (intent.action ?: return) {
Intent.ACTION_REBOOT -> {
SuCallbackHandler(context, intent.getStringExtra("action"), intent.extras)
}
Intent.ACTION_PACKAGE_REPLACED -> { Intent.ACTION_PACKAGE_REPLACED -> {
// This will only work pre-O // This will only work pre-O
if (Config.suReAuth) if (Config.suReAuth)
rmPolicy(getPkg(intent)) getUid(intent)?.let { rmPolicy(it) }
} }
Intent.ACTION_PACKAGE_FULLY_REMOVED -> { Intent.ACTION_UID_REMOVED -> {
val pkg = getPkg(intent) getUid(intent)?.let { rmPolicy(it) }
rmPolicy(pkg) getPkg(intent)?.let { Shell.su("magiskhide rm $it").submit() }
Shell.su("magiskhide --rm $pkg").submit()
} }
Intent.ACTION_LOCALE_CHANGED -> Shortcuts.setupDynamic(context) Intent.ACTION_LOCALE_CHANGED -> Shortcuts.setupDynamic(context)
} }

View File

@@ -1,32 +1,29 @@
package com.topjohnwu.magisk.core package com.topjohnwu.magisk.core
import android.app.Activity import android.content.Intent
import android.content.Context import android.net.Uri
import android.os.Bundle import android.os.Bundle
import com.topjohnwu.magisk.BuildConfig.APPLICATION_ID import com.topjohnwu.magisk.BuildConfig.APPLICATION_ID
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.data.repository.NetworkService import com.topjohnwu.magisk.core.base.BaseActivity
import com.topjohnwu.magisk.ktx.get import com.topjohnwu.magisk.core.tasks.HideAPK
import com.topjohnwu.magisk.di.ServiceLocator
import com.topjohnwu.magisk.ui.MainActivity import com.topjohnwu.magisk.ui.MainActivity
import com.topjohnwu.magisk.view.MagiskDialog
import com.topjohnwu.magisk.view.Notifications import com.topjohnwu.magisk.view.Notifications
import com.topjohnwu.magisk.view.Shortcuts import com.topjohnwu.magisk.view.Shortcuts
import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.Shell
import kotlinx.coroutines.Dispatchers import java.util.concurrent.CountDownLatch
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
open class SplashActivity : Activity() { open class SplashActivity : BaseActivity() {
override fun attachBaseContext(base: Context) { private val latch = CountDownLatch(1)
super.attachBaseContext(base.wrap())
}
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
setTheme(R.style.SplashTheme) setTheme(R.style.SplashTheme)
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
GlobalScope.launch(Dispatchers.IO) { // Pre-initialize root shell
initAndStart() Shell.getShell(null) { initAndStart() }
}
} }
private fun handleRepackage(pkg: String?) { private fun handleRepackage(pkg: String?) {
@@ -40,13 +37,26 @@ open class SplashActivity : Activity() {
if (Config.suManager.isNotEmpty()) if (Config.suManager.isNotEmpty())
Config.suManager = "" Config.suManager = ""
pkg ?: return pkg ?: return
Shell.su("(pm uninstall $pkg)& >/dev/null 2>&1").exec() if (!Shell.su("(pm uninstall $pkg)& >/dev/null 2>&1").exec().isSuccess)
uninstallApp(pkg)
} }
} }
private fun initAndStart() { private fun initAndStart() {
// Pre-initialize root shell if (isRunningAsStub && !Shell.rootAccess()) {
Shell.getShell() runOnUiThread {
MagiskDialog(this)
.applyTitle(R.string.unsupport_nonroot_stub_title)
.applyMessage(R.string.unsupport_nonroot_stub_msg)
.applyButton(MagiskDialog.ButtonType.POSITIVE) {
titleRes = R.string.install
onClick { HideAPK.restore(this@SplashActivity) }
}
.cancellable(false)
.reveal()
}
return
}
val prevPkg = intent.getStringExtra(Const.Key.PREV_PKG) val prevPkg = intent.getStringExtra(Const.Key.PREV_PKG)
@@ -57,14 +67,24 @@ open class SplashActivity : Activity() {
Shortcuts.setupDynamic(this) Shortcuts.setupDynamic(this)
// Pre-fetch network services // Pre-fetch network services
get<NetworkService>() ServiceLocator.networkService
DONE = true DONE = true
startActivity(redirect<MainActivity>())
redirect<MainActivity>().also { startActivity(it) }
finish() finish()
} }
@Suppress("DEPRECATION")
private fun uninstallApp(pkg: String) {
val uri = Uri.Builder().scheme("package").opaquePart(pkg).build()
val intent = Intent(Intent.ACTION_UNINSTALL_PACKAGE, uri)
intent.putExtra(Intent.EXTRA_RETURN_RESULT, true)
startActivityForResult(intent) { _, _ ->
latch.countDown()
}
latch.await()
}
companion object { companion object {
var DONE = false var DONE = false
} }

View File

@@ -1,37 +1,28 @@
package com.topjohnwu.magisk.core package com.topjohnwu.magisk.core
import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import androidx.work.* import androidx.work.*
import com.topjohnwu.magisk.BuildConfig import com.topjohnwu.magisk.BuildConfig
import com.topjohnwu.magisk.data.repository.NetworkService import com.topjohnwu.magisk.di.ServiceLocator
import com.topjohnwu.magisk.view.Notifications import com.topjohnwu.magisk.view.Notifications
import com.topjohnwu.superuser.Shell
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.koin.core.KoinComponent
import org.koin.core.inject
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
class UpdateCheckService(context: Context, workerParams: WorkerParameters) class UpdateCheckService(context: Context, workerParams: WorkerParameters)
: CoroutineWorker(context, workerParams), KoinComponent { : CoroutineWorker(context, workerParams) {
private val svc: NetworkService by inject() private val svc get() = ServiceLocator.networkService
override suspend fun doWork(): Result { override suspend fun doWork(): Result {
// Make sure shell initializer was ran return svc.fetchUpdate()?.run {
withContext(Dispatchers.IO) { if (Info.env.isActive && BuildConfig.VERSION_CODE < magisk.versionCode)
Shell.getShell()
}
return svc.fetchUpdate()?.let {
if (BuildConfig.VERSION_CODE < it.app.versionCode)
Notifications.managerUpdate(applicationContext) Notifications.managerUpdate(applicationContext)
else if (Info.env.isActive && Info.env.magiskVersionCode < it.magisk.versionCode)
Notifications.magiskUpdate(applicationContext)
Result.success() Result.success()
} ?: Result.failure() } ?: Result.failure()
} }
companion object { companion object {
@SuppressLint("NewApi")
fun schedule(context: Context) { fun schedule(context: Context) {
if (Config.checkUpdate) { if (Config.checkUpdate) {
val constraints = Constraints.Builder() val constraints = Constraints.Builder()

View File

@@ -7,6 +7,7 @@ import android.content.Intent
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.content.res.Configuration import android.content.res.Configuration
import android.os.Build import android.os.Build
import android.os.Bundle
import android.widget.Toast import android.widget.Toast
import androidx.annotation.CallSuper import androidx.annotation.CallSuper
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
@@ -14,9 +15,9 @@ import androidx.collection.SparseArrayCompat
import androidx.core.app.ActivityCompat import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.utils.currentLocale import com.topjohnwu.magisk.core.utils.currentLocale
import com.topjohnwu.magisk.core.wrap import com.topjohnwu.magisk.core.wrap
import com.topjohnwu.magisk.ktx.reflectField
import com.topjohnwu.magisk.ktx.set import com.topjohnwu.magisk.ktx.set
import com.topjohnwu.magisk.utils.Utils import com.topjohnwu.magisk.utils.Utils
import kotlin.random.Random import kotlin.random.Random
@@ -26,6 +27,13 @@ typealias ActivityResultCallback = BaseActivity.(Int, Intent?) -> Unit
abstract class BaseActivity : AppCompatActivity() { abstract class BaseActivity : AppCompatActivity() {
private val resultCallbacks by lazy { SparseArrayCompat<ActivityResultCallback>() } private val resultCallbacks by lazy { SparseArrayCompat<ActivityResultCallback>() }
private val newRequestCode: Int get() {
var requestCode: Int
do {
requestCode = Random.nextInt(0, 1 shl 15)
} while (resultCallbacks.containsKey(requestCode))
return requestCode
}
override fun applyOverrideConfiguration(config: Configuration?) { override fun applyOverrideConfiguration(config: Configuration?) {
// Force applying our preferred local // Force applying our preferred local
@@ -34,7 +42,16 @@ abstract class BaseActivity : AppCompatActivity() {
} }
override fun attachBaseContext(base: Context) { override fun attachBaseContext(base: Context) {
super.attachBaseContext(base.wrap(false)) super.attachBaseContext(base.wrap(true))
}
override fun onCreate(savedInstanceState: Bundle?) {
// 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)
super.onCreate(savedInstanceState)
} }
fun withPermission(permission: String, builder: PermissionRequestBuilder.() -> Unit) { fun withPermission(permission: String, builder: PermissionRequestBuilder.() -> Unit) {
@@ -49,10 +66,7 @@ abstract class BaseActivity : AppCompatActivity() {
if (ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED) { if (ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED) {
request.onSuccess() request.onSuccess()
} else { } else {
var requestCode: Int val requestCode = newRequestCode
do {
requestCode = Random.nextInt(Const.ID.MAX_ACTIVITY_RESULT + 1, 1 shl 15)
} while (resultCallbacks.containsKey(requestCode))
resultCallbacks[requestCode] = { result, _ -> resultCallbacks[requestCode] = { result, _ ->
if (result > 0) if (result > 0)
request.onSuccess() request.onSuccess()
@@ -69,6 +83,7 @@ abstract class BaseActivity : AppCompatActivity() {
override fun onRequestPermissionsResult( override fun onRequestPermissionsResult(
requestCode: Int, permissions: Array<out String>, grantResults: IntArray) { requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
var success = true var success = true
for (res in grantResults) { for (res in grantResults) {
if (res != PackageManager.PERMISSION_GRANTED) { if (res != PackageManager.PERMISSION_GRANTED) {
@@ -92,7 +107,8 @@ abstract class BaseActivity : AppCompatActivity() {
} }
} }
fun startActivityForResult(intent: Intent, requestCode: Int, callback: ActivityResultCallback) { fun startActivityForResult(intent: Intent, callback: ActivityResultCallback) {
val requestCode = newRequestCode
resultCallbacks[requestCode] = callback resultCallbacks[requestCode] = callback
try { try {
startActivityForResult(intent, requestCode) startActivityForResult(intent, requestCode)

View File

@@ -5,9 +5,8 @@ import android.content.Context
import android.content.ContextWrapper import android.content.ContextWrapper
import android.content.Intent import android.content.Intent
import com.topjohnwu.magisk.core.wrap import com.topjohnwu.magisk.core.wrap
import org.koin.core.KoinComponent
abstract class BaseReceiver : BroadcastReceiver(), KoinComponent { abstract class BaseReceiver : BroadcastReceiver() {
final override fun onReceive(context: Context, intent: Intent?) { final override fun onReceive(context: Context, intent: Intent?) {
onReceive(context.wrap() as ContextWrapper, intent) onReceive(context.wrap() as ContextWrapper, intent)

View File

@@ -3,9 +3,8 @@ package com.topjohnwu.magisk.core.base
import android.app.Service import android.app.Service
import android.content.Context import android.content.Context
import com.topjohnwu.magisk.core.wrap import com.topjohnwu.magisk.core.wrap
import org.koin.core.KoinComponent
abstract class BaseService : Service(), KoinComponent { abstract class BaseService : Service() {
override fun attachBaseContext(base: Context) { override fun attachBaseContext(base: Context) {
super.attachBaseContext(base.wrap()) super.attachBaseContext(base.wrap())
} }

View File

@@ -1,31 +0,0 @@
package com.topjohnwu.magisk.core.download
import android.net.Uri
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
sealed class Action : Parcelable {
sealed class Flash : Action() {
@Parcelize
object Primary : Flash()
@Parcelize
object Secondary : Flash()
}
@Parcelize
object Download : Action()
@Parcelize
object Uninstall : Action()
@Parcelize
object EnvFix : Action()
@Parcelize
data class Patch(val fileUri: Uri) : Action()
}

View File

@@ -8,19 +8,14 @@ import androidx.lifecycle.MutableLiveData
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.ForegroundTracker import com.topjohnwu.magisk.core.ForegroundTracker
import com.topjohnwu.magisk.core.base.BaseService import com.topjohnwu.magisk.core.base.BaseService
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.checkSum
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream
import com.topjohnwu.magisk.core.utils.ProgressInputStream import com.topjohnwu.magisk.core.utils.ProgressInputStream
import com.topjohnwu.magisk.data.repository.NetworkService import com.topjohnwu.magisk.di.ServiceLocator
import com.topjohnwu.magisk.ktx.withStreams
import com.topjohnwu.magisk.view.Notifications import com.topjohnwu.magisk.view.Notifications
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancel import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import okhttp3.ResponseBody import okhttp3.ResponseBody
import org.koin.android.ext.android.inject
import org.koin.core.KoinComponent
import timber.log.Timber import timber.log.Timber
import java.io.IOException import java.io.IOException
import java.io.InputStream import java.io.InputStream
@@ -28,13 +23,13 @@ import java.util.*
import kotlin.collections.HashMap import kotlin.collections.HashMap
import kotlin.random.Random.Default.nextInt import kotlin.random.Random.Default.nextInt
abstract class BaseDownloader : BaseService(), KoinComponent { abstract class BaseDownloader : BaseService() {
private val hasNotifications get() = notifications.isNotEmpty() private val hasNotifications get() = notifications.isNotEmpty()
private val notifications = Collections.synchronizedMap(HashMap<Int, Notification.Builder>()) private val notifications = Collections.synchronizedMap(HashMap<Int, Notification.Builder>())
private val coroutineScope = CoroutineScope(Dispatchers.IO) private val coroutineScope = CoroutineScope(Dispatchers.IO)
val service: NetworkService by inject() val service get() = ServiceLocator.networkService
// -- Service overrides // -- Service overrides
@@ -69,18 +64,11 @@ abstract class BaseDownloader : BaseService(), KoinComponent {
// -- Download logic // -- Download logic
private suspend fun Subject.startDownload() { private suspend fun Subject.startDownload() {
val skip = this is Subject.Magisk && file.checkSum("MD5", magisk.md5) val stream = service.fetchFile(url).toProgressStream(this)
if (!skip) { when (this) {
val stream = service.fetchFile(url).toProgressStream(this) is Subject.Module -> // Download and process on-the-fly
when (this) { stream.toModule(file, service.fetchInstaller().byteStream())
is Subject.Module -> // Download and process on-the-fly is Subject.Manager -> handleAPK(this, stream)
stream.toModule(file, service.fetchInstaller().byteStream())
else -> {
withStreams(stream, file.outputStream()) { it, out -> it.copyTo(out) }
if (this is Subject.Manager)
handleAPK(this)
}
}
} }
val newId = notifyFinish(this) val newId = notifyFinish(this)
if (ForegroundTracker.hasForeground) if (ForegroundTracker.hasForeground)
@@ -184,10 +172,10 @@ abstract class BaseDownloader : BaseService(), KoinComponent {
// --- // ---
companion object : KoinComponent { companion object {
const val ACTION_KEY = "download_action" const val ACTION_KEY = "download_action"
private val progressBroadcast = MutableLiveData<Pair<Float, Subject>>() private val progressBroadcast = MutableLiveData<Pair<Float, Subject>?>()
fun observeProgress(owner: LifecycleOwner, callback: (Float, Subject) -> Unit) { fun observeProgress(owner: LifecycleOwner, callback: (Float, Subject) -> Unit) {
progressBroadcast.value = null progressBroadcast.value = null

View File

@@ -1,46 +1,38 @@
package com.topjohnwu.magisk.core.download package com.topjohnwu.magisk.core.download
import android.annotation.SuppressLint
import android.app.Notification import android.app.Notification
import android.app.PendingIntent import android.app.PendingIntent
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Build import android.os.Build
import androidx.core.net.toFile import androidx.core.net.toFile
import com.topjohnwu.magisk.core.download.Action.* import com.topjohnwu.magisk.arch.BaseUIActivity
import com.topjohnwu.magisk.core.download.Action.Flash.Secondary import com.topjohnwu.magisk.core.ForegroundTracker
import com.topjohnwu.magisk.core.download.Subject.* import com.topjohnwu.magisk.core.download.Action.Flash
import com.topjohnwu.magisk.core.download.Subject.Manager
import com.topjohnwu.magisk.core.download.Subject.Module
import com.topjohnwu.magisk.core.intent import com.topjohnwu.magisk.core.intent
import com.topjohnwu.magisk.core.tasks.EnvFixTask
import com.topjohnwu.magisk.ui.flash.FlashFragment import com.topjohnwu.magisk.ui.flash.FlashFragment
import com.topjohnwu.magisk.utils.APKInstall import com.topjohnwu.magisk.utils.APKInstall
import com.topjohnwu.superuser.internal.UiThreadHandler
import kotlin.random.Random.Default.nextInt import kotlin.random.Random.Default.nextInt
@SuppressLint("Registered")
open class DownloadService : BaseDownloader() { open class DownloadService : BaseDownloader() {
private val context get() = this private val context get() = this
override suspend fun onFinish(subject: Subject, id: Int) = when (subject) { override suspend fun onFinish(subject: Subject, id: Int) = when (subject) {
is Magisk -> subject.onFinish(id)
is Module -> subject.onFinish(id) is Module -> subject.onFinish(id)
is Manager -> subject.onFinish(id) is Manager -> subject.onFinish(id)
} }
private suspend fun Magisk.onFinish(id: Int) = when (val action = action) {
Uninstall -> FlashFragment.uninstall(file, id)
EnvFix -> {
remove(id)
EnvFixTask(file).exec()
Unit
}
is Patch -> FlashFragment.patch(file, action.fileUri, id)
is Flash -> FlashFragment.flash(file, action is Secondary, id)
else -> Unit
}
private fun Module.onFinish(id: Int) = when (action) { private fun Module.onFinish(id: Int) = when (action) {
is Flash -> FlashFragment.install(file, id) Flash -> {
UiThreadHandler.run {
(ForegroundTracker.foreground as? BaseUIActivity<*, *>)
?.navigation?.navigate(FlashFragment.install(file, id))
}
}
else -> Unit else -> Unit
} }
@@ -53,22 +45,13 @@ open class DownloadService : BaseDownloader() {
override fun Notification.Builder.setIntent(subject: Subject) override fun Notification.Builder.setIntent(subject: Subject)
= when (subject) { = when (subject) {
is Magisk -> setIntent(subject)
is Module -> setIntent(subject) is Module -> setIntent(subject)
is Manager -> setIntent(subject) is Manager -> setIntent(subject)
} }
private fun Notification.Builder.setIntent(subject: Magisk)
= when (val action = subject.action) {
Uninstall -> setContentIntent(FlashFragment.uninstallIntent(context, subject.file))
is Flash -> setContentIntent(FlashFragment.flashIntent(context, subject.file, action is Secondary))
is Patch -> setContentIntent(FlashFragment.patchIntent(context, subject.file, action.fileUri))
else -> setContentIntent(Intent())
}
private fun Notification.Builder.setIntent(subject: Module) private fun Notification.Builder.setIntent(subject: Module)
= when (subject.action) { = when (subject.action) {
is Flash -> setContentIntent(FlashFragment.installIntent(context, subject.file)) Flash -> setContentIntent(FlashFragment.installIntent(context, subject.file))
else -> setContentIntent(Intent()) else -> setContentIntent(Intent())
} }

View File

@@ -2,19 +2,22 @@ package com.topjohnwu.magisk.core.download
import android.content.Context import android.content.Context
import androidx.core.net.toFile import androidx.core.net.toFile
import com.topjohnwu.magisk.BuildConfig
import com.topjohnwu.magisk.DynAPK import com.topjohnwu.magisk.DynAPK
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.Info import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.isRunningAsStub import com.topjohnwu.magisk.core.isRunningAsStub
import com.topjohnwu.magisk.core.tasks.HideAPK import com.topjohnwu.magisk.core.tasks.HideAPK
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream
import com.topjohnwu.magisk.ktx.relaunchApp import com.topjohnwu.magisk.ktx.relaunchApp
import com.topjohnwu.magisk.ktx.withStreams
import com.topjohnwu.magisk.ktx.writeTo import com.topjohnwu.magisk.ktx.writeTo
import java.io.File import java.io.File
import java.io.InputStream
import java.io.OutputStream
private fun Context.patch(apk: File) { private fun Context.patch(apk: File) {
val patched = File(apk.parent, "patched.apk") val patched = File(apk.parent, "patched.apk")
HideAPK.patch(this, apk.path, patched.path, packageName, applicationInfo.nonLocalizedLabel) HideAPK.patch(this, apk, patched, packageName, applicationInfo.nonLocalizedLabel)
apk.delete() apk.delete()
patched.renameTo(apk) patched.renameTo(apk)
} }
@@ -22,30 +25,51 @@ private fun Context.patch(apk: File) {
private fun BaseDownloader.notifyHide(id: Int) { private fun BaseDownloader.notifyHide(id: Int) {
update(id) { update(id) {
it.setProgress(0, 0, true) it.setProgress(0, 0, true)
.setContentTitle(getString(R.string.hide_manager_title)) .setContentTitle(getString(R.string.hide_app_title))
.setContentText("") .setContentText("")
} }
} }
suspend fun BaseDownloader.handleAPK(subject: Subject.Manager) { private class DupOutputStream(
val apk = subject.file.toFile() private val o1: OutputStream,
val id = subject.notifyID() private val o2: OutputStream
) : OutputStream() {
override fun write(b: Int) {
o1.write(b)
o2.write(b)
}
override fun write(b: ByteArray?, off: Int, len: Int) {
o1.write(b, off, len)
o2.write(b, off, len)
}
override fun close() {
o1.close()
o2.close()
}
}
suspend fun BaseDownloader.handleAPK(subject: Subject.Manager, stream: InputStream) {
fun write(output: OutputStream) {
val ext = subject.externalFile.outputStream()
val o = DupOutputStream(ext, output)
withStreams(stream, o) { src, out -> src.copyTo(out) }
}
if (isRunningAsStub) { if (isRunningAsStub) {
// Move to upgrade location val apk = subject.file.toFile()
apk.copyTo(DynAPK.update(this), overwrite = true) val id = subject.notifyID()
apk.delete() write(DynAPK.update(this).outputStream())
if (Info.stubChk.version < subject.stub.versionCode) { if (Info.stub!!.version < subject.stub.versionCode) {
notifyHide(id)
// Also upgrade stub // Also upgrade stub
service.fetchFile(subject.stub.link).byteStream().use { it.writeTo(apk) } notifyHide(id)
service.fetchFile(subject.stub.link).byteStream().writeTo(apk)
patch(apk) patch(apk)
} else { } else {
// Simply relaunch the app // Simply relaunch the app
stopSelf() stopSelf()
relaunchApp(this) relaunchApp(this)
} }
} else if (packageName != BuildConfig.APPLICATION_ID) { } else {
notifyHide(id) write(subject.file.outputStream())
patch(apk)
} }
} }

View File

@@ -1,8 +1,9 @@
package com.topjohnwu.magisk.core.download package com.topjohnwu.magisk.core.download
import android.net.Uri import android.net.Uri
import com.topjohnwu.magisk.ktx.withStreams
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream
import com.topjohnwu.magisk.ktx.forEach
import com.topjohnwu.magisk.ktx.withStreams
import java.io.InputStream import java.io.InputStream
import java.util.zip.ZipEntry import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream import java.util.zip.ZipInputStream
@@ -25,8 +26,7 @@ fun InputStream.toModule(file: Uri, installer: InputStream) {
zout.write("#MAGISK\n".toByteArray(charset("UTF-8"))) zout.write("#MAGISK\n".toByteArray(charset("UTF-8")))
var off = -1 var off = -1
var entry: ZipEntry? = zin.nextEntry zin.forEach { entry ->
while (entry != null) {
if (off < 0) { if (off < 0) {
off = entry.name.indexOf('/') + 1 off = entry.name.indexOf('/') + 1
} }
@@ -38,8 +38,6 @@ fun InputStream.toModule(file: Uri, installer: InputStream) {
zin.copyTo(zout) zin.copyTo(zout)
} }
} }
entry = zin.nextEntry
} }
} }
} }

View File

@@ -1,21 +1,19 @@
package com.topjohnwu.magisk.core.download package com.topjohnwu.magisk.core.download
import android.content.Context
import android.net.Uri import android.net.Uri
import android.os.Parcelable import android.os.Parcelable
import androidx.core.net.toUri import androidx.core.net.toUri
import com.topjohnwu.magisk.core.Info import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.model.MagiskJson import com.topjohnwu.magisk.core.model.MagiskJson
import com.topjohnwu.magisk.core.model.ManagerJson
import com.topjohnwu.magisk.core.model.StubJson import com.topjohnwu.magisk.core.model.StubJson
import com.topjohnwu.magisk.core.model.module.OnlineModule import com.topjohnwu.magisk.core.model.module.OnlineModule
import com.topjohnwu.magisk.core.utils.MediaStoreUtils import com.topjohnwu.magisk.core.utils.MediaStoreUtils
import com.topjohnwu.magisk.di.AppContext
import com.topjohnwu.magisk.ktx.cachedFile import com.topjohnwu.magisk.ktx.cachedFile
import com.topjohnwu.magisk.ktx.get
import kotlinx.parcelize.IgnoredOnParcel import kotlinx.parcelize.IgnoredOnParcel
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
private fun cachedFile(name: String) = get<Context>().cachedFile(name).apply { delete() }.toUri() private fun cachedFile(name: String) = AppContext.cachedFile(name).apply { delete() }.toUri()
sealed class Subject : Parcelable { sealed class Subject : Parcelable {
@@ -40,70 +38,26 @@ sealed class Subject : Parcelable {
@Parcelize @Parcelize
class Manager( class Manager(
private val app: ManagerJson = Info.remote.app, private val json: MagiskJson = Info.remote.magisk,
val stub: StubJson = Info.remote.stub val stub: StubJson = Info.remote.stub
) : Subject() { ) : Subject() {
override val action get() = Action.Download override val action get() = Action.Download
override val title: String get() = "MagiskManager-${app.version}(${app.versionCode})" override val title: String get() = "Magisk-${json.version}(${json.versionCode})"
override val url: String get() = app.link override val url: String get() = json.link
@IgnoredOnParcel @IgnoredOnParcel
override val file by lazy { override val file by lazy {
cachedFile("manager.apk") cachedFile("manager.apk")
} }
val externalFile get() = MediaStoreUtils.getFile("$title.apk").uri
} }
}
abstract class Magisk : Subject() {
sealed class Action : Parcelable {
val magisk: MagiskJson = Info.remote.magisk @Parcelize
object Flash : Action()
@Parcelize
private class Internal( @Parcelize
override val action: Action object Download : Action()
) : Magisk() {
override val url: String get() = magisk.link
override val title: String get() = "Magisk-${magisk.version}(${magisk.versionCode})"
@IgnoredOnParcel
override val file by lazy {
cachedFile("magisk.zip")
}
}
@Parcelize
private class Uninstall : Magisk() {
override val action get() = Action.Uninstall
override val url: String get() = Info.remote.uninstaller.link
override val title: String get() = "uninstall.zip"
@IgnoredOnParcel
override val file by lazy {
cachedFile(title)
}
}
@Parcelize
private class Download : Magisk() {
override val action get() = Action.Download
override val url: String get() = magisk.link
override val title: String get() = "Magisk-${magisk.version}(${magisk.versionCode}).zip"
@IgnoredOnParcel
override val file by lazy {
MediaStoreUtils.getFile(title).uri
}
}
companion object {
operator fun invoke(config: Action) = when (config) {
Action.Download -> Download()
Action.Uninstall -> Uninstall()
Action.EnvFix, is Action.Flash, is Action.Patch -> Internal(config)
}
}
}
} }

View File

@@ -1,11 +1,11 @@
package com.topjohnwu.magisk.core.magiskdb package com.topjohnwu.magisk.core.magiskdb
import android.content.Context
import android.content.pm.PackageManager import android.content.pm.PackageManager
import com.topjohnwu.magisk.core.Const import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.model.su.SuPolicy import com.topjohnwu.magisk.core.model.su.SuPolicy
import com.topjohnwu.magisk.core.model.su.toMap import com.topjohnwu.magisk.core.model.su.toMap
import com.topjohnwu.magisk.core.model.su.toPolicy import com.topjohnwu.magisk.core.model.su.toPolicy
import com.topjohnwu.magisk.di.AppContext
import com.topjohnwu.magisk.ktx.now import com.topjohnwu.magisk.ktx.now
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -13,9 +13,7 @@ import timber.log.Timber
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
class PolicyDao( class PolicyDao : BaseDao() {
private val context: Context
) : BaseDao() {
override val table: String = Table.POLICY override val table: String = Table.POLICY
@@ -31,12 +29,6 @@ class PolicyDao(
} }
}.commit() }.commit()
suspend fun delete(packageName: String) = buildQuery<Delete> {
condition {
equals("package_name", packageName)
}
}.commit()
suspend fun delete(uid: Int) = buildQuery<Delete> { suspend fun delete(uid: Int) = buildQuery<Delete> {
condition { condition {
equals("uid", uid) equals("uid", uid)
@@ -62,7 +54,7 @@ class PolicyDao(
} }
private fun Map<String, String>.toPolicyOrNull(): SuPolicy? { private fun Map<String, String>.toPolicyOrNull(): SuPolicy? {
return runCatching { toPolicy(context.packageManager) }.getOrElse { return runCatching { toPolicy(AppContext.packageManager) }.getOrElse {
Timber.e(it) Timber.e(it)
if (it is PackageManager.NameNotFoundException) { if (it is PackageManager.NameNotFoundException) {
val uid = getOrElse("uid") { null } ?: return null val uid = getOrElse("uid") { null } ?: return null

View File

@@ -6,29 +6,13 @@ import kotlinx.parcelize.Parcelize
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class UpdateInfo( data class UpdateInfo(
val app: ManagerJson = ManagerJson(),
val uninstaller: UninstallerJson = UninstallerJson(),
val magisk: MagiskJson = MagiskJson(), val magisk: MagiskJson = MagiskJson(),
val stub: StubJson = StubJson() val stub: StubJson = StubJson()
) )
@JsonClass(generateAdapter = true)
data class UninstallerJson(
val link: String = ""
)
@JsonClass(generateAdapter = true)
data class MagiskJson(
val version: String = "",
val versionCode: Int = -1,
val link: String = "",
val note: String = "",
val md5: String = ""
)
@Parcelize @Parcelize
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class ManagerJson( data class MagiskJson(
val version: String = "", val version: String = "",
val versionCode: Int = -1, val versionCode: Int = -1,
val link: String = "", val link: String = "",

View File

@@ -4,8 +4,7 @@ import android.os.Parcelable
import androidx.room.Entity import androidx.room.Entity
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
import com.topjohnwu.magisk.core.model.ModuleJson import com.topjohnwu.magisk.core.model.ModuleJson
import com.topjohnwu.magisk.data.repository.NetworkService import com.topjohnwu.magisk.di.ServiceLocator
import com.topjohnwu.magisk.ktx.get
import com.topjohnwu.magisk.ktx.legalFilename import com.topjohnwu.magisk.ktx.legalFilename
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import java.text.DateFormat import java.text.DateFormat
@@ -26,7 +25,7 @@ data class OnlineModule(
val notes_url: String val notes_url: String
) : Module(), Parcelable { ) : Module(), Parcelable {
private val svc: NetworkService get() = get() private val svc get() = ServiceLocator.networkService
constructor(info: ModuleJson) : this( constructor(info: ModuleJson) : this(
id = info.id, id = info.id,

View File

@@ -1,11 +1,13 @@
@file:SuppressLint("InlinedApi")
package com.topjohnwu.magisk.core.model.su package com.topjohnwu.magisk.core.model.su
import android.annotation.SuppressLint
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import com.topjohnwu.magisk.core.model.su.SuPolicy.Companion.INTERACTIVE import com.topjohnwu.magisk.core.model.su.SuPolicy.Companion.INTERACTIVE
import com.topjohnwu.magisk.ktx.getLabel import com.topjohnwu.magisk.ktx.getLabel
data class SuPolicy( data class SuPolicy(
var uid: Int, var uid: Int,
val packageName: String, val packageName: String,
@@ -38,7 +40,7 @@ fun SuPolicy.toMap() = mapOf(
fun Map<String, String>.toPolicy(pm: PackageManager): SuPolicy { fun Map<String, String>.toPolicy(pm: PackageManager): SuPolicy {
val uid = get("uid")?.toIntOrNull() ?: -1 val uid = get("uid")?.toIntOrNull() ?: -1
val packageName = get("package_name").orEmpty() val packageName = get("package_name").orEmpty()
val info = pm.getApplicationInfo(packageName, PackageManager.GET_UNINSTALLED_PACKAGES) val info = pm.getApplicationInfo(packageName, PackageManager.MATCH_UNINSTALLED_PACKAGES)
if (info.uid != uid) if (info.uid != uid)
throw PackageManager.NameNotFoundException() throw PackageManager.NameNotFoundException()
@@ -59,7 +61,7 @@ fun Map<String, String>.toPolicy(pm: PackageManager): SuPolicy {
fun Int.toPolicy(pm: PackageManager, policy: Int = INTERACTIVE): SuPolicy { fun Int.toPolicy(pm: PackageManager, policy: Int = INTERACTIVE): SuPolicy {
val pkg = pm.getPackagesForUid(this)?.firstOrNull() val pkg = pm.getPackagesForUid(this)?.firstOrNull()
?: throw PackageManager.NameNotFoundException() ?: throw PackageManager.NameNotFoundException()
val info = pm.getApplicationInfo(pkg, PackageManager.GET_UNINSTALLED_PACKAGES) val info = pm.getApplicationInfo(pkg, PackageManager.MATCH_UNINSTALLED_PACKAGES)
return SuPolicy( return SuPolicy(
uid = info.uid, uid = info.uid,
packageName = pkg, packageName = pkg,

View File

@@ -13,8 +13,7 @@ import com.topjohnwu.magisk.core.intent
import com.topjohnwu.magisk.core.model.su.SuPolicy import com.topjohnwu.magisk.core.model.su.SuPolicy
import com.topjohnwu.magisk.core.model.su.toLog import com.topjohnwu.magisk.core.model.su.toLog
import com.topjohnwu.magisk.core.model.su.toPolicy import com.topjohnwu.magisk.core.model.su.toPolicy
import com.topjohnwu.magisk.data.repository.LogRepository import com.topjohnwu.magisk.di.ServiceLocator
import com.topjohnwu.magisk.ktx.get
import com.topjohnwu.magisk.ktx.startActivity import com.topjohnwu.magisk.ktx.startActivity
import com.topjohnwu.magisk.ktx.startActivityWithRoot import com.topjohnwu.magisk.ktx.startActivityWithRoot
import com.topjohnwu.magisk.ui.surequest.SuRequestActivity import com.topjohnwu.magisk.ui.surequest.SuRequestActivity
@@ -104,9 +103,8 @@ object SuCallbackHandler {
command = command command = command
) )
val logRepo = get<LogRepository>()
GlobalScope.launch { GlobalScope.launch {
logRepo.insert(log) ServiceLocator.logRepo.insert(log)
} }
} }

View File

@@ -1,19 +1,16 @@
package com.topjohnwu.magisk.core.tasks package com.topjohnwu.magisk.core.tasks
import android.content.Context
import android.net.Uri import android.net.Uri
import androidx.core.os.postDelayed import androidx.core.net.toFile
import com.topjohnwu.magisk.core.Const import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.displayName import com.topjohnwu.magisk.core.utils.MediaStoreUtils.displayName
import com.topjohnwu.magisk.core.utils.unzip
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.inputStream import com.topjohnwu.magisk.core.utils.MediaStoreUtils.inputStream
import com.topjohnwu.magisk.core.utils.unzip
import com.topjohnwu.magisk.di.AppContext
import com.topjohnwu.magisk.ktx.writeTo import com.topjohnwu.magisk.ktx.writeTo
import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.Shell
import com.topjohnwu.superuser.internal.UiThreadHandler
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.koin.core.KoinComponent
import org.koin.core.inject
import timber.log.Timber import timber.log.Timber
import java.io.File import java.io.File
import java.io.FileNotFoundException import java.io.FileNotFoundException
@@ -23,63 +20,51 @@ open class FlashZip(
private val mUri: Uri, private val mUri: Uri,
private val console: MutableList<String>, private val console: MutableList<String>,
private val logs: MutableList<String> private val logs: MutableList<String>
): KoinComponent { ) {
val context: Context by inject() private val installDir = File(AppContext.cacheDir, "flash")
private val installFolder = File(context.cacheDir, "flash").apply { private lateinit var zipFile: File
if (!exists()) mkdirs()
}
private val tmpFile: File = File(installFolder, "install.zip")
@Throws(IOException::class)
private fun unzipAndCheck(): Boolean {
val parentFile = tmpFile.parentFile ?: return false
tmpFile.unzip(parentFile, "META-INF/com/google/android", true)
val updaterScript = File(parentFile, "updater-script")
return Shell
.su("grep -q '#MAGISK' $updaterScript")
.exec()
.isSuccess
}
@Throws(IOException::class) @Throws(IOException::class)
private fun flash(): Boolean { private fun flash(): Boolean {
console.add("- Copying zip to temp directory") installDir.deleteRecursively()
installDir.mkdirs()
runCatching { zipFile = if (mUri.scheme == "file") {
mUri.inputStream().writeTo(tmpFile) mUri.toFile()
}.getOrElse { } else {
when (it) { File(installDir, "install.zip").also {
is FileNotFoundException -> console.add("! Invalid Uri") console.add("- Copying zip to temp directory")
is IOException -> console.add("! Cannot copy to cache") try {
mUri.inputStream().writeTo(it)
} catch (e: IOException) {
when (e) {
is FileNotFoundException -> console.add("! Invalid Uri")
else -> console.add("! Cannot copy to cache")
}
throw e
}
} }
throw it
} }
val isMagiskModule = runCatching { val isValid = runCatching {
unzipAndCheck() zipFile.unzip(installDir, "META-INF/com/google/android", true)
val script = File(installDir, "updater-script")
script.readText().contains("#MAGISK")
}.getOrElse { }.getOrElse {
console.add("! Unzip error") console.add("! Unzip error")
throw it throw it
} }
if (!isMagiskModule) { if (!isValid) {
console.add("! This zip is not a Magisk Module!") console.add("! This zip is not a Magisk module!")
return false return false
} }
console.add("- Installing ${mUri.displayName}") console.add("- Installing ${mUri.displayName}")
val parentFile = tmpFile.parent ?: return false return Shell.su("sh $installDir/update-binary dummy 1 \'$zipFile\'")
.to(console, logs).exec().isSuccess
return Shell
.su(
"cd $parentFile",
"BOOTMODE=true sh update-binary dummy 1 $tmpFile"
)
.to(console, logs)
.exec().isSuccess
} }
open suspend fun exec() = withContext(Dispatchers.IO) { open suspend fun exec() = withContext(Dispatchers.IO) {
@@ -94,25 +79,7 @@ open class FlashZip(
Timber.e(e) Timber.e(e)
false false
} finally { } finally {
Shell.su("cd /", "rm -rf ${tmpFile.parent} ${Const.TMP_FOLDER_PATH}").submit() Shell.su("cd /", "rm -rf $installDir ${Const.TMPDIR}").submit()
} }
} }
class Uninstall(
uri: Uri,
console: MutableList<String>,
log: MutableList<String>
) : FlashZip(uri, console, log) {
override suspend fun exec(): Boolean {
val success = super.exec()
if (success) {
UiThreadHandler.handler.postDelayed(3000) {
Shell.su("pm uninstall " + context.packageName).exec()
}
}
return success
}
}
} }

View File

@@ -1,44 +1,46 @@
package com.topjohnwu.magisk.core.tasks package com.topjohnwu.magisk.core.tasks
import android.app.ProgressDialog import android.app.Activity
import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Build.VERSION.SDK_INT
import android.widget.Toast import android.widget.Toast
import com.topjohnwu.magisk.BuildConfig.APPLICATION_ID import com.topjohnwu.magisk.BuildConfig.APPLICATION_ID
import com.topjohnwu.magisk.DynAPK import com.topjohnwu.magisk.DynAPK
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.* 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.utils.AXML import com.topjohnwu.magisk.core.utils.AXML
import com.topjohnwu.magisk.core.utils.Keygen import com.topjohnwu.magisk.core.utils.Keygen
import com.topjohnwu.magisk.data.repository.NetworkService import com.topjohnwu.magisk.di.ServiceLocator
import com.topjohnwu.magisk.ktx.inject
import com.topjohnwu.magisk.ktx.writeTo import com.topjohnwu.magisk.ktx.writeTo
import com.topjohnwu.magisk.utils.APKInstall
import com.topjohnwu.magisk.utils.Utils import com.topjohnwu.magisk.utils.Utils
import com.topjohnwu.signing.JarMap import com.topjohnwu.signing.JarMap
import com.topjohnwu.signing.SignApk import com.topjohnwu.signing.SignApk
import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.Shell
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import timber.log.Timber import timber.log.Timber
import java.io.File import java.io.File
import java.io.FileOutputStream import java.io.FileOutputStream
import java.io.IOException import java.io.IOException
import java.lang.ref.WeakReference
import java.security.SecureRandom import java.security.SecureRandom
object HideAPK { object HideAPK {
private const val ALPHA = "abcdefghijklmnopqrstuvwxyz" private const val ALPHA = "abcdefghijklmnopqrstuvwxyz"
private const val ALPHADOTS = "$ALPHA....." private const val ALPHADOTS = "$ALPHA....."
private const val APP_NAME = "Magisk Manager" private const val APP_NAME = "Magisk"
private const val ANDROID_MANIFEST = "AndroidManifest.xml" private const val ANDROID_MANIFEST = "AndroidManifest.xml"
// Some arbitrary limit // Some arbitrary limit
const val MAX_LABEL_LENGTH = 32 const val MAX_LABEL_LENGTH = 32
private val svc: NetworkService by inject() private val svc get() = ServiceLocator.networkService
private val Context.APK_URI get() = Provider.APK_URI(packageName) private val Context.APK_URI get() = Provider.APK_URI(packageName)
private val Context.PREFS_URI get() = Provider.PREFS_URI(packageName) private val Context.PREFS_URI get() = Provider.PREFS_URI(packageName)
@@ -49,7 +51,7 @@ object HideAPK {
var next: Char var next: Char
var prev = 0.toChar() var prev = 0.toChar()
for (i in 0 until len) { for (i in 0 until len) {
next = if (prev == '.' || prev == 0.toChar() || i == len - 1) { next = if (prev == '.' || i == 0 || i == len - 1) {
ALPHA[random.nextInt(ALPHA.length)] ALPHA[random.nextInt(ALPHA.length)]
} else { } else {
ALPHADOTS[random.nextInt(ALPHADOTS.length)] ALPHADOTS[random.nextInt(ALPHADOTS.length)]
@@ -59,19 +61,19 @@ object HideAPK {
} }
if (!builder.contains('.')) { if (!builder.contains('.')) {
// Pick a random index and set it as dot // Pick a random index and set it as dot
val idx = random.nextInt(len - 1) val idx = random.nextInt(len - 2)
builder[idx] = '.' builder[idx + 1] = '.'
} }
return builder.toString() return builder.toString()
} }
fun patch( fun patch(
context: Context, context: Context,
apk: String, out: String, apk: File, out: File,
pkg: String, label: CharSequence pkg: String, label: CharSequence
): Boolean { ): Boolean {
try { try {
val jar = JarMap.open(apk) val jar = JarMap.open(apk, true)
val je = jar.getJarEntry(ANDROID_MANIFEST) val je = jar.getJarEntry(ANDROID_MANIFEST)
val xml = AXML(jar.getRawData(je)) val xml = AXML(jar.getRawData(je))
@@ -90,103 +92,76 @@ object HideAPK {
return true return true
} }
private suspend fun patchAndHide(context: Context, label: String): Boolean { private class WaitPackageReceiver(
val dlStub = !isRunningAsStub && SDK_INT >= 28 && Const.Version.atLeast_20_2() private val pkg: String,
val src = if (dlStub) { activity: Activity
val stub = File(context.cacheDir, "stub.apk") ) : BroadcastReceiver() {
try {
svc.fetchFile(Info.remote.stub.link).byteStream().use {
it.writeTo(stub)
}
} catch (e: IOException) {
Timber.e(e)
return false
}
stub.path
} else {
context.packageCodePath
}
// Generate a new random package name and signature private val activity = WeakReference(activity)
val repack = File(context.cacheDir, "patched.apk")
val pkg = genPackageName()
Config.keyStoreRaw = ""
if (!patch(context, src, repack.path, pkg, label)) private fun launchApp(): Unit = activity.get()?.run {
return false val intent = packageManager.getLaunchIntentForPackage(pkg) ?: return
Config.suManager = if (pkg == APPLICATION_ID) "" else pkg
// Install the application
if (!Shell.su("adb_pm_install $repack").exec().isSuccess)
return false
context.apply {
val intent = packageManager.getLaunchIntentForPackage(pkg) ?: return false
Config.suManager = pkg
grantUriPermission(pkg, APK_URI, Intent.FLAG_GRANT_READ_URI_PERMISSION) grantUriPermission(pkg, APK_URI, Intent.FLAG_GRANT_READ_URI_PERMISSION)
grantUriPermission(pkg, PREFS_URI, Intent.FLAG_GRANT_READ_URI_PERMISSION) grantUriPermission(pkg, PREFS_URI, Intent.FLAG_GRANT_READ_URI_PERMISSION)
intent.putExtra(Const.Key.PREV_PKG, packageName) intent.putExtra(Const.Key.PREV_PKG, packageName)
startActivity(intent) startActivity(intent)
} finish()
} ?: Unit
return true override fun onReceive(context: Context, intent: Intent) {
} when (intent.action ?: return) {
Intent.ACTION_PACKAGE_REPLACED, Intent.ACTION_PACKAGE_ADDED -> {
@Suppress("DEPRECATION") val newPkg = intent.data?.encodedSchemeSpecificPart.orEmpty()
fun hide(context: Context, label: String) { if (newPkg == pkg) {
val dialog = ProgressDialog.show(context, context.getString(R.string.hide_manager_title), "", true) context.unregisterReceiver(this)
GlobalScope.launch { launchApp()
val result = withContext(Dispatchers.IO) {
patchAndHide(context, label)
}
if (!result) {
Utils.toast(R.string.hide_manager_fail_toast, Toast.LENGTH_LONG)
dialog.dismiss()
}
}
}
private suspend fun downloadAndRestore(context: Context): Boolean {
val apk = if (isRunningAsStub) {
DynAPK.current(context)
} else {
File(context.cacheDir, "manager.apk").also { apk ->
try {
svc.fetchFile(Info.remote.app.link).byteStream().use {
it.writeTo(apk)
} }
} catch (e: IOException) {
Timber.e(e)
return false
} }
} }
} }
if (!Shell.su("adb_pm_install $apk").exec().isSuccess) }
return false
context.apply { private suspend fun patchAndHide(activity: Activity, label: String): Boolean {
val intent = packageManager.getLaunchIntentForPackage(APPLICATION_ID) ?: return false val stub = File(activity.cacheDir, "stub.apk")
Config.suManager = "" try {
grantUriPermission(APPLICATION_ID, APK_URI, Intent.FLAG_GRANT_READ_URI_PERMISSION) svc.fetchFile(Info.remote.stub.link).byteStream().writeTo(stub)
grantUriPermission(APPLICATION_ID, PREFS_URI, Intent.FLAG_GRANT_READ_URI_PERMISSION) } catch (e: IOException) {
intent.putExtra(Const.Key.PREV_PKG, packageName) Timber.e(e)
startActivity(intent) return false
} }
// Generate a new random package name and signature
val repack = File(activity.cacheDir, "patched.apk")
val pkg = genPackageName()
Config.keyStoreRaw = ""
if (!patch(activity, stub, repack, pkg, label))
return false
// Install and auto launch app
APKInstall.registerInstallReceiver(activity, WaitPackageReceiver(pkg, activity))
if (!Shell.su("adb_pm_install $repack").exec().isSuccess)
APKInstall.installHideResult(activity, repack)
return true return true
} }
@Suppress("DEPRECATION") suspend fun hide(activity: Activity, label: String) {
fun restore(context: Context) { val result = withContext(Dispatchers.IO) {
val dialog = ProgressDialog.show(context, context.getString(R.string.restore_img_msg), "", true) patchAndHide(activity, label)
GlobalScope.launch { }
val result = withContext(Dispatchers.IO) { if (!result) {
downloadAndRestore(context) Utils.toast(R.string.failure, Toast.LENGTH_LONG)
} }
if (!result) { }
Utils.toast(R.string.restore_manager_fail_toast, Toast.LENGTH_LONG)
dialog.dismiss() fun restore(activity: Activity) {
} val apk = DynAPK.current(activity)
APKInstall.registerInstallReceiver(activity, WaitPackageReceiver(APPLICATION_ID, activity))
Shell.su("adb_pm_install $apk").submit {
if (!it.isSuccess)
APKInstall.installHideResult(activity, apk)
} }
} }
} }

View File

@@ -1,25 +1,21 @@
package com.topjohnwu.magisk.core.tasks package com.topjohnwu.magisk.core.tasks
import android.content.Context
import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Build import android.system.Os
import android.widget.Toast import android.widget.Toast
import androidx.annotation.WorkerThread import androidx.annotation.WorkerThread
import androidx.core.os.postDelayed import androidx.core.os.postDelayed
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import com.topjohnwu.magisk.BuildConfig import com.topjohnwu.magisk.BuildConfig
import com.topjohnwu.magisk.DynAPK
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.Config import com.topjohnwu.magisk.core.*
import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.utils.MediaStoreUtils import com.topjohnwu.magisk.core.utils.MediaStoreUtils
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.inputStream import com.topjohnwu.magisk.core.utils.MediaStoreUtils.inputStream
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream
import com.topjohnwu.magisk.data.repository.NetworkService import com.topjohnwu.magisk.di.ServiceLocator
import com.topjohnwu.magisk.di.Protected
import com.topjohnwu.magisk.events.dialog.EnvFixDialog
import com.topjohnwu.magisk.ktx.reboot import com.topjohnwu.magisk.ktx.reboot
import com.topjohnwu.magisk.ktx.withStreams import com.topjohnwu.magisk.ktx.withStreams
import com.topjohnwu.magisk.ktx.writeTo
import com.topjohnwu.magisk.utils.Utils import com.topjohnwu.magisk.utils.Utils
import com.topjohnwu.signing.SignBoot import com.topjohnwu.signing.SignBoot
import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.Shell
@@ -36,157 +32,161 @@ import org.kamranzafar.jtar.TarEntry
import org.kamranzafar.jtar.TarHeader import org.kamranzafar.jtar.TarHeader
import org.kamranzafar.jtar.TarInputStream import org.kamranzafar.jtar.TarInputStream
import org.kamranzafar.jtar.TarOutputStream import org.kamranzafar.jtar.TarOutputStream
import org.koin.core.KoinComponent
import org.koin.core.get
import org.koin.core.inject
import timber.log.Timber import timber.log.Timber
import java.io.* import java.io.*
import java.nio.ByteBuffer import java.nio.ByteBuffer
import java.security.SecureRandom import java.security.SecureRandom
import java.util.* import java.util.*
import java.util.zip.ZipEntry import java.util.zip.ZipFile
import java.util.zip.ZipInputStream
abstract class MagiskInstallImpl : KoinComponent { abstract class MagiskInstallImpl protected constructor(
protected val console: MutableList<String> = NOPList.getInstance(),
private val logs: MutableList<String> = NOPList.getInstance()
) {
protected lateinit var installDir: File protected var installDir = File("xxx")
private lateinit var srcBoot: String private lateinit var srcBoot: File
private lateinit var zipUri: Uri
protected val console: MutableList<String> private val shell = Shell.getShell()
private val logs: MutableList<String> private val service get() = ServiceLocator.networkService
private var tarOut: TarOutputStream? = null protected val context get() = ServiceLocator.deContext
private val useRootDir = shell.isRoot && Info.noDataExec
private val service: NetworkService by inject()
protected val context: Context by inject()
protected constructor() {
console = NOPList.getInstance()
logs = NOPList.getInstance()
}
protected constructor(zip: Uri, out: MutableList<String>, err: MutableList<String>) {
console = out
logs = err
zipUri = zip
installDir = File(get<Context>(Protected).filesDir.parent, "install")
"rm -rf $installDir".sh()
installDir.mkdirs()
}
private fun findImage(): Boolean { private fun findImage(): Boolean {
srcBoot = "find_boot_image; echo \"\$BOOTIMAGE\"".fsh() val bootPath = "find_boot_image; echo \"\$BOOTIMAGE\"".fsh()
if (srcBoot.isEmpty()) { if (bootPath.isEmpty()) {
console.add("! Unable to detect target image") console.add("! Unable to detect target image")
return false return false
} }
console.add("- Target image: $srcBoot") srcBoot = SuFile(bootPath)
console.add("- Target image: $bootPath")
return true return true
} }
private fun findSecondaryImage(): Boolean { private fun findSecondary(): Boolean {
val slot = "echo \$SLOT".fsh() val slot = "echo \$SLOT".fsh()
val target = if (slot == "_a") "_b" else "_a" val target = if (slot == "_a") "_b" else "_a"
console.add("- Target slot: $target") console.add("- Target slot: $target")
srcBoot = arrayOf( val bootPath = arrayOf(
"SLOT=$target", "SLOT=$target",
"find_boot_image", "find_boot_image",
"SLOT=$slot", "SLOT=$slot",
"echo \"\$BOOTIMAGE\"").fsh() "echo \"\$BOOTIMAGE\"").fsh()
if (srcBoot.isEmpty()) { if (bootPath.isEmpty()) {
console.add("! Unable to detect target image") console.add("! Unable to detect target image")
return false return false
} }
console.add("- Target image: $srcBoot") srcBoot = SuFile(bootPath)
console.add("- Target image: $bootPath")
return true return true
} }
@Suppress("DEPRECATION") private fun extractFiles(): Boolean {
private fun extractZip(): Boolean { console.add("- Device platform: ${Const.CPU_ABI}")
val arch = if (Build.VERSION.SDK_INT >= 21) { console.add("- Installing: ${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})")
val abis = listOf(*Build.SUPPORTED_ABIS)
if (abis.contains("x86")) "x86" else "arm"
} else {
if (Build.CPU_ABI == "x86") "x86" else "arm"
}
console.add("- Device platform: " + Build.CPU_ABI) installDir = File(context.filesDir.parent, "install")
console.add("- Magisk Manager: ${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})") installDir.deleteRecursively()
console.add("- Install target: ${Info.remote.magisk.version} (${Info.remote.magisk.versionCode})") installDir.mkdirs()
try { try {
ZipInputStream(zipUri.inputStream().buffered()).use { zi -> // Extract binaries
lateinit var ze: ZipEntry if (isRunningAsStub) {
while (zi.nextEntry?.let { ze = it } != null) { val zf = ZipFile(DynAPK.current(context))
if (ze.isDirectory) zf.entries().asSequence().filter {
continue !it.isDirectory && it.name.startsWith("lib/${Const.CPU_ABI_32}/")
var name: String? = null }.forEach {
val names = arrayOf("$arch/", "common/", "META-INF/com/google/android/update-binary") val n = it.name.substring(it.name.lastIndexOf('/') + 1)
for (n in names) { val name = n.substring(3, n.length - 3)
ze.name.run { val dest = File(installDir, name)
if (startsWith(n)) { zf.getInputStream(it).writeTo(dest)
name = substring(lastIndexOf('/') + 1) }
} } else {
} val libs = Const.NATIVE_LIB_DIR.listFiles { _, name ->
name ?: continue name.startsWith("lib") && name.endsWith(".so")
break } ?: emptyArray()
} for (lib in libs) {
if (name == null && ze.name.startsWith("chromeos/")) val name = lib.name.substring(3, lib.name.length - 3)
name = ze.name Os.symlink(lib.path, "$installDir/$name")
name?.also {
val dest = if (installDir is SuFile)
SuFile(installDir, it)
else
File(installDir, it)
dest.parentFile!!.mkdirs()
SuFileOutputStream(dest).use { s -> zi.copyTo(s) }
} ?: continue
} }
} }
} catch (e: IOException) {
console.add("! Cannot unzip zip") // Extract scripts
for (script in listOf("util_functions.sh", "boot_patch.sh", "addon.d.sh")) {
val dest = File(installDir, script)
context.assets.open(script).writeTo(dest)
}
// Extract chromeos tools
File(installDir, "chromeos").mkdir()
for (file in listOf("futility", "kernel_data_key.vbprivk", "kernel.keyblock")) {
val name = "chromeos/$file"
val dest = File(installDir, name)
context.assets.open(name).writeTo(dest)
}
} catch (e: Exception) {
console.add("! Unable to extract files")
Timber.e(e) Timber.e(e)
return false return false
} }
val init64 = SuFile.open(installDir, "magiskinit64") if (useRootDir) {
if (Build.VERSION.SDK_INT >= 21 && Build.SUPPORTED_64_BIT_ABIS.isNotEmpty()) { // Move everything to tmpfs to workaround Samsung bullshit
init64.renameTo(SuFile.open(installDir, "magiskinit")) SuFile(Const.TMPDIR).also {
} else { arrayOf(
init64.delete() "rm -rf $it",
"mkdir -p $it",
"cp_readlink $installDir $it",
"rm -rf $installDir"
).sh()
installDir = it
}
} }
"cd $installDir; chmod 755 *".sh()
return true return true
} }
private fun newEntry(name: String, size: Long): TarEntry { // Optimization for SuFile I/O streams to skip an internal trial and error
private fun installDirFile(name: String): File {
return if (useRootDir)
SuFile(installDir, name)
else
File(installDir, name)
}
private fun InputStream.cleanPump(out: OutputStream) = withStreams(this, out) { src, _ ->
src.copyTo(out)
}
private fun newTarEntry(name: String, size: Long): TarEntry {
console.add("-- Writing: $name") console.add("-- Writing: $name")
return TarEntry(TarHeader.createHeader(name, size, 0, false, 420 /* 0644 */)) return TarEntry(TarHeader.createHeader(name, size, 0, false, 420 /* 0644 */))
} }
@Throws(IOException::class) @Throws(IOException::class)
private fun handleTar(input: InputStream, output: OutputStream): OutputStream { private fun processTar(input: InputStream, output: OutputStream): OutputStream {
console.add("- Processing tar file") console.add("- Processing tar file")
val tarOut = TarOutputStream(output) val tarOut = TarOutputStream(output)
TarInputStream(input).use { tarIn -> TarInputStream(input).use { tarIn ->
lateinit var entry: TarEntry lateinit var entry: TarEntry
fun decompressedStream() = fun decompressedStream(): InputStream {
if (entry.name.contains(".lz4")) LZ4FrameInputStream(tarIn) else tarIn 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 */ }
}
}
while (tarIn.nextEntry?.let { entry = it } != null) { while (tarIn.nextEntry?.let { entry = it } != null) {
if (entry.name.contains("boot.img") || if (entry.name.startsWith("boot.img") ||
(Config.recovery && entry.name.contains("recovery.img"))) { (Config.recovery && entry.name.contains("recovery.img"))) {
val name = entry.name.replace(".lz4", "") val name = entry.name.replace(".lz4", "")
console.add("-- Extracting: $name") console.add("-- Extracting: $name")
val extract = File(installDir, name) val extract = installDirFile(name)
FileOutputStream(extract).use { decompressedStream().copyTo(it) } decompressedStream().cleanPump(SuFileOutputStream.open(extract))
} else if (entry.name.contains("vbmeta.img")) { } else if (entry.name.contains("vbmeta.img")) {
val rawData = ByteArrayOutputStream().let { val rawData = decompressedStream().readBytes()
decompressedStream().copyTo(it)
it.toByteArray()
}
// Valid vbmeta.img should be at least 256 bytes // Valid vbmeta.img should be at least 256 bytes
if (rawData.size < 256) if (rawData.size < 256)
continue continue
@@ -195,7 +195,7 @@ abstract class MagiskInstallImpl : KoinComponent {
// AVB_VBMETA_IMAGE_FLAGS_VERIFICATION_DISABLED // AVB_VBMETA_IMAGE_FLAGS_VERIFICATION_DISABLED
console.add("-- Patching: vbmeta.img") console.add("-- Patching: vbmeta.img")
ByteBuffer.wrap(rawData).putInt(120, 3) ByteBuffer.wrap(rawData).putInt(120, 3)
tarOut.putNextEntry(newEntry("vbmeta.img", rawData.size.toLong())) tarOut.putNextEntry(newTarEntry("vbmeta.img", rawData.size.toLong()))
tarOut.write(rawData) tarOut.write(rawData)
} else { } else {
console.add("-- Copying: ${entry.name}") console.add("-- Copying: ${entry.name}")
@@ -203,29 +203,33 @@ abstract class MagiskInstallImpl : KoinComponent {
tarIn.copyTo(tarOut, bufferSize = 1024 * 1024) tarIn.copyTo(tarOut, bufferSize = 1024 * 1024)
} }
} }
val boot = SuFile.open(installDir, "boot.img") }
val recovery = SuFile.open(installDir, "recovery.img") val boot = installDirFile("boot.img")
if (Config.recovery && recovery.exists() && boot.exists()) { val recovery = installDirFile("recovery.img")
// Install Magisk to recovery if (Config.recovery && recovery.exists() && boot.exists()) {
srcBoot = recovery.path // Install to recovery
// Repack boot image to prevent restore srcBoot = recovery
arrayOf( // Repack boot image to prevent auto restore
"./magiskboot unpack boot.img", arrayOf(
"./magiskboot repack boot.img", "cd $installDir",
"./magiskboot cleanup", "chmod -R 755 .",
"mv new-boot.img boot.img").sh() "./magiskboot unpack boot.img",
SuFileInputStream(boot).use { "./magiskboot repack boot.img",
tarOut.putNextEntry(newEntry("boot.img", boot.length())) "cat new-boot.img > boot.img",
it.copyTo(tarOut) "./magiskboot cleanup",
} "rm -f new-boot.img",
boot.delete() "cd /").sh()
} else { SuFileInputStream.open(boot).use {
if (!boot.exists()) { tarOut.putNextEntry(newTarEntry("boot.img", boot.length()))
console.add("! No boot image found") it.copyTo(tarOut)
throw IOException()
}
srcBoot = boot.path
} }
boot.delete()
} else {
if (!boot.exists()) {
console.add("! No boot image found")
throw IOException()
}
srcBoot = boot
} }
return tarOut return tarOut
} }
@@ -248,20 +252,22 @@ abstract class MagiskInstallImpl : KoinComponent {
val alpha = "abcdefghijklmnopqrstuvwxyz" val alpha = "abcdefghijklmnopqrstuvwxyz"
val alphaNum = "$alpha${alpha.toUpperCase(Locale.ROOT)}0123456789" val alphaNum = "$alpha${alpha.toUpperCase(Locale.ROOT)}0123456789"
val random = SecureRandom() val random = SecureRandom()
val suffix = StringBuilder() val filename = StringBuilder("magisk_patched-${BuildConfig.VERSION_CODE}_").run {
for (i in 1..5) { for (i in 1..5) {
suffix.append(alphaNum[random.nextInt(alphaNum.length)]) append(alphaNum[random.nextInt(alphaNum.length)])
}
toString()
} }
val filename = "magisk_patched_$suffix"
outStream = if (magic.contentEquals("ustar".toByteArray())) { outStream = if (magic.contentEquals("ustar".toByteArray())) {
// tar file
outFile = MediaStoreUtils.getFile("$filename.tar", true) outFile = MediaStoreUtils.getFile("$filename.tar", true)
handleTar(src, outFile!!.uri.outputStream()) processTar(src, outFile!!.uri.outputStream())
} else { } else {
// Raw image // raw image
srcBoot = File(installDir, "boot.img").path srcBoot = installDirFile("boot.img")
console.add("- Copying image to cache") console.add("- Copying image to cache")
FileOutputStream(srcBoot).use { src.copyTo(it) } src.cleanPump(SuFileOutputStream.open(srcBoot))
outFile = MediaStoreUtils.getFile("$filename.img", true) outFile = MediaStoreUtils.getFile("$filename.img", true)
outFile!!.uri.outputStream() outFile!!.uri.outputStream()
} }
@@ -281,13 +287,13 @@ abstract class MagiskInstallImpl : KoinComponent {
// Output file // Output file
try { try {
val patched = SuFile.open(installDir, "new-boot.img") val newBoot = installDirFile("new-boot.img")
if (outStream is TarOutputStream) { if (outStream is TarOutputStream) {
val name = if (srcBoot.contains("recovery")) "recovery.img" else "boot.img" val name = if (srcBoot.path.contains("recovery")) "recovery.img" else "boot.img"
outStream.putNextEntry(newEntry(name, patched.length())) outStream.putNextEntry(newTarEntry(name, newBoot.length()))
} }
withStreams(SuFileInputStream(patched), outStream) { src, out -> src.copyTo(out) } SuFileInputStream.open(newBoot).cleanPump(outStream)
patched.delete() newBoot.delete()
console.add("") console.add("")
console.add("****************************") console.add("****************************")
@@ -301,130 +307,133 @@ abstract class MagiskInstallImpl : KoinComponent {
return false return false
} }
// Fix up binaries
srcBoot.delete()
if (shell.isRoot) {
"fix_env $installDir".sh()
} else {
"cp_readlink $installDir".sh()
}
return true return true
} }
private fun patchBoot(): Boolean { private fun patchBoot(): Boolean {
var srcNand = "" var isSigned = false
if ("[ -c $srcBoot ] && nanddump -f boot.img $srcBoot".sh().isSuccess) { if (srcBoot.let { it !is SuFile || !it.isCharacter }) {
srcNand = srcBoot try {
srcBoot = File(installDir, "boot.img").path SuFileInputStream.open(srcBoot).use {
} if (SignBoot.verifySignature(it, null)) {
isSigned = true
var isSigned: Boolean console.add("- Boot image is signed with AVB 1.0")
try { }
SuFileInputStream(srcBoot).use {
isSigned = SignBoot.verifySignature(it, null)
if (isSigned) {
console.add("- Boot image is signed with AVB 1.0")
} }
} catch (e: IOException) {
console.add("! Unable to check signature")
Timber.e(e)
return false
} }
} catch (e: IOException) { }
console.add("! Unable to check signature")
val newBoot = installDirFile("new-boot.img")
if (!useRootDir) {
// Create output files before hand
newBoot.createNewFile()
File(installDir, "stock_boot.img").createNewFile()
}
val cmds = arrayOf(
"cd $installDir",
"KEEPFORCEENCRYPT=${Config.keepEnc} " +
"KEEPVERITY=${Config.keepVerity} " +
"RECOVERYMODE=${Config.recovery} " +
"sh boot_patch.sh $srcBoot")
if (!cmds.sh().isSuccess)
return false return false
}
if (!("KEEPFORCEENCRYPT=${Config.keepEnc} KEEPVERITY=${Config.keepVerity} " + val job = shell.newJob().add("./magiskboot cleanup", "cd /")
"RECOVERYMODE=${Config.recovery} sh update-binary " +
"sh boot_patch.sh $srcBoot").sh().isSuccess) {
return false
}
if (srcNand.isNotEmpty()) {
srcBoot = srcNand
}
val job = Shell.sh(
"./magiskboot cleanup",
"mv bin/busybox busybox",
"rm -rf magisk.apk bin boot.img update-binary",
"cd /")
val patched = File(installDir, "new-boot.img")
if (isSigned) { if (isSigned) {
console.add("- Signing boot image with verity keys") console.add("- Signing boot image with verity keys")
val signed = File(installDir, "signed.img") val signed = File.createTempFile("signed", ".img", context.cacheDir)
try { try {
withStreams(SuFileInputStream(patched), signed.outputStream().buffered()) { val src = SuFileInputStream.open(newBoot).buffered()
input, out -> SignBoot.doSignature(null, null, input, out, "/boot") val out = signed.outputStream().buffered()
withStreams(src, out) { _, _ ->
SignBoot.doSignature(null, null, src, out, "/boot")
} }
} catch (e: IOException) { } catch (e: IOException) {
console.add("! Unable to sign image") console.add("! Unable to sign image")
Timber.e(e) Timber.e(e)
return false return false
} }
job.add("cat $signed > $newBoot", "rm -f $signed")
job.add("mv -f $signed $patched")
} }
job.exec() job.exec()
return true return true
} }
private fun copySepolicyRules(): Boolean { private fun flashBoot() = "direct_install $installDir $srcBoot".sh().isSuccess
if (Info.remote.magisk.versionCode >= 21100) return true
// Copy existing rules for migration
"copy_sepolicy_rules".sh()
return true
}
private fun flashBoot(): Boolean {
if (!"direct_install $installDir $srcBoot".sh().isSuccess)
return false
"run_migrations".sh()
return true
}
private suspend fun postOTA(): Boolean { private suspend fun postOTA(): Boolean {
val bootctl = SuFile("/data/adb/bootctl")
try { try {
withStreams(service.fetchBootctl().byteStream(), SuFileOutputStream(bootctl)) { val bootctl = File.createTempFile("bootctl", null, context.cacheDir)
it, out -> it.copyTo(out) service.fetchBootctl().byteStream().writeTo(bootctl)
} "post_ota $bootctl".sh()
} catch (e: IOException) { } catch (e: IOException) {
console.add("! Unable to download bootctl") console.add("! Unable to download bootctl")
Timber.e(e) Timber.e(e)
return false return false
} }
"post_ota ${bootctl.parent}".sh()
console.add("***************************************") console.add("***************************************")
console.add(" Next reboot will boot to second slot!") console.add(" Next reboot will boot to second slot!")
console.add("***************************************") console.add("***************************************")
return true return true
} }
private fun String.sh() = Shell.sh(this).to(console, logs).exec() private fun String.sh() = shell.newJob().add(this).to(console, logs).exec()
private fun Array<String>.sh() = Shell.sh(*this).to(console, logs).exec() private fun Array<String>.sh() = shell.newJob().add(*this).to(console, logs).exec()
private fun String.fsh() = ShellUtils.fastCmd(this) private fun String.fsh() = ShellUtils.fastCmd(shell, this)
private fun Array<String>.fsh() = ShellUtils.fastCmd(*this) private fun Array<String>.fsh() = ShellUtils.fastCmd(shell, *this)
protected fun doPatchFile(patchFile: Uri) = extractZip() && handleFile(patchFile) protected fun doPatchFile(patchFile: Uri) = extractFiles() && handleFile(patchFile)
protected fun direct() = findImage() && extractZip() && patchBoot() && protected fun direct() = findImage() && extractFiles() && patchBoot() && flashBoot()
copySepolicyRules() && flashBoot()
protected suspend fun secondSlot() = findSecondaryImage() && extractZip() && protected suspend fun secondSlot() =
patchBoot() && copySepolicyRules() && flashBoot() && postOTA() findSecondary() && extractFiles() && patchBoot() && flashBoot() && postOTA()
protected fun fixEnv(zip: Uri): Boolean { protected fun fixEnv() = extractFiles() && "fix_env $installDir".sh().isSuccess
installDir = SuFile("/data/adb/magisk")
Shell.su("rm -rf /data/adb/magisk/*").exec() protected fun uninstall() = "run_uninstaller ${AssetHack.apk}".sh().isSuccess
zipUri = zip
return extractZip() && Shell.su("fix_env").exec().isSuccess
}
@WorkerThread @WorkerThread
protected abstract suspend fun operations(): Boolean protected abstract suspend fun operations(): Boolean
open suspend fun exec() = withContext(Dispatchers.IO) { operations() } open suspend fun exec(): Boolean {
synchronized(Companion) {
if (haveActiveSession)
return false
haveActiveSession = true
}
val result = withContext(Dispatchers.IO) { operations() }
synchronized(Companion) {
haveActiveSession = false
}
return result
}
companion object {
private var haveActiveSession = false
}
} }
sealed class MagiskInstaller( abstract class MagiskInstaller(
file: Uri,
console: MutableList<String>, console: MutableList<String>,
logs: MutableList<String> logs: MutableList<String>
) : MagiskInstallImpl(file, console, logs) { ) : MagiskInstallImpl(console, logs) {
override suspend fun exec(): Boolean { override suspend fun exec(): Boolean {
val success = super.exec() val success = super.exec()
@@ -438,46 +447,64 @@ sealed class MagiskInstaller(
} }
class Patch( class Patch(
file: Uri,
private val uri: Uri, private val uri: Uri,
console: MutableList<String>, console: MutableList<String>,
logs: MutableList<String> logs: MutableList<String>
) : MagiskInstaller(file, console, logs) { ) : MagiskInstaller(console, logs) {
override suspend fun operations() = doPatchFile(uri) override suspend fun operations() = doPatchFile(uri)
} }
class SecondSlot( class SecondSlot(
file: Uri,
console: MutableList<String>, console: MutableList<String>,
logs: MutableList<String> logs: MutableList<String>
) : MagiskInstaller(file, console, logs) { ) : MagiskInstaller(console, logs) {
override suspend fun operations() = secondSlot() override suspend fun operations() = secondSlot()
} }
class Direct( class Direct(
file: Uri,
console: MutableList<String>, console: MutableList<String>,
logs: MutableList<String> logs: MutableList<String>
) : MagiskInstaller(file, console, logs) { ) : MagiskInstaller(console, logs) {
override suspend fun operations() = direct() override suspend fun operations() = direct()
} }
} class Emulator(
console: MutableList<String>,
logs: MutableList<String>
) : MagiskInstaller(console, logs) {
override suspend fun operations() = fixEnv()
}
class EnvFixTask( class Uninstall(
private val zip: Uri console: MutableList<String>,
) : MagiskInstallImpl() { logs: MutableList<String>
override suspend fun operations() = fixEnv(zip) ) : MagiskInstallImpl(console, logs) {
override suspend fun operations() = uninstall()
override suspend fun exec(): Boolean { override suspend fun exec(): Boolean {
val success = super.exec() val success = super.exec()
LocalBroadcastManager.getInstance(context).sendBroadcast(Intent(EnvFixDialog.DISMISS)) if (success) {
Utils.toast( UiThreadHandler.handler.postDelayed(3000) {
if (success) R.string.reboot_delay_toast else R.string.setup_fail, Shell.su("pm uninstall ${context.packageName}").exec()
Toast.LENGTH_LONG }
) }
if (success) return success
UiThreadHandler.handler.postDelayed(5000) { reboot() } }
return success }
class FixEnv(private val callback: () -> Unit) : MagiskInstallImpl() {
override suspend fun operations() = fixEnv()
override suspend fun exec(): Boolean {
val success = super.exec()
callback()
Utils.toast(
if (success) R.string.reboot_delay_toast else R.string.setup_fail,
Toast.LENGTH_LONG
)
if (success)
UiThreadHandler.handler.postDelayed(5000) { reboot() }
return success
}
} }
} }

View File

@@ -6,12 +6,11 @@ import androidx.core.content.ContextCompat
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.Config import com.topjohnwu.magisk.core.Config
import org.koin.core.KoinComponent import com.topjohnwu.magisk.di.AppContext
import org.koin.core.get
object BiometricHelper: KoinComponent { object BiometricHelper {
private val mgr by lazy { BiometricManager.from(get()) } private val mgr by lazy { BiometricManager.from(AppContext) }
val isSupported get() = when (mgr.canAuthenticate()) { val isSupported get() = when (mgr.canAuthenticate()) {
BiometricManager.BIOMETRIC_SUCCESS -> true BiometricManager.BIOMETRIC_SUCCESS -> true

View File

@@ -3,20 +3,14 @@
package com.topjohnwu.magisk.core.utils package com.topjohnwu.magisk.core.utils
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.res.AssetManager
import android.content.res.Configuration import android.content.res.Configuration
import android.content.res.Resources import android.content.res.Resources
import android.util.DisplayMetrics
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.AssetHack
import com.topjohnwu.magisk.core.Config import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.core.ResMgr
import com.topjohnwu.magisk.core.addAssetPath
import com.topjohnwu.magisk.ktx.langTagToLocale
import com.topjohnwu.magisk.ktx.toLangTag
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.util.* import java.util.*
import kotlin.Comparator
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
var currentLocale: Locale = Locale.getDefault() var currentLocale: Locale = Locale.getDefault()
@@ -30,11 +24,8 @@ suspend fun availableLocales() = cachedLocales ?:
withContext(Dispatchers.Default) { withContext(Dispatchers.Default) {
val compareId = R.string.app_changelog val compareId = R.string.app_changelog
// Create a completely new resource to prevent cross talk over app's configs // Create a completely new resource to prevent cross talk over active configs
val asset = AssetManager::class.java.newInstance().apply { addAssetPath(ResMgr.apk) } val res = AssetHack.newResource()
val config = Configuration(ResMgr.resource.configuration)
val metrics = DisplayMetrics().apply { setTo(ResMgr.resource.displayMetrics) }
val res = Resources(asset, metrics, config)
val locales = ArrayList<String>().apply { val locales = ArrayList<String>().apply {
// Add default locale // Add default locale
@@ -45,19 +36,17 @@ withContext(Dispatchers.Default) {
add("pt-BR") add("pt-BR")
// Then add all supported locales // Then add all supported locales
addAll(res.assets.locales) addAll(Resources.getSystem().assets.locales)
}.map { }.map {
it.langTagToLocale() Locale.forLanguageTag(it)
}.distinctBy { }.distinctBy {
config.setLocale(it) res.updateLocale(it)
res.updateConfiguration(config, metrics)
res.getString(compareId) res.getString(compareId)
}.sortedWith(Comparator { a, b -> }.sortedWith { a, b ->
a.getDisplayName(a).compareTo(b.getDisplayName(b), true) a.getDisplayName(a).compareTo(b.getDisplayName(b), true)
}) }
config.setLocale(defaultLocale) res.updateLocale(defaultLocale)
res.updateConfiguration(config, metrics)
val defName = res.getString(R.string.system_default) val defName = res.getString(R.string.system_default)
val names = ArrayList<String>(locales.size + 1) val names = ArrayList<String>(locales.size + 1)
@@ -68,7 +57,7 @@ withContext(Dispatchers.Default) {
locales.forEach { locale -> locales.forEach { locale ->
names.add(locale.getDisplayName(locale)) names.add(locale.getDisplayName(locale))
values.add(locale.toLangTag()) values.add(locale.toLanguageTag())
} }
(names.toTypedArray() to values.toTypedArray()).also { cachedLocales = it } (names.toTypedArray() to values.toTypedArray()).also { cachedLocales = it }
@@ -79,12 +68,17 @@ fun Resources.updateConfig(config: Configuration = configuration) {
updateConfiguration(config, displayMetrics) updateConfiguration(config, displayMetrics)
} }
fun Resources.updateLocale(locale: Locale) {
configuration.setLocale(locale)
updateConfiguration(configuration, displayMetrics)
}
fun refreshLocale() { fun refreshLocale() {
val localeConfig = Config.locale val localeConfig = Config.locale
currentLocale = when { currentLocale = when {
localeConfig.isEmpty() -> defaultLocale localeConfig.isEmpty() -> defaultLocale
else -> localeConfig.langTagToLocale() else -> Locale.forLanguageTag(localeConfig)
} }
Locale.setDefault(currentLocale) Locale.setDefault(currentLocale)
ResMgr.resource.updateConfig() AssetHack.resource.updateConfig()
} }

View File

@@ -1,9 +1,7 @@
package com.topjohnwu.magisk.core.utils package com.topjohnwu.magisk.core.utils
import android.content.ContentResolver
import android.content.ContentUris import android.content.ContentUris
import android.content.ContentValues import android.content.ContentValues
import android.content.Context
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
import android.os.Environment import android.os.Environment
@@ -13,7 +11,7 @@ import androidx.annotation.RequiresApi
import androidx.core.net.toFile import androidx.core.net.toFile
import androidx.core.net.toUri import androidx.core.net.toUri
import com.topjohnwu.magisk.core.Config import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.ktx.get import com.topjohnwu.magisk.di.AppContext
import java.io.File import java.io.File
import java.io.FileNotFoundException import java.io.FileNotFoundException
import java.io.IOException import java.io.IOException
@@ -24,7 +22,7 @@ import kotlin.experimental.and
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
object MediaStoreUtils { object MediaStoreUtils {
private val cr: ContentResolver by lazy { get<Context>().contentResolver } private val cr get() = AppContext.contentResolver
@get:RequiresApi(api = 29) @get:RequiresApi(api = 29)
private val tableUri private val tableUri

View File

@@ -1,59 +0,0 @@
package com.topjohnwu.magisk.core.utils
import android.content.Context
import android.os.Build
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.wrap
import com.topjohnwu.magisk.ktx.rawResource
import com.topjohnwu.superuser.Shell
import com.topjohnwu.superuser.ShellUtils
class RootInit : Shell.Initializer() {
override fun onInit(context: Context, shell: Shell): Boolean {
return init(context.wrap(), shell)
}
fun init(context: Context, shell: Shell): Boolean {
shell.newJob().apply {
add("export SDK_INT=${Build.VERSION.SDK_INT}")
if (Const.Version.atLeast_20_4()) {
add("export MAGISKTMP=\$(magisk --path)/.magisk")
} else {
add("export MAGISKTMP=/sbin/.magisk")
}
if (Const.Version.atLeast_21_0()) {
add("export ASH_STANDALONE=1")
add("[ -x /data/adb/magisk/busybox ] && exec /data/adb/magisk/busybox sh")
} else {
add("export PATH=\"\$MAGISKTMP/busybox:\$PATH\"")
}
add(context.rawResource(R.raw.manager))
if (shell.isRoot) {
add(context.rawResource(R.raw.util_functions))
}
add("mm_init")
}.exec()
fun fastCmd(cmd: String) = ShellUtils.fastCmd(shell, cmd)
fun getVar(name: String) = fastCmd("echo \$$name")
fun getBool(name: String) = getVar(name).toBoolean()
Const.MAGISKTMP = getVar("MAGISKTMP")
Info.isSAR = getBool("SYSTEM_ROOT")
Info.ramdisk = getBool("RAMDISKEXIST")
Info.isAB = getBool("ISAB")
Info.crypto = getVar("CRYPTOTYPE")
Info.isPixel = fastCmd("getprop ro.product.brand") == "google"
// Default presets
Config.recovery = getBool("RECOVERYMODE")
Config.keepVerity = getBool("KEEPVERITY")
Config.keepEnc = getBool("KEEPFORCEENCRYPT")
return true
}
}

View File

@@ -0,0 +1,101 @@
package com.topjohnwu.magisk.core.utils
import android.content.Context
import android.os.Build
import com.topjohnwu.magisk.DynAPK
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.*
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.superuser.Shell
import com.topjohnwu.superuser.ShellUtils
import java.io.File
import java.util.jar.JarFile
abstract class BaseShellInit : Shell.Initializer() {
final override fun onInit(context: Context, shell: Shell): Boolean {
return init(context.wrap(), shell)
}
abstract fun init(context: Context, shell: Shell): Boolean
}
class BusyBoxInit : BaseShellInit() {
override fun init(context: Context, shell: Shell): Boolean {
shell.newJob().apply {
add("export ASH_STANDALONE=1")
val localBB: File
if (isRunningAsStub) {
if (!shell.isRoot)
return true
val jar = JarFile(DynAPK.current(context))
val bb = jar.getJarEntry("lib/${Const.CPU_ABI_32}/libbusybox.so")
localBB = context.deviceProtectedContext.cachedFile("busybox")
localBB.delete()
jar.getInputStream(bb).writeTo(localBB)
localBB.setExecutable(true)
} else {
localBB = File(Const.NATIVE_LIB_DIR, "libbusybox.so")
}
if (shell.isRoot) {
add("export MAGISKTMP=\$(magisk --path)/.magisk")
// Test if we can properly execute stuff in /data
Info.noDataExec = !shell.newJob().add("$localBB true").exec().isSuccess
}
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",
"else",
" cp -af $localBB /dev/.busybox",
" exec /dev/.busybox sh",
"fi"
)
} else {
// Directly execute the file
add("exec $localBB sh")
}
}.exec()
return true
}
}
class AppShellInit : BaseShellInit() {
override fun init(context: Context, shell: Shell): Boolean {
fun fastCmd(cmd: String) = ShellUtils.fastCmd(shell, cmd)
fun getVar(name: String) = fastCmd("echo \$$name")
fun getBool(name: String) = getVar(name).toBoolean()
shell.newJob().apply {
add(context.rawResource(R.raw.manager))
if (shell.isRoot) {
add(context.assets.open("util_functions.sh"))
}
add("app_init")
}.exec()
Const.MAGISKTMP = getVar("MAGISKTMP")
Info.isSAR = getBool("SYSTEM_ROOT")
Info.ramdisk = getBool("RAMDISKEXIST")
Info.isAB = getBool("ISAB")
Info.crypto = getVar("CRYPTOTYPE")
// Default presets
Config.recovery = getBool("RECOVERYMODE")
Config.keepVerity = getBool("KEEPVERITY")
Config.keepEnc = getBool("KEEPFORCEENCRYPT")
return true
}
}

View File

@@ -36,7 +36,7 @@ fun InputStream.unzip(folder: File, path: String, junkPath: Boolean) {
dest = SuFile(folder, name) dest = SuFile(folder, name)
dest.parentFile!!.mkdirs() dest.parentFile!!.mkdirs()
} }
SuFileOutputStream(dest).use { out -> zin.copyTo(out) } SuFileOutputStream.open(dest).use { out -> zin.copyTo(out) }
} }
} catch (e: IOException) { } catch (e: IOException) {
e.printStackTrace() e.printStackTrace()

View File

@@ -20,11 +20,10 @@ abstract class NetworkObserver(
companion object { companion object {
fun observe(context: Context, callback: ConnectionCallback): NetworkObserver { fun observe(context: Context, callback: ConnectionCallback): NetworkObserver {
return when (Build.VERSION.SDK_INT) { val observer: NetworkObserver = if (Build.VERSION.SDK_INT >= 23)
in 23 until Int.MAX_VALUE -> MarshmallowNetworkObserver(context, callback) MarshmallowNetworkObserver(context, callback)
in 21 until 23 -> LollipopNetworkObserver(context, callback) else LollipopNetworkObserver(context, callback)
else -> PreLollipopNetworkObserver(context, callback) return observer.apply { getCurrentState() }
}.apply { getCurrentState() }
} }
} }
} }

View File

@@ -1,38 +0,0 @@
@file:Suppress("DEPRECATION")
package com.topjohnwu.magisk.core.utils.net
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.net.ConnectivityManager
import androidx.core.net.ConnectivityManagerCompat
class PreLollipopNetworkObserver(
context: Context,
callback: ConnectionCallback
): NetworkObserver(context, callback) {
private val receiver = ConnectivityBroadcastReceiver()
init {
val filter = IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)
app.registerReceiver(receiver, filter)
}
override fun stopObserving() {
app.unregisterReceiver(receiver)
}
override fun getCurrentState() {
callback(manager.activeNetworkInfo?.isConnected ?: false)
}
private inner class ConnectivityBroadcastReceiver: BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent) {
val info = ConnectivityManagerCompat.getNetworkInfoFromBroadcast(manager, intent)
callback(info?.isConnected ?: false)
}
}
}

View File

@@ -10,17 +10,15 @@ import retrofit2.http.*
private const val REVISION = "revision" private const val REVISION = "revision"
private const val BRANCH = "branch" private const val BRANCH = "branch"
private const val REPO = "repo" private const val REPO = "repo"
private const val FILE = "file"
const val MAGISK_FILES = "topjohnwu/magisk_files" const val MAGISK_FILES = "topjohnwu/magisk-files"
const val MAGISK_MAIN = "topjohnwu/Magisk" const val MAGISK_MAIN = "topjohnwu/Magisk"
interface GithubPageServices { interface GithubPageServices {
@GET("stable.json") @GET("{$FILE}")
suspend fun fetchStableUpdate(): UpdateInfo suspend fun fetchUpdateJSON(@Path(FILE) file: String): UpdateInfo
@GET("beta.json")
suspend fun fetchBetaUpdate(): UpdateInfo
} }
interface JSDelivrServices { interface JSDelivrServices {
@@ -33,9 +31,6 @@ interface JSDelivrServices {
@Streaming @Streaming
suspend fun fetchBootctl(@Path(REVISION) revision: String = Const.BOOTCTL_REVISION): ResponseBody suspend fun fetchBootctl(@Path(REVISION) revision: String = Const.BOOTCTL_REVISION): ResponseBody
@GET("$MAGISK_FILES@{$REVISION}/canary.json")
suspend fun fetchCanaryUpdate(@Path(REVISION) revision: String): UpdateInfo
@GET("$MAGISK_MAIN@{$REVISION}/scripts/module_installer.sh") @GET("$MAGISK_MAIN@{$REVISION}/scripts/module_installer.sh")
@Streaming @Streaming
suspend fun fetchInstaller(@Path(REVISION) revision: String): ResponseBody suspend fun fetchInstaller(@Path(REVISION) revision: String): ResponseBody

View File

@@ -9,8 +9,8 @@ import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty import kotlin.reflect.KProperty
interface DBConfig { interface DBConfig {
val settingsDao: SettingsDao val settingsDB: SettingsDao
val stringDao: StringDao val stringDB: StringDao
fun dbSettings( fun dbSettings(
name: String, name: String,
@@ -41,7 +41,7 @@ class DBSettingsValue(
override fun getValue(thisRef: DBConfig, property: KProperty<*>): Int { override fun getValue(thisRef: DBConfig, property: KProperty<*>): Int {
if (value == null) if (value == null)
value = runBlocking { value = runBlocking {
thisRef.settingsDao.fetch(name, default) thisRef.settingsDB.fetch(name, default)
} }
return value as Int return value as Int
} }
@@ -51,7 +51,7 @@ class DBSettingsValue(
this.value = value this.value = value
} }
GlobalScope.launch { GlobalScope.launch {
thisRef.settingsDao.put(name, value) thisRef.settingsDB.put(name, value)
} }
} }
} }
@@ -82,7 +82,7 @@ class DBStringsValue(
override fun getValue(thisRef: DBConfig, property: KProperty<*>): String { override fun getValue(thisRef: DBConfig, property: KProperty<*>): String {
if (value == null) if (value == null)
value = runBlocking { value = runBlocking {
thisRef.stringDao.fetch(name, default) thisRef.stringDB.fetch(name, default)
} }
return value!! return value!!
} }
@@ -94,21 +94,21 @@ class DBStringsValue(
if (value.isEmpty()) { if (value.isEmpty()) {
if (sync) { if (sync) {
runBlocking { runBlocking {
thisRef.stringDao.delete(name) thisRef.stringDB.delete(name)
} }
} else { } else {
GlobalScope.launch { GlobalScope.launch {
thisRef.stringDao.delete(name) thisRef.stringDB.delete(name)
} }
} }
} else { } else {
if (sync) { if (sync) {
runBlocking { runBlocking {
thisRef.stringDao.put(name, value) thisRef.stringDB.put(name, value)
} }
} else { } else {
GlobalScope.launch { GlobalScope.launch {
thisRef.stringDao.put(name, value) thisRef.stringDB.put(name, value)
} }
} }
} }

View File

@@ -8,7 +8,6 @@ import com.topjohnwu.magisk.core.Config.Value.DEFAULT_CHANNEL
import com.topjohnwu.magisk.core.Config.Value.STABLE_CHANNEL import com.topjohnwu.magisk.core.Config.Value.STABLE_CHANNEL
import com.topjohnwu.magisk.core.Const import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.Info import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.model.*
import com.topjohnwu.magisk.data.network.* import com.topjohnwu.magisk.data.network.*
import retrofit2.HttpException import retrofit2.HttpException
import timber.log.Timber import timber.log.Timber
@@ -34,31 +33,14 @@ class NetworkService(
Config.updateChannel = BETA_CHANNEL Config.updateChannel = BETA_CHANNEL
info = fetchBetaUpdate() info = fetchBetaUpdate()
} }
Info.remote = info
info info
} }
// UpdateInfo // UpdateInfo
private suspend fun fetchStableUpdate() = pages.fetchStableUpdate() private suspend fun fetchStableUpdate() = pages.fetchUpdateJSON("stable.json")
private suspend fun fetchBetaUpdate() = pages.fetchBetaUpdate() private suspend fun fetchBetaUpdate() = pages.fetchUpdateJSON("beta.json")
private suspend fun fetchCanaryUpdate() = pages.fetchUpdateJSON("canary.json")
private suspend fun fetchCustomUpdate(url: String) = raw.fetchCustomUpdate(url) private suspend fun fetchCustomUpdate(url: String) = raw.fetchCustomUpdate(url)
private suspend fun fetchCanaryUpdate(): UpdateInfo {
val sha = fetchCanaryVersion()
val info = jsd.fetchCanaryUpdate(sha)
fun genCDNUrl(name: String) = "${Const.Url.JS_DELIVR_URL}${MAGISK_FILES}@${sha}/${name}"
fun ManagerJson.updateCopy() = copy(link = genCDNUrl(link), note = genCDNUrl(note))
fun MagiskJson.updateCopy() = copy(link = genCDNUrl(link), note = genCDNUrl(note))
fun StubJson.updateCopy() = copy(link = genCDNUrl(link))
fun UninstallerJson.updateCopy() = copy(link = genCDNUrl(link))
return info.copy(
app = info.app.updateCopy(),
magisk = info.magisk.updateCopy(),
stub = info.stub.updateCopy(),
uninstaller = info.uninstaller.updateCopy()
)
}
private inline fun <T> safe(factory: () -> T): T? { private inline fun <T> safe(factory: () -> T): T? {
return try { return try {
@@ -92,6 +74,5 @@ class NetworkService(
suspend fun fetchFile(url: String) = wrap { raw.fetchFile(url) } suspend fun fetchFile(url: String) = wrap { raw.fetchFile(url) }
suspend fun fetchString(url: String) = wrap { raw.fetchString(url) } suspend fun fetchString(url: String) = wrap { raw.fetchString(url) }
private suspend fun fetchCanaryVersion() = api.fetchBranch(MAGISK_FILES, "canary").commit.sha
private suspend fun fetchMainVersion() = api.fetchBranch(MAGISK_MAIN, "master").commit.sha private suspend fun fetchMainVersion() = api.fetchBranch(MAGISK_MAIN, "master").commit.sha
} }

View File

@@ -28,12 +28,11 @@ import com.google.android.material.card.MaterialCardView
import com.google.android.material.chip.Chip import com.google.android.material.chip.Chip
import com.google.android.material.textfield.TextInputLayout import com.google.android.material.textfield.TextInputLayout
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.di.ServiceLocator
import com.topjohnwu.magisk.ktx.coroutineScope import com.topjohnwu.magisk.ktx.coroutineScope
import com.topjohnwu.magisk.ktx.get
import com.topjohnwu.magisk.ktx.replaceRandomWithSpecial import com.topjohnwu.magisk.ktx.replaceRandomWithSpecial
import com.topjohnwu.superuser.internal.UiThreadHandler import com.topjohnwu.superuser.internal.UiThreadHandler
import com.topjohnwu.widget.IndeterminateCheckBox import com.topjohnwu.widget.IndeterminateCheckBox
import io.noties.markwon.Markwon
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlin.math.roundToInt import kotlin.math.roundToInt
@@ -60,8 +59,7 @@ fun setInvisibleUnless(view: View, invisibleUnless: Boolean) {
@BindingAdapter("markdownText") @BindingAdapter("markdownText")
fun setMarkdownText(tv: TextView, text: CharSequence) { fun setMarkdownText(tv: TextView, text: CharSequence) {
tv.coroutineScope.launch(Dispatchers.IO) { tv.coroutineScope.launch(Dispatchers.IO) {
val markwon = get<Markwon>() ServiceLocator.markwon.setMarkdown(tv, text.toString())
markwon.setMarkdown(tv, text.toString())
} }
} }

View File

@@ -1,25 +0,0 @@
package com.topjohnwu.magisk.di
import android.content.Context
import android.os.Build
import androidx.preference.PreferenceManager
import com.topjohnwu.magisk.core.ResMgr
import org.koin.core.qualifier.named
import org.koin.dsl.module
val SUTimeout = named("su_timeout")
val Protected = named("protected")
val applicationModule = module {
factory { ResMgr.resource }
factory { get<Context>().packageManager }
factory(Protected) { createDEContext(get()) }
single(SUTimeout) { get<Context>(Protected).getSharedPreferences("su_timeout", 0) }
single { PreferenceManager.getDefaultSharedPreferences(get<Context>(Protected)) }
}
private fun createDEContext(context: Context): Context {
return if (Build.VERSION.SDK_INT >= 24)
context.createDeviceProtectedStorageContext()
else context
}

View File

@@ -1,32 +0,0 @@
package com.topjohnwu.magisk.di
import android.content.Context
import androidx.room.Room
import com.topjohnwu.magisk.core.magiskdb.PolicyDao
import com.topjohnwu.magisk.core.magiskdb.SettingsDao
import com.topjohnwu.magisk.core.magiskdb.StringDao
import com.topjohnwu.magisk.core.tasks.RepoUpdater
import com.topjohnwu.magisk.data.database.RepoDatabase
import com.topjohnwu.magisk.data.database.SuLogDatabase
import org.koin.dsl.module
val databaseModule = module {
single { PolicyDao(get()) }
single { SettingsDao() }
single { StringDao() }
single { createRepoDatabase(get()) }
single { get<RepoDatabase>().repoDao() }
single { createSuLogDatabase(get(Protected)).suLogDao() }
single { RepoUpdater(get(), get()) }
}
fun createRepoDatabase(context: Context) =
Room.databaseBuilder(context, RepoDatabase::class.java, "repo.db")
.fallbackToDestructiveMigration()
.build()
fun createSuLogDatabase(context: Context) =
Room.databaseBuilder(context, SuLogDatabase::class.java, "sulogs.db")
.fallbackToDestructiveMigration()
.build()

View File

@@ -1,9 +0,0 @@
package com.topjohnwu.magisk.di
val koinModules = listOf(
applicationModule,
networkingModule,
databaseModule,
repositoryModule,
viewModelModules
)

View File

@@ -1,49 +1,31 @@
package com.topjohnwu.magisk.di package com.topjohnwu.magisk.di
import android.content.Context import android.content.Context
import android.os.Build
import com.squareup.moshi.Moshi import com.squareup.moshi.Moshi
import com.topjohnwu.magisk.BuildConfig import com.topjohnwu.magisk.BuildConfig
import com.topjohnwu.magisk.ProviderInstaller
import com.topjohnwu.magisk.core.Config import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.Info import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.data.network.GithubApiServices
import com.topjohnwu.magisk.data.network.GithubPageServices
import com.topjohnwu.magisk.data.network.JSDelivrServices
import com.topjohnwu.magisk.data.network.RawServices
import com.topjohnwu.magisk.ktx.precomputedText import com.topjohnwu.magisk.ktx.precomputedText
import com.topjohnwu.magisk.net.Networking
import com.topjohnwu.magisk.net.NoSSLv3SocketFactory
import com.topjohnwu.magisk.utils.MarkwonImagePlugin import com.topjohnwu.magisk.utils.MarkwonImagePlugin
import io.noties.markwon.Markwon import io.noties.markwon.Markwon
import io.noties.markwon.html.HtmlPlugin import io.noties.markwon.html.HtmlPlugin
import okhttp3.Dns import okhttp3.Dns
import okhttp3.HttpUrl import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.dnsoverhttps.DnsOverHttps import okhttp3.dnsoverhttps.DnsOverHttps
import okhttp3.logging.HttpLoggingInterceptor import okhttp3.logging.HttpLoggingInterceptor
import org.koin.dsl.module
import retrofit2.Retrofit import retrofit2.Retrofit
import retrofit2.converter.moshi.MoshiConverterFactory import retrofit2.converter.moshi.MoshiConverterFactory
import retrofit2.converter.scalars.ScalarsConverterFactory import retrofit2.converter.scalars.ScalarsConverterFactory
import java.net.InetAddress import java.net.InetAddress
import java.net.UnknownHostException import java.net.UnknownHostException
val networkingModule = module {
single { createOkHttpClient(get()) }
single { createRetrofit(get()) }
single { createApiService<RawServices>(get(), Const.Url.GITHUB_RAW_URL) }
single { createApiService<GithubApiServices>(get(), Const.Url.GITHUB_API_URL) }
single { createApiService<GithubPageServices>(get(), Const.Url.GITHUB_PAGE_URL) }
single { createApiService<JSDelivrServices>(get(), Const.Url.JS_DELIVR_URL) }
single { createMarkwon(get(), get()) }
}
private class DnsResolver(client: OkHttpClient) : Dns { private class DnsResolver(client: OkHttpClient) : Dns {
private val doh by lazy { private val doh by lazy {
DnsOverHttps.Builder().client(client) DnsOverHttps.Builder().client(client)
.url(HttpUrl.get("https://cloudflare-dns.com/dns-query")) .url("https://cloudflare-dns.com/dns-query".toHttpUrl())
.bootstrapDnsHosts(listOf( .bootstrapDnsHosts(listOf(
InetAddress.getByName("162.159.36.1"), InetAddress.getByName("162.159.36.1"),
InetAddress.getByName("162.159.46.1"), InetAddress.getByName("162.159.46.1"),
@@ -79,10 +61,8 @@ fun createOkHttpClient(context: Context): OkHttpClient {
}) })
} }
if (!Networking.init(context)) { if (!ProviderInstaller.install(context)) {
Info.hasGMS = false Info.hasGMS = false
if (Build.VERSION.SDK_INT < 21)
builder.sslSocketFactory(NoSSLv3SocketFactory())
} }
builder.dns(DnsResolver(builder.build())) builder.dns(DnsResolver(builder.build()))

View File

@@ -1,11 +0,0 @@
package com.topjohnwu.magisk.di
import com.topjohnwu.magisk.data.repository.LogRepository
import com.topjohnwu.magisk.data.repository.NetworkService
import org.koin.dsl.module
val repositoryModule = module {
single { LogRepository(get()) }
single { NetworkService(get(), get(), get(), get()) }
}

View File

@@ -0,0 +1,88 @@
package com.topjohnwu.magisk.di
import android.annotation.SuppressLint
import android.content.Context
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelStoreOwner
import androidx.room.Room
import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.magiskdb.PolicyDao
import com.topjohnwu.magisk.core.magiskdb.SettingsDao
import com.topjohnwu.magisk.core.magiskdb.StringDao
import com.topjohnwu.magisk.core.tasks.RepoUpdater
import com.topjohnwu.magisk.data.database.RepoDatabase
import com.topjohnwu.magisk.data.database.SuLogDatabase
import com.topjohnwu.magisk.data.repository.LogRepository
import com.topjohnwu.magisk.data.repository.NetworkService
import com.topjohnwu.magisk.ktx.deviceProtectedContext
import com.topjohnwu.magisk.ui.home.HomeViewModel
import com.topjohnwu.magisk.ui.install.InstallViewModel
import com.topjohnwu.magisk.ui.log.LogViewModel
import com.topjohnwu.magisk.ui.module.ModuleViewModel
import com.topjohnwu.magisk.ui.settings.SettingsViewModel
import com.topjohnwu.magisk.ui.superuser.SuperuserViewModel
import com.topjohnwu.magisk.ui.surequest.SuRequestViewModel
val AppContext: Context inline get() = ServiceLocator.context
@SuppressLint("StaticFieldLeak")
object ServiceLocator {
lateinit var context: Context
val deContext by lazy { context.deviceProtectedContext }
val timeoutPrefs by lazy { deContext.getSharedPreferences("su_timeout", 0) }
// Database
val policyDB = PolicyDao()
val settingsDB = SettingsDao()
val stringDB = StringDao()
val repoDB by lazy { createRepoDatabase(context).repoDao() }
val sulogDB by lazy { createSuLogDatabase(deContext).suLogDao() }
val repoUpdater by lazy { RepoUpdater(networkService, repoDB) }
val logRepo by lazy { LogRepository(sulogDB) }
// Networking
val okhttp by lazy { createOkHttpClient(context) }
val retrofit by lazy { createRetrofit(okhttp) }
val markwon by lazy { createMarkwon(context, okhttp) }
val networkService by lazy {
NetworkService(
createApiService(retrofit, Const.Url.GITHUB_PAGE_URL),
createApiService(retrofit, Const.Url.GITHUB_RAW_URL),
createApiService(retrofit, Const.Url.JS_DELIVR_URL),
createApiService(retrofit, Const.Url.GITHUB_API_URL)
)
}
object VMFactory : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(clz: Class<T>): T {
return when (clz) {
HomeViewModel::class.java -> HomeViewModel(networkService)
LogViewModel::class.java -> LogViewModel(logRepo)
ModuleViewModel::class.java -> ModuleViewModel(repoDB, repoUpdater)
SettingsViewModel::class.java -> SettingsViewModel(repoDB)
SuperuserViewModel::class.java -> SuperuserViewModel(policyDB)
InstallViewModel::class.java -> InstallViewModel(networkService)
SuRequestViewModel::class.java -> SuRequestViewModel(policyDB, timeoutPrefs)
else -> clz.newInstance()
} as T
}
}
}
inline fun <reified VM : ViewModel> ViewModelStoreOwner.viewModel() =
lazy(LazyThreadSafetyMode.NONE) {
ViewModelProvider(this, ServiceLocator.VMFactory).get(VM::class.java)
}
private fun createRepoDatabase(context: Context) =
Room.databaseBuilder(context, RepoDatabase::class.java, "repo.db")
.fallbackToDestructiveMigration()
.build()
private fun createSuLogDatabase(context: Context) =
Room.databaseBuilder(context, SuLogDatabase::class.java, "sulogs.db")
.fallbackToDestructiveMigration()
.build()

View File

@@ -1,34 +0,0 @@
package com.topjohnwu.magisk.di
import com.topjohnwu.magisk.ui.MainViewModel
import com.topjohnwu.magisk.ui.flash.FlashFragmentArgs
import com.topjohnwu.magisk.ui.flash.FlashViewModel
import com.topjohnwu.magisk.ui.hide.HideViewModel
import com.topjohnwu.magisk.ui.home.HomeViewModel
import com.topjohnwu.magisk.ui.install.InstallViewModel
import com.topjohnwu.magisk.ui.log.LogViewModel
import com.topjohnwu.magisk.ui.module.ModuleViewModel
import com.topjohnwu.magisk.ui.safetynet.SafetynetViewModel
import com.topjohnwu.magisk.ui.settings.SettingsViewModel
import com.topjohnwu.magisk.ui.superuser.SuperuserViewModel
import com.topjohnwu.magisk.ui.surequest.SuRequestViewModel
import com.topjohnwu.magisk.ui.theme.ThemeViewModel
import org.koin.androidx.viewmodel.dsl.viewModel
import org.koin.dsl.module
val viewModelModules = module {
viewModel { HideViewModel() }
viewModel { HomeViewModel(get()) }
viewModel { LogViewModel(get()) }
viewModel { ModuleViewModel(get(), get()) }
viewModel { SafetynetViewModel() }
viewModel { SettingsViewModel(get()) }
viewModel { SuperuserViewModel(get(), get()) }
viewModel { ThemeViewModel() }
viewModel { InstallViewModel(get()) }
viewModel { MainViewModel() }
// Legacy
viewModel { (args: FlashFragmentArgs) -> FlashViewModel(args) }
viewModel { SuRequestViewModel(get(), get(), get(SUTimeout), get()) }
}

View File

@@ -29,10 +29,10 @@ object RebootEvent {
fun inflateMenu(activity: BaseActivity): PopupMenu { fun inflateMenu(activity: BaseActivity): PopupMenu {
val themeWrapper = ContextThemeWrapper(activity, R.style.Foundation_PopupMenu) val themeWrapper = ContextThemeWrapper(activity, R.style.Foundation_PopupMenu)
val menu = PopupMenu(themeWrapper, activity.findViewById(R.id.action_reboot)) val menu = PopupMenu(themeWrapper, activity.findViewById(R.id.action_reboot))
activity.menuInflater.inflate(R.menu.menu_reboot, menu.menu)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R &&
activity.getSystemService<PowerManager>()?.isRebootingUserspaceSupported == true) activity.getSystemService<PowerManager>()?.isRebootingUserspaceSupported == true)
menu.menu.getItem(R.id.action_reboot_userspace).isVisible = true menu.menu.findItem(R.id.action_reboot_userspace).isVisible = true
activity.menuInflater.inflate(R.menu.menu_reboot, menu.menu)
menu.setOnMenuItemClickListener(::reboot) menu.setOnMenuItemClickListener(::reboot)
return menu return menu
} }

View File

@@ -6,32 +6,33 @@ import com.google.android.material.snackbar.Snackbar
import com.topjohnwu.magisk.arch.ActivityExecutor import com.topjohnwu.magisk.arch.ActivityExecutor
import com.topjohnwu.magisk.arch.BaseUIActivity import com.topjohnwu.magisk.arch.BaseUIActivity
import com.topjohnwu.magisk.arch.ViewEvent import com.topjohnwu.magisk.arch.ViewEvent
import com.topjohnwu.magisk.utils.TransitiveText import com.topjohnwu.magisk.utils.TextHolder
import com.topjohnwu.magisk.utils.asText
class SnackbarEvent private constructor( class SnackbarEvent constructor(
private val msg: TransitiveText, private val msg: TextHolder,
private val length: Int, private val length: Int = Snackbar.LENGTH_SHORT,
private val builder: Snackbar.() -> Unit private val builder: Snackbar.() -> Unit = {}
) : ViewEvent(), ActivityExecutor { ) : ViewEvent(), ActivityExecutor {
constructor( constructor(
@StringRes res: Int, @StringRes res: Int,
length: Int = Snackbar.LENGTH_SHORT, length: Int = Snackbar.LENGTH_SHORT,
builder: Snackbar.() -> Unit = {} builder: Snackbar.() -> Unit = {}
) : this(TransitiveText.Res(res), length, builder) ) : this(res.asText(), length, builder)
constructor( constructor(
message: String, msg: String,
length: Int = Snackbar.LENGTH_SHORT, length: Int = Snackbar.LENGTH_SHORT,
builder: Snackbar.() -> Unit = {} builder: Snackbar.() -> Unit = {}
) : this(TransitiveText.String(message), length, builder) ) : this(msg.asText(), length, builder)
private fun snackbar( private fun snackbar(
view: View, view: View,
message: String, message: String,
length: Int = Snackbar.LENGTH_SHORT, length: Int,
builder: Snackbar.() -> Unit = {} builder: Snackbar.() -> Unit
) = Snackbar.make(view, message, length).apply(builder).show() ) = Snackbar.make(view, message, length).apply(builder).show()
override fun invoke(activity: BaseUIActivity<*, *>) { override fun invoke(activity: BaseUIActivity<*, *>) {
@@ -39,5 +40,4 @@ class SnackbarEvent private constructor(
msg.getText(activity.resources).toString(), msg.getText(activity.resources).toString(),
length, builder) length, builder)
} }
} }

View File

@@ -14,20 +14,22 @@ import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.base.ActivityResultCallback import com.topjohnwu.magisk.core.base.ActivityResultCallback
import com.topjohnwu.magisk.core.base.BaseActivity import com.topjohnwu.magisk.core.base.BaseActivity
import com.topjohnwu.magisk.core.model.module.OnlineModule import com.topjohnwu.magisk.core.model.module.OnlineModule
import com.topjohnwu.magisk.events.dialog.MarkDownDialog
import com.topjohnwu.magisk.utils.Utils import com.topjohnwu.magisk.utils.Utils
import com.topjohnwu.magisk.view.MarkDownWindow import com.topjohnwu.magisk.view.MagiskDialog
import com.topjohnwu.magisk.view.Shortcuts import com.topjohnwu.magisk.view.Shortcuts
import kotlinx.coroutines.launch
class ViewActionEvent(val action: BaseActivity.() -> Unit) : ViewEvent(), ActivityExecutor { class ViewActionEvent(val action: BaseActivity.() -> Unit) : ViewEvent(), ActivityExecutor {
override fun invoke(activity: BaseUIActivity<*, *>) = action(activity) override fun invoke(activity: BaseUIActivity<*, *>) = action(activity)
} }
class OpenReadmeEvent(val item: OnlineModule) : ViewEventWithScope(), ContextExecutor { class OpenReadmeEvent(private val item: OnlineModule) : MarkDownDialog() {
override fun invoke(context: Context) { override suspend fun getMarkdownText() = item.notes()
scope.launch { override fun build(dialog: MagiskDialog) {
MarkDownWindow.show(context, null, item::notes) super.build(dialog)
} dialog.applyButton(MagiskDialog.ButtonType.NEGATIVE) {
titleRes = android.R.string.cancel
}.cancellable(true)
} }
} }
@@ -78,7 +80,7 @@ class MagiskInstallFileEvent(private val callback: ActivityResultCallback)
override fun invoke(activity: BaseUIActivity<*, *>) { override fun invoke(activity: BaseUIActivity<*, *>) {
val intent = Intent(Intent.ACTION_GET_CONTENT).setType("*/*") val intent = Intent(Intent.ACTION_GET_CONTENT).setType("*/*")
try { try {
activity.startActivityForResult(intent, Const.ID.SELECT_FILE, callback) activity.startActivityForResult(intent, callback)
Utils.toast(R.string.patch_file_msg, Toast.LENGTH_LONG) Utils.toast(R.string.patch_file_msg, Toast.LENGTH_LONG)
} catch (e: ActivityNotFoundException) { } catch (e: ActivityNotFoundException) {
Utils.toast(R.string.app_not_found, Toast.LENGTH_SHORT) Utils.toast(R.string.app_not_found, Toast.LENGTH_SHORT)
@@ -107,10 +109,10 @@ class SelectModuleEvent : ViewEvent(), FragmentExecutor {
val intent = Intent(Intent.ACTION_GET_CONTENT).setType("application/zip") val intent = Intent(Intent.ACTION_GET_CONTENT).setType("application/zip")
try { try {
fragment.apply { fragment.apply {
activity.startActivityForResult(intent, Const.ID.FETCH_ZIP) { code, intent -> activity.startActivityForResult(intent) { code, intent ->
if (code == Activity.RESULT_OK && intent != null) { if (code == Activity.RESULT_OK && intent != null) {
intent.data?.also { intent.data?.also {
MainDirections.actionFlashFragment(it, Const.Value.FLASH_ZIP).navigate() MainDirections.actionFlashFragment(Const.Value.FLASH_ZIP, it).navigate()
} }
} }
} }

View File

@@ -10,7 +10,9 @@ abstract class DialogEvent : ViewEvent(), ActivityExecutor {
protected lateinit var dialog: MagiskDialog protected lateinit var dialog: MagiskDialog
override fun invoke(activity: BaseUIActivity<*, *>) { override fun invoke(activity: BaseUIActivity<*, *>) {
dialog = MagiskDialog(activity).apply(this::build).reveal() dialog = MagiskDialog(activity)
.apply { setOwnerActivity(activity) }
.apply(this::build).reveal()
} }
abstract fun build(dialog: MagiskDialog) abstract fun build(dialog: MagiskDialog)

View File

@@ -1,15 +1,11 @@
package com.topjohnwu.magisk.events.dialog package com.topjohnwu.magisk.events.dialog
import android.content.BroadcastReceiver import androidx.lifecycle.lifecycleScope
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.download.Action.EnvFix import com.topjohnwu.magisk.core.base.BaseActivity
import com.topjohnwu.magisk.core.download.DownloadService import com.topjohnwu.magisk.core.tasks.MagiskInstaller
import com.topjohnwu.magisk.core.download.Subject.Magisk
import com.topjohnwu.magisk.view.MagiskDialog import com.topjohnwu.magisk.view.MagiskDialog
import kotlinx.coroutines.launch
class EnvFixDialog : DialogEvent() { class EnvFixDialog : DialogEvent() {
@@ -24,20 +20,17 @@ class EnvFixDialog : DialogEvent() {
.applyMessage(R.string.setup_msg) .applyMessage(R.string.setup_msg)
.resetButtons() .resetButtons()
.cancellable(false) .cancellable(false)
val lbm = LocalBroadcastManager.getInstance(dialog.context) (dialog.ownerActivity as BaseActivity).lifecycleScope.launch {
lbm.registerReceiver(object : BroadcastReceiver() { MagiskInstaller.FixEnv {
override fun onReceive(context: Context, intent: Intent?) {
dialog.dismiss() dialog.dismiss()
lbm.unregisterReceiver(this) }.exec()
} }
}, IntentFilter(DISMISS))
DownloadService.start(dialog.context, Magisk(EnvFix))
} }
} }
.applyButton(MagiskDialog.ButtonType.NEGATIVE) { .applyButton(MagiskDialog.ButtonType.NEGATIVE) {
titleRes = android.R.string.cancel titleRes = android.R.string.cancel
} }
.let { Unit } .let { }
companion object { companion object {
const val DISMISS = "com.topjohnwu.magisk.ENV_DONE" const val DISMISS = "com.topjohnwu.magisk.ENV_DONE"

View File

@@ -4,37 +4,35 @@ import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.Info import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.download.DownloadService import com.topjohnwu.magisk.core.download.DownloadService
import com.topjohnwu.magisk.core.download.Subject import com.topjohnwu.magisk.core.download.Subject
import com.topjohnwu.magisk.ktx.res import com.topjohnwu.magisk.di.AppContext
import com.topjohnwu.magisk.di.ServiceLocator
import com.topjohnwu.magisk.view.MagiskDialog import com.topjohnwu.magisk.view.MagiskDialog
import com.topjohnwu.magisk.view.MarkDownWindow import java.io.File
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
class ManagerInstallDialog : DialogEvent() { class ManagerInstallDialog : MarkDownDialog() {
private val svc get() = ServiceLocator.networkService
override suspend fun getMarkdownText(): String {
val text = svc.fetchString(Info.remote.magisk.note)
// Cache the changelog
AppContext.cacheDir.listFiles { _, name -> name.endsWith(".md") }.orEmpty().forEach {
it.delete()
}
File(AppContext.cacheDir, "${Info.remote.magisk.versionCode}.md").writeText(text)
return text
}
override fun build(dialog: MagiskDialog) { override fun build(dialog: MagiskDialog) {
super.build(dialog)
with(dialog) { with(dialog) {
val subject = Subject.Manager()
applyTitle(R.string.repo_install_title.res(R.string.app_name.res()))
applyMessage(R.string.repo_install_msg.res(subject.title))
setCancelable(true) setCancelable(true)
applyButton(MagiskDialog.ButtonType.POSITIVE) { applyButton(MagiskDialog.ButtonType.POSITIVE) {
titleRes = R.string.install titleRes = R.string.install
onClick { DownloadService.start(context, subject) } onClick { DownloadService.start(context, Subject.Manager()) }
} }
if (Info.remote.app.note.isEmpty()) return
applyButton(MagiskDialog.ButtonType.NEGATIVE) { applyButton(MagiskDialog.ButtonType.NEGATIVE) {
titleRes = R.string.app_changelog titleRes = android.R.string.cancel
onClick {
GlobalScope.launch(Dispatchers.Main.immediate) {
MarkDownWindow.show(context, null, Info.remote.app.note)
}
}
} }
} }
} }

View File

@@ -0,0 +1,41 @@
package com.topjohnwu.magisk.events.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.di.ServiceLocator
import com.topjohnwu.magisk.view.MagiskDialog
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import timber.log.Timber
import kotlin.coroutines.cancellation.CancellationException
abstract class MarkDownDialog : DialogEvent() {
abstract suspend fun getMarkdownText(): String
@CallSuper
override fun build(dialog: MagiskDialog) {
with(dialog) {
val view = LayoutInflater.from(context).inflate(R.layout.markdown_window_md2, null)
applyView(view)
(ownerActivity as BaseActivity).lifecycleScope.launch {
val tv = view.findViewById<TextView>(R.id.md_txt)
withContext(Dispatchers.IO) {
try {
ServiceLocator.markwon.setMarkdown(tv, getMarkdownText())
} catch (e: Exception) {
if (e is CancellationException)
throw e
Timber.e(e)
tv.post { tv.setText(R.string.download_file_error) }
}
}
}
}
}
}

View File

@@ -14,7 +14,7 @@ class ModuleInstallDialog(private val item: OnlineModule) : DialogEvent() {
with(dialog) { with(dialog) {
fun download(install: Boolean) { fun download(install: Boolean) {
val config = if (install) Action.Flash.Primary else Action.Download val config = if (install) Action.Flash else Action.Download
val subject = Subject.Module(item, config) val subject = Subject.Module(item, config)
DownloadService.start(context, subject) DownloadService.start(context, subject)
} }

View File

@@ -3,10 +3,8 @@ package com.topjohnwu.magisk.events.dialog
import android.app.ProgressDialog import android.app.ProgressDialog
import android.widget.Toast import android.widget.Toast
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.Info import com.topjohnwu.magisk.arch.BaseUIActivity
import com.topjohnwu.magisk.core.download.Action import com.topjohnwu.magisk.ui.flash.FlashFragment
import com.topjohnwu.magisk.core.download.DownloadService
import com.topjohnwu.magisk.core.download.Subject
import com.topjohnwu.magisk.utils.Utils import com.topjohnwu.magisk.utils.Utils
import com.topjohnwu.magisk.view.MagiskDialog import com.topjohnwu.magisk.view.MagiskDialog
import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.Shell
@@ -20,12 +18,10 @@ class UninstallDialog : DialogEvent() {
titleRes = R.string.restore_img titleRes = R.string.restore_img
onClick { restore() } onClick { restore() }
} }
if (Info.remote.uninstaller.link.isNotEmpty()) { .applyButton(MagiskDialog.ButtonType.NEGATIVE) {
dialog.applyButton(MagiskDialog.ButtonType.NEGATIVE) {
titleRes = R.string.complete_uninstall titleRes = R.string.complete_uninstall
onClick { completeUninstall() } onClick { completeUninstall() }
} }
}
} }
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
@@ -46,7 +42,8 @@ class UninstallDialog : DialogEvent() {
} }
private fun completeUninstall() { private fun completeUninstall() {
DownloadService.start(dialog.context, Subject.Magisk(Action.Uninstall)) (dialog.ownerActivity as? BaseUIActivity<*, *>)
?.navigation?.navigate(FlashFragment.uninstall())
} }
} }

View File

@@ -21,7 +21,6 @@ import android.graphics.drawable.AdaptiveIconDrawable
import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.LayerDrawable import android.graphics.drawable.LayerDrawable
import android.net.Uri import android.net.Uri
import android.os.Build
import android.os.Build.VERSION.SDK_INT import android.os.Build.VERSION.SDK_INT
import android.text.PrecomputedText import android.text.PrecomputedText
import android.view.View import android.view.View
@@ -41,11 +40,13 @@ import androidx.core.widget.TextViewCompat
import androidx.databinding.BindingAdapter import androidx.databinding.BindingAdapter
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.interpolator.view.animation.FastOutSlowInInterpolator import androidx.interpolator.view.animation.FastOutSlowInInterpolator
import androidx.lifecycle.lifecycleScope
import androidx.transition.AutoTransition import androidx.transition.AutoTransition
import androidx.transition.TransitionManager import androidx.transition.TransitionManager
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.AssetHack
import com.topjohnwu.magisk.core.Const import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.ResMgr import com.topjohnwu.magisk.core.base.BaseActivity
import com.topjohnwu.magisk.core.utils.currentLocale import com.topjohnwu.magisk.core.utils.currentLocale
import com.topjohnwu.magisk.utils.DynamicClassLoader import com.topjohnwu.magisk.utils.DynamicClassLoader
import com.topjohnwu.magisk.utils.Utils import com.topjohnwu.magisk.utils.Utils
@@ -57,8 +58,6 @@ import kotlinx.coroutines.launch
import java.io.File import java.io.File
import java.lang.reflect.Array as JArray import java.lang.reflect.Array as JArray
val packageName: String get() = get<Context>().packageName
val ServiceInfo.isIsolated get() = (flags and FLAG_ISOLATED_PROCESS) != 0 val ServiceInfo.isIsolated get() = (flags and FLAG_ISOLATED_PROCESS) != 0
@get:SuppressLint("InlinedApi") @get:SuppressLint("InlinedApi")
@@ -83,6 +82,11 @@ fun Context.getBitmap(id: Int): Bitmap {
return bitmap return bitmap
} }
val Context.deviceProtectedContext: Context get() =
if (SDK_INT >= 24) {
createDeviceProtectedStorageContext()
} else { this }
fun Intent.startActivity(context: Context) = context.startActivity(this) fun Intent.startActivity(context: Context) = context.startActivity(this)
fun Intent.startActivityWithRoot() { fun Intent.startActivityWithRoot() {
@@ -227,15 +231,13 @@ fun Context.colorStateListCompat(@ColorRes id: Int) = try {
null null
} }
fun Context.drawableCompat(@DrawableRes id: Int) = ContextCompat.getDrawable(this, id) fun Context.drawableCompat(@DrawableRes id: Int) = AppCompatResources.getDrawable(this, id)
/** /**
* Pass [start] and [end] dimensions, function will return left and right * Pass [start] and [end] dimensions, function will return left and right
* with respect to RTL layout direction * with respect to RTL layout direction
*/ */
fun Context.startEndToLeftRight(start: Int, end: Int): Pair<Int, Int> { fun Context.startEndToLeftRight(start: Int, end: Int): Pair<Int, Int> {
if (SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 && if (resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_RTL) {
resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_RTL
) {
return end to start return end to start
} }
return start to end return start to end
@@ -243,8 +245,7 @@ fun Context.startEndToLeftRight(start: Int, end: Int): Pair<Int, Int> {
fun Context.openUrl(url: String) = Utils.openLink(this, url.toUri()) fun Context.openUrl(url: String) = Utils.openLink(this, url.toUri())
@Suppress("FunctionName") inline fun <reified T> T.createClassLoader(apk: File) =
inline fun <reified T> T.DynamicClassLoader(apk: File) =
DynamicClassLoader(apk, T::class.java.classLoader) DynamicClassLoader(apk, T::class.java.classLoader)
fun Context.unwrap(): Context { fun Context.unwrap(): Context {
@@ -295,8 +296,21 @@ fun ViewGroup.startAnimations() {
) )
} }
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
}
}
var View.coroutineScope: CoroutineScope var View.coroutineScope: CoroutineScope
get() = getTag(R.id.coroutineScope) as? CoroutineScope ?: GlobalScope get() = getTag(R.id.coroutineScope) as? CoroutineScope
?: (activity as? BaseActivity)?.lifecycleScope
?: GlobalScope
set(value) = setTag(R.id.coroutineScope, value) set(value) = setTag(R.id.coroutineScope, value)
@set:BindingAdapter("precomputedText") @set:BindingAdapter("precomputedText")
@@ -305,16 +319,6 @@ var TextView.precomputedText: CharSequence
set(value) { set(value) {
val callback = tag as? Runnable val callback = tag as? Runnable
// Don't even bother pre 21
if (SDK_INT < 21) {
post {
text = value
isGone = false
callback?.run()
}
return
}
coroutineScope.launch(Dispatchers.IO) { coroutineScope.launch(Dispatchers.IO) {
if (SDK_INT >= 29) { if (SDK_INT >= 29) {
// Internally PrecomputedTextCompat will use platform API on API 29+ // Internally PrecomputedTextCompat will use platform API on API 29+
@@ -346,6 +350,16 @@ var TextView.precomputedText: CharSequence
} }
fun Int.dpInPx(): Int { fun Int.dpInPx(): Int {
val scale = ResMgr.resource.displayMetrics.density val scale = AssetHack.resource.displayMetrics.density
return (this * scale + 0.5).toInt() return (this * scale + 0.5).toInt()
} }
@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
}

View File

@@ -1,19 +1,17 @@
package com.topjohnwu.magisk.ktx package com.topjohnwu.magisk.ktx
import android.os.Build
import androidx.collection.SparseArrayCompat import androidx.collection.SparseArrayCompat
import timber.log.Timber import timber.log.Timber
import java.io.File import java.io.File
import java.io.InputStream import java.io.InputStream
import java.io.OutputStream import java.io.OutputStream
import java.lang.reflect.Field import java.lang.reflect.Field
import java.lang.reflect.Method
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
import java.util.zip.ZipEntry import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream import java.util.zip.ZipInputStream
fun ZipInputStream.forEach(callback: (ZipEntry) -> Unit) { inline fun ZipInputStream.forEach(callback: (ZipEntry) -> Unit) {
var entry: ZipEntry? = nextEntry var entry: ZipEntry? = nextEntry
while (entry != null) { while (entry != null) {
callback(entry) callback(entry)
@@ -49,93 +47,8 @@ fun <T> MutableList<T>.synchronized() = Collections.synchronizedList(this)
fun <T> MutableSet<T>.synchronized() = Collections.synchronizedSet(this) fun <T> MutableSet<T>.synchronized() = Collections.synchronizedSet(this)
fun <K, V> MutableMap<K, V>.synchronized() = Collections.synchronizedMap(this) fun <K, V> MutableMap<K, V>.synchronized() = Collections.synchronizedMap(this)
fun String.langTagToLocale(): Locale {
if (Build.VERSION.SDK_INT >= 21) {
return Locale.forLanguageTag(this)
} else {
val tok = split("-".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
if (tok.isEmpty()) {
return Locale("")
}
val language = when (tok[0]) {
"und" -> "" // Undefined
"fil" -> "tl" // Filipino
else -> tok[0]
}
if (language.length != 2 && language.length != 3)
return Locale("")
if (tok.size == 1)
return Locale(language)
val country = tok[1]
return if (country.length != 2 && country.length != 3) Locale(language)
else Locale(language, country)
}
}
fun Locale.toLangTag(): String {
if (Build.VERSION.SDK_INT >= 21) {
return toLanguageTag()
} else {
var language = language
var country = country
var variant = variant
when {
language.isEmpty() || !language.matches("\\p{Alpha}{2,8}".toRegex()) ->
language = "und" // Follow the Locale#toLanguageTag() implementation
language == "iw" -> language = "he" // correct deprecated "Hebrew"
language == "in" -> language = "id" // correct deprecated "Indonesian"
language == "ji" -> language = "yi" // correct deprecated "Yiddish"
}
// ensure valid country code, if not well formed, it's omitted
// variant subtags that begin with a letter must be at least 5 characters long
// ensure valid country code, if not well formed, it's omitted
if (!country.matches("\\p{Alpha}{2}|\\p{Digit}{3}".toRegex())) {
country = ""
}
// variant subtags that begin with a letter must be at least 5 characters long
if (!variant.matches("\\p{Alnum}{5,8}|\\p{Digit}\\p{Alnum}{3}".toRegex())) {
variant = ""
}
val tag = StringBuilder(language)
if (country.isNotEmpty())
tag.append('-').append(country)
if (variant.isNotEmpty())
tag.append('-').append(variant)
return tag.toString()
}
}
fun SimpleDateFormat.parseOrNull(date: String) = fun SimpleDateFormat.parseOrNull(date: String) =
runCatching { parse(date) }.onFailure { Timber.e(it) }.getOrNull() runCatching { parse(date) }.onFailure { Timber.e(it) }.getOrNull()
// Reflection hacks fun Class<*>.reflectField(name: String): Field =
getDeclaredField(name).apply { isAccessible = true }
private val loadClass = ClassLoader::class.java.getMethod("loadClass", String::class.java)
private val getDeclaredMethod = Class::class.java.getMethod("getDeclaredMethod",
String::class.java, arrayOf<Class<*>>()::class.java)
private val getDeclaredField = Class::class.java.getMethod("getDeclaredField", String::class.java)
fun ClassLoader.forceLoadClass(name: String) =
runCatching { loadClass.invoke(this, name) }.getOrNull() as Class<*>?
fun Class<*>.forceGetDeclaredMethod(name: String, vararg types: Class<*>) =
(runCatching { getDeclaredMethod.invoke(this, name, types) }.getOrNull() as Method?)?.also {
it.isAccessible = true
}
fun Class<*>.forceGetDeclaredField(name: String) =
(runCatching { getDeclaredField.invoke(this, name) }.getOrNull() as Field?)?.also {
it.isAccessible = true
}
inline fun <reified T> T.forceGetClass(name: String) =
T::class.java.classLoader?.forceLoadClass(name)
fun Class<*>.forceGetField(name: String): Field? =
forceGetDeclaredField(name) ?: superclass?.forceGetField(name)
fun Class<*>.forceGetMethod(name: String, vararg types: Class<*>): Method? =
forceGetDeclaredMethod(name, *types) ?: superclass?.forceGetMethod(name, *types)

View File

@@ -1,17 +0,0 @@
package com.topjohnwu.magisk.ktx
import org.koin.core.context.KoinContextHandler
import org.koin.core.parameter.ParametersDefinition
import org.koin.core.qualifier.Qualifier
fun getKoin() = KoinContextHandler.get()
inline fun <reified T> inject(
qualifier: Qualifier? = null,
noinline parameters: ParametersDefinition? = null
) = lazy { get<T>(qualifier, parameters) }
inline fun <reified T> get(
qualifier: Qualifier? = null,
noinline parameters: ParametersDefinition? = null
): T = getKoin().get(qualifier, parameters)

View File

@@ -1,8 +1,5 @@
package com.topjohnwu.magisk.ktx package com.topjohnwu.magisk.ktx
import android.content.res.Resources
import android.os.Build
val specialChars = arrayOf('!', '@', '#', '$', '%', '&', '?') val specialChars = arrayOf('!', '@', '#', '$', '%', '&', '?')
val fullSpecialChars = arrayOf('', '', '', '', '', '', '') val fullSpecialChars = arrayOf('', '', '', '', '', '', '')
@@ -13,10 +10,7 @@ fun String.isCJK(): Boolean {
return false return false
} }
fun isCJK(codepoint: Int): Boolean { fun isCJK(codepoint: Int) = Character.isIdeographic(codepoint)
return if (Build.VERSION.SDK_INT < 19) false /* Pre 5.0 don't need to be pretty.. */
else Character.isIdeographic(codepoint)
}
fun String.replaceRandomWithSpecial(passes: Int): String { fun String.replaceRandomWithSpecial(passes: Int): String {
var string = this var string = this
@@ -38,11 +32,6 @@ fun String.replaceRandomWithSpecial(): String {
fun StringBuilder.appendIf(condition: Boolean, builder: StringBuilder.() -> Unit) = fun StringBuilder.appendIf(condition: Boolean, builder: StringBuilder.() -> Unit) =
if (condition) apply(builder) else this if (condition) apply(builder) else this
fun Int.res(vararg args: Any): String {
val resources: Resources by inject()
return resources.getString(this, *args)
}
fun String.trimEmptyToNull(): String? = if (isBlank()) null else this fun String.trimEmptyToNull(): String? = if (isBlank()) null else this
fun String.legalFilename() = replace(" ", "_").replace("'", "").replace("\"", "") fun String.legalFilename() = replace(" ", "_").replace("'", "").replace("\"", "")

View File

@@ -1,7 +1,7 @@
package com.topjohnwu.magisk.ui package com.topjohnwu.magisk.ui
import android.content.Intent import android.content.Intent
import android.os.Build import android.content.pm.ApplicationInfo
import android.os.Bundle import android.os.Bundle
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
@@ -10,9 +10,7 @@ import android.view.WindowManager
import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.content.pm.ShortcutManagerCompat import androidx.core.content.pm.ShortcutManagerCompat
import androidx.core.view.forEach import androidx.core.view.forEach
import androidx.core.view.updateLayoutParams
import androidx.navigation.NavDirections import androidx.navigation.NavDirections
import com.google.android.material.card.MaterialCardView
import com.topjohnwu.magisk.MainDirections import com.topjohnwu.magisk.MainDirections
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.arch.BaseUIActivity import com.topjohnwu.magisk.arch.BaseUIActivity
@@ -20,15 +18,14 @@ import com.topjohnwu.magisk.arch.BaseViewModel
import com.topjohnwu.magisk.arch.ReselectionTarget import com.topjohnwu.magisk.arch.ReselectionTarget
import com.topjohnwu.magisk.core.* import com.topjohnwu.magisk.core.*
import com.topjohnwu.magisk.databinding.ActivityMainMd2Binding import com.topjohnwu.magisk.databinding.ActivityMainMd2Binding
import com.topjohnwu.magisk.di.viewModel
import com.topjohnwu.magisk.ktx.startAnimations import com.topjohnwu.magisk.ktx.startAnimations
import com.topjohnwu.magisk.ui.home.HomeFragmentDirections import com.topjohnwu.magisk.ui.home.HomeFragmentDirections
import com.topjohnwu.magisk.utils.HideBottomViewOnScrollBehavior
import com.topjohnwu.magisk.utils.HideTopViewOnScrollBehavior
import com.topjohnwu.magisk.utils.HideableBehavior import com.topjohnwu.magisk.utils.HideableBehavior
import com.topjohnwu.magisk.utils.Utils import com.topjohnwu.magisk.utils.Utils
import com.topjohnwu.magisk.view.MagiskDialog import com.topjohnwu.magisk.view.MagiskDialog
import com.topjohnwu.magisk.view.Shortcuts import com.topjohnwu.magisk.view.Shortcuts
import org.koin.androidx.viewmodel.ext.android.viewModel import java.io.File
class MainViewModel : BaseViewModel() class MainViewModel : BaseViewModel()
@@ -36,7 +33,7 @@ open class MainActivity : BaseUIActivity<MainViewModel, ActivityMainMd2Binding>(
override val layoutRes = R.layout.activity_main_md2 override val layoutRes = R.layout.activity_main_md2
override val viewModel by viewModel<MainViewModel>() override val viewModel by viewModel<MainViewModel>()
override val navHost: Int = R.id.main_nav_host override val navHostId: Int = R.id.main_nav_host
private var isRootFragment = true private var isRootFragment = true
@@ -77,12 +74,6 @@ open class MainActivity : BaseUIActivity<MainViewModel, ActivityMainMd2Binding>(
setSupportActionBar(binding.mainToolbar) setSupportActionBar(binding.mainToolbar)
binding.mainToolbarWrapper.updateLayoutParams<CoordinatorLayout.LayoutParams> {
behavior = HideTopViewOnScrollBehavior<MaterialCardView>()
}
binding.mainBottomBar.updateLayoutParams<CoordinatorLayout.LayoutParams> {
behavior = HideBottomViewOnScrollBehavior<MaterialCardView>()
}
binding.mainNavigation.setOnNavigationItemSelectedListener { binding.mainNavigation.setOnNavigationItemSelectedListener {
getScreen(it.itemId)?.navigate() getScreen(it.itemId)?.navigate()
true true
@@ -91,7 +82,7 @@ open class MainActivity : BaseUIActivity<MainViewModel, ActivityMainMd2Binding>(
(currentFragment as? ReselectionTarget)?.onReselected() (currentFragment as? ReselectionTarget)?.onReselected()
} }
val section = if (intent.action == ACTION_APPLICATION_PREFERENCES) Const.Nav.SETTINGS val section = if (intent.action == Intent.ACTION_APPLICATION_PREFERENCES) Const.Nav.SETTINGS
else intent.getStringExtra(Const.Key.OPEN_SECTION) else intent.getStringExtra(Const.Key.OPEN_SECTION)
getScreen(section)?.navigate() getScreen(section)?.navigate()
@@ -131,10 +122,7 @@ open class MainActivity : BaseUIActivity<MainViewModel, ActivityMainMd2Binding>(
val topView = binding.mainToolbarWrapper val topView = binding.mainToolbarWrapper
val bottomView = binding.mainBottomBar val bottomView = binding.mainBottomBar
if ( if (!binding.mainBottomBar.isAttachedToWindow) {
Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT &&
!binding.mainBottomBar.isAttachedToWindow
) {
binding.mainBottomBar.viewTreeObserver.addOnWindowAttachListener(object : binding.mainBottomBar.viewTreeObserver.addOnWindowAttachListener(object :
ViewTreeObserver.OnWindowAttachListener { ViewTreeObserver.OnWindowAttachListener {
@@ -174,9 +162,9 @@ open class MainActivity : BaseUIActivity<MainViewModel, ActivityMainMd2Binding>(
private fun getScreen(name: String?): NavDirections? { private fun getScreen(name: String?): NavDirections? {
return when (name) { return when (name) {
Const.Nav.SUPERUSER -> HomeFragmentDirections.actionSuperuserFragment() Const.Nav.SUPERUSER -> MainDirections.actionSuperuserFragment()
Const.Nav.HIDE -> HomeFragmentDirections.actionHideFragment() Const.Nav.HIDE -> MainDirections.actionHideFragment()
Const.Nav.MODULES -> HomeFragmentDirections.actionModuleFragment() Const.Nav.MODULES -> MainDirections.actionModuleFragment()
Const.Nav.SETTINGS -> HomeFragmentDirections.actionHomeFragmentToSettingsFragment() Const.Nav.SETTINGS -> HomeFragmentDirections.actionHomeFragmentToSettingsFragment()
else -> null else -> null
} }
@@ -198,7 +186,37 @@ open class MainActivity : BaseUIActivity<MainViewModel, ActivityMainMd2Binding>(
.applyTitle(R.string.unsupport_magisk_title) .applyTitle(R.string.unsupport_magisk_title)
.applyMessage(R.string.unsupport_magisk_msg, Const.Version.MIN_VERSION) .applyMessage(R.string.unsupport_magisk_msg, Const.Version.MIN_VERSION)
.applyButton(MagiskDialog.ButtonType.POSITIVE) { titleRes = android.R.string.ok } .applyButton(MagiskDialog.ButtonType.POSITIVE) { titleRes = android.R.string.ok }
.cancellable(true) .cancellable(false)
.reveal()
}
if (!Info.isEmulator && Info.env.isActive && System.getenv("PATH")
?.split(':')
?.filterNot { File("$it/magisk").exists() }
?.any { File("$it/su").exists() } == true) {
MagiskDialog(this)
.applyTitle(R.string.unsupport_general_title)
.applyMessage(R.string.unsupport_other_su_msg)
.applyButton(MagiskDialog.ButtonType.POSITIVE) { titleRes = android.R.string.ok }
.cancellable(false)
.reveal()
}
if (applicationInfo.flags and ApplicationInfo.FLAG_SYSTEM != 0) {
MagiskDialog(this)
.applyTitle(R.string.unsupport_general_title)
.applyMessage(R.string.unsupport_system_app_msg)
.applyButton(MagiskDialog.ButtonType.POSITIVE) { titleRes = android.R.string.ok }
.cancellable(false)
.reveal()
}
if (applicationInfo.flags and ApplicationInfo.FLAG_EXTERNAL_STORAGE != 0) {
MagiskDialog(this)
.applyTitle(R.string.unsupport_general_title)
.applyMessage(R.string.unsupport_external_storage_msg)
.applyButton(MagiskDialog.ButtonType.POSITIVE) { titleRes = android.R.string.ok }
.cancellable(false)
.reveal() .reveal()
} }
} }
@@ -222,11 +240,4 @@ open class MainActivity : BaseUIActivity<MainViewModel, ActivityMainMd2Binding>(
.reveal() .reveal()
} }
} }
companion object {
private val ACTION_APPLICATION_PREFERENCES get() =
if (Build.VERSION.SDK_INT >= 24) Intent.ACTION_APPLICATION_PREFERENCES
else "???"
}
} }

View File

@@ -7,27 +7,27 @@ import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.view.* import android.view.*
import androidx.navigation.NavDeepLinkBuilder import androidx.navigation.NavDeepLinkBuilder
import com.topjohnwu.magisk.MainDirections
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.arch.BaseUIActivity
import com.topjohnwu.magisk.arch.BaseUIFragment import com.topjohnwu.magisk.arch.BaseUIFragment
import com.topjohnwu.magisk.core.Const import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.cmp import com.topjohnwu.magisk.core.cmp
import com.topjohnwu.magisk.databinding.FragmentFlashMd2Binding import com.topjohnwu.magisk.databinding.FragmentFlashMd2Binding
import com.topjohnwu.magisk.di.viewModel
import com.topjohnwu.magisk.ui.MainActivity import com.topjohnwu.magisk.ui.MainActivity
import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koin.core.parameter.parametersOf
import com.topjohnwu.magisk.MainDirections.Companion.actionFlashFragment as toFlash
import com.topjohnwu.magisk.ui.flash.FlashFragmentArgs as args
class FlashFragment : BaseUIFragment<FlashViewModel, FragmentFlashMd2Binding>() { class FlashFragment : BaseUIFragment<FlashViewModel, FragmentFlashMd2Binding>() {
override val layoutRes = R.layout.fragment_flash_md2 override val layoutRes = R.layout.fragment_flash_md2
override val viewModel by viewModel<FlashViewModel> { override val viewModel by viewModel<FlashViewModel>()
parametersOf(args.fromBundle(requireArguments()))
}
private var defaultOrientation = -1 private var defaultOrientation = -1
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel.args = FlashFragmentArgs.fromBundle(requireArguments())
}
override fun onStart() { override fun onStart() {
super.onStart() super.onStart()
setHasOptionsMenu(true) setHasOptionsMenu(true)
@@ -51,6 +51,7 @@ class FlashFragment : BaseUIFragment<FlashViewModel, FragmentFlashMd2Binding>()
defaultOrientation = activity.requestedOrientation defaultOrientation = activity.requestedOrientation
activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_NOSENSOR activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_NOSENSOR
viewModel.startFlashing()
} }
@SuppressLint("WrongConstant") @SuppressLint("WrongConstant")
@@ -78,7 +79,7 @@ class FlashFragment : BaseUIFragment<FlashViewModel, FragmentFlashMd2Binding>()
companion object { companion object {
private fun createIntent(context: Context, args: args) = private fun createIntent(context: Context, args: FlashFragmentArgs) =
NavDeepLinkBuilder(context) NavDeepLinkBuilder(context)
.setGraph(R.navigation.main) .setGraph(R.navigation.main)
.setComponentName(MainActivity::class.java.cmp(context.packageName)) .setComponentName(MainActivity::class.java.cmp(context.packageName))
@@ -91,61 +92,36 @@ class FlashFragment : BaseUIFragment<FlashViewModel, FragmentFlashMd2Binding>()
/* Flashing is understood as installing / flashing magisk itself */ /* Flashing is understood as installing / flashing magisk itself */
fun flashIntent(context: Context, file: Uri, isSecondSlot: Boolean, id: Int = -1) = args( fun flash(isSecondSlot: Boolean) = MainDirections.actionFlashFragment(
installer = file, action = flashType(isSecondSlot)
action = flashType(isSecondSlot), )
dismissId = id
).let { createIntent(context, it) }
fun flash(file: Uri, isSecondSlot: Boolean, id: Int) = toFlash(
installer = file,
action = flashType(isSecondSlot),
dismissId = id
).let { BaseUIActivity.postDirections(it) }
/* Patching is understood as injecting img files with magisk */ /* Patching is understood as injecting img files with magisk */
fun patchIntent(context: Context, file: Uri, uri: Uri, id: Int = -1) = args( fun patch(uri: Uri) = MainDirections.actionFlashFragment(
installer = file,
action = Const.Value.PATCH_FILE, action = Const.Value.PATCH_FILE,
additionalData = uri, additionalData = uri
dismissId = id )
).let { createIntent(context, it) }
fun patch(file: Uri, uri: Uri, id: Int) = toFlash(
installer = file,
action = Const.Value.PATCH_FILE,
additionalData = uri,
dismissId = id
).let { BaseUIActivity.postDirections(it) }
/* Uninstalling is understood as removing magisk entirely */ /* Uninstalling is understood as removing magisk entirely */
fun uninstallIntent(context: Context, file: Uri, id: Int = -1) = args( fun uninstall() = MainDirections.actionFlashFragment(
installer = file, action = Const.Value.UNINSTALL
action = Const.Value.UNINSTALL, )
dismissId = id
).let { createIntent(context, it) }
fun uninstall(file: Uri, id: Int) = toFlash(
installer = file,
action = Const.Value.UNINSTALL,
dismissId = id
).let { BaseUIActivity.postDirections(it) }
/* Installing is understood as flashing modules / zips */ /* Installing is understood as flashing modules / zips */
fun installIntent(context: Context, file: Uri, id: Int = -1) = args( fun installIntent(context: Context, file: Uri, id: Int = -1) = FlashFragmentArgs(
installer = file,
action = Const.Value.FLASH_ZIP, action = Const.Value.FLASH_ZIP,
additionalData = file,
dismissId = id dismissId = id
).let { createIntent(context, it) } ).let { createIntent(context, it) }
fun install(file: Uri, id: Int) = toFlash( fun install(file: Uri, id: Int) = MainDirections.actionFlashFragment(
installer = file,
action = Const.Value.FLASH_ZIP, action = Const.Value.FLASH_ZIP,
additionalData = file,
dismissId = id dismissId = id
).let { BaseUIActivity.postDirections(it) } )
} }
} }

View File

@@ -1,6 +1,5 @@
package com.topjohnwu.magisk.ui.flash package com.topjohnwu.magisk.ui.flash
import android.net.Uri
import android.view.MenuItem import android.view.MenuItem
import androidx.databinding.Bindable import androidx.databinding.Bindable
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
@@ -12,6 +11,7 @@ import com.topjohnwu.magisk.arch.BaseViewModel
import com.topjohnwu.magisk.arch.diffListOf import com.topjohnwu.magisk.arch.diffListOf
import com.topjohnwu.magisk.arch.itemBindingOf import com.topjohnwu.magisk.arch.itemBindingOf
import com.topjohnwu.magisk.core.Const import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.tasks.FlashZip import com.topjohnwu.magisk.core.tasks.FlashZip
import com.topjohnwu.magisk.core.tasks.MagiskInstaller import com.topjohnwu.magisk.core.tasks.MagiskInstaller
import com.topjohnwu.magisk.core.utils.MediaStoreUtils import com.topjohnwu.magisk.core.utils.MediaStoreUtils
@@ -26,9 +26,7 @@ import com.topjohnwu.superuser.Shell
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class FlashViewModel( class FlashViewModel : BaseViewModel() {
args: FlashFragmentArgs
) : BaseViewModel() {
@get:Bindable @get:Bindable
var showReboot = Shell.rootAccess() var showReboot = Shell.rootAccess()
@@ -40,6 +38,7 @@ class FlashViewModel(
val adapter = RvBindingAdapter<ConsoleItem>() val adapter = RvBindingAdapter<ConsoleItem>()
val items = diffListOf<ConsoleItem>() val items = diffListOf<ConsoleItem>()
val itemBinding = itemBindingOf<ConsoleItem>() val itemBinding = itemBindingOf<ConsoleItem>()
lateinit var args: FlashFragmentArgs
private val logItems = mutableListOf<String>().synchronized() private val logItems = mutableListOf<String>().synchronized()
private val outItems = object : CallbackList<String>() { private val outItems = object : CallbackList<String>() {
@@ -50,34 +49,33 @@ class FlashViewModel(
} }
} }
init { fun startFlashing() {
args.dismissId.takeIf { it != -1 }?.also { val (action, uri, id) = args
Notifications.mgr.cancel(it) if (id != -1)
} Notifications.mgr.cancel(id)
val (installer, action, uri) = args
startFlashing(installer, uri, action)
}
private fun startFlashing(installer: Uri, uri: Uri?, action: String) {
viewModelScope.launch { viewModelScope.launch {
val result = when (action) { val result = when (action) {
Const.Value.FLASH_ZIP -> { Const.Value.FLASH_ZIP -> {
FlashZip(installer, outItems, logItems).exec() FlashZip(uri!!, outItems, logItems).exec()
} }
Const.Value.UNINSTALL -> { Const.Value.UNINSTALL -> {
showReboot = false showReboot = false
FlashZip.Uninstall(installer, outItems, logItems).exec() MagiskInstaller.Uninstall(outItems, logItems).exec()
} }
Const.Value.FLASH_MAGISK -> { Const.Value.FLASH_MAGISK -> {
MagiskInstaller.Direct(installer, outItems, logItems).exec() if (Info.isEmulator)
MagiskInstaller.Emulator(outItems, logItems).exec()
else
MagiskInstaller.Direct(outItems, logItems).exec()
} }
Const.Value.FLASH_INACTIVE_SLOT -> { Const.Value.FLASH_INACTIVE_SLOT -> {
MagiskInstaller.SecondSlot(installer, outItems, logItems).exec() MagiskInstaller.SecondSlot(outItems, logItems).exec()
} }
Const.Value.PATCH_FILE -> { Const.Value.PATCH_FILE -> {
uri ?: return@launch uri ?: return@launch
showReboot = false showReboot = false
MagiskInstaller.Patch(installer, uri, outItems, logItems).exec() MagiskInstaller.Patch(uri, outItems, logItems).exec()
} }
else -> { else -> {
back() back()

View File

@@ -12,12 +12,12 @@ import androidx.recyclerview.widget.RecyclerView
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.arch.BaseUIFragment import com.topjohnwu.magisk.arch.BaseUIFragment
import com.topjohnwu.magisk.databinding.FragmentHideMd2Binding import com.topjohnwu.magisk.databinding.FragmentHideMd2Binding
import com.topjohnwu.magisk.di.viewModel
import com.topjohnwu.magisk.ktx.addSimpleItemDecoration import com.topjohnwu.magisk.ktx.addSimpleItemDecoration
import com.topjohnwu.magisk.ktx.addVerticalPadding import com.topjohnwu.magisk.ktx.addVerticalPadding
import com.topjohnwu.magisk.ktx.fixEdgeEffect import com.topjohnwu.magisk.ktx.fixEdgeEffect
import com.topjohnwu.magisk.ktx.hideKeyboard import com.topjohnwu.magisk.ktx.hideKeyboard
import com.topjohnwu.magisk.utils.MotionRevealHelper import com.topjohnwu.magisk.utils.MotionRevealHelper
import org.koin.androidx.viewmodel.ext.android.viewModel
class HideFragment : BaseUIFragment<HideViewModel, FragmentHideMd2Binding>() { class HideFragment : BaseUIFragment<HideViewModel, FragmentHideMd2Binding>() {

View File

@@ -7,6 +7,7 @@ import android.content.pm.PackageManager
import android.content.pm.PackageManager.* import android.content.pm.PackageManager.*
import android.content.pm.ServiceInfo import android.content.pm.ServiceInfo
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.os.Build.VERSION.SDK_INT
import com.topjohnwu.magisk.core.utils.currentLocale import com.topjohnwu.magisk.core.utils.currentLocale
import com.topjohnwu.magisk.ktx.getLabel import com.topjohnwu.magisk.ktx.getLabel
import com.topjohnwu.magisk.ktx.isIsolated import com.topjohnwu.magisk.ktx.isIsolated
@@ -59,7 +60,7 @@ class HideAppInfo(info: ApplicationInfo, pm: PackageManager, hideList: List<Cmdl
val hidden = hideList.filter { it.packageName == packageName || it.packageName == ISOLATED_MAGIC } val hidden = hideList.filter { it.packageName == packageName || it.packageName == ISOLATED_MAGIC }
fun createProcess(name: String, pkg: String = packageName): HideProcessInfo { fun createProcess(name: String, pkg: String = packageName): HideProcessInfo {
return HideProcessInfo(name, pkg, hidden.any { it.process == name }) return HideProcessInfo(name, pkg, hidden.any { it.process == name && it.packageName == pkg })
} }
var haveAppZygote = false var haveAppZygote = false
@@ -71,7 +72,8 @@ class HideAppInfo(info: ApplicationInfo, pm: PackageManager, hideList: List<Cmdl
// Using app zygote, don't need to track the process // Using app zygote, don't need to track the process
null null
} else { } else {
createProcess("${it.processName}:${it.name}", ISOLATED_MAGIC) val proc = if (SDK_INT >= 29) "${it.processName}:${it.name}" else it.processName
createProcess(proc, ISOLATED_MAGIC)
} }
} else { } else {
createProcess(it.processName) createProcess(it.processName)
@@ -84,7 +86,7 @@ class HideAppInfo(info: ApplicationInfo, pm: PackageManager, hideList: List<Cmdl
receivers?.processes().orEmpty() + receivers?.processes().orEmpty() +
providers?.processes().orEmpty() + providers?.processes().orEmpty() +
listOf(if (haveAppZygote) createProcess("${processName}_zygote") else null) listOf(if (haveAppZygote) createProcess("${processName}_zygote") else null)
}.filterNotNull().distinctBy { it.name }.sortedBy { it.name } }.filterNotNull().distinct().sortedBy { it.name }
} }
companion object { companion object {
@@ -100,6 +102,6 @@ data class HideProcessInfo(
val packageName: String, val packageName: String,
var isHidden: Boolean var isHidden: Boolean
) { ) {
val isIsolated get() = name == ISOLATED_MAGIC val isIsolated get() = packageName == ISOLATED_MAGIC
val isAppZygote get() = name.endsWith("_zygote") val isAppZygote get() = name.endsWith("_zygote")
} }

View File

@@ -93,13 +93,15 @@ class HideProcessRvItem(
override val layoutRes get() = R.layout.item_hide_process_md2 override val layoutRes get() = R.layout.item_hide_process_md2
val displayName = if (process.isIsolated) "(isolated) ${process.name}" else process.name
@get:Bindable @get:Bindable
var isHidden var isHidden
get() = process.isHidden get() = process.isHidden
set(value) = set(value, process.isHidden, { process.isHidden = it }, BR.hidden) { set(value) = set(value, process.isHidden, { process.isHidden = it }, BR.hidden) {
val arg = if (it) "add" else "rm" val arg = if (it) "add" else "rm"
val (name, pkg) = process val (name, pkg) = process
Shell.su("magiskhide --$arg $pkg $name").submit() Shell.su("magiskhide $arg $pkg \'$name\'").submit()
} }
fun toggle() { fun toggle() {
@@ -109,7 +111,10 @@ class HideProcessRvItem(
val defaultSelection get() = val defaultSelection get() =
process.isIsolated || process.isAppZygote || process.name == process.packageName process.isIsolated || process.isAppZygote || process.name == process.packageName
override fun contentSameAs(other: HideProcessRvItem) = process == other.process override fun contentSameAs(other: HideProcessRvItem) =
override fun itemSameAs(other: HideProcessRvItem) = process.name == other.process.name process.isHidden == other.process.isHidden
override fun itemSameAs(other: HideProcessRvItem) =
process.name == other.process.name && process.packageName == other.process.packageName
} }

View File

@@ -2,7 +2,6 @@ package com.topjohnwu.magisk.ui.hide
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.pm.ApplicationInfo import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES import android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES
import android.os.Process import android.os.Process
import androidx.databinding.Bindable import androidx.databinding.Bindable
@@ -13,8 +12,7 @@ import com.topjohnwu.magisk.arch.Queryable
import com.topjohnwu.magisk.arch.filterableListOf import com.topjohnwu.magisk.arch.filterableListOf
import com.topjohnwu.magisk.arch.itemBindingOf import com.topjohnwu.magisk.arch.itemBindingOf
import com.topjohnwu.magisk.core.Config import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.ktx.get import com.topjohnwu.magisk.di.AppContext
import com.topjohnwu.magisk.ktx.packageName
import com.topjohnwu.magisk.utils.Utils import com.topjohnwu.magisk.utils.Utils
import com.topjohnwu.magisk.utils.set import com.topjohnwu.magisk.utils.set
import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.Shell
@@ -61,13 +59,14 @@ class HideViewModel : BaseViewModel(), Queryable {
} }
state = State.LOADING state = State.LOADING
val (apps, diff) = withContext(Dispatchers.Default) { val (apps, diff) = withContext(Dispatchers.Default) {
val pm = get<PackageManager>() val pm = AppContext.packageManager
val hideList = Shell.su("magiskhide --ls").exec().out.map { CmdlineHiddenItem(it) } val hideList = Shell.su("magiskhide ls").exec().out.map { CmdlineHiddenItem(it) }
val apps = pm.getInstalledApplications(MATCH_UNINSTALLED_PACKAGES) val apps = pm.getInstalledApplications(MATCH_UNINSTALLED_PACKAGES)
.asSequence() .asSequence()
.filter { it.enabled && !blacklist.contains(it.packageName) } .filterNot { blacklist.contains(it.packageName) }
.map { HideAppInfo(it, pm, hideList) } .map { HideAppInfo(it, pm, hideList) }
.filter { it.processes.isNotEmpty() } .filter { it.processes.isNotEmpty() }
.filter { info -> info.enabled || info.processes.any { it.isHidden } }
.map { HideRvItem(it) } .map { HideRvItem(it) }
.toList() .toList()
.sorted() .sorted()
@@ -112,7 +111,7 @@ class HideViewModel : BaseViewModel(), Queryable {
companion object { companion object {
private val blacklist by lazy { listOf( private val blacklist by lazy { listOf(
packageName, AppContext.packageName,
"com.android.chrome", "com.android.chrome",
"com.chrome.beta", "com.chrome.beta",
"com.chrome.dev", "com.chrome.dev",

View File

@@ -2,13 +2,15 @@ package com.topjohnwu.magisk.ui.home
import android.os.Bundle import android.os.Bundle
import android.view.* import android.view.*
import android.widget.ImageView
import android.widget.TextView
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.arch.BaseUIFragment import com.topjohnwu.magisk.arch.BaseUIFragment
import com.topjohnwu.magisk.core.download.BaseDownloader import com.topjohnwu.magisk.core.download.BaseDownloader
import com.topjohnwu.magisk.databinding.FragmentHomeMd2Binding import com.topjohnwu.magisk.databinding.FragmentHomeMd2Binding
import com.topjohnwu.magisk.di.viewModel
import com.topjohnwu.magisk.events.RebootEvent import com.topjohnwu.magisk.events.RebootEvent
import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.Shell
import org.koin.androidx.viewmodel.ext.android.viewModel
class HomeFragment : BaseUIFragment<HomeViewModel, FragmentHomeMd2Binding>() { class HomeFragment : BaseUIFragment<HomeViewModel, FragmentHomeMd2Binding>() {
@@ -22,20 +24,32 @@ class HomeFragment : BaseUIFragment<HomeViewModel, FragmentHomeMd2Binding>() {
BaseDownloader.observeProgress(this, viewModel::onProgressUpdate) BaseDownloader.observeProgress(this, viewModel::onProgressUpdate)
} }
private fun checkTitle(text: TextView, icon: ImageView) {
text.post {
if (text.layout.getEllipsisCount(0) != 0) {
with (icon) {
layoutParams.width = 0
layoutParams.height = 0
requestLayout()
}
}
}
}
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup?, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View? { ): View {
super.onCreateView(inflater, container, savedInstanceState) super.onCreateView(inflater, container, savedInstanceState)
// Set barrier reference IDs in code, since resource IDs will be stripped in release mode // If titles are squished, hide icons
binding.homeMagiskWrapper.homeMagiskTitleBarrier.referencedIds = with(binding.homeMagiskWrapper) {
intArrayOf(R.id.home_magisk_button, R.id.home_magisk_title, R.id.home_magisk_icon) checkTitle(homeMagiskTitle, homeMagiskIcon)
binding.homeMagiskWrapper.homeMagiskBarrier.referencedIds = }
intArrayOf(R.id.home_magisk_latest_version, R.id.home_magisk_installed_version) with(binding.homeManagerWrapper) {
binding.homeManagerWrapper.homeManagerTitleBarrier.referencedIds = checkTitle(homeManagerTitle, homeManagerIcon)
intArrayOf(R.id.home_manager_button, R.id.home_manager_title, R.id.home_manager_icon) }
return binding.root return binding.root
} }
@@ -46,11 +60,14 @@ class HomeFragment : BaseUIFragment<HomeViewModel, FragmentHomeMd2Binding>() {
menu.removeItem(R.id.action_reboot) menu.removeItem(R.id.action_reboot)
} }
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) { override fun onOptionsItemSelected(item: MenuItem): Boolean {
R.id.action_settings -> HomeFragmentDirections.actionHomeFragmentToSettingsFragment() when (item.itemId) {
.navigate() R.id.action_settings ->
R.id.action_reboot -> RebootEvent.inflateMenu(activity).show() HomeFragmentDirections.actionHomeFragmentToSettingsFragment().navigate()
else -> null R.id.action_reboot -> RebootEvent.inflateMenu(activity).show()
}?.let { true } ?: super.onOptionsItemSelected(item) else -> return super.onOptionsItemSelected(item)
}
return true
}
} }

View File

@@ -9,8 +9,6 @@ import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.core.Info import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.download.Subject import com.topjohnwu.magisk.core.download.Subject
import com.topjohnwu.magisk.core.download.Subject.Manager import com.topjohnwu.magisk.core.download.Subject.Manager
import com.topjohnwu.magisk.core.model.MagiskJson
import com.topjohnwu.magisk.core.model.ManagerJson
import com.topjohnwu.magisk.data.repository.NetworkService import com.topjohnwu.magisk.data.repository.NetworkService
import com.topjohnwu.magisk.events.OpenInappLinkEvent import com.topjohnwu.magisk.events.OpenInappLinkEvent
import com.topjohnwu.magisk.events.SnackbarEvent import com.topjohnwu.magisk.events.SnackbarEvent
@@ -18,8 +16,7 @@ import com.topjohnwu.magisk.events.dialog.EnvFixDialog
import com.topjohnwu.magisk.events.dialog.ManagerInstallDialog import com.topjohnwu.magisk.events.dialog.ManagerInstallDialog
import com.topjohnwu.magisk.events.dialog.UninstallDialog import com.topjohnwu.magisk.events.dialog.UninstallDialog
import com.topjohnwu.magisk.ktx.await import com.topjohnwu.magisk.ktx.await
import com.topjohnwu.magisk.ktx.packageName import com.topjohnwu.magisk.utils.asText
import com.topjohnwu.magisk.ktx.res
import com.topjohnwu.magisk.utils.set import com.topjohnwu.magisk.utils.set
import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.Shell
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -34,41 +31,46 @@ class HomeViewModel(
private val svc: NetworkService private val svc: NetworkService
) : BaseViewModel() { ) : BaseViewModel() {
val magiskTitleBarrierIds =
intArrayOf(R.id.home_magisk_icon, R.id.home_magisk_title, R.id.home_magisk_button)
val magiskDetailBarrierIds =
intArrayOf(R.id.home_magisk_installed_version, R.id.home_device_details_ramdisk)
val appTitleBarrierIds =
intArrayOf(R.id.home_manager_icon, R.id.home_manager_title, R.id.home_manager_button)
@get:Bindable @get:Bindable
var isNoticeVisible = Config.safetyNotice var isNoticeVisible = Config.safetyNotice
set(value) = set(value, field, { field = it }, BR.noticeVisible) set(value) = set(value, field, { field = it }, BR.noticeVisible)
@get:Bindable val stateMagisk = when {
var stateMagisk = MagiskState.LOADING !Info.env.isActive -> MagiskState.NOT_INSTALLED
set(value) = set(value, field, { field = it }, BR.stateMagisk, BR.showUninstall) Info.env.magiskVersionCode < BuildConfig.VERSION_CODE -> MagiskState.OBSOLETE
else -> MagiskState.UP_TO_DATE
}
@get:Bindable @get:Bindable
var stateManager = MagiskState.LOADING var stateManager = MagiskState.LOADING
set(value) = set(value, field, { field = it }, BR.stateManager) set(value) = set(value, field, { field = it }, BR.stateManager)
@get:Bindable val magiskInstalledVersion get() = Info.env.run {
var magiskRemoteVersion = R.string.loading.res() if (isActive)
set(value) = set(value, field, { field = it }, BR.magiskRemoteVersion) "$magiskVersionString ($magiskVersionCode)".asText()
else
val magiskInstalledVersion get() = R.string.not_available.asText()
"${Info.env.magiskVersionString} (${Info.env.magiskVersionCode})" }
@get:Bindable @get:Bindable
var managerRemoteVersion = R.string.loading.res() var managerRemoteVersion = R.string.loading.asText()
set(value) = set(value, field, { field = it }, BR.managerRemoteVersion) set(value) = set(value, field, { field = it }, BR.managerRemoteVersion)
val managerInstalledVersion = Info.stub?.let { val managerInstalledVersion = Info.stub?.let {
"${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE}) (${it.version})" "${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE}) (${it.version})"
} ?: "${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})" } ?: "${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})"
val statePackageName = packageName
@get:Bindable @get:Bindable
var stateManagerProgress = 0 var stateManagerProgress = 0
set(value) = set(value, field, { field = it }, BR.stateManagerProgress) set(value) = set(value, field, { field = it }, BR.stateManagerProgress)
@get:Bindable
val showUninstall get() = Info.env.isActive && state != State.LOADING
@get:Bindable @get:Bindable
val showSafetyNet get() = Info.hasGMS && isConnected.get() val showSafetyNet get() = Info.hasGMS && isConnected.get()
@@ -80,30 +82,25 @@ class HomeViewModel(
override fun refresh() = viewModelScope.launch { override fun refresh() = viewModelScope.launch {
state = State.LOADING state = State.LOADING
svc.fetchUpdate()?.apply { notifyPropertyChanged(BR.showSafetyNet)
Info.getRemote(svc)?.apply {
state = State.LOADED state = State.LOADED
stateMagisk = when {
!Info.env.isActive -> MagiskState.NOT_INSTALLED
magisk.isObsolete -> MagiskState.OBSOLETE
else -> MagiskState.UP_TO_DATE
}
stateManager = when { stateManager = when {
app.isObsolete -> MagiskState.OBSOLETE BuildConfig.VERSION_CODE < magisk.versionCode -> MagiskState.OBSOLETE
else -> MagiskState.UP_TO_DATE else -> MagiskState.UP_TO_DATE
} }
magiskRemoteVersion =
"${magisk.version} (${magisk.versionCode})"
managerRemoteVersion = managerRemoteVersion =
"${app.version} (${app.versionCode}) (${stub.versionCode})" "${magisk.version} (${magisk.versionCode}) (${stub.versionCode})".asText()
launch { launch {
ensureEnv() ensureEnv()
} }
} ?: apply { state = State.LOADING_FAILED } } ?: {
notifyPropertyChanged(BR.showUninstall) state = State.LOADING_FAILED
notifyPropertyChanged(BR.showSafetyNet) managerRemoteVersion = R.string.not_available.asText()
}()
} }
val showTest = false val showTest = false
@@ -115,9 +112,8 @@ class HomeViewModel(
}.publish() }.publish()
fun onProgressUpdate(progress: Float, subject: Subject) { fun onProgressUpdate(progress: Float, subject: Subject) {
when (subject) { if (subject is Manager)
is Manager -> stateManagerProgress = progress.times(100f).roundToInt() stateManagerProgress = progress.times(100f).roundToInt()
}
} }
fun onLinkPressed(link: String) = OpenInappLinkEvent(link).publish() fun onLinkPressed(link: String) = OpenInappLinkEvent(link).publish()
@@ -125,21 +121,17 @@ class HomeViewModel(
fun onDeletePressed() = UninstallDialog().publish() fun onDeletePressed() = UninstallDialog().publish()
fun onManagerPressed() = when (state) { fun onManagerPressed() = when (state) {
State.LOADED -> ManagerInstallDialog().publish() State.LOADED -> withExternalRW { ManagerInstallDialog().publish() }
State.LOADING -> SnackbarEvent(R.string.loading).publish() State.LOADING -> SnackbarEvent(R.string.loading).publish()
else -> SnackbarEvent(R.string.no_connection).publish() else -> SnackbarEvent(R.string.no_connection).publish()
} }
fun onMagiskPressed() = when (state) { fun onMagiskPressed() = withExternalRW {
State.LOADED -> withExternalRW { HomeFragmentDirections.actionHomeFragmentToInstallFragment().navigate()
HomeFragmentDirections.actionHomeFragmentToInstallFragment().publish()
}
State.LOADING -> SnackbarEvent(R.string.loading).publish()
else -> SnackbarEvent(R.string.no_connection).publish()
} }
fun onSafetyNetPressed() = fun onSafetyNetPressed() =
HomeFragmentDirections.actionHomeFragmentToSafetynetFragment().publish() HomeFragmentDirections.actionHomeFragmentToSafetynetFragment().navigate()
fun hideNotice() { fun hideNotice() {
Config.safetyNotice = false Config.safetyNotice = false
@@ -160,9 +152,4 @@ class HomeViewModel(
} }
} }
private val MagiskJson.isObsolete
get() = Info.env.isActive && Info.env.magiskVersionCode < versionCode
private val ManagerJson.isObsolete
get() = BuildConfig.VERSION_CODE < versionCode
} }

View File

@@ -26,9 +26,7 @@ open class LayoutInflaterFactory(private val delegate: AppCompatDelegate) : Layo
open fun onViewCreated(view: View?, parent: View?, name: String, context: Context, attrs: AttributeSet) { open fun onViewCreated(view: View?, parent: View?, name: String, context: Context, attrs: AttributeSet) {
if (view == null) return if (view == null) return
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { WindowInsetsHelper.attach(view, attrs)
WindowInsetsHelper.attach(view, attrs)
}
} }
} }
@@ -85,4 +83,4 @@ private object LayoutInflaterFactoryDefaultImpl {
null null
} }
} }
} }

View File

@@ -7,10 +7,9 @@ import android.view.ViewGroup
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.arch.BaseUIFragment import com.topjohnwu.magisk.arch.BaseUIFragment
import com.topjohnwu.magisk.core.download.BaseDownloader
import com.topjohnwu.magisk.databinding.FragmentInstallMd2Binding import com.topjohnwu.magisk.databinding.FragmentInstallMd2Binding
import com.topjohnwu.magisk.di.viewModel
import com.topjohnwu.magisk.ktx.coroutineScope import com.topjohnwu.magisk.ktx.coroutineScope
import org.koin.androidx.viewmodel.ext.android.viewModel
class InstallFragment : BaseUIFragment<InstallViewModel, FragmentInstallMd2Binding>() { class InstallFragment : BaseUIFragment<InstallViewModel, FragmentInstallMd2Binding>() {
@@ -23,7 +22,6 @@ class InstallFragment : BaseUIFragment<InstallViewModel, FragmentInstallMd2Bindi
// Allow markwon to run in viewmodel scope // Allow markwon to run in viewmodel scope
binding.releaseNotes.coroutineScope = viewModel.viewModelScope binding.releaseNotes.coroutineScope = viewModel.viewModelScope
BaseDownloader.observeProgress(this, viewModel::onProgressUpdate)
} }
override fun onCreateView( override fun onCreateView(

View File

@@ -5,29 +5,30 @@ import android.net.Uri
import androidx.databinding.Bindable import androidx.databinding.Bindable
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.topjohnwu.magisk.BR import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.BuildConfig
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.arch.BaseViewModel import com.topjohnwu.magisk.arch.BaseViewModel
import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.Info import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.download.Action
import com.topjohnwu.magisk.core.download.DownloadService
import com.topjohnwu.magisk.core.download.Subject
import com.topjohnwu.magisk.data.repository.NetworkService import com.topjohnwu.magisk.data.repository.NetworkService
import com.topjohnwu.magisk.di.AppContext
import com.topjohnwu.magisk.events.MagiskInstallFileEvent import com.topjohnwu.magisk.events.MagiskInstallFileEvent
import com.topjohnwu.magisk.events.dialog.SecondSlotWarningDialog import com.topjohnwu.magisk.events.dialog.SecondSlotWarningDialog
import com.topjohnwu.magisk.ui.flash.FlashFragment
import com.topjohnwu.magisk.utils.set import com.topjohnwu.magisk.utils.set
import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.Shell
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.koin.core.get
import timber.log.Timber import timber.log.Timber
import java.io.File
import java.io.IOException import java.io.IOException
import kotlin.math.roundToInt
class InstallViewModel( class InstallViewModel(
svc: NetworkService svc: NetworkService
) : BaseViewModel(State.LOADED) { ) : BaseViewModel() {
val isRooted = Shell.rootAccess() val isRooted = Shell.rootAccess()
val skipOptions = Info.ramdisk && !Info.isFDE && Info.isSAR val skipOptions = Info.isEmulator || (Info.ramdisk && !Info.isFDE && Info.isSAR)
val noSecondSlot = !isRooted || Info.isPixel || Info.isVirtualAB || !Info.isAB || Info.isEmulator
@get:Bindable @get:Bindable
var step = if (skipOptions) 1 else 0 var step = if (skipOptions) 1 else 0
@@ -52,10 +53,6 @@ class InstallViewModel(
} }
} }
@get:Bindable
var progress = 0
set(value) = set(value, field, { field = it }, BR.progress)
@get:Bindable @get:Bindable
var data: Uri? = null var data: Uri? = null
set(value) = set(value, field, { field = it }, BR.data) set(value) = set(value, field, { field = it }, BR.data)
@@ -67,41 +64,34 @@ class InstallViewModel(
init { init {
viewModelScope.launch { viewModelScope.launch {
try { try {
notes = svc.fetchString(Info.remote.magisk.note) File(AppContext.cacheDir, "${BuildConfig.VERSION_CODE}.md").run {
notes = when {
exists() -> readText()
Const.Url.CHANGELOG_URL.isEmpty() -> ""
else -> {
val text = svc.fetchString(Const.Url.CHANGELOG_URL)
writeText(text)
text
}
}
}
} catch (e: IOException) { } catch (e: IOException) {
Timber.e(e) Timber.e(e)
} }
} }
} }
fun onProgressUpdate(progress: Float, subject: Subject) {
if (subject !is Subject.Magisk) {
return
}
this.progress = progress.times(100).roundToInt()
if (this.progress >= 100) {
state = State.LOADED
} else if (this.progress < -150) {
state = State.LOADING_FAILED
}
}
fun step(nextStep: Int) { fun step(nextStep: Int) {
step = nextStep step = nextStep
} }
fun install() { fun install() {
DownloadService.start(get(), Subject.Magisk(resolveAction())) when (method) {
R.id.method_patch -> FlashFragment.patch(data!!).navigate()
R.id.method_direct -> FlashFragment.flash(false).navigate()
R.id.method_inactive_slot -> FlashFragment.flash(true).navigate()
else -> error("Unknown value")
}
state = State.LOADING state = State.LOADING
} }
// ---
private fun resolveAction() = when (method) {
R.id.method_download -> Action.Download
R.id.method_patch -> Action.Patch(data!!)
R.id.method_direct -> Action.Flash.Primary
R.id.method_inactive_slot -> Action.Flash.Secondary
else -> error("Unknown value")
}
} }

View File

@@ -9,12 +9,12 @@ import androidx.core.view.isVisible
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.arch.BaseUIFragment import com.topjohnwu.magisk.arch.BaseUIFragment
import com.topjohnwu.magisk.databinding.FragmentLogMd2Binding import com.topjohnwu.magisk.databinding.FragmentLogMd2Binding
import com.topjohnwu.magisk.di.viewModel
import com.topjohnwu.magisk.ktx.addSimpleItemDecoration import com.topjohnwu.magisk.ktx.addSimpleItemDecoration
import com.topjohnwu.magisk.ktx.addVerticalPadding import com.topjohnwu.magisk.ktx.addVerticalPadding
import com.topjohnwu.magisk.ktx.fixEdgeEffect import com.topjohnwu.magisk.ktx.fixEdgeEffect
import com.topjohnwu.magisk.ui.MainActivity import com.topjohnwu.magisk.ui.MainActivity
import com.topjohnwu.magisk.utils.MotionRevealHelper import com.topjohnwu.magisk.utils.MotionRevealHelper
import org.koin.androidx.viewmodel.ext.android.viewModel
class LogFragment : BaseUIFragment<LogViewModel, FragmentLogMd2Binding>() { class LogFragment : BaseUIFragment<LogViewModel, FragmentLogMd2Binding>() {

View File

@@ -13,11 +13,11 @@ import com.topjohnwu.magisk.arch.ReselectionTarget
import com.topjohnwu.magisk.arch.ViewEvent import com.topjohnwu.magisk.arch.ViewEvent
import com.topjohnwu.magisk.core.download.BaseDownloader import com.topjohnwu.magisk.core.download.BaseDownloader
import com.topjohnwu.magisk.databinding.FragmentModuleMd2Binding import com.topjohnwu.magisk.databinding.FragmentModuleMd2Binding
import com.topjohnwu.magisk.di.viewModel
import com.topjohnwu.magisk.ktx.* import com.topjohnwu.magisk.ktx.*
import com.topjohnwu.magisk.ui.MainActivity import com.topjohnwu.magisk.ui.MainActivity
import com.topjohnwu.magisk.utils.EndlessRecyclerScrollListener import com.topjohnwu.magisk.utils.EndlessRecyclerScrollListener
import com.topjohnwu.magisk.utils.MotionRevealHelper import com.topjohnwu.magisk.utils.MotionRevealHelper
import org.koin.androidx.viewmodel.ext.android.viewModel
class ModuleFragment : BaseUIFragment<ModuleViewModel, FragmentModuleMd2Binding>(), class ModuleFragment : BaseUIFragment<ModuleViewModel, FragmentModuleMd2Binding>(),
ReselectionTarget { ReselectionTarget {

View File

@@ -313,10 +313,8 @@ class ModuleViewModel(
fun infoPressed(item: ModuleItem) { fun infoPressed(item: ModuleItem) {
item.repo?.also { item.repo?.also {
if (isConnected.get()) if (isConnected.get()) OpenReadmeEvent(it).publish()
OpenReadmeEvent(it).publish() else SnackbarEvent(R.string.no_connection).publish()
else
SnackbarEvent(R.string.no_connection).publish()
} ?: return } ?: return
} }
} }

View File

@@ -1,123 +1,130 @@
@file:Suppress("DEPRECATION")
package com.topjohnwu.magisk.ui.safetynet package com.topjohnwu.magisk.ui.safetynet
import android.content.Context import android.content.Context
import android.util.Base64
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import com.squareup.moshi.Moshi
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.arch.ContextExecutor import com.topjohnwu.magisk.arch.ContextExecutor
import com.topjohnwu.magisk.arch.ViewEventWithScope import com.topjohnwu.magisk.arch.ViewEventWithScope
import com.topjohnwu.magisk.core.Const import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.data.repository.NetworkService import com.topjohnwu.magisk.di.ServiceLocator
import com.topjohnwu.magisk.ktx.DynamicClassLoader import com.topjohnwu.magisk.ktx.createClassLoader
import com.topjohnwu.magisk.ktx.reflectField
import com.topjohnwu.magisk.ktx.writeTo import com.topjohnwu.magisk.ktx.writeTo
import com.topjohnwu.magisk.view.MagiskDialog import com.topjohnwu.magisk.view.MagiskDialog
import com.topjohnwu.signing.CryptoUtils
import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.Shell
import dalvik.system.BaseDexClassLoader
import dalvik.system.DexFile import dalvik.system.DexFile
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.json.JSONObject import org.bouncycastle.asn1.ASN1Encoding
import org.koin.core.KoinComponent import org.bouncycastle.asn1.ASN1Primitive
import org.koin.core.inject import org.bouncycastle.est.jcajce.JsseDefaultHostnameAuthorizer
import timber.log.Timber import timber.log.Timber
import java.io.ByteArrayInputStream
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException
import java.lang.reflect.InvocationHandler import java.lang.reflect.InvocationHandler
import java.lang.reflect.Proxy
import java.security.SecureRandom
import java.security.Signature
@Suppress("DEPRECATION")
class CheckSafetyNetEvent( class CheckSafetyNetEvent(
private val callback: (SafetyNetResult) -> Unit = {} private val callback: (SafetyNetResult) -> Unit = {}
) : ViewEventWithScope(), ContextExecutor, KoinComponent, SafetyNetHelper.Callback { ) : ViewEventWithScope(), ContextExecutor, SafetyNetHelper.Callback {
private val svc by inject<NetworkService>() private val svc get() = ServiceLocator.networkService
private lateinit var apk: File private lateinit var jar: File
private lateinit var dex: File private lateinit var nonce: ByteArray
override fun invoke(context: Context) { override fun invoke(context: Context) {
apk = File("${context.filesDir.parent}/snet", "snet.jar") jar = File("${context.filesDir.parent}/snet", "snet.jar")
dex = File(apk.parent, "snet.dex")
scope.launch { scope.launch(Dispatchers.IO) {
attest(context) { attest(context) {
// Download and retry // Download and retry
withContext(Dispatchers.IO) { Shell.sh("rm -rf " + jar.parent).exec()
Shell.sh("rm -rf " + apk.parent).exec() jar.parentFile?.mkdir()
apk.parentFile?.mkdir() withContext(Dispatchers.Main) {
showDialog(context)
} }
download(context, true)
} }
} }
} }
private suspend fun attest(context: Context, onError: suspend (Exception) -> Unit) { private suspend fun attest(context: Context, onError: suspend (Exception) -> Unit) {
val helper: SafetyNetHelper
try { try {
val helper = withContext(Dispatchers.IO) { val loader = createClassLoader(jar)
val loader = DynamicClassLoader(apk)
val dex = DexFile.loadDex(apk.path, dex.path, 0)
// Scan through the dex and find our helper class // Scan through the dex and find our helper class
var helperClass: Class<*>? = null var clazz: Class<*>? = null
for (className in dex.entries()) { loop@for (dex in loader.getDexFiles()) {
if (className.startsWith("x.")) { for (name in dex.entries()) {
val cls = loader.loadClass(className) val cls = loader.loadClass(name)
if (InvocationHandler::class.java.isAssignableFrom(cls)) { if (InvocationHandler::class.java.isAssignableFrom(cls)) {
helperClass = cls clazz = cls
break break@loop
}
} }
} }
helperClass ?: throw Exception()
val helper = helperClass
.getMethod("get", Class::class.java, Context::class.java, Any::class.java)
.invoke(
null, SafetyNetHelper::class.java,
context, this@CheckSafetyNetEvent
) as SafetyNetHelper
if (helper.version < Const.SNET_EXT_VER)
throw Exception()
helper
} }
helper.attest() clazz ?: throw Exception("Cannot find SafetyNetHelper implementation")
helper = Proxy.newProxyInstance(
loader, arrayOf(SafetyNetHelper::class.java),
clazz.newInstance() as InvocationHandler) as SafetyNetHelper
if (helper.version != Const.SNET_EXT_VER)
throw Exception("snet extension version mismatch")
} catch (e: Exception) { } catch (e: Exception) {
if (e is CancellationException)
throw e
onError(e) onError(e)
}
}
@Suppress("SameParameterValue")
private fun download(context: Context, askUser: Boolean) {
fun downloadInternal() = scope.launch {
val abort: suspend (Exception) -> Unit = {
Timber.e(it)
callback(SafetyNetResult())
}
try {
withContext(Dispatchers.IO) {
svc.fetchSafetynet().byteStream().writeTo(apk)
}
attest(context, abort)
} catch (e: IOException) {
if (e is CancellationException)
throw e
abort(e)
}
}
if (!askUser) {
downloadInternal()
return return
} }
val random = SecureRandom()
nonce = ByteArray(24)
random.nextBytes(nonce)
helper.attest(context, nonce, this)
}
// All of these fields are whitelisted
private fun BaseDexClassLoader.getDexFiles(): List<DexFile> {
val pathList = BaseDexClassLoader::class.java.reflectField("pathList").get(this)
val dexElements = pathList.javaClass.reflectField("dexElements").get(pathList) as Array<*>
val fileField = dexElements.javaClass.componentType.reflectField("dexFile")
return dexElements.map { fileField.get(it) as DexFile }
}
private fun download(context: Context) = scope.launch(Dispatchers.IO) {
val abort: suspend (Exception) -> Unit = {
Timber.e(it)
withContext(Dispatchers.Main) {
callback(SafetyNetResult())
}
}
try {
svc.fetchSafetynet().byteStream().writeTo(jar)
attest(context, abort)
} catch (e: IOException) {
abort(e)
}
}
private fun showDialog(context: Context) {
MagiskDialog(context) MagiskDialog(context)
.applyTitle(R.string.proprietary_title) .applyTitle(R.string.proprietary_title)
.applyMessage(R.string.proprietary_notice) .applyMessage(R.string.proprietary_notice)
.cancellable(false) .cancellable(false)
.applyButton(MagiskDialog.ButtonType.POSITIVE) { .applyButton(MagiskDialog.ButtonType.POSITIVE) {
titleRes = android.R.string.ok titleRes = android.R.string.ok
onClick { downloadInternal() } onClick { download(context) }
} }
.applyButton(MagiskDialog.ButtonType.NEGATIVE) { .applyButton(MagiskDialog.ButtonType.NEGATIVE) {
titleRes = android.R.string.cancel titleRes = android.R.string.cancel
@@ -129,7 +136,90 @@ class CheckSafetyNetEvent(
.reveal() .reveal()
} }
override fun onResponse(response: JSONObject?) { private fun String.decode(): ByteArray {
callback(SafetyNetResult(response)) return if (contains("[+/]".toRegex()))
Base64.decode(this, Base64.DEFAULT)
else
Base64.decode(this, Base64.URL_SAFE)
}
private fun String.parseJws(): SafetyNetResponse {
val jws = split('.')
val secondDot = lastIndexOf('.')
val rawHeader = String(jws[0].decode())
val payload = String(jws[1].decode())
var signature = jws[2].decode()
val signedBytes = substring(0, secondDot).toByteArray()
val moshi = Moshi.Builder().build()
val header = moshi.adapter(JwsHeader::class.java).fromJson(rawHeader)
?: error("Invalid JWS header")
val alg = when (header.algorithm) {
"RS256" -> "SHA256withRSA"
"ES256" -> {
// Convert to DER encoding
signature = ASN1Primitive.fromByteArray(signature).getEncoded(ASN1Encoding.DER)
"SHA256withECDSA"
}
else -> error("Unsupported algorithm: ${header.algorithm}")
}
// Verify signature
val certB64 = header.certificates?.first() ?: error("Cannot find certificate in JWS")
val bis = ByteArrayInputStream(certB64.decode())
val cert = CryptoUtils.readCertificate(bis)
val verifier = Signature.getInstance(alg)
verifier.initVerify(cert.publicKey)
verifier.update(signedBytes)
if (!verifier.verify(signature))
error("Signature mismatch")
// Verify hostname
val hostnameVerifier = JsseDefaultHostnameAuthorizer(setOf())
if (!hostnameVerifier.verify("attest.android.com", cert))
error("Hostname mismatch")
val response = moshi.adapter(SafetyNetResponse::class.java).fromJson(payload)
?: error("Invalid SafetyNet response")
// Verify results
if (!response.nonce.decode().contentEquals(nonce))
error("nonce mismatch")
return response
}
override fun onResponse(response: String?) {
if (response != null) {
scope.launch(Dispatchers.Default) {
val res = runCatching { response.parseJws() }.getOrElse {
Timber.e(it)
INVALID_RESPONSE
}
withContext(Dispatchers.Main) {
callback(SafetyNetResult(res))
}
}
} else {
callback(SafetyNetResult())
}
} }
} }
@JsonClass(generateAdapter = true)
data class JwsHeader(
@Json(name = "alg") val algorithm: String,
@Json(name = "x5c") val certificates: List<String>?
)
@JsonClass(generateAdapter = true)
data class SafetyNetResponse(
val nonce: String,
val ctsProfileMatch: Boolean,
val basicIntegrity: Boolean,
val evaluationType: String = ""
)
// Special instance to indicate invalid SafetyNet response
val INVALID_RESPONSE = SafetyNetResponse("", ctsProfileMatch = false, basicIntegrity = false)

View File

@@ -1,14 +1,14 @@
package com.topjohnwu.magisk.ui.safetynet package com.topjohnwu.magisk.ui.safetynet
import org.json.JSONObject import android.content.Context
interface SafetyNetHelper { interface SafetyNetHelper {
val version: Int val version: Int
fun attest() fun attest(context: Context, nonce: ByteArray, callback: Callback)
interface Callback { interface Callback {
fun onResponse(response: JSONObject?) fun onResponse(response: String?)
} }
} }

View File

@@ -7,7 +7,7 @@ import android.view.ViewGroup
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.arch.BaseUIFragment import com.topjohnwu.magisk.arch.BaseUIFragment
import com.topjohnwu.magisk.databinding.FragmentSafetynetMd2Binding import com.topjohnwu.magisk.databinding.FragmentSafetynetMd2Binding
import org.koin.androidx.viewmodel.ext.android.viewModel import com.topjohnwu.magisk.di.viewModel
class SafetynetFragment : BaseUIFragment<SafetynetViewModel, FragmentSafetynetMd2Binding>() { class SafetynetFragment : BaseUIFragment<SafetynetViewModel, FragmentSafetynetMd2Binding>() {

View File

@@ -5,10 +5,9 @@ import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.arch.BaseViewModel import com.topjohnwu.magisk.arch.BaseViewModel
import com.topjohnwu.magisk.utils.set import com.topjohnwu.magisk.utils.set
import org.json.JSONObject
data class SafetyNetResult( class SafetyNetResult(
val response: JSONObject? = null, val response: SafetyNetResponse? = null,
val dismiss: Boolean = false val dismiss: Boolean = false
) )
@@ -43,59 +42,54 @@ class SafetynetViewModel : BaseViewModel() {
init { init {
cachedResult?.also { cachedResult?.also {
resolveResponse(SafetyNetResult(it)) handleResult(SafetyNetResult(it))
} ?: attest() } ?: attest()
} }
private fun attest() { private fun attest() {
isChecking = true isChecking = true
CheckSafetyNetEvent { CheckSafetyNetEvent(::handleResult).publish()
resolveResponse(it)
}.publish()
} }
fun reset() = attest() fun reset() = attest()
private fun resolveResponse(response: SafetyNetResult) { private fun handleResult(result: SafetyNetResult) {
isChecking = false isChecking = false
if (response.dismiss) { if (result.dismiss) {
back() back()
return return
} }
response.response?.apply { result.response?.apply {
runCatching { cachedResult = this
val cts = getBoolean("ctsProfileMatch") if (this === INVALID_RESPONSE) {
val basic = getBoolean("basicIntegrity")
val eval = optString("evaluationType")
val result = cts && basic
cachedResult = this
ctsState = cts
basicIntegrityState = basic
evalType = if (eval.contains("HARDWARE")) "HARDWARE" else "BASIC"
isSuccess = result
safetyNetTitle =
if (result) R.string.safetynet_attest_success
else R.string.safetynet_attest_failure
}.onFailure {
isSuccess = false isSuccess = false
ctsState = false ctsState = false
basicIntegrityState = false basicIntegrityState = false
evalType = "N/A" evalType = "N/A"
safetyNetTitle = R.string.safetynet_res_invalid safetyNetTitle = R.string.safetynet_res_invalid
} else {
val success = ctsProfileMatch && basicIntegrity
isSuccess = success
ctsState = ctsProfileMatch
basicIntegrityState = basicIntegrity
evalType = if (evaluationType.contains("HARDWARE")) "HARDWARE" else "BASIC"
safetyNetTitle =
if (success) R.string.safetynet_attest_success
else R.string.safetynet_attest_failure
} }
} ?: { } ?: run {
isSuccess = false isSuccess = false
ctsState = false ctsState = false
basicIntegrityState = false basicIntegrityState = false
evalType = "N/A" evalType = "N/A"
safetyNetTitle = R.string.safetynet_api_error safetyNetTitle = R.string.safetynet_api_error
}() }
} }
companion object { companion object {
private var cachedResult: JSONObject? = null private var cachedResult: SafetyNetResponse? = null
} }
} }

View File

@@ -3,27 +3,23 @@ package com.topjohnwu.magisk.ui.settings
import android.content.Context import android.content.Context
import android.content.res.Resources import android.content.res.Resources
import android.view.View import android.view.View
import androidx.annotation.ArrayRes
import androidx.annotation.CallSuper import androidx.annotation.CallSuper
import androidx.databinding.Bindable import androidx.databinding.Bindable
import com.topjohnwu.magisk.BR import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.databinding.ObservableItem import com.topjohnwu.magisk.databinding.ObservableItem
import com.topjohnwu.magisk.utils.TransitiveText import com.topjohnwu.magisk.utils.TextHolder
import com.topjohnwu.magisk.utils.asTransitive
import com.topjohnwu.magisk.utils.set import com.topjohnwu.magisk.utils.set
import com.topjohnwu.magisk.view.MagiskDialog import com.topjohnwu.magisk.view.MagiskDialog
import org.koin.core.KoinComponent
import org.koin.core.get
sealed class BaseSettingsItem : ObservableItem<BaseSettingsItem>() { sealed class BaseSettingsItem : ObservableItem<BaseSettingsItem>() {
override val layoutRes get() = R.layout.item_settings override val layoutRes get() = R.layout.item_settings
open val icon: Int get() = 0 open val icon: Int get() = 0
open val title: TransitiveText get() = TransitiveText.EMPTY open val title: TextHolder get() = TextHolder.EMPTY
@get:Bindable @get:Bindable
open val description: TransitiveText get() = TransitiveText.EMPTY open val description: TextHolder get() = TextHolder.EMPTY
// --- // ---
@@ -141,34 +137,29 @@ sealed class BaseSettingsItem : ObservableItem<BaseSettingsItem>() {
abstract fun getView(context: Context): View abstract fun getView(context: Context): View
} }
abstract class Selector : Value<Int>(), KoinComponent { abstract class Selector : Value<Int>() {
protected val resources get() = get<Resources>() open val entryRes get() = -1
open val descriptionRes get() = entryRes
open fun entries(res: Resources) = res.getArrayOrEmpty(entryRes)
open fun descriptions(res: Resources) = res.getArrayOrEmpty(descriptionRes)
@ArrayRes open val entryRes = -1 override val description = object : TextHolder() {
@ArrayRes open val entryValRes = -1 override fun getText(resources: Resources): CharSequence {
return descriptions(resources).getOrElse(value) { "" }
open val entries get() = resources.getArrayOrEmpty(entryRes) }
open val entryValues get() = resources.getArrayOrEmpty(entryValRes) }
override val description: TransitiveText
get() = entries.getOrNull(value)?.asTransitive() ?: TransitiveText.EMPTY
private fun Resources.getArrayOrEmpty(id: Int): Array<String> = private fun Resources.getArrayOrEmpty(id: Int): Array<String> =
runCatching { getStringArray(id) }.getOrDefault(emptyArray()) runCatching { getStringArray(id) }.getOrDefault(emptyArray())
override fun onPressed(view: View, callback: Callback) {
if (entries.isEmpty()) return
super.onPressed(view, callback)
}
override fun onPressed(view: View) { override fun onPressed(view: View) {
MagiskDialog(view.context) MagiskDialog(view.context)
.applyTitle(title.getText(resources)) .applyTitle(title.getText(view.resources))
.applyButton(MagiskDialog.ButtonType.NEGATIVE) { .applyButton(MagiskDialog.ButtonType.NEGATIVE) {
titleRes = android.R.string.cancel titleRes = android.R.string.cancel
} }
.applyAdapter(entries) { .applyAdapter(entries(view.resources)) {
value = it value = it
} }
.reveal() .reveal()

View File

@@ -5,11 +5,11 @@ import android.view.View
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.arch.BaseUIFragment import com.topjohnwu.magisk.arch.BaseUIFragment
import com.topjohnwu.magisk.databinding.FragmentSettingsMd2Binding import com.topjohnwu.magisk.databinding.FragmentSettingsMd2Binding
import com.topjohnwu.magisk.di.viewModel
import com.topjohnwu.magisk.ktx.addSimpleItemDecoration import com.topjohnwu.magisk.ktx.addSimpleItemDecoration
import com.topjohnwu.magisk.ktx.addVerticalPadding import com.topjohnwu.magisk.ktx.addVerticalPadding
import com.topjohnwu.magisk.ktx.fixEdgeEffect import com.topjohnwu.magisk.ktx.fixEdgeEffect
import com.topjohnwu.magisk.ktx.setOnViewReadyListener import com.topjohnwu.magisk.ktx.setOnViewReadyListener
import org.koin.androidx.viewmodel.ext.android.viewModel
class SettingsFragment : BaseUIFragment<SettingsViewModel, FragmentSettingsMd2Binding>() { class SettingsFragment : BaseUIFragment<SettingsViewModel, FragmentSettingsMd2Binding>() {

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