Compare commits

...

939 Commits

Author SHA1 Message Date
topjohnwu
64effe9385 Add v21.0 release notes 2020-10-03 03:30:22 -07:00
topjohnwu
96dd24e91d Add changelogs and release notes 2020-10-03 02:53:10 -07:00
topjohnwu
fbb4f85ef0 Update documentation 2020-10-03 02:53:10 -07:00
topjohnwu
716f06846b Use GitHub pages URLs for public channel JSONs 2020-10-03 02:51:51 -07:00
topjohnwu
241f2656fa Prepare for public release 2020-10-03 02:42:02 -07:00
Jarl-Penguin
e973d49517 Fix Korean translation typo 2020-10-03 02:41:47 -07:00
Taewan Park
c3bf9a095b Update korean translation
Update Magisk Manager app translation
 - Korean translation updated
(Translation based on "en" strings.xml)
2020-10-03 02:03:24 -07:00
cristisilaghi
abfc28db32 Update Romanian 2020-10-03 02:02:58 -07:00
topjohnwu
8b5652ced5 Skip image padding on Pixel C 2020-09-29 02:49:10 -07:00
Vladimír Kubala
d6dbab53cd Update Slovak translation 2020-09-28 04:47:23 -07:00
topjohnwu
46de1ed968 Better handling of data encryption 2020-09-28 04:45:56 -07:00
topjohnwu
9bebe07d5a Better network connection observing 2020-09-27 21:21:38 -07:00
topjohnwu
ee4db43136 Update proguard rules
Fix #3190
2020-09-27 04:49:49 -07:00
topjohnwu
efac220998 Fix strings 2020-09-27 04:40:07 -07:00
Ludovico Latini
31026b43f4 Update strings.xml 2020-09-27 04:35:05 -07:00
Rikka
bc3fbe09f5 Update several colors in themes
* Change color for Mew theme

The original color looks like disabled color.

* Change color for Zapdos theme

The original colors have extremely poor readability. For yellow colors, it is difficult to balance readability and beauty, maybe remove it is a better choice?

* Change colors

- Use original colors for dark themes
- Adjust light colors

* Change colorError for dark themes
2020-09-27 04:34:38 -07:00
vvb2060
7ac55068db Catch ActivityNotFoundException 2020-09-27 04:33:46 -07:00
topjohnwu
6abd9aa8a4 Add new --install-module command
Close #2253
2020-09-26 16:50:41 -07:00
topjohnwu
c91ebfbcc1 Pad images to original sizes with zero
Close #2005
2020-09-26 14:36:57 -07:00
topjohnwu
2f232fc670 Support modern Samsung AP.tar patching 2020-09-26 13:32:51 -07:00
topjohnwu
41f5c8d96c Magisk Manager always have to be upgraded first 2020-09-24 03:16:43 -07:00
topjohnwu
4fd04e62af Remove compressed ramdisk support
It is causing more issues than it addresses
2020-09-24 02:49:09 -07:00
Viktor De Pasquale
63a9a7d643 Fixed bottom bar not hiding on device without root 2020-09-24 00:51:08 -07:00
vvb2060
a63d6c03fd Update dependencies 2020-09-23 20:57:19 -07:00
vvb2060
fd552e68a9 Don't hide app with uid < 10000 2020-09-23 20:57:19 -07:00
vvb2060
de4e26b488 Allow download modules when Magisk is not installed 2020-09-23 20:57:19 -07:00
vvb2060
fa3865e962 Check command result 2020-09-23 20:57:19 -07:00
vvb2060
a6950b8aca Add failed state 2020-09-23 20:57:19 -07:00
vvb2060
8df96ff664 Fix string 2020-09-23 20:57:19 -07:00
Ilya Kushnir
8b29267ad6 Update RU strings 2020-09-23 05:08:05 -07:00
topjohnwu
0ef92a4866 Hide OTA option on Pixel devices 2020-09-23 04:49:38 -07:00
topjohnwu
85bef8fa96 Fix install fragment changelog 2020-09-23 04:49:38 -07:00
topjohnwu
ca9f9fee9a Update device state detection 2020-09-23 04:49:38 -07:00
Viktor De Pasquale
b59e05c63e Added a check against view being initialized in its behavior
The view will be simply hidden if not
2020-09-22 20:40:28 -07:00
Viktor De Pasquale
3c0630bfc0 Added forced checks on view being attached to window before performing hiding on it 2020-09-22 20:40:28 -07:00
Viktor De Pasquale
bf84dd6518 Added hiding bottom bar when magisk is not active 2020-09-22 20:40:28 -07:00
nikk
f575155a41 Fix focus on main elements in Modern UI
Co-authored-by: John Wu <topjohnwu@gmail.com>
2020-09-21 03:27:29 -07:00
AdiityaAndre
bd240ba48c Update Indonesian translations 2020-09-21 03:18:29 -07:00
孟武.尼德霍格.龍
106a2bb7df 更新繁體字串
新增部分字串
修正部分字串標點符號
2020-09-21 03:17:52 -07:00
Rom
82bbbe05b2 Update French translation
According to 765b51285a
2020-09-21 03:17:08 -07:00
Vladimír Kubala
9956dc0995 Update Slovak translation 2020-09-21 03:16:44 -07:00
RikkaW
fc76673802 Black splash screen background for dark theme 2020-09-21 03:15:59 -07:00
topjohnwu
17b5291bbb Fix Android 8.0 selinux rules 2020-09-20 14:39:11 -07:00
topjohnwu
9908dfd79a Hide MagiskHide entry when disabled 2020-09-20 14:30:57 -07:00
topjohnwu
2dbaf9595c Remove strokes from most cards 2020-09-17 02:27:43 -07:00
topjohnwu
9a16ab1bd7 Always show install button
Fix #3172
2020-09-16 23:55:50 -07:00
topjohnwu
9e5cb6cb91 Proper way to setup attr colors 2020-09-16 22:16:28 -07:00
vvb2060
8c19654d20 Update zh-rCN translation 2020-09-13 15:13:33 -07:00
RikkaW
d5a7a75d9d Set android:windowContentOverlay to null so that there will be not "shadow" on pre-21 2020-09-13 12:40:40 -07:00
topjohnwu
851b676077 Remove custom fonts 2020-09-13 06:51:17 -07:00
topjohnwu
765b51285a Add settings to disable DoH
Close #3130
2020-09-13 04:34:00 -07:00
topjohnwu
8a338de696 Hide MagiskHide pre Kitkat 2020-09-13 03:55:12 -07:00
topjohnwu
8a61ae621d Disable DoH upon failure 2020-09-13 03:46:06 -07:00
topjohnwu
60e1e07e87 Proper SafetyNet UI databinding 2020-09-13 00:23:23 -07:00
topjohnwu
e51a3dacb9 Support theme switching pre SDK 21 2020-09-12 18:42:05 -07:00
topjohnwu
9a8a27dbb9 Do not access color attributes in background
Workaround with ImageView tints
2020-09-12 03:17:21 -07:00
topjohnwu
2eb001876a Code cleanup 2020-09-11 03:17:43 -07:00
topjohnwu
b510dc51ac Fix su request auto response 2020-09-11 03:09:01 -07:00
topjohnwu
d7f7508fa2 Move setContentView out of onCreate 2020-09-11 02:31:41 -07:00
Rom
e66b0bf3b2 Little French translation update 2020-09-10 01:13:20 -07:00
AioiLight
0555b73a19 Update strings.xml 2020-09-10 01:12:52 -07:00
RoySchutte
877a297de4 Update strings.xml
Fixed 1 grammatical error, and changed a string to the proper translation.
2020-09-10 01:12:17 -07:00
vvb2060
49559ec0ec try root if adb install fail 2020-09-10 01:02:50 -07:00
topjohnwu
30e45f863d Remove unnecessary workaround 2020-09-10 01:01:51 -07:00
topjohnwu
434efec860 Use FIFO for su request communication
Fix #3159
2020-09-10 00:38:29 -07:00
topjohnwu
5022f00a55 Cleanup homescreen 2020-09-08 23:40:44 -07:00
topjohnwu
8aac373ca3 Fix network status detection 2020-09-08 21:55:43 -07:00
topjohnwu
c3586fe0a5 Upstream external/selinux
Fix #2645
2020-09-04 19:20:08 -07:00
topjohnwu
11f254e5e5 Fix SELinux support for Android 8.0 - 10
Fix #3139
2020-09-04 14:42:09 -07:00
topjohnwu
c61ec2465f Rename function to be more descriptive 2020-09-04 06:21:25 -07:00
topjohnwu
fd5ad91d26 Proper 2SI detection 2020-09-04 06:06:03 -07:00
topjohnwu
5c4c391f94 Fix typo causing rootfs device bootloops
Fix #3134
2020-09-04 04:43:46 -07:00
topjohnwu
4dacffd7a1 Fix some issues with selinux rules 2020-09-04 00:03:24 -07:00
topjohnwu
61599059d5 Fix typo for SAR first stage init 2020-09-03 19:17:25 -07:00
topjohnwu
f32a29911b Properly detect 2SI init
Fix #2994
2020-09-02 21:20:15 -07:00
topjohnwu
b73d5753f2 Minor code cleanups 2020-09-02 02:49:32 -07:00
Simon Shi
2eee335b5f Track more sepolicy cil files.
Reference: https://android.googlesource.com/platform/system/core/+/refs/tags/android-r-beta-3/init/selinux.cpp
2020-08-31 21:38:55 -07:00
topjohnwu
013a2e1336 Minor code changes 2020-08-31 04:02:47 -07:00
topjohnwu
fbaf2bded6 Patch AndroidManifest.xml properly
Parse and rebuild the string pool of the AXML format for patching
string in AndroidManifest.xml
2020-08-31 03:39:20 -07:00
o4x
38a34a7eeb Add persian language 2020-08-30 12:57:03 -07:00
vvb2060
70174e093b Optimize network status display 2020-08-30 12:56:36 -07:00
vvb2060
0333e82e86 Fix string 2020-08-30 12:46:22 -07:00
peter9811
36a8839cf8 Update strings.xml 2020-08-30 12:46:03 -07:00
peter9811
d0ed6e7fe3 Update spanish 2020-08-30 12:46:03 -07:00
孟武.尼德霍格.龍
72dfbf5e44 更新繁體中文字串
更新繁體字串到適用v295的版本及以後
2020-08-30 12:45:19 -07:00
topjohnwu
114a3c037f Some minor UI tweaks 2020-08-29 22:55:18 -07:00
topjohnwu
782adc9a9f Cleanup some styles 2020-08-29 21:42:05 -07:00
vvb2060
e0642b018d Update zh-rCN translation 2020-08-28 04:54:32 -07:00
topjohnwu
6bd4006652 Rename method 2020-08-28 04:50:46 -07:00
topjohnwu
01efe7a4ea 100% functional manager self upgrade
Fix #2929
2020-08-28 04:46:05 -07:00
topjohnwu
7e133b0cf4 UI tweaks for pre API 21 2020-08-27 05:46:41 -07:00
topjohnwu
fd808bd51e Use the correct handler 2020-08-27 04:17:21 -07:00
topjohnwu
b4e8860ee4 Only make navbar and status bar transparent on 21+
There is no easy way to handle insets pre 21, forget about it
2020-08-27 04:07:34 -07:00
topjohnwu
fb3f8605fd Change to a more recognizable icon 2020-08-27 03:10:25 -07:00
topjohnwu
e394445f1b Properly handle dialog dismiss for SafetyNet
Fix #3103
2020-08-26 18:54:38 -07:00
topjohnwu
ca1b0bf1ce Fix strings 2020-08-26 18:51:22 -07:00
topjohnwu
bf5798190d Fix dialog when toggling keyboard multiple times 2020-08-26 06:39:59 -07:00
AdiityaAndre
ca5030a646 Updated stub translation
* small derp in main translation
2020-08-26 06:00:00 -07:00
AdiityaAndre
e22324e434 Update and improve Indonesian translation 2020-08-26 06:00:00 -07:00
JoanVC100
e46d4ecd3e Corrections and update ca-strings 2020-08-26 05:58:10 -07:00
topjohnwu
84f92bd661 Cleanup hide fragment code 2020-08-26 05:46:37 -07:00
vvb2060
b44dcc2da0 Fix SafetyNet 2020-08-24 06:27:05 -07:00
topjohnwu
d6062944f1 Update snet extension to prevent crashes on release builds 2020-08-24 06:24:58 -07:00
Ilya Kushnir
79f549795b Update main RU strings 2020-08-24 04:14:03 -07:00
Ilya Kushnir
eaf7c3c486 Update stub RU strings 2020-08-24 04:14:03 -07:00
Rom
1ac379c17a Update French translation 2020-08-24 04:12:58 -07:00
kubalav
51a4dbf263 Update strings.xml 2020-08-24 04:08:30 -07:00
kubalav
2d91bfd9e6 Update Slovak translation 2020-08-24 04:08:30 -07:00
topjohnwu
e437ffdbae Improvements to the installation UI
- No longer show irrelevant options to the user
- No longer require an additional button press to start installation
2020-08-24 04:04:52 -07:00
topjohnwu
ccde8b73a2 Cleanup install screen layout 2020-08-24 00:19:08 -07:00
topjohnwu
65f88e4ae2 Remove unnecessary permissions 2020-08-23 04:36:22 -07:00
topjohnwu
354440ee8a Fix hide manager dialog 2020-08-23 04:24:06 -07:00
topjohnwu
59106e4f52 Make sure app relaunching works after upgrade 2020-08-23 03:47:05 -07:00
topjohnwu
d76c266fbc Add strings that will be used in stub 2020-08-23 00:12:58 -07:00
topjohnwu
31681c9c5f Remove ProcessPhoenix 2020-08-23 00:12:58 -07:00
topjohnwu
0e5a32b476 Close streams 2020-08-22 20:33:50 -07:00
topjohnwu
a22a1dd284 Only offer shortcuts when running as stub 2020-08-22 10:51:32 -07:00
topjohnwu
27c59dbb65 Disable animations in toolbar
Fix #2907
2020-08-22 05:52:57 -07:00
topjohnwu
fb04e32480 Bypass external rw check in the proper location 2020-08-22 05:27:30 -07:00
topjohnwu
14a2f63b8b Several changes for using MediaStore
- Change config key name so default downloads to folder 'Download'
- Always use getFile as we do not need existing file deleted
- Fallback to use File based I/O pre API 29 as officially MediaStore
  APIs do not support general purpose usage. And also, it was working
  fine on all devices before. If it ain't broke, don't fix it
- Show full download path in settings to make it more clear to the user
- Close streams after using them
2020-08-22 04:38:51 -07:00
vvb2060
9e81db8692 Support scoped storage 2020-08-22 04:38:51 -07:00
topjohnwu
1ed67eed35 Rename classes and fields 2020-08-21 06:45:40 -07:00
topjohnwu
abc5457136 Cleanup DownloadService 2020-08-21 06:27:13 -07:00
topjohnwu
4b238a9cd0 Add feature to create launch shortcuts 2020-08-21 03:36:12 -07:00
topjohnwu
f200d472ef Move icon resources out of stubs 2020-08-20 06:02:22 -07:00
topjohnwu
105b2fc114 Ensure SplashActivity is ran before MainActivity 2020-08-19 05:19:24 -07:00
topjohnwu
5ed4071f74 Change ActivityExecutor signature 2020-08-19 05:19:24 -07:00
topjohnwu
551a478fdc Fix occasional broken animation 2020-08-19 05:19:24 -07:00
topjohnwu
7c319f5fc3 Moar refactoring 2020-08-19 05:19:24 -07:00
topjohnwu
1fcf35ebeb Do not hardcode appcompat widget classes
They should be handled by the theme
2020-08-19 05:19:24 -07:00
osm0sis
6d749a58c6 scripts: fix addon.d using $TMPDIR
/data/adb/magisk/addon.d.sh: cd: line 73: can't cd to /dev/tmp: no such file or directory
2020-08-19 02:05:58 -07:00
topjohnwu
34450cdddd More refactoring
Cleanups, move classes to sane locations, etc.
2020-08-19 02:05:23 -07:00
topjohnwu
846bbb4da1 Reorganize app source code 2020-08-18 06:31:15 -07:00
topjohnwu
d7a26dbf27 Tidy up ViewEvents 2020-08-18 06:03:12 -07:00
topjohnwu
a86d5b3e61 Remove unnecessary abstractions 2020-08-18 05:03:56 -07:00
topjohnwu
b2bece9ef6 Fix resources 2020-08-18 02:53:47 -07:00
topjohnwu
f9cbf883ac Update Kotlin 2020-08-18 01:52:05 -07:00
topjohnwu
7f225b3973 Minor MarkwonImagePlugin updates 2020-08-15 23:20:49 -07:00
孟武.尼德霍格.龍
72e7605fce 更新繁體中文字串
同上
2020-08-15 22:16:43 -07:00
vvb2060
a4c1ddd9f2 Use uid 2000 to install patched apk 2020-08-15 22:16:15 -07:00
cristisilaghi
ddd513110f Update Romanian 2020-08-15 22:07:35 -07:00
topjohnwu
e33d623d40 Update dependencies 2020-08-15 05:43:28 -07:00
Rom
eec19ba9af Update French translation
It should contain all new strings.
2020-08-14 03:24:07 -07:00
Mevlüt TOPÇU
413b3f394b Update strings.xml
Hi,

Update Turkish language

Merge please

Thanks
2020-08-14 03:20:58 -07:00
Ilya Kushnir
88cee1212b Update RU strings 2020-08-14 03:20:02 -07:00
vvb2060
cf25fa8ed8 Update build.gradle 2020-08-14 03:18:45 -07:00
topjohnwu
3f053b8547 Minor code changes 2020-08-14 03:17:10 -07:00
Viktor De Pasquale
79aa261ca2 Fixed manager beginning to hide immediately on field change
Bug was caused by lenient usage of "value" property defined in the "line item" in settings. Developer error allowed to use the internal value, that was not properly protected, in a way that did not conform with the latest "Observer" rewrite.

Additional comments were added to hopefully prevent bugs of this kind in the future. The property is now properly protected so it gives away clues that this access is considered "not cool".
2020-08-14 02:23:03 -07:00
topjohnwu
ac2a9da4c4 Fix Markdown rendering
Close #3074
2020-08-14 02:00:06 -07:00
Viktor De Pasquale
d8b1d79879 Fixed first title being partially obscured by toolbar 2020-08-12 04:33:00 -07:00
topjohnwu
feb0f4b7b5 Fix MagiskDialog 2020-08-12 03:33:19 -07:00
topjohnwu
6c8fe46590 Remove unused resources 2020-08-11 04:33:07 -07:00
Taras
5e3c9e5022 Update Ukrainian translation 2020-08-11 03:39:29 -07:00
Vladimír Kubala
f7f821b93c Update Slovak translation 2020-08-11 03:39:01 -07:00
topjohnwu
36a70e995f Remote -> Online 2020-08-11 03:36:41 -07:00
topjohnwu
537ae1a315 Cleanup setting items 2020-08-11 03:30:00 -07:00
topjohnwu
87b6bf2c26 Remove strip in settings item 2020-08-11 00:54:19 -07:00
topjohnwu
9df6b0618a Update MagiskHide list 2020-08-10 07:05:07 -07:00
topjohnwu
c7e30ac63e Update superuser list 2020-08-10 02:33:44 -07:00
topjohnwu
f5e547944a Do not toggle when clicking cards 2020-08-09 22:30:38 -07:00
topjohnwu
d10680187d Nest CardView with alpha in another FrameLayout
RecyclerView animator will mess with alpha when animating
Check this StackOverflow question for more info:
https://stackoverflow.com/questions/40942116/setalpha-on-onbindview-in-recyclerview-doesnt-work-on-first-display
2020-08-09 22:04:09 -07:00
topjohnwu
f5aa6a3cf8 Update module fragment
Update UI and logic for loading modules
2020-08-09 21:41:23 -07:00
topjohnwu
c944277e78 Use switches with 2 way binding instead of custom ImageView 2020-08-09 14:50:16 -07:00
topjohnwu
2e5402d741 Disable scroll effect for icon links 2020-08-09 13:29:18 -07:00
topjohnwu
24f6024383 More homescreen UI improvements 2020-08-09 06:52:02 -07:00
topjohnwu
15b1215972 Only show SafetyNet when GMS exists 2020-08-09 04:39:12 -07:00
topjohnwu
11222c89d4 Update SafetyNet icon 2020-08-09 03:37:34 -07:00
topjohnwu
893a8ec8d9 Strip out most StaggeredGridLayoutManager in code 2020-08-09 03:30:00 -07:00
topjohnwu
da2b00de59 Several home screen changes 2020-08-09 02:32:13 -07:00
RoySchutte
1276c28e03 Update strings.xml 2020-08-08 05:26:21 -07:00
vvb2060
e458215f27 Let isolation namespace base on app namespace 2020-08-08 05:21:03 -07:00
vvb2060
fee4031d0f Keep disable file when module update 2020-08-08 05:19:41 -07:00
topjohnwu
0835ff88b2 Update zipsigner version 2020-08-08 05:12:02 -07:00
vvb2060
2e95d9f07e Update to APK Signature Scheme v2 2020-08-08 05:12:02 -07:00
topjohnwu
fe2388394d Update dependencies 2020-08-08 04:12:08 -07:00
topjohnwu
7fc9b908d4 Update Android 11 emulator support 2020-07-27 03:33:11 -07:00
classic-gentleman
0ed524f173 Test for NVIDIA/Tegra partition naming scheme first
Fixes https://github.com/topjohnwu/Magisk/issues/3014
2020-07-24 00:26:51 -07:00
topjohnwu
aed3ab994e Update libsu 2020-07-24 00:26:21 -07:00
topjohnwu
5347cedfa6 Disable Jetifier
Jetifier does not support multi-release JARs
2020-07-23 23:12:38 -07:00
topjohnwu
5b28a713e0 Move functions around 2020-07-23 22:43:25 -07:00
topjohnwu
f1fb7404c2 Catch exceptions when loading remote modules
Fix #3004
2020-07-20 22:35:50 -07:00
topjohnwu
fc67c0195f Workaround bug in AOSP code
Fix #2983, https://issuetracker.google.com/issues/36984866
2020-07-20 22:22:57 -07:00
topjohnwu
2f02f9a580 Update libsu 2020-07-20 21:58:23 -07:00
topjohnwu
07f712a1ce Always show hidden apps 2020-07-17 06:05:38 -07:00
topjohnwu
c7044b0d20 Remember show system app toggle in MagiskHide list 2020-07-17 05:32:08 -07:00
topjohnwu
15866cfba9 Fix incorrect command
Fix #2992
2020-07-17 05:28:18 -07:00
topjohnwu
4c2570628d Make SettingsItems make much more sense 2020-07-17 03:02:58 -07:00
topjohnwu
113eec59f9 Request storage rw for saving logs
Fix #2993
2020-07-17 01:27:52 -07:00
topjohnwu
f7abc03dac Move copy util_functions.sh from Python scripts to gradle 2020-07-17 00:44:51 -07:00
antikruk
ef3f188a2c bel 2020-07-17 00:16:32 -07:00
topjohnwu
dd62fe89f7 Use CallbackList for collecting STDOUT in flash screen
Fix #2988
2020-07-17 00:13:18 -07:00
topjohnwu
ec2d7d77eb Reduce usage of ObservableField 2020-07-15 02:52:15 -07:00
topjohnwu
6c6368fd81 Reduce usage of delegation 2020-07-15 01:21:57 -07:00
topjohnwu
ba31c6b625 Use coroutines instead of raw executors 2020-07-14 02:37:52 -07:00
topjohnwu
cad189d2dc Remove unnecessary indirection 2020-07-12 14:37:07 -07:00
topjohnwu
7cf3da1b3b Update implementation to use new methods 2020-07-12 14:35:21 -07:00
topjohnwu
45fabf8e03 Update SettingsItems 2020-07-12 06:15:32 -07:00
topjohnwu
2c12fe6eb2 More efficient databinding 2020-07-12 03:17:50 -07:00
topjohnwu
b41b2283f4 Rename package 2020-07-11 05:36:31 -07:00
topjohnwu
e8e7cd5008 Simply redirect isConnected ObservableField 2020-07-11 03:22:17 -07:00
topjohnwu
7873433977 Remove RxJava as dependency 2020-07-10 23:12:09 -07:00
topjohnwu
52d19d3ea2 Roll our own NetworkObserver 2020-07-10 23:12:09 -07:00
topjohnwu
6348d0a6fb Remove more code using RxJava 2020-07-10 04:19:18 -07:00
topjohnwu
f7a650b9a4 Clear up RxJava from ViewModels 2020-07-09 05:15:53 -07:00
topjohnwu
a97d278bcd Remove RxBus 2020-07-09 05:13:24 -07:00
topjohnwu
8647ba4729 Remove more RxJava 2020-07-09 04:49:14 -07:00
topjohnwu
4631077c49 Call the correct constructor 2020-07-09 04:40:07 -07:00
topjohnwu
18dab28c32 Remove usage of KObservableField 2020-07-08 06:14:32 -07:00
topjohnwu
8ffbffddb3 Update SuRequest handler 2020-07-08 03:13:01 -07:00
topjohnwu
f191db2fe0 Allow ViewModel to opt-out RxJava
Transition period
2020-07-08 01:50:28 -07:00
topjohnwu
dc8f0f6feb Bug fixes in modules fragment
- Progress is not updated in upgradable modules (and can cause crash)
- EndlessRecyclerScrollListener is not reset on new query
2020-07-08 01:40:08 -07:00
topjohnwu
01a43b03bd De-Rx ModuleViewModel 2020-07-08 01:26:45 -07:00
topjohnwu
86db0cd2cd Load installed modules with coroutine 2020-07-07 03:37:53 -07:00
topjohnwu
ae6dd50ccd Fix RepoUpdater force refresh bug 2020-07-07 03:18:01 -07:00
topjohnwu
77032eced1 Load repos with coroutine 2020-07-07 01:57:56 -07:00
topjohnwu
820427e93b Have some fun with Kotlin Coroutines 2020-07-06 22:30:21 -07:00
topjohnwu
89e11c9cc8 Minor changes in flash viewmodel 2020-07-06 21:05:43 -07:00
topjohnwu
05cf53fe6f Merge files 2020-07-06 15:40:05 -07:00
topjohnwu
97b72a5941 Revert to old SElinux rules on pre 8.0 devices
Fix #2910
2020-07-06 01:13:50 -07:00
topjohnwu
7922f65243 Welcome Gradle Kotlin DSL 2020-07-04 06:54:53 -07:00
topjohnwu
67f7935421 Restructure project 2020-07-04 04:09:19 -07:00
topjohnwu
9348c5bad9 Fix build script 2020-07-04 03:50:39 -07:00
topjohnwu
0f7caa66fb Remove usage of grid layouts 2020-07-04 03:28:21 -07:00
Mexit
bd14994eb9 Update Polish translation 2020-07-04 03:01:55 -07:00
vvb2060
08818e8542 Remove force_pm_install
gms package verifier only checks the `notBefore` value of the certificate
2020-07-04 03:00:51 -07:00
topjohnwu
706eba329d Add release notes to the install fragment 2020-07-04 02:55:19 -07:00
topjohnwu
f6a2b1c882 Minor gradle script changes 2020-07-02 05:01:55 -07:00
topjohnwu
c2e6622016 Update README
Recommend Android Studio embedded JDK again
2020-07-02 04:16:02 -07:00
topjohnwu
53904b0627 Use gradle magic to optimize resources 2020-07-02 04:02:20 -07:00
waffshappen
cef14d4576 Fix release build XLint error for translations
Due to the accidental safety>N<et the release build would fail with XLint complaining about a missing default translation. Correcting this to be in line with the actual translation fixes the build error.


Xlint Error in Question: 

```res/values-in/strings.xml:106: Error: "safetyNet_api_error" is translated here but not found in default locale [ExtraTranslation]
    <string name="safetyNet_api_error">Kesalahan API SafetyNet</string>```
2020-07-01 09:56:14 -07:00
topjohnwu
73203a55ca Use fancy NestedScrollView for Magisk logs 2020-06-30 04:14:23 -07:00
topjohnwu
397f7326a3 Update SafetyNet UI to show evalType 2020-06-30 03:56:41 -07:00
topjohnwu
4bbd7989dd Update snet extension
Receive full snet payload from extension
2020-06-30 02:24:58 -07:00
topjohnwu
a0b47f3ca3 Precompute TextView in I/O thread for performance 2020-06-29 05:26:07 -07:00
topjohnwu
89e9e7c176 Simplify UI code for Magisk logs
We have all texts, no need to go through recyclerview
2020-06-29 05:22:16 -07:00
topjohnwu
ddc2f317ab Update dependencies 2020-06-29 03:58:19 -07:00
topjohnwu
867bab8513 Restart activity with fresh intent
Fix #2706
2020-06-29 03:30:23 -07:00
topjohnwu
b1e0c5ff38 Export MAGISKTMP so it survives exec
Fix #2926
2020-06-29 03:24:53 -07:00
Shaka Huang
6dbd9bfb12 Place pthread_mutex_init() before init_list()
Fix crash in #2900

Signed-off-by: Shaka Huang <shakalaca@gmail.com>
2020-06-28 07:06:19 -07:00
topjohnwu
3c78344812 Refactor version handling 2020-06-28 06:52:02 -07:00
Ilya Kushnir
594f268885 Update RU strings 2020-06-27 13:23:45 -07:00
Fox2Code
93d5716414 Disable Volumes keys on flash
- Thank Diareuse for helping me
2020-06-27 13:22:09 -07:00
Shaka Huang
4b8e92f00a compile options should be set after evaulation process
Signed-off-by: Shaka Huang <shakalaca@gmail.com>
2020-06-27 04:58:08 -07:00
vvb2060
fc6ef7dd57 Show magisk update notification only when magisk is installed 2020-06-27 04:54:58 -07:00
mustafairaqi8
c881fd4964 Updated Arabic Translation 2020-06-25 10:02:15 -07:00
Viktor De Pasquale
4bcc2b2f03 Added bottom padding to hide screen
Close #2903
2020-06-25 10:01:37 -07:00
topjohnwu
6150055a05 Update BusyBox 2020-06-25 04:34:16 -07:00
topjohnwu
23a33b4351 Remove core only mode
Replaced by native safe mode
2020-06-21 15:59:06 -07:00
topjohnwu
e02386a6ac Move install module button to the top 2020-06-21 12:53:31 -07:00
topjohnwu
099e703834 Build compatible bytecode with newer JDKs
Fix #2898, close #2899
2020-06-21 02:43:32 -07:00
YFdyh000
1ededc637e l10n: Update Chinese Simplified translations 2020-06-20 12:12:40 -07:00
topjohnwu
0850bca9d3 Update README 2020-06-20 04:58:54 -07:00
topjohnwu
6d2fd480bf Upgrade gradle wrapper 2020-06-20 04:41:54 -07:00
vvb2060
ddf0c379be Fix build 2020-06-20 03:03:46 -07:00
topjohnwu
45b5e89912 Remove canary debug channel
All canary builds will be debug only
2020-06-20 02:45:02 -07:00
Albert I
a748d5291a app: l10n: Update Indonesian translations
Signed-off-by: Albert I <kras@raphielgang.org>
2020-06-20 01:37:47 -07:00
Peter Meiser
f5131fae56 Update German translation 2020-06-20 01:37:27 -07:00
Chris Renshaw
f79a40a67a scripts: uninstaller fixes
- LOS Recovery can't decrypt or even mount /data, thus the installer can't do everything it needs to do and must abort, so also suggest uninstall via Manager at that point
- fix removal of addon.d script when uninstall is run via Manager on SAR
- fix removal of addon.d with dynamic/logical partitions via mapper
2020-06-20 01:37:06 -07:00
topjohnwu
43146b8316 Update su request process
Due to changes in ec3705f2ed, the app can
no longer communicate with the dameon through a socket opened on the
daemon side due to SELinux restrictions. The workaround here is to have
the daemon decide a socket name, send it to the app, have the app create
the socket server, then finally the daemon connects to the app through
the socket.
2020-06-19 03:52:25 -07:00
topjohnwu
b71b4bd4e5 Fix colors in su request dialog 2020-06-19 03:14:23 -07:00
topjohnwu
44895a86b8 Fix compilation of single applets 2020-06-19 02:45:57 -07:00
topjohnwu
eecb66f4f1 Create ForegroundTracker 2020-06-17 04:07:31 -07:00
topjohnwu
e7f1c03151 Cleanup code for su request 2020-06-17 03:47:12 -07:00
topjohnwu
56602cb9a3 Update gradle files 2020-06-17 02:33:33 -07:00
topjohnwu
1e2f776b83 Move logging.hpp 2020-06-17 01:17:28 -07:00
topjohnwu
ec3705f2ed Redesign of MagiskSU's sepolicy model
Introduce new domain `magisk_client` and new file type `magisk_exec`.

Connection to magiskd's always-on socket is restricted to magisk_client
only. Whitelisted process domains can transit to magisk_client through
executing files labelled magisk_exec. The main magisk binary shall be
the only file labelled as magisk_exec throughout the whole system.
All processes thus are no longer allowed to connect to magiskd directly
without going through the proper magisk binary.

Connection failures are silenced from audit logs with dontaudit rules,
so crazy processes which traverse through all unix domain sockets to try
connection can no longer check logcat to know the actual reason behind
EACCES, leaking the denied process policy (which is u:r:magisk:s0).

This also allows us to remove many rules that open up holes in
untrusted_app domains that were used to make remote shell work properly.
Since all processes establishing the remote shell are now restricted to
the magisk_client domain, all these rules are moved to magisk_client.
This makes Magisk require fewer compromises in Android's security model.

Note: as of this commit, requesting new root access via Magisk Manager
will stop working as Magisk Manager can no longer communicate with
magiskd directly. This will be addressed in a future commit that
involves changes in both native and application side.
2020-06-03 23:29:42 -07:00
topjohnwu
ae0dcabf43 Fix typo in sepolicy.cpp 2020-06-03 03:11:10 -07:00
topjohnwu
6030b00ee2 Remove excessive logging 2020-06-03 01:28:50 -07:00
topjohnwu
a17908f6e1 Only resolve via DoH for specific hostnames 2020-06-03 01:15:05 -07:00
topjohnwu
cb7148a24c Switch to debug logging in modules 2020-06-01 04:22:57 -07:00
topjohnwu
2f824f59dc Better logging system
Use C++ magic to strip out debug logs at compile time
2020-06-01 04:15:37 -07:00
Chris Renshaw
ad94f10205 Fix direct install on NAND devices
Co-authored-by: John Wu <topjohnwu@gmail.com>
2020-06-01 02:08:13 -07:00
Shaka Huang
02b2290b16 Correct path of x86 libraries
Signed-off-by: Shaka Huang <shakalaca@gmail.com>
2020-05-31 05:33:35 -07:00
Ilya Kushnir
f8a814a588 Fix RU strings 2020-05-31 05:32:18 -07:00
topjohnwu
4c4338cc02 Adapt to AGP 4.0 2020-05-30 13:06:03 -07:00
Facundo Montero
5675a1ae7d app/stub: values-es: update to provide more consistency.
This update aims to provide better consistency to the Spanish
translation by properly separating each possible pronoun.

Other small grammar errors have also been corrected.
2020-05-30 12:56:37 -07:00
AioiLight
0952224c3d Update JA strings 2020-05-30 12:56:02 -07:00
JoanVC100
4e26c10287 Fix CA strings 2020-05-30 12:55:26 -07:00
vvb2060
f3e82b9ef1 Add DoH using cloudflare-dns 2020-05-30 12:53:46 -07:00
osm0sis
e50295d337 magiskboot: add support for lz4 compressed dt (extra)
- legacy devices brought up to Android 10 may now use a compressed dt in a hdr_v0 AOSP dt variant extra section, so detect, decompress and recompress this
- so far these have only been done using lz4 compression (latest format revision magic), e.g. LOS 17.1 victara (Moto X)
2020-05-30 12:52:15 -07:00
topjohnwu
fde78be2b4 Update Android Studio 2020-05-30 12:50:08 -07:00
topjohnwu
c071ac8973 Remove unused code 2020-05-29 10:41:52 -07:00
topjohnwu
599ee57d39 Simplify sepolicy rules 2020-05-25 02:30:39 -07:00
topjohnwu
4499cebcd9 Support new sepolicy rules
Support declare new type with attribute and declare new attributes
2020-05-25 02:09:43 -07:00
topjohnwu
cd6eca1dc2 Optimize match-all-type rules
For match-all-type rules (e.g. "allow magisk * * *" used in Magisk),
we used to iterate and apply rules on all existing types. However, this
is actually unnecessary as all selinux types should have at least 1
attributes assigned to it (process types "domain", file context types
"file_type" etc.). This means in order to create rules that applies to
all types, we actually only need to create rules for all attributes.

This optimization SIGNIFICANTLY reduces the patched sepolicy that is
loaded into the kernel when running Magisk. For example on Pixel 4 XL
running Android R DP4, the sepolicy sizes are
patched (before) : 3455948
patched (after)  : 843176
stock            : 630229

The active sepolicy size actually impacts the performance of every single
operation in the operating system, because the larger the policies gets,
the longer it takes for the kernel to lookup and match rules.
2020-05-24 05:41:19 -07:00
topjohnwu
951273f8ef Cleanup some implementations 2020-05-24 04:16:40 -07:00
vvb2060
51eeb89f67 Allow consecutive points 2020-05-23 14:58:17 -07:00
topjohnwu
0efa73d96c Update selinux libs 2020-05-23 05:02:26 -07:00
topjohnwu
63512b39b2 Update NDK to r21b 2020-05-23 00:48:49 -07:00
topjohnwu
f392ade78d Rewrite sepolicy.c in C++ 2020-05-23 00:18:25 -07:00
topjohnwu
0236ab887e Several statement parsing improvements
- Update help message to match the spec
- Make tokenization not seg fault in certain conditions
- Moar template + macro magic to reduce boilerplate
2020-05-22 14:05:56 -07:00
topjohnwu
d4baae411b Modernize magiskpolicy 2020-05-21 06:48:02 -07:00
topjohnwu
e02e46d0fc Detect volume down key combo for safe mode
It is possible that a module is breaking the device so bad that zygote
cannot even be started. In this case, system_server cannot start and
detect the safe mode key combo, set the persist property, and reboot.

Also on old Android versions, the system directly goes to safe mode
after detecting a key combo without rebooting, defeating the purpose of
Magisk's safe mode protection if we only check for the persist property.

Directly adding key combo check natively in magiskd allows us to enter
Magisk safe mode before the system is even aware of it.
2020-05-19 04:57:47 -07:00
Chris Renshaw
3c04dab472 magiskhide: fix late_prop_key setprop, reorganize props slightly 2020-05-18 23:31:22 -07:00
topjohnwu
fc1844b4df Update policy for handling /data/adb 2020-05-18 23:29:26 -07:00
topjohnwu
99ef20627a Remove unused code 2020-05-18 05:45:08 -07:00
topjohnwu
4497e0aaca Don't expose module_list 2020-05-18 05:36:02 -07:00
topjohnwu
c3e045e367 Use daemon state to determine late prop hiding 2020-05-18 05:21:47 -07:00
topjohnwu
501d3e6c32 Maintain global daemon status 2020-05-18 05:18:49 -07:00
topjohnwu
b27b9c1d18 Minor code changes 2020-05-18 04:56:51 -07:00
topjohnwu
f7d3d1eeaf Increase post-fs-data mode to 40 secs 2020-05-18 04:56:51 -07:00
topjohnwu
0d72a4c8ba Fix compile error 2020-05-18 04:56:51 -07:00
topjohnwu
dbdb0a2560 Move late props to boot complete 2020-05-18 03:51:41 -07:00
Tornike Khintibidze
18a09703de Updated Georgian translation 2020-05-17 15:09:51 -07:00
topjohnwu
bc6a14d30f Remove property ro.build.selinux 2020-05-17 15:01:37 -07:00
topjohnwu
97db49a57b Move vendor property manipulation to late start 2020-05-17 15:01:37 -07:00
topjohnwu
eca2168685 Guard magiskhide state with mutexes 2020-05-17 15:01:37 -07:00
Hen Ry
1bcef38739 Fix German translation 2020-05-16 22:29:26 -07:00
topjohnwu
aac6ad73da Fix collect modules 2020-05-16 13:45:22 -07:00
topjohnwu
122b4d66b6 Move Android logging out of libutils 2020-05-10 00:48:41 -07:00
topjohnwu
0f8f4e361b Update collect log logic 2020-05-10 00:30:11 -07:00
Chris Renshaw
3733b589ac native: fix slower build on non-Windows platforms 2020-05-09 04:41:07 -07:00
Chris Renshaw
6a2e781db2 magiskhide: add vendor.* props 2020-05-09 04:40:55 -07:00
vvb2060
c6569ce022 Fix service scripts 2020-05-09 04:40:05 -07:00
topjohnwu
a62bdc58cb Use env variables to enable standalone mode 2020-05-08 04:09:58 -07:00
topjohnwu
912009494d Revert accidental build script change 2020-05-08 01:44:10 -07:00
topjohnwu
a5d7c41d20 Support Safe Mode detection
When detecting device is booting as Safe Mode, disable all modules and
MagiskHide and skip all operations. The only thing that'll be available
in this state is root (Magisk Manager will also be disabled by system).

Since the next normal boot will also have all modules disabled, this can
be used to rescue a device in the case when a rogue module causes
bootloop and no custom recovery is available (or recoveries without
the ability to decrypt data).
2020-05-08 00:45:11 -07:00
topjohnwu
232ae2a189 Update resetprop to partially use system impl 2020-05-07 23:54:00 -07:00
topjohnwu
aa8b23105f Modernize resetprop with fancy C++ 2020-05-07 06:08:30 -07:00
topjohnwu
c113f854a2 Fix overlay.d on SAR again 2020-05-07 02:30:43 -07:00
topjohnwu
87de0e7a0e Force remove AVB for 2SI since it may bootloop some devices 2020-05-05 03:29:36 -07:00
topjohnwu
85755e3022 Tone down our DTB patching
- Do not attempt to patch DTB anywhere outside of boot images as they
are no longer essential. This makes Magisk installation to only modify
strictly boot/recovery partitions again.
- The only required patch for DTB is to strip verity out of partitions
2020-05-05 03:29:36 -07:00
topjohnwu
02dc1172be Revert DTB patches to in-place binary patches
Since we no longer need to add new properties in the device tree, and
all the patches we do removes strings, we can just directly patch
the flat device tree in-place, ignoring basically all the higher level
DTB structure and format to accomplish 100% compatibility.
2020-05-05 01:03:09 -07:00
topjohnwu
dbf8c41209 Force init to load fstab from file in 2SI
Patching DTBs is proven to be difficult and problematic as there are
tons of different formats out there. Adding support for all the formats
in magiskboot has been quite an headache in the past year, and it still
definitely does not cover all possible cases of them out there.

There is another issue: fake dt fstabs. Some super old devices do not
have device trees in their boot images, so some custom ROM developers
had came up with a "genius" solution: hardcode fstab entries directly
in the kernel source code and create fake device tree nodes even if
Android 10+ init can graciously take fstab files instead (-_-) 。。。

And there is YET another issue: DTBs are not always in boot images!
Google is crazy enough to litter DTBs all over the place, it is like
they cannot make up their minds (duh). This means the dt fstabs can be
either concatnated after the kernel (1), in the DTB partition (2), in
the DTBO partition (3), in the recovery_dtbo section in boot images (4),
or in the dtb section in boot images (5). FIVE f**king places, how can
anyone keep up with that!

With Android 10+ that uses 2 stage inits, it is crutual for Magisk to
be able to modify fstab mount points in order to let the original init
mount partitions for us, but NOT switch root and continue booting. For
devices using dt for early mount fstab, we used to patch the DTB at
install time with magiskboot. However these changes are permanent and
cannot be restored back at reinstallation.

With this commit, Magisk will read dt fstabs and write them to ramdisk
at boot time. And in that case, the init binary will also be patched
to force it to NEVER use fstabs in device-tree. By doing so, we can
unify ramdisk based 2SI fstab patching as basically we are just patching
fstab files. This also means we can manipulate fstab whatever Magisk
needs in the future without the need to going through the headache that
is patching DTBs at installation.
2020-05-04 02:21:51 -07:00
topjohnwu
8c4fd759c6 Strip Huawei specific logic
Users should manually switch to recovery mode instead
2020-05-03 23:07:40 -07:00
Chris Renshaw
23dc19ad94 scripts: don't abort if /vendor fails to mount
- /vendor is used only on some older devices for recovery AVBv1 signing so is not critical if fails
- this fixes installation in Lineage Recovery on some older devices where /vendor is actually by-name partitions like oem, cust (or even cache), which likely also don't require the AVBv1 signing
2020-05-03 23:06:16 -07:00
topjohnwu
0c99c4d93f More complete support for fstab in dt 2020-05-03 22:49:54 -07:00
topjohnwu
8ab045331b Workaround realpath FORTIFY crashes 2020-05-03 22:11:39 -07:00
topjohnwu
a8d0936e04 Update BusyBox 2020-05-02 03:42:42 -07:00
topjohnwu
4e349acb50 Build libselinux without ANDROID defined 2020-05-01 00:45:23 -07:00
topjohnwu
947e3b06b4 Use template to get lambda for RAII 2020-04-30 01:27:48 -07:00
topjohnwu
5fd574a14f Fix --remove-modules command 2020-04-30 01:27:48 -07:00
osm0sis
03c1053871 scripts: fix persist out-of-space copying sepolicy.rule
- bugged TWRPs were filling persist with recovery logs, so clean those as a potential workaround
- abort module install if sepolicy.rule fails to copy, since 99% of the time the module wouldn't include it if it could function without it

Closes #2461
2020-04-29 20:25:18 -07:00
topjohnwu
c7ed0ef5eb Fix SAR support for overlay.d 2020-04-25 23:19:36 -07:00
osm0sis
2aede97754 scripts: fix find_block false positives /dev/log/kernel and /dev/BOOT
- try /dev/block first with full depth to catch all platform/soc variations to the by-name directory, and the new dynamic partition /dev/block/mapper
- next try uevent for block devices as before
- lastly try /dev with maxdepth 1 (immediate directory) to find /dev/bootimg, /dev/recovery, etc. while avoiding /dev/log/kernel
- move bootimg higher in the list than boot so /dev/bootimg gets found first and avoids /dev/BOOT
- recovery_a/_b now also exists
- minor touch-ups for readability and consistency

Fixes #2720
2020-04-24 02:24:36 -07:00
osm0sis
9b8a5e9bf3 scripts: add author name back to module install banner print 2020-04-24 02:24:36 -07:00
osm0sis
0f910f2d40 scripts: ensure system is able to be mounted rw before attempting
- this is needed for installations on Lineage 17.1 Recovery (AOSP Q) for logical partition devices, which uses /dev/block/mapper to stage the partitions

Thanks LuK1337 & erfanoabdi @ Lineage
2020-04-24 02:24:36 -07:00
topjohnwu
15f155100c Rewrite skel_node mounting and construction logic
Close #2725
2020-04-24 02:07:46 -07:00
topjohnwu
2468f5a6c4 Fix custom sepolicy patches 2020-04-22 23:01:11 -07:00
topjohnwu
945a52a99f Handle extremely rare edge case 2020-04-22 05:07:50 -07:00
topjohnwu
486b2c82a7 Disable kmsg rate limiting 2020-04-22 05:07:50 -07:00
topjohnwu
800b7f4370 Bump min module Magisk version to v20.0
It has been over half an year now, time for an update!
2020-04-21 01:14:14 -07:00
topjohnwu
8ca5a048d6 Support system_ext 2020-04-20 23:57:29 -07:00
topjohnwu
44b7a3c3f1 Only run bootsigner on Android 5.0+
Close #2712
2020-04-20 22:12:14 -07:00
topjohnwu
554ebe7206 Skel dest could not exist
Close #2713
2020-04-20 22:04:57 -07:00
vvb2060
d7b87fcb8e Add untrusted_app_29 for Android 11 2020-04-20 21:50:52 -07:00
topjohnwu
c94f9e1cc9 Use a binary that exists on all devices for hijacking 2020-04-20 04:41:11 -07:00
vvb2060
68532fade3 Update SAR detection method for Android 11 2020-04-20 04:41:11 -07:00
topjohnwu
e219867cdf Hijack another binary for 2nd stage
Instead of using ptrace hacks, use another hack instead :D
2020-04-19 22:15:12 -07:00
topjohnwu
765d5d9729 Small magiskinit cleanup 2020-04-19 04:57:18 -07:00
topjohnwu
43029f37b1 Cleanup our tracks 2020-04-19 04:57:18 -07:00
voodik
7188462c55 fix Magisk install on ODROID-N2/C4
add /dev/block/dtbs support
2020-04-19 02:51:05 -07:00
topjohnwu
f9ff814955 Update gradle files 2020-04-19 02:47:22 -07:00
topjohnwu
dfbd1305b3 Android 11 support 🎉 2020-04-19 02:47:22 -07:00
topjohnwu
c9255ab31b Remove legacy migration
It has been quite a long time ago...
2020-04-18 23:46:56 -07:00
topjohnwu
1e714af3cf Support MagiskHide when /sbin does not exist 2020-04-18 23:45:00 -07:00
topjohnwu
4c959cd983 Support cases when /sbin does not exist 2020-04-18 23:19:19 -07:00
topjohnwu
d959c35723 Make cleaner mount info 2020-04-18 18:50:25 -07:00
topjohnwu
69a9d7485b Support injecting magisk bins 2020-04-18 05:15:59 -07:00
topjohnwu
dcf07ad8c7 Directly filter '.' and '..' in xreaddir 2020-04-18 04:20:21 -07:00
topjohnwu
ed6cdb2eb4 Rename file 2020-04-18 04:10:19 -07:00
topjohnwu
a73e7e9f99 Introduce new module mount implementation
Rewrite the whole module mounting logic from scratch.
Even the algorithm is different compared to the old one.

This new design focuses on a few key points:
- Modular: Custom nodes can be injected into the mount tree.
  It's the main reason for starting the rewrite (needed for Android 11)
- Efficient: Compared to the existing implementation, this is the most
  efficient (both in terms of computation and memory usage) design I
  currently can come up with.
- Accurate: The old mounting logic relies on handling specifically every
  edge case I can think of. During this rewrite I actually found some
  cases that the old design does not handle properly. This new design is
  architected in a way (node types and its rankings) that it should
  handle edge cases all by itself when constructing mount trees.
2020-04-18 02:00:48 -07:00
topjohnwu
ab853e1fcf Update dir traversal code 2020-04-12 13:38:57 -07:00
topjohnwu
37d38b62b1 Fix strings 2020-04-12 05:53:23 -07:00
Ilya Kushnir
f9bb517142 Update RU strings 2020-04-12 05:50:58 -07:00
tzagim
efe9b867d5 Add Hebrew Translation 2020-04-12 05:49:22 -07:00
Viktor De Pasquale
d9cf33d1ba Fixed shortcuts
This has been broken due to recent transition to navigation components
2020-04-12 05:40:19 -07:00
Viktor De Pasquale
ee3028e67d Updated layout of modules screen
The modules will show updates at the top, active modules in the middle and finally remote modules at the bottom. The modules "install" button will be at the top of the "active" list.
This is done over usability concerns, as updates are more important than a list of installed modules.
2020-04-12 05:40:19 -07:00
Viktor De Pasquale
d810e6c82d Fixed modules screen crashing on load
This commit fixes the issue of adding single-span items in between full-span items whilst using `StaggeredGridLayoutManager` on recycler view.

Adding such items results in:
```
java.lang.ArrayIndexOutOfBoundsException: Array index out of range: 13
	at java.util.Arrays.rangeCheck(Arrays.java:123)
	at java.util.Arrays.fill(Arrays.java:2828)
	at androidx.recyclerview.widget.StaggeredGridLayoutManager$LazySpanLookup.invalidateAfter(StaggeredGridLayoutManager.java:2876)
	at androidx.recyclerview.widget.StaggeredGridLayoutManager.handleUpdate(StaggeredGridLayoutManager.java:1548)
	at androidx.recyclerview.widget.StaggeredGridLayoutManager.onItemsUpdated(StaggeredGridLayoutManager.java:1524)
	at androidx.recyclerview.widget.RecyclerView$6.dispatchUpdate(RecyclerView.java:1021)
	at androidx.recyclerview.widget.RecyclerView$6.onDispatchSecondPass(RecyclerView.java:1032)
	at androidx.recyclerview.widget.AdapterHelper.consumePostponedUpdates(AdapterHelper.java:121)
	at androidx.recyclerview.widget.AdapterHelper.consumeUpdatesInOnePass(AdapterHelper.java:557)
	at androidx.recyclerview.widget.RecyclerView.dispatchLayoutStep2(RecyclerView.java:4128)
	at androidx.recyclerview.widget.RecyclerView.dispatchLayout(RecyclerView.java:3851)
	at androidx.recyclerview.widget.RecyclerView.onLayout(RecyclerView.java:4404)
	...and more
```

Affects versions including and prior to androidx.recyclerview:recyclerview:1.2.0-alpha02 (at the time of this commit) and possibly more after that.

This bug is caused by a single fact and that is - array inside of `LazySpanLookup` is not being invalidated and resized correctly when non-full-span item is being added in between of two full-span items. The invalidation however passes on some (high performance) devices so it doesn't necessarily cause issues for _some_ users; others keep getting the same crash over and over again.

Possible fix for anyone reading this, in the hope of fixing the same error, is to copy-paste the `StaggeredGridLayoutManager` and fix the array length before calling `Arrays.fill()`. There's no fix from user's perspective if you need to keep the UI as-is.
We however don't need the UI as-is, so we're instead opting to use LinearLayoutManager until is the issue resolved.

Continues tracking at https://issuetracker.google.com/issues/37034096

Close #2631
2020-04-12 05:40:19 -07:00
topjohnwu
e0a281583d Preparation for dynamic tmpfs path 2020-04-12 05:34:56 -07:00
topjohnwu
d739dcac2b Remove dependency on magisk.hpp in libutils 2020-04-11 04:40:40 -07:00
topjohnwu
cdd4cb8ec2 Update BusyBox to build on latest NDK 2020-04-11 04:05:58 -07:00
topjohnwu
93ef90cd24 Fix FORTIFY crashes 2020-04-11 04:05:34 -07:00
topjohnwu
e165a1e65c Use BusyBox standalone mode if available 2020-04-11 02:21:47 -07:00
topjohnwu
4066e5bf14 Update Makefiles 2020-04-06 22:45:08 -07:00
topjohnwu
4729514a22 Remove snet module from Magisk 2020-04-05 02:13:53 -07:00
topjohnwu
93aedcfeb7 Update all hardcode paths in app and script 2020-04-05 01:27:07 -07:00
topjohnwu
47d18bb896 Fix typo of boot methods 2020-04-04 01:48:28 -07:00
topjohnwu
61dafbe06e Fix LV for Boot Method C 2020-04-04 01:27:27 -07:00
topjohnwu
474325da68 Add 'Android Booting Shenanigans' to docs 2020-04-04 01:17:50 -07:00
topjohnwu
9317401d57 Update Windows instruction for Python 2020-04-03 16:52:28 -07:00
topjohnwu
67d746a62c Let build.py setup NDK 2020-04-03 03:34:07 -07:00
topjohnwu
2f1f68f12f Prepare compilation for NDK r21 2020-04-03 02:58:39 -07:00
Chris Renshaw
2742edd73f scripts: only show addon.d error once on failures 2020-04-02 20:54:56 -07:00
Shaka Huang
834561a5de Content in dt_fstab is not null terminated in emulator
Value of <dt>/fstab/<partition>/dev and <dt>/fstab/<partition>/type in official Android emulator ends with newline instead of \0, Magisk won’t be able to patch sepolicy and crash the system.

Signed-off-by: Shaka Huang <shakalaca@gmail.com>
2020-04-02 20:54:41 -07:00
Chris Renshaw
11102b4dd6 scripts: fix finding nand/mtd boot on some devices
Fixes #2619
2020-04-02 20:52:46 -07:00
zivmc
fef2da3c0b Fix bug in compiling elf_cleaner with g++
Signed-off-by: zivmc <zivmc@users.noreply.github.com>
2020-04-02 20:52:18 -07:00
topjohnwu
9820296e92 Update files.cpp in libutils 2020-04-02 02:17:45 -07:00
topjohnwu
dbfde74c1e Clean rootfs in switch_root 2020-04-01 23:37:11 -07:00
topjohnwu
b28668e18d Prevent possible race condition 2020-04-01 22:40:59 -07:00
topjohnwu
5f1174de27 Introduce new boot flow to handle SAR 2SI
The existing method for handling legacy SAR is:
1. Mount /sbin tmpfs overlay
2. Dump all patched/new files into /sbin
3. Magic mount root dir and re-exec patched stock init

With Android 11 removing the /sbin folder, it is quite obvious that
things completely break down right in step 1.

To overcome this issue, we have to find a way to swap out the init
binary AFTER we re-exec stock init. This is where 2SI comes to rescue!

2SI normal boot procedure is:
1st stage -> Load sepolicy -> 2nd stage -> boot continue...

2SI Magisk boot procedure is:
MagiskInit 1st stage -> Stock 1st stage -> MagiskInit 2nd Stage ->
-> Stock init load sepolicy -> Stock 2nd stage -> boot continue...

As you can see, the trick is to make stock 1st stage init re-exec back
into MagiskInit so we can do our setup. This is possible by manipulating
some ramdisk files on initramfs based 2SI devices (old ass non SAR
devices AND super modern devices like Pixel 3/4), but not possible
on device that are stuck using legacy SAR (device that are not that
modern but not too old, like Pixel 1/2. Fucking Google logic!!)

This commit introduces a new way to intercept stock init re-exec flow:
ptrace init with forked tracer, monitor PTRACE_EVENT_EXEC, then swap
out the init file with bind mounts right before execv returns!

Going through this flow however will lose some necessary backup files,
so some bookkeeping has to be done by making the tracer hold these
files in memory and act as a daemon. 2nd stage MagiskInit will ack the
daemon to release these files at the correct time.

It just works™  ¯\_(ツ)_/¯
2020-04-01 04:39:28 -07:00
topjohnwu
543ce937ec Don't need to find system_dev 2020-03-31 22:41:25 -07:00
topjohnwu
5537b083a8 Move surequest out of legacy 2020-03-30 23:53:21 -07:00
Viktor De Pasquale
6b0854749f Added setting resetting state on install screen
It will additionally show that download is complete rather than being stuck on loading.
2020-03-30 21:58:26 -07:00
Viktor De Pasquale
09ba4772b8 Fixed using wrong argument for flashing order 2020-03-30 21:58:26 -07:00
topjohnwu
06a1d08465 Replace ellipsis with recommended characters 2020-03-30 04:50:10 -07:00
fessmm
d510ead877 fix letters 2020-03-30 04:47:10 -07:00
topjohnwu
2968a1559e Get rid of the final Java file in app 2020-03-30 04:41:54 -07:00
topjohnwu
cba26eedb5 Move several stuffs out of shared 2020-03-30 04:25:42 -07:00
topjohnwu
23e74b2781 Prevent showing empty screen in stealth mode 2020-03-30 04:03:56 -07:00
topjohnwu
ef0277d10e Properly set themes for dialogs in stub 2020-03-30 04:03:33 -07:00
topjohnwu
a623a5b7cc Set proper component name in FlashFragment 2020-03-29 23:11:09 -07:00
Davy Defaud
be8479fdba French translation update 2020-03-29 06:13:28 -07:00
Taras
e97e6d467c Update Ukrainian strings 2020-03-29 06:12:56 -07:00
JoanVC100
75ec890d46 module_permission_declined string 2020-03-29 06:12:27 -07:00
Vladimír Kubala
871a9c29c8 Update strings.xml 2020-03-29 06:12:10 -07:00
dark-basic
a4f903d947 Update strings.xml
Add new line.
2020-03-29 06:11:55 -07:00
Viktor De Pasquale
1920a52829 Added progressbar indicating content loading on modules screen 2020-03-29 06:10:19 -07:00
Viktor De Pasquale
6e14a727b1 Fixed modules screen not offering reboot when local modules change 2020-03-29 06:10:19 -07:00
Viktor De Pasquale
ea855837df Fixed melting UI on pre A10 devices 2020-03-29 06:10:19 -07:00
Chris Renshaw
d05ed0e59c Manager: remove v from download names
- now that Canaries are only commit hashes for the version string, the v is unnecessary/confusing, so the simplest solution is to just remove the v from the filename for all Manager-based downloads of Magisk and Manager
2020-03-28 22:53:38 -07:00
topjohnwu
a9eb443072 Ignore existing attributes in manifest
Close #2595
2020-03-28 21:42:31 -07:00
topjohnwu
d382b00efd Accept all paths in FileProvider
Get rid of file_paths.xml
2020-03-28 01:27:41 -07:00
topjohnwu
ef9d077c7f Update build.py 2020-03-27 23:23:26 -07:00
topjohnwu
e4b20abf8e Update gradle files 2020-03-27 22:42:02 -07:00
John Wu
e9f0a10175 Update stable release badges 2020-03-27 21:43:52 -07:00
topjohnwu
c3968a26cf Remove dynamic loading code
All these code are moved into a private repo. The stub is actually
just a stub now; mixing dynamic load and stub is confusing.
2020-03-27 01:25:05 -07:00
topjohnwu
9371515ecc Disable animations in superuser fragment 2020-03-27 00:53:38 -07:00
topjohnwu
a83e055b19 Fix strings 2020-03-27 00:53:13 -07:00
Viktor De Pasquale
6907651756 Updated flash screen so it's a fragment
The FlashActivity has been removed and all of it's functionality has been transferred to the FlashFragment.
The FlashFragment needs to be however launched in a different way than the activity using the MainActivity's stub and so seemingly massive changes had to be made.

Notably the RemoteFileService didn't seem to be calling Service.startForeground(), which has been crashing the application due to the system requirements, so that's been fixed.
2020-03-26 03:42:52 -07:00
Viktor De Pasquale
fc2d0246e6 Added requesting navigation being hidden when showing alternative view 2020-03-26 03:42:52 -07:00
Viktor De Pasquale
bb9c362bab Added back button for ModuleFragment when displaying filter 2020-03-26 03:42:52 -07:00
Viktor De Pasquale
51402e68d2 Fixed log not displaying back button when alternative view is shown 2020-03-26 03:42:52 -07:00
Viktor De Pasquale
1b8813228b Updated the app to use navigation components instead of custom solution
Welcome to mid 2018.
2020-03-26 03:42:52 -07:00
Viktor De Pasquale
922e36cfb0 Updated the width of bottom navigation 2020-03-26 03:42:52 -07:00
Viktor De Pasquale
edff094626 Added log as primary fragment 2020-03-26 03:42:52 -07:00
Chris Renshaw
aa72a080b0 core: clean up /data/adb/magisk.img, etc. as well
- now that magisk.img -> /data/adb/modules migration is no longer taking place make sure all magisk.img locations get cleaned up
2020-03-26 03:39:49 -07:00
John Wu
2a93d1c652 Update shields.io URL for caching 2020-03-25 09:08:10 -07:00
topjohnwu
6b2f23712c Add live download counts 2020-03-25 04:00:21 -07:00
topjohnwu
375ab93ee3 Update logo.png 2020-03-23 05:12:30 -07:00
topjohnwu
d5962e9d71 Update README.MD 2020-03-23 04:45:06 -07:00
topjohnwu
ffaa264bd3 Update documentation 2020-03-23 04:24:20 -07:00
topjohnwu
ba7cb47383 Make version reporting consistent 2020-03-23 01:17:13 -07:00
topjohnwu
48d417f9af Add symlink for backwards compatibility
The native code has to run with an old verison of Magisk Manager,
add this back so things will work properly.
2020-03-22 21:00:40 -07:00
Heimen Stoffels
df4db6bf6b Added Dutch translation for stub 2020-03-22 13:45:26 -07:00
Heimen Stoffels
b8ef491bc7 Updated Dutch translation 2020-03-22 13:45:26 -07:00
kam821
ea1ebb8d00 Polish translation - fix missing string
Add previously deleted string, due incorrent (duplicated) variable name.
Described in: 31142180cb
2020-03-22 13:45:02 -07:00
osm0sis
91b6d2852a scripts: add nand/mtd support to uninstaller 2020-03-22 13:43:13 -07:00
Zackptg5
d7cd1b37f8 add missing flags 2020-03-22 13:41:55 -07:00
topjohnwu
160ff7bb07 Update abort function to cleanup module installs
CLose #2373
2020-03-22 00:08:04 -07:00
topjohnwu
31142180cb Fix strings 2020-03-21 13:13:26 -07:00
Vladimír Kubala
38b0fa04a8 Small translation fix 2020-03-21 13:10:04 -07:00
fessmm
29817245ba update de strings 2020-03-21 13:09:37 -07:00
Ilya Kushnir
925fe6f152 Update RU strings 2020-03-21 04:34:45 -07:00
孟武.尼德霍格.龍
93fd574b75 更新繁體中文字串
更新繁體中文字串(適用278版)
2020-03-21 04:34:09 -07:00
kam821
0de88bcbb9 Polish translation - add missing strings, small improvements. 2020-03-21 04:33:43 -07:00
osm0sis
0b70bd2b60 scripts: make remaining header/section dividers uniform
- match other recent formatting updates from topjohnwu
2020-03-21 04:32:20 -07:00
osm0sis
84ecba4629 scripts: fix addon.d again by ensuring all arguments get passed
- /proc/$$/cmdline is \0 terminated argument strings except for the last argument which has no terminus, so the last argument was being dropped by `while read` which requires input to be \n terminated
- switch to a for loop, which will use the \n delimiter but also read the last argument; all arguments are still protected by quoting
- clean up potentially breaking recovery env since $OLD_PATH no longer exists
2020-03-20 10:51:55 -07:00
topjohnwu
f7142e69b6 Fix module install in util_functions.sh 2020-03-19 03:53:15 -07:00
topjohnwu
ed7e560849 Fix ensure_bb implementation
Close #2549, close #2560
2020-03-19 03:53:15 -07:00
osm0sis
47e50e8511 scripts: add nand/mtd support to installer
- Magisk's busybox now has nanddump, flash_eraseall and nandwrite, so use these to support character devices

Closes #1526
2020-03-15 12:37:19 -07:00
topjohnwu
72f6770d61 Fix string resources 2020-03-15 00:39:56 -07:00
topjohnwu
7da35e5468 Extract full module installation logic 2020-03-15 00:23:07 -07:00
Simon Shi
7768274b2f Fix build issue 2020-03-14 11:17:51 -07:00
topjohnwu
33f006655d Update README 2020-03-13 02:12:35 -07:00
topjohnwu
612b51d48f Disable MagiskHide by default
Since SafetyNet CTS is impossible to achieve, leaving MagiskHide on
by default no longer serves a purpose.

For more details regarding the latest SafetyNet changes, please check:
https://twitter.com/topjohnwu/status/1237656703929180160
https://twitter.com/topjohnwu/status/1237830555523149824

MagiskHide's functionality will continue to exist within the Magisk
project as it is still extremely effective to hide modifications in
userspace (including SafetyNet's basicIntegrity check).

Future MagiskHide improvements _may_ come, but since the holy grail
has been taken, any form of improvement is now a very low priority.
2020-03-13 01:48:14 -07:00
topjohnwu
8101f3f67d Set proper permissions 2020-03-12 00:51:46 -07:00
GaryOderNichts
e3c8d723e3 Add linebrake notice for module.prop
This made some trouble when creating a module.prop on Windows. The file could not be read properly by magisk manager and my module folder had an \r at the end which made it unremovable through Magisk Manager.
2020-03-12 00:34:56 -07:00
Tornike Khintibidze
4579825758 Updated Georgian strings 2020-03-12 00:33:04 -07:00
Ilya Kushnir
ef91c33f55 Update RU strings 2020-03-12 00:31:55 -07:00
dark-basic
511d5993df Update Strings-es.xml 2020-03-12 00:31:34 -07:00
Viktor De Pasquale
9f4958e869 Updated safetynet success color to primary 2020-03-12 00:30:00 -07:00
Fox2Code
c07775f5e3 Add missing ro.vendor(.boot).warranty_bit props
Co-authored-by: John Wu <topjohnwu@gmail.com>
2020-03-12 00:28:43 -07:00
topjohnwu
e261579e72 Use standalone mode in boot scripts 2020-03-11 00:11:15 -07:00
topjohnwu
cf54cad3ce deleteprop -> delprop 2020-03-09 02:05:24 -07:00
topjohnwu
a0998009c1 Small native code reorganization 2020-03-09 01:50:30 -07:00
topjohnwu
d6fdbfe9b7 Utilize standalone mode for emulator.sh 2020-03-08 23:27:06 -07:00
Vladimír Kubala
07228279a3 Update Slovak translation 2020-03-08 23:26:08 -07:00
JoanVC100
6877ef790f Add strings 2020-03-08 23:25:25 -07:00
cristisilaghi
a3809648dd Update Romanian 2020-03-08 23:25:05 -07:00
YU-YEN HSU
df15606b00 prop compare fix 2020-03-08 22:58:37 -07:00
YU-YEN HSU
4dc0d13688 Xiaomi cross region flash hacks 2020-03-08 22:58:37 -07:00
topjohnwu
541fa5cb1f Update dependencies 2020-03-08 22:54:14 -07:00
Alessandro Astone
ab9442d4ae Fixup mounting system on Lineage Recovery
* Lineage Recovery 17.1, like AOSP Q recovery, has '/' as a shared
   mount point, causing `mount --move` to fail.
   If it fails, directly mount system to /system_root via
   /dev/block/ symlinks, like AnyKernel and OpenGapps

Co-authored-by: John Wu <topjohnwu@gmail.com>
2020-03-08 22:38:47 -07:00
osm0sis
f5c099e9a7 scripts: fix addon.d after merge to trampoline
- pass addon.d arguments through trampoline or nothing will happen
- exit immediately after handing over from trampoline
- better grep for recovery OUTFD which should work in all cases
- output to logcat when booted and no binaries are found
- use /postinstall/tmp path to call functions from addon.d-v2 in progress
- remove unnecessary check for $MAGISKBIN since we're already executing from within it
- make sure we're not in $TMPDIR again before we delete it
- use $MAGISKBIN wherever possible in case it ever needs to be changed
2020-03-08 22:33:19 -07:00
Shaka Huang
9582379e1b Fix error patching boot.img
Signed-off-by: Shaka Huang <shakalaca@gmail.com>
2020-03-08 22:27:18 -07:00
topjohnwu
db9a4b31f9 Update scripts to use BusyBox standalone mode 2020-03-08 22:25:06 -07:00
Viktor De Pasquale
409cb06ea0 Fixed layout not reacting to nested scroll on su screen 2020-03-04 23:51:06 -08:00
Viktor De Pasquale
88d917b662 Added permission check for installing/downloading modules 2020-03-04 23:50:50 -08:00
topjohnwu
faf077b494 Min ver is 19.0, don't need legacy commands 2020-02-29 15:41:15 -08:00
topjohnwu
ee1f45aa91 Add new commandline option to get tmpfs root 2020-02-29 15:33:11 -08:00
topjohnwu
915fd3020b Small string resource reorganization
Close #2454
2020-02-29 14:49:06 -08:00
micheleberrettis1
642788abec Typo fix.
Fixed two typos in the Italian translation.
2020-02-29 02:09:08 -08:00
Vladimír Kubala
3cd11dd9a0 Update Slovak translation 2020-02-29 02:08:52 -08:00
kam821
bf2c5ce368 Updated Polish translation
Synced with latest strings file, some minor fixes.
2020-02-29 02:08:36 -08:00
cristisilaghi
65c510a211 Update Romanian strings 2020-02-29 02:08:23 -08:00
topjohnwu
6fbc38d764 Add more notice/messages 2020-02-29 02:04:31 -08:00
topjohnwu
200bf993d8 Show unsupported message when running low versions 2020-02-28 21:09:52 -08:00
topjohnwu
38af82e152 Update AS 2020-02-28 17:49:19 -08:00
topjohnwu
fc05f377fb Update env fix handling logic 2020-02-28 17:44:03 -08:00
topjohnwu
5c0e86383c Add test button toggle in code 2020-02-28 11:53:25 -08:00
topjohnwu
64f5ff5475 Use global A/B detection 2020-02-28 10:30:53 -08:00
topjohnwu
758777111a Improve application startup 2020-02-27 12:54:42 -08:00
topjohnwu
b90e0430f8 Don't do layered cards 2020-02-27 01:43:00 -08:00
topjohnwu
0ce7da1bf6 Upgrade AGP 2020-02-25 15:14:24 -08:00
Viktor De Pasquale
e6464c5c7f Fixed module filter list not respecting single column layout 2020-02-25 15:03:31 -08:00
topjohnwu
c6b3f06b95 Cleanup stuffs 2020-02-22 01:28:33 -08:00
topjohnwu
581419b6a3 Update dependencies 2020-02-21 23:40:20 -08:00
topjohnwu
696ab677be New pre-init magic mount implementation 2020-02-21 00:49:58 -08:00
topjohnwu
0d229dac3b Support Android 11 SELinux paths
This is NOT proper Android 11 support
2020-02-21 00:49:33 -08:00
topjohnwu
3b8ea599f0 Fix switch_root implementation 2020-02-20 21:08:59 -08:00
topjohnwu
3e70a61e33 Fix strings 2020-02-18 16:14:11 -08:00
dark-basic
76f35d02b7 Update strings-es 2020-02-18 16:00:40 -08:00
Ilya Kushnir
356b417a04 Update RU strings 2020-02-18 16:00:23 -08:00
Tornike Khintibidze
56147a80b5 Updated Translations 2020-02-18 16:00:09 -08:00
dark-basic
91728991d7 Update Strings-es STUB version
Little change.

----
For translators of other languages, simply modify a sentence. There is nothing new to translate in this sector.
2020-02-18 15:59:38 -08:00
JoanVC100
0f7e59d288 Update + Fixes ca-strings 2020-02-18 15:59:01 -08:00
Gozzwip
f33028c645 some changes 2020-02-18 15:58:47 -08:00
Davy Defaud
f9149ad433 French translation 2020-02-18 15:58:17 -08:00
topjohnwu
0d7474cc88 Fix all locale issues 2020-02-18 14:02:08 -08:00
topjohnwu
1e7e06d1cc Proper canary version detection 2020-02-17 22:05:32 -08:00
topjohnwu
8453282fa6 Improve flash console screen 2020-02-16 19:04:26 -08:00
topjohnwu
40f971d18a Add entrypoint for testing
Should do it with proper unit test, but duh
2020-02-15 21:57:03 -08:00
topjohnwu
ce7cb1eeae Remove device section 2020-02-12 13:26:10 -08:00
Viktor De Pasquale
d2701616da Fixed bottom navigation colors so it doesn't blend together with the misused layout underneath 2020-02-12 10:48:24 -08:00
Fox2Code
10eb159e1b Disable Grant Button for 1 seconds after popup display 2020-02-11 21:45:10 -08:00
topjohnwu
36897ceb19 Add slight stroke to navigation card 2020-02-11 21:32:44 -08:00
topjohnwu
9a8274130b Manually set referenced resource ID for barriers 2020-02-11 20:54:23 -08:00
topjohnwu
c8d050c3e3 Fix strings resources 2020-02-11 20:05:17 -08:00
孟武.尼德霍格.龍
a46cd63c9d 更新繁體中文字串
更新繁體中文字串到 740559e3bc 基準
2020-02-11 20:00:53 -08:00
zrq8
e9e6eaf079 Update Simplified Chinese Translation 2020-02-11 20:00:40 -08:00
Mevlüt TOPÇU
cb5897af93 Update Turkish translation
Hi,

Merge please.

Thanks
2020-02-11 20:00:31 -08:00
Vladimír Kubala
d701d6eb82 Update Slovak translation 2020-02-11 19:59:52 -08:00
Ilya Kushnir
470ebb54e2 Update RU strings 2020-02-11 19:58:51 -08:00
dark-basic
632cab398e ReFormat Strings 2020-02-11 19:58:38 -08:00
Taras
189c4cc9d8 Update UK strings 2020-02-11 19:58:23 -08:00
topjohnwu
70d5e2dee8 Remove board info from home screen 2020-02-11 19:57:33 -08:00
topjohnwu
c586106e51 Remove confusing scrambled "Manager" text 2020-02-11 19:55:21 -08:00
topjohnwu
ffa85a616a Update home screen layout (yet again) 2020-02-11 19:46:29 -08:00
Viktor De Pasquale
e5ea3e4a43 Fixed button text color on flash screen 2020-02-11 10:42:17 -08:00
Viktor De Pasquale
0492e63862 Added unified switches to install screen 2020-02-11 10:42:05 -08:00
Viktor De Pasquale
9952387356 Updated layouts to fit new widget that should visually represent a switch
The switch is not actually a switch, but a representation of internal state, the layouts continue to accept touch events as beforehand.
2020-02-11 10:42:05 -08:00
Viktor De Pasquale
d7653e6e42 Cleaned up unused resources 2020-02-11 10:42:05 -08:00
Viktor De Pasquale
e9fc40d285 Removed grid scale gestures and reverted back to * simple list as a default
* disgusting (:
2020-02-11 10:42:05 -08:00
topjohnwu
740559e3bc Fix int detection in scripts 2020-02-10 16:45:44 -08:00
topjohnwu
9471577b3b Properly detect advanced device info 2020-02-10 16:33:58 -08:00
topjohnwu
e85d5e54e2 Update root shell preparation 2020-02-10 16:31:41 -08:00
topjohnwu
5fb071d80b Merge app scripts 2020-02-10 03:36:28 -08:00
Fox2Code
022151fefd Prevent fake clicks on SuRequest 2020-02-10 02:08:53 -08:00
topjohnwu
3b8d2fe8b7 Add ramdisk detection 2020-02-10 01:56:34 -08:00
topjohnwu
d51d549a28 Refactor string resources 2020-02-10 01:43:28 -08:00
topjohnwu
b5ac24f239 Fix strings 2020-02-09 21:11:27 -08:00
dark-basic
3ca99005f8 Update strings.xml
New lines added.
2020-02-09 21:06:47 -08:00
Ilya Kushnir
0b9f2921d2 Update RU strings 2020-02-09 21:06:34 -08:00
kam821
389501ad0c Polish translation - Added missing strings 2020-02-09 21:06:20 -08:00
Hen Ry
082e4eb05c Update strings.xml
Fix
2020-02-09 21:06:06 -08:00
Oliver Cervera
47f885a566 Update Italian translation 2020-02-09 21:05:52 -08:00
Taras
bc964b8588 Update Ukrainian (UK) translation 2020-02-09 21:05:36 -08:00
zrq8
b57b3313e4 Update Simplified Chinese Translation 2020-02-09 21:05:21 -08:00
JoanVC100
f185cefa11 Missing string 2020-02-09 21:05:09 -08:00
cristisilaghi
9d256e02d7 Add missing string for Romanian 2020-02-09 21:04:58 -08:00
Vladimír Kubala
086c64c0be Update Slovak translation 2020-02-09 21:04:43 -08:00
Tornike Khintibidze
798fe57025 Update Georgian translation 2020-02-09 21:04:26 -08:00
Eun Gang Ku
a03f744648 Update strings.xml
Add new strings
2020-02-09 21:03:57 -08:00
topjohnwu
64f35744c4 Reorganize home screen layout 2020-02-09 17:03:05 -08:00
Viktor De Pasquale
b512528148 Updated toolbar layout to match the new aesthetic 2020-02-09 03:20:38 -08:00
Viktor De Pasquale
fdfa037dca Added very slight hint of the card being selectable 2020-02-09 03:20:28 -08:00
Viktor De Pasquale
db4ef1443d Removed unnecessary code 2020-02-09 03:20:14 -08:00
Viktor De Pasquale
810468c279 Added offline states for magisk and manager sections 2020-02-09 03:20:14 -08:00
Viktor De Pasquale
8146d0830d Fixed wrong horizontal bias 2020-02-09 03:20:14 -08:00
Viktor De Pasquale
7e946b040c Updated uninstall button to match aesthetic 2020-02-09 03:20:14 -08:00
Viktor De Pasquale
97d24a7d4d Removed single-use reboot menu
This addition will be used in modules as soon as the homepage gets merged
2020-02-09 03:20:14 -08:00
Viktor De Pasquale
f8bea66313 Fixed menu inflating unnecessarily on every click 2020-02-09 03:20:14 -08:00
Viktor De Pasquale
dd9129017f Added a condition to hide reboot button when no root is available 2020-02-09 03:20:14 -08:00
Viktor De Pasquale
cbe3602cb7 Fixed views hiding the view below with them 2020-02-09 03:20:14 -08:00
Viktor De Pasquale
1d831d65f3 Added overflow menu for reboot 2020-02-09 03:20:14 -08:00
Viktor De Pasquale
c35d020731 Added uninstall button to magisk details 2020-02-09 03:20:14 -08:00
Viktor De Pasquale
c18db555a4 Updated sections' title colors 2020-02-09 03:20:14 -08:00
Viktor De Pasquale
373092af16 Updated homepage layout
The updated layout has extended features such as reboot (not implemented yet), more details with not text ellipsis and easy extendability with further parameters, detail or whatever
More improvements to homescreen to come in upcoming commits.
2020-02-09 03:20:14 -08:00
topjohnwu
1a2e157cda 4000th commit! 🎉
Just for celebration, empty commit
2020-02-09 03:08:39 -08:00
topjohnwu
b3bc1a3907 Merge addon.d scripts 2020-02-09 03:07:49 -08:00
topjohnwu
4dd8d75cc0 Update scripts 2020-02-08 03:26:39 -08:00
topjohnwu
e5f50bb7e0 Update busybox 2020-02-07 21:57:26 -08:00
osm0sis
45d5b4bea6 scripts: recovery addon.d-v2 and env fixes
- recovery addon.d-v2 requires /system and /system_root stay mounted
- find OUTFD from recovery update_engine for addon.d-v2 output
- fix finding OUTFD on addon.d failure with toybox ps
- simplify heredoc creation
- update to longer apex BOOTCLASSPATH
- save and restore any mountpoint symlinks encountered

Closes #2284
2020-02-07 21:56:28 -08:00
zrq8
ed58cf953a Add missing string resources 2020-02-03 13:25:29 +08:00
cristisilaghi
ec26bc5ab7 Corrections for Romanian 2020-02-03 13:25:11 +08:00
topjohnwu
84e4bd3d41 Move readlinkat fix into xwrap 2020-02-03 13:24:02 +08:00
Shaka Huang
0ecfb63cd6 Fix crash during boot in x86 platform
readlinkat() may return random value instead of the number of bytes placed in buf and crashing the system in two ways:
1. segmentation fault (buf[-7633350] = ‘\0’)
2. wrong link of watchdogd, resulting dog timeout

Confirmed working in ZenFone 2 x86 series, may fix #2247 and #2356

Signed-off-by: Shaka Huang <shakalaca@gmail.com>
2020-02-03 13:02:30 +08:00
topjohnwu
ebdd6ec40c Fallback to getprop to get SDK_INT
Close #2274, close #2279
2020-02-03 12:58:59 +08:00
kam821
0586760347 Polish translation - Minor corrections/improvements 2020-02-02 16:06:36 +08:00
Tornike Khintibidze
d535f244ad Corrected translation 2020-02-02 16:06:12 +08:00
Mevlüt TOPÇU
613d46824d Update
Hi

Update Turkish language translations

Merge please

Thanks
2020-02-02 16:05:35 +08:00
Wagg13
041355f182 Final Language Update PT-BR 2020-02-02 16:04:54 +08:00
Viktor De Pasquale
6977dc082f Fixed texts being incorrect if injected from context 2020-02-02 16:01:50 +08:00
Viktor De Pasquale
d3dffe8165 Updated legacy theme to match error color instead of having separate secondary color 2020-02-02 16:00:15 +08:00
Viktor De Pasquale
6812f9d202 Updated su request dialog to match overall app theme 2020-02-02 16:00:04 +08:00
Viktor De Pasquale
555e7cc907 Fixed dialog not being centered
Close #2369
2020-02-02 15:59:44 +08:00
topjohnwu
6180558068 Add support for genfscon sepolicy rules
Close #2367
2020-02-02 01:16:42 +08:00
Shaka Huang
cf589f8c64 Fix error loading libsqlite.so
Vendors are always adding “extra libraries” in /vendor/lib* for their own sake, in this case AS*S loaded with customized `libicuuc.so` for Zenf*ne 5z and led to the failure of dynamic loading libsqlite.so:

<quote>
db: dlopen failed: cannot locate symbol "UCNV_FROM_U_CALLBACK_ESCAPE_63" referenced by "/apex/com.android.runtime/lib64/libandroidicu.so"...
</quote>

Signed-off-by: Shaka Huang <shakalaca@gmail.com>

* Minor optimizations

Co-authored-by: John Wu <topjohnwu@gmail.com>
2020-02-01 14:36:34 +08:00
topjohnwu
e864919c0b Jellybean supports modules 2020-02-01 14:22:31 +08:00
topjohnwu
c72d83b637 Update docs 2020-02-01 01:42:40 +08:00
osm0sis
f2d2f28e23 scripts: fixes for Tegra partition naming + bootsigner on Android 10
- adjust mount scripts to support SOS, APP and CAC Tegra partition naming (vendor is still vendor, oddly)
- -Xnodex2oat is removed on Android 10 in AOSP (despite it still erroneously showing in dalvikvm --help); older devices will still run safely without it
- Android 10 dynamically linked binaries need APEX mounts and variables so add this to recovery_actions/cleanup (thanks @Zackptg5)
- clean up known systemless root leftovers because we're helpful
2020-02-01 01:09:12 +08:00
osm0sis
a7435dad6d magiskboot: fix lzop detection 2020-02-01 01:09:12 +08:00
osm0sis
793f0b605c init: fix Tegra "APP" /system partition mounting
- thanks rootfan in https://github.com/topjohnwu/Magisk/issues/2063#issuecomment-573232567

Closes #2243
2020-02-01 01:09:12 +08:00
topjohnwu
5b56ca7ffc Use MAX_FDT_GROWTH instead of hardcode value 2020-02-01 00:58:33 +08:00
topjohnwu
5c988510b3 Preserve fdt paddings
Some Motorola devices (Qualcomm kernel with CONFIG_MMI_DEVICE_DTBS
configuration enabled) need 1k of padding to the DTBs to allow for
environment variables to be runtime added by the bootloader.
Those extra paddings will be removed during the process of dtb patch,
devices won’t be able to boot-up and return to fastboot mode immediately
after flashed the flawed boot.img.

Credits to @shakalaca, close #2273
2020-02-01 00:48:21 +08:00
topjohnwu
290624844b Reorganize dtb code 2020-02-01 00:15:52 +08:00
topjohnwu
497efc9f5e Make scrambled text prettier 2020-01-31 04:48:02 +08:00
topjohnwu
19d76b635c Fix de strings 2020-01-31 04:29:06 +08:00
Davy Defaud
4875def31c Complete and improve French translation
- fix some typos
- translate the two last strings
- fit French orthotypographic rules (use true apostrophes and hyphens…)
2020-01-31 04:24:10 +08:00
Vladimír Kubala
155c0e3609 Update Slovak language 2020-01-31 04:22:34 +08:00
kam821
00ea15dc19 Update and fix Polish language
- Added missing strings.
- Fixed some incorrect translations
- Improved grammar.

Many lines rewritten to keep original (EN) meaning as much as possible.
2020-01-31 04:20:06 +08:00
Hen Ry
f04c4cb78a Update de strings 2020-01-31 04:19:40 +08:00
topjohnwu
6e4777692e Change recreate logic
Although this new behavior is a little more annoying, at least
it properly kills the activity and restarts it with updated configs.
2020-01-31 04:17:17 +08:00
Viktor De Pasquale
4638fdf2d7 Fixed dialog content being squished unnecessarily
...by updating constraint layout
...lol
2020-01-31 04:13:08 +08:00
wiki de pasquale
0783d385d5 Removed security note
Note, and the backdrop, has been removed, since users which have chosen device that doesn't receive security updates in, at least 2 months, are getting triggered by their own choices (:
2020-01-31 04:12:49 +08:00
Viktor De Pasquale
cf918e7df8 Updated text "variant" transparency 2020-01-31 04:09:45 +08:00
Viktor De Pasquale
1ba9faf35b Added legacy theme (Fraxure)
Theme is, in color, identical to legacy colors.
2020-01-31 04:09:45 +08:00
Viktor De Pasquale
6e48294f2a Removed unnecessary files and merged styles 2020-01-31 04:09:45 +08:00
topjohnwu
dea607b148 Small SignAPK improvements 2020-01-31 04:07:12 +08:00
topjohnwu
e938e717b0 Refactor PatchAPK code 2020-01-31 03:37:39 +08:00
topjohnwu
2eed09ef1b Upgrade AS 2020-01-30 22:04:31 +08:00
topjohnwu
8a6b3644be Strip only debug and verbose logging 2020-01-29 22:16:53 +08:00
topjohnwu
1d89fe503b Fix manager hiding
Workaround bizarre optimization bugs
2020-01-29 20:55:20 +08:00
topjohnwu
788db036fd Don't use Zopfli 2020-01-29 17:38:36 +08:00
topjohnwu
c38c473e11 Fix compile error 2020-01-29 17:38:36 +08:00
Eun Gang Ku
aef1f8f701 Update strings.xml 2020-01-29 13:35:01 +08:00
Eun Gang Ku
83f9767254 Update strings.xml
Add small corrections.
2020-01-29 13:34:49 +08:00
JoanVC100
3e0352eee6 Update strings + Corrections 2020-01-29 01:51:17 +08:00
Rom
28faff6425 Fix French translation 2020-01-29 01:51:07 +08:00
topjohnwu
d0112f989c Cleanup classes 2020-01-29 01:49:59 +08:00
Viktor De Pasquale
9c4c310f46 Fixed messages on modules screen replicating indefinitely 2020-01-29 01:12:48 +08:00
Viktor De Pasquale
7bf7bfb9c6 Updated Flash / SuRequest activities with app themes
CompatActivity/Fragment logic has been moved to respective BaseUI. Some deprecated and unused styles have been removed in favor or newer themes.
2020-01-29 01:12:21 +08:00
Mevlüt TOPÇU
fbe776db0b Update Turkish language
Hi

Update Turkish language

Merge please

Thanks
2020-01-27 21:54:44 +08:00
topjohnwu
1e2de1bb14 Preserve everything in package 'a'
Close #2301
2020-01-27 21:25:41 +08:00
topjohnwu
e395c9442f Upstream system_properties 2020-01-27 19:50:03 +08:00
Tornike Khintibidze
30286f0ea5 Fixed translations 2020-01-27 18:24:26 +08:00
cristisilaghi
60ee742855 Update RO strings 2020-01-27 18:23:52 +08:00
Rom
a913ede48f French translation update 2020-01-27 18:23:43 +08:00
Wagg13
9592583783 Language update PT-BR
Minor improvements and some corrections that better match words in the language.

I adapt it as I test it.
2020-01-27 18:23:34 +08:00
zrq8
ad49d3ad26 Update Simplified Chinese Translation 2020-01-27 18:23:17 +08:00
dark-basic
21ee73c2a3 Translation Correction 2020-01-27 18:23:02 +08:00
Viktor De Pasquale
f5d0cc9f32 Updated helper lists so they are lazily populated 2020-01-27 18:22:29 +08:00
vvb2060
b90c65370e Fix build on Windows 2020-01-26 12:27:07 +08:00
John Wu
88920e0546 Merge pull request #2320 from Displax/RU
Update RU strings
2020-01-26 12:19:58 +08:00
dark-basic
d27773de03 Add missing string-es resources 2020-01-26 12:19:43 +08:00
Tornike Khintibidze
8abdaeb044 Translated newest string 2020-01-26 12:19:22 +08:00
孟武.尼德霍格.龍
9682d2f84a 更新繁體中文字串
新增一處字串
2020-01-26 12:18:57 +08:00
Ilya Kushnir
a86b9e81e9 Update RU strings 2020-01-25 17:30:59 +02:00
topjohnwu
a8bb7c68a3 Add missing string resources 2020-01-25 23:03:39 +08:00
topjohnwu
bdad29adab Fix pt-rBR strings 2020-01-25 23:03:14 +08:00
Tornike Khintibidze
fadcfe5f7a Added new Georgian (ქართული) translation 2020-01-25 22:56:40 +08:00
Wagg13
fbd83b5ff3 Update language Portuguese (pt-br) 2020-01-25 22:56:22 +08:00
Ilya Kushnir
c351174fa4 Preliminary Russian translation refactoring 2020-01-25 22:56:00 +08:00
zrq8
cc4f99fe28 Update Simplified Chinese Translation 2020-01-25 22:55:41 +08:00
dark-basic
b2a9b88fe5 Updated Spanish translations 2020-01-25 22:55:27 +08:00
孟武.尼德霍格.龍
da06e0ec76 更新繁體中文字串
-針對 v7.5.2(270)以後的 Manager 進行翻譯的增補
-祝您新年快樂
2020-01-25 22:55:01 +08:00
Viktor De Pasquale
851ee81486 Updated removing of "empty list" messages
Before this commit, the loader removed messages _after_ it updated the
 list. Coincidentally the list updating mechanism is asynchronous to
 some extent and so slower devices might've had the message removed
 after changes have been dispatched which confused the recyclerview and
 caused the crash.
Now, the loader is stripped of the responsibility update the list
 holding helper messages. The responsibility is for the user itself to
 notify listeners and then clear the helper list. This should hopefully
 delay the removal to the point where choreographer had enough time to
 traverse through the hierarchy.

Stupid recycler view / layout managers. Literally unnecessary crash.
2020-01-25 22:31:19 +08:00
topjohnwu
0dc9f5c324 Rename some string IDs 2020-01-23 02:34:18 +08:00
topjohnwu
36513c2301 Don't direct reference R id 2020-01-23 02:23:42 +08:00
topjohnwu
3a10597aed Remove unused resources 2020-01-23 02:14:00 +08:00
topjohnwu
2291be5d26 Merge string resources 2020-01-23 01:40:36 +08:00
topjohnwu
345c3ef15e Remove old settings page 2020-01-23 00:57:32 +08:00
topjohnwu
c1dad11cb3 Merge branch 'md2' 2020-01-22 14:55:06 +08:00
jjhitel
12b219e7b2 Update Korean translation 2020-01-22 14:50:33 +08:00
孟武.尼德霍格.龍
f8b48cf18d 更新繁體中文字串
更新繁體中文字串的顯示
2020-01-22 14:50:16 +08:00
topjohnwu
12a9792c7d Remove old install dialog 2020-01-22 14:49:46 +08:00
topjohnwu
ba55e2bc32 Backup proper magiskinit in A-only 2SI 2020-01-22 05:12:04 +08:00
Viktor De Pasquale
c5e5b70e08 Added safe mode notice to modules 2020-01-21 22:01:55 +01:00
Viktor De Pasquale
327b186240 Fixed theme mode title 2020-01-21 21:50:13 +01:00
Viktor De Pasquale
5c1417e276 Updated home layout 2020-01-21 21:47:57 +01:00
topjohnwu
0a2c99f1dc Use __LP64__ to detect 64 bit 2020-01-22 01:20:14 +08:00
topjohnwu
836bfbdd02 Wrapper is no longer needed 2020-01-22 01:17:30 +08:00
topjohnwu
b13a35057a Support building standalone resetprop 2020-01-21 00:48:52 +08:00
topjohnwu
c3e77b1ec1 Add BusyBox SELinux support
Close #1523
2020-01-20 20:48:05 +08:00
topjohnwu
fb60bea659 Update external/selinux 2020-01-20 18:36:16 +08:00
topjohnwu
b2ddba4cbf Proper repo fetching behavior 2020-01-19 03:15:51 +08:00
topjohnwu
053251d566 Merge ViewModel Koin modules 2020-01-18 04:07:15 +08:00
topjohnwu
cf161a5dd9 Show url dialog only if necessary 2020-01-18 03:59:02 +08:00
topjohnwu
e4bcdbd0c4 Make settings page more reasonable 2020-01-18 03:06:33 +08:00
topjohnwu
cae43b26f4 Improve settings item code 2020-01-18 01:34:46 +08:00
topjohnwu
b95cf9b9a3 Show detail descriptions in settings 2020-01-17 17:02:40 +08:00
topjohnwu
e6f443cb24 More backwards compatibility 2020-01-17 00:40:16 +08:00
topjohnwu
087ccd69c9 Cleanup resources 2020-01-16 00:07:40 +08:00
topjohnwu
7532477a2f Make release builds work 2020-01-15 00:51:27 +08:00
topjohnwu
433ae89e53 Make things run on API 17 2020-01-15 00:05:44 +08:00
topjohnwu
de853a2651 Fix crash when manually refresh repo db 2020-01-14 22:03:29 +08:00
topjohnwu
47c3045980 Log full stack trace for unhandled exception 2020-01-14 20:14:20 +08:00
topjohnwu
dd50c19ba3 Consolidate stubs 2020-01-13 23:10:17 +08:00
topjohnwu
707d7b3342 Separate core components 2020-01-13 22:01:46 +08:00
topjohnwu
ba1a2fbce4 Remove deprecate APIs 2020-01-13 04:00:35 +08:00
topjohnwu
84f1e78660 Consolidate base viewmodel implementation 2020-01-13 03:56:03 +08:00
topjohnwu
3490ba0a56 Redesign is now the new norm 2020-01-13 00:43:09 +08:00
topjohnwu
1449486958 Replace old design with redesign (p3) 2020-01-12 21:52:32 +08:00
topjohnwu
9094cf7ce3 Replace old design with redesign (p2) 2020-01-12 16:07:30 +08:00
topjohnwu
df0a5b59f8 Replace old design with redesign (p1) 2020-01-12 15:00:49 +08:00
topjohnwu
0827044caf Make Room incremental 2020-01-12 04:54:20 +08:00
topjohnwu
342ae7c8cd Update AS 2020-01-12 04:51:52 +08:00
topjohnwu
fc690b9f02 Update dependencies 2020-01-12 03:50:12 +08:00
topjohnwu
22c9d836e0 Merge branch 'master' into feature/redesign 2020-01-12 03:02:03 +08:00
topjohnwu
984997e73b Update paths
Close #2244
2020-01-11 12:37:08 +08:00
topjohnwu
b39f407596 Load libsqlite dynamically 2020-01-11 03:20:59 +08:00
osm0sis
615ad0cc5a core: remove remaining legacy workarounds/leftovers 2020-01-10 23:31:43 +08:00
topjohnwu
0b41cd8564 Fix sk strings 2020-01-10 01:55:23 +08:00
topjohnwu
7db523071d Update changelog 2020-01-10 01:41:39 +08:00
JoanVC100
974ee58b9c New string added 2020-01-10 01:37:32 +08:00
Kamil Kras
1e88f2c382 Updated polish translation
Added translation for:
unsupport_magisk_msg
settings_su_biometric_title
settings_su_biometric_summary
no_biometric
2020-01-10 01:37:20 +08:00
Vladimír Kubala
0bdcfcaaf5 Update Slovak translation 2020-01-10 01:37:05 +08:00
Albert I
5f9c78d04f app: l10n: Update Indonesian translations
Signed-off-by: Albert I <kras@raphielgang.org>
2020-01-10 01:36:55 +08:00
Viktor De Pasquale
afa178fdec Fixed the checkbox being wrongly recognized as clickable 2020-01-09 18:06:30 +01:00
topjohnwu
3a0e3c98f7 Minor adjustments to prevent crashes 2020-01-09 23:42:27 +08:00
topjohnwu
fafa92d44b Simplify rootfs persist mount 2020-01-08 22:42:54 +08:00
Viktor De Pasquale
fcedd06e72 Added grid column count settings
*Cough, cough* Use pinch to zoom gesture instead, not a fan of this.
2020-01-06 20:00:33 +01:00
Viktor De Pasquale
6a2acbe929 Added tiny hint that magisk can be uninstalled using the icon 2020-01-06 19:31:06 +01:00
Viktor De Pasquale
4cfff40475 Updated special chars to their full-width variants
Close #2219
2020-01-06 19:06:19 +01:00
Viktor De Pasquale
904948dc7d Added empty states for all remaining screens
Fixed some issues in the process as the MergeObservableList doesn't support additions or deletions, duh...
2020-01-06 19:03:36 +01:00
Viktor De Pasquale
7342509b2e Added resetting state of the recyclerview scroll listener
In some edge-cases the listener can still think that the content is loading.
2020-01-06 18:37:45 +01:00
Viktor De Pasquale
ed837ba26f Updated the logic that refreshes the modules
Also added empty states for installed and made updatable modules visible all the time to avoid unnecessary transitions
2020-01-06 17:46:08 +01:00
Viktor De Pasquale
13262fdb18 Fixed the dialog not being actually closed on click outside
Misplaced methods ftw
2020-01-06 16:41:44 +01:00
Viktor De Pasquale
baf18a8762 Fixed cardview resizing itself idiotically in constraint layout 2020-01-06 16:33:55 +01:00
Viktor De Pasquale
c0b56b927f Updated the material library back to alpha03 version
The aforementioned fragment has fixed issue with layouts being oversized on API21 (maybe a bit lower and higher as well, did not test) which was notable on homepage.
Unfortunately it deprecated most of the logic behind hiding of the top action view. Since it inherited and overridden the functionality from HideBottomViewOnScrollBehavior it no longer called the old methods and so the whole class was rendered _useless_. Fortunately we didn't need the whole backing implementation so the parent class was changed to the bare minimum. Hopefully this incident will not repeat.

Thanks goes to material team for introducing breaking changes in feature update.
2020-01-06 16:24:27 +01:00
topjohnwu
242e64d72f Make write return something sane 2020-01-06 17:09:09 +08:00
topjohnwu
2262af728e Eliminate undefined behavior 2020-01-06 05:31:38 +08:00
topjohnwu
ea9947081f Use widgets from AndroidX 2020-01-05 16:27:28 +08:00
topjohnwu
e04f943980 Backwards support back to API 21
Things still crash pre-21
2020-01-05 16:05:22 +08:00
topjohnwu
b38e940088 Fix font files 2020-01-05 14:24:36 +08:00
Viktor De Pasquale
bc0bb92f7a Updated indication of whether the module is enabled 2020-01-04 16:46:13 +01:00
Viktor De Pasquale
8737be2623 Updated policy (apps) layout to be more compact
Added pinch in to increase list span count / out to decrease
  The setting will be remembered across the whole app (every list that uses Staggered Grid)
Updated indication of whether the policy has root access enabled permitted or not
  Displays crossed out app logo if not permitted
2020-01-04 16:07:53 +01:00
Viktor De Pasquale
eb929160b3 Reverted updating material library 2020-01-04 13:30:33 +01:00
Viktor De Pasquale
b8b0f257db Updated navigation behavior to be consistent and easily manageable 2020-01-04 13:30:21 +01:00
Viktor De Pasquale
67b5f39df2 Updated device info view to be expanded by default 2020-01-04 11:52:29 +01:00
topjohnwu
7e9b3f1a60 Merge components 2020-01-04 04:48:13 +08:00
topjohnwu
bce777d7c6 Set stub version in stub 2020-01-03 02:19:10 +08:00
topjohnwu
465aaeff82 Remove dexter dependency 2020-01-03 02:06:53 +08:00
topjohnwu
40c64d50d5 Merge branch 'master' into feature/redesign 2020-01-02 14:52:50 +08:00
topjohnwu
15bd2da824 Update magiskboot docs 2020-01-02 13:46:59 +08:00
topjohnwu
bd438ca288 Update docs 2020-01-02 13:45:08 +08:00
Viktor De Pasquale
89b1fa341b Added assigning repo to installed modules to show readme
Close #1089
2019-12-13 15:19:56 +01:00
Viktor De Pasquale
3bda7cb26b Removed exclusive usage of 29 level API 2019-12-13 14:41:49 +01:00
Viktor De Pasquale
85a350b6c8 Fixed minor RTL issues 2019-12-06 20:53:22 +01:00
Viktor De Pasquale
eae4eff92f Fixed custom dialog behaving oddly while displaying scrollable content 2019-12-06 20:34:25 +01:00
Viktor De Pasquale
848be8f806 Fixed rtl for reveal animations 2019-12-06 18:06:59 +01:00
Viktor De Pasquale
c79b79b37e Cleaned up extensions and utilities in redesign 2019-12-06 16:28:41 +01:00
Viktor De Pasquale
8a03c366b8 Updated settings item location >in code< 2019-12-06 15:39:12 +01:00
Viktor De Pasquale
37677f389c Finished rebranding core-only mode to safe mode
In compliance with #2131

Mentioned features are not contained within this commit
2019-12-06 15:00:55 +01:00
Viktor De Pasquale
2692234b8c Updated hide items to follow suit with the rest of scrollable content 2019-12-02 18:53:55 +01:00
Viktor De Pasquale
bfb5d7e5ac Reverted tinting headlines 2019-12-02 18:51:22 +01:00
Viktor De Pasquale
8c818e707f Updated homepage to be less aggressive 2019-12-02 18:39:22 +01:00
Viktor De Pasquale
3efea47ca8 Updated settings to level functionality with the legacy 2019-12-02 18:35:48 +01:00
Viktor De Pasquale
89da45f9ac Fixed state not being propagated correctly on startup 2019-11-29 21:04:47 +01:00
Viktor De Pasquale
34a0a00e3c Updated constraint layout to fix layout issue in dialog 2019-11-29 21:04:32 +01:00
Viktor De Pasquale
dec1094a59 Added "input" settings item, that opens custom input dialog
Updated order of some items in settings
2019-11-29 20:22:24 +01:00
Viktor De Pasquale
02e323133d Updated selector "selection" design 2019-11-29 14:49:41 +01:00
Viktor De Pasquale
cb96b536a2 Added fair amount of settings implemented from the UI side
Updated dialog to create recycler as it behaves better than regular listview
2019-11-28 21:53:31 +01:00
Viktor De Pasquale
627b40799c Fixed checkbox (switch) colors 2019-11-28 18:58:04 +01:00
Viktor De Pasquale
73c4b21285 Added (partially) settings screen
Most importantly added design and functionality backing for these items
2019-11-27 19:47:20 +01:00
Viktor De Pasquale
78d7c45be3 Merge remote-tracking branch 'john/master' into feature/redesign 2019-11-26 14:34:38 +01:00
Viktor De Pasquale
ac5ecf222e Fixed style for announcement card 2019-11-25 17:54:04 +01:00
Viktor De Pasquale
a20594ed48 Added emphasis on support section 2019-11-25 17:25:49 +01:00
Viktor De Pasquale
cb59cc92a3 Updated action cards to be more colorful 2019-11-25 16:58:57 +01:00
Viktor De Pasquale
cc7e47bbb6 Added themes
All files (that used styles) were refactored to use styles directly so themes can only actually adjust colors
 - Elaborate themes would be super hard to maintain and would certainly break over time
2019-11-22 19:29:53 +01:00
Viktor De Pasquale
42606162b2 Fixed text color in logs not changing with theme 2019-11-21 18:21:08 +01:00
Viktor De Pasquale
e82bc1b7bc Fixed issues after merge 2019-11-21 18:07:13 +01:00
Viktor De Pasquale
4f0e1c6c61 Merge remote-tracking branch 'john/master' into feature/redesign
# Conflicts:
#	app/build.gradle
#	app/src/main/java/com/topjohnwu/magisk/Hacks.kt
#	app/src/main/java/com/topjohnwu/magisk/data/database/RepoDatabase.kt
#	app/src/main/java/com/topjohnwu/magisk/data/repository/LogRepository.kt
#	app/src/main/java/com/topjohnwu/magisk/di/DatabaseModule.kt
#	app/src/main/java/com/topjohnwu/magisk/extensions/RxJava.kt
#	app/src/main/java/com/topjohnwu/magisk/extensions/XAndroid.kt
#	app/src/main/java/com/topjohnwu/magisk/extensions/XJava.kt
#	app/src/main/java/com/topjohnwu/magisk/model/download/RemoteFileService.kt
#	app/src/main/java/com/topjohnwu/magisk/model/entity/recycler/LogRvItem.kt
#	app/src/main/java/com/topjohnwu/magisk/model/events/ViewEvents.kt
#	app/src/main/java/com/topjohnwu/magisk/ui/SplashActivity.kt
#	app/src/main/res/xml/app_settings.xml
2019-11-21 17:46:59 +01:00
Viktor De Pasquale
550f6aff7e Updated showing / hiding filters 2019-11-21 17:35:29 +01:00
Viktor De Pasquale
67c50d7504 Added magisk log screen 2019-11-21 17:31:37 +01:00
Viktor De Pasquale
94f0c61619 Added ignoring emulators for env_fix dialog 2019-11-21 14:30:12 +01:00
Viktor De Pasquale
8a86b30fd1 Fixed core UI elements not behaving properly after recreating 2019-11-21 14:24:14 +01:00
Viktor De Pasquale
6379108a75 Added new log screen 2019-11-20 22:42:44 +01:00
Viktor De Pasquale
fbeaad077f Updated themes so they are separated from styles 2019-11-19 18:53:50 +01:00
Viktor De Pasquale
8918113a31 Added colored borderless ripple effect 2019-11-19 17:56:32 +01:00
Viktor De Pasquale
c5385b5b4c Added custom markdown window for redesign 2019-11-19 17:41:24 +01:00
Viktor De Pasquale
35475e1d25 Added option to include simple view to MagiskDialog 2019-11-19 17:41:09 +01:00
Viktor De Pasquale
fb2c292f35 Updated dialog base to handle large content 2019-11-19 17:40:42 +01:00
Viktor De Pasquale
afc3fb10c7 Updated icon padding for all buttons 2019-11-19 17:16:07 +01:00
Viktor De Pasquale
0a239c2fef Added QOL improvements
- fast scroll in module/filter list
- auto closing keyboard on scroll
2019-11-19 17:15:44 +01:00
Viktor De Pasquale
f5342a09d3 Added back safe mode notice 2019-11-19 16:07:19 +01:00
Viktor De Pasquale
f72de687c5 Fixed module lists not being strictly typed in builder 2019-11-19 15:56:10 +01:00
Viktor De Pasquale
833269fd0a Updated install from storage button to be more expressive 2019-11-18 17:49:03 +01:00
Viktor De Pasquale
332c1a6c59 Removed overcomplicated updates loading
The mechanism was replaced by loading updated directly by id to the initial list. There are two factors why yesterday-me was dumb:

1) By asynchronously loading update state, you have no control over it - hence no search
2) It's incredibly wasteful; running that hardcore search on every query? Not cool
...and from UX stand-point having updates inlined right under installed modules is by far better than nitpicking it from the list or in the search
2019-11-18 17:21:23 +01:00
Viktor De Pasquale
0f1f43057e Updated handling queries so first query is always instant 2019-11-18 16:29:27 +01:00
Viktor De Pasquale
784a7a7f24 Added back press closing filters in hide and module screens 2019-11-17 13:53:41 +01:00
Viktor De Pasquale
8e34baa59f Fixed bottom padding being too small 2019-11-17 13:48:52 +01:00
Viktor De Pasquale
2926772bba Added checks for updatable state on remote repos 2019-11-17 13:46:56 +01:00
Viktor De Pasquale
a7f4496db7 Added info dialog for repos 2019-11-16 20:51:56 +01:00
Viktor De Pasquale
f972f02fff Fixed clipping version string so it better shows the update's impact
Incremental canary bugfix will be:
b4b2c4 > f5d2e6
Version bump will be always:
20.2 > 20.3 (regardless of canary/beta/stable)
2019-11-16 20:21:09 +01:00
Viktor De Pasquale
1c77e26c05 Added sorting order to modules 2019-11-16 20:07:59 +01:00
Viktor De Pasquale
59c5363933 Updated colors and styles
Namely added secondary button
2019-11-16 20:07:35 +01:00
Viktor De Pasquale
b744bb0a5a Fixed loading showing in incorrect view-states 2019-11-16 19:20:44 +01:00
Viktor De Pasquale
0f140b408c Added installing external modules from storage 2019-11-16 19:16:59 +01:00
Viktor De Pasquale
711799b194 Added forced loading per user's demand
Added reselecting action (scroll up real fast)
2019-11-15 22:26:00 +01:00
Viktor De Pasquale
2105cacce3 Fixed fab background color in dark mode
Now it's recognizable from the background
2019-11-14 19:10:49 +01:00
Viktor De Pasquale
9d1d1710eb Added new search functionality to module screen 2019-11-14 18:56:03 +01:00
Viktor De Pasquale
c69dcf3e20 Added hiding keyboard when "done" button pressed 2019-11-14 15:19:48 +01:00
Viktor De Pasquale
eec5b37da1 Fixed inconsistent animations in hide 2019-11-14 15:12:53 +01:00
Viktor De Pasquale
e1bda4ee8b Added new filter for hide section
Parts of which will be reused in modules down the line
2019-11-13 18:29:30 +01:00
Viktor De Pasquale
54930024f5 Added biometric dialog instead of fingerprint one 2019-11-12 18:01:24 +01:00
Viktor De Pasquale
c5f2f63458 Fixed slow scrolling and list updates on hide screen 2019-11-12 17:23:27 +01:00
Viktor De Pasquale
b2b81a5d0f Fixed "enabled" state for download action button 2019-11-12 16:45:28 +01:00
Viktor De Pasquale
265dca3723 Removed intermediate loading item
- adding intermediate items causes recyclerview renderer to display artifacts (copies of some views)
2019-11-12 15:58:32 +01:00
Viktor De Pasquale
495e734428 Updated module sections so it looks more consistent 2019-11-11 19:36:40 +01:00
Viktor De Pasquale
82120cf47f Added processing of the download states 2019-11-11 18:01:23 +01:00
Viktor De Pasquale
027a5695f2 Added progressbars to repo items 2019-11-11 18:00:36 +01:00
Viktor De Pasquale
d6d82edff5 Fixed file service not broadcasting indeterminate states 2019-11-11 17:59:44 +01:00
Viktor De Pasquale
a12eb3fc6f Fixed incorrect indeterminate progressbar color 2019-11-11 17:59:09 +01:00
Viktor De Pasquale
6c84574366 Added loading item to modules 2019-11-11 16:22:33 +01:00
Viktor De Pasquale
bc5cbe9fba Updated module item design to follow suit with the rest of the app 2019-11-08 19:19:19 +01:00
Viktor De Pasquale
f83f92d3fa Updated modules screen so it displays all the content in one recyclerview
Added "endless" scrolling support
 - this is done in order to display everything very swiftly and load as user needs it
 - for the most part we'll download only ~10 items and load the rest as scroll progresses, this accomplishes the illusion that whole list is being populated
Added sections and updated repo view
2019-11-08 19:04:30 +01:00
Viktor De Pasquale
19fd4dd89c Partially reverted removing "moveToState" 2019-11-06 18:52:58 +01:00
Viktor De Pasquale
f941f5c0b0 Fixed observer not being called immediately 2019-11-06 18:37:11 +01:00
Viktor De Pasquale
c7cad7e4aa Updated modules so they are properly arranged to respective sections
Small updates to module UI
2019-11-06 17:22:26 +01:00
Viktor De Pasquale
1c8988d3f7 Updated "night" style of elevated card 2019-11-05 19:39:06 +01:00
Viktor De Pasquale
70a3dbe2b0 Added primitive implementation of modules screen 2019-11-05 19:38:02 +01:00
Viktor De Pasquale
efbb3ab25f Fixed red tint regarding system security not being spanned across the whole card 2019-11-05 16:19:41 +01:00
Viktor De Pasquale
b0e7c65504 Added icon for "all processes hidden" state 2019-11-04 17:40:13 +01:00
Viktor De Pasquale
b18b044b63 Updated filter card to be more compact 2019-11-04 17:39:51 +01:00
Viktor De Pasquale
8f5f8db717 Fixed dark mode colors 2019-11-04 17:39:17 +01:00
Viktor De Pasquale
016e28383b Added animated progressbar 2019-11-04 16:58:39 +01:00
Viktor De Pasquale
f1427e9279 Updated hide item layout 2019-11-04 16:45:54 +01:00
Viktor De Pasquale
169e9ab5ad Updated hide fragment layout and design of the filter window 2019-11-04 16:02:23 +01:00
Viktor De Pasquale
dad52724db Updated hide fragment with more robust filtering UI 2019-11-02 18:31:20 +01:00
Viktor De Pasquale
d48e9d5d72 Removed button patterns defying MD 2019-11-02 15:59:07 +01:00
Viktor De Pasquale
24e2c3a5e9 Removed unused icons 2019-11-01 21:30:29 +01:00
Viktor De Pasquale
064523ef25 Updated checkbox height 2019-11-01 21:15:02 +01:00
Viktor De Pasquale
85f293a44e Updated policy colors and internal ids 2019-11-01 21:10:00 +01:00
Viktor De Pasquale
8e412bee5f Updated radio button sizes 2019-11-01 19:15:05 +01:00
Viktor De Pasquale
7d5555f82e Added safety notice and support section description 2019-11-01 19:02:30 +01:00
Viktor De Pasquale
6720725d27 Added clarifying tooltips and captions 2019-11-01 18:12:52 +01:00
Viktor De Pasquale
fe5c65d798 Fixed use of RxBus for toggling policies 2019-11-01 17:58:42 +01:00
Viktor De Pasquale
253f3cf1ba Fixed inconsistent icon sizes 2019-11-01 17:55:25 +01:00
Viktor De Pasquale
db2e48b49f Added manager mode recognition 2019-10-31 20:58:17 +01:00
Viktor De Pasquale
5e089451af Added loaders to superuser and hide 2019-10-31 20:53:57 +01:00
Viktor De Pasquale
6aa22267f4 Updated Hide screen to be fully functioning
...although still misses search :(
2019-10-31 20:34:07 +01:00
Viktor De Pasquale
f76c020dd7 Added implementation of hide screen
Very much wip and doesn't work at all
2019-10-30 21:58:42 +01:00
Viktor De Pasquale
722fba7805 Updated bottom nav width to not spread useless spaces 2019-10-30 21:09:00 +01:00
Viktor De Pasquale
86551909fc Added safe mode notice to modules screen 2019-10-30 17:59:03 +01:00
Viktor De Pasquale
588e94c11d Updated locations of certain elements
Settings are now only on home screen as it directly relates to what user might want to do. It is highly unlikely that they would jump from any other screen to settings.
Log is no longer main destination as it's not used very widely; it's been moved to Superuser screen. This screen now encapsulates all root-related stuff.

Home screen is now strictly info-based, except install buttons, of course.
2019-10-30 17:11:42 +01:00
Viktor De Pasquale
9e66310c28 Updated fragment container 2019-10-29 16:58:46 +01:00
Viktor De Pasquale
93c422dce6 Added post-merge fixes 2019-10-29 16:52:42 +01:00
Viktor De Pasquale
7d6eebdae3 Fixed unreasonable change resulting in major breakage all around the app 2019-10-29 16:50:01 +01:00
Viktor De Pasquale
f11bb609c9 Merge remote-tracking branch 'john/master' into feature/redesign
# Conflicts:
#	app/build.gradle
#	app/src/main/java/com/topjohnwu/magisk/ClassMap.kt
#	app/src/main/java/com/topjohnwu/magisk/Info.kt
#	app/src/main/java/com/topjohnwu/magisk/extensions/XAndroid.kt
#	app/src/main/java/com/topjohnwu/magisk/ui/SplashActivity.kt
#	app/src/main/java/com/topjohnwu/magisk/ui/settings/SettingsFragment.kt
#	app/src/main/java/com/topjohnwu/magisk/utils/DataBindingAdapters.kt
2019-10-29 15:53:53 +01:00
Viktor De Pasquale
b910a92731 Fixed ui issues in unrooted state 2019-10-27 11:00:16 +01:00
Viktor De Pasquale
ee7d297ca8 Partially reverted developer section changes 2019-10-26 21:14:01 +02:00
Viktor De Pasquale
a70c0174e1 Added device info card 2019-10-26 21:03:25 +02:00
Viktor De Pasquale
da707afa3f Updated install ui to better fit app's theme 2019-10-26 17:40:29 +02:00
Viktor De Pasquale
a41597431c Added more information to magisk/manager cards 2019-10-26 17:33:27 +02:00
Viktor De Pasquale
d0b817381e Added "caching" of the safetynet response 2019-10-26 16:03:07 +02:00
Viktor De Pasquale
60a2e9b5dc Updated home info cards to be more compressed 2019-10-26 15:14:20 +02:00
Viktor De Pasquale
df3a37b0a3 Updated developer section to be horizontally scrollable instead of vertically
In order to make room for more information
2019-10-26 12:41:34 +02:00
Viktor De Pasquale
5f4718cd13 Added string resources for install screen 2019-10-25 19:17:13 +02:00
Viktor De Pasquale
3cc5cb3123 Updated the install flow
Now the binary is downloaded after user selects a method. It also shows download progress as the file's being downloaded
2019-10-25 19:13:54 +02:00
Viktor De Pasquale
8a2872afa4 Removed pre-download while installing magisk 2019-10-25 17:46:20 +02:00
Viktor De Pasquale
85941c4729 Removed lol code 2019-10-25 15:40:46 +02:00
Viktor De Pasquale
82eeefb544 Added system version to the details section for safetynet checks 2019-10-24 18:40:15 +02:00
Viktor De Pasquale
f6061ba00e Fixed bottom navigation popping up when it shouldn't 2019-10-24 18:07:36 +02:00
Viktor De Pasquale
9e3afcfe7a Added safetynet implementation 2019-10-24 18:00:51 +02:00
Viktor De Pasquale
21f2f86cb8 Added safetynet implementation
The implementation itself was moved from fragment to self contained event. The result resolution might be moved to the event as well
2019-10-23 21:17:53 +02:00
Viktor De Pasquale
04576ca828 Added install implementation 2019-10-23 19:14:39 +02:00
Viktor De Pasquale
067cb0cd9d Fixed magisk button states 2019-10-23 16:20:01 +02:00
Viktor De Pasquale
17fb8f2298 Added new magisk install flow 2019-10-22 20:46:09 +02:00
Viktor De Pasquale
fbfc4e72ca Updated appbar design to be more android-like 2019-10-22 18:09:26 +02:00
Viktor De Pasquale
d2e171eabc Added a way to listen to download service from homepage and behave accordingly 2019-10-22 17:29:45 +02:00
Viktor De Pasquale
e50094af80 Added install fragment 2019-10-22 16:29:01 +02:00
Viktor De Pasquale
93edf72993 Rationalize download failure 2019-10-22 16:26:28 +02:00
Viktor De Pasquale
a230d63cf9 Fixed having an error doesn't inform external listeners 2019-10-22 16:22:28 +02:00
Viktor De Pasquale
2bb39bee2f Updated redesign button design 2019-10-21 19:30:14 +02:00
Viktor De Pasquale
ce2ca5446a Fixed checkbox's theme implementation in dark mode 2019-10-21 19:22:56 +02:00
Viktor De Pasquale
8a014ff786 Added most of the remaining functionality for Magisk install dialog 2019-10-21 19:22:16 +02:00
Viktor De Pasquale
dc09ec7598 Added theme mode picker dialog
Redesigned settings' selector for dark mode a bit
2019-10-20 17:28:18 +02:00
Viktor De Pasquale
27fb0474d5 Added more standard night-mode system 2019-10-20 17:27:39 +02:00
Viktor De Pasquale
7f0a87742a Fixed design issues in dialogs
Dark theme not being properly implemented
Icons were not set
2019-10-20 17:26:00 +02:00
Viktor De Pasquale
47e236788c Added uninstall dialog 2019-10-20 16:47:02 +02:00
Viktor De Pasquale
236ad57608 Added showing download progress in the home ui 2019-10-20 12:40:50 +02:00
Viktor De Pasquale
6d03798314 Added internal download pseudo broadcasts 2019-10-20 12:40:26 +02:00
Viktor De Pasquale
c954a4f7bc Updated icons and texts for magisk hide and safetynet 2019-10-20 11:29:04 +02:00
Viktor De Pasquale
ba588d1097 Updated position of quick links on superuser screen 2019-10-20 11:24:45 +02:00
Viktor De Pasquale
44f7c9a545 Added animations for toolbar transitions 2019-10-20 11:14:49 +02:00
Viktor De Pasquale
b910db322b Fixed snackbar behavior in contrast with bottom navigation 2019-10-20 10:57:29 +02:00
Viktor De Pasquale
c44a942fb7 Added entry for safetynet attestation 2019-10-19 22:28:01 +02:00
Viktor De Pasquale
d713ad3499 Added "advanced" install options for installing magisk 2019-10-19 22:14:23 +02:00
Viktor De Pasquale
ddf40df649 Updated colors and elevation to be less distracting 2019-10-19 21:12:30 +02:00
Viktor De Pasquale
7c6d85221d Updated policy items design 2019-10-19 21:07:06 +02:00
Viktor De Pasquale
b66b82a6e9 Added logic to superuser screen 2019-10-19 20:51:28 +02:00
Viktor De Pasquale
c44b85ea87 Fixed paddings on su screen 2019-10-19 18:29:48 +02:00
Viktor De Pasquale
fcbf56e93a Added superuser screen implementation
* partially
2019-10-18 19:38:55 +02:00
Viktor De Pasquale
a539ffb188 Updated styles due to low accessibility 2019-10-18 18:15:22 +02:00
Viktor De Pasquale
512f533a80 Added hide fragment for future use 2019-10-18 17:04:41 +02:00
Viktor De Pasquale
96ef9cdbee Fixed insets not being implicitly asked for by the framework resulting in no coverage for other than main fragments 2019-10-18 16:39:08 +02:00
Viktor De Pasquale
28fcbbcf7b Added basic preferences to settings 2019-10-17 19:26:35 +02:00
Viktor De Pasquale
0f4326151f Added titles 2019-10-17 19:26:25 +02:00
Viktor De Pasquale
e0e27774ad Added remaining stubs for the redesign 2019-10-17 18:57:00 +02:00
Viktor De Pasquale
1223b48b2c Fixed dialog automatically dismissing itself 2019-10-17 15:52:52 +02:00
Viktor De Pasquale
d8338f0b48 Fixed event duplication 2019-10-17 15:47:11 +02:00
Viktor De Pasquale
38019f7f42 Added env check to homepage 2019-10-17 15:37:10 +02:00
Viktor De Pasquale
23978ef4d2 Updated bottom padding for home fragment 2019-10-16 19:04:37 +02:00
Viktor De Pasquale
3b4cb23112 Fixed error in navigation implementation 2019-10-16 18:53:38 +02:00
Viktor De Pasquale
974cb1167f Added post-merge fixes 2019-10-16 17:53:35 +02:00
Viktor De Pasquale
6ccbc272c6 Merge remote-tracking branch 'john/master' into feature/redesign
# Conflicts:
#	app/build.gradle
#	app/src/main/AndroidManifest.xml
#	app/src/main/java/com/topjohnwu/magisk/model/events/ViewEvents.kt
#	app/src/main/java/com/topjohnwu/magisk/model/navigation/MagiskNavigationEvent.kt
#	app/src/main/java/com/topjohnwu/magisk/ui/SplashActivity.kt
#	app/src/main/java/com/topjohnwu/magisk/ui/settings/SettingsFragment.kt
#	app/src/main/java/com/topjohnwu/magisk/view/MagiskDialog.kt
#	app/src/main/res/layout/dialog_magisk_base.xml
2019-10-16 17:33:54 +02:00
Viktor De Pasquale
0eb28c3265 Added navigation delegation to bypass default one
By making a delegate like such we protect ourselves against intrusions in views' logic
2019-10-16 17:27:11 +02:00
Viktor De Pasquale
2daa131fb2 Added layout behavior to dismiss toolbars when scrolling 2019-10-16 16:08:07 +02:00
Viktor De Pasquale
51247d36c5 Added disabled state for sections where unrooted user shouldn't have access 2019-10-16 15:47:41 +02:00
Viktor De Pasquale
37fa227fb5 Added refreshing upon connection restore 2019-10-12 16:30:18 +02:00
Viktor De Pasquale
9dd272b357 Fixed main cards collapsing on themselves 2019-10-12 16:22:12 +02:00
Viktor De Pasquale
277298feae Updated night style for elevated cards 2019-10-12 16:11:05 +02:00
Viktor De Pasquale
ff24bc0b68 Updated card design on homepage 2019-10-12 16:10:44 +02:00
Viktor De Pasquale
700c51f95c Added animated home icons 2019-10-10 22:00:39 +02:00
Viktor De Pasquale
659914afbe Updated navigation icons 2019-10-10 19:08:15 +02:00
Viktor De Pasquale
ee06aed94b Updated toolbar and bottom bar design implementation
TBD:
Both toolbars should move away from the screen when scrolling
2019-10-10 17:34:06 +02:00
Viktor De Pasquale
af1f5d5ab2 Fixed showing magisk version when not installed 2019-10-10 16:51:20 +02:00
Viktor De Pasquale
4292ddd0ae Added custom install dialogs 2019-10-08 20:29:55 +02:00
Viktor De Pasquale
4a68fd65b6 Updated UI so magisk cannot be updated unless latest manager is installed 2019-10-08 18:51:31 +02:00
Viktor De Pasquale
0e33632e79 Added package name when it differs from the default one 2019-10-08 18:30:23 +02:00
Viktor De Pasquale
a9b20dae33 Fixed showing update information 2019-10-08 18:29:58 +02:00
Viktor De Pasquale
e595937740 Added versions to home screen
To overview (when updatable)
  - It is very hard to spot a difference in versions so versions are now regarded as commit messages (after dash [-]) when applicable
      - This will result in more clear, understandable text
      - Bleeding edge (canary) user would see:

        ffed229 > ffe02ed or 19.4 > ffe02ed

        as opposed to:

        19.4-ffed229 (19404)
        19.5-ffe02ed (19501)
      - Regular beta+ user would see:

        19.4 > 19.5
To bottom of the screen
  - This change is with respect to regular user. They don't care which version they run as long as they know that "up-to-date" is a gold standard
  - It takes tons of real-estate on the screen which takes away the glance-ability from the overview.
2019-10-07 20:09:12 +02:00
Viktor De Pasquale
72eb584e65 Fixed fonts for the thousandth time 2019-10-07 19:23:57 +02:00
Viktor De Pasquale
8999a57f06 Added in-app settings shortcut from system settings 2019-10-06 12:20:05 +02:00
Viktor De Pasquale
8024089bde Added indication of whether the manager is hidden 2019-10-06 12:06:31 +02:00
Viktor De Pasquale
5e01f785ae Added handling for state with no connection 2019-10-06 12:06:09 +02:00
Viktor De Pasquale
d35d1b8860 Added more styles to be used on top of primary color 2019-10-06 11:14:11 +02:00
Viktor De Pasquale
88027f2151 Fixed fonts 2019-10-05 23:31:05 +02:00
Viktor De Pasquale
cd41e7108b Fixed fonts 2019-10-05 23:13:17 +02:00
Viktor De Pasquale
6da566faff Fixed overlay color for home item 2019-10-05 22:59:28 +02:00
Viktor De Pasquale
df7a866617 Fixed widget order
Content mustn't overlay appbar
2019-10-05 22:59:04 +02:00
Viktor De Pasquale
1cc8f13d54 Added section icons 2019-10-05 22:20:57 +02:00
Viktor De Pasquale
086ce63c6c Updated material library 2019-10-05 22:04:34 +02:00
Viktor De Pasquale
f1dcecc6cf Added link opening on homepage 2019-10-05 22:04:03 +02:00
Viktor De Pasquale
fe1ce08a6c Added self-handling event types 2019-10-05 21:53:33 +02:00
Viktor De Pasquale
1d64ddb7f5 Fixed dimensions and padding throughout the homescreen 2019-10-05 12:53:40 +02:00
Viktor De Pasquale
823b121cc7 Added support section content 2019-10-05 12:42:27 +02:00
Viktor De Pasquale
149d35c687 Updated strings 2019-10-05 11:39:33 +02:00
Viktor De Pasquale
3a18e68751 Updated arrangement of manager/magisk sections 2019-10-04 19:57:27 +02:00
Viktor De Pasquale
6afcc83955 Added logic to redesigned home
(partially)
2019-10-04 19:36:26 +02:00
Viktor De Pasquale
277d8773f2 Added automated loading to new compat-based redesign 2019-10-04 19:35:22 +02:00
Viktor De Pasquale
f161cf8b0a Removed no-root state 2019-10-04 18:56:35 +02:00
Viktor De Pasquale
dc62ae95a6 Added icons for navigation 2019-10-04 18:25:06 +02:00
Viktor De Pasquale
f4ecc315d0 Replaced temporary id names 2019-10-04 18:03:06 +02:00
Viktor De Pasquale
cb2a1e57fe Added text switchers for descriptions 2019-10-04 17:56:30 +02:00
Viktor De Pasquale
1396faf433 Added animated icon for magisk (and its uninstall) 2019-10-04 17:12:28 +02:00
Viktor De Pasquale
dc8d2ae683 Added basic navigation 2019-10-03 19:38:57 +02:00
Viktor De Pasquale
191c7c50b6 Added night theme colors 2019-10-03 19:17:11 +02:00
Viktor De Pasquale
c6725b0518 Added icons for magisk/manager 2019-10-03 19:08:35 +02:00
Viktor De Pasquale
4820a6e01c Updated toolbar to look more android-like 2019-10-03 18:41:04 +02:00
Viktor De Pasquale
57a9b5bc0c Added home screen 2019-10-03 17:31:45 +02:00
Viktor De Pasquale
8c224da5d5 Added compat layer for activities and fragments
This change is made so logic is not placed within the "old" base substrate. Changes made in the redesign could potentially affect the already working part which we obviously do not want.
2019-10-03 16:42:47 +02:00
Viktor De Pasquale
14e49f3c80 Added redesign base
... also basic switching to redesign was added, haha
2019-10-02 19:42:38 +02:00
Viktor De Pasquale
cc8f1adca3 Added more styles regarding homescreen 2019-10-02 18:15:16 +02:00
Viktor De Pasquale
122e2f7a8e Updated styles and simplified dimension usage 2019-09-30 19:37:57 +02:00
Viktor De Pasquale
b4e1585e2b Added custom font 2019-09-30 19:36:51 +02:00
Viktor De Pasquale
a5830599c4 Added initial load of styles and attributes
Required for creating basic screens
2019-09-24 20:29:35 +02:00
788 changed files with 31086 additions and 34029 deletions

5
.gitattributes vendored
View File

@@ -17,3 +17,8 @@ tools/** binary
*.apk binary
*.png binary
*.jpg binary
*.ttf binary
# Help GitHub detect languages
native/jni/external/** linguist-vendored
native/jni/systemproperties/** linguist-language=C++

3
.gitmodules vendored
View File

@@ -22,6 +22,9 @@
[submodule "mincrypt"]
path = native/jni/external/mincrypt
url = https://github.com/topjohnwu/mincrypt.git
[submodule "pcre"]
path = native/jni/external/pcre
url = https://android.googlesource.com/platform/external/pcre
[submodule "termux-elf-cleaner"]
path = tools/termux-elf-cleaner
url = https://github.com/termux/termux-elf-cleaner.git

View File

@@ -1,34 +1,70 @@
# Magisk
![](docs/images/logo.png)
[Downloads](https://github.com/topjohnwu/Magisk/releases) \| [Documentation](https://topjohnwu.github.io/Magisk/) \| [XDA Thread](https://forum.xda-developers.com/apps/magisk/official-magisk-v7-universal-systemless-t3473445)
![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)
![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
Magisk is a suite of open source tools for customizing Android, supporting devices higher than Android 4.2 (API 17). It covers the fundamental parts for Android customization: root, boot scripts, SELinux patches, AVB2.0 / dm-verity / forceencrypt removals etc.
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.
Furthermore, Magisk provides a **Systemless Interface** to alter the system (or vendor) arbitrarily while the actual partitions stay completely intact. With its systemless nature along with several other hacks, Magisk can hide modifications from nearly any system integrity verifications used in banking apps, corporation monitoring apps, game cheat detections, and most importantly [Google's SafetyNet API](https://developer.android.com/training/safetynet/index.html).
Here are some feature highlights:
- **MagiskSU**: Provide root access to your device
- **Magisk Modules**: Modify read-only partitions by installing modules
- **MagiskHide**: Hide Magisk from root detections / system integrity checks
## Downloads
[![](https://img.shields.io/badge/Magisk%20Manager-v7.5.1-green)](https://github.com/topjohnwu/Magisk/releases/download/manager-v7.5.1/MagiskManager-v7.5.1.apk)
[![](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-v20.4-blue)](https://github.com/topjohnwu/Magisk/releases/tag/v20.4)
[![](https://img.shields.io/badge/Magisk%20Beta-v20.4-blue)](https://github.com/topjohnwu/Magisk/releases/tag/v20.4)
## Useful Links
- [Installation Instruction](https://topjohnwu.github.io/Magisk/install.html)
- [Frequently Asked Questions](https://topjohnwu.github.io/Magisk/faq.html)
- [Full Official Docs](https://topjohnwu.github.io/Magisk/)
- [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
**Make sure to install the latest [Canary Build](https://forum.xda-developers.com/apps/magisk/dev-magisk-canary-channel-bleeding-edge-t3839337) before reporting any bugs!** **DO NOT** report bugs that are already fixed upstream. Follow the instructions in the [Canary Channel XDA Thread](https://forum.xda-developers.com/apps/magisk/dev-magisk-canary-channel-bleeding-edge-t3839337), and report a bug either by [opening an issue on GitHub](https://github.com/topjohnwu/Magisk/issues) or directly in the thread.
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.
## Building Environment Requirements
**Only bug reports from Canary builds will be accepted.**
- Python 3: run `build.py` script
- Java Development Kit (JDK) 8: Compile Magisk Manager and sign zips
- Latest Android SDK: set `ANDROID_HOME` environment variable to the path to Android SDK
- Android NDK: Install NDK along with SDK (`$ANDROID_HOME/ndk-bundle`), or optionally specify a custom path `ANDROID_NDK_HOME`
- (Windows Only) Python package Colorama: Install with `pip install colorama`, used for ANSI color codes
For installation issues, upload both boot image and install logs.<br>
For Magisk issues, upload boot logcat or dmesg.<br>
For Magisk Manager crashes, record and upload the logcat when the crash occurs.
## Building Notes and Instructions
## Building and Development
- Clone sources with submodules: `git clone --recurse-submodules https://github.com/topjohnwu/Magisk.git`
- Building is supported on macOS, Linux, and Windows. Official releases are built and tested with [FrankeNDK](https://github.com/topjohnwu/FrankeNDK); point `ANDROID_NDK_HOME` to FrankeNDK if you want to use it for compiling.
- Set configurations in `config.prop`. A sample file `config.prop.sample` is provided as an example.
- Run `build.py` with argument `-h` to see the built-in help message. The `-h` option also works for each supported actions, e.g. `./build.py binary -h`
- By default, `build.py` build binaries and Magisk Manager in debug mode. If you want to build Magisk Manager in release mode (via the `-r, --release` flag), you need a Java Keystore file `release-key.jks` (only `JKS` format is supported) to sign APKs and zips. For more information, check out [Google's Official Documentation](https://developer.android.com/studio/publish/app-signing.html#signing-manually).
- Magisk builds on any OS Android Studio supports. Install Android Studio and do the initial setups.
- Clone sources: `git clone --recurse-submodules https://github.com/topjohnwu/Magisk.git`
- Install Python 3.6+ \
(Windows only: select **'Add Python to PATH'** in installer, and run `pip install colorama` after install)
- Configure to use the JDK bundled in Android Studio:
- macOS: `export JAVA_HOME="/Applications/Android Studio.app/Contents/jre/jdk/Contents/Home"`
- Linux: `export PATH="/path/to/androidstudio/jre/bin:$PATH"`
- Windows: Add `C:\Path\To\Android Studio\jre\bin` to environment variable `PATH`
- Set environment variable `ANDROID_HOME` to the Android SDK folder (can be found in Android Studio settings)
- Run `./build.py ndk` to let the script download and install NDK for you
- Set configurations in `config.prop`. A sample `config.prop.sample` is provided.
- To start building, run `build.py` to see your options. \
For each action, use `-h` to access help (e.g. `./build.py all -h`)
- To start development, open the project 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.
- `build.py` builds in debug mode by default. If you want release builds (with `-r, --release`), you need a Java Keystore to sign APKs and zips. For more information, check [Google's Documentation](https://developer.android.com/studio/publish/app-signing.html#generate-key).
## Translations
## Translation Contributions
Default string resources for Magisk Manager and its stub APK are located here:
@@ -37,27 +73,6 @@ Default string resources for Magisk Manager and its stub APK are located here:
Translate each and place them in the respective locations (`[module]/src/main/res/values-[lang]/strings.xml`).
## Signature Verification
Official release zips and APKs are signed with my personal private key. You can verify the key certificate to make sure the binaries you downloaded are not manipulated in anyway.
``` bash
# Use the keytool command from JDK to print certificates
keytool -printcert -jarfile <APK or Magisk zip>
# The output should contain the following signature
Owner: CN=John Wu, L=Taipei, C=TW
Issuer: CN=John Wu, L=Taipei, C=TW
Serial number: 50514879
Valid from: Sun Aug 14 13:23:44 EDT 2016 until: Tue Jul 21 13:23:44 EDT 2116
Certificate fingerprints:
MD5: CE:DA:68:C1:E1:74:71:0A:EF:58:89:7D:AE:6E:AB:4F
SHA1: DC:0F:2B:61:CB:D7:E9:D3:DB:BE:06:0B:2B:87:0D:46:BB:06:02:11
SHA256: B4:CB:83:B4:DA:D9:9F:99:7D:BE:87:2F:01:3A:A1:6C:14:EE:C4:1D:16:70:21:F3:71:F7:E1:33:0F:27:3E:E6
Signature algorithm name: SHA256withRSA
Version: 3
```
## License
Magisk, including all git submodules are free software:

View File

@@ -1,135 +0,0 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
kapt {
correctErrorTypes = true
useBuildCache = true
mapDiagnosticLocations = true
javacOptions {
option("-Xmaxerrs", 1000)
}
}
android {
defaultConfig {
applicationId 'com.topjohnwu.magisk'
vectorDrawables.useSupportLibrary = true
multiDexEnabled true
versionName props['appVersion']
versionCode props['appVersionCode'] as Integer
}
buildTypes {
release {
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'),
'proguard-rules.pro', 'proguard-kotlin.pro'
}
}
dataBinding {
enabled = true
}
packagingOptions {
exclude '/META-INF/**'
exclude '/androidsupportmultidexversion.txt'
exclude '/org/bouncycastle/**'
exclude '/kotlin/**'
exclude '/kotlinx/**'
exclude '/okhttp3/**'
}
kotlinOptions {
jvmTarget = '1.8'
}
}
androidExtensions {
experimental = true
}
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation project(':shared')
implementation project(':signing')
implementation 'com.github.topjohnwu:jtar:1.0.0'
implementation 'com.jakewharton.timber:timber:4.7.1'
implementation 'com.ncapdevi:frag-nav:3.2.0'
implementation 'com.github.pwittchen:reactivenetwork-rx2:3.0.6'
implementation 'io.reactivex.rxjava2:rxjava:2.2.13'
implementation 'io.reactivex.rxjava2:rxkotlin:2.4.0'
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
implementation "org.jetbrains.kotlin:kotlin-stdlib:${vKotlin}"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${vKotlin}"
def vBAdapt = '3.1.1'
def bindingAdapter = 'me.tatarka.bindingcollectionadapter2:bindingcollectionadapter'
implementation "${bindingAdapter}:${vBAdapt}"
implementation "${bindingAdapter}-recyclerview:${vBAdapt}"
def vMarkwon = '4.2.0'
implementation "io.noties.markwon:core:${vMarkwon}"
implementation "io.noties.markwon:html:${vMarkwon}"
implementation "io.noties.markwon:image:${vMarkwon}"
implementation 'com.caverock:androidsvg:1.4'
def vLibsu = '2.5.1'
implementation "com.github.topjohnwu.libsu:core:${vLibsu}"
implementation "com.github.topjohnwu.libsu:io:${vLibsu}"
def vKoin = '2.0.1'
implementation "org.koin:koin-core:${vKoin}"
implementation "org.koin:koin-android:${vKoin}"
implementation "org.koin:koin-androidx-viewmodel:${vKoin}"
def vRetrofit = '2.6.2'
implementation "com.squareup.retrofit2:retrofit:${vRetrofit}"
implementation "com.squareup.retrofit2:converter-moshi:${vRetrofit}"
implementation "com.squareup.retrofit2:converter-scalars:${vRetrofit}"
implementation "com.squareup.retrofit2:adapter-rxjava2:${vRetrofit}"
def vOkHttp = '3.12.6'
implementation "com.squareup.okhttp3:okhttp:${vOkHttp}"
implementation "com.squareup.okhttp3:logging-interceptor:${vOkHttp}"
def vMoshi = '1.9.2'
implementation "com.squareup.moshi:moshi:${vMoshi}"
def vKotshi = '2.0.2'
implementation "se.ansman.kotshi:api:${vKotshi}"
kapt "se.ansman.kotshi:compiler:${vKotshi}"
modules {
module('androidx.room:room-runtime') {
replacedBy('com.github.topjohnwu:room-runtime')
}
}
def vRoom = '2.2.2'
implementation "com.github.topjohnwu:room-runtime:${vRoom}"
implementation "androidx.room:room-rxjava2:${vRoom}"
kapt "androidx.room:room-compiler:${vRoom}"
def vNav = '2.1.0'
implementation "androidx.navigation:navigation-fragment-ktx:${vNav}"
implementation "androidx.navigation:navigation-ui-ktx:${vNav}"
implementation 'androidx.biometric:biometric:1.0.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0-alpha03'
implementation 'androidx.preference:preference:1.1.0'
implementation 'androidx.recyclerview:recyclerview:1.1.0'
implementation 'androidx.fragment:fragment-ktx:1.2.0-rc03'
implementation 'androidx.cardview:cardview:1.0.0'
implementation 'androidx.work:work-runtime:2.2.0'
implementation 'androidx.transition:transition:1.3.0-rc02'
implementation 'androidx.multidex:multidex:2.0.1'
implementation 'androidx.core:core-ktx:1.1.0'
implementation 'com.google.android.material:material:1.2.0-alpha02'
}

147
app/build.gradle.kts Normal file
View File

@@ -0,0 +1,147 @@
plugins {
id("com.android.application")
kotlin("android")
kotlin("android.extensions")
kotlin("kapt")
id("androidx.navigation.safeargs.kotlin")
}
kapt {
correctErrorTypes = true
useBuildCache = true
mapDiagnosticLocations = true
javacOptions {
option("-Xmaxerrs", 1000)
}
}
android {
defaultConfig {
applicationId = "com.topjohnwu.magisk"
vectorDrawables.useSupportLibrary = true
multiDexEnabled = true
versionName = Config["appVersion"]
versionCode = Config["appVersionCode"]?.toInt()
buildConfigField("int", "LATEST_MAGISK", Config["versionCode"] ?: "Integer.MAX_VALUE")
javaCompileOptions.annotationProcessorOptions.arguments(
mapOf("room.incremental" to "true")
)
}
buildTypes {
getByName("release") {
isMinifyEnabled = true
isShrinkResources = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
buildFeatures {
dataBinding = true
}
dependenciesInfo {
includeInApk = false
includeInBundle = false
}
packagingOptions {
exclude("/META-INF/**")
exclude("/androidsupportmultidexversion.txt")
exclude("/org/bouncycastle/**")
exclude("/kotlin/**")
exclude("/kotlinx/**")
exclude("/okhttp3/**")
}
kotlinOptions {
jvmTarget = "1.8"
}
}
androidExtensions {
isExperimental = true
}
val copyUtils = tasks.register("copyUtils", Copy::class) {
from(rootProject.file("scripts/util_functions.sh"))
into("src/main/res/raw")
}
tasks["preBuild"]?.dependsOn(copyUtils)
dependencies {
implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar"))))
implementation(kotlin("stdlib"))
implementation(project(":app:shared"))
implementation(project(":app:signing"))
implementation("com.github.topjohnwu:jtar:1.0.0")
implementation("com.github.topjohnwu:indeterminate-checkbox:1.0.7")
implementation("com.jakewharton.timber:timber:4.7.1")
val vBAdapt = "4.0.0"
val bindingAdapter = "me.tatarka.bindingcollectionadapter2:bindingcollectionadapter"
implementation("${bindingAdapter}:${vBAdapt}")
implementation("${bindingAdapter}-recyclerview:${vBAdapt}")
val vMarkwon = "4.6.0"
implementation("io.noties.markwon:core:${vMarkwon}")
implementation("io.noties.markwon:html:${vMarkwon}")
implementation("io.noties.markwon:image:${vMarkwon}")
implementation("com.caverock:androidsvg:1.4")
val vLibsu = "3.0.2"
implementation("com.github.topjohnwu.libsu:core:${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"
implementation("com.squareup.retrofit2:retrofit:${vRetrofit}")
implementation("com.squareup.retrofit2:converter-moshi:${vRetrofit}")
implementation("com.squareup.retrofit2:converter-scalars:${vRetrofit}")
val vOkHttp = "3.12.12"
implementation("com.squareup.okhttp3:okhttp") {
version {
strictly(vOkHttp)
}
}
implementation("com.squareup.okhttp3:logging-interceptor:${vOkHttp}")
implementation("com.squareup.okhttp3:okhttp-dnsoverhttps:${vOkHttp}")
val vMoshi = "1.10.0"
implementation("com.squareup.moshi:moshi:${vMoshi}")
kapt("com.squareup.moshi:moshi-kotlin-codegen:${vMoshi}")
val vRoom = "2.2.5"
implementation("androidx.room:room-runtime:${vRoom}")
implementation("androidx.room:room-ktx:${vRoom}")
kapt("androidx.room:room-compiler:${vRoom}")
val vNav: String by rootProject.extra
implementation("androidx.navigation:navigation-fragment-ktx:${vNav}")
implementation("androidx.navigation:navigation-ui-ktx:${vNav}")
implementation("androidx.biometric:biometric:1.0.1")
implementation("androidx.constraintlayout:constraintlayout:2.0.1")
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
implementation("androidx.browser:browser:1.2.0")
implementation("androidx.preference:preference:1.1.1")
implementation("androidx.recyclerview:recyclerview:1.1.0")
implementation("androidx.fragment:fragment-ktx:1.2.5")
implementation("androidx.work:work-runtime-ktx:2.4.0")
implementation("androidx.transition:transition:1.3.1")
implementation("androidx.multidex:multidex:2.0.1")
implementation("androidx.core:core-ktx:1.3.1")
implementation("androidx.localbroadcastmanager:localbroadcastmanager:1.0.0")
implementation("com.google.android.material:material:1.2.1")
}

View File

@@ -1,20 +0,0 @@
## So every class is case insensitive to avoid some bizare problems
-dontusemixedcaseclassnames
## If reflection issues come up uncomment this, that should temporarily fix it
#-keep class kotlin.** { *; }
#-keep class kotlin.Metadata { *; }
#-keepclassmembers class kotlin.Metadata {
# public <methods>;
#}
## Never warn about Kotlin, it should work as-is
-dontwarn kotlin.**
## Removes runtime null checks - doesn't really matter if it crashes on kotlin or java NPE
-assumenosideeffects class kotlin.jvm.internal.Intrinsics {
static void checkParameterIsNotNull(java.lang.Object, java.lang.String);
}
## Useless option for dex
-dontpreverify

View File

@@ -16,33 +16,39 @@
# public *;
#}
# Kotlin
-assumenosideeffects class kotlin.jvm.internal.Intrinsics {
public static void checkExpressionValueIsNotNull(...);
public static void checkNotNullExpressionValue(...);
public static void checkReturnedValueIsNotNull(...);
public static void checkFieldIsNotNull(...);
public static void checkParameterIsNotNull(...);
}
# Stubs
-keep class a.* { *; }
# Snet
-keepclassmembers class com.topjohnwu.magisk.utils.SafetyNetHelper { *; }
-keep,allowobfuscation interface com.topjohnwu.magisk.utils.SafetyNetHelper$Callback
-keepclassmembers class * implements com.topjohnwu.magisk.utils.SafetyNetHelper$Callback {
void onResponse(int);
-keepclassmembers class com.topjohnwu.magisk.ui.safetynet.SafetyNetHelper { *; }
-keep,allowobfuscation interface com.topjohnwu.magisk.ui.safetynet.SafetyNetHelper$Callback
-keepclassmembers class * implements com.topjohnwu.magisk.ui.safetynet.SafetyNetHelper$Callback {
void onResponse(org.json.JSONObject);
}
# Keep all fragment constructors
-keepclassmembers class * extends androidx.fragment.app.Fragment {
public <init>(...);
# Fragments
# TODO: Remove when AGP 4.1 release
# https://issuetracker.google.com/issues/142601969
-keep,allowobfuscation class * extends androidx.fragment.app.Fragment
-keepnames class androidx.navigation.fragment.NavHostFragment
# Strip Timber verbose and debug logging
-assumenosideeffects class timber.log.Timber.Tree {
public void v(**);
public void d(**);
}
# DelegateWorker
-keep,allowobfuscation class * extends com.topjohnwu.magisk.base.DelegateWorker
# BootSigner
-keep class a.a { *; }
# Workaround R8 bug
-keep,allowobfuscation class com.topjohnwu.magisk.model.receiver.GeneralReceiver
-keepclassmembers class a.e { *; }
# Strip logging
-assumenosideeffects class timber.log.Timber.Tree { *; }
# Excessive obfuscation
-repackageclasses 'a'
-repackageclasses
-allowaccessmodification
# QOL

View File

@@ -1,5 +0,0 @@
com.topjohnwu.magisk:color/xxxxxxxx = 0x7f010000
com.topjohnwu.magisk:drawable/xxxxxxxx = 0x7f020000
com.topjohnwu.magisk:string/xxxxxxxx = 0x7f030000
com.topjohnwu.magisk:style/xxxxxxxx = 0x7f040000
com.topjohnwu.magisk:xml/xxxxxxxx = 0x7f050000

View File

@@ -0,0 +1,14 @@
plugins {
id("com.android.library")
}
android {
defaultConfig {
vectorDrawables.useSupportLibrary = true
consumerProguardFiles("proguard-rules.pro")
}
}
dependencies {
implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar"))))
}

View File

@@ -4,21 +4,15 @@
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application
android:icon="@drawable/ic_launcher"
android:label="Magisk Manager"
android:installLocation="internalOnly"
android:usesCleartextTraffic="true"
android:supportsRtl="true"
android:theme="@android:style/Theme.Translucent.NoTitleBar"
tools:ignore="UnusedAttribute">
<activity
android:name="a.r"
android:theme="@android:style/Theme.Translucent.NoTitleBar"
android:process=":remote" />
<provider
android:name="a.p"
android:authorities="${applicationId}.provider"

View File

@@ -11,8 +11,6 @@ import static android.os.Build.VERSION.SDK_INT;
public class DynAPK {
private static final int STUB_VERSION = 6;
// Indices of the object array
private static final int STUB_VERSION_ENTRY = 0;
private static final int CLASS_COMPONENT_MAP = 1;
@@ -64,7 +62,7 @@ public class DynAPK {
}
public static class Data {
public int version = STUB_VERSION;
public int version;
public Map<String, String> classToComponent;
}
}

View File

@@ -4,7 +4,6 @@ import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.Context;
import android.content.pm.ProviderInfo;
import android.content.res.XmlResourceParser;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.net.Uri;
@@ -16,19 +15,12 @@ import android.provider.OpenableColumns;
import android.text.TextUtils;
import android.webkit.MimeTypeMap;
import com.topjohnwu.shared.R;
import org.xmlpull.v1.XmlPullParserException;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
import static org.xmlpull.v1.XmlPullParser.START_TAG;
/**
* Modified from androidx.core.content.FileProvider
*/
@@ -36,17 +28,6 @@ public class FileProvider extends ContentProvider {
private static final String[] COLUMNS = {
OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE };
private static final String TAG_ROOT_PATH = "root-path";
private static final String TAG_FILES_PATH = "files-path";
private static final String TAG_CACHE_PATH = "cache-path";
private static final String TAG_EXTERNAL = "external-path";
private static final String TAG_EXTERNAL_FILES = "external-files-path";
private static final String TAG_EXTERNAL_CACHE = "external-cache-path";
private static final String TAG_EXTERNAL_MEDIA = "external-media-path";
private static final String ATTR_NAME = "name";
private static final String ATTR_PATH = "path";
private static final File DEVICE_ROOT = new File("/");
private static HashMap<String, PathStrategy> sCache = new HashMap<>();
@@ -171,63 +152,36 @@ public class FileProvider extends ContentProvider {
synchronized (sCache) {
strat = sCache.get(authority);
if (strat == null) {
try {
strat = parsePathStrategy(context, authority);
} catch (IOException e) {
throw new IllegalArgumentException("Failed to parse xml", e);
} catch (XmlPullParserException e) {
throw new IllegalArgumentException("Failed to parse xml", e);
}
strat = createPathStrategy(context, authority);
sCache.put(authority, strat);
}
}
return strat;
}
private static PathStrategy parsePathStrategy(Context context, String authority)
throws IOException, XmlPullParserException {
private static PathStrategy createPathStrategy(Context context, String authority) {
final SimplePathStrategy strat = new SimplePathStrategy(authority);
final XmlResourceParser in = context.getResources().getXml(R.xml.file_paths);
int type;
while ((type = in.next()) != END_DOCUMENT) {
if (type == START_TAG) {
final String tag = in.getName();
final String name = in.getAttributeValue(null, ATTR_NAME);
String path = in.getAttributeValue(null, ATTR_PATH);
File target = null;
if (TAG_ROOT_PATH.equals(tag)) {
target = DEVICE_ROOT;
} else if (TAG_FILES_PATH.equals(tag)) {
target = context.getFilesDir();
} else if (TAG_CACHE_PATH.equals(tag)) {
target = context.getCacheDir();
} else if (TAG_EXTERNAL.equals(tag)) {
target = Environment.getExternalStorageDirectory();
} else if (TAG_EXTERNAL_FILES.equals(tag)) {
File[] externalFilesDirs = getExternalFilesDirs(context, null);
if (externalFilesDirs.length > 0) {
target = externalFilesDirs[0];
}
} else if (TAG_EXTERNAL_CACHE.equals(tag)) {
File[] externalCacheDirs = getExternalCacheDirs(context);
if (externalCacheDirs.length > 0) {
target = externalCacheDirs[0];
}
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP
&& TAG_EXTERNAL_MEDIA.equals(tag)) {
File[] externalMediaDirs = context.getExternalMediaDirs();
if (externalMediaDirs.length > 0) {
target = externalMediaDirs[0];
}
}
if (target != null) {
strat.addRoot(name, buildPath(target, path));
}
strat.addRoot("root_files", buildPath(DEVICE_ROOT, "."));
strat.addRoot("internal_files", buildPath(context.getFilesDir(), "."));
strat.addRoot("cache_files", buildPath(context.getCacheDir(), "."));
strat.addRoot("external_files", buildPath(Environment.getExternalStorageDirectory(), "."));
{
File[] externalFilesDirs = getExternalFilesDirs(context, null);
if (externalFilesDirs.length > 0) {
strat.addRoot("external_file_files", buildPath(externalFilesDirs[0], "."));
}
}
{
File[] externalCacheDirs = getExternalCacheDirs(context);
if (externalCacheDirs.length > 0) {
strat.addRoot("external_cache_files", buildPath(externalCacheDirs[0], "."));
}
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
File[] externalMediaDirs = context.getExternalMediaDirs();
if (externalMediaDirs.length > 0) {
strat.addRoot("external_media_files", buildPath(externalMediaDirs[0], "."));
}
}

View File

@@ -47,10 +47,9 @@ public class Networking {
} catch (Exception e) {
if (Build.VERSION.SDK_INT < 21) {
// Failed to update SSL provider, use NoSSLv3SocketFactory on SDK < 21
// and return false to notify potential issues
HttpsURLConnection.setDefaultSSLSocketFactory(new NoSSLv3SocketFactory());
return false;
}
return false;
}
return true;
}

View File

@@ -0,0 +1,35 @@
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
plugins {
id("java-library")
id("java")
id("com.github.johnrengelman.shadow") version "6.0.0"
}
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
val jar by tasks.getting(Jar::class) {
manifest {
attributes["Main-Class"] = "com.topjohnwu.signing.ZipSigner"
}
}
val shadowJar by tasks.getting(ShadowJar::class) {
archiveBaseName.set("zipsigner")
archiveClassifier.set(null as String?)
archiveVersion.set("4.0")
}
repositories {
jcenter()
}
dependencies {
implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar"))))
api("org.bouncycastle:bcprov-jdk15on:1.66")
api("org.bouncycastle:bcpkix-jdk15on:1.66")
}

View File

@@ -0,0 +1,772 @@
package com.topjohnwu.signing;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.security.DigestException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.MGF1ParameterSpec;
import java.security.spec.PSSParameterSpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* APK Signature Scheme v2 signer.
*
* <p>APK Signature Scheme v2 is a whole-file signature scheme which aims to protect every single
* bit of the APK, as opposed to the JAR Signature Scheme which protects only the names and
* uncompressed contents of ZIP entries.
*/
public abstract class ApkSignerV2 {
/*
* The two main goals of APK Signature Scheme v2 are:
* 1. Detect any unauthorized modifications to the APK. This is achieved by making the signature
* cover every byte of the APK being signed.
* 2. Enable much faster signature and integrity verification. This is achieved by requiring
* only a minimal amount of APK parsing before the signature is verified, thus completely
* bypassing ZIP entry decompression and by making integrity verification parallelizable by
* employing a hash tree.
*
* The generated signature block is wrapped into an APK Signing Block and inserted into the
* original APK immediately before the start of ZIP Central Directory. This is to ensure that
* JAR and ZIP parsers continue to work on the signed APK. The APK Signing Block is designed for
* extensibility. For example, a future signature scheme could insert its signatures there as
* well. The contract of the APK Signing Block is that all contents outside of the block must be
* protected by signatures inside the block.
*/
public static final int SIGNATURE_RSA_PSS_WITH_SHA256 = 0x0101;
public static final int SIGNATURE_RSA_PSS_WITH_SHA512 = 0x0102;
public static final int SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256 = 0x0103;
public static final int SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512 = 0x0104;
public static final int SIGNATURE_ECDSA_WITH_SHA256 = 0x0201;
public static final int SIGNATURE_ECDSA_WITH_SHA512 = 0x0202;
public static final int SIGNATURE_DSA_WITH_SHA256 = 0x0301;
public static final int SIGNATURE_DSA_WITH_SHA512 = 0x0302;
/**
* {@code .SF} file header section attribute indicating that the APK is signed not just with
* JAR signature scheme but also with APK Signature Scheme v2 or newer. This attribute
* facilitates v2 signature stripping detection.
*
* <p>The attribute contains a comma-separated set of signature scheme IDs.
*/
public static final String SF_ATTRIBUTE_ANDROID_APK_SIGNED_NAME = "X-Android-APK-Signed";
public static final String SF_ATTRIBUTE_ANDROID_APK_SIGNED_VALUE = "2";
private static final int CONTENT_DIGEST_CHUNKED_SHA256 = 0;
private static final int CONTENT_DIGEST_CHUNKED_SHA512 = 1;
private static final int CONTENT_DIGESTED_CHUNK_MAX_SIZE_BYTES = 1024 * 1024;
private static final byte[] APK_SIGNING_BLOCK_MAGIC =
new byte[] {
0x41, 0x50, 0x4b, 0x20, 0x53, 0x69, 0x67, 0x20,
0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x20, 0x34, 0x32,
};
private static final int APK_SIGNATURE_SCHEME_V2_BLOCK_ID = 0x7109871a;
private ApkSignerV2() {}
/**
* Signer configuration.
*/
public static final class SignerConfig {
/** Private key. */
public PrivateKey privateKey;
/**
* Certificates, with the first certificate containing the public key corresponding to
* {@link #privateKey}.
*/
public List<X509Certificate> certificates;
/**
* List of signature algorithms with which to sign (see {@code SIGNATURE_...} constants).
*/
public List<Integer> signatureAlgorithms;
}
/**
* Signs the provided APK using APK Signature Scheme v2 and returns the signed APK as a list of
* consecutive chunks.
*
* <p>NOTE: To enable APK signature verifier to detect v2 signature stripping, header sections
* of META-INF/*.SF files of APK being signed must contain the
* {@code X-Android-APK-Signed: true} attribute.
*
* @param inputApk contents of the APK to be signed. The APK starts at the current position
* of the buffer and ends at the limit of the buffer.
* @param signerConfigs signer configurations, one for each signer.
*
* @throws ApkParseException if the APK cannot be parsed.
* @throws InvalidKeyException if a signing key is not suitable for this signature scheme or
* cannot be used in general.
* @throws SignatureException if an error occurs when computing digests of generating
* signatures.
*/
public static ByteBuffer[] sign(
ByteBuffer inputApk,
List<SignerConfig> signerConfigs)
throws ApkParseException, InvalidKeyException, SignatureException {
// Slice/create a view in the inputApk to make sure that:
// 1. inputApk is what's between position and limit of the original inputApk, and
// 2. changes to position, limit, and byte order are not reflected in the original.
ByteBuffer originalInputApk = inputApk;
inputApk = originalInputApk.slice();
inputApk.order(ByteOrder.LITTLE_ENDIAN);
// Locate ZIP End of Central Directory (EoCD), Central Directory, and check that Central
// Directory is immediately followed by the ZIP End of Central Directory.
int eocdOffset = ZipUtils.findZipEndOfCentralDirectoryRecord(inputApk);
if (eocdOffset == -1) {
throw new ApkParseException("Failed to locate ZIP End of Central Directory");
}
if (ZipUtils.isZip64EndOfCentralDirectoryLocatorPresent(inputApk, eocdOffset)) {
throw new ApkParseException("ZIP64 format not supported");
}
inputApk.position(eocdOffset);
long centralDirSizeLong = ZipUtils.getZipEocdCentralDirectorySizeBytes(inputApk);
if (centralDirSizeLong > Integer.MAX_VALUE) {
throw new ApkParseException(
"ZIP Central Directory size out of range: " + centralDirSizeLong);
}
int centralDirSize = (int) centralDirSizeLong;
long centralDirOffsetLong = ZipUtils.getZipEocdCentralDirectoryOffset(inputApk);
if (centralDirOffsetLong > Integer.MAX_VALUE) {
throw new ApkParseException(
"ZIP Central Directory offset in file out of range: " + centralDirOffsetLong);
}
int centralDirOffset = (int) centralDirOffsetLong;
int expectedEocdOffset = centralDirOffset + centralDirSize;
if (expectedEocdOffset < centralDirOffset) {
throw new ApkParseException(
"ZIP Central Directory extent too large. Offset: " + centralDirOffset
+ ", size: " + centralDirSize);
}
if (eocdOffset != expectedEocdOffset) {
throw new ApkParseException(
"ZIP Central Directory not immeiately followed by ZIP End of"
+ " Central Directory. CD end: " + expectedEocdOffset
+ ", EoCD start: " + eocdOffset);
}
// Create ByteBuffers holding the contents of everything before ZIP Central Directory,
// ZIP Central Directory, and ZIP End of Central Directory.
inputApk.clear();
ByteBuffer beforeCentralDir = getByteBuffer(inputApk, centralDirOffset);
ByteBuffer centralDir = getByteBuffer(inputApk, eocdOffset - centralDirOffset);
// Create a copy of End of Central Directory because we'll need modify its contents later.
byte[] eocdBytes = new byte[inputApk.remaining()];
inputApk.get(eocdBytes);
ByteBuffer eocd = ByteBuffer.wrap(eocdBytes);
eocd.order(inputApk.order());
// Figure which which digests to use for APK contents.
Set<Integer> contentDigestAlgorithms = new HashSet<>();
for (SignerConfig signerConfig : signerConfigs) {
for (int signatureAlgorithm : signerConfig.signatureAlgorithms) {
contentDigestAlgorithms.add(
getSignatureAlgorithmContentDigestAlgorithm(signatureAlgorithm));
}
}
// Compute digests of APK contents.
Map<Integer, byte[]> contentDigests; // digest algorithm ID -> digest
try {
contentDigests =
computeContentDigests(
contentDigestAlgorithms,
new ByteBuffer[] {beforeCentralDir, centralDir, eocd});
} catch (DigestException e) {
throw new SignatureException("Failed to compute digests of APK", e);
}
// Sign the digests and wrap the signatures and signer info into an APK Signing Block.
ByteBuffer apkSigningBlock =
ByteBuffer.wrap(generateApkSigningBlock(signerConfigs, contentDigests));
// Update Central Directory Offset in End of Central Directory Record. Central Directory
// follows the APK Signing Block and thus is shifted by the size of the APK Signing Block.
centralDirOffset += apkSigningBlock.remaining();
eocd.clear();
ZipUtils.setZipEocdCentralDirectoryOffset(eocd, centralDirOffset);
// Follow the Java NIO pattern for ByteBuffer whose contents have been consumed.
originalInputApk.position(originalInputApk.limit());
// Reset positions (to 0) and limits (to capacity) in the ByteBuffers below to follow the
// Java NIO pattern for ByteBuffers which are ready for their contents to be read by caller.
// Contrary to the name, this does not clear the contents of these ByteBuffer.
beforeCentralDir.clear();
centralDir.clear();
eocd.clear();
// Insert APK Signing Block immediately before the ZIP Central Directory.
return new ByteBuffer[] {
beforeCentralDir,
apkSigningBlock,
centralDir,
eocd,
};
}
private static Map<Integer, byte[]> computeContentDigests(
Set<Integer> digestAlgorithms,
ByteBuffer[] contents) throws DigestException {
// For each digest algorithm the result is computed as follows:
// 1. Each segment of contents is split into consecutive chunks of 1 MB in size.
// The final chunk will be shorter iff the length of segment is not a multiple of 1 MB.
// No chunks are produced for empty (zero length) segments.
// 2. The digest of each chunk is computed over the concatenation of byte 0xa5, the chunk's
// length in bytes (uint32 little-endian) and the chunk's contents.
// 3. The output digest is computed over the concatenation of the byte 0x5a, the number of
// chunks (uint32 little-endian) and the concatenation of digests of chunks of all
// segments in-order.
int chunkCount = 0;
for (ByteBuffer input : contents) {
chunkCount += getChunkCount(input.remaining(), CONTENT_DIGESTED_CHUNK_MAX_SIZE_BYTES);
}
final Map<Integer, byte[]> digestsOfChunks = new HashMap<>(digestAlgorithms.size());
for (int digestAlgorithm : digestAlgorithms) {
int digestOutputSizeBytes = getContentDigestAlgorithmOutputSizeBytes(digestAlgorithm);
byte[] concatenationOfChunkCountAndChunkDigests =
new byte[5 + chunkCount * digestOutputSizeBytes];
concatenationOfChunkCountAndChunkDigests[0] = 0x5a;
setUnsignedInt32LittleEngian(
chunkCount, concatenationOfChunkCountAndChunkDigests, 1);
digestsOfChunks.put(digestAlgorithm, concatenationOfChunkCountAndChunkDigests);
}
int chunkIndex = 0;
byte[] chunkContentPrefix = new byte[5];
chunkContentPrefix[0] = (byte) 0xa5;
// Optimization opportunity: digests of chunks can be computed in parallel.
for (ByteBuffer input : contents) {
while (input.hasRemaining()) {
int chunkSize =
Math.min(input.remaining(), CONTENT_DIGESTED_CHUNK_MAX_SIZE_BYTES);
final ByteBuffer chunk = getByteBuffer(input, chunkSize);
for (int digestAlgorithm : digestAlgorithms) {
String jcaAlgorithmName =
getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithm);
MessageDigest md;
try {
md = MessageDigest.getInstance(jcaAlgorithmName);
} catch (NoSuchAlgorithmException e) {
throw new DigestException(
jcaAlgorithmName + " MessageDigest not supported", e);
}
// Reset position to 0 and limit to capacity. Position would've been modified
// by the preceding iteration of this loop. NOTE: Contrary to the method name,
// this does not modify the contents of the chunk.
chunk.clear();
setUnsignedInt32LittleEngian(chunk.remaining(), chunkContentPrefix, 1);
md.update(chunkContentPrefix);
md.update(chunk);
byte[] concatenationOfChunkCountAndChunkDigests =
digestsOfChunks.get(digestAlgorithm);
int expectedDigestSizeBytes =
getContentDigestAlgorithmOutputSizeBytes(digestAlgorithm);
int actualDigestSizeBytes =
md.digest(
concatenationOfChunkCountAndChunkDigests,
5 + chunkIndex * expectedDigestSizeBytes,
expectedDigestSizeBytes);
if (actualDigestSizeBytes != expectedDigestSizeBytes) {
throw new DigestException(
"Unexpected output size of " + md.getAlgorithm()
+ " digest: " + actualDigestSizeBytes);
}
}
chunkIndex++;
}
}
Map<Integer, byte[]> result = new HashMap<>(digestAlgorithms.size());
for (Map.Entry<Integer, byte[]> entry : digestsOfChunks.entrySet()) {
int digestAlgorithm = entry.getKey();
byte[] concatenationOfChunkCountAndChunkDigests = entry.getValue();
String jcaAlgorithmName = getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithm);
MessageDigest md;
try {
md = MessageDigest.getInstance(jcaAlgorithmName);
} catch (NoSuchAlgorithmException e) {
throw new DigestException(jcaAlgorithmName + " MessageDigest not supported", e);
}
result.put(digestAlgorithm, md.digest(concatenationOfChunkCountAndChunkDigests));
}
return result;
}
private static int getChunkCount(int inputSize, int chunkSize) {
return (inputSize + chunkSize - 1) / chunkSize;
}
private static void setUnsignedInt32LittleEngian(int value, byte[] result, int offset) {
result[offset] = (byte) (value & 0xff);
result[offset + 1] = (byte) ((value >> 8) & 0xff);
result[offset + 2] = (byte) ((value >> 16) & 0xff);
result[offset + 3] = (byte) ((value >> 24) & 0xff);
}
private static byte[] generateApkSigningBlock(
List<SignerConfig> signerConfigs,
Map<Integer, byte[]> contentDigests) throws InvalidKeyException, SignatureException {
byte[] apkSignatureSchemeV2Block =
generateApkSignatureSchemeV2Block(signerConfigs, contentDigests);
return generateApkSigningBlock(apkSignatureSchemeV2Block);
}
private static byte[] generateApkSigningBlock(byte[] apkSignatureSchemeV2Block) {
// FORMAT:
// uint64: size (excluding this field)
// repeated ID-value pairs:
// uint64: size (excluding this field)
// uint32: ID
// (size - 4) bytes: value
// uint64: size (same as the one above)
// uint128: magic
int resultSize =
8 // size
+ 8 + 4 + apkSignatureSchemeV2Block.length // v2Block as ID-value pair
+ 8 // size
+ 16 // magic
;
ByteBuffer result = ByteBuffer.allocate(resultSize);
result.order(ByteOrder.LITTLE_ENDIAN);
long blockSizeFieldValue = resultSize - 8;
result.putLong(blockSizeFieldValue);
long pairSizeFieldValue = 4 + apkSignatureSchemeV2Block.length;
result.putLong(pairSizeFieldValue);
result.putInt(APK_SIGNATURE_SCHEME_V2_BLOCK_ID);
result.put(apkSignatureSchemeV2Block);
result.putLong(blockSizeFieldValue);
result.put(APK_SIGNING_BLOCK_MAGIC);
return result.array();
}
private static byte[] generateApkSignatureSchemeV2Block(
List<SignerConfig> signerConfigs,
Map<Integer, byte[]> contentDigests) throws InvalidKeyException, SignatureException {
// FORMAT:
// * length-prefixed sequence of length-prefixed signer blocks.
List<byte[]> signerBlocks = new ArrayList<>(signerConfigs.size());
int signerNumber = 0;
for (SignerConfig signerConfig : signerConfigs) {
signerNumber++;
byte[] signerBlock;
try {
signerBlock = generateSignerBlock(signerConfig, contentDigests);
} catch (InvalidKeyException e) {
throw new InvalidKeyException("Signer #" + signerNumber + " failed", e);
} catch (SignatureException e) {
throw new SignatureException("Signer #" + signerNumber + " failed", e);
}
signerBlocks.add(signerBlock);
}
return encodeAsSequenceOfLengthPrefixedElements(
new byte[][] {
encodeAsSequenceOfLengthPrefixedElements(signerBlocks),
});
}
private static byte[] generateSignerBlock(
SignerConfig signerConfig,
Map<Integer, byte[]> contentDigests) throws InvalidKeyException, SignatureException {
if (signerConfig.certificates.isEmpty()) {
throw new SignatureException("No certificates configured for signer");
}
PublicKey publicKey = signerConfig.certificates.get(0).getPublicKey();
byte[] encodedPublicKey = encodePublicKey(publicKey);
V2SignatureSchemeBlock.SignedData signedData = new V2SignatureSchemeBlock.SignedData();
try {
signedData.certificates = encodeCertificates(signerConfig.certificates);
} catch (CertificateEncodingException e) {
throw new SignatureException("Failed to encode certificates", e);
}
List<Pair<Integer, byte[]>> digests =
new ArrayList<>(signerConfig.signatureAlgorithms.size());
for (int signatureAlgorithm : signerConfig.signatureAlgorithms) {
int contentDigestAlgorithm =
getSignatureAlgorithmContentDigestAlgorithm(signatureAlgorithm);
byte[] contentDigest = contentDigests.get(contentDigestAlgorithm);
if (contentDigest == null) {
throw new RuntimeException(
getContentDigestAlgorithmJcaDigestAlgorithm(contentDigestAlgorithm)
+ " content digest for "
+ getSignatureAlgorithmJcaSignatureAlgorithm(signatureAlgorithm)
+ " not computed");
}
digests.add(Pair.create(signatureAlgorithm, contentDigest));
}
signedData.digests = digests;
V2SignatureSchemeBlock.Signer signer = new V2SignatureSchemeBlock.Signer();
// FORMAT:
// * length-prefixed sequence of length-prefixed digests:
// * uint32: signature algorithm ID
// * length-prefixed bytes: digest of contents
// * length-prefixed sequence of certificates:
// * length-prefixed bytes: X.509 certificate (ASN.1 DER encoded).
// * length-prefixed sequence of length-prefixed additional attributes:
// * uint32: ID
// * (length - 4) bytes: value
signer.signedData = encodeAsSequenceOfLengthPrefixedElements(new byte[][] {
encodeAsSequenceOfLengthPrefixedPairsOfIntAndLengthPrefixedBytes(signedData.digests),
encodeAsSequenceOfLengthPrefixedElements(signedData.certificates),
// additional attributes
new byte[0],
});
signer.publicKey = encodedPublicKey;
signer.signatures = new ArrayList<>();
for (int signatureAlgorithm : signerConfig.signatureAlgorithms) {
Pair<String, ? extends AlgorithmParameterSpec> signatureParams =
getSignatureAlgorithmJcaSignatureAlgorithm(signatureAlgorithm);
String jcaSignatureAlgorithm = signatureParams.getFirst();
AlgorithmParameterSpec jcaSignatureAlgorithmParams = signatureParams.getSecond();
byte[] signatureBytes;
try {
Signature signature = Signature.getInstance(jcaSignatureAlgorithm);
signature.initSign(signerConfig.privateKey);
if (jcaSignatureAlgorithmParams != null) {
signature.setParameter(jcaSignatureAlgorithmParams);
}
signature.update(signer.signedData);
signatureBytes = signature.sign();
} catch (InvalidKeyException e) {
throw new InvalidKeyException("Failed sign using " + jcaSignatureAlgorithm, e);
} catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException
| SignatureException e) {
throw new SignatureException("Failed sign using " + jcaSignatureAlgorithm, e);
}
try {
Signature signature = Signature.getInstance(jcaSignatureAlgorithm);
signature.initVerify(publicKey);
if (jcaSignatureAlgorithmParams != null) {
signature.setParameter(jcaSignatureAlgorithmParams);
}
signature.update(signer.signedData);
if (!signature.verify(signatureBytes)) {
throw new SignatureException("Signature did not verify");
}
} catch (InvalidKeyException e) {
throw new InvalidKeyException("Failed to verify generated " + jcaSignatureAlgorithm
+ " signature using public key from certificate", e);
} catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException
| SignatureException e) {
throw new SignatureException("Failed to verify generated " + jcaSignatureAlgorithm
+ " signature using public key from certificate", e);
}
signer.signatures.add(Pair.create(signatureAlgorithm, signatureBytes));
}
// FORMAT:
// * length-prefixed signed data
// * length-prefixed sequence of length-prefixed signatures:
// * uint32: signature algorithm ID
// * length-prefixed bytes: signature of signed data
// * length-prefixed bytes: public key (X.509 SubjectPublicKeyInfo, ASN.1 DER encoded)
return encodeAsSequenceOfLengthPrefixedElements(
new byte[][] {
signer.signedData,
encodeAsSequenceOfLengthPrefixedPairsOfIntAndLengthPrefixedBytes(
signer.signatures),
signer.publicKey,
});
}
private static final class V2SignatureSchemeBlock {
private static final class Signer {
public byte[] signedData;
public List<Pair<Integer, byte[]>> signatures;
public byte[] publicKey;
}
private static final class SignedData {
public List<Pair<Integer, byte[]>> digests;
public List<byte[]> certificates;
}
}
private static byte[] encodePublicKey(PublicKey publicKey) throws InvalidKeyException {
byte[] encodedPublicKey = null;
if ("X.509".equals(publicKey.getFormat())) {
encodedPublicKey = publicKey.getEncoded();
}
if (encodedPublicKey == null) {
try {
encodedPublicKey =
KeyFactory.getInstance(publicKey.getAlgorithm())
.getKeySpec(publicKey, X509EncodedKeySpec.class)
.getEncoded();
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
throw new InvalidKeyException(
"Failed to obtain X.509 encoded form of public key " + publicKey
+ " of class " + publicKey.getClass().getName(),
e);
}
}
if ((encodedPublicKey == null) || (encodedPublicKey.length == 0)) {
throw new InvalidKeyException(
"Failed to obtain X.509 encoded form of public key " + publicKey
+ " of class " + publicKey.getClass().getName());
}
return encodedPublicKey;
}
public static List<byte[]> encodeCertificates(List<X509Certificate> certificates)
throws CertificateEncodingException {
List<byte[]> result = new ArrayList<>();
for (X509Certificate certificate : certificates) {
result.add(certificate.getEncoded());
}
return result;
}
private static byte[] encodeAsSequenceOfLengthPrefixedElements(List<byte[]> sequence) {
return encodeAsSequenceOfLengthPrefixedElements(
sequence.toArray(new byte[sequence.size()][]));
}
private static byte[] encodeAsSequenceOfLengthPrefixedElements(byte[][] sequence) {
int payloadSize = 0;
for (byte[] element : sequence) {
payloadSize += 4 + element.length;
}
ByteBuffer result = ByteBuffer.allocate(payloadSize);
result.order(ByteOrder.LITTLE_ENDIAN);
for (byte[] element : sequence) {
result.putInt(element.length);
result.put(element);
}
return result.array();
}
private static byte[] encodeAsSequenceOfLengthPrefixedPairsOfIntAndLengthPrefixedBytes(
List<Pair<Integer, byte[]>> sequence) {
int resultSize = 0;
for (Pair<Integer, byte[]> element : sequence) {
resultSize += 12 + element.getSecond().length;
}
ByteBuffer result = ByteBuffer.allocate(resultSize);
result.order(ByteOrder.LITTLE_ENDIAN);
for (Pair<Integer, byte[]> element : sequence) {
byte[] second = element.getSecond();
result.putInt(8 + second.length);
result.putInt(element.getFirst());
result.putInt(second.length);
result.put(second);
}
return result.array();
}
/**
* Relative <em>get</em> method for reading {@code size} number of bytes from the current
* position of this buffer.
*
* <p>This method reads the next {@code size} bytes at this buffer's current position,
* returning them as a {@code ByteBuffer} with start set to 0, limit and capacity set to
* {@code size}, byte order set to this buffer's byte order; and then increments the position by
* {@code size}.
*/
private static ByteBuffer getByteBuffer(ByteBuffer source, int size) {
if (size < 0) {
throw new IllegalArgumentException("size: " + size);
}
int originalLimit = source.limit();
int position = source.position();
int limit = position + size;
if ((limit < position) || (limit > originalLimit)) {
throw new BufferUnderflowException();
}
source.limit(limit);
try {
ByteBuffer result = source.slice();
result.order(source.order());
source.position(limit);
return result;
} finally {
source.limit(originalLimit);
}
}
private static Pair<String, ? extends AlgorithmParameterSpec>
getSignatureAlgorithmJcaSignatureAlgorithm(int sigAlgorithm) {
switch (sigAlgorithm) {
case SIGNATURE_RSA_PSS_WITH_SHA256:
return Pair.create(
"SHA256withRSA/PSS",
new PSSParameterSpec(
"SHA-256", "MGF1", MGF1ParameterSpec.SHA256, 256 / 8, 1));
case SIGNATURE_RSA_PSS_WITH_SHA512:
return Pair.create(
"SHA512withRSA/PSS",
new PSSParameterSpec(
"SHA-512", "MGF1", MGF1ParameterSpec.SHA512, 512 / 8, 1));
case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256:
return Pair.create("SHA256withRSA", null);
case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512:
return Pair.create("SHA512withRSA", null);
case SIGNATURE_ECDSA_WITH_SHA256:
return Pair.create("SHA256withECDSA", null);
case SIGNATURE_ECDSA_WITH_SHA512:
return Pair.create("SHA512withECDSA", null);
case SIGNATURE_DSA_WITH_SHA256:
return Pair.create("SHA256withDSA", null);
case SIGNATURE_DSA_WITH_SHA512:
return Pair.create("SHA512withDSA", null);
default:
throw new IllegalArgumentException(
"Unknown signature algorithm: 0x"
+ Long.toHexString(sigAlgorithm & 0xffffffff));
}
}
private static int getSignatureAlgorithmContentDigestAlgorithm(int sigAlgorithm) {
switch (sigAlgorithm) {
case SIGNATURE_RSA_PSS_WITH_SHA256:
case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256:
case SIGNATURE_ECDSA_WITH_SHA256:
case SIGNATURE_DSA_WITH_SHA256:
return CONTENT_DIGEST_CHUNKED_SHA256;
case SIGNATURE_RSA_PSS_WITH_SHA512:
case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512:
case SIGNATURE_ECDSA_WITH_SHA512:
case SIGNATURE_DSA_WITH_SHA512:
return CONTENT_DIGEST_CHUNKED_SHA512;
default:
throw new IllegalArgumentException(
"Unknown signature algorithm: 0x"
+ Long.toHexString(sigAlgorithm & 0xffffffff));
}
}
private static String getContentDigestAlgorithmJcaDigestAlgorithm(int digestAlgorithm) {
switch (digestAlgorithm) {
case CONTENT_DIGEST_CHUNKED_SHA256:
return "SHA-256";
case CONTENT_DIGEST_CHUNKED_SHA512:
return "SHA-512";
default:
throw new IllegalArgumentException(
"Unknown content digest algorthm: " + digestAlgorithm);
}
}
private static int getContentDigestAlgorithmOutputSizeBytes(int digestAlgorithm) {
switch (digestAlgorithm) {
case CONTENT_DIGEST_CHUNKED_SHA256:
return 256 / 8;
case CONTENT_DIGEST_CHUNKED_SHA512:
return 512 / 8;
default:
throw new IllegalArgumentException(
"Unknown content digest algorthm: " + digestAlgorithm);
}
}
/**
* Indicates that APK file could not be parsed.
*/
public static class ApkParseException extends Exception {
private static final long serialVersionUID = 1L;
public ApkParseException(String message) {
super(message);
}
public ApkParseException(String message, Throwable cause) {
super(message, cause);
}
}
/**
* Pair of two elements.
*/
private static class Pair<A, B> {
private final A mFirst;
private final B mSecond;
private Pair(A first, B second) {
mFirst = first;
mSecond = second;
}
public static <A, B> Pair<A, B> create(A first, B second) {
return new Pair<>(first, second);
}
public A getFirst() {
return mFirst;
}
public B getSecond() {
return mSecond;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((mFirst == null) ? 0 : mFirst.hashCode());
result = prime * result + ((mSecond == null) ? 0 : mSecond.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
@SuppressWarnings("rawtypes")
Pair other = (Pair) obj;
if (mFirst == null) {
if (other.mFirst != null) {
return false;
}
} else if (!mFirst.equals(other.mFirst)) {
return false;
}
if (mSecond == null) {
return other.mSecond == null;
} else return mSecond.equals(other.mSecond);
}
}
}

View File

@@ -1,9 +1,8 @@
package com.topjohnwu.signing;
import org.bouncycastle.asn1.ASN1Encoding;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.DEROutputStream;
import org.bouncycastle.asn1.cms.CMSObjectIdentifiers;
import org.bouncycastle.asn1.ASN1OutputStream;
import org.bouncycastle.cert.jcajce.JcaCertStore;
import org.bouncycastle.cms.CMSException;
import org.bouncycastle.cms.CMSProcessableByteArray;
@@ -11,34 +10,38 @@ import org.bouncycastle.cms.CMSSignedData;
import org.bouncycastle.cms.CMSSignedDataGenerator;
import org.bouncycastle.cms.CMSTypedData;
import org.bouncycastle.cms.jcajce.JcaSignerInfoGeneratorBuilder;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
import org.bouncycastle.util.encoders.Base64;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.security.DigestOutputStream;
import java.security.GeneralSecurityException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Security;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;
import java.util.TreeMap;
import java.util.jar.Attributes;
import java.util.jar.JarEntry;
@@ -48,71 +51,28 @@ import java.util.jar.Manifest;
import java.util.regex.Pattern;
/*
* Modified from from AOSP(Marshmallow) SignAPK.java
* */
public class SignAPK {
* Modified from from AOSP
* https://android.googlesource.com/platform/build/+/refs/tags/android-7.1.2_r39/tools/signapk/src/com/android/signapk/SignApk.java
* */
public class SignApk {
private static final String CERT_SF_NAME = "META-INF/CERT.SF";
private static final String CERT_SIG_NAME = "META-INF/CERT.%s";
private static final String CERT_SF_MULTI_NAME = "META-INF/CERT%d.SF";
private static final String CERT_SIG_MULTI_NAME = "META-INF/CERT%d.%s";
// bitmasks for which hash algorithms we need the manifest to include.
private static final int USE_SHA1 = 1;
private static final int USE_SHA256 = 2;
public static void signAndAdjust(X509Certificate cert, PrivateKey key,
JarMap input, OutputStream output) throws Exception {
File temp1 = File.createTempFile("signAPK", null);
File temp2 = File.createTempFile("signAPK", null);
try {
try (OutputStream out = new BufferedOutputStream(new FileOutputStream(temp1))) {
sign(cert, key, input, out, false);
}
ZipAdjust.adjust(temp1, temp2);
try (JarMap map = JarMap.open(temp2, false)) {
sign(cert, key, map, output, true);
}
} finally {
temp1.delete();
temp2.delete();
}
}
public static void sign(X509Certificate cert, PrivateKey key,
JarMap input, OutputStream output) throws Exception {
sign(cert, key, input, output, false);
}
private static void sign(X509Certificate cert, PrivateKey key,
JarMap input, OutputStream output, boolean minSign) throws Exception {
int hashes = 0;
hashes |= getDigestAlgorithm(cert);
// Set the ZIP file timestamp to the starting valid time
// of the 0th certificate plus one hour (to match what
// we've historically done).
long timestamp = cert.getNotBefore().getTime() + 3600L * 1000;
if (minSign) {
signWholeFile(input.getFile(), cert, key, output);
} else {
JarOutputStream outputJar = new JarOutputStream(output);
// For signing .apks, use the maximum compression to make
// them as small as possible (since they live forever on
// the system partition). For OTA packages, use the
// default compression level, which is much much faster
// and produces output that is only a tiny bit larger
// (~0.1% on full OTA packages I tested).
outputJar.setLevel(9);
Manifest manifest = addDigestsToManifest(input, hashes);
copyFiles(manifest, input, outputJar, timestamp, 4);
signFile(manifest, input, cert, key, outputJar);
outputJar.close();
}
}
/**
* Digest algorithm used when signing the APK using APK Signature Scheme v2.
*/
private static final String APK_SIG_SCHEME_V2_DIGEST_ALGORITHM = "SHA-256";
// Files matching this pattern are not copied to the output.
private static final Pattern stripPattern =
Pattern.compile("^(META-INF/((.*)[.](SF|RSA|DSA|EC)|com/android/otacert))|(" +
Pattern.quote(JarFile.MANIFEST_NAME) + ")$");
/**
* Return one of USE_SHA1 or USE_SHA256 according to the signature
@@ -120,7 +80,7 @@ public class SignAPK {
*/
private static int getDigestAlgorithm(X509Certificate cert) {
String sigAlg = cert.getSigAlgName().toUpperCase(Locale.US);
if (sigAlg.startsWith("SHA1WITHRSA") || sigAlg.startsWith("MD5WITHRSA")) {
if ("SHA1WITHRSA".equals(sigAlg) || "MD5WITHRSA".equals(sigAlg)) {
return USE_SHA1;
} else if (sigAlg.startsWith("SHA256WITH")) {
return USE_SHA256;
@@ -129,9 +89,11 @@ public class SignAPK {
"\" in cert [" + cert.getSubjectDN());
}
}
/** Returns the expected signature algorithm for this key type. */
/**
* Returns the expected signature algorithm for this key type.
*/
private static String getSignatureAlgorithm(X509Certificate cert) {
String sigAlg = cert.getSigAlgName().toUpperCase(Locale.US);
String keyType = cert.getPublicKey().getAlgorithm().toUpperCase(Locale.US);
if ("RSA".equalsIgnoreCase(keyType)) {
if (getDigestAlgorithm(cert) == USE_SHA256) {
@@ -145,10 +107,6 @@ public class SignAPK {
throw new IllegalArgumentException("unsupported key type: " + keyType);
}
}
// Files matching this pattern are not copied to the output.
private static Pattern stripPattern =
Pattern.compile("^(META-INF/((.*)[.](SF|RSA|DSA|EC)|com/android/otacert))|(" +
Pattern.quote(JarFile.MANIFEST_NAME) + ")$");
/**
* Add the hash(es) of every file to the manifest, creating it if
@@ -165,6 +123,7 @@ public class SignAPK {
main.putValue("Manifest-Version", "1.0");
main.putValue("Created-By", "1.0 (Android SignApk)");
}
MessageDigest md_sha1 = null;
MessageDigest md_sha256 = null;
if ((hashes & USE_SHA1) != 0) {
@@ -173,32 +132,51 @@ public class SignAPK {
if ((hashes & USE_SHA256) != 0) {
md_sha256 = MessageDigest.getInstance("SHA256");
}
byte[] buffer = new byte[4096];
int num;
// We sort the input entries by name, and add them to the
// output manifest in sorted order. We expect that the output
// map will be deterministic.
TreeMap<String, JarEntry> byName = new TreeMap<String, JarEntry>();
TreeMap<String, JarEntry> byName = new TreeMap<>();
for (Enumeration<JarEntry> e = jar.entries(); e.hasMoreElements(); ) {
JarEntry entry = e.nextElement();
byName.put(entry.getName(), entry);
}
for (JarEntry entry: byName.values()) {
for (JarEntry entry : byName.values()) {
String name = entry.getName();
if (!entry.isDirectory() &&
(stripPattern == null || !stripPattern.matcher(name).matches())) {
if (!entry.isDirectory() && !stripPattern.matcher(name).matches()) {
InputStream data = jar.getInputStream(entry);
while ((num = data.read(buffer)) > 0) {
if (md_sha1 != null) md_sha1.update(buffer, 0, num);
if (md_sha256 != null) md_sha256.update(buffer, 0, num);
}
Attributes attr = null;
if (input != null) attr = input.getAttributes(name);
attr = attr != null ? new Attributes(attr) : new Attributes();
// Remove any previously computed digests from this entry's attributes.
for (Iterator<Object> i = attr.keySet().iterator(); i.hasNext(); ) {
Object key = i.next();
if (!(key instanceof Attributes.Name)) {
continue;
}
String attributeNameLowerCase =
key.toString().toLowerCase(Locale.US);
if (attributeNameLowerCase.endsWith("-digest")) {
i.remove();
}
}
// Add SHA-1 digest if requested
if (md_sha1 != null) {
attr.putValue("SHA1-Digest",
new String(Base64.encode(md_sha1.digest()), "ASCII"));
}
// Add SHA-256 digest if requested
if (md_sha256 != null) {
attr.putValue("SHA-256-Digest",
new String(Base64.encode(md_sha256.digest()), "ASCII"));
@@ -206,33 +184,13 @@ public class SignAPK {
output.getEntries().put(name, attr);
}
}
return output;
}
/** Write to another stream and track how many bytes have been
* written.
/**
* Write a .SF file with a digest of the specified manifest.
*/
private static class CountOutputStream extends FilterOutputStream {
private int mCount;
public CountOutputStream(OutputStream out) {
super(out);
mCount = 0;
}
@Override
public void write(int b) throws IOException {
super.write(b);
mCount++;
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
super.write(b, off, len);
mCount += len;
}
public int size() {
return mCount;
}
}
/** Write a .SF file with a digest of the specified manifest. */
private static void writeSignatureFile(Manifest manifest, OutputStream out,
int hash)
throws IOException, GeneralSecurityException {
@@ -240,16 +198,25 @@ public class SignAPK {
Attributes main = sf.getMainAttributes();
main.putValue("Signature-Version", "1.0");
main.putValue("Created-By", "1.0 (Android SignApk)");
MessageDigest md = MessageDigest.getInstance(
hash == USE_SHA256 ? "SHA256" : "SHA1");
PrintStream print = new PrintStream(
new DigestOutputStream(new ByteArrayOutputStream(), md),
// Add APK Signature Scheme v2 signature stripping protection.
// This attribute indicates that this APK is supposed to have been signed using one or
// more APK-specific signature schemes in addition to the standard JAR signature scheme
// used by this code. APK signature verifier should reject the APK if it does not
// contain a signature for the signature scheme the verifier prefers out of this set.
main.putValue(
ApkSignerV2.SF_ATTRIBUTE_ANDROID_APK_SIGNED_NAME,
ApkSignerV2.SF_ATTRIBUTE_ANDROID_APK_SIGNED_VALUE);
MessageDigest md = MessageDigest.getInstance(hash == USE_SHA256 ? "SHA256" : "SHA1");
PrintStream print = new PrintStream(new DigestOutputStream(new ByteArrayOutputStream(), md),
true, "UTF-8");
// Digest of the entire manifest
manifest.write(print);
print.flush();
main.putValue(hash == USE_SHA256 ? "SHA-256-Digest-Manifest" : "SHA1-Digest-Manifest",
new String(Base64.encode(md.digest()), "ASCII"));
Map<String, Attributes> entries = manifest.getEntries();
for (Map.Entry<String, Attributes> entry : entries.entrySet()) {
// Digest of the manifest stanza for this entry.
@@ -259,13 +226,16 @@ public class SignAPK {
}
print.print("\r\n");
print.flush();
Attributes sfAttr = new Attributes();
sfAttr.putValue(hash == USE_SHA256 ? "SHA-256-Digest" : "SHA1-Digest-Manifest",
sfAttr.putValue(hash == USE_SHA256 ? "SHA-256-Digest" : "SHA1-Digest",
new String(Base64.encode(md.digest()), "ASCII"));
sf.getEntries().put(entry.getKey(), sfAttr);
}
CountOutputStream cout = new CountOutputStream(out);
sf.write(cout);
// A bug in the java.util.jar implementation of Android platforms
// up to version 1.6 will cause a spurious IOException to be thrown
// if the length of the signature file is a multiple of 1024 bytes.
@@ -275,10 +245,12 @@ public class SignAPK {
cout.write('\n');
}
}
/** Sign data and write the digital signature to 'out'. */
/**
* Sign data and write the digital signature to 'out'.
*/
private static void writeSignatureBlock(
CMSTypedData data, X509Certificate publicKey, PrivateKey privateKey,
OutputStream out)
CMSTypedData data, X509Certificate publicKey, PrivateKey privateKey, OutputStream out)
throws IOException,
CertificateEncodingException,
OperatorCreationException,
@@ -286,20 +258,24 @@ public class SignAPK {
ArrayList<X509Certificate> certList = new ArrayList<>(1);
certList.add(publicKey);
JcaCertStore certs = new JcaCertStore(certList);
CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
ContentSigner signer = new JcaContentSignerBuilder(getSignatureAlgorithm(publicKey))
.build(privateKey);
gen.addSignerInfoGenerator(
new JcaSignerInfoGeneratorBuilder(
new JcaDigestCalculatorProviderBuilder().build())
new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().build())
.setDirectSignature(true)
.build(signer, publicKey));
.build(signer, publicKey)
);
gen.addCertificates(certs);
CMSSignedData sigData = gen.generate(data, false);
ASN1InputStream asn1 = new ASN1InputStream(sigData.getEncoded());
DEROutputStream dos = new DEROutputStream(out);
dos.writeObject(asn1.readObject());
try (ASN1InputStream asn1 = new ASN1InputStream(sigData.getEncoded())) {
ASN1OutputStream dos = ASN1OutputStream.create(out, ASN1Encoding.DER);
dos.writeObject(asn1.readObject());
}
}
/**
* Copy all the files in a manifest from input to output. We set
* the modification times in the output to a fixed time, so as to
@@ -307,27 +283,37 @@ public class SignAPK {
* more efficient.
*/
private static void copyFiles(Manifest manifest, JarMap in, JarOutputStream out,
long timestamp, int alignment) throws IOException {
long timestamp, int defaultAlignment) throws IOException {
byte[] buffer = new byte[4096];
int num;
Map<String, Attributes> entries = manifest.getEntries();
ArrayList<String> names = new ArrayList<>(entries.keySet());
Collections.sort(names);
boolean firstEntry = true;
long offset = 0L;
// We do the copy in two passes -- first copying all the
// entries that are STORED, then copying all the entries that
// have any other compression flag (which in practice means
// DEFLATED). This groups all the stored entries together at
// the start of the file and makes it easier to do alignment
// on them (since only stored entries are aligned).
for (String name : names) {
JarEntry inEntry = in.getJarEntry(name);
JarEntry outEntry = null;
JarEntry outEntry;
if (inEntry.getMethod() != JarEntry.STORED) continue;
// Preserve the STORED method of the input entry.
outEntry = new JarEntry(inEntry);
outEntry.setTime(timestamp);
// Discard comment and extra fields of this entry to
// simplify alignment logic below and for consistency with
// how compressed entries are handled later.
outEntry.setComment(null);
outEntry.setExtra(null);
// 'offset' is the offset into the file at which we expect
// the file data to begin. This is the value we need to
// make a multiple of 'alignement'.
@@ -341,15 +327,18 @@ public class SignAPK {
offset += 4;
firstEntry = false;
}
int alignment = getStoredEntryDataAlignment(name, defaultAlignment);
if (alignment > 0 && (offset % alignment != 0)) {
// Set the "extra data" of the entry to between 1 and
// alignment-1 bytes, to make the file data begin at
// an aligned offset.
int needed = alignment - (int)(offset % alignment);
int needed = alignment - (int) (offset % alignment);
outEntry.setExtra(new byte[needed]);
offset += needed;
}
out.putNextEntry(outEntry);
InputStream data = in.getInputStream(inEntry);
while ((num = data.read(buffer)) > 0) {
out.write(buffer, 0, num);
@@ -357,17 +346,20 @@ public class SignAPK {
}
out.flush();
}
// Copy all the non-STORED entries. We don't attempt to
// maintain the 'offset' variable past this point; we don't do
// alignment on these entries.
for (String name : names) {
JarEntry inEntry = in.getJarEntry(name);
JarEntry outEntry = null;
JarEntry outEntry;
if (inEntry.getMethod() == JarEntry.STORED) continue;
// Create a new entry so that the compressed len is recomputed.
outEntry = new JarEntry(name);
outEntry.setTime(timestamp);
out.putNextEntry(outEntry);
InputStream data = in.getInputStream(inEntry);
while ((num = data.read(buffer)) > 0) {
out.write(buffer, 0, num);
@@ -376,134 +368,203 @@ public class SignAPK {
}
}
// This class is to provide a file's content, but trimming out the last two bytes
// Used for signWholeFile
private static class CMSProcessableFile implements CMSTypedData {
private ASN1ObjectIdentifier type;
private RandomAccessFile file;
CMSProcessableFile(File file) throws FileNotFoundException {
this.file = new RandomAccessFile(file, "r");
type = new ASN1ObjectIdentifier(CMSObjectIdentifiers.data.getId());
/**
* Returns the multiple (in bytes) at which the provided {@code STORED} entry's data must start
* relative to start of file or {@code 0} if alignment of this entry's data is not important.
*/
private static int getStoredEntryDataAlignment(String entryName, int defaultAlignment) {
if (defaultAlignment <= 0) {
return 0;
}
@Override
public ASN1ObjectIdentifier getContentType() {
return type;
}
@Override
public void write(OutputStream out) throws IOException, CMSException {
file.seek(0);
int read;
byte buffer[] = new byte[4096];
int len = (int) file.length() - 2;
while ((read = file.read(buffer, 0, len < buffer.length ? len : buffer.length)) > 0) {
out.write(buffer, 0, read);
len -= read;
}
}
@Override
public Object getContent() {
return file;
}
byte[] getTail() throws IOException {
byte tail[] = new byte[22];
file.seek(file.length() - 22);
file.readFully(tail);
return tail;
if (entryName.endsWith(".so")) {
// Align .so contents to memory page boundary to enable memory-mapped
// execution.
return 4096;
} else {
return defaultAlignment;
}
}
private static void signWholeFile(File input, X509Certificate publicKey,
PrivateKey privateKey, OutputStream outputStream)
throws Exception {
ByteArrayOutputStream temp = new ByteArrayOutputStream();
// put a readable message and a null char at the start of the
// archive comment, so that tools that display the comment
// (hopefully) show something sensible.
// TODO: anything more useful we can put in this message?
byte[] message = "signed by SignApk".getBytes("UTF-8");
temp.write(message);
temp.write(0);
CMSProcessableFile cmsFile = new CMSProcessableFile(input);
writeSignatureBlock(cmsFile, publicKey, privateKey, temp);
// For a zip with no archive comment, the
// end-of-central-directory record will be 22 bytes long, so
// we expect to find the EOCD marker 22 bytes from the end.
byte[] zipData = cmsFile.getTail();
if (zipData[zipData.length-22] != 0x50 ||
zipData[zipData.length-21] != 0x4b ||
zipData[zipData.length-20] != 0x05 ||
zipData[zipData.length-19] != 0x06) {
throw new IllegalArgumentException("zip data already has an archive comment");
}
int total_size = temp.size() + 6;
if (total_size > 0xffff) {
throw new IllegalArgumentException("signature is too big for ZIP file comment");
}
// signature starts this many bytes from the end of the file
int signature_start = total_size - message.length - 1;
temp.write(signature_start & 0xff);
temp.write((signature_start >> 8) & 0xff);
// Why the 0xff bytes? In a zip file with no archive comment,
// bytes [-6:-2] of the file are the little-endian offset from
// the start of the file to the central directory. So for the
// two high bytes to be 0xff 0xff, the archive would have to
// be nearly 4GB in size. So it's unlikely that a real
// commentless archive would have 0xffs here, and lets us tell
// an old signed archive from a new one.
temp.write(0xff);
temp.write(0xff);
temp.write(total_size & 0xff);
temp.write((total_size >> 8) & 0xff);
temp.flush();
// Signature verification checks that the EOCD header is the
// last such sequence in the file (to avoid minzip finding a
// fake EOCD appended after the signature in its scan). The
// odds of producing this sequence by chance are very low, but
// let's catch it here if it does.
byte[] b = temp.toByteArray();
for (int i = 0; i < b.length-3; ++i) {
if (b[i] == 0x50 && b[i+1] == 0x4b && b[i+2] == 0x05 && b[i+3] == 0x06) {
throw new IllegalArgumentException("found spurious EOCD header at " + i);
}
}
cmsFile.write(outputStream);
outputStream.write(total_size & 0xff);
outputStream.write((total_size >> 8) & 0xff);
temp.writeTo(outputStream);
outputStream.close();
}
private static void signFile(Manifest manifest, JarMap inputJar,
X509Certificate cert, PrivateKey privateKey,
JarOutputStream outputJar)
throws Exception {
// Assume the certificate is valid for at least an hour.
long timestamp = cert.getNotBefore().getTime() + 3600L * 1000;
private static void signFile(Manifest manifest,
X509Certificate[] publicKey, PrivateKey[] privateKey,
long timestamp, JarOutputStream outputJar) throws Exception {
// MANIFEST.MF
JarEntry je = new JarEntry(JarFile.MANIFEST_NAME);
je.setTime(timestamp);
outputJar.putNextEntry(je);
manifest.write(outputJar);
je = new JarEntry(CERT_SF_NAME);
je.setTime(timestamp);
outputJar.putNextEntry(je);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
writeSignatureFile(manifest, baos, getDigestAlgorithm(cert));
byte[] signedData = baos.toByteArray();
outputJar.write(signedData);
// CERT.{EC,RSA} / CERT#.{EC,RSA}
final String keyType = cert.getPublicKey().getAlgorithm();
je = new JarEntry(String.format(CERT_SIG_NAME, keyType));
je.setTime(timestamp);
outputJar.putNextEntry(je);
writeSignatureBlock(new CMSProcessableByteArray(signedData),
cert, privateKey, outputJar);
int numKeys = publicKey.length;
for (int k = 0; k < numKeys; ++k) {
// CERT.SF / CERT#.SF
je = new JarEntry(numKeys == 1 ? CERT_SF_NAME :
(String.format(Locale.US, CERT_SF_MULTI_NAME, k)));
je.setTime(timestamp);
outputJar.putNextEntry(je);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
writeSignatureFile(manifest, baos, getDigestAlgorithm(publicKey[k]));
byte[] signedData = baos.toByteArray();
outputJar.write(signedData);
// CERT.{EC,RSA} / CERT#.{EC,RSA}
final String keyType = publicKey[k].getPublicKey().getAlgorithm();
je = new JarEntry(numKeys == 1 ? (String.format(CERT_SIG_NAME, keyType)) :
(String.format(Locale.US, CERT_SIG_MULTI_NAME, k, keyType)));
je.setTime(timestamp);
outputJar.putNextEntry(je);
writeSignatureBlock(new CMSProcessableByteArray(signedData),
publicKey[k], privateKey[k], outputJar);
}
}
/**
* Converts the provided lists of private keys, their X.509 certificates, and digest algorithms
* into a list of APK Signature Scheme v2 {@code SignerConfig} instances.
*/
private static List<ApkSignerV2.SignerConfig> createV2SignerConfigs(
PrivateKey[] privateKeys, X509Certificate[] certificates, String[] digestAlgorithms)
throws InvalidKeyException {
if (privateKeys.length != certificates.length) {
throw new IllegalArgumentException(
"The number of private keys must match the number of certificates: "
+ privateKeys.length + " vs" + certificates.length);
}
List<ApkSignerV2.SignerConfig> result = new ArrayList<>(privateKeys.length);
for (int i = 0; i < privateKeys.length; i++) {
PrivateKey privateKey = privateKeys[i];
X509Certificate certificate = certificates[i];
PublicKey publicKey = certificate.getPublicKey();
String keyAlgorithm = privateKey.getAlgorithm();
if (!keyAlgorithm.equalsIgnoreCase(publicKey.getAlgorithm())) {
throw new InvalidKeyException(
"Key algorithm of private key #" + (i + 1) + " does not match key"
+ " algorithm of public key #" + (i + 1) + ": " + keyAlgorithm
+ " vs " + publicKey.getAlgorithm());
}
ApkSignerV2.SignerConfig signerConfig = new ApkSignerV2.SignerConfig();
signerConfig.privateKey = privateKey;
signerConfig.certificates = Collections.singletonList(certificate);
List<Integer> signatureAlgorithms = new ArrayList<>(digestAlgorithms.length);
for (String digestAlgorithm : digestAlgorithms) {
try {
signatureAlgorithms.add(getV2SignatureAlgorithm(keyAlgorithm, digestAlgorithm));
} catch (IllegalArgumentException e) {
throw new InvalidKeyException(
"Unsupported key and digest algorithm combination for signer #"
+ (i + 1), e);
}
}
signerConfig.signatureAlgorithms = signatureAlgorithms;
result.add(signerConfig);
}
return result;
}
private static int getV2SignatureAlgorithm(String keyAlgorithm, String digestAlgorithm) {
if ("SHA-256".equalsIgnoreCase(digestAlgorithm)) {
if ("RSA".equalsIgnoreCase(keyAlgorithm)) {
// Use RSASSA-PKCS1-v1_5 signature scheme instead of RSASSA-PSS to guarantee
// deterministic signatures which make life easier for OTA updates (fewer files
// changed when deterministic signature schemes are used).
return ApkSignerV2.SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256;
} else if ("EC".equalsIgnoreCase(keyAlgorithm)) {
return ApkSignerV2.SIGNATURE_ECDSA_WITH_SHA256;
} else if ("DSA".equalsIgnoreCase(keyAlgorithm)) {
return ApkSignerV2.SIGNATURE_DSA_WITH_SHA256;
} else {
throw new IllegalArgumentException("Unsupported key algorithm: " + keyAlgorithm);
}
} else if ("SHA-512".equalsIgnoreCase(digestAlgorithm)) {
if ("RSA".equalsIgnoreCase(keyAlgorithm)) {
// Use RSASSA-PKCS1-v1_5 signature scheme instead of RSASSA-PSS to guarantee
// deterministic signatures which make life easier for OTA updates (fewer files
// changed when deterministic signature schemes are used).
return ApkSignerV2.SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512;
} else if ("EC".equalsIgnoreCase(keyAlgorithm)) {
return ApkSignerV2.SIGNATURE_ECDSA_WITH_SHA512;
} else if ("DSA".equalsIgnoreCase(keyAlgorithm)) {
return ApkSignerV2.SIGNATURE_DSA_WITH_SHA512;
} else {
throw new IllegalArgumentException("Unsupported key algorithm: " + keyAlgorithm);
}
} else {
throw new IllegalArgumentException("Unsupported digest algorithm: " + digestAlgorithm);
}
}
public static void sign(X509Certificate cert, PrivateKey key,
JarMap inputJar, FileOutputStream outputFile) throws Exception {
int alignment = 4;
int hashes = 0;
X509Certificate[] publicKey = new X509Certificate[1];
publicKey[0] = cert;
hashes |= getDigestAlgorithm(publicKey[0]);
// Set all ZIP file timestamps to Jan 1 2009 00:00:00.
long timestamp = 1230768000000L;
// The Java ZipEntry API we're using converts milliseconds since epoch into MS-DOS
// timestamp using the current timezone. We thus adjust the milliseconds since epoch
// value to end up with MS-DOS timestamp of Jan 1 2009 00:00:00.
timestamp -= TimeZone.getDefault().getOffset(timestamp);
PrivateKey[] privateKey = new PrivateKey[1];
privateKey[0] = key;
// Generate, in memory, an APK signed using standard JAR Signature Scheme.
ByteArrayOutputStream v1SignedApkBuf = new ByteArrayOutputStream();
JarOutputStream outputJar = new JarOutputStream(v1SignedApkBuf);
// Use maximum compression for compressed entries because the APK lives forever on
// the system partition.
outputJar.setLevel(9);
Manifest manifest = addDigestsToManifest(inputJar, hashes);
copyFiles(manifest, inputJar, outputJar, timestamp, alignment);
signFile(manifest, publicKey, privateKey, timestamp, outputJar);
outputJar.close();
ByteBuffer v1SignedApk = ByteBuffer.wrap(v1SignedApkBuf.toByteArray());
v1SignedApkBuf.reset();
ByteBuffer[] outputChunks;
List<ApkSignerV2.SignerConfig> signerConfigs = createV2SignerConfigs(privateKey, publicKey,
new String[]{APK_SIG_SCHEME_V2_DIGEST_ALGORITHM});
outputChunks = ApkSignerV2.sign(v1SignedApk, signerConfigs);
// This assumes outputChunks are array-backed. To avoid this assumption, the
// code could be rewritten to use FileChannel.
for (ByteBuffer outputChunk : outputChunks) {
outputFile.write(outputChunk.array(),
outputChunk.arrayOffset() + outputChunk.position(), outputChunk.remaining());
outputChunk.position(outputChunk.limit());
}
}
/**
* Write to another stream and track how many bytes have been
* written.
*/
private static class CountOutputStream extends FilterOutputStream {
private int mCount;
public CountOutputStream(OutputStream out) {
super(out);
mCount = 0;
}
@Override
public void write(int b) throws IOException {
super.write(b);
mCount++;
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
super.write(b, off, len);
mCount += len;
}
public int size() {
return mCount;
}
}
}

View File

@@ -6,7 +6,6 @@ import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
@@ -28,20 +27,20 @@ public class ZipSigner {
System.exit(2);
}
private static void sign(JarMap input, OutputStream output) throws Exception {
sign(SignAPK.class.getResourceAsStream("/keys/testkey.x509.pem"),
SignAPK.class.getResourceAsStream("/keys/testkey.pk8"), input, output);
private static void sign(JarMap input, FileOutputStream output) throws Exception {
sign(SignApk.class.getResourceAsStream("/keys/testkey.x509.pem"),
SignApk.class.getResourceAsStream("/keys/testkey.pk8"), input, output);
}
private static void sign(InputStream certIs, InputStream keyIs,
JarMap input, OutputStream output) throws Exception {
JarMap input, FileOutputStream output) throws Exception {
X509Certificate cert = CryptoUtils.readCertificate(certIs);
PrivateKey key = CryptoUtils.readPrivateKey(keyIs);
SignAPK.signAndAdjust(cert, key, input, output);
SignApk.sign(cert, key, input, output);
}
private static void sign(String keyStore, String keyStorePass, String alias, String keyPass,
JarMap in, OutputStream out) throws Exception {
JarMap in, FileOutputStream out) throws Exception {
KeyStore ks;
try {
ks = KeyStore.getInstance("JKS");
@@ -56,7 +55,7 @@ public class ZipSigner {
}
X509Certificate cert = (X509Certificate) ks.getCertificate(alias);
PrivateKey key = (PrivateKey) ks.getKey(alias, keyPass.toCharArray());
SignAPK.signAndAdjust(cert, key, in, out);
SignApk.sign(cert, key, in, out);
}
public static void main(String[] args) throws Exception {
@@ -66,7 +65,7 @@ public class ZipSigner {
Security.insertProviderAt(new BouncyCastleProvider(), 1);
try (JarMap in = JarMap.open(args[args.length - 2], false);
OutputStream out = new FileOutputStream(args[args.length - 1])) {
FileOutputStream out = new FileOutputStream(args[args.length - 1])) {
if (args.length == 2) {
sign(in, out);
} else if (args.length == 4) {

View File

@@ -0,0 +1,136 @@
package com.topjohnwu.signing;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
/**
* Assorted ZIP format helpers.
*
* <p>NOTE: Most helper methods operating on {@code ByteBuffer} instances expect that the byte
* order of these buffers is little-endian.
*/
public abstract class ZipUtils {
private static final int ZIP_EOCD_REC_MIN_SIZE = 22;
private static final int ZIP_EOCD_REC_SIG = 0x06054b50;
private static final int ZIP_EOCD_CENTRAL_DIR_SIZE_FIELD_OFFSET = 12;
private static final int ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_OFFSET = 16;
private static final int ZIP_EOCD_COMMENT_LENGTH_FIELD_OFFSET = 20;
private static final int ZIP64_EOCD_LOCATOR_SIZE = 20;
private static final int ZIP64_EOCD_LOCATOR_SIG = 0x07064b50;
private static final int UINT16_MAX_VALUE = 0xffff;
private ZipUtils() {
}
/**
* Returns the position at which ZIP End of Central Directory record starts in the provided
* buffer or {@code -1} if the record is not present.
*
* <p>NOTE: Byte order of {@code zipContents} must be little-endian.
*/
public static int findZipEndOfCentralDirectoryRecord(ByteBuffer zipContents) {
assertByteOrderLittleEndian(zipContents);
// ZIP End of Central Directory (EOCD) record is located at the very end of the ZIP archive.
// The record can be identified by its 4-byte signature/magic which is located at the very
// beginning of the record. A complication is that the record is variable-length because of
// the comment field.
// The algorithm for locating the ZIP EOCD record is as follows. We search backwards from
// end of the buffer for the EOCD record signature. Whenever we find a signature, we check
// the candidate record's comment length is such that the remainder of the record takes up
// exactly the remaining bytes in the buffer. The search is bounded because the maximum
// size of the comment field is 65535 bytes because the field is an unsigned 16-bit number.
int archiveSize = zipContents.capacity();
if (archiveSize < ZIP_EOCD_REC_MIN_SIZE) {
return -1;
}
int maxCommentLength = Math.min(archiveSize - ZIP_EOCD_REC_MIN_SIZE, UINT16_MAX_VALUE);
int eocdWithEmptyCommentStartPosition = archiveSize - ZIP_EOCD_REC_MIN_SIZE;
for (int expectedCommentLength = 0; expectedCommentLength < maxCommentLength; expectedCommentLength++) {
int eocdStartPos = eocdWithEmptyCommentStartPosition - expectedCommentLength;
if (zipContents.getInt(eocdStartPos) == ZIP_EOCD_REC_SIG) {
int actualCommentLength = getUnsignedInt16(zipContents, eocdStartPos + ZIP_EOCD_COMMENT_LENGTH_FIELD_OFFSET);
if (actualCommentLength == expectedCommentLength) {
return eocdStartPos;
}
}
}
return -1;
}
/**
* Returns {@code true} if the provided buffer contains a ZIP64 End of Central Directory
* Locator.
*
* <p>NOTE: Byte order of {@code zipContents} must be little-endian.
*/
public static boolean isZip64EndOfCentralDirectoryLocatorPresent(ByteBuffer zipContents, int zipEndOfCentralDirectoryPosition) {
assertByteOrderLittleEndian(zipContents);
// ZIP64 End of Central Directory Locator immediately precedes the ZIP End of Central
// Directory Record.
int locatorPosition = zipEndOfCentralDirectoryPosition - ZIP64_EOCD_LOCATOR_SIZE;
if (locatorPosition < 0) {
return false;
}
return zipContents.getInt(locatorPosition) == ZIP64_EOCD_LOCATOR_SIG;
}
/**
* Returns the offset of the start of the ZIP Central Directory in the archive.
*
* <p>NOTE: Byte order of {@code zipEndOfCentralDirectory} must be little-endian.
*/
public static long getZipEocdCentralDirectoryOffset(ByteBuffer zipEndOfCentralDirectory) {
assertByteOrderLittleEndian(zipEndOfCentralDirectory);
return getUnsignedInt32(zipEndOfCentralDirectory, zipEndOfCentralDirectory.position() + ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_OFFSET);
}
/**
* Sets the offset of the start of the ZIP Central Directory in the archive.
*
* <p>NOTE: Byte order of {@code zipEndOfCentralDirectory} must be little-endian.
*/
public static void setZipEocdCentralDirectoryOffset(ByteBuffer zipEndOfCentralDirectory, long offset) {
assertByteOrderLittleEndian(zipEndOfCentralDirectory);
setUnsignedInt32(zipEndOfCentralDirectory, zipEndOfCentralDirectory.position() + ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_OFFSET, offset);
}
/**
* Returns the size (in bytes) of the ZIP Central Directory.
*
* <p>NOTE: Byte order of {@code zipEndOfCentralDirectory} must be little-endian.
*/
public static long getZipEocdCentralDirectorySizeBytes(ByteBuffer zipEndOfCentralDirectory) {
assertByteOrderLittleEndian(zipEndOfCentralDirectory);
return getUnsignedInt32(zipEndOfCentralDirectory, zipEndOfCentralDirectory.position() + ZIP_EOCD_CENTRAL_DIR_SIZE_FIELD_OFFSET);
}
private static void assertByteOrderLittleEndian(ByteBuffer buffer) {
if (buffer.order() != ByteOrder.LITTLE_ENDIAN) {
throw new IllegalArgumentException("ByteBuffer byte order must be little endian");
}
}
private static int getUnsignedInt16(ByteBuffer buffer, int offset) {
return buffer.getShort(offset) & 0xffff;
}
private static long getUnsignedInt32(ByteBuffer buffer, int offset) {
return buffer.getInt(offset) & 0xffffffffL;
}
private static void setUnsignedInt32(ByteBuffer buffer, int offset, long value) {
if ((value < 0) || (value > 0xffffffffL)) {
throw new IllegalArgumentException("uint32 value of out range: " + value);
}
buffer.putInt(buffer.position() + offset, (int) value);
}
}

View File

@@ -1,5 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.topjohnwu.magisk">
@@ -7,8 +6,12 @@
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28" />
<application
android:icon="@drawable/ic_launcher"
android:name="a.e"
android:allowBackup="true"
tools:ignore="UnusedAttribute,GoogleAppIndexingWarning">
@@ -21,21 +24,21 @@
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.APPLICATION_PREFERENCES" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<!-- Main -->
<activity android:name="a.b" />
<!-- Flashing -->
<activity android:name="a.f" />
<!-- Superuser -->
<activity
android:name="a.m"
android:directBootAware="true"
android:excludeFromRecents="true"
android:exported="false"
android:theme="@android:style/Theme.Translucent.NoTitleBar"
tools:ignore="AppLinkUrlError">
<intent-filter>
<action android:name="android.intent.action.VIEW"/>

View File

@@ -1,20 +0,0 @@
package a;
import com.topjohnwu.magisk.utils.PatchAPK;
import com.topjohnwu.signing.BootSigner;
public class a {
@Deprecated
public static boolean patchAPK(String in, String out, String pkg) {
return PatchAPK.patch(in, out, pkg);
}
public static boolean patchAPK(String in, String out, String pkg, String label) {
return PatchAPK.patch(in, out, pkg, label);
}
public static void main(String[] args) throws Exception {
BootSigner.main(args);
}
}

View File

@@ -1,7 +0,0 @@
package a;
import com.topjohnwu.magisk.ui.MainActivity;
public class b extends MainActivity {
/* stub */
}

View File

@@ -1,7 +0,0 @@
package a;
import com.topjohnwu.magisk.ui.SplashActivity;
public class c extends SplashActivity {
/* stub */
}

View File

@@ -1,13 +0,0 @@
package a;
import com.topjohnwu.magisk.App;
public class e extends App {
public e() {
super();
}
public e(Object o) {
super(o);
}
}

View File

@@ -1,7 +0,0 @@
package a;
import com.topjohnwu.magisk.ui.flash.FlashActivity;
public class f extends FlashActivity {
/* stub */
}

View File

@@ -1,15 +0,0 @@
package a;
import android.content.Context;
import com.topjohnwu.magisk.model.update.UpdateCheckService;
import androidx.annotation.NonNull;
import androidx.work.WorkerParameters;
public class g extends w<UpdateCheckService> {
/* Stub */
public g(@NonNull Context context, @NonNull WorkerParameters workerParams) {
super(context, workerParams);
}
}

View File

@@ -1,7 +0,0 @@
package a;
import com.topjohnwu.magisk.model.receiver.GeneralReceiver;
public class h extends GeneralReceiver {
/* stub */
}

View File

@@ -1,7 +0,0 @@
package a;
import com.topjohnwu.magisk.model.download.DownloadService;
public class j extends DownloadService {
/* stub */
}

View File

@@ -1,7 +0,0 @@
package a;
import com.topjohnwu.magisk.ui.surequest.SuRequestActivity;
public class m extends SuRequestActivity {
/* stub */
}

View File

@@ -0,0 +1,29 @@
@file:JvmName("a")
package a
import com.topjohnwu.magisk.core.App
import com.topjohnwu.magisk.core.GeneralReceiver
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.BootSigner
fun main(args: Array<String>) {
BootSigner.main(args)
}
class b : MainActivity()
class c : SplashActivity()
class e : App {
constructor() : super()
constructor(o: Any) : super(o)
}
class h : GeneralReceiver()
class j : DownloadService()
class m : SuRequestActivity()

View File

@@ -1,42 +0,0 @@
package a;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.work.Worker;
import androidx.work.WorkerParameters;
import com.topjohnwu.magisk.base.DelegateWorker;
import java.lang.reflect.ParameterizedType;
public abstract class w<T extends DelegateWorker> extends Worker {
/* Wrapper class to workaround Proguard -keep class * extends Worker */
private T base;
@SuppressWarnings("unchecked")
w(@NonNull Context context, @NonNull WorkerParameters workerParams) {
super(context, workerParams);
try {
base = ((Class<T>) ((ParameterizedType) getClass().getGenericSuperclass())
.getActualTypeArguments()[0]).newInstance();
base.attachWorker(this);
} catch (Exception ignored) {}
}
@NonNull
@Override
public Result doWork() {
if (base == null)
return Result.failure();
return base.doWork();
}
@Override
public void onStopped() {
if (base != null)
base.onStopped();
}
}

View File

@@ -1,77 +0,0 @@
package com.topjohnwu.magisk
import com.github.pwittchen.reactivenetwork.library.rx2.ReactiveNetwork
import com.topjohnwu.magisk.extensions.get
import com.topjohnwu.magisk.extensions.subscribeK
import com.topjohnwu.magisk.model.entity.UpdateInfo
import com.topjohnwu.magisk.utils.CachedValue
import com.topjohnwu.magisk.utils.KObservableField
import com.topjohnwu.superuser.Shell
import com.topjohnwu.superuser.ShellUtils
import java.io.FileInputStream
import java.io.IOException
val isRunningAsStub get() = Info.stub != null
object Info {
val envRef = CachedValue { loadState() }
val env by envRef // Local
var remote = UpdateInfo() // Remote
var stub: DynAPK.Data? = null // Stub
var keepVerity = false
var keepEnc = false
var recovery = false
val isConnected by lazy {
KObservableField(false).also { field ->
ReactiveNetwork.observeNetworkConnectivity(get())
.subscribeK {
field.value = it.available()
}
}
}
val isNewReboot by lazy {
try {
FileInputStream("/proc/sys/kernel/random/boot_id").bufferedReader().use {
val id = it.readLine()
if (id != Config.bootId) {
Config.bootId = id
true
} else {
false
}
}
} catch (e: IOException) {
false
}
}
private fun loadState() = runCatching {
val str = ShellUtils.fastCmd("magisk -v").split(":".toRegex())[0]
val code = ShellUtils.fastCmd("magisk -V").toInt()
val hide = Shell.su("magiskhide --status").exec().isSuccess
Env(str, code, hide)
}.getOrElse { Env() }
class Env(
val magiskVersionString: String = "",
code: Int = -1,
hide: Boolean = false
) {
val magiskHide get() = Config.magiskHide
val magiskVersionCode = when (code) {
in Int.MIN_VALUE..Const.Version.MIN_VERCODE -> -1
else -> if(Shell.rootAccess()) code else -1
}
val isUnsupported = code > 0 && code < Const.Version.MIN_VERCODE
val isActive = magiskVersionCode >= 0
init {
Config.magiskHide = hide
}
}
}

View File

@@ -0,0 +1,113 @@
package com.topjohnwu.magisk.arch
import android.content.Intent
import android.os.Bundle
import android.view.KeyEvent
import android.view.View
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.content.res.use
import androidx.databinding.DataBindingUtil
import androidx.databinding.ViewDataBinding
import androidx.lifecycle.MutableLiveData
import androidx.navigation.NavController
import androidx.navigation.NavDirections
import androidx.navigation.fragment.NavHostFragment
import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.core.base.BaseActivity
import com.topjohnwu.magisk.ui.theme.Theme
abstract class BaseUIActivity<VM : BaseViewModel, Binding : ViewDataBinding> :
BaseActivity(), BaseUIComponent<VM> {
protected lateinit var binding: Binding
protected abstract val layoutRes: Int
protected open val themeRes: Int = Theme.selected.themeRes
private val navHostFragment by lazy {
supportFragmentManager.findFragmentById(navHost) as? NavHostFragment
}
private val topFragment get() = navHostFragment?.childFragmentManager?.fragments?.getOrNull(0)
protected val currentFragment get() = topFragment as? BaseUIFragment<*, *>
override val viewRoot: View get() = binding.root
open val navigation: NavController? get() = navHostFragment?.navController
open val navHost: Int = 0
open val snackbarView get() = binding.root
init {
val theme = Config.darkTheme
AppCompatDelegate.setDefaultNightMode(theme)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
currentFragment?.onActivityResult(requestCode, resultCode, data)
}
override fun onCreate(savedInstanceState: Bundle?) {
setTheme(themeRes)
super.onCreate(savedInstanceState)
startObserveEvents()
// We need to set the window background explicitly since for whatever reason it's not
// propagated upstream
obtainStyledAttributes(intArrayOf(android.R.attr.windowBackground))
.use { it.getDrawable(0) }
.also { window.setBackgroundDrawable(it) }
directionsDispatcher.observe(this) {
it?.navigate()
// we don't want the directions to be re-dispatched, so we preemptively set them to null
if (it != null) {
directionsDispatcher.value = null
}
}
}
fun setContentView() {
binding = DataBindingUtil.setContentView<Binding>(this, layoutRes).also {
it.setVariable(BR.viewModel, viewModel)
it.lifecycleOwner = this
}
ensureInsets()
}
override fun onResume() {
super.onResume()
viewModel.requestRefresh()
}
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
return currentFragment?.onKeyEvent(event) == true || super.dispatchKeyEvent(event)
}
override fun onEventDispatched(event: ViewEvent) = when(event) {
is ContextExecutor -> event(this)
is ActivityExecutor -> event(this)
else -> Unit
}
override fun onBackPressed() {
if (navigation == null || currentFragment?.onBackPressed()?.not() == true) {
super.onBackPressed()
}
}
fun NavDirections.navigate() {
navigation?.navigate(this)
}
companion object {
private val directionsDispatcher = MutableLiveData<NavDirections?>()
fun postDirections(navDirections: NavDirections) =
directionsDispatcher.postValue(navDirections)
}
}

View File

@@ -0,0 +1,63 @@
package com.topjohnwu.magisk.arch
import android.view.View
import androidx.core.graphics.Insets
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.lifecycle.LifecycleOwner
interface BaseUIComponent<VM : BaseViewModel>: LifecycleOwner {
val viewRoot: View
val viewModel: VM
fun startObserveEvents() {
viewModel.viewEvents.observe(this) {
onEventDispatched(it)
}
}
fun consumeSystemWindowInsets(insets: Insets): Insets? = null
/**
* Called for all [ViewEvent]s published by associated viewModel.
*/
fun onEventDispatched(event: ViewEvent) {}
fun ensureInsets() {
ViewCompat.setOnApplyWindowInsetsListener(viewRoot) { _, insets ->
insets.asInsets()
.also { viewModel.insets = it }
.let { consumeSystemWindowInsets(it) }
?.subtractBy(insets) ?: insets
}
if (ViewCompat.isAttachedToWindow(viewRoot)) {
ViewCompat.requestApplyInsets(viewRoot)
} else {
viewRoot.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
override fun onViewDetachedFromWindow(v: View) = Unit
override fun onViewAttachedToWindow(v: View) {
ViewCompat.requestApplyInsets(v)
}
})
}
}
private fun WindowInsetsCompat.asInsets() = Insets.of(
systemWindowInsetLeft,
systemWindowInsetTop,
systemWindowInsetRight,
systemWindowInsetBottom
)
private fun Insets.subtractBy(insets: WindowInsetsCompat) =
WindowInsetsCompat.Builder(insets).setSystemWindowInsets(
Insets.of(
insets.systemWindowInsetLeft - left,
insets.systemWindowInsetTop - top,
insets.systemWindowInsetRight - right,
insets.systemWindowInsetBottom - bottom
)
).build()
}

View File

@@ -0,0 +1,90 @@
package com.topjohnwu.magisk.arch
import android.os.Bundle
import android.view.KeyEvent
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.graphics.Insets
import androidx.databinding.DataBindingUtil
import androidx.databinding.OnRebindCallback
import androidx.databinding.ViewDataBinding
import androidx.fragment.app.Fragment
import androidx.navigation.NavDirections
import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.ktx.startAnimations
abstract class BaseUIFragment<VM : BaseViewModel, Binding : ViewDataBinding> :
Fragment(), BaseUIComponent<VM> {
protected val activity get() = requireActivity() as BaseUIActivity<*, *>
protected lateinit var binding: Binding
protected abstract val layoutRes: Int
override val viewRoot: View get() = binding.root
private val navigation get() = activity.navigation
override fun consumeSystemWindowInsets(insets: Insets) = insets
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
startObserveEvents()
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = DataBindingUtil.inflate<Binding>(inflater, layoutRes, container, false).also {
it.setVariable(BR.viewModel, viewModel)
it.lifecycleOwner = this
}
return binding.root
}
override fun onEventDispatched(event: ViewEvent) = when(event) {
is ContextExecutor -> event(requireContext())
is ActivityExecutor -> event(activity)
is FragmentExecutor -> event(this)
else -> Unit
}
open fun onKeyEvent(event: KeyEvent): Boolean {
return false
}
open fun onBackPressed(): Boolean = false
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.addOnRebindCallback(object : OnRebindCallback<Binding>() {
override fun onPreBind(binding: Binding): Boolean {
this@BaseUIFragment.onPreBind(binding)
return true
}
})
ensureInsets()
}
override fun onResume() {
super.onResume()
viewModel.requestRefresh()
}
protected open fun onPreBind(binding: Binding) {
(binding.root as? ViewGroup)?.startAnimations()
}
fun NavDirections.navigate() {
navigation?.navigate(this)
}
}
interface ReselectionTarget {
fun onReselected()
}

View File

@@ -0,0 +1,114 @@
package com.topjohnwu.magisk.arch
import android.Manifest
import android.os.Build
import androidx.annotation.CallSuper
import androidx.core.graphics.Insets
import androidx.databinding.Bindable
import androidx.databinding.Observable
import androidx.databinding.PropertyChangeRegistry
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.navigation.NavDirections
import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.base.BaseActivity
import com.topjohnwu.magisk.events.*
import com.topjohnwu.magisk.utils.ObservableHost
import com.topjohnwu.magisk.utils.set
import kotlinx.coroutines.Job
import org.koin.core.KoinComponent
abstract class BaseViewModel(
initialState: State = State.LOADING
) : ViewModel(), ObservableHost, KoinComponent {
override var callbacks: PropertyChangeRegistry? = null
enum class State {
LOADED, LOADING, LOADING_FAILED
}
@get:Bindable
val loading get() = state == State.LOADING
@get:Bindable
val loaded get() = state == State.LOADED
@get:Bindable
val loadFailed get() = state == State.LOADING_FAILED
val isConnected get() = Info.isConnected
val viewEvents: LiveData<ViewEvent> get() = _viewEvents
@get:Bindable
var insets = Insets.NONE
set(value) = set(value, field, { field = it }, BR.insets)
var state= initialState
set(value) = set(value, field, { field = it }, BR.loading, BR.loaded, BR.loadFailed)
private val _viewEvents = MutableLiveData<ViewEvent>()
private var runningJob: Job? = null
private val refreshCallback = object : Observable.OnPropertyChangedCallback() {
override fun onPropertyChanged(sender: Observable?, propertyId: Int) {
requestRefresh()
}
}
init {
isConnected.addOnPropertyChangedCallback(refreshCallback)
}
/** This should probably never be called manually, it's called manually via delegate. */
@Synchronized
fun requestRefresh() {
if (runningJob?.isActive == true) {
return
}
runningJob = refresh()
}
protected open fun refresh(): Job? = null
@CallSuper
override fun onCleared() {
isConnected.removeOnPropertyChangedCallback(refreshCallback)
super.onCleared()
}
fun withView(action: BaseActivity.() -> Unit) {
ViewActionEvent(action).publish()
}
fun withPermission(permission: String, callback: (Boolean) -> Unit) {
PermissionEvent(permission, callback).publish()
}
fun withExternalRW(callback: () -> Unit) {
withPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) {
if (!it) {
SnackbarEvent(R.string.external_rw_permission_denied).publish()
} else {
callback()
}
}
}
fun back() = BackPressEvent().publish()
fun <Event : ViewEvent> Event.publish() {
_viewEvents.postValue(this)
}
fun <Event : ViewEventWithScope> Event.publish() {
scope = viewModelScope
_viewEvents.postValue(this)
}
fun NavDirections.publish() {
_viewEvents.postValue(NavigationEvent(this))
}
}

View File

@@ -0,0 +1,48 @@
package com.topjohnwu.magisk.arch
import androidx.databinding.ViewDataBinding
import com.topjohnwu.magisk.databinding.ComparableRvItem
import com.topjohnwu.magisk.databinding.RvItem
import com.topjohnwu.magisk.utils.DiffObservableList
import com.topjohnwu.magisk.utils.FilterableDiffObservableList
import me.tatarka.bindingcollectionadapter2.BindingRecyclerViewAdapter
import me.tatarka.bindingcollectionadapter2.ItemBinding
import me.tatarka.bindingcollectionadapter2.OnItemBind
inline fun <T : ComparableRvItem<*>> diffListOf(
vararg newItems: T
) = diffListOf(newItems.toList())
inline fun <T : ComparableRvItem<*>> diffListOf(
newItems: List<T>
) = DiffObservableList(object : DiffObservableList.Callback<T> {
override fun areItemsTheSame(oldItem: T, newItem: T) = oldItem.genericItemSameAs(newItem)
override fun areContentsTheSame(oldItem: T, newItem: T) = oldItem.genericContentSameAs(newItem)
}).also { it.update(newItems) }
inline fun <T : ComparableRvItem<*>> filterableListOf(
vararg newItems: T
) = FilterableDiffObservableList(object : DiffObservableList.Callback<T> {
override fun areItemsTheSame(oldItem: T, newItem: T) = oldItem.genericItemSameAs(newItem)
override fun areContentsTheSame(oldItem: T, newItem: T) = oldItem.genericContentSameAs(newItem)
}).also { it.update(newItems.toList()) }
fun <T : RvItem> adapterOf() = object : BindingRecyclerViewAdapter<T>() {
override fun onBindBinding(
binding: ViewDataBinding,
variableId: Int,
layoutRes: Int,
position: Int,
item: T
) {
super.onBindBinding(binding, variableId, layoutRes, position, item)
item.onBindingBound(binding)
}
}
inline fun <T : RvItem> itemBindingOf(
crossinline body: (ItemBinding<*>) -> Unit = {}
) = OnItemBind<T> { itemBinding, _, item ->
item.bind(itemBinding)
body(itemBinding)
}

View File

@@ -0,0 +1,17 @@
package com.topjohnwu.magisk.arch
import android.os.Handler
import androidx.core.os.postDelayed
import com.topjohnwu.superuser.internal.UiThreadHandler
interface Queryable {
val queryDelay: Long
val queryHandler: Handler get() = UiThreadHandler.handler
fun submitQuery() {
queryHandler.postDelayed(queryDelay) { query() }
}
fun query()
}

View File

@@ -0,0 +1,27 @@
package com.topjohnwu.magisk.arch
import android.content.Context
import androidx.fragment.app.Fragment
import kotlinx.coroutines.CoroutineScope
/**
* Class for passing events from ViewModels to Activities/Fragments
* (see https://medium.com/google-developers/livedata-with-snackbar-navigation-and-other-events-the-singleliveevent-case-ac2622673150)
*/
abstract class ViewEvent
abstract class ViewEventWithScope: ViewEvent() {
lateinit var scope: CoroutineScope
}
interface ContextExecutor {
operator fun invoke(context: Context)
}
interface ActivityExecutor {
operator fun invoke(activity: BaseUIActivity<*, *>)
}
interface FragmentExecutor {
operator fun invoke(fragment: Fragment)
}

View File

@@ -1,124 +0,0 @@
package com.topjohnwu.magisk.base
import android.Manifest
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.content.res.Configuration
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
import androidx.collection.SparseArrayCompat
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.databinding.DataBindingUtil
import androidx.databinding.ViewDataBinding
import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.Config
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.base.viewmodel.BaseViewModel
import com.topjohnwu.magisk.extensions.set
import com.topjohnwu.magisk.model.events.EventHandler
import com.topjohnwu.magisk.model.permissions.PermissionRequestBuilder
import com.topjohnwu.magisk.utils.currentLocale
import com.topjohnwu.magisk.wrap
import kotlin.random.Random
typealias RequestCallback = BaseActivity<*, *>.(Int, Intent?) -> Unit
abstract class BaseActivity<ViewModel : BaseViewModel, Binding : ViewDataBinding> :
AppCompatActivity(), EventHandler {
protected lateinit var binding: Binding
protected abstract val layoutRes: Int
protected abstract val viewModel: ViewModel
protected open val themeRes: Int = R.style.MagiskTheme
protected open val snackbarView get() = binding.root
private val resultCallbacks by lazy { SparseArrayCompat<RequestCallback>() }
init {
val theme = if (Config.darkTheme) {
AppCompatDelegate.MODE_NIGHT_YES
} else {
AppCompatDelegate.MODE_NIGHT_NO
}
AppCompatDelegate.setDefaultNightMode(theme)
}
override fun applyOverrideConfiguration(config: Configuration?) {
// Force applying our preferred local
config?.setLocale(currentLocale)
super.applyOverrideConfiguration(config)
}
override fun attachBaseContext(base: Context) {
super.attachBaseContext(base.wrap(false))
}
override fun onCreate(savedInstanceState: Bundle?) {
setTheme(themeRes)
super.onCreate(savedInstanceState)
viewModel.viewEvents.observe(this, viewEventObserver)
binding = DataBindingUtil.setContentView<Binding>(this, layoutRes).apply {
setVariable(BR.viewModel, viewModel)
lifecycleOwner = this@BaseActivity
}
}
fun withPermissions(vararg permissions: String, builder: PermissionRequestBuilder.() -> Unit) {
val request = PermissionRequestBuilder().apply(builder).build()
val ungranted = permissions.filter {
ContextCompat.checkSelfPermission(this, it) != PackageManager.PERMISSION_GRANTED
}
if (ungranted.isEmpty()) {
request.onSuccess()
} else {
val requestCode = Random.nextInt(256, 512)
resultCallbacks[requestCode] = { result, _ ->
if (result > 0)
request.onSuccess()
else
request.onFailure()
}
ActivityCompat.requestPermissions(this, ungranted.toTypedArray(), requestCode)
}
}
fun withExternalRW(builder: PermissionRequestBuilder.() -> Unit) {
withPermissions(Manifest.permission.WRITE_EXTERNAL_STORAGE, builder = builder)
}
override fun onRequestPermissionsResult(
requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
var success = true
for (res in grantResults) {
if (res != PackageManager.PERMISSION_GRANTED) {
success = false
break
}
}
resultCallbacks[requestCode]?.apply {
resultCallbacks.remove(requestCode)
invoke(this@BaseActivity, if (success) 1 else -1, null)
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
resultCallbacks[requestCode]?.apply {
resultCallbacks.remove(requestCode)
invoke(this@BaseActivity, resultCode, data)
}
}
fun startActivityForResult(intent: Intent, requestCode: Int, listener: RequestCallback) {
resultCallbacks[requestCode] = listener
startActivityForResult(intent, requestCode)
}
}

View File

@@ -1,50 +0,0 @@
package com.topjohnwu.magisk.base
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.CallSuper
import androidx.databinding.DataBindingUtil
import androidx.databinding.ViewDataBinding
import androidx.fragment.app.Fragment
import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.base.viewmodel.BaseViewModel
import com.topjohnwu.magisk.model.events.EventHandler
import com.topjohnwu.magisk.model.events.ViewEvent
abstract class BaseFragment<ViewModel : BaseViewModel, Binding : ViewDataBinding> :
Fragment(), EventHandler {
protected val activity get() = requireActivity() as BaseActivity<*, *>
protected lateinit var binding: Binding
protected abstract val layoutRes: Int
protected abstract val viewModel: ViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel.viewEvents.observe(this, viewEventObserver)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = DataBindingUtil.inflate<Binding>(inflater, layoutRes, container, false).apply {
setVariable(BR.viewModel, viewModel)
lifecycleOwner = this@BaseFragment
}
return binding.root
}
@CallSuper
override fun onEventDispatched(event: ViewEvent) {
super.onEventDispatched(event)
activity.onEventDispatched(event)
}
open fun onBackPressed(): Boolean = false
}

View File

@@ -1,56 +0,0 @@
package com.topjohnwu.magisk.base
import android.annotation.SuppressLint
import android.content.SharedPreferences
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.preference.*
import androidx.recyclerview.widget.RecyclerView
import org.koin.android.ext.android.inject
abstract class BasePreferenceFragment : PreferenceFragmentCompat(),
SharedPreferences.OnSharedPreferenceChangeListener {
protected val prefs: SharedPreferences by inject()
protected val activity get() = requireActivity() as BaseActivity<*, *>
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val v = super.onCreateView(inflater, container, savedInstanceState)
prefs.registerOnSharedPreferenceChangeListener(this)
return v
}
override fun onDestroyView() {
prefs.unregisterOnSharedPreferenceChangeListener(this)
super.onDestroyView()
}
private fun setAllPreferencesToAvoidHavingExtraSpace(preference: Preference) {
preference.isIconSpaceReserved = false
if (preference is PreferenceGroup)
for (i in 0 until preference.preferenceCount)
setAllPreferencesToAvoidHavingExtraSpace(preference.getPreference(i))
}
override fun setPreferenceScreen(preferenceScreen: PreferenceScreen?) {
if (preferenceScreen != null)
setAllPreferencesToAvoidHavingExtraSpace(preferenceScreen)
super.setPreferenceScreen(preferenceScreen)
}
override fun onCreateAdapter(preferenceScreen: PreferenceScreen?): RecyclerView.Adapter<*> =
object : PreferenceGroupAdapter(preferenceScreen) {
@SuppressLint("RestrictedApi")
override fun onPreferenceHierarchyChange(preference: Preference?) {
if (preference != null)
setAllPreferencesToAvoidHavingExtraSpace(preference)
super.onPreferenceHierarchyChange(preference)
}
}
}

View File

@@ -1,35 +0,0 @@
package com.topjohnwu.magisk.base.viewmodel
import com.topjohnwu.magisk.base.BaseActivity
import com.topjohnwu.magisk.extensions.doOnSubscribeUi
import com.topjohnwu.magisk.model.events.BackPressEvent
import com.topjohnwu.magisk.model.events.PermissionEvent
import com.topjohnwu.magisk.model.events.ViewActionEvent
import com.topjohnwu.magisk.utils.KObservableField
import io.reactivex.Observable
import io.reactivex.subjects.PublishSubject
import com.topjohnwu.magisk.Info.isConnected as gIsConnected
abstract class BaseViewModel(
initialState: State = State.LOADING
) : LoadingViewModel(initialState) {
val isConnected = object : KObservableField<Boolean>(gIsConnected.value, gIsConnected) {
override fun get(): Boolean {
return gIsConnected.value
}
}
fun withView(action: BaseActivity<*, *>.() -> Unit) {
ViewActionEvent(action).publish()
}
fun withPermissions(vararg permissions: String): Observable<Boolean> {
val subject = PublishSubject.create<Boolean>()
return subject.doOnSubscribeUi { PermissionEvent(permissions.toList(), subject).publish() }
}
fun back() = BackPressEvent().publish()
}

View File

@@ -1,78 +0,0 @@
package com.topjohnwu.magisk.base.viewmodel
import androidx.databinding.Bindable
import com.topjohnwu.magisk.BR
import io.reactivex.*
abstract class LoadingViewModel(defaultState: State = State.LOADING) :
StatefulViewModel<LoadingViewModel.State>(defaultState) {
val loading @Bindable get() = state == State.LOADING
val loaded @Bindable get() = state == State.LOADED
val loadingFailed @Bindable get() = state == State.LOADING_FAILED
@Deprecated(
"Direct access is recommended since 0.2. This access method will be removed in 1.0",
ReplaceWith("state = State.LOADING", "com.topjohnwu.magisk.base.viewmodel.LoadingViewModel.State"),
DeprecationLevel.WARNING
)
fun setLoading() {
state = State.LOADING
}
@Deprecated(
"Direct access is recommended since 0.2. This access method will be removed in 1.0",
ReplaceWith("state = State.LOADED", "com.topjohnwu.magisk.base.viewmodel.LoadingViewModel.State"),
DeprecationLevel.WARNING
)
fun setLoaded() {
state = State.LOADED
}
@Deprecated(
"Direct access is recommended since 0.2. This access method will be removed in 1.0",
ReplaceWith("state = State.LOADING_FAILED", "com.topjohnwu.magisk.base.viewmodel.LoadingViewModel.State"),
DeprecationLevel.WARNING
)
fun setLoadingFailed() {
state = State.LOADING_FAILED
}
override fun notifyStateChanged() {
notifyPropertyChanged(BR.loading)
notifyPropertyChanged(BR.loaded)
notifyPropertyChanged(BR.loadingFailed)
}
enum class State {
LOADED, LOADING, LOADING_FAILED
}
//region Rx
protected fun <T> Observable<T>.applyViewModel(viewModel: LoadingViewModel, allowFinishing: Boolean = true) =
doOnSubscribe { viewModel.state = State.LOADING }
.doOnError { viewModel.state = State.LOADING_FAILED }
.doOnNext { if (allowFinishing) viewModel.state = State.LOADED }
protected fun <T> Single<T>.applyViewModel(viewModel: LoadingViewModel, allowFinishing: Boolean = true) =
doOnSubscribe { viewModel.state = State.LOADING }
.doOnError { viewModel.state = State.LOADING_FAILED }
.doOnSuccess { if (allowFinishing) viewModel.state = State.LOADED }
protected fun <T> Maybe<T>.applyViewModel(viewModel: LoadingViewModel, allowFinishing: Boolean = true) =
doOnSubscribe { viewModel.state = State.LOADING }
.doOnError { viewModel.state = State.LOADING_FAILED }
.doOnComplete { if (allowFinishing) viewModel.state = State.LOADED }
.doOnSuccess { if (allowFinishing) viewModel.state = State.LOADED }
protected fun <T> Flowable<T>.applyViewModel(viewModel: LoadingViewModel, allowFinishing: Boolean = true) =
doOnSubscribe { viewModel.state = State.LOADING }
.doOnError { viewModel.state = State.LOADING_FAILED }
.doOnNext { if (allowFinishing) viewModel.state = State.LOADED }
protected fun Completable.applyViewModel(viewModel: LoadingViewModel, allowFinishing: Boolean = true) =
doOnSubscribe { viewModel.state = State.LOADING }
.doOnError { viewModel.state = State.LOADING_FAILED }
.doOnComplete { if (allowFinishing) viewModel.state = State.LOADED }
//endregion
}

View File

@@ -1,46 +0,0 @@
package com.topjohnwu.magisk.base.viewmodel
import androidx.databinding.Observable
import androidx.databinding.PropertyChangeRegistry
import androidx.lifecycle.ViewModel
/**
* Copy of [android.databinding.BaseObservable] which extends [ViewModel]
*/
abstract class ObservableViewModel : TeanityViewModel(), Observable {
@Transient
private var callbacks: PropertyChangeRegistry? = null
@Synchronized
override fun addOnPropertyChangedCallback(callback: Observable.OnPropertyChangedCallback) {
if (callbacks == null) {
callbacks = PropertyChangeRegistry()
}
callbacks?.add(callback)
}
@Synchronized
override fun removeOnPropertyChangedCallback(callback: Observable.OnPropertyChangedCallback) {
callbacks?.remove(callback)
}
/**
* Notifies listeners that all properties of this instance have changed.
*/
@Synchronized
fun notifyChange() {
callbacks?.notifyCallbacks(this, 0, null)
}
/**
* Notifies listeners that a specific property has changed. The getter for the property
* that changes should be marked with [android.databinding.Bindable] to generate a field in
* `BR` to be used as `fieldId`.
*
* @param fieldId The generated BR id for the Bindable field.
*/
fun notifyPropertyChanged(fieldId: Int) {
callbacks?.notifyCallbacks(this, fieldId, null)
}
}

View File

@@ -1,15 +0,0 @@
package com.topjohnwu.magisk.base.viewmodel
abstract class StatefulViewModel<State : Enum<*>>(
val defaultState: State
) : ObservableViewModel() {
var state: State = defaultState
set(value) {
field = value
notifyStateChanged()
}
open fun notifyStateChanged() = Unit
}

View File

@@ -1,33 +0,0 @@
package com.topjohnwu.magisk.base.viewmodel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.topjohnwu.magisk.model.events.SimpleViewEvent
import com.topjohnwu.magisk.model.events.ViewEvent
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.disposables.Disposable
abstract class TeanityViewModel : ViewModel() {
private val disposables = CompositeDisposable()
private val _viewEvents = MutableLiveData<ViewEvent>()
val viewEvents: LiveData<ViewEvent> get() = _viewEvents
override fun onCleared() {
super.onCleared()
disposables.clear()
}
fun <Event : ViewEvent> Event.publish() {
_viewEvents.value = this
}
fun Int.publish() {
_viewEvents.value = SimpleViewEvent(this)
}
fun Disposable.add() {
disposables.add(this)
}
}

View File

@@ -1,29 +1,27 @@
package com.topjohnwu.magisk
package com.topjohnwu.magisk.core
import android.app.Activity
import android.app.Application
import android.content.Context
import android.content.res.Configuration
import android.os.Bundle
import androidx.appcompat.app.AppCompatDelegate
import androidx.multidex.MultiDex
import androidx.room.Room
import androidx.work.WorkManager
import androidx.work.impl.WorkDatabase
import androidx.work.impl.WorkDatabase_Impl
import com.topjohnwu.magisk.data.database.RepoDatabase
import com.topjohnwu.magisk.data.database.RepoDatabase_Impl
import com.topjohnwu.magisk.data.database.SuLogDatabase
import com.topjohnwu.magisk.data.database.SuLogDatabase_Impl
import com.topjohnwu.magisk.di.ActivityTracker
import com.topjohnwu.magisk.BuildConfig
import com.topjohnwu.magisk.DynAPK
import com.topjohnwu.magisk.FileProvider
import com.topjohnwu.magisk.core.su.SuCallbackHandler
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.di.koinModules
import com.topjohnwu.magisk.extensions.get
import com.topjohnwu.magisk.extensions.unwrap
import com.topjohnwu.magisk.utils.RootInit
import com.topjohnwu.magisk.utils.SuHandler
import com.topjohnwu.magisk.utils.updateConfig
import com.topjohnwu.magisk.ktx.unwrap
import com.topjohnwu.superuser.Shell
import org.koin.android.ext.koin.androidContext
import org.koin.core.context.startKoin
import timber.log.Timber
import kotlin.system.exitProcess
open class App() : Application() {
@@ -33,18 +31,18 @@ open class App() : Application() {
init {
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)
Shell.Config.setFlags(Shell.FLAG_MOUNT_MASTER or Shell.FLAG_USE_MAGISK_BUSYBOX)
Shell.Config.verboseLogging(BuildConfig.DEBUG)
Shell.Config.addInitializers(RootInit::class.java)
Shell.Config.setTimeout(2)
FileProvider.callHandler = SuHandler
Room.setFactory {
when (it) {
WorkDatabase::class.java -> WorkDatabase_Impl()
RepoDatabase::class.java -> RepoDatabase_Impl()
SuLogDatabase::class.java -> SuLogDatabase_Impl()
else -> null
}
Shell.setDefaultBuilder(Shell.Builder.create()
.setFlags(Shell.FLAG_MOUNT_MASTER)
.setInitializers(RootInit::class.java)
.setTimeout(2))
Shell.EXECUTOR = IODispatcherExecutor()
FileProvider.callHandler = SuCallbackHandler
// Always log full stack trace with Timber
Timber.plant(Timber.DebugTree())
Thread.setDefaultUncaughtExceptionHandler { _, e ->
Timber.e(e)
exitProcess(1)
}
}
@@ -52,7 +50,6 @@ open class App() : Application() {
// Basic setup
if (BuildConfig.DEBUG)
MultiDex.install(base)
Timber.plant(Timber.DebugTree())
// Some context magic
val app: Application
@@ -72,8 +69,8 @@ open class App() : Application() {
androidContext(wrapped)
modules(koinModules)
}
ResourceMgr.init(impl)
app.registerActivityLifecycleCallbacks(get<ActivityTracker>())
ResMgr.init(impl)
app.registerActivityLifecycleCallbacks(ForegroundTracker)
WorkManager.initialize(impl.wrapJob(), androidx.work.Configuration.Builder().build())
}
@@ -88,3 +85,25 @@ open class App() : Application() {
super.onConfigurationChanged(newConfig)
}
}
object ForegroundTracker : Application.ActivityLifecycleCallbacks {
@Volatile
var foreground: Activity? = null
val hasForeground get() = foreground != null
override fun onActivityResumed(activity: Activity) {
foreground = activity
}
override fun onActivityPaused(activity: Activity) {
foreground = null
}
override fun onActivityCreated(activity: Activity, bundle: Bundle?) {}
override fun onActivityStarted(activity: Activity) {}
override fun onActivityStopped(activity: Activity) {}
override fun onActivitySaveInstanceState(activity: Activity, bundle: Bundle) {}
override fun onActivityDestroyed(activity: Activity) {}
}

View File

@@ -1,19 +1,22 @@
package com.topjohnwu.magisk
package com.topjohnwu.magisk.core
import android.content.Context
import android.content.SharedPreferences
import android.os.Environment
import android.util.Xml
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.content.edit
import com.topjohnwu.magisk.data.database.SettingsDao
import com.topjohnwu.magisk.data.database.StringDao
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.defaultLocale
import com.topjohnwu.magisk.core.utils.refreshLocale
import com.topjohnwu.magisk.data.preference.PreferenceModel
import com.topjohnwu.magisk.data.repository.DBConfig
import com.topjohnwu.magisk.di.Protected
import com.topjohnwu.magisk.extensions.get
import com.topjohnwu.magisk.extensions.inject
import com.topjohnwu.magisk.model.preference.PreferenceModel
import com.topjohnwu.magisk.utils.BiometricHelper
import com.topjohnwu.magisk.utils.Utils
import com.topjohnwu.magisk.ktx.get
import com.topjohnwu.magisk.ktx.inject
import com.topjohnwu.magisk.ui.theme.Theme
import com.topjohnwu.superuser.Shell
import com.topjohnwu.superuser.io.SuFile
import com.topjohnwu.superuser.io.SuFileInputStream
@@ -44,15 +47,18 @@ object Config : PreferenceModel, DBConfig {
const val UPDATE_CHANNEL = "update_channel"
const val CUSTOM_CHANNEL = "custom_channel"
const val LOCALE = "locale"
const val DARK_THEME = "dark_theme"
const val DARK_THEME = "dark_theme_extended"
const val REPO_ORDER = "repo_order"
const val SHOW_SYSTEM_APP = "show_system"
const val DOWNLOAD_PATH = "download_path"
const val DOWNLOAD_DIR = "download_dir"
const val SAFETY = "safety_notice"
const val THEME_ORDINAL = "theme_ordinal"
const val BOOT_ID = "boot_id"
const val ASKED_HOME = "asked_home"
const val DOH = "doh"
// system state
const val MAGISKHIDE = "magiskhide"
const val COREONLY = "disable"
}
object Value {
@@ -62,7 +68,6 @@ object Config : PreferenceModel, DBConfig {
const val BETA_CHANNEL = 1
const val CUSTOM_CHANNEL = 2
const val CANARY_CHANNEL = 3
const val CANARY_DEBUG_CHANNEL = 4
// root access mode
const val ROOT_ACCESS_DISABLED = 0
@@ -98,17 +103,19 @@ object Config : PreferenceModel, DBConfig {
}
private val defaultChannel =
if (Utils.isCanary) {
if (BuildConfig.DEBUG)
Value.CANARY_DEBUG_CHANNEL
else
Value.CANARY_CHANNEL
}
else Value.DEFAULT_CHANNEL
if (BuildConfig.DEBUG)
Value.CANARY_CHANNEL
else
Value.DEFAULT_CHANNEL
@JvmStatic var keepVerity = false
@JvmStatic var keepEnc = false
@JvmStatic var recovery = false
var bootId by preference(Key.BOOT_ID, "")
var askedHome by preference(Key.ASKED_HOME, false)
var downloadPath by preference(Key.DOWNLOAD_PATH, Environment.DIRECTORY_DOWNLOADS)
var downloadDir by preference(Key.DOWNLOAD_DIR, "")
var repoOrder by preference(Key.REPO_ORDER, Value.ORDER_DATE)
var suDefaultTimeout by preferenceStrInt(Key.SU_REQUEST_TIMEOUT, 10)
@@ -116,15 +123,23 @@ object Config : PreferenceModel, DBConfig {
var suNotification by preferenceStrInt(Key.SU_NOTIFICATION, Value.NOTIFICATION_TOAST)
var updateChannel by preferenceStrInt(Key.UPDATE_CHANNEL, defaultChannel)
var darkTheme by preference(Key.DARK_THEME, true)
var safetyNotice by preference(Key.SAFETY, true)
var darkTheme by preference(Key.DARK_THEME, AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
var themeOrdinal by preference(Key.THEME_ORDINAL, Theme.Piplup.ordinal)
var suReAuth by preference(Key.SU_REAUTH, false)
var checkUpdate by preference(Key.CHECK_UPDATES, true)
var doh by preference(Key.DOH, defaultLocale.country == "CN")
var magiskHide by preference(Key.MAGISKHIDE, true)
var coreOnly by preference(Key.COREONLY, false)
var showSystemApp by preference(Key.SHOW_SYSTEM_APP, false)
var customChannelUrl by preference(Key.CUSTOM_CHANNEL, "")
var locale by preference(Key.LOCALE, "")
private var localePrefs by preference(Key.LOCALE, "")
var locale
get() = localePrefs
set(value) {
localePrefs = value
refreshLocale()
}
var rootMode by dbSettings(Key.ROOT_ACCESS, Value.ROOT_ACCESS_APPS_AND_ADB)
var suMntNamespaceMode by dbSettings(Key.SU_MNT_NS, Value.NAMESPACE_MODE_REQUESTER)
@@ -133,36 +148,32 @@ object Config : PreferenceModel, DBConfig {
var suManager by dbStrings(Key.SU_MANAGER, "", true)
var keyStoreRaw by dbStrings(Key.KEYSTORE, "", true)
// Always return a path in external storage where we can write
val downloadDirectory get() =
Utils.ensureDownloadPath(downloadPath) ?: get<Context>().getExternalFilesDir(null)!!
private const val SU_FINGERPRINT = "su_fingerprint"
fun initialize() = prefs.also {
if (it.getBoolean(SU_FINGERPRINT, false)) {
suBiometric = true
fun initialize() {
prefs.edit { parsePrefs() }
prefs.edit {
// Settings migration
if (prefs.getBoolean(SU_FINGERPRINT, false))
suBiometric = true
remove(SU_FINGERPRINT)
prefs.getString(Key.UPDATE_CHANNEL, null).also {
if (it == null)
putString(Key.UPDATE_CHANNEL, defaultChannel.toString())
else if (it.toInt() > Value.CANARY_CHANNEL)
putString(Key.UPDATE_CHANNEL, Value.CANARY_CHANNEL.toString())
}
// Write database configs
putString(Key.ROOT_ACCESS, rootMode.toString())
putString(Key.SU_MNT_NS, suMntNamespaceMode.toString())
putString(Key.SU_MULTIUSER_MODE, suMultiuserMode.toString())
putBoolean(Key.SU_BIOMETRIC, BiometricHelper.isEnabled)
}
}.edit {
parsePrefs(this)
// Legacy stuff
remove(SU_FINGERPRINT)
// Get actual state
putBoolean(Key.COREONLY, Const.MAGISK_DISABLE_FILE.exists())
// Write database configs
putString(Key.ROOT_ACCESS, rootMode.toString())
putString(Key.SU_MNT_NS, suMntNamespaceMode.toString())
putString(Key.SU_MULTIUSER_MODE, suMultiuserMode.toString())
putBoolean(Key.SU_BIOMETRIC, BiometricHelper.isEnabled)
}.also {
if (!prefs.contains(Key.UPDATE_CHANNEL))
prefs.edit().putString(Key.UPDATE_CHANNEL, defaultChannel.toString()).apply()
}
private fun parsePrefs(editor: SharedPreferences.Editor) = editor.apply {
private fun SharedPreferences.Editor.parsePrefs() {
val config = SuFile.open("/data/adb", Const.MANAGER_CONFIGS)
if (config.exists()) runCatching {
val input = SuFileInputStream(config)
@@ -215,7 +226,9 @@ object Config : PreferenceModel, DBConfig {
fun export() {
// Flush prefs to disk
prefs.edit().commit()
prefs.edit().apply {
remove(Key.ASKED_HOME)
}.commit()
val context = get<Context>(Protected)
val xml = File(
"${context.filesDir.parent}/shared_prefs",

View File

@@ -1,19 +1,18 @@
package com.topjohnwu.magisk
package com.topjohnwu.magisk.core
import android.os.Process
import java.io.File
object Const {
// Paths
const val MAGISK_PATH = "/sbin/.magisk/img"
var MAGISK_DISABLE_FILE = File("xxx")
lateinit var MAGISKTMP: String
val MAGISK_PATH get() = "$MAGISKTMP/modules"
const val TMP_FOLDER_PATH = "/dev/tmp"
const val MAGISK_LOG = "/cache/magisk.log"
// Versions
const val SNET_EXT_VER = 13
const val SNET_REVISION = "a6c47f86f10b310358afa9dbe837037dd5d561df"
const val SNET_EXT_VER = 15
const val SNET_REVISION = "d494bc726e86166913a13629e3b1336728ec5d7f"
const val BOOTCTL_REVISION = "a6c47f86f10b310358afa9dbe837037dd5d561df"
// Misc
@@ -23,10 +22,13 @@ object Const {
val USER_ID = Process.myUid() / 100000
object Version {
const val MIN_VERSION = "v18.0"
const val MIN_VERCODE = 18000
const val CONNECT_MODE = 20100
const val PROVIDER_CONNECT = 20102
const val MIN_VERSION = "v19.0"
const val MIN_VERCODE = 19000
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 isCanary() = Info.env.magiskVersionCode % 100 != 0
}
object ID {
@@ -45,14 +47,12 @@ object Const {
object Url {
const val ZIP_URL = "https://github.com/Magisk-Modules-Repo/%s/archive/master.zip"
const val PAYPAL_URL = "https://www.paypal.me/topjohnwu"
const val PATREON_URL = "https://www.patreon.com/topjohnwu"
const val TWITTER_URL = "https://twitter.com/topjohnwu"
const val XDA_THREAD = "http://forum.xda-developers.com/showthread.php?t=3432382"
const val SOURCE_CODE_URL = "https://github.com/topjohnwu/Magisk"
const val GITHUB_RAW_URL = "https://raw.githubusercontent.com/"
const val GITHUB_API_URL = "https://api.github.com/users/Magisk-Modules-Repo/"
const val GITHUB_PAGE_URL = "https://topjohnwu.github.io/magisk_files/"
}
object Key {
@@ -62,12 +62,6 @@ object Const {
const val ETAG_KEY = "ETag"
// intents
const val OPEN_SECTION = "section"
const val INTENT_SET_APP = "app_json"
const val FLASH_ACTION = "action"
const val FLASH_DATA = "additional_data"
const val DISMISS_ID = "dismiss_id"
const val BROADCAST_MANAGER_UPDATE = "manager_update"
const val BROADCAST_REBOOT = "reboot"
}
object Value {
@@ -78,4 +72,11 @@ object Const {
const val UNINSTALL = "uninstall"
}
object Nav {
const val HOME = "home"
const val SETTINGS = "settings"
const val HIDE = "hide"
const val MODULES = "modules"
const val SUPERUSER = "superuser"
}
}

View File

@@ -0,0 +1,46 @@
package com.topjohnwu.magisk.core
import android.content.ContextWrapper
import android.content.Intent
import com.topjohnwu.magisk.core.base.BaseReceiver
import com.topjohnwu.magisk.core.magiskdb.PolicyDao
import com.topjohnwu.magisk.core.su.SuCallbackHandler
import com.topjohnwu.magisk.view.Shortcuts
import com.topjohnwu.superuser.Shell
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import org.koin.core.inject
open class GeneralReceiver : BaseReceiver() {
private val policyDB: PolicyDao by inject()
private fun getPkg(intent: Intent): String {
return intent.data?.encodedSchemeSpecificPart.orEmpty()
}
override fun onReceive(context: ContextWrapper, intent: Intent?) {
intent ?: return
fun rmPolicy(pkg: String) = GlobalScope.launch {
policyDB.delete(pkg)
}
when (intent.action ?: return) {
Intent.ACTION_REBOOT -> {
SuCallbackHandler(context, intent.getStringExtra("action"), intent.extras)
}
Intent.ACTION_PACKAGE_REPLACED -> {
// This will only work pre-O
if (Config.suReAuth)
rmPolicy(getPkg(intent))
}
Intent.ACTION_PACKAGE_FULLY_REMOVED -> {
val pkg = getPkg(intent)
rmPolicy(pkg)
Shell.su("magiskhide --rm $pkg").submit()
}
Intent.ACTION_LOCALE_CHANGED -> Shortcuts.setupDynamic(context)
}
}
}

View File

@@ -1,8 +1,9 @@
@file:Suppress("DEPRECATION")
package com.topjohnwu.magisk
package com.topjohnwu.magisk.core
import android.annotation.SuppressLint
import android.app.Activity
import android.app.job.JobInfo
import android.app.job.JobScheduler
import android.app.job.JobWorkItem
@@ -14,23 +15,21 @@ import android.content.res.AssetManager
import android.content.res.Configuration
import android.content.res.Resources
import androidx.annotation.RequiresApi
import com.topjohnwu.magisk.extensions.forceGetDeclaredField
import com.topjohnwu.magisk.model.download.DownloadService
import com.topjohnwu.magisk.model.receiver.GeneralReceiver
import com.topjohnwu.magisk.model.update.UpdateCheckService
import com.topjohnwu.magisk.DynAPK
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.updateConfig
import com.topjohnwu.magisk.ktx.forceGetDeclaredField
import com.topjohnwu.magisk.ui.MainActivity
import com.topjohnwu.magisk.ui.SplashActivity
import com.topjohnwu.magisk.ui.flash.FlashActivity
import com.topjohnwu.magisk.ui.surequest.SuRequestActivity
import com.topjohnwu.magisk.utils.refreshLocale
import com.topjohnwu.magisk.utils.updateConfig
fun AssetManager.addAssetPath(path: String) {
DynAPK.addAssetPath(this, path)
}
fun Context.wrap(global: Boolean = true): Context
= if (global) GlobalResContext(this) else ResContext(this)
fun Context.wrap(global: Boolean = true): Context =
if (global) GlobalResContext(this) else ResContext(this)
fun Context.wrapJob(): Context = object : GlobalResContext(this) {
@@ -54,10 +53,14 @@ fun Class<*>.cmp(pkg: String): ComponentName {
return ComponentName(pkg, Info.stub?.classToComponent?.get(name) ?: name)
}
inline fun <reified T> Activity.redirect() = Intent(intent)
.setComponent(T::class.java.cmp(packageName))
.setFlags(0)
inline fun <reified T> Context.intent() = Intent().setComponent(T::class.java.cmp(packageName))
private open class GlobalResContext(base: Context) : ContextWrapper(base) {
open val mRes: Resources get() = ResourceMgr.resource
open val mRes: Resources get() = ResMgr.resource
override fun getResources(): Resources {
return mRes
@@ -78,22 +81,24 @@ private class ResContext(base: Context) : GlobalResContext(base) {
private fun Resources.patch(): Resources {
updateConfig()
if (isRunningAsStub)
assets.addAssetPath(ResourceMgr.resApk)
assets.addAssetPath(ResMgr.apk)
return this
}
}
object ResourceMgr {
object ResMgr {
lateinit var resource: Resources
lateinit var resApk: String
lateinit var apk: String
fun init(context: Context) {
resource = context.resources
refreshLocale()
if (isRunningAsStub) {
resApk = DynAPK.current(context).path
resource.assets.addAssetPath(resApk)
apk = DynAPK.current(context).path
resource.assets.addAssetPath(apk)
} else {
apk = context.packageResourcePath
}
}
}
@@ -130,26 +135,39 @@ private class JobSchedulerWrapper(private val base: JobScheduler) : JobScheduler
val name = service.className
val component = ComponentName(
service.packageName,
Info.stub!!.classToComponent[name] ?: name)
Info.stubChk.classToComponent[name] ?: name
)
javaClass.forceGetDeclaredField("service")?.set(this, component)
return this
}
}
object ClassMap {
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,
FlashActivity::class.java to a.f::class.java,
UpdateCheckService::class.java to a.g::class.java,
GeneralReceiver::class.java to a.h::class.java,
DownloadService::class.java to a.j::class.java,
SuRequestActivity::class.java to a.m::class.java,
ProcessPhoenix::class.java to a.r::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
// being removed when running "remove unused resources"
val shouldKeepResources = listOf(
R.string.no_info_provided,
R.string.release_notes,
R.string.invalid_update_channel,
R.string.update_available,
R.string.safetynet_api_error,
R.raw.changelog,
R.drawable.ic_device,
R.drawable.ic_hide_select_md2,
R.drawable.ic_more,
R.drawable.ic_magisk_delete
)

View File

@@ -0,0 +1,87 @@
package com.topjohnwu.magisk.core
import androidx.databinding.ObservableBoolean
import com.topjohnwu.magisk.DynAPK
import com.topjohnwu.magisk.core.model.UpdateInfo
import com.topjohnwu.magisk.core.utils.net.NetworkObserver
import com.topjohnwu.magisk.ktx.get
import com.topjohnwu.magisk.utils.CachedValue
import com.topjohnwu.superuser.Shell
import com.topjohnwu.superuser.ShellUtils.fastCmd
import com.topjohnwu.superuser.internal.UiThreadHandler
import java.io.FileInputStream
import java.io.IOException
import java.util.*
val isRunningAsStub get() = Info.stub != null
object Info {
val envRef = CachedValue { loadState() }
@JvmStatic val env by envRef
var stub: DynAPK.Data? = null
val stubChk: DynAPK.Data
get() = stub as DynAPK.Data
var remote = UpdateInfo()
// Device state
var crypto = ""
@JvmStatic var isSAR = false
@JvmStatic var isAB = false
@JvmStatic val isFDE get() = crypto == "block"
@JvmStatic var ramdisk = false
@JvmStatic var hasGMS = true
@JvmStatic var isPixel = false
@JvmStatic val cryptoText get() = crypto.capitalize(Locale.US)
val isConnected by lazy {
ObservableBoolean(false).also { field ->
NetworkObserver.observe(get()) {
UiThreadHandler.run { field.set(it) }
}
}
}
val isNewReboot by lazy {
try {
FileInputStream("/proc/sys/kernel/random/boot_id").bufferedReader().use {
val id = it.readLine()
if (id != Config.bootId) {
Config.bootId = id
true
} else {
false
}
}
} catch (e: IOException) {
false
}
}
private fun loadState() = Env(
fastCmd("magisk -v").split(":".toRegex())[0],
runCatching { fastCmd("magisk -V").toInt() }.getOrDefault(-1),
Shell.su("magiskhide --status").exec().isSuccess
)
class Env(
val magiskVersionString: String = "",
code: Int = -1,
hide: Boolean = false
) {
val magiskHide get() = Config.magiskHide
val magiskVersionCode = when (code) {
in Int.MIN_VALUE..Const.Version.MIN_VERCODE -> -1
else -> if (Shell.rootAccess()) code else -1
}
val isUnsupported = code > 0 && code < Const.Version.MIN_VERCODE
val isActive = magiskVersionCode >= 0
init {
Config.magiskHide = hide
}
}
}

View File

@@ -0,0 +1,69 @@
package com.topjohnwu.magisk.core
import android.app.Activity
import android.content.Context
import android.os.Bundle
import com.topjohnwu.magisk.BuildConfig
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.data.network.GithubRawServices
import com.topjohnwu.magisk.ktx.get
import com.topjohnwu.magisk.ui.MainActivity
import com.topjohnwu.magisk.view.Notifications
import com.topjohnwu.magisk.view.Shortcuts
import com.topjohnwu.superuser.Shell
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
open class SplashActivity : Activity() {
override fun attachBaseContext(base: Context) {
super.attachBaseContext(base.wrap())
}
override fun onCreate(savedInstanceState: Bundle?) {
setTheme(R.style.SplashTheme)
super.onCreate(savedInstanceState)
GlobalScope.launch(Dispatchers.IO) {
initAndStart()
}
}
private fun handleRepackage() {
val pkg = Config.suManager
if (Config.suManager.isNotEmpty() && packageName == BuildConfig.APPLICATION_ID) {
Config.suManager = ""
Shell.su("(pm uninstall $pkg)& >/dev/null 2>&1").exec()
}
if (pkg == packageName) {
runCatching {
// We are the manager, remove com.topjohnwu.magisk as it could be malware
packageManager.getApplicationInfo(BuildConfig.APPLICATION_ID, 0)
Shell.su("(pm uninstall ${BuildConfig.APPLICATION_ID})& >/dev/null 2>&1").exec()
}
}
}
private fun initAndStart() {
// Pre-initialize root shell
Shell.getShell()
Config.initialize()
handleRepackage()
Notifications.setup(this)
UpdateCheckService.schedule(this)
Shortcuts.setupDynamic(this)
// Pre-fetch network stuffs
get<GithubRawServices>()
DONE = true
redirect<MainActivity>().also { startActivity(it) }
finish()
}
companion object {
var DONE = false
}
}

View File

@@ -0,0 +1,53 @@
package com.topjohnwu.magisk.core
import android.content.Context
import androidx.work.*
import com.topjohnwu.magisk.BuildConfig
import com.topjohnwu.magisk.data.repository.MagiskRepository
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
class UpdateCheckService(context: Context, workerParams: WorkerParameters)
: CoroutineWorker(context, workerParams), KoinComponent {
private val magiskRepo: MagiskRepository by inject()
override suspend fun doWork(): Result {
// Make sure shell initializer was ran
withContext(Dispatchers.IO) {
Shell.getShell()
}
return magiskRepo.fetchUpdate()?.let {
if (BuildConfig.VERSION_CODE < it.app.versionCode)
Notifications.managerUpdate(applicationContext)
else if (Info.env.isActive && Info.env.magiskVersionCode < it.magisk.versionCode)
Notifications.magiskUpdate(applicationContext)
Result.success()
} ?: Result.failure()
}
companion object {
fun schedule(context: Context) {
if (Config.checkUpdate) {
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.setRequiresDeviceIdle(true)
.build()
val request = PeriodicWorkRequestBuilder<UpdateCheckService>(12, TimeUnit.HOURS)
.setConstraints(constraints)
.build()
WorkManager.getInstance(context).enqueueUniquePeriodicWork(
Const.ID.CHECK_MAGISK_UPDATE_WORKER_ID,
ExistingPeriodicWorkPolicy.REPLACE, request)
} else {
WorkManager.getInstance(context)
.cancelUniqueWork(Const.ID.CHECK_MAGISK_UPDATE_WORKER_ID)
}
}
}
}

View File

@@ -0,0 +1,104 @@
package com.topjohnwu.magisk.core.base
import android.Manifest
import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.content.res.Configuration
import android.os.Build
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.collection.SparseArrayCompat
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.utils.currentLocale
import com.topjohnwu.magisk.core.wrap
import com.topjohnwu.magisk.ktx.set
import com.topjohnwu.magisk.utils.Utils
import kotlin.random.Random
typealias RequestCallback = BaseActivity.(Int, Intent?) -> Unit
abstract class BaseActivity : AppCompatActivity() {
private val resultCallbacks by lazy { SparseArrayCompat<RequestCallback>() }
override fun applyOverrideConfiguration(config: Configuration?) {
// Force applying our preferred local
config?.setLocale(currentLocale)
super.applyOverrideConfiguration(config)
}
override fun attachBaseContext(base: Context) {
super.attachBaseContext(base.wrap(false))
}
fun withPermission(permission: String, builder: PermissionRequestBuilder.() -> Unit) {
val request = PermissionRequestBuilder().apply(builder).build()
if (permission == Manifest.permission.WRITE_EXTERNAL_STORAGE &&
Build.VERSION.SDK_INT >= 29) {
// We do not need external rw on 29+
request.onSuccess()
return
}
if (ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED) {
request.onSuccess()
} else {
val requestCode = Random.nextInt(256, 512)
resultCallbacks[requestCode] = { result, _ ->
if (result > 0)
request.onSuccess()
else
request.onFailure()
}
ActivityCompat.requestPermissions(this, arrayOf(permission), requestCode)
}
}
fun withExternalRW(builder: PermissionRequestBuilder.() -> Unit) {
withPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, builder = builder)
}
override fun onRequestPermissionsResult(
requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
var success = true
for (res in grantResults) {
if (res != PackageManager.PERMISSION_GRANTED) {
success = false
break
}
}
resultCallbacks[requestCode]?.also {
resultCallbacks.remove(requestCode)
it(this, if (success) 1 else -1, null)
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
resultCallbacks[requestCode]?.also {
resultCallbacks.remove(requestCode)
it(this, resultCode, data)
}
}
fun startActivityForResult(intent: Intent, requestCode: Int, listener: RequestCallback) {
resultCallbacks[requestCode] = listener
try {
startActivityForResult(intent, requestCode)
} catch (e: ActivityNotFoundException) {
Utils.toast(R.string.app_not_found, Toast.LENGTH_SHORT)
}
}
override fun recreate() {
startActivity(Intent().setComponent(intent.component))
finish()
}
}

View File

@@ -1,10 +1,10 @@
package com.topjohnwu.magisk.base
package com.topjohnwu.magisk.core.base
import android.content.BroadcastReceiver
import android.content.Context
import android.content.ContextWrapper
import android.content.Intent
import com.topjohnwu.magisk.wrap
import com.topjohnwu.magisk.core.wrap
import org.koin.core.KoinComponent
abstract class BaseReceiver : BroadcastReceiver(), KoinComponent {

View File

@@ -1,8 +1,8 @@
package com.topjohnwu.magisk.base
package com.topjohnwu.magisk.core.base
import android.app.Service
import android.content.Context
import com.topjohnwu.magisk.wrap
import com.topjohnwu.magisk.core.wrap
import org.koin.core.KoinComponent
abstract class BaseService : Service(), KoinComponent {

View File

@@ -1,4 +1,4 @@
package com.topjohnwu.magisk.base
package com.topjohnwu.magisk.core.base
import android.content.Context
import android.net.Network
@@ -10,7 +10,7 @@ import androidx.work.ListenableWorker
import com.google.common.util.concurrent.ListenableFuture
import java.util.*
abstract class DelegateWorker {
abstract class BaseWorkerWrapper {
private lateinit var worker: ListenableWorker

View File

@@ -1,4 +1,4 @@
package com.topjohnwu.magisk.model.permissions
package com.topjohnwu.magisk.core.base
typealias SimpleCallback = () -> Unit
typealias PermissionRationaleCallback = (List<String>) -> Unit
@@ -37,4 +37,4 @@ class PermissionRequest(
fun onFailure() = onFailureCallback()
fun onShowRationale(permissions: List<String>) = onShowRationaleCallback(permissions)
}
}

View File

@@ -1,12 +1,12 @@
package com.topjohnwu.magisk.model.entity.internal
package com.topjohnwu.magisk.core.download
import android.net.Uri
import android.os.Parcelable
import kotlinx.android.parcel.Parcelize
sealed class Configuration : Parcelable {
sealed class Action : Parcelable {
sealed class Flash : Configuration() {
sealed class Flash : Action() {
@Parcelize
object Primary : Flash()
@@ -16,7 +16,7 @@ sealed class Configuration : Parcelable {
}
sealed class APK : Configuration() {
sealed class APK : Action() {
@Parcelize
object Upgrade : APK()
@@ -26,12 +26,15 @@ sealed class Configuration : Parcelable {
}
@Parcelize
object Download : Configuration()
object Download : Action()
@Parcelize
object Uninstall : Configuration()
object Uninstall : Action()
@Parcelize
data class Patch(val fileUri: Uri) : Configuration()
object EnvFix : Action()
}
@Parcelize
data class Patch(val fileUri: Uri) : Action()
}

View File

@@ -0,0 +1,204 @@
package com.topjohnwu.magisk.core.download
import android.app.Notification
import android.content.Intent
import android.os.IBinder
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.MutableLiveData
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.ForegroundTracker
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.data.network.GithubRawServices
import com.topjohnwu.magisk.ktx.withStreams
import com.topjohnwu.magisk.view.Notifications
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import okhttp3.ResponseBody
import org.koin.android.ext.android.inject
import org.koin.core.KoinComponent
import timber.log.Timber
import java.io.IOException
import java.io.InputStream
import java.util.*
import kotlin.collections.HashMap
import kotlin.random.Random.Default.nextInt
abstract class BaseDownloader : BaseService(), KoinComponent {
private val hasNotifications get() = notifications.isNotEmpty()
private val notifications = Collections.synchronizedMap(HashMap<Int, Notification.Builder>())
private val coroutineScope = CoroutineScope(Dispatchers.IO)
val service: GithubRawServices by inject()
// -- Service overrides
override fun onBind(intent: Intent?): IBinder? = null
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
intent.getParcelableExtra<Subject>(ACTION_KEY)?.let { subject ->
update(subject.notifyID())
coroutineScope.launch {
try {
subject.startDownload()
} catch (e: IOException) {
Timber.e(e)
notifyFail(subject)
}
}
}
return START_REDELIVER_INTENT
}
override fun onTaskRemoved(rootIntent: Intent?) {
super.onTaskRemoved(rootIntent)
notifications.forEach { cancel(it.key) }
notifications.clear()
}
override fun onDestroy() {
super.onDestroy()
coroutineScope.cancel()
}
// -- Download logic
private suspend fun Subject.startDownload() {
val skip = this is Subject.Magisk && file.checkSum("MD5", magisk.md5)
if (!skip) {
val stream = service.fetchFile(url).toProgressStream(this)
when (this) {
is Subject.Module -> // Download and process on-the-fly
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)
if (ForegroundTracker.hasForeground)
onFinish(this, newId)
if (!hasNotifications)
stopSelf()
}
private fun ResponseBody.toProgressStream(subject: Subject): InputStream {
val max = contentLength()
val total = max.toFloat() / 1048576
val id = subject.notifyID()
update(id) { it.setContentTitle(subject.title) }
return ProgressInputStream(byteStream()) {
val progress = it.toFloat() / 1048576
update(id) { notification ->
if (max > 0) {
broadcast(progress / total, subject)
notification
.setProgress(max.toInt(), it.toInt(), false)
.setContentText("%.2f / %.2f MB".format(progress, total))
} else {
broadcast(-1f, subject)
notification.setContentText("%.2f MB / ??".format(progress))
}
}
}
}
// --- Notification managements
fun Subject.notifyID() = hashCode()
private fun notifyFail(subject: Subject) = lastNotify(subject.notifyID()) {
broadcast(-1f, subject)
it.setContentText(getString(R.string.download_file_error))
.setSmallIcon(android.R.drawable.stat_notify_error)
.setOngoing(false)
}
private fun notifyFinish(subject: Subject) = lastNotify(subject.notifyID()) {
broadcast(1f, subject)
it.setIntent(subject)
.setContentTitle(subject.title)
.setContentText(getString(R.string.download_complete))
.setSmallIcon(android.R.drawable.stat_sys_download_done)
.setProgress(0, 0, false)
.setOngoing(false)
.setAutoCancel(true)
}
private fun create() = Notifications.progress(this, "")
fun update(id: Int, editor: (Notification.Builder) -> Unit = {}) {
val wasEmpty = !hasNotifications
val notification = notifications.getOrPut(id, ::create).also(editor)
if (wasEmpty)
updateForeground()
else
notify(id, notification.build())
}
private fun lastNotify(
id: Int,
editor: (Notification.Builder) -> Notification.Builder? = { null }
) : Int {
val notification = remove(id)?.run(editor) ?: return -1
val newId: Int = nextInt()
notify(newId, notification.build())
return newId
}
protected fun remove(id: Int) = notifications.remove(id)
?.also { updateForeground(); cancel(id) }
?: { cancel(id); null }()
private fun notify(id: Int, notification: Notification) {
Notifications.mgr.notify(id, notification)
}
private fun cancel(id: Int) {
Notifications.mgr.cancel(id)
}
private fun updateForeground() {
if (hasNotifications) {
val (id, notification) = notifications.entries.first()
startForeground(id, notification.build())
} else {
stopForeground(false)
}
}
// --- Implement custom logic
protected abstract suspend fun onFinish(subject: Subject, id: Int)
protected abstract fun Notification.Builder.setIntent(subject: Subject): Notification.Builder
// ---
companion object : KoinComponent {
const val ACTION_KEY = "download_action"
private val progressBroadcast = MutableLiveData<Pair<Float, Subject>>()
fun observeProgress(owner: LifecycleOwner, callback: (Float, Subject) -> Unit) {
progressBroadcast.value = null
progressBroadcast.observe(owner) {
val (progress, subject) = it ?: return@observe
callback(progress, subject)
}
}
private fun broadcast(progress: Float, subject: Subject) {
progressBroadcast.postValue(progress to subject)
}
}
}

View File

@@ -0,0 +1,113 @@
package com.topjohnwu.magisk.core.download
import android.annotation.SuppressLint
import android.app.Notification
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.os.Build
import androidx.core.net.toFile
import com.topjohnwu.magisk.core.download.Action.*
import com.topjohnwu.magisk.core.download.Action.Flash.Secondary
import com.topjohnwu.magisk.core.download.Subject.*
import com.topjohnwu.magisk.core.intent
import com.topjohnwu.magisk.core.tasks.EnvFixTask
import com.topjohnwu.magisk.ui.flash.FlashFragment
import com.topjohnwu.magisk.utils.APKInstall
import kotlin.random.Random.Default.nextInt
@SuppressLint("Registered")
open class DownloadService : BaseDownloader() {
private val context get() = this
override suspend fun onFinish(subject: Subject, id: Int) = when (subject) {
is Magisk -> subject.onFinish(id)
is Module -> 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) {
is Flash -> FlashFragment.install(file, id)
else -> Unit
}
private fun Manager.onFinish(id: Int) {
remove(id)
APKInstall.install(context, file.toFile())
}
// --- Customize finish notification
override fun Notification.Builder.setIntent(subject: Subject)
= when (subject) {
is Magisk -> setIntent(subject)
is Module -> 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)
= when (subject.action) {
is Flash -> setContentIntent(FlashFragment.installIntent(context, subject.file))
else -> setContentIntent(Intent())
}
private fun Notification.Builder.setIntent(subject: Manager)
= when (subject.action) {
APK.Upgrade -> setContentIntent(APKInstall.installIntent(context, subject.file.toFile()))
else -> setContentIntent(Intent())
}
private fun Notification.Builder.setContentIntent(intent: Intent) =
setContentIntent(
PendingIntent.getActivity(context, nextInt(), intent, PendingIntent.FLAG_ONE_SHOT)
)
// ---
companion object {
private fun intent(context: Context, subject: Subject) =
context.intent<DownloadService>().putExtra(ACTION_KEY, subject)
fun pendingIntent(context: Context, subject: Subject): PendingIntent {
return if (Build.VERSION.SDK_INT >= 26) {
PendingIntent.getForegroundService(context, nextInt(),
intent(context, subject), PendingIntent.FLAG_UPDATE_CURRENT)
} else {
PendingIntent.getService(context, nextInt(),
intent(context, subject), PendingIntent.FLAG_UPDATE_CURRENT)
}
}
fun start(context: Context, subject: Subject) {
val app = context.applicationContext
if (Build.VERSION.SDK_INT >= 26) {
app.startForegroundService(intent(app, subject))
} else {
app.startService(intent(app, subject))
}
}
}
}

View File

@@ -0,0 +1,72 @@
package com.topjohnwu.magisk.core.download
import android.content.Context
import androidx.core.net.toFile
import com.topjohnwu.magisk.BuildConfig
import com.topjohnwu.magisk.DynAPK
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.download.Action.APK.Restore
import com.topjohnwu.magisk.core.download.Action.APK.Upgrade
import com.topjohnwu.magisk.core.isRunningAsStub
import com.topjohnwu.magisk.core.tasks.PatchAPK
import com.topjohnwu.magisk.ktx.relaunchApp
import com.topjohnwu.magisk.ktx.writeTo
import com.topjohnwu.superuser.Shell
import java.io.File
private fun Context.patch(apk: File) {
val patched = File(apk.parent, "patched.apk")
PatchAPK.patch(this, apk.path, patched.path, packageName, applicationInfo.nonLocalizedLabel)
apk.delete()
patched.renameTo(apk)
}
private fun BaseDownloader.notifyHide(id: Int) {
update(id) {
it.setProgress(0, 0, true)
.setContentTitle(getString(R.string.hide_manager_title))
.setContentText("")
}
}
private suspend fun BaseDownloader.upgrade(subject: Subject.Manager) {
val apk = subject.file.toFile()
val id = subject.notifyID()
if (isRunningAsStub) {
// Move to upgrade location
apk.copyTo(DynAPK.update(this), overwrite = true)
apk.delete()
if (Info.stubChk.version < subject.stub.versionCode) {
notifyHide(id)
// Also upgrade stub
service.fetchFile(subject.stub.link).byteStream().use { it.writeTo(apk) }
patch(apk)
} else {
// Simply relaunch the app
stopSelf()
relaunchApp(this)
}
} else if (packageName != BuildConfig.APPLICATION_ID) {
notifyHide(id)
patch(apk)
}
}
private fun BaseDownloader.restore(apk: File, id: Int) {
update(id) {
it.setProgress(0, 0, true)
.setProgress(0, 0, true)
.setContentTitle(getString(R.string.restore_img_msg))
.setContentText("")
}
Config.export()
Shell.su("pm install $apk && pm uninstall $packageName").exec()
}
suspend fun BaseDownloader.handleAPK(subject: Subject.Manager) =
when (subject.action) {
is Upgrade -> upgrade(subject)
is Restore -> restore(subject.file.toFile(), subject.notifyID())
}

View File

@@ -1,13 +1,14 @@
package com.topjohnwu.magisk.model.download
package com.topjohnwu.magisk.core.download
import com.topjohnwu.magisk.extensions.withStreams
import java.io.File
import android.net.Uri
import com.topjohnwu.magisk.ktx.withStreams
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream
import java.io.InputStream
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
import java.util.zip.ZipOutputStream
fun InputStream.toModule(file: File, installer: InputStream) {
fun InputStream.toModule(file: Uri, installer: InputStream) {
val input = ZipInputStream(buffered())
val output = ZipOutputStream(file.outputStream().buffered())
@@ -41,4 +42,4 @@ fun InputStream.toModule(file: File, installer: InputStream) {
entry = zin.nextEntry
}
}
}
}

View File

@@ -0,0 +1,114 @@
package com.topjohnwu.magisk.core.download
import android.content.Context
import android.net.Uri
import android.os.Parcelable
import androidx.core.net.toUri
import com.topjohnwu.magisk.core.Info
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.module.Repo
import com.topjohnwu.magisk.core.utils.MediaStoreUtils
import com.topjohnwu.magisk.ktx.cachedFile
import com.topjohnwu.magisk.ktx.get
import kotlinx.android.parcel.IgnoredOnParcel
import kotlinx.android.parcel.Parcelize
private fun cachedFile(name: String) = get<Context>().cachedFile(name).apply { delete() }.toUri()
sealed class Subject : Parcelable {
abstract val url: String
abstract val file: Uri
abstract val action: Action
abstract val title: String
@Parcelize
class Module(
val module: Repo,
override val action: Action
) : Subject() {
override val url: String get() = module.zipUrl
override val title: String get() = module.downloadFilename
@IgnoredOnParcel
override val file by lazy {
MediaStoreUtils.getFile(title).uri
}
}
@Parcelize
class Manager(
override val action: Action.APK,
private val app: ManagerJson = Info.remote.app,
val stub: StubJson = Info.remote.stub
) : Subject() {
override val title: String
get() = "MagiskManager-${app.version}(${app.versionCode})"
override val url: String
get() = app.link
@IgnoredOnParcel
override val file by lazy {
cachedFile("manager.apk")
}
}
abstract class Magisk : Subject() {
val magisk: MagiskJson = Info.remote.magisk
@Parcelize
private class Internal(
override val action: 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)
else -> throw IllegalArgumentException()
}
}
}
}

View File

@@ -0,0 +1,28 @@
package com.topjohnwu.magisk.core.magiskdb
import androidx.annotation.StringDef
abstract class BaseDao {
object Table {
const val POLICY = "policies"
const val LOG = "logs"
const val SETTINGS = "settings"
const val STRINGS = "strings"
}
@StringDef(Table.POLICY, Table.LOG, Table.SETTINGS, Table.STRINGS)
@Retention(AnnotationRetention.SOURCE)
annotation class TableStrict
@TableStrict
abstract val table: String
inline fun <reified Builder : Query.Builder> buildQuery(builder: Builder.() -> Unit = {}) =
Builder::class.java.newInstance()
.apply { table = this@BaseDao.table }
.apply(builder)
.toString()
.let { Query(it) }
}

View File

@@ -0,0 +1,77 @@
package com.topjohnwu.magisk.core.magiskdb
import android.content.Context
import android.content.pm.PackageManager
import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.model.su.SuPolicy
import com.topjohnwu.magisk.core.model.su.toMap
import com.topjohnwu.magisk.core.model.su.toPolicy
import com.topjohnwu.magisk.ktx.now
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import timber.log.Timber
import java.util.concurrent.TimeUnit
class PolicyDao(
private val context: Context
) : BaseDao() {
override val table: String = Table.POLICY
suspend fun deleteOutdated() = buildQuery<Delete> {
condition {
greaterThan("until", "0")
and {
lessThan("until", TimeUnit.MILLISECONDS.toSeconds(now).toString())
}
or {
lessThan("until", "0")
}
}
}.commit()
suspend fun delete(packageName: String) = buildQuery<Delete> {
condition {
equals("package_name", packageName)
}
}.commit()
suspend fun delete(uid: Int) = buildQuery<Delete> {
condition {
equals("uid", uid)
}
}.commit()
suspend fun fetch(uid: Int) = buildQuery<Select> {
condition {
equals("uid", uid)
}
}.query().first().toPolicyOrNull()
suspend fun update(policy: SuPolicy) = buildQuery<Replace> {
values(policy.toMap())
}.commit()
suspend fun <R: Any> fetchAll(mapper: (SuPolicy) -> R) = buildQuery<Select> {
condition {
equals("uid/100000", Const.USER_ID)
}
}.query {
it.toPolicyOrNull()?.let(mapper)
}
private fun Map<String, String>.toPolicyOrNull(): SuPolicy? {
return runCatching { toPolicy(context.packageManager) }.getOrElse {
Timber.e(it)
if (it is PackageManager.NameNotFoundException) {
val uid = getOrElse("uid") { null } ?: return null
GlobalScope.launch {
delete(uid.toInt())
}
}
null
}
}
}

View File

@@ -1,6 +1,12 @@
package com.topjohnwu.magisk.data.database.magiskdb
package com.topjohnwu.magisk.core.magiskdb
import androidx.annotation.StringDef
import com.topjohnwu.magisk.ktx.await
import com.topjohnwu.superuser.Shell
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.withContext
class Query(private val _query: String) {
val query get() = "magisk --sqlite '$_query'"
@@ -9,6 +15,24 @@ class Query(private val _query: String) {
val requestType: String
var table: String
}
suspend inline fun <R : Any> query(crossinline mapper: (Map<String, String>) -> R?): List<R> =
withContext(Dispatchers.Default) {
Shell.su(query).await().out.map { line ->
async {
line.split("\\|".toRegex())
.map { it.split("=", limit = 2) }
.filter { it.size == 2 }
.map { it[0] to it[1] }
.toMap()
.let(mapper)
}
}.awaitAll().filterNotNull()
}
suspend inline fun query() = query { it }
suspend inline fun commit() = Shell.su(query).to(null).await()
}
class Delete : Query.Builder {

View File

@@ -0,0 +1,22 @@
package com.topjohnwu.magisk.core.magiskdb
class SettingsDao : BaseDao() {
override val table = Table.SETTINGS
suspend fun delete(key: String) = buildQuery<Delete> {
condition { equals("key", key) }
}.commit()
suspend fun put(key: String, value: Int) = buildQuery<Replace> {
values("key" to key, "value" to value)
}.commit()
suspend fun fetch(key: String, default: Int = -1) = buildQuery<Select> {
fields("value")
condition { equals("key", key) }
}.query {
it["value"]?.toIntOrNull()
}.firstOrNull() ?: default
}

View File

@@ -0,0 +1,22 @@
package com.topjohnwu.magisk.core.magiskdb
class StringDao : BaseDao() {
override val table = Table.STRINGS
suspend fun delete(key: String) = buildQuery<Delete> {
condition { equals("key", key) }
}.commit()
suspend fun put(key: String, value: String) = buildQuery<Replace> {
values("key" to key, "value" to value)
}.commit()
suspend fun fetch(key: String, default: String = "") = buildQuery<Select> {
fields("value")
condition { equals("key", key) }
}.query {
it["value"]
}.firstOrNull() ?: default
}

View File

@@ -1,10 +1,10 @@
package com.topjohnwu.magisk.model.entity
package com.topjohnwu.magisk.core.model
import android.os.Parcelable
import com.squareup.moshi.JsonClass
import kotlinx.android.parcel.Parcelize
import se.ansman.kotshi.JsonSerializable
@JsonSerializable
@JsonClass(generateAdapter = true)
data class UpdateInfo(
val app: ManagerJson = ManagerJson(),
val uninstaller: UninstallerJson = UninstallerJson(),
@@ -12,12 +12,12 @@ data class UpdateInfo(
val stub: StubJson = StubJson()
)
@JsonSerializable
@JsonClass(generateAdapter = true)
data class UninstallerJson(
val link: String = ""
)
@JsonSerializable
@JsonClass(generateAdapter = true)
data class MagiskJson(
val version: String = "",
val versionCode: Int = -1,
@@ -27,7 +27,7 @@ data class MagiskJson(
)
@Parcelize
@JsonSerializable
@JsonClass(generateAdapter = true)
data class ManagerJson(
val version: String = "",
val versionCode: Int = -1,
@@ -35,8 +35,9 @@ data class ManagerJson(
val note: String = ""
) : Parcelable
@JsonSerializable
@Parcelize
@JsonClass(generateAdapter = true)
data class StubJson(
val versionCode: Int = -1,
val link: String = ""
)
) : Parcelable

View File

@@ -1,4 +1,4 @@
package com.topjohnwu.magisk.model.entity.module
package com.topjohnwu.magisk.core.model.module
abstract class BaseModule : Comparable<BaseModule> {
abstract var id: String

View File

@@ -1,9 +1,10 @@
package com.topjohnwu.magisk.model.entity.module
package com.topjohnwu.magisk.core.model.module
import androidx.annotation.WorkerThread
import com.topjohnwu.magisk.Const
import com.topjohnwu.magisk.core.Const
import com.topjohnwu.superuser.Shell
import com.topjohnwu.superuser.io.SuFile
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
class Module(path: String) : BaseModule() {
override var id: String = ""
@@ -18,12 +19,13 @@ class Module(path: String) : BaseModule() {
private val updateFile = SuFile(path, "update")
private val ruleFile = SuFile(path, "sepolicy.rule")
val updated: Boolean = updateFile.exists()
val updated: Boolean get() = updateFile.exists()
var enable: Boolean = !disableFile.exists()
var enable: Boolean
get() = !disableFile.exists()
set(enable) {
val dir = "$PERSIST/$id"
field = if (enable) {
if (enable) {
Shell.su("mkdir -p $dir", "cp -af $ruleFile $dir").submit()
disableFile.delete()
} else {
@@ -32,9 +34,10 @@ class Module(path: String) : BaseModule() {
}
}
var remove: Boolean = removeFile.exists()
var remove: Boolean
get() = removeFile.exists()
set(remove) {
field = if (remove) {
if (remove) {
Shell.su("rm -rf $PERSIST/$id").submit()
removeFile.createNewFile()
} else {
@@ -60,20 +63,15 @@ class Module(path: String) : BaseModule() {
companion object {
private const val PERSIST = "/sbin/.magisk/mirror/persist/magisk"
private val PERSIST get() = "${Const.MAGISKTMP}/mirror/persist/magisk"
@WorkerThread
fun loadModules(): List<Module> {
val moduleList = mutableListOf<Module>()
val path = SuFile(Const.MAGISK_PATH)
val modules =
path.listFiles { _, name -> name != "lost+found" && name != ".core" }.orEmpty()
for (file in modules) {
if (file.isFile) continue
val module = Module(Const.MAGISK_PATH + "/" + file.name)
moduleList.add(module)
}
return moduleList.sortedBy { it.name.toLowerCase() }
suspend fun installed() = withContext(Dispatchers.IO) {
SuFile(Const.MAGISK_PATH)
.listFiles { _, name -> name != "lost+found" && name != ".core" }
.orEmpty()
.filter { !it.isFile }
.map { Module("${Const.MAGISK_PATH}/${it.name}") }
.sortedBy { it.name.toLowerCase() }
}
}
}

View File

@@ -1,12 +1,12 @@
package com.topjohnwu.magisk.model.entity.module
package com.topjohnwu.magisk.core.model.module
import android.os.Parcelable
import androidx.room.Entity
import androidx.room.PrimaryKey
import com.topjohnwu.magisk.Const
import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.data.repository.StringRepository
import com.topjohnwu.magisk.extensions.get
import com.topjohnwu.magisk.extensions.legalFilename
import com.topjohnwu.magisk.ktx.get
import com.topjohnwu.magisk.ktx.legalFilename
import kotlinx.android.parcel.Parcelize
import java.text.DateFormat
import java.util.*
@@ -31,22 +31,15 @@ data class Repo(
val downloadFilename: String get() = "$name-$version($versionCode).zip".legalFilename()
val readme get() = stringRepo.getReadme(this)
suspend fun readme() = stringRepo.getReadme(this)
val zipUrl: String get() = Const.Url.ZIP_URL.format(id)
constructor(id: String) : this(id, "", "", "", -1, "", 0)
@Throws(IllegalRepoException::class)
fun update() {
val props = runCatching {
stringRepo.getMetadata(this).blockingGet()
.orEmpty().split("\\n".toRegex()).dropLastWhile { it.isEmpty() }
}.getOrElse {
throw IllegalRepoException("Repo [$id] module.prop download error: " + it.message)
}
props.runCatching {
private fun loadProps(props: String) {
props.split("\\n".toRegex()).dropLastWhile { it.isEmpty() }.runCatching {
parseProps(this)
}.onFailure {
throw IllegalRepoException("Repo [$id] parse error: " + it.message)
@@ -58,14 +51,14 @@ data class Repo(
}
@Throws(IllegalRepoException::class)
fun update(lastUpdate: Date) {
last_update = lastUpdate.time
update()
suspend fun update(lastUpdate: Date? = null) {
lastUpdate?.let { last_update = it.time }
loadProps(stringRepo.getMetadata(this))
}
class IllegalRepoException(message: String) : Exception(message)
companion object {
val dateFormat = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM)!!
val dateFormat = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM)
}
}

View File

@@ -1,15 +1,15 @@
package com.topjohnwu.magisk.model.entity
package com.topjohnwu.magisk.core.model.su
import androidx.room.Entity
import androidx.room.Ignore
import androidx.room.PrimaryKey
import com.topjohnwu.magisk.extensions.now
import com.topjohnwu.magisk.extensions.timeFormatTime
import com.topjohnwu.magisk.extensions.toTime
import com.topjohnwu.magisk.model.entity.MagiskPolicy.Companion.ALLOW
import com.topjohnwu.magisk.core.model.su.SuPolicy.Companion.ALLOW
import com.topjohnwu.magisk.ktx.now
import com.topjohnwu.magisk.ktx.timeFormatTime
import com.topjohnwu.magisk.ktx.toTime
@Entity(tableName = "logs")
data class MagiskLog(
data class SuLog(
val fromUid: Int,
val toUid: Int,
val fromPid: Int,
@@ -23,13 +23,8 @@ data class MagiskLog(
@Ignore val timeString = time.toTime(timeFormatTime)
}
data class WrappedMagiskLog(
val time: Long,
val items: List<MagiskLog>
)
fun MagiskPolicy.toLog(
fun SuPolicy.toLog(
toUid: Int,
fromPid: Int,
command: String
) = MagiskLog(uid, toUid, fromPid, packageName, appName, command, policy == ALLOW, now)
) = SuLog(uid, toUid, fromPid, packageName, appName, command, policy == ALLOW, now)

View File

@@ -1,20 +1,20 @@
package com.topjohnwu.magisk.model.entity
package com.topjohnwu.magisk.core.model.su
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import com.topjohnwu.magisk.extensions.getLabel
import com.topjohnwu.magisk.model.entity.MagiskPolicy.Companion.INTERACTIVE
import android.graphics.drawable.Drawable
import com.topjohnwu.magisk.core.model.su.SuPolicy.Companion.INTERACTIVE
import com.topjohnwu.magisk.ktx.getLabel
data class MagiskPolicy(
data class SuPolicy(
var uid: Int,
val packageName: String,
val appName: String,
val icon: Drawable,
var policy: Int = INTERACTIVE,
var until: Long = -1L,
val logging: Boolean = true,
val notification: Boolean = true,
val applicationInfo: ApplicationInfo
val notification: Boolean = true
) {
companion object {
@@ -25,7 +25,7 @@ data class MagiskPolicy(
}
fun MagiskPolicy.toMap() = mapOf(
fun SuPolicy.toMap() = mapOf(
"uid" to uid,
"package_name" to packageName,
"policy" to policy,
@@ -35,7 +35,7 @@ fun MagiskPolicy.toMap() = mapOf(
)
@Throws(PackageManager.NameNotFoundException::class)
fun Map<String, String>.toPolicy(pm: PackageManager): MagiskPolicy {
fun Map<String, String>.toPolicy(pm: PackageManager): SuPolicy {
val uid = get("uid")?.toIntOrNull() ?: -1
val packageName = get("package_name").orEmpty()
val info = pm.getApplicationInfo(packageName, PackageManager.GET_UNINSTALLED_PACKAGES)
@@ -43,28 +43,28 @@ fun Map<String, String>.toPolicy(pm: PackageManager): MagiskPolicy {
if (info.uid != uid)
throw PackageManager.NameNotFoundException()
return MagiskPolicy(
return SuPolicy(
uid = uid,
packageName = packageName,
appName = info.getLabel(pm),
icon = info.loadIcon(pm),
policy = get("policy")?.toIntOrNull() ?: INTERACTIVE,
until = get("until")?.toLongOrNull() ?: -1L,
logging = get("logging")?.toIntOrNull() != 0,
notification = get("notification")?.toIntOrNull() != 0,
applicationInfo = info,
appName = info.getLabel(pm)
notification = get("notification")?.toIntOrNull() != 0
)
}
@Throws(PackageManager.NameNotFoundException::class)
fun Int.toPolicy(pm: PackageManager, policy: Int = INTERACTIVE): MagiskPolicy {
fun Int.toPolicy(pm: PackageManager, policy: Int = INTERACTIVE): SuPolicy {
val pkg = pm.getPackagesForUid(this)?.firstOrNull()
?: throw PackageManager.NameNotFoundException()
val info = pm.getApplicationInfo(pkg, PackageManager.GET_UNINSTALLED_PACKAGES)
return MagiskPolicy(
return SuPolicy(
uid = info.uid,
packageName = pkg,
policy = policy,
applicationInfo = info,
appName = info.getLabel(pm)
appName = info.getLabel(pm),
icon = info.loadIcon(pm),
policy = policy
)
}

View File

@@ -1,4 +1,4 @@
package com.topjohnwu.magisk.utils
package com.topjohnwu.magisk.core.su
import android.content.Context
import android.content.Intent
@@ -6,20 +6,27 @@ import android.os.Build
import android.os.Bundle
import android.os.Process
import android.widget.Toast
import com.topjohnwu.magisk.*
import com.topjohnwu.magisk.BuildConfig
import com.topjohnwu.magisk.ProviderCallHandler
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.core.intent
import com.topjohnwu.magisk.core.model.su.SuPolicy
import com.topjohnwu.magisk.core.model.su.toLog
import com.topjohnwu.magisk.core.model.su.toPolicy
import com.topjohnwu.magisk.core.wrap
import com.topjohnwu.magisk.data.repository.LogRepository
import com.topjohnwu.magisk.extensions.get
import com.topjohnwu.magisk.extensions.startActivity
import com.topjohnwu.magisk.extensions.startActivityWithRoot
import com.topjohnwu.magisk.extensions.subscribeK
import com.topjohnwu.magisk.model.entity.MagiskPolicy
import com.topjohnwu.magisk.model.entity.toLog
import com.topjohnwu.magisk.model.entity.toPolicy
import com.topjohnwu.magisk.ktx.get
import com.topjohnwu.magisk.ktx.startActivity
import com.topjohnwu.magisk.ktx.startActivityWithRoot
import com.topjohnwu.magisk.ui.surequest.SuRequestActivity
import com.topjohnwu.magisk.utils.Utils
import com.topjohnwu.superuser.Shell
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import timber.log.Timber
object SuHandler : ProviderCallHandler {
object SuCallbackHandler : ProviderCallHandler {
const val REQUEST = "request"
const val LOG = "log"
@@ -45,20 +52,8 @@ object SuHandler : ProviderCallHandler {
}
when (action) {
REQUEST -> {
val intent = context.intent<SuRequestActivity>()
.setAction(action)
.putExtras(data)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK)
if (Build.VERSION.SDK_INT >= 29) {
// Android Q does not allow starting activity from background
intent.startActivityWithRoot()
} else {
intent.startActivity(context)
}
}
LOG -> handleLogs(context, data)
REQUEST -> handleRequest(context, data)
LOG -> handleLogging(context, data)
NOTIFY -> handleNotify(context, data)
TEST -> {
val mode = data.getInt("mode", 2)
@@ -72,13 +67,26 @@ object SuHandler : ProviderCallHandler {
private fun Any?.toInt(): Int? {
return when (this) {
is Int -> this
is Long -> this.toInt()
is Number -> this.toInt()
else -> null
}
}
private fun handleLogs(context: Context, data: Bundle) {
private fun handleRequest(context: Context, data: Bundle) {
val intent = context.intent<SuRequestActivity>()
.setAction(REQUEST)
.putExtras(data)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK)
if (Build.VERSION.SDK_INT >= 29) {
// Android Q does not allow starting activity from background
intent.startActivityWithRoot()
} else {
intent.startActivity(context)
}
}
private fun handleLogging(context: Context, data: Bundle) {
val fromUid = data["from.uid"].toInt() ?: return
if (fromUid == Process.myUid())
return
@@ -104,7 +112,9 @@ object SuHandler : ProviderCallHandler {
)
val logRepo = get<LogRepository>()
logRepo.insert(log).subscribeK(onError = { Timber.e(it) })
GlobalScope.launch {
logRepo.insert(log)
}
}
private fun handleNotify(context: Context, data: Bundle) {
@@ -122,9 +132,9 @@ object SuHandler : ProviderCallHandler {
}
}
private fun notify(context: Context, policy: MagiskPolicy) {
private fun notify(context: Context, policy: SuPolicy) {
if (policy.notification && Config.suNotification == Config.Value.NOTIFICATION_TOAST) {
val resId = if (policy.policy == MagiskPolicy.ALLOW)
val resId = if (policy.policy == SuPolicy.ALLOW)
R.string.su_allow_toast
else
R.string.su_deny_toast

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