Compare commits

...

493 Commits

Author SHA1 Message Date
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
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
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
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
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
608 changed files with 19696 additions and 24099 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

@@ -6,11 +6,18 @@
Magisk is a suite of open source tools for customizing Android, supporting devices higher than Android 4.2 (API 17). It covers the fundamental parts for Android customization: root, boot scripts, SELinux patches, AVB2.0 / dm-verity / forceencrypt removals etc.
Furthermore, Magisk provides a **Systemless Interface** to alter the system (or vendor) arbitrarily while the actual partitions stay completely intact. With its systemless nature along with several other hacks, Magisk can hide modifications from nearly any system integrity verifications used in banking apps, corporation monitoring apps, game cheat detections, and most importantly [Google's SafetyNet API](https://developer.android.com/training/safetynet/index.html).
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 almost perfectly hide modifications within userspace. Note that since 2020.3, the CTS check of [Google's SafetyNet API](https://developer.android.com/training/safetynet/index.html) will **NOT** pass.
## 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.
**Only reports using debug canary builds will be accepted.** \
Access canary builds by upgrading to either canary Magisk Manager:
- [Canary Manager (Release)](https://raw.githubusercontent.com/topjohnwu/magisk_files/canary/app-release.apk)
- [Canary Manager (Debug)](https://raw.githubusercontent.com/topjohnwu/magisk_files/canary/app-debug.apk)
For installation issues, upload both boot image and install logs. \
For Magisk issues, upload boot logcat or dmesg. \
For Magisk Manager crashes, record and upload the logcat when the crash occurs.
## Building Environment Requirements
@@ -37,27 +44,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

@@ -19,6 +19,12 @@ android {
multiDexEnabled true
versionName props['appVersion']
versionCode props['appVersionCode'] as Integer
javaCompileOptions {
annotationProcessorOptions {
arguments = ["room.incremental":"true"]
}
}
}
buildTypes {
@@ -26,7 +32,7 @@ android {
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'),
'proguard-rules.pro', 'proguard-kotlin.pro'
'proguard-rules.pro'
}
}
@@ -62,7 +68,7 @@ dependencies {
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:rxjava:2.2.18'
implementation 'io.reactivex.rxjava2:rxkotlin:2.4.0'
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
@@ -74,7 +80,7 @@ dependencies {
implementation "${bindingAdapter}:${vBAdapt}"
implementation "${bindingAdapter}-recyclerview:${vBAdapt}"
def vMarkwon = '4.2.0'
def vMarkwon = '4.2.1'
implementation "io.noties.markwon:core:${vMarkwon}"
implementation "io.noties.markwon:html:${vMarkwon}"
implementation "io.noties.markwon:image:${vMarkwon}"
@@ -89,47 +95,41 @@ dependencies {
implementation "org.koin:koin-android:${vKoin}"
implementation "org.koin:koin-androidx-viewmodel:${vKoin}"
def vRetrofit = '2.6.2'
def vRetrofit = '2.7.1'
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}"
def vOkHttp = '3.12.10'
implementation("com.squareup.okhttp3:okhttp:${vOkHttp}") {
force = true
}
implementation "com.squareup.okhttp3:logging-interceptor:${vOkHttp}"
def vMoshi = '1.9.2'
def vMoshi = '1.10.0-SNAPSHOT'
implementation "com.squareup.moshi:moshi:${vMoshi}"
kapt "com.squareup.moshi:moshi-kotlin-codegen:${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}"
def vRoom = '2.2.4'
implementation "androidx.room:room-runtime:${vRoom}"
implementation "androidx.room:room-rxjava2:${vRoom}"
kapt "androidx.room:room-compiler:${vRoom}"
def vNav = '2.1.0'
def vNav = '2.2.1'
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.biometric:biometric:1.0.1'
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta4'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0-beta01'
implementation 'androidx.browser:browser:1.2.0'
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.fragment:fragment-ktx:1.2.2'
implementation 'androidx.work:work-runtime:2.3.3'
implementation 'androidx.transition:transition:1.3.1'
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'
implementation 'androidx.core:core-ktx:1.2.0'
implementation 'com.google.android.material:material:1.2.0-alpha03'
}

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,36 @@
# 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 {
-keepclassmembers class com.topjohnwu.magisk.core.utils.SafetyNetHelper { *; }
-keep,allowobfuscation interface com.topjohnwu.magisk.core.utils.SafetyNetHelper$Callback
-keepclassmembers class * implements com.topjohnwu.magisk.core.utils.SafetyNetHelper$Callback {
void onResponse(int);
}
# Keep all fragment constructors
-keepclassmembers class * extends androidx.fragment.app.Fragment {
public <init>(...);
# Fragments
-keep,allowobfuscation class * extends androidx.fragment.app.Fragment
# 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 +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">
@@ -21,6 +20,10 @@
<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 -->

View File

@@ -1,19 +1,9 @@
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,26 @@
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.legacy.flash.FlashActivity
import com.topjohnwu.magisk.legacy.surequest.SuRequestActivity
import com.topjohnwu.magisk.ui.MainActivity
class b : MainActivity()
class c : SplashActivity()
class e : App {
constructor() : super()
constructor(o: Any) : super(o)
}
class f : FlashActivity()
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,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,26 @@
package com.topjohnwu.magisk
package com.topjohnwu.magisk.core
import android.app.Application
import android.content.Context
import android.content.res.Configuration
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.BuildConfig
import com.topjohnwu.magisk.DynAPK
import com.topjohnwu.magisk.FileProvider
import com.topjohnwu.magisk.core.su.SuCallbackHandler
import com.topjohnwu.magisk.core.utils.RootInit
import com.topjohnwu.magisk.core.utils.updateConfig
import com.topjohnwu.magisk.di.ActivityTracker
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.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() {
@@ -37,14 +34,13 @@ open class App() : Application() {
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
}
FileProvider.callHandler = SuCallbackHandler
// Always log full stack trace with Timber
Timber.plant(Timber.DebugTree())
Thread.setDefaultUncaughtExceptionHandler { _, e ->
Timber.e(e)
exitProcess(1)
}
}
@@ -52,7 +48,6 @@ open class App() : Application() {
// Basic setup
if (BuildConfig.DEBUG)
MultiDex.install(base)
Timber.plant(Timber.DebugTree())
// Some context magic
val app: Application
@@ -72,7 +67,7 @@ open class App() : Application() {
androidContext(wrapped)
modules(koinModules)
}
ResourceMgr.init(impl)
ResMgr.init(impl)
app.registerActivityLifecycleCallbacks(get<ActivityTracker>())
WorkManager.initialize(impl.wrapJob(), androidx.work.Configuration.Builder().build())
}

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.Utils
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.ui.theme.Theme
import com.topjohnwu.superuser.Shell
import com.topjohnwu.superuser.io.SuFile
import com.topjohnwu.superuser.io.SuFileInputStream
@@ -45,10 +48,15 @@ object Config : PreferenceModel, DBConfig {
const val CUSTOM_CHANNEL = "custom_channel"
const val LOCALE = "locale"
const val DARK_THEME = "dark_theme"
const val DARK_THEME_EXTENDED = "dark_theme_extended"
const val REPO_ORDER = "repo_order"
const val SHOW_SYSTEM_APP = "show_system"
const val DOWNLOAD_PATH = "download_path"
const val REDESIGN = "redesign"
const val SAFETY = "safety_notice"
const val THEME_ORDINAL = "theme_ordinal"
const val BOOT_ID = "boot_id"
const val LIST_SPAN_COUNT = "column_count"
// system state
const val MAGISKHIDE = "magiskhide"
@@ -98,13 +106,12 @@ object Config : PreferenceModel, DBConfig {
}
private val defaultChannel =
if (Utils.isCanary) {
if (isCanaryVersion) {
if (BuildConfig.DEBUG)
Value.CANARY_DEBUG_CHANNEL
else
Value.CANARY_CHANNEL
}
else Value.DEFAULT_CHANNEL
} else Value.DEFAULT_CHANNEL
var bootId by preference(Key.BOOT_ID, "")
@@ -116,12 +123,20 @@ 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 darkThemeExtended by preference(
Key.DARK_THEME_EXTENDED,
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 magiskHide by preference(Key.MAGISKHIDE, true)
@JvmStatic
var coreOnly by preference(Key.COREONLY, false)
var showSystemApp by preference(Key.SHOW_SYSTEM_APP, false)
@JvmStatic
var listSpanCount by preference(Key.LIST_SPAN_COUNT, 1)
var customChannelUrl by preference(Key.CUSTOM_CHANNEL, "")
var locale by preference(Key.LOCALE, "")
@@ -163,7 +178,9 @@ object Config : PreferenceModel, DBConfig {
}
private fun parsePrefs(editor: SharedPreferences.Editor) = editor.apply {
val config = SuFile.open("/data/adb", Const.MANAGER_CONFIGS)
val config = SuFile.open("/data/adb",
Const.MANAGER_CONFIGS
)
if (config.exists()) runCatching {
val input = SuFileInputStream(config)
val parser = Xml.newPullParser()

View File

@@ -1,4 +1,4 @@
package com.topjohnwu.magisk
package com.topjohnwu.magisk.core
import android.os.Process
import java.io.File
@@ -6,7 +6,7 @@ import java.io.File
object Const {
// Paths
const val MAGISK_PATH = "/sbin/.magisk/img"
const val MAGISK_PATH = "/sbin/.magisk/modules"
var MAGISK_DISABLE_FILE = File("xxx")
const val TMP_FOLDER_PATH = "/dev/tmp"
const val MAGISK_LOG = "/cache/magisk.log"
@@ -23,8 +23,8 @@ object Const {
val USER_ID = Process.myUid() / 100000
object Version {
const val MIN_VERSION = "v18.0"
const val MIN_VERCODE = 18000
const val MIN_VERSION = "v19.0"
const val MIN_VERCODE = 19000
const val CONNECT_MODE = 20100
const val PROVIDER_CONNECT = 20102
}
@@ -62,6 +62,7 @@ object Const {
const val ETAG_KEY = "ETag"
// intents
const val OPEN_SECTION = "section"
const val OPEN_SETTINGS = "settings"
const val INTENT_SET_APP = "app_json"
const val FLASH_ACTION = "action"
const val FLASH_DATA = "additional_data"

View File

@@ -1,19 +1,16 @@
package com.topjohnwu.magisk.model.receiver
package com.topjohnwu.magisk.core
import android.content.ContextWrapper
import android.content.Intent
import com.topjohnwu.magisk.Config
import com.topjohnwu.magisk.Const
import com.topjohnwu.magisk.Info
import com.topjohnwu.magisk.base.BaseReceiver
import com.topjohnwu.magisk.data.database.PolicyDao
import com.topjohnwu.magisk.core.base.BaseReceiver
import com.topjohnwu.magisk.core.download.DownloadService
import com.topjohnwu.magisk.core.magiskdb.PolicyDao
import com.topjohnwu.magisk.core.model.ManagerJson
import com.topjohnwu.magisk.core.su.SuCallbackHandler
import com.topjohnwu.magisk.core.view.Shortcuts
import com.topjohnwu.magisk.extensions.reboot
import com.topjohnwu.magisk.model.download.DownloadService
import com.topjohnwu.magisk.model.entity.ManagerJson
import com.topjohnwu.magisk.model.entity.internal.Configuration
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject
import com.topjohnwu.magisk.utils.SuHandler
import com.topjohnwu.magisk.view.Shortcuts
import com.topjohnwu.superuser.Shell
import org.koin.core.inject
@@ -30,7 +27,7 @@ open class GeneralReceiver : BaseReceiver() {
when (intent.action ?: return) {
Intent.ACTION_REBOOT -> {
SuHandler(context, intent.getStringExtra("action"), intent.extras)
SuCallbackHandler(context, intent.getStringExtra("action"), intent.extras)
}
Intent.ACTION_PACKAGE_REPLACED -> {
// This will only work pre-O

View File

@@ -1,6 +1,6 @@
@file:Suppress("DEPRECATION")
package com.topjohnwu.magisk
package com.topjohnwu.magisk.core
import android.annotation.SuppressLint
import android.app.job.JobInfo
@@ -14,23 +14,23 @@ import android.content.res.AssetManager
import android.content.res.Configuration
import android.content.res.Resources
import androidx.annotation.RequiresApi
import com.topjohnwu.magisk.DynAPK
import com.topjohnwu.magisk.ProcessPhoenix
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.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.legacy.flash.FlashActivity
import com.topjohnwu.magisk.legacy.surequest.SuRequestActivity
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) {
@@ -57,7 +57,7 @@ fun Class<*>.cmp(pkg: String): ComponentName {
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 +78,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,7 +132,8 @@ private class JobSchedulerWrapper(private val base: JobScheduler) : JobScheduler
val name = service.className
val component = ComponentName(
service.packageName,
Info.stub!!.classToComponent[name] ?: name)
Info.stub!!.classToComponent[name] ?: name
)
javaClass.forceGetDeclaredField("service")?.set(this, component)
return this
@@ -144,7 +147,6 @@ object ClassMap {
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,
@@ -153,3 +155,16 @@ object ClassMap {
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(
/* TODO: The following strings should be used somewhere */
R.string.no_apps_found,
R.string.no_info_provided,
R.string.release_notes,
R.string.settings_download_path_error,
R.string.invalid_update_channel,
R.string.update_available
)

View File

@@ -1,29 +1,40 @@
package com.topjohnwu.magisk
package com.topjohnwu.magisk.core
import com.github.pwittchen.reactivenetwork.library.rx2.ReactiveNetwork
import com.topjohnwu.magisk.BuildConfig
import com.topjohnwu.magisk.DynAPK
import com.topjohnwu.magisk.core.model.UpdateInfo
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 com.topjohnwu.superuser.ShellUtils.fastCmd
import java.io.FileInputStream
import java.io.IOException
val isRunningAsStub get() = Info.stub != null
val isCanaryVersion = !BuildConfig.VERSION_NAME.contains(".")
object Info {
val envRef = CachedValue { loadState() }
@JvmStatic
val env by envRef // Local
var remote = UpdateInfo() // Remote
@JvmStatic
var stub: DynAPK.Data? = null // Stub
var keepVerity = false
var keepEnc = false
var recovery = false
// Toggle-able options
@JvmStatic var keepVerity = false
@JvmStatic var keepEnc = false
@JvmStatic var recovery = false
// Immutable device state
@JvmStatic var isSAR = false
@JvmStatic var isAB = false
@JvmStatic var ramdisk = false
val isConnected by lazy {
KObservableField(false).also { field ->
@@ -50,12 +61,11 @@ object Info {
}
}
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() }
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 = "",

View File

@@ -0,0 +1,64 @@
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.core.tasks.patchDTB
import com.topjohnwu.magisk.core.utils.Utils
import com.topjohnwu.magisk.core.view.Notifications
import com.topjohnwu.magisk.core.view.Shortcuts
import com.topjohnwu.magisk.data.network.GithubRawServices
import com.topjohnwu.magisk.extensions.get
import com.topjohnwu.magisk.model.navigation.Navigation
import com.topjohnwu.superuser.Shell
open class SplashActivity : Activity() {
override fun attachBaseContext(base: Context) {
super.attachBaseContext(base.wrap())
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Shell.getShell { Shell.EXECUTOR.execute(this::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() {
Config.initialize()
handleRepackage()
Notifications.setup(this)
Utils.scheduleUpdateCheck(this)
Shortcuts.setup(this)
// Patch DTB partitions if needed
patchDTB(this)
// Pre-fetch network stuffs
get<GithubRawServices>()
DONE = true
Navigation.start(intent, this)
finish()
}
companion object {
var DONE = false
}
}

View File

@@ -1,19 +1,20 @@
package com.topjohnwu.magisk.model.update
package com.topjohnwu.magisk.core
import androidx.work.ListenableWorker
import android.content.Context
import androidx.work.Worker
import androidx.work.WorkerParameters
import com.topjohnwu.magisk.BuildConfig
import com.topjohnwu.magisk.Info
import com.topjohnwu.magisk.base.DelegateWorker
import com.topjohnwu.magisk.core.view.Notifications
import com.topjohnwu.magisk.data.repository.MagiskRepository
import com.topjohnwu.magisk.extensions.inject
import com.topjohnwu.magisk.view.Notifications
import com.topjohnwu.superuser.Shell
class UpdateCheckService : DelegateWorker() {
class UpdateCheckService(context: Context, workerParams: WorkerParameters)
: Worker(context, workerParams) {
private val magiskRepo: MagiskRepository by inject()
override fun doWork(): ListenableWorker.Result {
override fun doWork(): Result {
// Make sure shell initializer was ran
Shell.getShell()
return runCatching {
@@ -22,9 +23,9 @@ class UpdateCheckService : DelegateWorker() {
Notifications.managerUpdate(applicationContext)
else if (Info.env.magiskVersionCode < Info.remote.magisk.versionCode)
Notifications.magiskUpdate(applicationContext)
ListenableWorker.Result.success()
Result.success()
}.getOrElse {
ListenableWorker.Result.failure()
Result.failure()
}
}
}

View File

@@ -1,51 +1,26 @@
package com.topjohnwu.magisk.base
package com.topjohnwu.magisk.core.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.core.utils.currentLocale
import com.topjohnwu.magisk.core.wrap
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
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
abstract class BaseActivity : AppCompatActivity() {
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)
@@ -56,18 +31,6 @@ abstract class BaseActivity<ViewModel : BaseViewModel, Binding : ViewDataBinding
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 {
@@ -101,18 +64,18 @@ abstract class BaseActivity<ViewModel : BaseViewModel, Binding : ViewDataBinding
break
}
}
resultCallbacks[requestCode]?.apply {
resultCallbacks[requestCode]?.also {
resultCallbacks.remove(requestCode)
invoke(this@BaseActivity, if (success) 1 else -1, null)
it(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[requestCode]?.also {
resultCallbacks.remove(requestCode)
invoke(this@BaseActivity, resultCode, data)
it(this@BaseActivity, resultCode, data)
}
}
@@ -121,4 +84,9 @@ abstract class BaseActivity<ViewModel : BaseViewModel, Binding : ViewDataBinding
startActivityForResult(intent, requestCode)
}
override fun recreate() {
startActivity(intent)
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.download
package com.topjohnwu.magisk.core.download
import android.annotation.SuppressLint
import android.app.Notification
@@ -8,16 +8,19 @@ import android.content.Intent
import android.os.Build
import android.webkit.MimeTypeMap
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.intent
import com.topjohnwu.magisk.core.tasks.EnvFixTask
import com.topjohnwu.magisk.extensions.chooser
import com.topjohnwu.magisk.extensions.exists
import com.topjohnwu.magisk.extensions.provide
import com.topjohnwu.magisk.intent
import com.topjohnwu.magisk.extensions.subscribeK
import com.topjohnwu.magisk.legacy.flash.FlashActivity
import com.topjohnwu.magisk.model.entity.internal.Configuration.*
import com.topjohnwu.magisk.model.entity.internal.Configuration.Flash.Secondary
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject.*
import com.topjohnwu.magisk.ui.flash.FlashActivity
import com.topjohnwu.magisk.utils.APKInstall
import io.reactivex.Completable
import org.koin.core.get
import java.io.File
import kotlin.random.Random.Default.nextInt
@@ -43,6 +46,7 @@ open class DownloadService : RemoteFileService() {
id: Int
) = when (val conf = subject.configuration) {
Uninstall -> FlashActivity.uninstall(this, subject.file, id)
EnvFix -> { remove(id); EnvFixTask(subject.file).exec() }
is Patch -> FlashActivity.patch(this, subject.file, conf.fileUri, id)
is Flash -> FlashActivity.flash(this, subject.file, conf is Secondary, id)
else -> Unit
@@ -60,12 +64,16 @@ open class DownloadService : RemoteFileService() {
subject: Manager,
id: Int
) {
Completable.fromAction {
handleAPK(subject)
}.subscribeK {
remove(id)
when (subject.configuration) {
is APK.Upgrade -> APKInstall.install(this, subject.file)
is APK.Restore -> Unit
}
}
}
// ---
@@ -114,13 +122,15 @@ open class DownloadService : RemoteFileService() {
@Suppress("ReplaceSingleLineLet")
private fun Notification.Builder.setContentIntent(intent: Intent) =
setContentIntent(
PendingIntent.getActivity(context, nextInt(), intent, PendingIntent.FLAG_ONE_SHOT)
.let { setContentIntent(it) }
)
@Suppress("ReplaceSingleLineLet")
private fun Notification.Builder.addAction(icon: Int, title: Int, intent: Intent) =
addAction(icon, getString(title),
PendingIntent.getActivity(context, nextInt(), intent, PendingIntent.FLAG_ONE_SHOT)
.let { addAction(icon, getString(title), it) }
)
// ---

View File

@@ -1,11 +1,18 @@
package com.topjohnwu.magisk.model.download
package com.topjohnwu.magisk.core.download
import com.topjohnwu.magisk.*
import com.topjohnwu.magisk.BuildConfig
import com.topjohnwu.magisk.DynAPK
import com.topjohnwu.magisk.ProcessPhoenix
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.intent
import com.topjohnwu.magisk.core.isRunningAsStub
import com.topjohnwu.magisk.core.utils.PatchAPK
import com.topjohnwu.magisk.extensions.writeTo
import com.topjohnwu.magisk.model.entity.internal.Configuration.APK.Restore
import com.topjohnwu.magisk.model.entity.internal.Configuration.APK.Upgrade
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject
import com.topjohnwu.magisk.utils.PatchAPK
import com.topjohnwu.superuser.Shell
import java.io.File
@@ -13,14 +20,14 @@ private fun RemoteFileService.patch(apk: File, id: Int) {
if (packageName == BuildConfig.APPLICATION_ID)
return
update(id) { notification ->
notification.setProgress(0, 0, true)
update(id) {
it.setProgress(0, 0, true)
.setProgress(0, 0, true)
.setContentTitle(getString(R.string.hide_manager_title))
.setContentText("")
}
val patched = File(apk.parent, "patched.apk")
PatchAPK.patch(apk, patched, packageName, applicationInfo.nonLocalizedLabel.toString())
PatchAPK.patch(apk.path, patched.path, packageName, applicationInfo.nonLocalizedLabel)
apk.delete()
patched.renameTo(apk)
}
@@ -46,8 +53,8 @@ private fun RemoteFileService.upgrade(apk: File, id: Int) {
}
private fun RemoteFileService.restore(apk: File, id: Int) {
update(id) { notification ->
notification.setProgress(0, 0, true)
update(id) {
it.setProgress(0, 0, true)
.setProgress(0, 0, true)
.setContentTitle(getString(R.string.restore_img_msg))
.setContentText("")
@@ -58,8 +65,8 @@ private fun RemoteFileService.restore(apk: File, id: Int) {
Shell.su("pm install $apk && pm uninstall $packageName").exec()
}
fun RemoteFileService.handleAPK(subject: DownloadSubject.Manager)
= when (subject.configuration) {
fun RemoteFileService.handleAPK(subject: DownloadSubject.Manager) =
when (subject.configuration) {
is Upgrade -> upgrade(subject.file, subject.hashCode())
is Restore -> restore(subject.file, subject.hashCode())
}

View File

@@ -1,4 +1,4 @@
package com.topjohnwu.magisk.model.download
package com.topjohnwu.magisk.core.download
import com.topjohnwu.magisk.extensions.withStreams
import java.io.File

View File

@@ -1,22 +1,20 @@
package com.topjohnwu.magisk.model.download
package com.topjohnwu.magisk.core.download
import android.app.Notification
import android.content.Intent
import android.os.IBinder
import com.topjohnwu.magisk.base.BaseService
import com.topjohnwu.magisk.view.Notifications
import com.topjohnwu.magisk.core.base.BaseService
import com.topjohnwu.magisk.core.view.Notifications
import org.koin.core.KoinComponent
import java.util.*
import kotlin.collections.HashMap
import kotlin.random.Random.Default.nextInt
abstract class NotificationService : BaseService(), KoinComponent {
abstract val defaultNotification: Notification.Builder
private val hasNotifications get() = notifications.isNotEmpty()
private val notifications =
Collections.synchronizedMap(mutableMapOf<Int, Notification.Builder>())
private val notifications = Collections.synchronizedMap(HashMap<Int, Notification.Builder>())
override fun onTaskRemoved(rootIntent: Intent?) {
super.onTaskRemoved(rootIntent)
@@ -24,22 +22,23 @@ abstract class NotificationService : BaseService(), KoinComponent {
notifications.clear()
}
abstract fun createNotification(): Notification.Builder
// --
fun update(
id: Int,
body: (Notification.Builder) -> Unit = {}
) {
val notification = notifications.getOrPut(id) { defaultNotification }
notify(id, notification.also(body).build())
if (notifications.size == 1) {
val wasEmpty = notifications.isEmpty()
val notification = notifications.getOrPut(id, ::createNotification).also(body)
if (wasEmpty)
updateForeground()
}
else
notify(id, notification.build())
}
protected fun finishNotify(
protected fun lastNotify(
id: Int,
editBody: (Notification.Builder) -> Notification.Builder? = { null }
) : Int {
@@ -57,6 +56,11 @@ abstract class NotificationService : BaseService(), KoinComponent {
return newId
}
protected fun remove(id: Int) = notifications.remove(id).also {
cancel(id)
updateForeground()
}
// ---
private fun notify(id: Int, notification: Notification) {
@@ -67,17 +71,14 @@ abstract class NotificationService : BaseService(), KoinComponent {
Notifications.mgr.cancel(id)
}
protected fun remove(id: Int) = notifications.remove(id).also {
cancel(id)
updateForeground()
}
private fun updateForeground() {
if (hasNotifications)
startForeground(notifications.keys.first(), notifications.values.first().build())
else
if (hasNotifications) {
val first = notifications.entries.first()
startForeground(first.key, first.value.build())
} else {
stopForeground(true)
}
}
// --

View File

@@ -1,22 +1,26 @@
package com.topjohnwu.magisk.model.download
package com.topjohnwu.magisk.core.download
import android.app.Activity
import android.app.Notification
import android.content.Intent
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.utils.ProgressInputStream
import com.topjohnwu.magisk.core.view.Notifications
import com.topjohnwu.magisk.data.network.GithubRawServices
import com.topjohnwu.magisk.di.NullActivity
import com.topjohnwu.magisk.extensions.get
import com.topjohnwu.magisk.extensions.subscribeK
import com.topjohnwu.magisk.extensions.writeTo
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject.*
import com.topjohnwu.magisk.utils.ProgressInputStream
import com.topjohnwu.magisk.view.Notifications
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject.Magisk
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject.Module
import com.topjohnwu.superuser.ShellUtils
import io.reactivex.Completable
import okhttp3.ResponseBody
import org.koin.android.ext.android.inject
import org.koin.core.KoinComponent
import timber.log.Timber
import java.io.InputStream
@@ -24,26 +28,20 @@ abstract class RemoteFileService : NotificationService() {
val service: GithubRawServices by inject()
override val defaultNotification
get() = Notifications.progress(this, "")
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
intent?.getParcelableExtra<DownloadSubject>(ARG_URL)?.let { start(it) }
return START_REDELIVER_INTENT
}
override fun createNotification() = Notifications.progress(this, "")
// ---
private fun start(subject: DownloadSubject) = checkExisting(subject)
.onErrorResumeNext { download(subject) }
.doOnSubscribe { update(subject.hashCode()) { it.setContentTitle(subject.title) } }
.subscribeK(onError = {
Timber.e(it)
finishNotify(subject.hashCode()) { notification ->
notification.setContentText(getString(R.string.download_file_error))
.setSmallIcon(android.R.drawable.stat_notify_error)
.setOngoing(false)
}
failNotify(subject)
}) {
val newId = finishNotify(subject)
if (get<Activity>() !is NullActivity) {
@@ -53,16 +51,14 @@ abstract class RemoteFileService : NotificationService() {
private fun checkExisting(subject: DownloadSubject) = Completable.fromAction {
check(subject is Magisk) { "Download cache is disabled" }
subject.file.also {
check(it.exists() && ShellUtils.checkSum("MD5", it, subject.magisk.md5)) {
check(subject.file.exists() &&
ShellUtils.checkSum("MD5", subject.file, subject.magisk.md5)) {
"The given file does not match checksum"
}
}
}
private fun download(subject: DownloadSubject) = service.fetchFile(subject.url)
.map { it.toStream(subject.hashCode()) }
.map { it.toProgressStream(subject) }
.flatMapCompletable { stream ->
when (subject) {
is Module -> service.fetchInstaller()
@@ -70,30 +66,40 @@ abstract class RemoteFileService : NotificationService() {
.ignoreElement()
else -> Completable.fromAction { stream.writeTo(subject.file) }
}
}.doOnComplete {
if (subject is Manager)
handleAPK(subject)
}
private fun ResponseBody.toStream(id: Int): InputStream {
private fun ResponseBody.toProgressStream(subject: DownloadSubject): InputStream {
val maxRaw = contentLength()
val max = maxRaw / 1_000_000f
val id = subject.hashCode()
update(id) { it.setContentTitle(subject.title) }
return ProgressInputStream(byteStream()) {
val progress = it / 1_000_000f
update(id) { notification ->
if (maxRaw > 0) {
send(progress / max, subject)
notification
.setProgress(maxRaw.toInt(), it.toInt(), false)
.setContentText("%.2f / %.2f MB".format(progress, max))
} else {
send(-1f, subject)
notification.setContentText("%.2f MB / ??".format(progress))
}
}
}
}
private fun finishNotify(subject: DownloadSubject) = finishNotify(subject.hashCode()) {
private fun failNotify(subject: DownloadSubject) = lastNotify(subject.hashCode()) {
send(0f, subject)
it.setContentText(getString(R.string.download_file_error))
.setSmallIcon(android.R.drawable.stat_notify_error)
.setOngoing(false)
}
private fun finishNotify(subject: DownloadSubject) = lastNotify(subject.hashCode()) {
send(1f, subject)
it.addActions(subject)
.setContentText(getString(R.string.download_complete))
.setSmallIcon(android.R.drawable.stat_sys_download_done)
@@ -111,8 +117,19 @@ abstract class RemoteFileService : NotificationService() {
protected abstract fun Notification.Builder.addActions(subject: DownloadSubject)
: Notification.Builder
companion object {
companion object : KoinComponent {
const val ARG_URL = "arg_url"
private val internalProgressBroadcast = MutableLiveData<Pair<Float, DownloadSubject>>()
val progressBroadcast: LiveData<Pair<Float, DownloadSubject>> get() = internalProgressBroadcast
fun send(progress: Float, subject: DownloadSubject) {
internalProgressBroadcast.postValue(progress to subject)
}
fun reset() {
internalProgressBroadcast.value = null
}
}
}

View File

@@ -1,4 +1,4 @@
package com.topjohnwu.magisk.data.database.magiskdb
package com.topjohnwu.magisk.core.magiskdb
import androidx.annotation.StringDef
import com.topjohnwu.superuser.Shell

View File

@@ -1,16 +1,12 @@
package com.topjohnwu.magisk.data.database
package com.topjohnwu.magisk.core.magiskdb
import android.content.Context
import android.content.pm.PackageManager
import com.topjohnwu.magisk.Const
import com.topjohnwu.magisk.data.database.magiskdb.BaseDao
import com.topjohnwu.magisk.data.database.magiskdb.Delete
import com.topjohnwu.magisk.data.database.magiskdb.Replace
import com.topjohnwu.magisk.data.database.magiskdb.Select
import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.model.MagiskPolicy
import com.topjohnwu.magisk.core.model.toMap
import com.topjohnwu.magisk.core.model.toPolicy
import com.topjohnwu.magisk.extensions.now
import com.topjohnwu.magisk.model.entity.MagiskPolicy
import com.topjohnwu.magisk.model.entity.toMap
import com.topjohnwu.magisk.model.entity.toPolicy
import timber.log.Timber
import java.util.concurrent.TimeUnit

View File

@@ -1,4 +1,4 @@
package com.topjohnwu.magisk.data.database.magiskdb
package com.topjohnwu.magisk.core.magiskdb
import androidx.annotation.StringDef

View File

@@ -1,9 +1,4 @@
package com.topjohnwu.magisk.data.database
import com.topjohnwu.magisk.data.database.magiskdb.BaseDao
import com.topjohnwu.magisk.data.database.magiskdb.Delete
import com.topjohnwu.magisk.data.database.magiskdb.Replace
import com.topjohnwu.magisk.data.database.magiskdb.Select
package com.topjohnwu.magisk.core.magiskdb
class SettingsDao : BaseDao() {

View File

@@ -1,9 +1,4 @@
package com.topjohnwu.magisk.data.database
import com.topjohnwu.magisk.data.database.magiskdb.BaseDao
import com.topjohnwu.magisk.data.database.magiskdb.Delete
import com.topjohnwu.magisk.data.database.magiskdb.Replace
import com.topjohnwu.magisk.data.database.magiskdb.Select
package com.topjohnwu.magisk.core.magiskdb
class StringDao : BaseDao() {

View File

@@ -1,9 +1,9 @@
package com.topjohnwu.magisk.model.entity
package com.topjohnwu.magisk.core.model
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import com.topjohnwu.magisk.core.model.MagiskPolicy.Companion.INTERACTIVE
import com.topjohnwu.magisk.extensions.getLabel
import com.topjohnwu.magisk.model.entity.MagiskPolicy.Companion.INTERACTIVE
data class MagiskPolicy(

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,7 +35,7 @@ data class ManagerJson(
val note: String = ""
) : Parcelable
@JsonSerializable
@JsonClass(generateAdapter = true)
data class StubJson(
val versionCode: Int = -1,
val link: String = ""

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

View File

@@ -1,9 +1,9 @@
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

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,26 @@ 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.MagiskPolicy
import com.topjohnwu.magisk.core.model.toPolicy
import com.topjohnwu.magisk.core.utils.Utils
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.legacy.surequest.SuRequestActivity
import com.topjohnwu.magisk.model.entity.toLog
import com.topjohnwu.magisk.model.entity.toPolicy
import com.topjohnwu.magisk.ui.surequest.SuRequestActivity
import com.topjohnwu.superuser.Shell
import timber.log.Timber
object SuHandler : ProviderCallHandler {
object SuCallbackHandler : ProviderCallHandler {
const val REQUEST = "request"
const val LOG = "log"

View File

@@ -1,4 +1,4 @@
package com.topjohnwu.magisk.utils
package com.topjohnwu.magisk.core.su
import android.net.LocalSocket
import android.net.LocalSocketAddress

View File

@@ -0,0 +1,103 @@
package com.topjohnwu.magisk.core.su
import android.content.Intent
import android.content.pm.PackageManager
import android.os.CountDownTimer
import com.topjohnwu.magisk.BuildConfig
import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.magiskdb.PolicyDao
import com.topjohnwu.magisk.core.model.MagiskPolicy
import com.topjohnwu.magisk.core.model.toPolicy
import com.topjohnwu.magisk.extensions.now
import timber.log.Timber
import java.util.concurrent.TimeUnit
abstract class SuRequestHandler(
private val packageManager: PackageManager,
private val policyDB: PolicyDao
) {
protected var timer: CountDownTimer = object : CountDownTimer(
TimeUnit.MINUTES.toMillis(1), TimeUnit.MINUTES.toMillis(1)) {
override fun onFinish() {
respond(MagiskPolicy.DENY, 0)
}
override fun onTick(remains: Long) {}
}
set(value) {
field.cancel()
field = value
field.start()
}
protected lateinit var policy: MagiskPolicy
private val cleanupTasks = mutableListOf<() -> Unit>()
private lateinit var connector: SuConnector
abstract fun onStart()
abstract fun onRespond()
fun start(intent: Intent): Boolean {
val socketName = intent.getStringExtra("socket") ?: return false
try {
connector = object : SuConnector(socketName) {
override fun onResponse() {
out.writeInt(policy.policy)
}
}
val map = connector.readRequest()
val uid = map["uid"]?.toIntOrNull() ?: return false
policy = uid.toPolicy(packageManager)
} catch (e: Exception) {
Timber.e(e)
return false
}
// Never allow com.topjohnwu.magisk (could be malware)
if (policy.packageName == BuildConfig.APPLICATION_ID)
return false
when (Config.suAutoReponse) {
Config.Value.SU_AUTO_DENY -> {
respond(MagiskPolicy.DENY, 0)
return true
}
Config.Value.SU_AUTO_ALLOW -> {
respond(MagiskPolicy.ALLOW, 0)
return true
}
}
timer.start()
cleanupTasks.add {
timer.cancel()
}
onStart()
return true
}
private fun respond() {
connector.response()
cleanupTasks.forEach { it() }
onRespond()
}
fun respond(action: Int, time: Int) {
val until = if (time > 0)
TimeUnit.MILLISECONDS.toSeconds(now) + TimeUnit.MINUTES.toSeconds(time.toLong())
else
time.toLong()
policy.policy = action
policy.until = until
policy.uid = policy.uid % 100000 + Const.USER_ID * 100000
if (until >= 0)
policyDB.update(policy).blockingAwait()
respond()
}
}

View File

@@ -0,0 +1,37 @@
package com.topjohnwu.magisk.core.tasks
import android.content.Context
import android.content.ContextWrapper
import android.content.Intent
import android.content.IntentFilter
import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.base.BaseReceiver
import com.topjohnwu.magisk.core.view.Notifications
import com.topjohnwu.superuser.Shell
import timber.log.Timber
private const val DTB_PATCH_RESULT = "dtb_result"
private const val DTB_PATCH_ACTION = "com.topjohnwu.magisk.DTBO_PATCH"
private class DTBPatchReceiver : BaseReceiver() {
override fun onReceive(context: ContextWrapper, intent: Intent?) {
intent?.also {
val result = it.getIntExtra(DTB_PATCH_RESULT, 1)
Timber.d("result=[$result]")
if (result == 0)
Notifications.dtboPatched(context)
}
context.unregisterReceiver(this)
}
}
fun patchDTB(context: Context) {
if (Info.isNewReboot) {
val c = context.applicationContext
c.registerReceiver(DTBPatchReceiver(), IntentFilter(DTB_PATCH_ACTION))
val broadcastCmd = "am broadcast --user ${Const.USER_ID} -p ${c.packageName} " +
"-a $DTB_PATCH_ACTION --ei $DTB_PATCH_RESULT \$result"
Shell.su("mm_patch_dtb '$broadcastCmd'").submit()
}
}

View File

@@ -0,0 +1,10 @@
package com.topjohnwu.magisk.core.tasks
import androidx.annotation.MainThread
interface FlashResultListener {
@MainThread
fun onResult(success: Boolean)
}

View File

@@ -1,13 +1,13 @@
package com.topjohnwu.magisk.tasks
package com.topjohnwu.magisk.core.tasks
import android.content.Context
import android.net.Uri
import com.topjohnwu.magisk.Const
import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.utils.unzip
import com.topjohnwu.magisk.extensions.fileName
import com.topjohnwu.magisk.extensions.inject
import com.topjohnwu.magisk.extensions.readUri
import com.topjohnwu.magisk.extensions.subscribeK
import com.topjohnwu.magisk.utils.unzip
import com.topjohnwu.superuser.Shell
import io.reactivex.Single
import java.io.File
@@ -18,7 +18,7 @@ abstract class FlashZip(
private val mUri: Uri,
private val console: MutableList<String>,
private val logs: MutableList<String>
) {
) : FlashResultListener {
private val context: Context by inject()
private val installFolder = File(context.cacheDir, "flash").apply {
@@ -94,6 +94,4 @@ abstract class FlashZip(
.subscribeK(onError = { onResult(false) }) { onResult(it) }
.let { Unit } // ignores result disposable
protected abstract fun onResult(success: Boolean)
}

View File

@@ -1,10 +1,9 @@
package com.topjohnwu.magisk.model.flash
package com.topjohnwu.magisk.core.tasks
import android.content.Context
import android.net.Uri
import androidx.core.os.postDelayed
import com.topjohnwu.magisk.extensions.inject
import com.topjohnwu.magisk.tasks.FlashZip
import com.topjohnwu.superuser.Shell
import com.topjohnwu.superuser.internal.UiThreadHandler
@@ -28,16 +27,7 @@ sealed class Flashing(
console: MutableList<String>,
log: MutableList<String>,
resultListener: FlashResultListener
) : Flashing(uri, console, log, resultListener) {
override fun onResult(success: Boolean) {
if (success) {
//Utils.loadModules()
}
super.onResult(success)
}
}
) : Flashing(uri, console, log, resultListener)
class Uninstall(
uri: Uri,

View File

@@ -1,13 +1,13 @@
package com.topjohnwu.magisk.tasks
package com.topjohnwu.magisk.core.tasks
import android.content.Context
import android.net.Uri
import android.os.Build
import android.text.TextUtils
import androidx.annotation.MainThread
import androidx.annotation.WorkerThread
import com.topjohnwu.magisk.Config
import com.topjohnwu.magisk.Info
import androidx.core.net.toUri
import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.data.network.GithubRawServices
import com.topjohnwu.magisk.di.Protected
import com.topjohnwu.magisk.extensions.*
@@ -32,19 +32,19 @@ import java.nio.ByteBuffer
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
abstract class MagiskInstaller {
abstract class MagiskInstallImpl : FlashResultListener {
protected lateinit var srcBoot: String
protected lateinit var destFile: File
protected lateinit var installDir: File
protected lateinit var zipUri: Uri
private lateinit var srcBoot: String
private lateinit var destFile: File
private lateinit var zipUri: Uri
private val console: MutableList<String>
private val logs: MutableList<String>
private var tarOut: TarOutputStream? = null
private val service: GithubRawServices by inject()
private val context: Context by inject()
protected val context: Context by inject()
protected constructor() {
console = NOPList.getInstance()
@@ -60,7 +60,7 @@ abstract class MagiskInstaller {
installDir.mkdirs()
}
protected fun findImage(): Boolean {
private fun findImage(): Boolean {
srcBoot = "find_boot_image; echo \"\$BOOTIMAGE\"".fsh()
if (srcBoot.isEmpty()) {
console.add("! Unable to detect target image")
@@ -70,7 +70,7 @@ abstract class MagiskInstaller {
return true
}
protected fun findSecondaryImage(): Boolean {
private fun findSecondaryImage(): Boolean {
val slot = "echo \$SLOT".fsh()
val target = if (slot == "_a") "_b" else "_a"
console.add("- Target slot: $target")
@@ -87,7 +87,7 @@ abstract class MagiskInstaller {
return true
}
protected fun extractZip(): Boolean {
private fun extractZip(): Boolean {
val arch: String
arch = if (Build.VERSION.SDK_INT >= 21) {
val abis = listOf(*Build.SUPPORTED_ABIS)
@@ -164,7 +164,7 @@ abstract class MagiskInstaller {
FileOutputStream(extract).use { tarIn.copyTo(it) }
if (name.contains(".lz4")) {
console.add("-- Decompressing: $name")
"./magiskboot --decompress $extract".sh()
"./magiskboot decompress $extract".sh()
}
} else if (entry.name.contains("vbmeta.img")) {
vbmeta = true
@@ -189,9 +189,9 @@ abstract class MagiskInstaller {
srcBoot = recovery.path
// Repack boot image to prevent restore
arrayOf(
"./magiskboot --unpack boot.img",
"./magiskboot --repack boot.img",
"./magiskboot --cleanup",
"./magiskboot unpack boot.img",
"./magiskboot repack boot.img",
"./magiskboot cleanup",
"mv new-boot.img boot.img").sh()
SuFileInputStream(boot).use {
tarOut.putNextEntry(newEntry("boot.img", boot.length()))
@@ -208,7 +208,7 @@ abstract class MagiskInstaller {
}
}
protected fun handleFile(uri: Uri): Boolean {
private fun handleFile(uri: Uri): Boolean {
try {
context.readUri(uri).buffered().use {
it.mark(500)
@@ -238,7 +238,7 @@ abstract class MagiskInstaller {
return true
}
protected fun patchBoot(): Boolean {
private fun patchBoot(): Boolean {
var isSigned = false
try {
SuFileInputStream(srcBoot).use {
@@ -259,7 +259,7 @@ abstract class MagiskInstaller {
}
val job = Shell.sh(
"./magiskboot --cleanup",
"./magiskboot cleanup",
"mv bin/busybox busybox",
"rm -rf magisk.apk bin boot.img update-binary",
"cd /")
@@ -284,7 +284,7 @@ abstract class MagiskInstaller {
return true
}
protected fun flashBoot(): Boolean {
private fun flashBoot(): Boolean {
if (!"direct_install $installDir $srcBoot".sh().isSuccess)
return false
arrayOf(
@@ -294,7 +294,7 @@ abstract class MagiskInstaller {
return true
}
protected fun storeBoot(): Boolean {
private fun storeBoot(): Boolean {
val patched = SuFile.open(installDir, "new-boot.img")
try {
val os = tarOut?.let {
@@ -320,7 +320,7 @@ abstract class MagiskInstaller {
return true
}
protected fun postOTA(): Boolean {
private fun postOTA(): Boolean {
val bootctl = SuFile("/data/adb/bootctl")
try {
withStreams(service.fetchBootctl().blockingGet().byteStream(), bootctl.suOutputStream()) {
@@ -345,12 +345,24 @@ abstract class MagiskInstaller {
private fun String.fsh() = ShellUtils.fastCmd(this)
private fun Array<String>.fsh() = ShellUtils.fastCmd(*this)
protected fun doPatchFile(patchFile: Uri) =
extractZip() && handleFile(patchFile) && patchBoot() && storeBoot()
protected fun direct() = findImage() && extractZip() && patchBoot() && flashBoot()
protected fun secondSlot() =
findSecondaryImage() && extractZip() && patchBoot() && flashBoot() && postOTA()
protected fun fixEnv(zip: File): Boolean {
installDir = SuFile("/data/adb/magisk")
Shell.su("rm -rf /data/adb/magisk/*").exec()
zipUri = zip.toUri()
return extractZip() && Shell.su("fix_env").exec().isSuccess
}
@WorkerThread
protected abstract fun operations(): Boolean
@MainThread
protected abstract fun onResult(success: Boolean)
fun exec() {
Single.fromCallable { operations() }.subscribeK { onResult(it) }
}

View File

@@ -0,0 +1,77 @@
package com.topjohnwu.magisk.core.tasks
import android.content.Intent
import android.net.Uri
import android.widget.Toast
import androidx.core.os.postDelayed
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.utils.Utils
import com.topjohnwu.magisk.extensions.reboot
import com.topjohnwu.magisk.model.events.dialog.EnvFixDialog
import com.topjohnwu.superuser.Shell
import com.topjohnwu.superuser.internal.UiThreadHandler
import java.io.File
sealed class MagiskInstaller(
file: Uri,
private val console: MutableList<String>,
logs: MutableList<String>,
private val resultListener: FlashResultListener
) : MagiskInstallImpl(file, console, logs) {
override fun onResult(success: Boolean) {
if (success) {
console.add("- All done!")
} else {
Shell.sh("rm -rf $installDir").submit()
console.add("! Installation failed")
}
resultListener.onResult(success)
}
class Patch(
file: Uri,
private val uri: Uri,
console: MutableList<String>,
logs: MutableList<String>,
resultListener: FlashResultListener
) : MagiskInstaller(file, console, logs, resultListener) {
override fun operations() = doPatchFile(uri)
}
class SecondSlot(
file: Uri,
console: MutableList<String>,
logs: MutableList<String>,
resultListener: FlashResultListener
) : MagiskInstaller(file, console, logs, resultListener) {
override fun operations() = secondSlot()
}
class Direct(
file: Uri,
console: MutableList<String>,
logs: MutableList<String>,
resultListener: FlashResultListener
) : MagiskInstaller(file, console, logs, resultListener) {
override fun operations() = direct()
}
}
class EnvFixTask(
private val zip: File
) : MagiskInstallImpl() {
override fun operations() = fixEnv(zip)
override fun onResult(success: Boolean) {
LocalBroadcastManager.getInstance(context).sendBroadcast(Intent(EnvFixDialog.DISMISS))
Utils.toast(
if (success) R.string.reboot_delay_toast else R.string.setup_fail,
Toast.LENGTH_LONG
)
if (success)
UiThreadHandler.handler.postDelayed(5000) { reboot() }
}
}

View File

@@ -0,0 +1,101 @@
package com.topjohnwu.magisk.core.tasks
import com.squareup.moshi.JsonClass
import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.model.module.Repo
import com.topjohnwu.magisk.data.database.RepoDao
import com.topjohnwu.magisk.data.network.GithubApiServices
import io.reactivex.Completable
import io.reactivex.Flowable
import io.reactivex.rxkotlin.toFlowable
import io.reactivex.schedulers.Schedulers
import timber.log.Timber
import java.net.HttpURLConnection
import java.text.SimpleDateFormat
import java.util.*
import kotlin.collections.HashSet
class RepoUpdater(
private val api: GithubApiServices,
private val repoDB: RepoDao
) {
private fun loadRepos(repos: List<GithubRepoInfo>, cached: MutableSet<String>) =
repos.toFlowable().parallel().runOn(Schedulers.io()).map {
// Skip submission
if (it.id == "submission")
return@map
val repo = repoDB.getRepo(it.id)?.apply { cached.remove(it.id) } ?: Repo(it.id)
repo.runCatching {
update(it.pushDate)
repoDB.addRepo(this)
}.getOrElse(Timber::e)
}.sequential()
private fun loadPage(
cached: MutableSet<String>,
page: Int = 1,
etag: String = ""
): Flowable<Unit> = api.fetchRepos(page, etag).flatMap {
it.error()?.also { throw it }
it.response()?.run {
if (code() == HttpURLConnection.HTTP_NOT_MODIFIED)
return@run Flowable.error<Unit>(CachedException())
if (page == 1)
repoDB.etagKey = headers()[Const.Key.ETAG_KEY].orEmpty().trimEtag()
val flow = loadRepos(body()!!, cached)
if (headers()[Const.Key.LINK_KEY].orEmpty().contains("next")) {
flow.mergeWith(loadPage(cached, page + 1))
} else {
flow
}
}
}
private fun forcedReload(cached: MutableSet<String>) =
cached.toFlowable().parallel().runOn(Schedulers.io()).map {
runCatching {
Repo(it).update()
}.getOrElse(Timber::e)
}.sequential()
private fun String.trimEtag() = substring(indexOf('\"'), lastIndexOf('\"') + 1)
@Suppress("RedundantLambdaArrow")
operator fun invoke(forced: Boolean) : Completable {
return Flowable
.fromCallable { Collections.synchronizedSet(HashSet(repoDB.repoIDList)) }
.flatMap { cached ->
loadPage(cached, etag = repoDB.etagKey).doOnComplete {
repoDB.removeRepos(cached)
}.onErrorResumeNext { it: Throwable ->
if (it is CachedException) {
if (forced)
return@onErrorResumeNext forcedReload(cached)
} else {
Timber.e(it)
}
Flowable.empty()
}
}.ignoreElements()
}
class CachedException : Exception()
}
private val dateFormat: SimpleDateFormat =
SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US).apply {
timeZone = TimeZone.getTimeZone("UTC")
}
@JsonClass(generateAdapter = true)
data class GithubRepoInfo(
val name: String,
val pushed_at: String
) {
val id get() = name
@Transient
val pushDate = dateFormat.parse(pushed_at)!!
}

View File

@@ -1,11 +1,11 @@
package com.topjohnwu.magisk.utils
package com.topjohnwu.magisk.core.utils
import androidx.biometric.BiometricManager
import androidx.biometric.BiometricPrompt
import androidx.core.content.ContextCompat
import androidx.fragment.app.FragmentActivity
import com.topjohnwu.magisk.Config
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.Config
import org.koin.core.KoinComponent
import org.koin.core.get

View File

@@ -1,21 +1,17 @@
package com.topjohnwu.magisk.utils
package com.topjohnwu.magisk.core.utils
import android.content.Context
import android.content.pm.PackageManager
import android.util.Base64
import android.util.Base64OutputStream
import com.topjohnwu.magisk.Config
import com.topjohnwu.magisk.di.koinModules
import com.topjohnwu.magisk.utils.PatchAPK.ALPHANUM
import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.core.utils.PatchAPK.ALPHANUM
import com.topjohnwu.signing.CryptoUtils.readCertificate
import com.topjohnwu.signing.CryptoUtils.readPrivateKey
import com.topjohnwu.superuser.internal.InternalUtils
import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder
import org.koin.core.context.GlobalContext
import org.koin.core.context.startKoin
import timber.log.Timber
import java.io.ByteArrayOutputStream
import java.math.BigInteger
import java.security.KeyPairGenerator
@@ -33,7 +29,7 @@ private interface CertKeyProvider {
}
@Suppress("DEPRECATION")
class Keygen: CertKeyProvider {
class Keygen(context: Context) : CertKeyProvider {
companion object {
private const val ALIAS = "magisk"
@@ -50,10 +46,14 @@ class Keygen: CertKeyProvider {
private val provider: CertKeyProvider
inner class KeyStoreProvider : CertKeyProvider {
inner class KeyStoreProvider :
CertKeyProvider {
private val ks by lazy { init() }
override val cert by lazy { ks.getCertificate(ALIAS) as X509Certificate }
override val key by lazy { ks.getKey(ALIAS, PASSWORD) as PrivateKey }
override val key by lazy { ks.getKey(
ALIAS,
PASSWORD
) as PrivateKey }
}
class TestProvider : CertKeyProvider {
@@ -66,9 +66,6 @@ class Keygen: CertKeyProvider {
}
init {
// This object could possibly be accessed from an external app
// Get context from reflection into Android's framework
val context = InternalUtils.getContext()
val pm = context.packageManager
val info = pm.getPackageInfo(context.packageName, PackageManager.GET_SIGNATURES)
val sig = info.signatures[0]
@@ -100,21 +97,17 @@ class Keygen: CertKeyProvider {
}
private fun init(): KeyStore {
GlobalContext.getOrNull() ?: {
// Invoked externally, do some basic initialization
startKoin {
modules(koinModules)
}
Timber.plant(Timber.DebugTree())
}()
val raw = Config.keyStoreRaw
val ks = KeyStore.getInstance("PKCS12")
if (raw.isEmpty()) {
ks.load(null)
} else {
GZIPInputStream(Base64.decode(raw, BASE64_FLAG).inputStream()).use {
ks.load(it, PASSWORD)
GZIPInputStream(Base64.decode(raw,
BASE64_FLAG
).inputStream()).use {
ks.load(it,
PASSWORD
)
}
}
@@ -127,14 +120,20 @@ class Keygen: CertKeyProvider {
val dname = X500Name("CN=${randomString()}")
val builder = JcaX509v3CertificateBuilder(dname, BigInteger(160, Random()),
start.time, end.time, dname, kp.public)
val signer = JcaContentSignerBuilder("SHA256WithRSA").build(kp.private)
val signer = JcaContentSignerBuilder("SHA1WithRSA").build(kp.private)
val cert = JcaX509CertificateConverter().getCertificate(builder.build(signer))
// Store them into keystore
ks.setKeyEntry(ALIAS, kp.private, PASSWORD, arrayOf(cert))
ks.setKeyEntry(
ALIAS, kp.private,
PASSWORD, arrayOf(cert))
val bytes = ByteArrayOutputStream()
GZIPOutputStream(Base64OutputStream(bytes, BASE64_FLAG)).use {
ks.store(it, PASSWORD)
GZIPOutputStream(Base64OutputStream(bytes,
BASE64_FLAG
)).use {
ks.store(it,
PASSWORD
)
}
Config.keyStoreRaw = bytes.toString("UTF-8")

View File

@@ -1,13 +1,16 @@
@file:Suppress("DEPRECATION")
package com.topjohnwu.magisk.utils
package com.topjohnwu.magisk.core.utils
import android.annotation.SuppressLint
import android.content.res.AssetManager
import android.content.res.Configuration
import android.content.res.Resources
import com.topjohnwu.magisk.Config
import android.util.DisplayMetrics
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.ResourceMgr
import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.core.ResMgr
import com.topjohnwu.magisk.core.addAssetPath
import com.topjohnwu.magisk.extensions.langTagToLocale
import com.topjohnwu.magisk.extensions.toLangTag
import io.reactivex.Single
@@ -22,41 +25,37 @@ val defaultLocale: Locale = Locale.getDefault()
val availableLocales = Single.fromCallable {
val compareId = R.string.app_changelog
val config = ResourceMgr.resource.configuration
val metrics = ResourceMgr.resource.displayMetrics
val res = Resources(ResourceMgr.resource.assets, metrics, config)
val locales = mutableListOf<Locale>().apply {
// Create a completely new resource to prevent cross talk over app's configs
val asset = AssetManager::class.java.newInstance().apply { addAssetPath(ResMgr.apk) }
val config = Configuration(ResMgr.resource.configuration)
val metrics = DisplayMetrics().apply { setTo(ResMgr.resource.displayMetrics) }
val res = Resources(asset, metrics, config)
val locales = ArrayList<String>().apply {
// Add default locale
add(Locale.ENGLISH)
add("en")
// Add some special locales
add(Locale.TAIWAN)
add(Locale("pt", "BR"))
add("zh-TW")
add("pt-BR")
// Other locales
val otherLocales = ResourceMgr.resource.assets.locales
.map { it.langTagToLocale() }
.distinctBy {
// Then add all supported locales
addAll(res.assets.locales)
}.map {
it.langTagToLocale()
}.distinctBy {
config.setLocale(it)
res.updateConfiguration(config, metrics)
res.getString(compareId)
}
addAll(otherLocales)
}.sortedWith(Comparator { a, b ->
a.getDisplayName(a).toLowerCase(a)
.compareTo(b.getDisplayName(b).toLowerCase(b))
a.getDisplayName(a).compareTo(b.getDisplayName(b), true)
})
config.setLocale(defaultLocale)
res.updateConfiguration(config, metrics)
val defName = res.getString(R.string.system_default)
// Restore back to current locale
config.setLocale(currentLocale)
res.updateConfiguration(config, metrics)
Pair(locales, defName)
}.map { (locales, defName) ->
val names = ArrayList<String>(locales.size + 1)
@@ -85,5 +84,5 @@ fun refreshLocale() {
else -> localeConfig.langTagToLocale()
}
Locale.setDefault(currentLocale)
ResourceMgr.resource.updateConfig()
ResMgr.resource.updateConfig()
}

View File

@@ -1,19 +1,22 @@
package com.topjohnwu.magisk.utils
package com.topjohnwu.magisk.core.utils
import android.content.Context
import android.os.Build.VERSION.SDK_INT
import android.widget.Toast
import com.topjohnwu.magisk.*
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.isRunningAsStub
import com.topjohnwu.magisk.core.view.Notifications
import com.topjohnwu.magisk.data.network.GithubRawServices
import com.topjohnwu.magisk.extensions.DynamicClassLoader
import com.topjohnwu.magisk.extensions.get
import com.topjohnwu.magisk.extensions.subscribeK
import com.topjohnwu.magisk.extensions.writeTo
import com.topjohnwu.magisk.view.Notifications
import com.topjohnwu.signing.JarMap
import com.topjohnwu.signing.SignAPK
import com.topjohnwu.superuser.Shell
import io.reactivex.Completable
import io.reactivex.Single
import timber.log.Timber
import java.io.File
import java.io.FileOutputStream
@@ -23,14 +26,15 @@ import java.security.SecureRandom
object PatchAPK {
private const val LOWERALPHA = "abcdefghijklmnopqrstuvwxyz"
private val UPPERALPHA = LOWERALPHA.toUpperCase()
private val ALPHA = LOWERALPHA + UPPERALPHA
private const val ALPHA = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
private const val DIGITS = "0123456789"
val ALPHANUM = ALPHA + DIGITS
private val ALPHANUMDOTS = "$ALPHANUM............"
const val ALPHANUM = ALPHA + DIGITS
private const val ALPHANUMDOTS = "$ALPHANUM............"
private fun genPackageName(prefix: String, length: Int): String {
private const val APP_ID = "com.topjohnwu.magisk"
private const val APP_NAME = "Magisk Manager"
private fun genPackageName(prefix: String, length: Int): CharSequence {
val builder = StringBuilder(length)
builder.append(prefix)
val len = length - prefix.length
@@ -46,10 +50,10 @@ object PatchAPK {
builder.append(next)
prev = next
}
return builder.toString()
return builder
}
private fun findAndPatch(xml: ByteArray, from: String, to: String): Boolean {
private fun findAndPatch(xml: ByteArray, from: CharSequence, to: CharSequence): Boolean {
if (to.length > from.length)
return false
val buf = ByteBuffer.wrap(xml).order(ByteOrder.LITTLE_ENDIAN).asCharBuffer()
@@ -68,7 +72,7 @@ object PatchAPK {
if (offList.isEmpty())
return false
val toBuf = to.toCharArray().copyOf(from.length)
val toBuf = to.toString().toCharArray().copyOf(from.length)
for (off in offList) {
buf.position(off)
buf.put(toBuf)
@@ -76,18 +80,40 @@ object PatchAPK {
return true
}
fun patch(apk: String, out: String, pkg: CharSequence, label: CharSequence): Boolean {
try {
val jar = JarMap.open(apk)
val je = jar.getJarEntry(Const.ANDROID_MANIFEST)
val xml = jar.getRawData(je)
if (!findAndPatch(xml, APP_ID, pkg) ||
!findAndPatch(xml, APP_NAME, label))
return false
// Write apk changes
jar.getOutputStream(je).write(xml)
val keys = Keygen(get())
SignAPK.sign(keys.cert, keys.key, jar, FileOutputStream(out).buffered())
} catch (e: Exception) {
Timber.e(e)
return false
}
return true
}
private fun patchAndHide(context: Context, label: String): Boolean {
val src = if (!isRunningAsStub && SDK_INT >= 28 &&
Info.env.magiskVersionCode >= Const.Version.PROVIDER_CONNECT) {
// If not running as stub, and we are compatible with stub, use stub
val dlStub = !isRunningAsStub && SDK_INT >= 28 &&
Info.env.magiskVersionCode >= Const.Version.PROVIDER_CONNECT
val src = if (dlStub) {
val stub = File(context.cacheDir, "stub.apk")
val svc = get<GithubRawServices>()
runCatching {
try {
svc.fetchFile(Info.remote.stub.link).blockingGet().byteStream().use {
it.writeTo(stub)
}
}.onFailure {
Timber.e(it)
} catch (e: Exception) {
Timber.e(e)
return false
}
stub.path
@@ -97,7 +123,7 @@ object PatchAPK {
// Generate a new random package name and signature
val repack = File(context.cacheDir, "patched.apk")
val pkg = genPackageName("com.", BuildConfig.APPLICATION_ID.length)
val pkg = genPackageName("com.", APP_ID.length)
Config.keyStoreRaw = ""
if (!patch(src, repack.path, pkg, label))
@@ -108,69 +134,22 @@ object PatchAPK {
if (!Shell.su("force_pm_install $repack").exec().isSuccess)
return false
Config.suManager = pkg
Config.suManager = pkg.toString()
Config.export()
Shell.su("pm uninstall ${BuildConfig.APPLICATION_ID}").submit()
Shell.su("pm uninstall $APP_ID").submit()
return true
}
@JvmStatic
@JvmOverloads
fun patch(apk: String, out: String, pkg: String, label: String = "Manager"): Boolean {
try {
val jar = JarMap.open(apk)
val je = jar.getJarEntry(Const.ANDROID_MANIFEST)
val xml = jar.getRawData(je)
if (!findAndPatch(xml, BuildConfig.APPLICATION_ID, pkg) ||
!findAndPatch(xml, "Magisk Manager", label))
return false
// Write apk changes
jar.getOutputStream(je).write(xml)
val keys = Keygen()
SignAPK.sign(keys.cert, keys.key, jar, FileOutputStream(out).buffered())
} catch (e: Exception) {
Timber.e(e)
return false
}
return true
}
fun patch(apk: File, out: File, pkg: String, label: String): Boolean {
try {
if (apk.length() < 1 shl 18) {
// APK is smaller than 256K, must be stub
return patch(apk.path, out.path, pkg, label)
}
// Try using the new APK to patch itself
val loader = DynamicClassLoader(apk)
val cls = loader.loadClass("a.a")
for (m in cls.declaredMethods) {
val pars = m.parameterTypes
if (pars.size == 4 && pars[0] == String::class.java) {
return m.invoke(null, apk.path, out.path, pkg, label) as Boolean
}
}
throw Exception("No matching method found")
} catch (e: Exception) {
Timber.e(e)
// Fallback to use the current implementation
return patch(apk.path, out.path, pkg, label)
}
}
fun hideManager(context: Context, label: String) {
Completable.fromAction {
val progress = Notifications.progress(context, context.getString(R.string.hide_manager_title))
Notifications.mgr.notify(Const.ID.HIDE_MANAGER_NOTIFICATION_ID, progress.build())
if (!patchAndHide(context, label))
Single.fromCallable {
patchAndHide(context, label)
}.subscribeK {
if (!it)
Utils.toast(R.string.hide_manager_fail_toast, Toast.LENGTH_LONG)
Notifications.mgr.cancel(Const.ID.HIDE_MANAGER_NOTIFICATION_ID)
}.subscribeK()
}
}
}

View File

@@ -1,4 +1,4 @@
package com.topjohnwu.magisk.utils
package com.topjohnwu.magisk.core.utils
import com.topjohnwu.superuser.internal.UiThreadHandler
import java.io.FilterInputStream

View File

@@ -0,0 +1,46 @@
package com.topjohnwu.magisk.core.utils
import android.content.Context
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.wrap
import com.topjohnwu.magisk.extensions.rawResource
import com.topjohnwu.superuser.Shell
import com.topjohnwu.superuser.ShellUtils
import com.topjohnwu.superuser.io.SuFile
class RootInit : Shell.Initializer() {
override fun onInit(context: Context, shell: Shell): Boolean {
return init(context.wrap(), shell)
}
fun init(context: Context, shell: Shell): Boolean {
val job = shell.newJob()
job.add(context.rawResource(R.raw.manager))
if (shell.isRoot) {
job.add(context.rawResource(R.raw.util_functions))
.add("SHA1=`grep_prop SHA1 /sbin/.magisk/config`")
Const.MAGISK_DISABLE_FILE = SuFile("/cache/.disable_magisk")
}
job.add(
"export BOOTMODE=true",
"mount_partitions",
"get_flags",
"run_migrations"
).exec()
fun getvar(name: String) = ShellUtils.fastCmd(shell, "echo \$$name").toBoolean()
Info.keepVerity = getvar("KEEPVERITY")
Info.keepEnc = getvar("KEEPFORCEENCRYPT")
Info.isSAR = getvar("SYSTEM_ROOT")
Info.ramdisk = shell.newJob().add("check_boot_ramdisk").exec().isSuccess
Info.recovery = getvar("RECOVERYMODE")
Info.isAB = getvar("ISAB")
return true
}
}

View File

@@ -0,0 +1,21 @@
package com.topjohnwu.magisk.core.utils
interface SafetyNetHelper {
val version: Int
fun attest()
interface Callback {
fun onResponse(responseCode: Int)
}
companion object {
const val RESPONSE_ERR = 0x01
const val CONNECTION_FAIL = 0x02
const val BASIC_PASS = 0x10
const val CTS_PASS = 0x20
}
}

View File

@@ -1,4 +1,4 @@
package com.topjohnwu.magisk.utils
package com.topjohnwu.magisk.core.utils
import android.content.Context
import android.content.Intent
@@ -7,18 +7,18 @@ import android.net.Uri
import android.os.Environment
import android.widget.Toast
import androidx.work.*
import com.topjohnwu.magisk.*
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.UpdateCheckService
import com.topjohnwu.magisk.extensions.get
import com.topjohnwu.magisk.model.update.UpdateCheckService
import com.topjohnwu.superuser.internal.UiThreadHandler
import java.io.File
import java.util.concurrent.TimeUnit
object Utils {
val isCanary: Boolean = BuildConfig.VERSION_NAME.contains("-")
fun toast(msg: CharSequence, duration: Int) {
UiThreadHandler.run { Toast.makeText(get(), msg, duration).show() }
}
@@ -44,12 +44,13 @@ object Utils {
.setRequiresDeviceIdle(true)
.build()
val request = PeriodicWorkRequest
.Builder(ClassMap[UpdateCheckService::class.java] as Class<Worker>, 12, TimeUnit.HOURS)
.Builder(UpdateCheckService::class.java, 12, TimeUnit.HOURS)
.setConstraints(constraints)
.build()
WorkManager.getInstance(context).enqueueUniquePeriodicWork(
Const.ID.CHECK_MAGISK_UPDATE_WORKER_ID,
ExistingPeriodicWorkPolicy.REPLACE, request)
ExistingPeriodicWorkPolicy.REPLACE, request
)
} else {
WorkManager.getInstance(context)
.cancelUniqueWork(Const.ID.CHECK_MAGISK_UPDATE_WORKER_ID)
@@ -62,7 +63,10 @@ object Utils {
if (intent.resolveActivity(context.packageManager) != null) {
context.startActivity(intent)
} else {
toast(R.string.open_link_failed_toast, Toast.LENGTH_SHORT)
toast(
R.string.open_link_failed_toast,
Toast.LENGTH_SHORT
)
}
}

View File

@@ -1,4 +1,4 @@
package com.topjohnwu.magisk.utils
package com.topjohnwu.magisk.core.utils
import com.topjohnwu.superuser.io.SuFile
import com.topjohnwu.superuser.io.SuFileOutputStream

View File

@@ -1,4 +1,4 @@
package com.topjohnwu.magisk.view
package com.topjohnwu.magisk.core.view
import android.app.Notification
import android.app.NotificationChannel
@@ -9,13 +9,12 @@ import android.os.Build.VERSION.SDK_INT
import androidx.core.app.TaskStackBuilder
import androidx.core.content.getSystemService
import androidx.core.graphics.drawable.toIcon
import com.topjohnwu.magisk.*
import com.topjohnwu.magisk.Const.ID.PROGRESS_NOTIFICATION_CHANNEL
import com.topjohnwu.magisk.Const.ID.UPDATE_NOTIFICATION_CHANNEL
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.*
import com.topjohnwu.magisk.core.Const.ID.PROGRESS_NOTIFICATION_CHANNEL
import com.topjohnwu.magisk.core.Const.ID.UPDATE_NOTIFICATION_CHANNEL
import com.topjohnwu.magisk.extensions.get
import com.topjohnwu.magisk.extensions.getBitmap
import com.topjohnwu.magisk.model.receiver.GeneralReceiver
import com.topjohnwu.magisk.ui.SplashActivity
object Notifications {
@@ -52,10 +51,13 @@ object Notifications {
val stackBuilder = TaskStackBuilder.create(context)
stackBuilder.addParentStack(SplashActivity::class.java.cmp(context.packageName))
stackBuilder.addNextIntent(intent)
val pendingIntent = stackBuilder.getPendingIntent(Const.ID.MAGISK_UPDATE_NOTIFICATION_ID,
val pendingIntent = stackBuilder.getPendingIntent(
Const.ID.MAGISK_UPDATE_NOTIFICATION_ID,
PendingIntent.FLAG_UPDATE_CURRENT)
val builder = updateBuilder(context)
val builder = updateBuilder(
context
)
.setContentTitle(context.getString(R.string.magisk_update_title))
.setContentText(context.getString(R.string.manager_download_install))
.setAutoCancel(true)
@@ -72,7 +74,9 @@ object Notifications {
val pendingIntent = PendingIntent.getBroadcast(context,
Const.ID.APK_UPDATE_NOTIFICATION_ID, intent, PendingIntent.FLAG_UPDATE_CURRENT)
val builder = updateBuilder(context)
val builder = updateBuilder(
context
)
.setContentTitle(context.getString(R.string.manager_update_title))
.setContentText(context.getString(R.string.manager_download_install))
.setAutoCancel(true)
@@ -87,7 +91,9 @@ object Notifications {
val pendingIntent = PendingIntent.getBroadcast(context,
Const.ID.DTBO_NOTIFICATION_ID, intent, PendingIntent.FLAG_UPDATE_CURRENT)
val builder = updateBuilder(context)
val builder = updateBuilder(
context
)
.setContentTitle(context.getString(R.string.dtbo_patched_title))
.setContentText(context.getString(R.string.dtbo_patched_reboot))

View File

@@ -1,4 +1,4 @@
package com.topjohnwu.magisk.view
package com.topjohnwu.magisk.core.view
import android.content.Context
import android.content.Intent
@@ -10,17 +10,18 @@ import androidx.annotation.RequiresApi
import androidx.core.content.getSystemService
import androidx.core.graphics.drawable.toAdaptiveIcon
import androidx.core.graphics.drawable.toIcon
import com.topjohnwu.magisk.*
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.*
import com.topjohnwu.magisk.core.utils.Utils
import com.topjohnwu.magisk.extensions.getBitmap
import com.topjohnwu.magisk.ui.SplashActivity
import com.topjohnwu.magisk.utils.Utils
object Shortcuts {
fun setup(context: Context) {
if (Build.VERSION.SDK_INT >= 25) {
val manager = context.getSystemService<ShortcutManager>()
manager?.dynamicShortcuts = getShortCuts(context)
manager?.dynamicShortcuts =
getShortCuts(context)
}
}
@@ -77,19 +78,6 @@ object Shortcuts {
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
)
.setIcon(getIcon(R.drawable.sc_extension))
.setRank(3)
.build()
)
shortCuts.add(
ShortcutInfo.Builder(context, "downloads")
.setShortLabel(context.getString(R.string.downloads))
.setIntent(
Intent(intent)
.putExtra(Const.Key.OPEN_SECTION, "downloads")
.setAction(Intent.ACTION_VIEW)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
)
.setIcon(getIcon(R.drawable.sc_cloud_download))
.setRank(2)
.build()
)

View File

@@ -0,0 +1,67 @@
@file:JvmMultifileClass
package com.topjohnwu.magisk.data.database
import androidx.room.Dao
import androidx.room.Query
import com.topjohnwu.magisk.core.model.module.Repo
interface RepoBase {
fun getRepos(offset: Int, limit: Int = LIMIT): List<Repo>
fun searchRepos(query: String, offset: Int, limit: Int = LIMIT): List<Repo>
@Query("SELECT * FROM repos WHERE id = :id AND versionCode > :versionCode LIMIT 1")
fun getUpdatableRepoById(id: String, versionCode: Int): Repo?
@Query("SELECT * FROM repos WHERE id = :id LIMIT 1")
fun getRepoById(id: String): Repo?
companion object {
const val LIMIT = 10
}
}
@Dao
interface RepoByUpdatedDao : RepoBase {
@Query("SELECT * FROM repos ORDER BY last_update DESC LIMIT :limit OFFSET :offset")
override fun getRepos(offset: Int, limit: Int): List<Repo>
@Query(
"""SELECT *
FROM repos
WHERE
(author LIKE '%' || :query || '%') ||
(name LIKE '%' || :query || '%') ||
(description LIKE '%' || :query || '%')
ORDER BY last_update DESC
LIMIT :limit
OFFSET :offset"""
)
override fun searchRepos(query: String, offset: Int, limit: Int): List<Repo>
}
@Dao
interface RepoByNameDao : RepoBase {
@Query("SELECT * FROM repos ORDER BY name COLLATE NOCASE LIMIT :limit OFFSET :offset")
override fun getRepos(offset: Int, limit: Int): List<Repo>
@Query(
"""SELECT *
FROM repos
WHERE
(author LIKE '%' || :query || '%') ||
(name LIKE '%' || :query || '%') ||
(description LIKE '%' || :query || '%')
ORDER BY name COLLATE NOCASE
LIMIT :limit
OFFSET :offset"""
)
override fun searchRepos(query: String, offset: Int, limit: Int): List<Repo>
}

View File

@@ -1,13 +1,15 @@
package com.topjohnwu.magisk.data.database
import androidx.room.*
import com.topjohnwu.magisk.Config
import com.topjohnwu.magisk.model.entity.module.Repo
import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.core.model.module.Repo
@Database(version = 6, entities = [Repo::class, RepoEtag::class])
@Database(version = 6, entities = [Repo::class, RepoEtag::class], exportSchema = false)
abstract class RepoDatabase : RoomDatabase() {
abstract fun repoDao() : RepoDao
abstract fun repoByUpdatedDao(): RepoByUpdatedDao
abstract fun repoByNameDao(): RepoByNameDao
}
@Dao

View File

@@ -6,7 +6,7 @@ import io.reactivex.Completable
import io.reactivex.Single
import java.util.*
@Database(version = 1, entities = [MagiskLog::class])
@Database(version = 1, entities = [MagiskLog::class], exportSchema = false)
abstract class SuLogDatabase : RoomDatabase() {
abstract fun suLogDao(): SuLogDao

View File

@@ -1,8 +1,8 @@
package com.topjohnwu.magisk.data.network
import com.topjohnwu.magisk.Const
import com.topjohnwu.magisk.model.entity.UpdateInfo
import com.topjohnwu.magisk.tasks.GithubRepoInfo
import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.model.UpdateInfo
import com.topjohnwu.magisk.core.tasks.GithubRepoInfo
import io.reactivex.Flowable
import io.reactivex.Single
import okhttp3.ResponseBody

View File

@@ -1,7 +1,7 @@
package com.topjohnwu.magisk.data.repository
import com.topjohnwu.magisk.data.database.SettingsDao
import com.topjohnwu.magisk.data.database.StringDao
import com.topjohnwu.magisk.core.magiskdb.SettingsDao
import com.topjohnwu.magisk.core.magiskdb.StringDao
import io.reactivex.schedulers.Schedulers
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty

View File

@@ -1,20 +1,18 @@
package com.topjohnwu.magisk.data.repository
import com.topjohnwu.magisk.Const
import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.data.database.SuLogDao
import com.topjohnwu.magisk.model.entity.MagiskLog
import com.topjohnwu.magisk.model.entity.WrappedMagiskLog
import com.topjohnwu.superuser.Shell
import io.reactivex.Completable
import io.reactivex.Single
import java.util.concurrent.TimeUnit
class LogRepository(
private val logDao: SuLogDao
) {
fun fetchLogs() = logDao.fetchAll().map { it.wrap() }
fun fetchLogs() = logDao.fetchAll()
fun fetchMagiskLogs() = Single.fromCallable {
Shell.su("tail -n 5000 ${Const.MAGISK_LOG}").exec().out
@@ -28,11 +26,4 @@ class LogRepository(
fun insert(log: MagiskLog) = logDao.insert(log)
private fun List<MagiskLog>.wrap(): List<WrappedMagiskLog> {
val day = TimeUnit.DAYS.toMillis(1)
return groupBy { it.time / day }
.map { WrappedMagiskLog(it.key * day, it.value) }
}
}

View File

@@ -1,8 +1,8 @@
package com.topjohnwu.magisk.data.repository
import android.content.pm.PackageManager
import com.topjohnwu.magisk.Config
import com.topjohnwu.magisk.Info
import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.data.network.GithubRawServices
import com.topjohnwu.magisk.extensions.getLabel
import com.topjohnwu.magisk.extensions.packageName
@@ -24,7 +24,8 @@ class MagiskRepository(
Config.Value.BETA_CHANNEL -> apiRaw.fetchBetaUpdate()
Config.Value.CANARY_CHANNEL -> apiRaw.fetchCanaryUpdate()
Config.Value.CANARY_DEBUG_CHANNEL -> apiRaw.fetchCanaryDebugUpdate()
Config.Value.CUSTOM_CHANNEL -> apiRaw.fetchCustomUpdate(Config.customChannelUrl)
Config.Value.CUSTOM_CHANNEL -> apiRaw.fetchCustomUpdate(
Config.customChannelUrl)
else -> throw IllegalArgumentException()
}.flatMap {
// If remote version is lower than current installed, try switching to beta

View File

@@ -1,7 +1,7 @@
package com.topjohnwu.magisk.data.repository
import com.topjohnwu.magisk.core.model.module.Repo
import com.topjohnwu.magisk.data.network.GithubRawServices
import com.topjohnwu.magisk.model.entity.module.Repo
class StringRepository(
private val api: GithubRawServices

View File

@@ -6,7 +6,9 @@ import android.app.Application
import android.content.Context
import android.os.Build
import android.os.Bundle
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import androidx.preference.PreferenceManager
import com.topjohnwu.magisk.core.ResMgr
import com.topjohnwu.magisk.utils.RxBus
import org.koin.core.qualifier.named
import org.koin.dsl.module
@@ -16,13 +18,14 @@ val Protected = named("protected")
val applicationModule = module {
single { RxBus() }
factory { get<Context>().resources }
factory { ResMgr.resource }
factory { get<Context>().packageManager }
factory(Protected) { createDEContext(get()) }
single(SUTimeout) { get<Context>(Protected).getSharedPreferences("su_timeout", 0) }
single { PreferenceManager.getDefaultSharedPreferences(get<Context>(Protected)) }
single { ActivityTracker() }
factory { get<ActivityTracker>().foreground ?: NullActivity }
single { LocalBroadcastManager.getInstance(get()) }
}
private fun createDEContext(context: Context): Context {

View File

@@ -2,8 +2,12 @@ package com.topjohnwu.magisk.di
import android.content.Context
import androidx.room.Room
import com.topjohnwu.magisk.data.database.*
import com.topjohnwu.magisk.tasks.RepoUpdater
import com.topjohnwu.magisk.core.magiskdb.PolicyDao
import com.topjohnwu.magisk.core.magiskdb.SettingsDao
import com.topjohnwu.magisk.core.magiskdb.StringDao
import com.topjohnwu.magisk.core.tasks.RepoUpdater
import com.topjohnwu.magisk.data.database.RepoDatabase
import com.topjohnwu.magisk.data.database.SuLogDatabase
import org.koin.dsl.module
@@ -11,7 +15,10 @@ val databaseModule = module {
single { PolicyDao(get()) }
single { SettingsDao() }
single { StringDao() }
single { createRepoDatabase(get()).repoDao() }
single { createRepoDatabase(get()) }
single { get<RepoDatabase>().repoDao() }
single { get<RepoDatabase>().repoByNameDao() }
single { get<RepoDatabase>().repoByUpdatedDao() }
single { createSuLogDatabase(get(Protected)).suLogDao() }
single { RepoUpdater(get(), get()) }
}

View File

@@ -1,10 +1,9 @@
package com.topjohnwu.magisk.di
import android.content.Context
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.Moshi
import com.topjohnwu.magisk.BuildConfig
import com.topjohnwu.magisk.Const
import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.data.network.GithubApiServices
import com.topjohnwu.magisk.data.network.GithubRawServices
import com.topjohnwu.magisk.net.Networking
@@ -20,7 +19,6 @@ import retrofit2.Retrofit
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
import retrofit2.converter.moshi.MoshiConverterFactory
import retrofit2.converter.scalars.ScalarsConverterFactory
import se.ansman.kotshi.KotshiJsonAdapterFactory
val networkingModule = module {
single { createOkHttpClient(get()) }
@@ -49,9 +47,7 @@ fun createOkHttpClient(context: Context): OkHttpClient {
}
fun createMoshiConverterFactory(): MoshiConverterFactory {
val moshi = Moshi.Builder()
.add(KotshiJsonAdapterFactory)
.build()
val moshi = Moshi.Builder().build()
return MoshiConverterFactory.create(moshi)
}
@@ -63,9 +59,6 @@ fun createRetrofit(okHttpClient: OkHttpClient): Retrofit.Builder {
.client(okHttpClient)
}
@KotshiJsonAdapterFactory
abstract class JsonAdapterFactory : JsonAdapter.Factory
inline fun <reified T> createApiService(retrofitBuilder: Retrofit.Builder, baseUrl: String): T {
return retrofitBuilder
.baseUrl(baseUrl)

View File

@@ -1,27 +1,35 @@
package com.topjohnwu.magisk.di
import android.net.Uri
import com.topjohnwu.magisk.legacy.flash.FlashViewModel
import com.topjohnwu.magisk.legacy.surequest.SuRequestViewModel
import com.topjohnwu.magisk.ui.MainViewModel
import com.topjohnwu.magisk.ui.flash.FlashViewModel
import com.topjohnwu.magisk.ui.hide.HideViewModel
import com.topjohnwu.magisk.ui.home.HomeViewModel
import com.topjohnwu.magisk.ui.install.InstallViewModel
import com.topjohnwu.magisk.ui.log.LogViewModel
import com.topjohnwu.magisk.ui.module.ModuleViewModel
import com.topjohnwu.magisk.ui.request.RequestViewModel
import com.topjohnwu.magisk.ui.safetynet.SafetynetViewModel
import com.topjohnwu.magisk.ui.settings.SettingsViewModel
import com.topjohnwu.magisk.ui.superuser.SuperuserViewModel
import com.topjohnwu.magisk.ui.surequest.SuRequestViewModel
import com.topjohnwu.magisk.ui.theme.ThemeViewModel
import org.koin.androidx.viewmodel.dsl.viewModel
import org.koin.dsl.module
val viewModelModules = module {
viewModel { MainViewModel() }
viewModel { HideViewModel(get()) }
viewModel { HomeViewModel(get()) }
viewModel { SuperuserViewModel(get(), get(), get(), get()) }
viewModel { HideViewModel(get(), get()) }
viewModel { LogViewModel(get()) }
viewModel { ModuleViewModel(get(), get(), get()) }
viewModel { LogViewModel(get(), get()) }
viewModel { (action: String, file: Uri, additional: Uri) ->
FlashViewModel(action, file, additional, get())
}
viewModel { RequestViewModel() }
viewModel { SafetynetViewModel(get()) }
viewModel { SettingsViewModel(get()) }
viewModel { SuperuserViewModel(get(), get(), get()) }
viewModel { ThemeViewModel() }
viewModel { InstallViewModel() }
viewModel { MainViewModel() }
// Legacy
viewModel { FlashViewModel(get()) }
viewModel { SuRequestViewModel(get(), get(), get(SUTimeout), get()) }
}

View File

@@ -52,9 +52,9 @@ fun <T> Observable<T>.subscribeK(
fun <T> Single<T>.subscribeK(
onError: OnErrorListener = { it.printStackTrace() },
onSuccess: OnSuccessListener<T> = {}
onNext: OnSuccessListener<T> = {}
) = applySchedulers()
.subscribe(onSuccess, onError)
.subscribe(onNext, onError)
fun <T> Maybe<T>.subscribeK(
onError: OnErrorListener = { it.printStackTrace() },
@@ -198,5 +198,8 @@ fun <T> ObservableField<T>.toObservable(): Observable<T> {
fun <T : Any> T.toSingle() = Single.just(this)
fun <T1, T2, R> zip(t1: Single<T1>, t2: Single<T2>, zipper: (T1, T2) -> R) =
Single.zip(t1, t2, BiFunction<T1, T2, R> { rt1, rt2 -> zipper(rt1, rt2) })
inline fun <T1, T2, R> zip(
t1: Single<T1>,
t2: Single<T2>,
crossinline zipper: (T1, T2) -> R
) = Single.zip(t1, t2, BiFunction<T1, T2, R> { rt1, rt2 -> zipper(rt1, rt2) })

View File

@@ -1,5 +1,6 @@
package com.topjohnwu.magisk.extensions
import android.app.Activity
import android.content.ComponentName
import android.content.Context
import android.content.ContextWrapper
@@ -22,17 +23,20 @@ import android.os.Build
import android.os.Build.VERSION.SDK_INT
import android.provider.OpenableColumns
import android.view.View
import android.view.inputmethod.InputMethodManager
import androidx.annotation.ColorRes
import androidx.annotation.DrawableRes
import androidx.appcompat.content.res.AppCompatResources
import androidx.core.content.ContextCompat
import androidx.core.content.getSystemService
import androidx.core.net.toFile
import androidx.core.net.toUri
import com.topjohnwu.magisk.Const
import androidx.fragment.app.Fragment
import com.topjohnwu.magisk.FileProvider
import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.utils.Utils
import com.topjohnwu.magisk.core.utils.currentLocale
import com.topjohnwu.magisk.utils.DynamicClassLoader
import com.topjohnwu.magisk.utils.Utils
import com.topjohnwu.magisk.utils.currentLocale
import com.topjohnwu.superuser.Shell
import java.io.File
import java.io.FileNotFoundException
@@ -283,7 +287,7 @@ fun Context.drawableCompat(@DrawableRes id: Int) = ContextCompat.getDrawable(thi
* with respect to RTL layout direction
*/
fun Context.startEndToLeftRight(start: Int, end: Int): Pair<Int, Int> {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 &&
if (SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 &&
resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_RTL
) {
return end to start
@@ -294,8 +298,8 @@ fun Context.startEndToLeftRight(start: Int, end: Int): Pair<Int, Int> {
fun Context.openUrl(url: String) = Utils.openLink(this, url.toUri())
@Suppress("FunctionName")
inline fun <reified T> T.DynamicClassLoader(apk: File)
= DynamicClassLoader(apk, T::class.java.classLoader)
inline fun <reified T> T.DynamicClassLoader(apk: File) =
DynamicClassLoader(apk, T::class.java.classLoader)
fun Context.unwrap(): Context {
var context = this
@@ -309,3 +313,18 @@ fun Context.unwrap() : Context {
}
fun Uri.writeTo(file: File) = toFile().copyTo(file)
fun Context.hasPermissions(vararg permissions: String) = permissions.all {
ContextCompat.checkSelfPermission(this, it) == PERMISSION_GRANTED
}
fun Activity.hideKeyboard() {
val view = currentFocus ?: return
getSystemService<InputMethodManager>()
?.hideSoftInputFromWindow(view.windowToken, 0)
view.clearFocus()
}
fun Fragment.hideKeyboard() {
activity?.hideKeyboard()
}

View File

@@ -1,8 +1,71 @@
package com.topjohnwu.magisk.extensions
import androidx.databinding.ObservableList
import com.topjohnwu.magisk.utils.KObservableField
fun KObservableField<Boolean>.toggle() {
value = !value
}
fun <T> ObservableList<T>.addOnListChangedCallback(
onChanged: ((sender: ObservableList<T>) -> Unit)? = null,
onItemRangeRemoved: ((sender: ObservableList<T>, positionStart: Int, itemCount: Int) -> Unit)? = null,
onItemRangeMoved: ((sender: ObservableList<T>, fromPosition: Int, toPosition: Int, itemCount: Int) -> Unit)? = null,
onItemRangeInserted: ((sender: ObservableList<T>, positionStart: Int, itemCount: Int) -> Unit)? = null,
onItemRangeChanged: ((sender: ObservableList<T>, positionStart: Int, itemCount: Int) -> Unit)? = null
) = addOnListChangedCallback(object : ObservableList.OnListChangedCallback<ObservableList<T>>() {
override fun onChanged(sender: ObservableList<T>?) {
onChanged?.invoke(sender ?: return)
}
override fun onItemRangeRemoved(
sender: ObservableList<T>?,
positionStart: Int,
itemCount: Int
) {
onItemRangeRemoved?.invoke(
sender ?: return,
positionStart,
itemCount
)
}
override fun onItemRangeMoved(
sender: ObservableList<T>?,
fromPosition: Int,
toPosition: Int,
itemCount: Int
) {
onItemRangeMoved?.invoke(
sender ?: return,
fromPosition,
toPosition,
itemCount
)
}
override fun onItemRangeInserted(
sender: ObservableList<T>?,
positionStart: Int,
itemCount: Int
) {
onItemRangeInserted?.invoke(
sender ?: return,
positionStart,
itemCount
)
}
override fun onItemRangeChanged(
sender: ObservableList<T>?,
positionStart: Int,
itemCount: Int
) {
onItemRangeChanged?.invoke(
sender ?: return,
positionStart,
itemCount
)
}
})

View File

@@ -1,11 +1,13 @@
package com.topjohnwu.magisk.extensions
import android.os.Build
import timber.log.Timber
import java.io.File
import java.io.InputStream
import java.io.OutputStream
import java.lang.reflect.Field
import java.lang.reflect.Method
import java.text.SimpleDateFormat
import java.util.*
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
@@ -100,6 +102,9 @@ fun Locale.toLangTag(): String {
}
}
fun SimpleDateFormat.parseOrNull(date: String) =
runCatching { parse(date) }.onFailure { Timber.e(it) }.getOrNull()
// Reflection hacks
private val loadClass = ClassLoader::class.java.getMethod("loadClass", String::class.java)

View File

@@ -1,6 +1,6 @@
package com.topjohnwu.magisk.extensions
import com.topjohnwu.magisk.Info
import com.topjohnwu.magisk.core.Info
import com.topjohnwu.superuser.Shell
import com.topjohnwu.superuser.io.SuFileInputStream
import com.topjohnwu.superuser.io.SuFileOutputStream
@@ -12,3 +12,5 @@ fun reboot(reason: String = if (Info.recovery) "recovery" else "") {
fun File.suOutputStream() = SuFileOutputStream(this)
fun File.suInputStream() = SuFileInputStream(this)
val hasRoot get() = Shell.rootAccess()

View File

@@ -1,15 +1,38 @@
package com.topjohnwu.magisk.extensions
import android.content.res.Resources
import android.os.Build
val specialChars = arrayOf('!', '@', '#', '$', '%', '&', '?')
val fullSpecialChars = arrayOf('', '', '', '', '', '', '')
fun String.isCJK(): Boolean {
for (i in 0 until length)
if (isCJK(codePointAt(i)))
return true
return false
}
fun isCJK(codepoint: Int): Boolean {
return if (Build.VERSION.SDK_INT < 19) false /* Pre 5.0 don't need to be pretty.. */
else Character.isIdeographic(codepoint)
}
fun String.replaceRandomWithSpecial(passes: Int): String {
var string = this
repeat(passes) {
string = string.replaceRandomWithSpecial()
}
return string
}
fun String.replaceRandomWithSpecial(): String {
val sp = if (isCJK()) fullSpecialChars else specialChars
var random: Char
do {
random = random()
} while (random == '.')
return replace(random, specialChars.random())
return replace(random, sp.random())
}
fun StringBuilder.appendIf(condition: Boolean, builder: StringBuilder.() -> Unit) =

View File

@@ -1,6 +1,6 @@
package com.topjohnwu.magisk.extensions
import com.topjohnwu.magisk.utils.currentLocale
import com.topjohnwu.magisk.core.utils.currentLocale
import java.text.DateFormat
import java.text.ParseException
import java.text.SimpleDateFormat
@@ -14,7 +14,22 @@ fun String.toTime(format: DateFormat) = try {
-1L
}
val timeFormatFull by lazy { SimpleDateFormat("yyyy/MM/dd_HH:mm:ss", currentLocale) }
val timeFormatStandard by lazy { SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", currentLocale) }
val timeFormatMedium by lazy { DateFormat.getDateInstance(DateFormat.MEDIUM, currentLocale) }
val timeFormatTime by lazy { SimpleDateFormat("h:mm a", currentLocale) }
val timeFormatFull by lazy { SimpleDateFormat("yyyy/MM/dd_HH:mm:ss",
currentLocale
) }
val timeFormatStandard by lazy { SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'",
currentLocale
) }
val timeFormatMedium by lazy { DateFormat.getDateInstance(DateFormat.MEDIUM,
currentLocale
) }
val timeFormatTime by lazy { SimpleDateFormat("h:mm a",
currentLocale
) }
val timeDateFormat by lazy {
DateFormat.getDateTimeInstance(
DateFormat.DEFAULT,
DateFormat.DEFAULT,
currentLocale
)
}

View File

@@ -1,7 +1,11 @@
package com.topjohnwu.magisk.extensions
import android.view.View
import android.view.ViewGroup
import android.view.ViewTreeObserver
import androidx.interpolator.view.animation.FastOutSlowInInterpolator
import androidx.transition.AutoTransition
import androidx.transition.TransitionManager
fun View.setOnViewReadyListener(callback: () -> Unit) = addOnGlobalLayoutListener(true, callback)
@@ -12,3 +16,8 @@ fun View.addOnGlobalLayoutListener(oneShot: Boolean = false, callback: () -> Uni
callback()
}
})
fun ViewGroup.startAnimations() {
val transition = AutoTransition().setInterpolator(FastOutSlowInInterpolator()).setDuration(400)
TransitionManager.beginDelayedTransition(this, transition)
}

View File

@@ -1,4 +1,4 @@
package com.topjohnwu.magisk.ui.flash
package com.topjohnwu.magisk.legacy.flash
import android.content.Context
import android.content.Intent
@@ -6,31 +6,27 @@ import android.content.pm.ActivityInfo
import android.net.Uri
import android.os.Bundle
import androidx.core.net.toUri
import com.topjohnwu.magisk.Const
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.base.BaseActivity
import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.intent
import com.topjohnwu.magisk.core.view.Notifications
import com.topjohnwu.magisk.databinding.ActivityFlashBinding
import com.topjohnwu.magisk.extensions.snackbar
import com.topjohnwu.magisk.intent
import com.topjohnwu.magisk.model.events.BackPressEvent
import com.topjohnwu.magisk.model.events.PermissionEvent
import com.topjohnwu.magisk.model.events.SnackbarEvent
import com.topjohnwu.magisk.model.events.ViewEvent
import com.topjohnwu.magisk.view.Notifications
import com.topjohnwu.magisk.ui.base.BaseUIActivity
import com.topjohnwu.magisk.ui.base.CompatNavigationDelegate
import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koin.core.parameter.parametersOf
import java.io.File
open class FlashActivity : BaseActivity<FlashViewModel, ActivityFlashBinding>() {
open class FlashActivity : BaseUIActivity<FlashViewModel, ActivityFlashBinding>() {
override val layoutRes: Int = R.layout.activity_flash
override val themeRes: Int = R.style.MagiskTheme_Flashing
override val viewModel: FlashViewModel by viewModel {
val uri = intent.data ?: let { finish(); Uri.EMPTY }
val additionalUri = intent.getParcelableExtra(Const.Key.FLASH_DATA) ?: uri
val action = intent.getStringExtra(Const.Key.FLASH_ACTION) ?: let { finish();"" }
parametersOf(action, uri, additionalUri)
}
override val viewModel: FlashViewModel by viewModel()
override val navigation: CompatNavigationDelegate<BaseUIActivity<FlashViewModel, ActivityFlashBinding>>? =
null
override fun onCreate(savedInstanceState: Bundle?) {
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_NOSENSOR
@@ -38,6 +34,7 @@ open class FlashActivity : BaseActivity<FlashViewModel, ActivityFlashBinding>()
val id = intent.getIntExtra(Const.Key.DISMISS_ID, -1)
if (id != -1)
Notifications.mgr.cancel(id)
viewModel.startFlashing(intent)
}
override fun onBackPressed() {
@@ -49,7 +46,6 @@ open class FlashActivity : BaseActivity<FlashViewModel, ActivityFlashBinding>()
super.onEventDispatched(event)
when (event) {
is SnackbarEvent -> snackbar(snackbarView, event.message(this), event.length, event.f)
is BackPressEvent -> onBackPressed()
is PermissionEvent -> withPermissions(*event.permissions.toTypedArray()) {
onSuccess { event.callback.onNext(true) }
onFailure {

View File

@@ -0,0 +1,117 @@
package com.topjohnwu.magisk.legacy.flash
import android.Manifest.permission.READ_EXTERNAL_STORAGE
import android.Manifest.permission.WRITE_EXTERNAL_STORAGE
import android.content.Intent
import android.content.res.Resources
import android.net.Uri
import android.os.Handler
import android.view.MenuItem
import androidx.core.os.postDelayed
import androidx.databinding.ObservableArrayList
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.tasks.FlashResultListener
import com.topjohnwu.magisk.core.tasks.Flashing
import com.topjohnwu.magisk.core.tasks.MagiskInstaller
import com.topjohnwu.magisk.extensions.*
import com.topjohnwu.magisk.model.binding.BindingAdapter
import com.topjohnwu.magisk.model.entity.recycler.ConsoleItem
import com.topjohnwu.magisk.model.events.SnackbarEvent
import com.topjohnwu.magisk.ui.base.BaseViewModel
import com.topjohnwu.magisk.ui.base.diffListOf
import com.topjohnwu.magisk.ui.base.itemBindingOf
import com.topjohnwu.magisk.utils.KObservableField
import com.topjohnwu.superuser.Shell
import java.io.File
import java.util.*
class FlashViewModel(
private val resources: Resources
) : BaseViewModel(), FlashResultListener {
val canShowReboot = Shell.rootAccess()
val showRestartTitle = KObservableField(false)
val behaviorText = KObservableField(resources.getString(R.string.flashing))
val adapter = BindingAdapter<ConsoleItem>()
val items = diffListOf<ConsoleItem>()
val itemBinding = itemBindingOf<ConsoleItem>()
private val outItems = ObservableArrayList<String>()
private val logItems = Collections.synchronizedList(mutableListOf<String>())
init {
outItems.sendUpdatesTo(items) { it.map { ConsoleItem(it) } }
outItems.copyNewInputInto(logItems)
}
fun startFlashing(intent: Intent) {
val installer = intent.data ?: return
val uri: Uri? = intent.getParcelableExtra(Const.Key.FLASH_DATA)
val action = intent.getStringExtra(Const.Key.FLASH_ACTION) ?: return
when (action) {
Const.Value.FLASH_ZIP -> Flashing
.Install(installer, outItems, logItems, this)
.exec()
Const.Value.UNINSTALL -> Flashing
.Uninstall(installer, outItems, logItems, this)
.exec()
Const.Value.FLASH_MAGISK -> MagiskInstaller
.Direct(installer, outItems, logItems, this)
.exec()
Const.Value.FLASH_INACTIVE_SLOT -> MagiskInstaller
.SecondSlot(installer, outItems, logItems, this)
.exec()
Const.Value.PATCH_FILE -> MagiskInstaller
.Patch(installer, uri ?: return, outItems, logItems, this)
.exec()
}
}
override fun onResult(success: Boolean) {
state = if (success) State.LOADED else State.LOADING_FAILED
behaviorText.value = when {
success -> resources.getString(R.string.done)
else -> resources.getString(R.string.failure)
}
if (success) {
Handler().postDelayed(500) {
showRestartTitle.value = true
}
}
}
fun onMenuItemClicked(item: MenuItem): Boolean {
when (item.itemId) {
R.id.action_save -> savePressed()
}
return true
}
private fun savePressed() = withPermissions(READ_EXTERNAL_STORAGE, WRITE_EXTERNAL_STORAGE)
.map { now }
.map { it.toTime(timeFormatStandard) }
.map { Const.MAGISK_INSTALL_LOG_FILENAME.format(it) }
.map { File(Config.downloadDirectory, it) }
.map { file ->
file.bufferedWriter().use { writer ->
logItems.forEach {
writer.write(it)
writer.newLine()
}
}
file.path
}
.subscribeK { SnackbarEvent(it).publish() }
.add()
fun restartPressed() = reboot()
fun backPressed() = back()
}

View File

@@ -1,26 +1,31 @@
package com.topjohnwu.magisk.ui.surequest
package com.topjohnwu.magisk.legacy.surequest
import android.content.Intent
import android.content.pm.ActivityInfo
import android.content.res.Resources
import android.os.Build
import android.os.Bundle
import android.view.Window
import android.view.WindowManager
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.base.BaseActivity
import com.topjohnwu.magisk.core.su.SuCallbackHandler
import com.topjohnwu.magisk.core.su.SuCallbackHandler.REQUEST
import com.topjohnwu.magisk.databinding.ActivityRequestBinding
import com.topjohnwu.magisk.model.events.DieEvent
import com.topjohnwu.magisk.model.events.ViewActionEvent
import com.topjohnwu.magisk.model.events.ViewEvent
import com.topjohnwu.magisk.utils.SuHandler
import com.topjohnwu.magisk.utils.SuHandler.REQUEST
import com.topjohnwu.magisk.ui.base.BaseUIActivity
import com.topjohnwu.magisk.ui.base.CompatNavigationDelegate
import org.koin.androidx.viewmodel.ext.android.viewModel
open class SuRequestActivity : BaseActivity<SuRequestViewModel, ActivityRequestBinding>() {
open class SuRequestActivity : BaseUIActivity<SuRequestViewModel, ActivityRequestBinding>() {
override val layoutRes: Int = R.layout.activity_request
override val themeRes: Int = R.style.MagiskTheme_SU
override val viewModel: SuRequestViewModel by viewModel()
override val navigation: CompatNavigationDelegate<BaseUIActivity<SuRequestViewModel, ActivityRequestBinding>>? =
null
override fun onBackPressed() {
viewModel.denyPressed()
}
@@ -28,6 +33,8 @@ open class SuRequestActivity : BaseActivity<SuRequestViewModel, ActivityRequestB
override fun onCreate(savedInstanceState: Bundle?) {
supportRequestWindowFeature(Window.FEATURE_NO_TITLE)
lockOrientation()
window.setFlags(WindowManager.LayoutParams.FLAG_SECURE,
WindowManager.LayoutParams.FLAG_SECURE)
super.onCreate(savedInstanceState)
fun showRequest() {
@@ -36,7 +43,7 @@ open class SuRequestActivity : BaseActivity<SuRequestViewModel, ActivityRequestB
}
fun runHandler(action: String?) {
SuHandler(this, action, intent.extras)
SuCallbackHandler(this, action, intent.extras)
finish()
}
@@ -54,6 +61,12 @@ open class SuRequestActivity : BaseActivity<SuRequestViewModel, ActivityRequestB
}
}
override fun getTheme(): Resources.Theme {
val theme = super.getTheme()
theme.applyStyle(R.style.Foundation_Floating, true)
return theme
}
override fun onEventDispatched(event: ViewEvent) {
super.onEventDispatched(event)
when (event) {

View File

@@ -0,0 +1,128 @@
package com.topjohnwu.magisk.legacy.surequest
import android.content.Intent
import android.content.SharedPreferences
import android.content.pm.PackageManager
import android.content.res.Resources
import android.graphics.drawable.Drawable
import android.os.CountDownTimer
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.core.magiskdb.PolicyDao
import com.topjohnwu.magisk.core.model.MagiskPolicy.Companion.ALLOW
import com.topjohnwu.magisk.core.model.MagiskPolicy.Companion.DENY
import com.topjohnwu.magisk.core.su.SuRequestHandler
import com.topjohnwu.magisk.core.utils.BiometricHelper
import com.topjohnwu.magisk.databinding.ComparableRvItem
import com.topjohnwu.magisk.model.entity.recycler.SpinnerRvItem
import com.topjohnwu.magisk.model.events.DieEvent
import com.topjohnwu.magisk.ui.base.BaseViewModel
import com.topjohnwu.magisk.utils.DiffObservableList
import com.topjohnwu.magisk.utils.KObservableField
import me.tatarka.bindingcollectionadapter2.BindingListViewAdapter
import me.tatarka.bindingcollectionadapter2.ItemBinding
import java.util.concurrent.TimeUnit.SECONDS
class SuRequestViewModel(
private val pm: PackageManager,
private val policyDB: PolicyDao,
private val timeoutPrefs: SharedPreferences,
private val res: Resources
) : BaseViewModel() {
val icon = KObservableField<Drawable?>(null)
val title = KObservableField("")
val packageName = KObservableField("")
val denyText = KObservableField(res.getString(R.string.deny))
val warningText = KObservableField<CharSequence>(res.getString(R.string.su_warning))
val selectedItemPosition = KObservableField(0)
val grantEnabled = KObservableField(false)
private val items = DiffObservableList(ComparableRvItem.callback)
private val itemBinding = ItemBinding.of<ComparableRvItem<*>> { binding, _, item ->
item.bind(binding)
}
val adapter = BindingListViewAdapter<ComparableRvItem<*>>(1).apply {
itemBinding = this@SuRequestViewModel.itemBinding
setItems(items)
}
private val handler = Handler()
fun grantPressed() {
handler.cancelTimer()
if (BiometricHelper.isEnabled) {
withView {
BiometricHelper.authenticate(this) {
handler.respond(ALLOW)
}
}
} else {
handler.respond(ALLOW)
}
}
fun denyPressed() {
handler.respond(DENY)
}
fun spinnerTouched(): Boolean {
handler.cancelTimer()
return false
}
fun handleRequest(intent: Intent): Boolean {
return handler.start(intent)
}
private inner class Handler : SuRequestHandler(pm, policyDB) {
fun respond(action: Int) {
val pos = selectedItemPosition.value
timeoutPrefs.edit().putInt(policy.packageName, pos).apply()
respond(action, Config.Value.TIMEOUT_LIST[pos])
}
fun cancelTimer() {
timer.cancel()
denyText.value = res.getString(R.string.deny)
}
override fun onStart() {
res.getStringArray(R.array.allow_timeout)
.map { SpinnerRvItem(it) }
.let { items.update(it) }
icon.value = policy.applicationInfo.loadIcon(pm)
title.value = policy.appName
packageName.value = policy.packageName
selectedItemPosition.value = timeoutPrefs.getInt(policy.packageName, 0)
// Override timer
val millis = SECONDS.toMillis(Config.suDefaultTimeout.toLong())
timer = object : CountDownTimer(millis, 1000) {
override fun onTick(remains: Long) {
if (remains <= millis - 1000) {
grantEnabled.value = true
}
denyText.value = "${res.getString(R.string.deny)} (${(remains / 1000) + 1})"
}
override fun onFinish() {
denyText.value = res.getString(R.string.deny)
respond(DENY)
}
}
}
override fun onRespond() {
// Kill activity after response
DieEvent().publish()
}
}
}

View File

@@ -6,7 +6,7 @@ import com.topjohnwu.magisk.databinding.ComparableRvItem
import com.topjohnwu.magisk.model.entity.recycler.LenientRvItem
import me.tatarka.bindingcollectionadapter2.BindingRecyclerViewAdapter
class BindingAdapter : BindingRecyclerViewAdapter<ComparableRvItem<*>>() {
class BindingAdapter <T : ComparableRvItem<*>> : BindingRecyclerViewAdapter<T>() {
private var recyclerView: RecyclerView? = null
@@ -15,12 +15,12 @@ class BindingAdapter : BindingRecyclerViewAdapter<ComparableRvItem<*>>() {
variableId: Int,
layoutRes: Int,
position: Int,
item: ComparableRvItem<*>
item: T
) {
super.onBindBinding(binding, variableId, layoutRes, position, item)
when (item) {
is LenientRvItem -> {
is LenientRvItem<*> -> {
val recycler = recyclerView ?: return
item.onBindingBound(binding)
item.onBindingBound(binding, recycler)

View File

@@ -14,3 +14,14 @@ class HideAppInfo(
val processes = info.packageInfo?.processes?.distinct() ?: listOf(info.packageName)
}
data class StatefulProcess(
val name: String,
val packageName: String,
val isHidden: Boolean
)
class ProcessHideApp(
val info: HideAppInfo,
val processes: List<StatefulProcess>
)

View File

@@ -3,10 +3,11 @@ package com.topjohnwu.magisk.model.entity
import androidx.room.Entity
import androidx.room.Ignore
import androidx.room.PrimaryKey
import com.topjohnwu.magisk.core.model.MagiskPolicy
import com.topjohnwu.magisk.core.model.MagiskPolicy.Companion.ALLOW
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
@Entity(tableName = "logs")
data class MagiskLog(
@@ -23,11 +24,6 @@ data class MagiskLog(
@Ignore val timeString = time.toTime(timeFormatTime)
}
data class WrappedMagiskLog(
val time: Long,
val items: List<MagiskLog>
)
fun MagiskPolicy.toLog(
toUid: Int,
fromPid: Int,

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