Compare commits

...

525 Commits
v19.3 ... v20.3

Author SHA1 Message Date
topjohnwu
0b41cd8564 Fix sk strings 2020-01-10 01:55:23 +08:00
topjohnwu
7db523071d Update changelog 2020-01-10 01:41:39 +08:00
JoanVC100
974ee58b9c New string added 2020-01-10 01:37:32 +08:00
Kamil Kras
1e88f2c382 Updated polish translation
Added translation for:
unsupport_magisk_msg
settings_su_biometric_title
settings_su_biometric_summary
no_biometric
2020-01-10 01:37:20 +08:00
Vladimír Kubala
0bdcfcaaf5 Update Slovak translation 2020-01-10 01:37:05 +08:00
Albert I
5f9c78d04f app: l10n: Update Indonesian translations
Signed-off-by: Albert I <kras@raphielgang.org>
2020-01-10 01:36:55 +08:00
Viktor De Pasquale
afa178fdec Fixed the checkbox being wrongly recognized as clickable 2020-01-09 18:06:30 +01:00
topjohnwu
3a0e3c98f7 Minor adjustments to prevent crashes 2020-01-09 23:42:27 +08:00
topjohnwu
fafa92d44b Simplify rootfs persist mount 2020-01-08 22:42:54 +08:00
topjohnwu
242e64d72f Make write return something sane 2020-01-06 17:09:09 +08:00
topjohnwu
2262af728e Eliminate undefined behavior 2020-01-06 05:31:38 +08:00
topjohnwu
bce777d7c6 Set stub version in stub 2020-01-03 02:19:10 +08:00
topjohnwu
15bd2da824 Update magiskboot docs 2020-01-02 13:46:59 +08:00
topjohnwu
bd438ca288 Update docs 2020-01-02 13:45:08 +08:00
topjohnwu
e0d02a61a9 Add v7.5.0 changelog 2020-01-02 12:09:36 +08:00
topjohnwu
b3328a0ec2 Make sure shell command won't block 2020-01-02 02:19:56 +08:00
nikk gitanes
3c2041933f Remote focus fixes (classic UI) 2020-01-01 15:06:24 +08:00
孟武.尼德霍格.龍
e88b1cc443 繁體中文字串更新 2020-01-01 15:05:07 +08:00
Davy Defaud
71b05b18a0 Spelling, typographical and wording fixes
- spelling fixes
- typographical fixes : thin spaces before exclamation and interrogation marks, true apostrophes instead of single quotes, non-breaking spaces to avoid orphan words, etc.
- rewording for a better French translation
- fix various misinterpretation
2020-01-01 15:04:34 +08:00
Davy Defaud
b07b528e2a Add missing translation for "dling" string 2020-01-01 15:04:34 +08:00
Davy Defaud
1aeb6315ff Spelling & typographical fixes
- spelling fixes : complête → complète
- typographical fixes : thin spaces before exclamation and interrogation marks
- rewording for a better French translation
2020-01-01 15:04:34 +08:00
topjohnwu
1b4a3d2d9f More precise env detection in non-root 2020-01-01 14:19:24 +08:00
topjohnwu
3049a81c3b Update several scripts
- Update backup format as we might be patching multiple partitions
- Update uninstaller to remove files in persist (sepolicy.rule)
- Better handling for dtb/dtbo partition patching
2020-01-01 14:02:44 +08:00
topjohnwu
2db1e5cb74 Minor module related fixes 2019-12-30 13:21:39 +08:00
topjohnwu
78c64d39ec Add split command to magiskboot
Allow splitting image.*-dtb files to kernel and dtb
2019-12-30 13:04:39 +08:00
topjohnwu
46ba726232 Match exact "SKIPUNZIP=1" to prevent confusion 2019-12-29 15:16:04 +08:00
topjohnwu
eb26e62889 Update documentation 2019-12-29 03:28:03 +08:00
topjohnwu
7f667fed18 Allow customize.sh to skip unzip
Close #2184
2019-12-29 00:45:49 +08:00
topjohnwu
b2cb2b8b75 Reduce socket name length
Some detectors simply ban long abstract sockets
2019-12-28 21:27:55 +08:00
Zackptg5
d19f65ce4a Ignore twrp fstabs 2019-12-28 13:47:05 +08:00
topjohnwu
025b060506 Exclude META-INF in unzip 2019-12-28 02:33:35 +08:00
topjohnwu
7fa2625a03 Fix strings 2019-12-27 20:37:33 +08:00
topjohnwu
33d62d7f21 Handle sepolicy.rule when disable/remove in app 2019-12-27 19:03:45 +08:00
topjohnwu
b336655a79 Brand new module installer script
The new module installer script completely changes the way how module
installer zips are structured. More info will come later in docs.

The new installer script also supports installing sepolicy.rule to
persist partitions in order to make the module work on the next boot.
2019-12-27 17:53:27 +08:00
topjohnwu
3beffd84d6 Copy sepolicy rules to persist every boot 2019-12-22 03:44:07 -05:00
topjohnwu
02761f5f35 Fix French resources
Close #2169
2019-12-21 06:01:18 -05:00
topjohnwu
3b9f7885e0 Stop using chdir 2019-12-21 05:29:38 -05:00
topjohnwu
7668e45890 Cleanup legacy code 2019-12-17 17:15:31 -05:00
topjohnwu
695c8bc5d0 Detect package name for copying binaries
Close #2152
2019-12-17 16:38:12 -05:00
topjohnwu
06c42d05c3 Drop image based Magisk support 2019-12-15 21:01:12 -05:00
JoanVC100
404104208f Update ca-strings + Fixes 2019-12-15 19:49:12 -05:00
Rom
b4d0ad9713 Update French translation 2019-12-15 19:49:05 -05:00
topjohnwu
4f4f54a059 Remove unused code 2019-12-13 08:31:24 -05:00
topjohnwu
12fda29280 Add support for pre-init custom sepolicy patches
Close #1685
2019-12-13 06:05:12 -05:00
topjohnwu
af060b3132 General QoL changes 2019-12-13 00:37:06 -05:00
topjohnwu
8c500709e4 Remove SAR compatibility mode 2019-12-12 03:25:48 -05:00
topjohnwu
490e6a6f23 Add new API to load sepolicy rule file 2019-12-09 04:14:30 -05:00
topjohnwu
08177c3dd8 Mount persist partition mirror pre-init 2019-12-09 04:09:23 -05:00
topjohnwu
d22b9c26b6 Pull out common logic 2019-12-06 15:31:49 -05:00
topjohnwu
4bb8ad19cf Small init refactoring 2019-12-06 12:02:34 -05:00
topjohnwu
3e275b7dba Update a bunch of stuffs 2019-12-06 00:30:00 -05:00
topjohnwu
11b7076a43 Fix broken getxattr calls 2019-12-05 17:34:50 -05:00
Mevlüt TOPÇU
291c718ba2 Update Turkish language
Hi,

Update Turkish language

Merge please

Thanks
2019-12-05 17:21:42 -05:00
Hen Ry
fcd6071c57 Update de strings 2019-12-05 17:21:31 -05:00
topjohnwu
476b61c4c9 Support system_root with NVIDIA partition names
Fix #2063
2019-12-05 17:20:32 -05:00
topjohnwu
8cc5f096a2 Some minor changes 2019-12-05 17:20:32 -05:00
Alvin Wong
474d65207e Fix MagiskHide unmounting paths under /product
Fixes #2107
2019-12-03 05:42:10 -05:00
topjohnwu
03428329ef Add new verity and encryption patterns
Close #2118
2019-12-03 05:39:39 -05:00
topjohnwu
8d21988656 Support patching DTB/DTBO partition format 2019-12-02 04:34:21 -05:00
topjohnwu
72edbfc455 Some platforms do not like null Bundles 2019-11-25 19:09:54 -05:00
topjohnwu
276535dad6 Fix incorrect kmsg path
/proc/kmsg -> /dev/kmsg
2019-11-25 19:09:02 -05:00
topjohnwu
e373e59661 Make sure file descriptors are setup properly 2019-11-25 19:07:06 -05:00
topjohnwu
34bb18448c Fix compile errors 2019-11-23 17:18:55 -05:00
topjohnwu
01253f050a Use smart pointers 2019-11-23 04:57:52 -05:00
topjohnwu
5bee1c56a9 Properly use RAII to reduce complication 2019-11-22 03:01:49 -05:00
Lennoard
474cc7d56d Updated pt-BR strings (based on current
values/strings.xml)
2019-11-21 17:44:27 -05:00
topjohnwu
bffdedddb4 Fix fwrite/fread params 2019-11-21 17:43:31 -05:00
topjohnwu
fd72f658c0 Fix SQL command when creating magiskdb 2019-11-21 14:40:12 -05:00
topjohnwu
d3b5cf82d8 Small adjustments 2019-11-21 06:17:28 -05:00
topjohnwu
d26d804cc2 Migrate to generic stream implementation 2019-11-21 06:08:02 -05:00
topjohnwu
4f9a25ee89 Create generic streams on top of stdio
WIP
2019-11-20 21:48:49 -05:00
topjohnwu
bb9ce0e897 Make sepolicy dump more efficient 2019-11-20 03:47:15 -05:00
topjohnwu
d6fb9868bf Small sepolicy refactor and fixes 2019-11-19 05:20:18 -05:00
topjohnwu
9aff1a57d3 Cleanup headers 2019-11-19 02:04:47 -05:00
topjohnwu
7681fde4d0 Record mounts to be cleaned up in a vector 2019-11-19 00:16:20 -05:00
topjohnwu
d3b7b41927 Fix kmsg logging in magiskinit 2019-11-18 17:18:56 -05:00
topjohnwu
da159e4655 Better environment status detection 2019-11-16 17:38:10 -05:00
osm0sis
7f6a6016d6 magiskboot: add simple workaround for Samsung offset header variant
- some Samsung devices (e.g. Galaxy S5 SMG-900H) use a slightly different AOSP bootimg.h variant with `#define BOOT_NAME_SIZE 20` instead of 16
- since all known examples of these device images do not have anything in the NAME or CMDLINE fields, and the bootloader also accepts standard AOSP images, simply offset the SHA1/SHA256 detection by 4 bytes to avoid false positives from these images, remain an equally effective detection shortcut, and ensure a proper SHA1 checksum on repack

aosp-dtbhdt2-4offhash-seandroid-256sig-samsung_gs5-smg900h-boot.img
UNPACK CHECKSUM [00000000b11580f7d20f70297cdc31e02626def0356c82b90000000000000000]
REPACK CHECKSUM [73b18751202e56c433f89dfd1902c290eaf4eef3e167fcf03b814b59a5e984b6]
AIK CHECKSUM    [b11580f7d20f70297cdc31e02626def0356c82b9000000000000000000000000]

This patch should result in a `magiskboot unpack -n boot.img; magiskboot repack boot.img` new-boot.img matching the AIK CHECKSUM above.
2019-11-16 03:23:49 -05:00
Nick
44ed0a3279 Update RU strings
Minor improvements and fixes
2019-11-16 03:23:32 -05:00
dark-basic
9964e1bb8e Update strings.xml 2019-11-16 03:23:20 -05:00
Viktor De Pasquale
8b8f725499 Fixed log items not being refreshed
Close #2079
2019-11-16 03:20:43 -05:00
topjohnwu
bab856bce2 Move biometric settings higher in the list 2019-11-16 03:19:25 -05:00
topjohnwu
3d285b91c6 Use ContextCompat 2019-11-15 11:01:39 -05:00
vvb2060
1dc531930d Update zh-rCN translation 2019-11-15 10:55:51 -05:00
Ilya Kushnir
3d3345acac Update RU strings 2019-11-15 10:55:41 -05:00
topjohnwu
b29f0ca4d1 Support using BiometricPrompt 2019-11-14 05:42:39 -05:00
topjohnwu
576efbdc1b Move su logs out of magiskdb 2019-11-14 00:01:06 -05:00
topjohnwu
a7f0510a3e Update build.gradle 2019-11-13 17:17:21 -05:00
topjohnwu
2ef088cb60 Update RepoDao 2019-11-13 13:23:58 -05:00
topjohnwu
7c320b6fc4 Reformat RxJava extensions 2019-11-13 13:22:51 -05:00
topjohnwu
5a4c82b860 Bump stub version 2019-11-13 03:01:38 -05:00
孟武.尼德霍格.龍
9b297b752e Update strings.xml 2019-11-13 02:37:35 -05:00
vvb2060
1d6ba58ccd Update zh-rCN translation 2019-11-13 02:37:10 -05:00
topjohnwu
1542447822 Reuse buffer 2019-11-13 02:36:45 -05:00
topjohnwu
a6f0aff659 Only store UID for current user 2019-11-13 02:36:34 -05:00
topjohnwu
171ddab32b Reorganize code handling su requests 2019-11-12 03:20:07 -05:00
topjohnwu
2aee0b0be0 Refactor code for handling MagiskDB 2019-11-11 15:46:02 -05:00
vvb2060
817cdf7113 fix multiuser owner_managed mode 2019-11-11 14:12:26 -05:00
topjohnwu
1a38f25bd9 Properly invoke method 2019-11-10 14:59:19 -05:00
topjohnwu
ad40e53349 Update hacks 2019-11-09 18:17:16 -05:00
topjohnwu
a2ddf362d8 Make a.a not extend AppComponentFactory
Fix #2053
2019-11-09 16:13:15 -05:00
Ilya Kushnir
65eca31635 Updating RU translation 2019-11-09 04:40:10 -05:00
osm0sis
8b0b4a2c39 SignBoot: also catch empty streamed signature as indicating not signed
- compare against new byte[] array as a quick tell, since when streaming from a partition with an unsigned image "signature" would of course read without issue but then remain filled by zero padding, resulting in the following:
    java.io.IOException: unexpected end-of-contents marker
        at org.bouncycastle.asn1.ASN1InputStream.readObject(Unknown Source:14)
        at com.topjohnwu.signing.SignBoot$BootSignature.<init>(SignBoot.java:235)
        at com.topjohnwu.signing.SignBoot.verifySignature(SignBoot.java:144)
        at com.topjohnwu.signing.BootSigner.main(BootSigner.java:15)
        at a.a.main(a.java:20)
2019-11-09 04:39:41 -05:00
topjohnwu
c0216c0653 Get XMLs directly 2019-11-08 02:59:09 -05:00
topjohnwu
61de63a518 Cleanup manifest 2019-11-08 02:15:30 -05:00
topjohnwu
d952cc2327 Properly solve the connection problem 2019-11-07 17:41:59 -05:00
topjohnwu
46447f7cfd Proper string buffer size 2019-11-05 01:46:46 -05:00
topjohnwu
a6e62e07a2 Sort modules ignore case
Close #2024
2019-11-04 17:14:18 -05:00
topjohnwu
b1d25e0503 Reuse ALPHANUM 2019-11-04 15:42:40 -05:00
topjohnwu
25c557248c Use ContentProvider call method for communication
Previously, we use either BroadcastReceivers or Activities to receive
messages from our native daemon, but both have their own downsides.
Some OEMs blocks broadcasts if the app is not running in the background,
regardless of who the caller is. Activities on the other hand, despite
working 100% of the time, will steal the focus of the current foreground
app, even though we are just doing some logging and showing a toast.
In addition, since stubs for hiding Magisk Manager is introduced, our
only communication method is left with the broadcast option, as
only broadcasting allows targeting a specific package name, not a
component name (which will be obfuscated in the case of stubs).

To make sure root requests will work on all devices, Magisk had to do
some experiments every boot to test whether broadcast is deliverable or
not. This makes the whole thing even more complicated then ever.

So lets take a look at another kind of component in Android apps:
ContentProviders. It is a vital part of Android's ecosystem, and as far
as I know no OEMs will block requests to ContentProviders (or else
tons of functionality will break catastrophically). Starting at API 11,
the system supports calling a specific method in ContentProviders,
optionally sending extra data along with the method call. This is
perfect for the native daemon to start a communication with Magisk
Manager. Another cool thing is that we no longer need to know the
component name of the reciever, as ContentProviders identify themselves
with an "authority" name, which in Magisk Manager's case is tied to the
package name. We already have a mechanism to keep track of our current
manager package name, so this works out of the box.

So yay! No more flaky broadcast tests, no more stupid OEMs blocking
broadcasts for some bizzare reasons. This method should in theory
work on almost all devices and situations.
2019-11-04 14:32:28 -05:00
topjohnwu
472cde29b8 Allow non supported Magisk to use Magisk Manager
Close #1576
2019-11-04 03:24:27 -05:00
linar10
73525d19e9 Update strings.xml 2019-11-03 17:15:17 -05:00
topjohnwu
26618f8d73 Don't do broadcast tests from app
Running broadcast tests from the app does not accurately verifies
whether the broadcasts can be delivered when the app is not running in
the foreground, which is why we are running the test.

The only sane way to verify broadcasts is to trigger the broadcast test
directly from the daemon on boot complete. If it is not deliverable,
then activity mode shall be chosen.

In the meantime, cleanup AndroidManifest.xml
2019-11-03 17:01:09 -05:00
topjohnwu
6f7c13b814 Refactor JarMap 2019-11-03 04:45:35 -05:00
osm0sis
e7d668502c SignBoot: improve error catching/reporting
- `!= remain` shouldn't indicate "not signed", it should indicate a read error as with `!= hdr.length`
- attempt to catch unsigned images at signature read, before they make it to `BootSignature bootsig = new BootSignature(signature);` and result in the following:
    java.io.IOException: unexpected end-of-contents marker
            at org.bouncycastle.asn1.ASN1InputStream.readObject(Unknown Source:14)
            at com.topjohnwu.signing.SignBoot$BootSignature.<init>(SignBoot.java:230)
            at com.topjohnwu.signing.SignBoot.verifySignature(SignBoot.java:139)
            at com.topjohnwu.signing.BootSigner.main(BootSigner.java:15)
            at a.a.main(a.java:20)
2019-11-03 04:22:21 -05:00
osm0sis
6fd357962f scripts: fix signing in recovery with addon.d-v1
- change to $TMPDIR in addon.d.sh since recovery addon.d-v1 backup + restore leaves you in /tmp/addon.d which the restore then deletes, which would break $BOOTSIGNER execution with the following:
    libc: Fatal signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0 in tid 1078 (main), pid 1078 (main)
    Segmentation fault
- also move $BOOTSIGNER execution to after `cd $MAGISKBIN` to ensure it's in a working directory in all cases
- addon.d.sh data mount wasn't doing anything since /data has to already be mounted for the script to be running, so move it into /system/addon.d/99-magisk.sh stub script where it might be useful on recoveries that don't mount /data initially

Fixes #2013
2019-11-03 03:00:08 -05:00
topjohnwu
0c9feedb37 Support restarting app when obfuscated 2019-11-03 02:55:22 -05:00
Vladimír Kubala
14ba002cbc Update Slovak translation 2019-11-02 11:59:20 -04:00
topjohnwu
7da97489cc Add v7.4.0 release notes 2019-11-02 01:24:56 -04:00
topjohnwu
a9f11b28c8 Fix busybox scripts again 2019-11-02 01:16:54 -04:00
topjohnwu
b31d986c8d Update scripts 2019-11-02 00:41:51 -04:00
Oliver Cervera
2dad751889 Update Italian translation
- updated existing strings based on english updates
- added new strings
2019-11-02 00:28:07 -04:00
osm0sis
c85b1c56af signing: fixes for bootimg hdr_v1 and hdr_v2
- increase SignBoot bootimg header version maximum from 4 to 8 (upstream AOSP is already at 3) and make a variable for future ease
- hdr read size of 1024 bytes was too small as hdr_v1 and hdr_v2 have increased the used header page areas to 1632 and 1648 bytes, respectively, so raise this to the minimum page size of 2048 and also make a variable for future ease
- do not return "not signed" for all caught exceptions, show StackTrace for future debugging then still return false for script purposes
- correct "test keys" boot image signing strings (scripts and app) to "verity keys"
2019-11-02 00:27:56 -04:00
osm0sis
6dd34aec47 scripts: refactor and major addon.d fixes
- remove redundant addon.d.sh script bits that were covered elsewhere ($TMPDIR in util_functions.sh, find_dtbo_image in patch_dtbo_image)
- refactor addon.d.sh and flash_script.sh for simplicity and readability, and put common flashing script in util_functions.sh (as patch_boot_image), which should greatly help avoid them getting out of sync going forward and fixes compressing ramdisk support and post-patch cleanup for addon.d
- add check_data to addon.d.sh since moving stock_boot* and stock_dtbo* backups depend on it and so weren't occuring with addon.d
- fix find_manager_apk with working fallback for recovery addon.d execution (where `magisk --sqlite` will not work for hidden Manager), Manager DynAPK hiding, and print a useful log warning if an APK can't be found
2019-11-02 00:27:56 -04:00
topjohnwu
4cd154675f Random dname 2019-11-01 18:52:37 -04:00
Viktor De Pasquale
d8d72f92b3 Fixed policy toggle being impossible to cancel 2019-11-01 14:47:59 +01:00
topjohnwu
a30f5b175f Fix busybox makefiles 2019-11-01 09:38:01 -04:00
topjohnwu
8277896ca1 Make sure uninstall.sh is executed on remove 2019-11-01 03:07:12 -04:00
topjohnwu
493068c073 Attempt to rescan zygote multiple times
Close #1654
2019-11-01 02:12:28 -04:00
topjohnwu
f4299fbea8 Update BusyBox to 1.31.1 2019-10-31 18:11:10 -04:00
topjohnwu
10ce11d671 Fix config/locale issues
Close #1977
2019-10-31 17:13:06 -04:00
topjohnwu
0f34457a10 Directly store strings in viewmodel 2019-10-31 15:33:13 -04:00
topjohnwu
34c65e13bc Fix strings
Close #2012
2019-10-31 12:39:54 -04:00
John Wu
17a77e2577 Shortcut booleans 2019-10-31 02:44:25 -04:00
John Wu
0f219e5ae6 Better argument parsing logic 2019-10-31 02:44:25 -04:00
osm0sis
353c3c7d81 magiskboot: add unpack -n to help with repack validity tests
- support unpack without decompression to allow easy testing of magiskboot's header, structure and hashing handling by comparing repack checksum versus origbootimg
- make -n first to match repack
2019-10-31 02:44:25 -04:00
Rom
0a89edf3b0 Update French translation 2019-10-31 02:04:00 -04:00
topjohnwu
e7155837d7 Make sure magisk daemon won't get killed by init
According to this comment in #1880:
https://github.com/topjohnwu/Magisk/issues/1880#issuecomment-546657588

If Linux recycled our PPID, and coincidentally the process that reused
the PPID is root, AND init wants to kill the whole process group,
magiskd will get killed as a result.

There is no real way to block a SIGKILL signal, so we simply make sure
our daemon PID is the process group leader by renaming the directory.

Close #1880
2019-10-31 01:57:47 -04:00
topjohnwu
31e003bda5 Fix bug in version detection 2019-10-30 05:24:22 -04:00
topjohnwu
490e4d3180 Target the proper channel in stub 2019-10-30 05:00:52 -04:00
topjohnwu
dc9f69bab0 Minor changes 2019-10-30 04:15:53 -04:00
topjohnwu
fdf04f77f2 Send bitmap to notifications and shortcuts
On API 23+, the platform unifies the way to handle drawable
resources across processes: all drawables can be passed via Icon.
This allows us to send raw bitmap to the system without the need to
specify a resource ID. This means that we are allowed to NOT include
these drawable resources within our stub APK, since our full APK can
draw the images programmatically and send raw bitmaps to the system.
2019-10-30 01:02:53 -04:00
topjohnwu
5e87483f34 Move addAssetPath to shared 2019-10-29 07:37:19 -04:00
topjohnwu
f7aa451591 Update strings 2019-10-29 07:36:50 -04:00
topjohnwu
321d11c2c6 Move Mapping class 2019-10-29 07:21:14 -04:00
topjohnwu
ee447bc4ce Improve Keygen yet again 2019-10-26 21:11:32 -04:00
Nathan Muccino
31153e4366 Minor grammatical changes
The plural form of the words 'documentation' and 'following' are used very rarely if ever and I don't believe that they should be used in this particular context.
2019-10-26 19:26:27 -04:00
topjohnwu
7693024c29 Replace general resources with platform 2019-10-26 19:23:57 -04:00
Mevlüt TOPÇU
9628700a2f Update Turkish language
Hi,

Merge please

Thanks
2019-10-26 19:03:52 -04:00
Taras
38576173cb Update Ukrainian translation 2019-10-26 19:03:37 -04:00
topjohnwu
19a769c12e Update dependencies 2019-10-26 19:02:11 -04:00
topjohnwu
3c1db7d2f7 Fix some A/B devices unable to boot into recovery
Some newer recovery ramdisk no longer have /sbin/recovery.
Add /system/bin/recovery as an additional indication for recovery.

Close #1920
2019-10-26 17:12:35 -04:00
topjohnwu
626507093a Don't need to wrap another layer of context 2019-10-26 15:37:12 -04:00
topjohnwu
588b3d14a3 Fix typo 2019-10-24 15:37:32 -04:00
vvb2060
815efa7791 Update zh-rCN translation 2019-10-24 13:04:36 -04:00
topjohnwu
97a691ce2f Improve keygen for signing repackaged manager 2019-10-24 13:04:15 -04:00
topjohnwu
9d948f2c2b Temporary disable verification when hiding app
For some reason, Google Play Protect randomly blocks our self-signed
repackaged Magisk Manager APKs. Since we are root, the sky is our
limit, so yeah, disable package verification temporarily when installing
patched APKs, LOLz

Close #1979
2019-10-24 12:23:03 -04:00
topjohnwu
0b87108174 Move things around 2019-10-24 05:21:42 -04:00
topjohnwu
7fc7809cfc More precise channel targeting 2019-10-24 04:25:05 -04:00
topjohnwu
c30be20e49 Minor CachedValue fix 2019-10-24 04:02:01 -04:00
topjohnwu
25c64db0a1 Treat outdated stub as outdated manager 2019-10-24 03:54:16 -04:00
topjohnwu
676e9c6593 Provide upgrade path for stubs 2019-10-24 02:47:40 -04:00
topjohnwu
d459859361 Show stub version 2019-10-24 00:54:40 -04:00
topjohnwu
2be0cef446 Add proper intent filters to stub 2019-10-23 17:55:26 -04:00
topjohnwu
294db93fde Copy instead of move
We might be copying from CE to DE storage, which cannot be moved
2019-10-23 17:20:55 -04:00
topjohnwu
7f971f7173 Make sure our constructor is preserved 2019-10-23 07:51:32 -04:00
topjohnwu
5c7b59524d Fix strings 2019-10-23 07:15:28 -04:00
topjohnwu
5133e5910e Don't relaunch app immediately 2019-10-23 07:12:00 -04:00
osm0sis
1512c350df magiskboot: add SPRD dt support
- per https://github.com/USA-RedDragon/sprd-mkbootimg-tools/blob/master/dtbtool.c
- touch up hdr and table naming to be more uniform
2019-10-23 06:58:31 -04:00
あきと ミズキト
a5fc7891a6 build: Addressed file not found 2019-10-23 06:57:47 -04:00
Abhishek Dubey
3eb9633231 Add Hindi Translation 2019-10-23 06:53:46 -04:00
onevt
ac67b48247 Fix swedish translation typo 2019-10-23 06:53:07 -04:00
topjohnwu
81b65ea646 Exclude stub id mappings from git 2019-10-23 06:45:47 -04:00
topjohnwu
45c1f6bc27 Fix restore manager when running as stub 2019-10-23 06:43:08 -04:00
topjohnwu
0d31e5c8b1 Properly migrate update channels when repackaging 2019-10-23 06:41:25 -04:00
topjohnwu
6378abf454 Make stub support directBootAware 2019-10-23 05:52:32 -04:00
topjohnwu
f8fcaadb5b Hide manager with stub if feasible 2019-10-23 05:50:06 -04:00
topjohnwu
0b5fd3ee76 Only allow hide/restore app if connected 2019-10-23 05:43:01 -04:00
topjohnwu
d010cb7e42 Update stub 2019-10-23 05:19:54 -04:00
topjohnwu
71136d7347 Manually trigger broadcast tests if necessary 2019-10-22 16:04:20 -04:00
topjohnwu
a18c552ddf Guard env state behind cached objects 2019-10-22 15:37:55 -04:00
topjohnwu
9656878ef3 Actually apply the input name 2019-10-22 05:06:17 -04:00
Viktor De Pasquale
7ded7de39a Added custom dialog for setting app's name after repackaging 2019-10-22 04:52:19 -04:00
topjohnwu
0f74e89b44 Introduce component agnostic communication
Usually, the communication between native and the app is done via
sending intents to either broadcast or activity. These communication
channels are for launching root requests dialogs, sending root request
notifications (the toast you see when an app gained root access), and
root request logging.

Sending intents by am (activity manager) usually requires specifying
the component name in the format of <pkg>/<class name>. This means parts
of Magisk Manager cannot be randomized or else the native daemon is
unable to know where to send data to the app.

On modern Android (not sure which API is it introduced), it is possible
to send broadcasts to a package, not a specific component. Which
component will receive the intent depends on the intent filter declared
in AndroidManifest.xml. Since we already have a mechanism in native code
to keep track of the package name of Magisk Manager, this makes it
perfect to pass intents to Magisk Manager that have components being
randomly obfuscated (stub APKs).

There are a few caveats though. Although this broadcasting method works
perfectly fine on AOSP and most systems, there are OEMs out there
shipping ROMs blocking broadcasts unexpectedly. In order to make sure
Magisk works in all kinds of scenarios, we run actual tests every boot
to determine which communication method should be used.

We have 3 methods in total, ordered in preference:
1. Broadcasting to a package
2. Broadcasting to a specific component
3. Starting a specific activity component

Method 3 will always work on any device, but the downside is anytime
a communication happens, Magisk Manager will steal foreground focus
regardless of whether UI is drawn. Method 1 is the only way to support
obfuscated stub APKs. The communication test will test method 1 and 2,
and if Magisk Manager is able to receive the messages, it will then
update the daemon configuration to use whichever is preferable. If none
of the broadcasts can be delivered, then the fallback method 3 will be
used.
2019-10-21 13:59:04 -04:00
topjohnwu
953c40b083 Allow upgrade Magisk daemon in emulator 2019-10-21 13:58:57 -04:00
topjohnwu
271b0287d8 Pass in stub version just in case 2019-10-20 17:47:55 -04:00
topjohnwu
96a8a2a8b8 Make SuRequest default to Translucent.NoTitleBar
Close #1959
2019-10-20 17:35:38 -04:00
topjohnwu
75306f658f Revert "Drop API 17 (Android 4.2) support"
Turns out that we cannot use AndroidKeystore anyways, so we don't
actually need to drop API 17. Revert this change.
2019-10-20 07:13:03 -04:00
topjohnwu
325d9a0b86 Generate keys for signing hidden Magisk Manager 2019-10-20 06:56:33 -04:00
topjohnwu
a02493fbaa Workaround R8 bug 2019-10-19 05:44:56 -04:00
topjohnwu
9c27d691dd Drop API 17 (Android 4.2) support 2019-10-19 03:11:54 -04:00
topjohnwu
935bd01f59 Post process release APKs 2019-10-17 18:02:31 -04:00
topjohnwu
eeb5d669f6 Assign signing keystore location in config 2019-10-17 16:20:01 -04:00
topjohnwu
78daa2eb62 Do not use string resources for app label
This not only simplifies hiding stub APKs (no resource IDs involved),
but also opens the opportunity to allow users to customize whatever
app name they want after it is hidden.
2019-10-17 04:47:46 -04:00
topjohnwu
40eda05a30 Make main app fully independent from the stub
- Skip 0x7f01XXXX - 0x7f05XXXX resource IDs in the main app; they are
reserved for stub resources
- Support sending additional data from host to guest
- Use resource mapping passed from host when they are being sent
to the system framework (notifications and shortcuts)
2019-10-17 02:55:42 -04:00
topjohnwu
9f9de8c43b Obfuscate WorkManager components
Remove unused components and hack the context sent into WorkManager
2019-10-16 17:03:55 -04:00
topjohnwu
a910c8ccd8 Support stub APK upgrades 2019-10-16 05:07:29 -04:00
topjohnwu
43bda2d4a4 Allow component classname obfuscation 2019-10-16 04:38:31 -04:00
topjohnwu
c7033dd757 Allow injecting custom channel URL for debug 2019-10-16 01:54:59 -04:00
topjohnwu
5673a9bace Move system accessible resources to shared 2019-10-15 05:49:23 -04:00
topjohnwu
34ff764515 Stabilize resource IDs 2019-10-15 04:37:12 -04:00
topjohnwu
1b3a009da7 Remove unused WorkManager components 2019-10-15 04:36:09 -04:00
topjohnwu
a49002bb2c Reorganize string resources 2019-10-15 03:33:22 -04:00
Omar Kharrab
7342fc2307 Update Arabic translation 2019-10-15 02:57:43 -04:00
topjohnwu
9867a3bd60 Pedantic boot_img_hdr multi-version support 2019-10-15 01:46:29 -04:00
topjohnwu
5ffb9eaa5b Support loading Magisk Manager from stub on 9.0+
In the effort of preventing apps from crawling APK contents across the
whole installed app list to detect Magisk Manager, the solution here
is to NOT install the actual APK into the system, but instead
dynamically load the full app at runtime by a stub app. The full APK
will be stored in the application's private internal data where
non-root processes cannot read or scan.

The basis of this implementation is the class "AppComponentFactory"
that is introduced in API 28. If assigned, the system framework will
delegate app component instantiation to our custom implementation,
which allows us to do all sorts of crazy stuffs, in our case dynamically
load classes and create objects that does not exist in our APK.

There are a few challenges to achieve our goal though. First, Java
ClassLoaders follow the "delegation pattern", which means class loading
resolution will first be delegated to the parent loader before we get
a chance to do anything. This includes DexClassLoader, which is what
we will be using to load DEX files at runtime. This is a problem
because our stub app and full app share quite a lot of class names.
A custom ClassLoader, DynamicClassLoader, is created to overcome this
issue: it will always load classes in its current dex path before
delegating it to the parent.

Second, all app components (with the exception of runtime
BroadcastReceivers) are required to be declared in AndroidManifest.xml.
The full Magisk Manager has quite a lot of components (including
those from WorkManager and Room). The solution is to copy the complete
AndroidManifest.xml from the full app to the stub, and our
AppComponentFactory is responsible to construct the proper objects or
return dummy implementations in case the full APK isn't downloaded yet.

Third, other than classes, all resources required to run the full app
are also not bundled with the stub APK. We have to call an internal API
`AssetManager.addAssetPath(String)` to add our downloaded full APK into
AssetManager in order to access resources within our full app. That
internal API has existed forever, and is whitelisted from restricted
API access on modern Android versions, so it is pretty safe to use.

Fourth, on the subject of resources, some resources are not just being
used by our app at runtime. Resources such as the app icon, app label,
launch theme, basically everything referred in AndroidManifest.xml,
are used by the system to display the app properly. The system get these
resources via resource IDs and direct loading from the installed APK.
This subset of resources would have to be copied into the stub to make
the app work properly.

Fifth, resource IDs are used all over the place in XMLs and Java code.
The resource IDs in the stub and full app cannot missmatch, or
somewhere, either it be the system or AssetManager, will refer to the
incorrect resource. The full app will have to include all resources in
the stub, and all of them have to be assigned to the exact same IDs in
both APKs. To achieve this, we use AAPT2's "--emit-ids" option to dump
the resource ID mapping when building the stub, and "--stable-ids" when
building the full APK to make sure all overlapping resources in full
and stub are always assigned to the same ID.

Finally, both stub and full app have to work properly independently.
On 9.0+, the stub will have to first launch an Activity to download
the full APK before it can relaunch into the full app. On pre-9.0, the
stub should behave as it always did: download and prompt installation
to upgrade itself to full Magisk Manager. In the full app, the goal
is to introduce minimal intrusion to the code base to make sure this
whole thing is maintainable in the future. Fortunately, the solution
ends up pretty slick: all ContextWrappers in the app will be injected
with custom Contexts. The custom Contexts will return our patched
Resources object and the ClassLoader that loads itself, which will be
DynamicClassLoader in the case of running as a delegate app.
By directly patching the base Context of ContextWrappers (which covers
tons of app components) and in the Koin DI, the effect propagates deep
into every aspect of the code, making this change basically fully
transparent to almost every piece of code in full Magisk Manager.

After this commit, the stub app is able to properly download and launch
the full app, with most basic functionalities working just fine.
Do not expect Magisk Manager upgrades and hiding (repackaging) to
work properly, and some other minor issues might pop up.
This feature is still in the early WIP stages.
2019-10-14 03:49:17 -04:00
topjohnwu
b05b688267 Fix issues in stub APK 2019-10-12 03:58:45 -04:00
Simon Shi
f3d7f85063 Fix incorrect link path for /sbin/.core 2019-10-12 01:00:15 -04:00
topjohnwu
de969a9dab Downgrade recyclerview 2019-10-12 00:53:04 -04:00
topjohnwu
59fd38bbf8 Add v7.3.5 changelog 2019-10-11 16:12:32 -04:00
topjohnwu
06dc6df270 Allow dalvik runtime to load snet 2019-10-11 03:58:04 -04:00
topjohnwu
ff8460b361 Update dependencies 2019-10-11 03:29:55 -04:00
topjohnwu
674d272eaa Support pre-5.0 without GMS
Fix #1912
2019-10-11 01:46:15 -04:00
topjohnwu
c3e00c279d Legacy adb shell does not have uname 2019-10-11 01:45:06 -04:00
dark-basic
175d920c94 Update strings.xml
I'M BACK.
New translations were added.
2019-10-10 17:17:09 -04:00
topjohnwu
04920883ea Change code for handling tar files 2019-10-10 15:07:45 -04:00
topjohnwu
5e44b0b9d5 Use raw literals for scripts 2019-10-09 17:38:45 -04:00
topjohnwu
23c1a1dab8 Some code reorganizing 2019-10-09 16:01:21 -04:00
topjohnwu
f5d054b93c Add support for PXA DTBs 2019-10-08 23:49:21 -04:00
topjohnwu
d25ae5e0a9 Add __attribute__((packed)) just in case 2019-10-08 16:55:25 -04:00
topjohnwu
c42a51dcbb Add support to patch DTBH DTBs
Apparently, Qualcomm is not the only on creating weird DTB formats,
Samsung also have their own DTBH format for Exynos platforms.

Close #1902
2019-10-08 16:43:27 -04:00
topjohnwu
da3fd92b31 Prevent unsigned overflow
Close #1898
2019-10-08 15:55:27 -04:00
topjohnwu
4a45ba3c14 Update magisk_files commit hashes 2019-10-08 14:53:04 -04:00
Madis
dbc8bed234 Estonian update 2019-10-07 23:04:19 -04:00
Gaurav
f8b4190a11 Fix Typos 2019-10-07 23:03:09 -04:00
Mevlüt TOPÇU
479972e3ae Update Turkish language
Hi

Merge please

Thank's
2019-10-07 23:02:29 -04:00
Viktor De Pasquale
3ea28b0afb Fixed permission event not being executed 2019-10-07 22:58:14 -04:00
Viktor De Pasquale
2b3cc28966 Fixed snackbar not showing up for dumping files 2019-10-07 22:58:14 -04:00
Viktor De Pasquale
751642b39a Fixed back button not working on flash screen 2019-10-07 22:58:14 -04:00
topjohnwu
d6c2c821a4 Minor improvements in QCDT logic 2019-10-07 22:57:01 -04:00
Alessandro Astone
dfc65b95f7 qcdt: pad the last dtb too 2019-10-07 22:48:54 -04:00
Alessandro Astone
b45d922463 qcdt: include padding in the table length fields 2019-10-07 22:48:54 -04:00
topjohnwu
f87ee3fcf9 Refactor boot image unpack/repack code base 2019-10-07 04:35:02 -04:00
topjohnwu
e0927cd763 Add support to patch QCDT
Old Qualcomn devices have their own special QC table of DTB to
store device trees. Since patching fstab is now mandatory on Android 10,
and for older devices all early mount devices have to be included into
the fstab in DTBs, patching QCDT is crucial for rooting Android 10
on legacy devices.

Close #1876 (Thanks for getting me aware of this issue!)
2019-10-07 00:38:02 -04:00
topjohnwu
21099eabfa Small changes in DTB code 2019-10-05 17:24:53 -04:00
topjohnwu
abbd2e6b72 Update AS 2019-10-05 17:02:08 -04:00
topjohnwu
5b7ddbbb01 Fix status report UI 2019-09-30 15:32:28 -04:00
Viktor De Pasquale
6352fbb3b2 Added additional sorting for installed modules 2019-09-30 14:07:14 -04:00
topjohnwu
d3f49334e2 Move function as extension 2019-09-28 12:17:34 -04:00
topjohnwu
c4356171b3 Update dependencies block 2019-09-28 05:01:51 -04:00
topjohnwu
5c5625911d Fix back button behavior 2019-09-28 05:01:25 -04:00
topjohnwu
6a10cc9c55 Remove dependency Dexter 2019-09-28 04:23:21 -04:00
topjohnwu
6b317f918e Rename base class names 2019-09-28 03:50:11 -04:00
topjohnwu
08b528dc4f Reorganize classes
- Move base classes to its own package
- Move most logic out of MagiskActivity to MainActivity
2019-09-28 03:37:24 -04:00
topjohnwu
fc886a5a47 Merge Teanity into sources 2019-09-28 01:56:16 -04:00
topjohnwu
0cb90e2e55 Update BasePreferenceFragment 2019-09-27 19:54:03 -04:00
topjohnwu
64113a69b4 Remove unused warnings 2019-09-26 13:54:40 -04:00
topjohnwu
544bb7459c Don't pass by reference 2019-09-26 03:49:05 -04:00
Viktor De Pasquale
578a50b464 Added hiding actions on notifications typed "Download" 2019-09-26 03:15:46 -04:00
topjohnwu
3d4081d0af Fix patch verity and forceencrypt 2019-09-26 03:14:56 -04:00
topjohnwu
b763b81f56 Use mutex_guard to lock su_info 2019-09-26 01:49:50 -04:00
topjohnwu
947dae4900 Rename classes and small adjustments 2019-09-25 23:55:39 -04:00
topjohnwu
debd1d7d54 Update canary channel links 2019-09-24 03:09:02 -04:00
osm0sis
cba0d04000 magiskpolicy: rules: standardize update_engine sepolicy when rooted
The state of ROM A/B OTA addon.d-v2 support is an inconsistent mess currently:
- LineageOS builds userdebug with permissive update_engine domain, OmniROM builds userdebug with a more restricted update_engine domain, and CarbonROM builds user with a hybrid closer to Omni's
- addon.d-v2 scripts cannot function to the full extent they should when there is a more restricted update_engine domain sepolicy in place, which is likely why Lineage made update_engine completely permissive

Evidence for the above:
- many addon.d-v2 scripts only work (or fully work) on Lineage, see below
- Magisk's addon.d-v2 script would work on Lineage without issue, but would work on Carbon and Omni only if further allow rules were added for basic things like "file read" and "dir search" suggesting these ROMs' addon.d-v2 is severely limited
- Omni includes a /system/addon.d/69-gapps.sh script with the ROM itself (despite shipping without GApps), and with Magisk's more permissive sepolicy and no GApps installed it will remove important ROM files during OTA, resulting in a bootloop; the issue with shipping this script was therefore masked by Omni's overly restrictive update_engine sepolicy not allowing the script to function as intended

The solution:
- guarantee a consistent addon.d-v2 experience for users across ROMs when rooted with Magisk by making update_engine permissive as Lineage has
- hopefully ROMs can work together to come up with something standard for unrooted addon.d-v2 function
2019-09-23 07:55:25 -04:00
topjohnwu
695e7e6da0 Create product mirror if /system/product exist 2019-09-23 06:52:24 -04:00
topjohnwu
4cd4bfa1d7 Add ':' to allowed characters for magiskhide process name 2019-09-22 16:17:51 -04:00
topjohnwu
16b400964b Update vars for 2SI 2019-09-22 06:45:23 -04:00
topjohnwu
cf2d02c0dd Don't wipe ramdisk when A-only SAR 2019-09-22 06:17:54 -04:00
topjohnwu
0fcd0de0d1 Fix potential crash when traversing cpio entries 2019-09-22 06:15:19 -04:00
topjohnwu
748a35774f Support patching fstab in ramdisk for A-only 2SI 2019-09-22 05:30:04 -04:00
topjohnwu
a52a3e38ed Change some class names 2019-09-22 05:20:51 -04:00
topjohnwu
ee0cef06a6 Add support for A-only 2SI 2019-09-22 05:15:31 -04:00
topjohnwu
0e5a113a0c Support patching mnt_point in fstab in dtb 2019-09-22 04:17:15 -04:00
topjohnwu
a1ccd44013 Change MagiskBoot patch behavior
Use environment variables to toggle configurations for patching ramdisk
2019-09-21 05:55:23 -04:00
topjohnwu
4d91e50d6d Update dtb patch to not use in-place modification 2019-09-21 05:30:04 -04:00
topjohnwu
120668c7bc Revise dtb commands CLI 2019-09-20 03:53:58 -04:00
topjohnwu
d81ccde569 Pretty print dtb content 2019-09-20 03:05:14 -04:00
topjohnwu
e8581b4adb Fix links in docs 2019-09-19 05:48:21 -04:00
topjohnwu
19906575a3 Update v7.3.4 changelogs 2019-09-19 05:29:45 -04:00
topjohnwu
9329094a4e Update documentations 2019-09-19 05:00:29 -04:00
topjohnwu
b44f5122fd Pass int directly as pointer 2019-09-19 00:13:42 -04:00
topjohnwu
17981730a4 Remove load_persist_props in post-fs-data
Close #1607
2019-09-17 13:50:53 -04:00
topjohnwu
53de6da26c Only print relevant info according to header version 2019-09-17 05:11:09 -04:00
topjohnwu
3e30ccdeee Make parsing behaves according to header
Close #1778. Close #1848
2019-09-17 05:01:04 -04:00
topjohnwu
baaaf7d5de Fully match zygote/usap process names 2019-09-17 01:50:45 -04:00
hoijui
45d8d139a9 Cross-link-ify install instructions 2019-09-17 01:28:41 -04:00
topjohnwu
fe644e10d0 Make sure post-fs-data is first ran
Close #1601
2019-09-17 00:21:07 -04:00
impulsiva
f383d11d10 Update TR strings
Fixed several typos and mistranslated strings.
2019-09-13 16:29:25 -04:00
topjohnwu
ef1b928532 LD_LIBRARY_PATH patch for apex should not propagate
Fix #1832
2019-09-13 15:22:49 -04:00
topjohnwu
6e46d394b1 Fix su_info cache yet again... 2019-09-13 14:05:28 -04:00
topjohnwu
f109038d12 Hardcode shell uid to 2000 2019-09-13 03:14:58 -04:00
topjohnwu
e31e687602 Allow ADB shell to remove modules and reboot 2019-09-13 03:14:21 -04:00
topjohnwu
86bfb22d4c Override module when .replace is found 2019-09-12 16:08:30 -04:00
topjohnwu
3f057367e3 Update dependencies 2019-09-12 12:50:44 -04:00
topjohnwu
3d7ed5820e Update busybox
Close #1520
2019-09-11 23:06:49 -04:00
topjohnwu
0118f2efa7 Merge styles 2019-09-09 19:58:19 -04:00
topjohnwu
15312e4709 Remove unused resources 2019-09-09 17:57:25 -04:00
topjohnwu
bf1568a73a Fix strings 2019-09-09 17:43:16 -04:00
Salim B
13a2520ea5 Fix typo and add link to GH issues 2019-09-09 17:39:15 -04:00
Frieder Bluemle
f53238f206 Update Gradle wrapper to 5.6.2 2019-09-09 17:38:54 -04:00
Rom
9375748d9b Update French translation 2019-09-09 17:38:22 -04:00
Gozzwip
201df54e79 new strings added 2019-09-09 17:37:41 -04:00
vvb2060
0b54fe477b Update zh-rCN translation 2019-09-09 17:37:08 -04:00
Ilya Kushnir
4119e6669e Update RU strings 2019-09-09 17:36:57 -04:00
Taras
d33e5226b3 Update Ukrainian translation 2019-09-09 17:36:39 -04:00
topjohnwu
d73f39c706 Fix manager update after hidden
Fix #1828
2019-09-09 17:24:29 -04:00
topjohnwu
087b451e17 Fix strings 2019-09-08 01:19:33 -04:00
topjohnwu
86481c74ff Allow user to select recovery mode
Close #1674
2019-09-08 00:44:26 -04:00
topjohnwu
5b937fb1fa Random changes 2019-09-05 11:36:48 -04:00
topjohnwu
ff828116bc Only cache magisk zips 2019-09-05 11:26:35 -04:00
topjohnwu
ee39616a8b Update emulator.sh to support all AVD images 2019-09-04 11:12:09 -04:00
topjohnwu
cdb53ca049 Fix su_info cache bug 2019-09-04 11:04:59 -04:00
topjohnwu
8cf475f708 Add scripts to setup Magisk in AVD 2019-09-03 17:06:14 -04:00
topjohnwu
0cb449e1d6 We need to support pre-5.0 platforms 2019-09-03 16:28:27 -04:00
topjohnwu
e6adb7abca Make kotlin version a variable globally 2019-09-03 16:27:57 -04:00
topjohnwu
cfad7dd317 Sanitize magiskhide targets
Fix #1785
2019-09-01 14:16:12 +08:00
topjohnwu
dd35224f92 Minor adjustments to exec_sql 2019-09-01 13:58:50 +08:00
Chris Renshaw
1283590eeb scripts: prepare addon.d for recovery addon.d-v2 support
- naturally there's no `su` in recovery
- major refactor for common actions and simplicity
2019-09-01 02:19:59 +08:00
osm0sis
dca3fe396f scripts: hide expected x86 busybox error on arm
- Magisk Manager installs have busybox in the $PATH before extracting busybox from update-binary so an error from busybox ash (as sh) attempting to parse the x86 busybox like a shell script would be shown:
./bin/busybox: line 1: syntax error: unexpected "("
- this will only occur when ash tries to run a binary it can't handle, so basically only with x86 binary on an arm* device
2019-09-01 02:19:59 +08:00
cristisilaghi
8d87eae11b Update RO 2019-09-01 02:17:13 +08:00
Frieder Bluemle
fd7eaacae0 Update Gradle wrapper to 5.6.1 2019-09-01 01:17:22 +08:00
topjohnwu
fba33cbbe9 Fix strings 2019-09-01 01:15:15 +08:00
Gozzwip
950ffcd790 Translation is done 2019-09-01 01:12:15 +08:00
Gozzwip
c178299013 Translation is done 2019-09-01 01:12:08 +08:00
Rom
5d17c1f588 French translation update 2019-09-01 01:11:54 +08:00
linar10
a75c00d94e Update strings.xml 2019-09-01 01:11:40 +08:00
Albert I
cd19517414 app: l10n: Update Indonesian translations
* Update the wordings
* Delocalize "Core Only" strings
* Add 3 months worth of missing translations

Signed-off-by: Albert I <kras@raphielgang.org>
2019-09-01 01:11:33 +08:00
Gozzwip
155f39aab5 Some changes 2019-09-01 01:11:06 +08:00
Mevlüt TOPÇU
4514d0b467 Update Turkish language
Hi,

Merge please

Thanks.
2019-09-01 01:10:51 +08:00
zertyuiop
6f4a938a31 Added missing strings 2019-09-01 01:10:33 +08:00
VergeDX
1303ea95dd Update & Fix Chinese Translate. 2019-09-01 01:10:19 +08:00
Oliver Cervera
727fe1bd15 Update Italian translation
Added new strings
2019-09-01 01:10:10 +08:00
topjohnwu
64ebc977e9 Small magic mount adjustments 2019-08-31 21:53:47 +08:00
topjohnwu
e89c50d934 Support /system/product wihtout /product
Fix #1676
2019-08-29 22:56:34 +08:00
topjohnwu
c859ddfb8f Upgrade Kotlin 2019-08-27 02:30:10 +08:00
topjohnwu
a6126c5eda Cosmetic changes 2019-08-23 03:05:41 +08:00
topjohnwu
85d9bd9106 Fix compile errors 2019-08-23 00:30:21 +08:00
Viktor De Pasquale
39e9622205 Fixed magisk version
Added refreshing versions before and after the request to remote
2019-08-22 08:03:17 +02:00
topjohnwu
021994c9f3 Clean elf after building shared binaries 2019-08-22 02:51:17 +08:00
topjohnwu
2e7ce2a769 Update gradle files 2019-08-21 10:38:09 +08:00
topjohnwu
84f0ff2fad Fix manager package name database management 2019-08-12 03:31:59 -07:00
topjohnwu
e6561e5f84 Fix XML parsing Kotlin error 2019-08-12 03:14:51 -07:00
topjohnwu
5fa452aa74 Multiple minor changes 2019-08-12 01:54:33 -07:00
topjohnwu
2225ccb146 Flush settings to persistent storage 2019-08-12 00:05:19 -07:00
topjohnwu
5aafc78847 Cleanup const 2019-08-11 23:53:43 -07:00
topjohnwu
0d03833cff Name module zips with version code 2019-08-11 22:46:39 -07:00
topjohnwu
a797d5d396 Update snet extension 2019-08-08 04:18:32 -07:00
topjohnwu
f2494374f8 Eliminate any traces of Java in app 2019-08-08 00:59:23 -07:00
topjohnwu
48395ba860 Remove unused files 2019-08-08 00:29:27 -07:00
topjohnwu
5ba5f5f94e Observe network connnectivity
Observe internet connectivity will ping google.com
2019-08-07 22:26:44 -07:00
topjohnwu
42ce6fd334 Workaround stupid Moshi proguard rules 2019-08-07 22:26:25 -07:00
Viktor De Pasquale
f5c3ee3ae1 Added elements of UI to "hide list" 2019-08-07 03:07:18 -07:00
Viktor De Pasquale
3c7ece1605 Fixed not showing current version
Current version was not displaying under circumstances that involve loss of connection. Versions are displayed whether the device is connected or not.
2019-08-07 03:07:18 -07:00
Viktor De Pasquale
870efc49ea Fixed using mapping function incorrectly 2019-08-07 03:07:18 -07:00
Viktor De Pasquale
085ede6d93 Added simple ui blocks for whenever connection drops out 2019-08-07 03:07:18 -07:00
Viktor De Pasquale
4ef19d17da Added a flag for connection status
Reactively updated flag which only checks whether the "data" / "wifi" / "ethernet" is plugged in or enabled. If the user connects to the wifi but has no actual connection, the app will never know.
Please refrain from using other access methods (like pinging a host), it can get picked up by a VPN or other methods and possibly expose MM.
2019-08-07 03:07:18 -07:00
topjohnwu
223913c30a Remove unnecessary App usage 2019-08-05 00:21:38 -07:00
topjohnwu
010e4de4e1 Introduce DynamicClassLoader 2019-08-04 23:49:09 -07:00
topjohnwu
41134466ed Upgrade dependencies 2019-08-04 18:33:20 -07:00
topjohnwu
8f07747452 Remove net module 2019-08-04 18:33:20 -07:00
topjohnwu
eb5ce5be1e Fix saving logs
Fix #1722
2019-08-04 14:17:01 -07:00
topjohnwu
71d855e836 Cleanup more code 2019-08-04 13:47:14 -07:00
topjohnwu
33b7ab593c Migrate PatchAPK to Kotlin 2019-08-04 13:00:27 -07:00
topjohnwu
8706d834b4 Update Android Studio 2019-08-02 21:26:00 -07:00
topjohnwu
7cfab33ebb Make sure DownloadService always start with app context 2019-08-02 01:21:22 -07:00
topjohnwu
1ababc8c7f RepoDB does not need to run on main thread 2019-08-02 01:20:16 -07:00
topjohnwu
1f75e63c37 Fix crashes in MarkdownWindow
Fix #1628
2019-08-02 01:16:04 -07:00
topjohnwu
cb3f9b9740 More tweaking to Rx pipeline 2019-08-01 23:08:58 -07:00
topjohnwu
9784353223 Fix ActivityTracker
Koin does not support nullable types
2019-07-29 04:18:05 -07:00
topjohnwu
7d93ca5c73 Modernize MagiskInstaller 2019-07-29 04:05:54 -07:00
topjohnwu
ac20063e86 Disable cache for Magisk Manager 2019-07-29 03:56:35 -07:00
topjohnwu
debaec32af Remove old download progress update system 2019-07-29 00:42:53 -07:00
topjohnwu
0e9b71e7a9 Show notification on error 2019-07-29 00:37:01 -07:00
topjohnwu
85f5ff3c14 Download Magisk Manager via new service 2019-07-29 00:26:18 -07:00
Frieder Bluemle
3d81f167ea Update Gradle wrapper to 5.5.1 2019-07-28 15:32:33 -07:00
Taras
fb70a2e52d Update Ukrainian translation 2019-07-28 15:32:01 -07:00
JoanVC100
460e85a1b5 New lines added and fixs 2019-07-28 15:31:49 -07:00
cheese1
539b64bd57 update german language-file 2019-07-28 15:31:24 -07:00
linar10
90e38a06a2 Update strings.xml 2019-07-28 15:31:03 -07:00
zertyuiop
09ab910630 Added missing strings 2019-07-28 15:30:54 -07:00
topjohnwu
c15f80b33f Improve Rx pipeline 2019-07-28 14:49:06 -07:00
topjohnwu
b2e6ba3c4a Move no thanks from dialogs 2019-07-28 03:54:46 -07:00
topjohnwu
b16f696b0e Cleanups 2019-07-28 03:47:07 -07:00
topjohnwu
9adfb382e8 Only launch FlashActivity if app is foreground 2019-07-28 03:38:27 -07:00
topjohnwu
44368383f4 Fix fetching repo ordering 2019-07-28 02:21:55 -07:00
topjohnwu
d1ff7e0ffe Move extensions to its own package 2019-07-28 02:10:22 -07:00
topjohnwu
42e7db8d13 Modernize Repo class for Magisk modules
- Use Kotlin
- Use Room database
- Use retrofit for networking
- Use RxJava pipeline for repo updating
2019-07-28 01:54:34 -07:00
topjohnwu
0c17ea5755 Migrate Magisk Modules to Kotlin 2019-07-27 15:46:44 -07:00
topjohnwu
cdaff5b39c Update module download pipeline 2019-07-26 02:26:02 -07:00
topjohnwu
2b1b970e78 Update dependencies 2019-07-26 02:00:42 -07:00
topjohnwu
0aebc0a8e3 Use new service to download uninstall.zip 2019-07-25 03:10:24 -07:00
topjohnwu
c3a89f589e Download to proper filename 2019-07-25 01:54:42 -07:00
topjohnwu
971cd73fb3 Dismiss notification on error 2019-07-25 01:37:47 -07:00
topjohnwu
1947860d61 Dismiss notification after flashing 2019-07-25 01:05:06 -07:00
topjohnwu
55aaa421e8 Directly download to target location 2019-07-23 01:31:59 -07:00
topjohnwu
a8932706d8 Consolidate Magisk download subject 2019-07-23 00:55:12 -07:00
topjohnwu
a97972aac0 Update notification once per second 2019-07-23 00:33:28 -07:00
topjohnwu
094c3d559a Minor fixes and cleanups 2019-07-22 01:49:21 -07:00
topjohnwu
6fb032b3c2 Clean ups 2019-07-20 22:37:34 -07:00
topjohnwu
8ca188f4d4 Stream and process module zips 2019-07-20 21:04:06 -07:00
topjohnwu
746a1d8d59 Directly download to magisk.zip for flashing 2019-07-20 14:57:03 -07:00
topjohnwu
63c5e00d86 Update Android Studio 2019-07-20 14:57:03 -07:00
Viktor De Pasquale
9d2e5d6665 Updated design for custom channel field so it matches the other dialog 2019-07-20 14:57:03 -07:00
Viktor De Pasquale
f6045bf8b5 Added custom dialog for download location only 2019-07-20 14:57:03 -07:00
Viktor De Pasquale
e83f40d5c5 Added actions for opening files in the file browser
No icons are added at this time, so crashes might occur
2019-07-20 14:57:03 -07:00
Viktor De Pasquale
e5118418b2 Added option to have custom download location
The location is automatically added to list of supported paths for caching
2019-07-20 14:57:03 -07:00
Viktor De Pasquale
7cd814d917 Updated service to use extra transformer so the service itself is not plagued by unnecessary code 2019-07-20 14:57:03 -07:00
Viktor De Pasquale
78282c1a49 Removed unused entry 2019-07-20 14:57:03 -07:00
Viktor De Pasquale
fd4214ccf3 Fixed minor bugs regarding notification cancellation 2019-07-20 14:57:03 -07:00
Viktor De Pasquale
0785945635 Added appending installers to modules 2019-07-20 14:57:03 -07:00
Viktor De Pasquale
967bdeae7b Updated service architecture and extracted useful tools to separate class 2019-07-20 14:57:03 -07:00
Viktor De Pasquale
452db51669 Updated flash location so it's one layer deeper preventing accidental cache deletion 2019-07-20 14:57:03 -07:00
Viktor De Pasquale
5875ced367 Fixed launching activities on newer systems 2019-07-20 14:57:03 -07:00
Viktor De Pasquale
fbac6bcfd0 Fixed substrate handling multiple downloads at once 2019-07-20 14:57:03 -07:00
Viktor De Pasquale
0dcd3ece9d Updated downloading modules 2019-07-20 14:57:03 -07:00
Viktor De Pasquale
224fff89e3 Updated object usage for module subjects 2019-07-20 14:57:03 -07:00
Viktor De Pasquale
22e73644f9 Added option to run service in foreground right away 2019-07-20 14:57:03 -07:00
Viktor De Pasquale
6a0f6ab319 Updated magisk installer so it uses predownloaded file 2019-07-20 14:57:03 -07:00
Viktor De Pasquale
88a394836f Replaced all install methods with the download service 2019-07-20 14:57:03 -07:00
Viktor De Pasquale
f822c1c2e4 Added default to flash configuration 2019-07-20 14:57:03 -07:00
Viktor De Pasquale
1d16d980b3 Added second slot flashing capability 2019-07-20 14:57:03 -07:00
Viktor De Pasquale
501b18f986 Added default value to magisk subject 2019-07-20 14:57:03 -07:00
Viktor De Pasquale
21ed759e53 Removed duplicate helper 2019-07-20 14:57:03 -07:00
Viktor De Pasquale
8d50dfd93c Fixed overwriting file in download mode
Added prevention of copying itself to itself
2019-07-20 14:57:03 -07:00
Viktor De Pasquale
51e40dd98c Fixed crashes caused by file exposure beyond app bounds 2019-07-20 14:57:03 -07:00
Viktor De Pasquale
b2048379af Fixed uris so in case there's no additional the data one (with zips) is selected instead 2019-07-20 14:57:03 -07:00
Viktor De Pasquale
011539f6f1 Added permission requirements for using service 2019-07-20 14:57:03 -07:00
Viktor De Pasquale
5457c3803f Added remaining methods of installation/flashing/uninstall to service
Updated parameters of patching step and fixed new ordered flashing format
2019-07-20 14:57:03 -07:00
Viktor De Pasquale
b3d777bb6c Updated configuration to hold data when necessary 2019-07-20 14:57:03 -07:00
Viktor De Pasquale
12e00c3054 Updated method naming scheme
Added new configurations
Added flashing methods and annotated viewModel's uri as deprecated in function
2019-07-20 14:57:03 -07:00
Viktor De Pasquale
40b683111c Added an option to disable the new caching mechanism completely 2019-07-20 14:57:03 -07:00
Viktor De Pasquale
9542ca773f Added new CompoundDownloadService which will encapsulate all downloads and should manage post-download events as well
As of now it's still in a development stage and isn't connected to anything
2019-07-20 14:57:03 -07:00
Viktor De Pasquale
8af832a496 Added several calls to FlashActivity so it manages its launch parameters by itself
Its reach will be deepened further in the future commits
2019-07-20 14:57:03 -07:00
Viktor De Pasquale
6836130fda Added overloaded method call for progress notification so it accepts foreign context 2019-07-20 14:57:03 -07:00
Viktor De Pasquale
724893879f Added option to intercept progress while copying files 2019-07-20 14:57:03 -07:00
topjohnwu
736729f5ef Maintain a list of pre-init mounts
Keep track of everything to unmount
2019-07-16 23:54:52 -07:00
topjohnwu
aa47966347 Fix raw_data move constructor 2019-07-16 23:30:54 -07:00
Gozzwip
d64d12afe8 Some fixes 2019-07-16 01:20:10 -07:00
topjohnwu
1f8df419c4 Extract x86 busybox first
Fix #1600
2019-07-16 01:16:29 -07:00
topjohnwu
7ba8202af5 Introduce new root overlay system 2019-07-16 01:08:28 -07:00
topjohnwu
d7b691cf59 Move libutil internal headers out of include path 2019-07-14 23:55:52 -07:00
osm0sis
7058d5e4cd magiskpolicy: rules: fix writing to loop devices using upstream sepolicy 2019-07-14 22:09:26 -07:00
topjohnwu
52fd508fea Do not use std::random_device
Directly read from urandom instead of using std::random_device.
libc++ will use iostream under-the-hood, which brings significant
binary size increase that is not welcomed, especially in magiskinit.
2019-07-14 21:56:21 -07:00
topjohnwu
41045b62dc Introduce more randomness
- Use C++ random generator instead of old and broken rand()
- Randomize string length to piss off stupid detectors
2019-07-14 17:42:49 -07:00
Viktor De Pasquale
188ea2644a Updated downloading magisk to pull the zip from cache if eligible 2019-07-08 11:40:02 -07:00
topjohnwu
4c8f357978 Update to support updated FrankeNDK 2019-07-07 17:38:57 -07:00
Marius
4bb2fd6ba6 Fix typos in german translation 2019-07-07 12:40:20 -07:00
osm0sis
33c9f74508 magiskpolicy: rules: fix rootfs operations with SAR Magisk
- while many newer devices cannot allow / (system partition) to be mounted rw due to compressed fs (e.g. erofs) or logical partitions, it should remain possible to alter rootfs files/directories on those that previously allowed it
2019-07-07 12:33:20 -07:00
osm0sis
f53fe67372 BootSigner: support setting name with no cert/key pair supplied 2019-07-07 12:33:02 -07:00
topjohnwu
51ff724691 Unblock all signals in root shell process
Fix #1563
2019-07-07 12:30:57 -07:00
topjohnwu
291bf93f9d Proper timing 2019-07-07 12:20:47 -07:00
topjohnwu
5fcd629f16 Rearrange su daemon routine 2019-07-07 12:20:19 -07:00
topjohnwu
ab90901793 Use C++ smart pointer for caching su_info 2019-07-07 00:31:49 -07:00
topjohnwu
4f206fd918 Fix compile errors 2019-07-06 23:04:24 -07:00
topjohnwu
7233285437 Use relative symbolic links 2019-07-04 17:58:46 -07:00
John Wu
8e348a11c2 Support non standard image headers
Some Samsung device uses the header version field as extra section size
2019-07-04 11:09:45 -07:00
osm0sis
085ea6d0a1 SignBoot: use verity keys not testkey to correctly follow AOSP 2019-07-04 11:09:45 -07:00
osm0sis
aaf88b1895 BootSigner: add ability to change target name
- supports signing /recovery images
- add as final argument and default to /boot if not supplied so installer scripts remain the same
2019-07-04 11:09:45 -07:00
osm0sis
4f4a9412a3 SignBoot: updates from AOSP for boot_img_hdr_v1 and v2
"Allow recovery-dtbo in recovery.img to be signed" by Hridya Valsaraju:
9bb9f8f857

"boot_signer should support boot header version 2" by Hridya Valsaraju
590e58454d
2019-07-04 11:09:45 -07:00
topjohnwu
a92e039363 Split util headers 2019-07-01 22:58:19 -07:00
topjohnwu
33aa4ca4b7 Move libmincrypt into separate repo 2019-06-30 19:53:03 -07:00
topjohnwu
05658cafc7 Fix typo causing sbin clone failure 2019-06-30 19:24:14 -07:00
topjohnwu
ff3710de66 Minor code changes across all sources 2019-06-30 19:09:31 -07:00
topjohnwu
db8dd9f186 Init code rearrangement 2019-06-30 11:39:13 -07:00
topjohnwu
e8b73ba6d1 Add separate product partition support 2019-06-29 14:19:10 -07:00
topjohnwu
f1112fdf37 Logical Resizable Android Partitions support
The way how logical partition, or "Logical Resizable Android Partitions"
as they say in AOSP source code, is setup makes it impossible to early
mount the partitions from the shared super partition with just
a few lines of code; in fact, AOSP has a whole "fs_mgr" folder which
consist of multiple complex libraries, with 15K lines of code just
to deal with the device mapper shenanigans.

In order to keep the already overly complicated MagiskInit more
managable, I chose NOT to go the route of including fs_mgr directly
into MagiskInit. Luckily, starting from Android Q, Google decided to
split init startup into 3 stages, with the first stage doing _only_
early mount. This is great news, because we can simply let the stock
init do its own thing for us, and we intercept the bootup sequence.

So the workflow can be visualized roughly below:

Magisk First Stage --> First Stage Mount --> Magisk Second Stage --+
   (MagiskInit)         (Original Init)         (MagiskInit)       +
                                                                   +
                                                                   +
     ...Rest of the boot... <-- Second Stage <-- Selinux Setup  <--+
      (__________________ Original Init ____________________)

The catch here is that after doing all the first stage mounting, /init
will pivot /system as root directory (/), leaving us impossible to
regain control after we hand it over. So the solution here is to patch
fstab in /first_stage_ramdisk on-the-fly to redirect /system to
/system_root, making the original init do all the hard work for
us and mount required early mount partitions, but skips the step of
switching root directory. It will also conveniently hand over execution
back to MagiskInit, which we will reuse the routine for patching
root directory in normal system-as-root situations.
2019-06-29 01:25:54 -07:00
osm0sis
a48c4f9e05 magiskboot: don't clobber /overlay with cpio restore anymore
- Magisk "dirty" flashes would remove the /overlay directory which might have been put there by a custom kernel or other mod
- this is a leftover from when Magisk itself used /overlay for placing init.magisk.rc, so just remove this file specifically and leave the rest intact
2019-06-27 18:59:54 -04:00
Rom
19a521d2e9 French translation update 2019-06-27 18:59:29 -04:00
cristisilaghi
dd6e55ac31 [ro] Add translators 2019-06-27 04:10:51 -04:00
osm0sis
b1e63f0f14 Manager: fix ModulesFragment reboot menu
- correct 'booloader' typo breaking bootloader entry
- remove extra bootloader entry Shell.su line which is unnecessary since it's covered by reboot()
- revert to using `reboot recovery` for recovery entry since `svc power reboot recovery` triggers a very disconcerting "Factory data reset" reboot dialog on many devices
- add Reboot to EDL mode option for good measure
2019-06-27 04:09:41 -04:00
topjohnwu
b0e49a4cc8 Kill blastula pool when magiskhide init 2019-06-27 00:49:27 -07:00
topjohnwu
1e94517a72 MagiskHide is coming back strong 2019-06-27 00:28:34 -07:00
topjohnwu
98f60216ac Temporary disable MagiskHide by default
Latest Android Q beta does not like when zygote is ptraced on
boot. Disable it for now until further investigation.
2019-06-25 23:32:07 -07:00
topjohnwu
e29b712108 Start Magisk in SAR 2019-06-25 23:31:59 -07:00
topjohnwu
a462435f2f Load custom sepolicy 2019-06-25 21:34:02 -07:00
topjohnwu
911b8273fe Fix typo in sbin clone 2019-06-25 03:35:25 -07:00
topjohnwu
09935e591a Proper SARInit test 2019-06-25 03:34:54 -07:00
topjohnwu
4a212dba35 Early mount partitions in SAR 2019-06-25 02:47:16 -07:00
topjohnwu
aac9e85e04 More Q cleanup 2019-06-25 02:38:34 -07:00
topjohnwu
bb67a837d3 Adjust class structures 2019-06-24 01:50:47 -07:00
topjohnwu
6cde695194 Remove Q dirty hacks in SARCompat 2019-06-24 01:31:42 -07:00
topjohnwu
a1a1ac0bbb Add sbin overlay to system-as-root 2019-06-24 01:21:33 -07:00
topjohnwu
9ec8bc2166 Boot MagiskInit as actual system-as-root
WIP, no customization. DO NOT USE YET!
2019-06-23 15:14:47 -07:00
topjohnwu
28cd6a75e7 Add missing functions in bionic 2019-06-23 14:54:48 -07:00
topjohnwu
4cc7aced15 Add new util function 2019-06-23 03:53:41 -07:00
topjohnwu
1058aeb04f Label current SAR impl as compat
The current system-as-root magiskinit implementation (converting
root directory in system partition to legacy rootfs setup) is now
considered as backwards compatible only.

The new implementation that is hide and Android Q friendly is coming soon.
2019-06-22 03:18:45 -07:00
topjohnwu
cfec0db947 Delay mounting sbin overlay 2019-06-22 03:14:33 -07:00
topjohnwu
120bd6cd68 v7.3.2 Changelog 2019-06-21 01:12:50 -07:00
JoanVC100
9aef06d1b8 Fix traductors 2019-06-21 04:00:54 -04:00
topjohnwu
e6e9dd751c Upgrade Kotlin 2019-06-21 00:53:40 -07:00
Viktor De Pasquale
5dd677756f Fixed multiple fetch tasks running at once
Disposing wouldn't help since the shell doesn't appear to handle concurrency well
2019-06-21 00:36:37 -07:00
Viktor De Pasquale
b77c590910 Fixed the searchView being collapsed after searching through it
Now they have their state synced with viewModel to allow continuity
2019-06-21 00:36:37 -07:00
Viktor De Pasquale
7e5f2822ae Fix superuser fragment crashes
Fix superuser screen encountering inconsistencies when refreshing the data rapidly
2019-06-21 00:36:01 -07:00
topjohnwu
12bbc7fd6b Update v7.3.1 changelog 2019-06-17 22:15:38 -07:00
topjohnwu
bf9ac8252b Cleanup UpdateInfo 2019-06-16 16:47:30 -07:00
topjohnwu
4a3f5dc619 Cleanups 2019-06-16 14:35:51 -07:00
Viktor De Pasquale
ca156befbd Fixed mapping generic pairs to policy crashing when no policy is found
The policy (app) is now deleted when found invalid (uninstalled)
2019-06-16 16:50:08 -04:00
Viktor De Pasquale
4db41e2ac4 Added attempted fix for parsing data off default thread 2019-06-16 16:50:08 -04:00
Viktor De Pasquale
982a43fce1 Moved diff computation of policy list to the background thread 2019-06-16 16:50:08 -04:00
Viktor De Pasquale
dd76a74e1c Fixed fast scroll button crashing while scrolling to undefined position 2019-06-16 16:50:08 -04:00
Viktor De Pasquale
70cb52b2c7 Fixed fast scroll button being visible when log is empty 2019-06-16 16:50:08 -04:00
topjohnwu
5c7f69acaa Separate SAR and legacy implementation 2019-06-16 12:45:32 -07:00
topjohnwu
f1d9015e5f Move load kernel info out of class 2019-06-15 22:25:09 -07:00
topjohnwu
e8d900c58e Fix typo 2019-06-15 18:12:12 -07:00
topjohnwu
a6241ae912 Fix magiskboot unpack option parsing 2019-06-15 16:15:12 -07:00
topjohnwu
4a697ca2ec Add 7.3.0 changelog 2019-06-14 22:39:31 -07:00
topjohnwu
58bec7f2c9 Update dependencies 2019-06-14 22:39:31 -07:00
Wenlin Shen
213f84985c Update Chinese (Traditional) translate
Update wordings to improve readability.
2019-06-13 21:33:24 -07:00
Viktor De Pasquale
074b1f8c61 Added one-click scroll to the bottom 2019-06-12 16:08:02 +02:00
topjohnwu
326eee8c83 Migrate a lot of classes to Kotlin 2019-06-12 03:29:38 -07:00
topjohnwu
00bff4912e Use svc for reboot if feasible
Close #1488
2019-06-12 00:55:21 -07:00
Viktor De Pasquale
0ce1720516 Fixed magisk log screen lines having multiple lines 2019-06-11 21:52:03 -07:00
osm0sis
ee407472cf magiskboot: allow forcing no recompression on ramdisk.cpio
- when input image had a compressed ramdisk magiskboot had no way to force the repack with the uncompressed ramdisk.cpio since it does not formally recognize cpio as its own format, so add a switch to support forcing repacking to any possible ramdisk format regardless of input image
2019-06-10 21:57:39 -07:00
osm0sis
f341f3b2dd magiskboot: accept forcing recognized but unsupported format compression
- when input image had a different supported format (e.g. gzip) magiskboot would not accept a manually compressed ramdisk or kernel in an unsupported format (e.g. lzop) despite being able to recognize it, so instead would double compress using whatever the input format was, breaking the image with, in effect, a ramdisk.cpio.lzo.gz
2019-06-10 21:56:51 -07:00
Ian Macdonald
8513946e09 'magiskboot hexpatch' will exit with status 1 when nothing patched. 2019-06-10 21:41:40 -07:00
nonnymoose
8ebd9c8927 Use original file type when creating device nodes 2019-06-10 21:41:17 -07:00
topjohnwu
1d54c5144e Fix background update checks 2019-06-10 21:25:42 -07:00
topjohnwu
e40d4318fa Let Kotlin target Java 8 2019-06-10 21:22:07 -07:00
topjohnwu
7756e10779 Rewrite configs with Kotlin delagate properties 2019-06-10 04:37:56 -07:00
topjohnwu
3e58d502d0 Update SettingsFragment to Kotlin 2019-06-09 03:04:37 -07:00
topjohnwu
1c8846dc57 Make PreferenceModel an interface 2019-06-08 16:30:12 -07:00
topjohnwu
2f320c7239 Update ClassMap 2019-06-08 15:34:15 -07:00
topjohnwu
e799918ab6 Update update check service 2019-06-08 15:28:59 -07:00
topjohnwu
86c4928e0f Fix locale settings
AppCompatActivity changed its impl again...
2019-06-08 02:11:10 -07:00
topjohnwu
0293eb5c51 Never refetch magisk version dynamically 2019-06-08 01:44:02 -07:00
topjohnwu
1ee75b6aa6 Download snet package without legacy impl 2019-06-08 01:39:22 -07:00
topjohnwu
4b30b224b5 Remove separate constant class 2019-06-08 00:41:03 -07:00
topjohnwu
16b232d2a3 Enable okhttp logging in debug only 2019-06-07 02:03:17 -07:00
topjohnwu
3f3b1f5b1d Sort policy with app name 2019-06-07 01:24:54 -07:00
topjohnwu
cec017b7bf More MagidkDB fixes 2019-06-07 01:05:54 -07:00
topjohnwu
3123cc1059 Update AndroidX dependencies 2019-06-07 00:27:07 -07:00
topjohnwu
caa9df86bc Switch to R8 friendly room-runtime 2019-06-07 00:17:00 -07:00
topjohnwu
f417389a7a Fix magisk database code in app 2019-06-06 00:39:24 -07:00
topjohnwu
662a5c8ea6 Upgrade Retrofit 2.6.0 2019-06-05 23:41:51 -07:00
topjohnwu
7edfbfb764 Upgrade SDK 2019-06-05 21:33:09 -07:00
560 changed files with 16788 additions and 17512 deletions

2
.gitattributes vendored
View File

@@ -11,7 +11,7 @@
*.bat text eol=crlf
# Denote all files that are truly binary and should not be modified.
chromeos/** binary
tools/** binary
*.jar binary
*.exe binary
*.apk binary

4
.gitignore vendored
View File

@@ -2,8 +2,8 @@ out
*.zip
*.jks
*.apk
config.prop
update.sh
/config.prop
/update.sh
# Built binaries
native/out

6
.gitmodules vendored
View File

@@ -19,3 +19,9 @@
[submodule "nanopb"]
path = native/jni/external/nanopb
url = https://github.com/nanopb/nanopb.git
[submodule "mincrypt"]
path = native/jni/external/mincrypt
url = https://github.com/topjohnwu/mincrypt.git
[submodule "termux-elf-cleaner"]
path = tools/termux-elf-cleaner
url = https://github.com/termux/termux-elf-cleaner.git

View File

@@ -10,7 +10,7 @@ Furthermore, Magisk provides a **Systemless Interface** to alter the system (or
## Bug Reports
**Make sure to install the latest [Canary Build](https://forum.xda-developers.com/apps/magisk/dev-magisk-canary-channel-bleeding-edge-t3839337) before reporting any bugs!** **DO NOT** report bugs that is already fixed upstream. Follow the instructions in the [Canary Channel XDA Thread](https://forum.xda-developers.com/apps/magisk/dev-magisk-canary-channel-bleeding-edge-t3839337), and report a bug either by opening an issue on GitHub or directly in the thread.
**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.
## Building Environment Requirements
@@ -30,13 +30,12 @@ Furthermore, Magisk provides a **Systemless Interface** to alter the system (or
## Translations
Default string resources for Magisk Manager are scattered throughout
Default string resources for Magisk Manager and its stub APK are located here:
- `app/src/main/res/values/strings.xml`
- `stub/src/main/res/values/strings.xml`
- `shared/src/main/res/values/strings.xml`
Translate each and place them in the respective locations (`<module>/src/main/res/values-<lang>/strings.xml`).
Translate each and place them in the respective locations (`[module]/src/main/res/values-[lang]/strings.xml`).
## Signature Verification

View File

@@ -17,8 +17,8 @@ android {
applicationId 'com.topjohnwu.magisk'
vectorDrawables.useSupportLibrary = true
multiDexEnabled true
versionName configProps['appVersion']
versionCode configProps['appVersionCode'] as Integer
versionName props['appVersion']
versionCode props['appVersionCode'] as Integer
}
buildTypes {
@@ -35,13 +35,16 @@ android {
}
packagingOptions {
exclude '/META-INF/*.version'
exclude '/META-INF/*.kotlin_module'
exclude '/META-INF/rxkotlin.properties'
exclude '/META-INF/**'
exclude '/androidsupportmultidexversion.txt'
exclude '/org/**'
exclude '/org/bouncycastle/**'
exclude '/kotlin/**'
exclude '/kotlinx/**'
exclude '/okhttp3/**'
}
kotlinOptions {
jvmTarget = '1.8'
}
}
@@ -51,52 +54,82 @@ androidExtensions {
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation project(':net')
implementation project(':shared')
implementation project(':signing')
implementation 'com.github.topjohnwu:jtar:1.0.0'
implementation 'com.jakewharton.timber:timber:4.7.1'
implementation 'com.github.skoumalcz:teanity:0.3.3'
implementation 'com.ncapdevi:frag-nav:3.2.0'
implementation 'com.github.pwittchen:reactivenetwork-rx2:3.0.6'
def vMarkwon = '3.0.1'
implementation "ru.noties.markwon:core:${vMarkwon}"
implementation "ru.noties.markwon:html:${vMarkwon}"
implementation "ru.noties.markwon:image-svg:${vMarkwon}"
implementation 'io.reactivex.rxjava2:rxjava:2.2.13'
implementation 'io.reactivex.rxjava2:rxkotlin:2.4.0'
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
def vLibsu = '2.5.0'
implementation "org.jetbrains.kotlin:kotlin-stdlib:${vKotlin}"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${vKotlin}"
def vBAdapt = '3.1.1'
def bindingAdapter = 'me.tatarka.bindingcollectionadapter2:bindingcollectionadapter'
implementation "${bindingAdapter}:${vBAdapt}"
implementation "${bindingAdapter}-recyclerview:${vBAdapt}"
def vMarkwon = '4.2.0'
implementation "io.noties.markwon:core:${vMarkwon}"
implementation "io.noties.markwon:html:${vMarkwon}"
implementation "io.noties.markwon:image:${vMarkwon}"
implementation 'com.caverock:androidsvg:1.4'
def vLibsu = '2.5.1'
implementation "com.github.topjohnwu.libsu:core:${vLibsu}"
implementation "com.github.topjohnwu.libsu:io:${vLibsu}"
def vKoin = "2.0.1"
def vKoin = '2.0.1'
implementation "org.koin:koin-core:${vKoin}"
implementation "org.koin:koin-android:${vKoin}"
implementation "org.koin:koin-androidx-viewmodel:${vKoin}"
def vRetrofit = "2.5.0"
def vRetrofit = '2.6.2'
implementation "com.squareup.retrofit2:retrofit:${vRetrofit}"
implementation "com.squareup.retrofit2:converter-moshi:${vRetrofit}"
implementation "com.squareup.retrofit2:converter-scalars:${vRetrofit}"
implementation "com.squareup.retrofit2:adapter-rxjava2:${vRetrofit}"
def vOkHttp = "3.12.0"
def vOkHttp = '3.12.6'
implementation "com.squareup.okhttp3:okhttp:${vOkHttp}"
implementation "com.squareup.okhttp3:logging-interceptor:${vOkHttp}"
def vMoshi = "1.8.0"
def vMoshi = '1.9.2'
implementation "com.squareup.moshi:moshi:${vMoshi}"
def vKotshi = "2.0.1"
def vKotshi = '2.0.2'
implementation "se.ansman.kotshi:api:${vKotshi}"
kapt "se.ansman.kotshi:compiler:${vKotshi}"
modules {
module('androidx.room:room-runtime') {
replacedBy('com.github.topjohnwu:room-runtime')
}
}
def vRoom = '2.2.2'
implementation "com.github.topjohnwu:room-runtime:${vRoom}"
implementation "androidx.room:room-rxjava2:${vRoom}"
kapt "androidx.room:room-compiler:${vRoom}"
def vNav = '2.1.0'
implementation "androidx.navigation:navigation-fragment-ktx:${vNav}"
implementation "androidx.navigation:navigation-ui-ktx:${vNav}"
implementation 'androidx.biometric:biometric:1.0.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.browser:browser:1.0.0'
implementation 'androidx.preference:preference:1.0.0'
implementation 'androidx.recyclerview:recyclerview:1.1.0-alpha05'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0-alpha03'
implementation 'androidx.preference:preference:1.1.0'
implementation 'androidx.recyclerview:recyclerview:1.1.0'
implementation 'androidx.fragment:fragment-ktx:1.2.0-rc03'
implementation 'androidx.cardview:cardview:1.0.0'
implementation 'androidx.work:work-runtime:2.0.1'
implementation 'androidx.transition:transition:1.2.0-alpha01'
implementation 'androidx.work:work-runtime:2.2.0'
implementation 'androidx.transition:transition:1.3.0-rc02'
implementation 'androidx.multidex:multidex:2.0.1'
implementation 'com.google.android.material:material:1.1.0-alpha06'
implementation 'androidx.core:core-ktx:1.1.0'
implementation 'com.google.android.material:material:1.2.0-alpha02'
}

View File

@@ -16,13 +16,10 @@
# public *;
#}
# Retrofit classes
-keep,allowobfuscation class com.topjohnwu.magisk.data.network.*
# Snet
-keepclassmembers class com.topjohnwu.magisk.utils.ISafetyNetHelper { *; }
-keep,allowobfuscation interface com.topjohnwu.magisk.utils.ISafetyNetHelper$Callback
-keepclassmembers class * implements com.topjohnwu.magisk.utils.ISafetyNetHelper$Callback {
-keepclassmembers class com.topjohnwu.magisk.utils.SafetyNetHelper { *; }
-keep,allowobfuscation interface com.topjohnwu.magisk.utils.SafetyNetHelper$Callback
-keepclassmembers class * implements com.topjohnwu.magisk.utils.SafetyNetHelper$Callback {
void onResponse(int);
}
@@ -32,16 +29,17 @@
}
# DelegateWorker
-keep,allowobfuscation class * extends com.topjohnwu.magisk.model.worker.DelegateWorker
-keep,allowobfuscation class * extends com.topjohnwu.magisk.base.DelegateWorker
# BootSigner
-keepclassmembers class com.topjohnwu.signing.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 { *; }
-assumenosideeffects class com.topjohnwu.magisk.utils.Logger {
public *** debug(...);
}
# Excessive obfuscation
-repackageclasses 'a'

5
app/res-ids.txt Normal file
View File

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

View File

@@ -1,58 +1,54 @@
<?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">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
<application
android:allowBackup="true"
android:name="a.e"
android:theme="@style/MagiskTheme"
android:usesCleartextTraffic="true"
android:allowBackup="true"
tools:ignore="UnusedAttribute,GoogleAppIndexingWarning">
<!-- Activities -->
<activity
android:name="a.b"
android:configChanges="orientation|screenSize"
android:exported="true" />
<!-- Splash -->
<activity
android:name="a.c"
android:configChanges="orientation|screenSize"
android:exported="true"
android:theme="@style/SplashTheme">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name="a.f"
android:configChanges="keyboardHidden|orientation|screenSize"
android:screenOrientation="nosensor"
android:theme="@style/MagiskTheme.Flashing" />
<!-- Main -->
<activity android:name="a.b" />
<!-- Flashing -->
<activity android:name="a.f" />
<!-- Superuser -->
<activity
android:name="a.m"
android:exported="false"
android:directBootAware="true"
android:excludeFromRecents="true"
android:theme="@style/MagiskTheme.SU" />
android:exported="false"
android:theme="@android:style/Theme.Translucent.NoTitleBar"
tools:ignore="AppLinkUrlError">
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
<!-- Receiver -->
<receiver
android:name="a.h"
android:directBootAware="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.REBOOT" />
<action android:name="android.intent.action.LOCALE_CHANGED" />
</intent-filter>
<intent-filter>
@@ -63,8 +59,7 @@
</intent-filter>
</receiver>
<!-- Service -->
<!-- DownloadService -->
<service android:name="a.j" />
<!-- Hardcode GMS version -->
@@ -72,6 +67,22 @@
android:name="com.google.android.gms.version"
android:value="12451000" />
<!-- Initialize WorkManager on-demand -->
<provider
android:name="androidx.work.impl.WorkManagerInitializer"
android:authorities="${applicationId}.workmanager-init"
tools:node="remove" />
<!-- We don't invalidate Room -->
<service
android:name="androidx.room.MultiInstanceInvalidationService"
tools:node="remove"/>
<!-- We don't use Device Credentials -->
<activity
android:name="androidx.biometric.DeviceCredentialHandlerActivity"
tools:node="remove" />
</application>
</manifest>

View File

@@ -3,11 +3,18 @@ package a;
import com.topjohnwu.magisk.utils.PatchAPK;
import com.topjohnwu.signing.BootSigner;
import androidx.annotation.Keep;
public class a {
@Keep
public class a extends BootSigner {
@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

@@ -3,5 +3,11 @@ package a;
import com.topjohnwu.magisk.App;
public class e extends App {
/* stub */
public e() {
super();
}
public e(Object o) {
super(o);
}
}

View File

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

View File

@@ -2,14 +2,14 @@ package a;
import android.content.Context;
import com.topjohnwu.magisk.model.worker.DelegateWorker;
import java.lang.reflect.ParameterizedType;
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 */
@@ -22,7 +22,7 @@ public abstract class w<T extends DelegateWorker> extends Worker {
try {
base = ((Class<T>) ((ParameterizedType) getClass().getGenericSuperclass())
.getActualTypeArguments()[0]).newInstance();
base.setActualWorker(this);
base.attachWorker(this);
} catch (Exception ignored) {}
}

View File

@@ -1,119 +1,90 @@
package com.topjohnwu.magisk
import android.annotation.SuppressLint
import android.app.Activity
import android.app.Application
import android.content.Context
import android.content.res.Configuration
import android.os.AsyncTask
import android.os.Build
import android.os.Bundle
import androidx.appcompat.app.AppCompatDelegate
import androidx.multidex.MultiDex
import androidx.room.Room
import androidx.work.WorkManager
import androidx.work.impl.WorkDatabase
import androidx.work.impl.WorkDatabase_Impl
import com.topjohnwu.magisk.data.database.RepoDatabase
import com.topjohnwu.magisk.data.database.RepoDatabase_Impl
import com.topjohnwu.magisk.data.database.SuLogDatabase
import com.topjohnwu.magisk.data.database.SuLogDatabase_Impl
import com.topjohnwu.magisk.di.ActivityTracker
import com.topjohnwu.magisk.di.koinModules
import com.topjohnwu.magisk.utils.LocaleManager
import com.topjohnwu.magisk.utils.RootUtils
import com.topjohnwu.magisk.utils.inject
import com.topjohnwu.net.Networking
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 java.util.concurrent.ThreadPoolExecutor
open class App : Application(), Application.ActivityLifecycleCallbacks {
open class App() : Application() {
lateinit var protectedContext: Context
constructor(o: Any) : this() {
Info.stub = DynAPK.load(o)
}
@Volatile
private var foreground: Activity? = null
init {
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)
Shell.Config.setFlags(Shell.FLAG_MOUNT_MASTER or Shell.FLAG_USE_MAGISK_BUSYBOX)
Shell.Config.verboseLogging(BuildConfig.DEBUG)
Shell.Config.addInitializers(RootInit::class.java)
Shell.Config.setTimeout(2)
FileProvider.callHandler = SuHandler
Room.setFactory {
when (it) {
WorkDatabase::class.java -> WorkDatabase_Impl()
RepoDatabase::class.java -> RepoDatabase_Impl()
SuLogDatabase::class.java -> SuLogDatabase_Impl()
else -> null
}
}
}
override fun attachBaseContext(base: Context) {
super.attachBaseContext(base)
// Basic setup
if (BuildConfig.DEBUG)
MultiDex.install(base)
Timber.plant(Timber.DebugTree())
// Some context magic
val app: Application
val impl: Context
if (base is Application) {
app = base
impl = base.baseContext
} else {
app = this
impl = base
}
val wrapped = impl.wrap()
super.attachBaseContext(wrapped)
// Normal startup
startKoin {
androidContext(this@App)
androidContext(wrapped)
modules(koinModules)
}
ResourceMgr.init(impl)
app.registerActivityLifecycleCallbacks(get<ActivityTracker>())
WorkManager.initialize(impl.wrapJob(), androidx.work.Configuration.Builder().build())
}
protectedContext = baseContext
self = this
deContext = base
if (Build.VERSION.SDK_INT >= 24) {
protectedContext = base.createDeviceProtectedStorageContext()
deContext = protectedContext
deContext.moveSharedPreferencesFrom(base, base.defaultPrefsName)
}
registerActivityLifecycleCallbacks(this)
Networking.init(base)
LocaleManager.setLocale(this)
// This is required as some platforms expect ContextImpl
override fun getBaseContext(): Context {
return super.getBaseContext().unwrap()
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
LocaleManager.setLocale(this)
}
//region ActivityLifecycleCallbacks
override fun onActivityCreated(activity: Activity, bundle: Bundle?) {}
override fun onActivityStarted(activity: Activity) {}
@Synchronized
override fun onActivityResumed(activity: Activity) {
foreground = activity
}
@Synchronized
override fun onActivityPaused(activity: Activity) {
foreground = null
}
override fun onActivityStopped(activity: Activity) {}
override fun onActivitySaveInstanceState(activity: Activity, bundle: Bundle) {}
override fun onActivityDestroyed(activity: Activity) {}
//endregion
private val Context.defaultPrefsName get() = "${packageName}_preferences"
companion object {
@SuppressLint("StaticFieldLeak")
@Deprecated("Use dependency injection")
@JvmStatic
lateinit var self: App
@SuppressLint("StaticFieldLeak")
@Deprecated("Use dependency injection; replace with protectedContext")
@JvmStatic
lateinit var deContext: Context
@Deprecated("Use Rx or similar")
@JvmField
var THREAD_POOL: ThreadPoolExecutor
init {
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)
Shell.Config.setFlags(Shell.FLAG_MOUNT_MASTER or Shell.FLAG_USE_MAGISK_BUSYBOX)
Shell.Config.verboseLogging(BuildConfig.DEBUG)
Shell.Config.addInitializers(RootUtils::class.java)
Shell.Config.setTimeout(2)
THREAD_POOL = AsyncTask.THREAD_POOL_EXECUTOR as ThreadPoolExecutor
}
@Deprecated("")
@JvmStatic
fun foreground(): Activity? {
val app: App by inject()
return app.foreground
}
resources.updateConfig(newConfig)
if (!isRunningAsStub)
super.onConfigurationChanged(newConfig)
}
}

View File

@@ -1,27 +0,0 @@
package com.topjohnwu.magisk
import com.topjohnwu.magisk.model.download.DownloadModuleService
import com.topjohnwu.magisk.model.receiver.GeneralReceiver
import com.topjohnwu.magisk.model.update.UpdateCheckService
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
object ClassMap {
private val map = mapOf(
App::class.java to a.e::class.java,
MainActivity::class.java to a.b::class.java,
SplashActivity::class.java to a.c::class.java,
FlashActivity::class.java to a.f::class.java,
UpdateCheckService::class.java to a.g::class.java,
GeneralReceiver::class.java to a.h::class.java,
DownloadModuleService::class.java to a.j::class.java,
SuRequestActivity::class.java to a.m::class.java
)
@JvmStatic
operator fun get(c: Class<*>): Class<*>? {
return map.getOrElse(c) { null } //as? Class<T>
}
}

View File

@@ -1,399 +0,0 @@
package com.topjohnwu.magisk;
import android.content.Context;
import android.content.SharedPreferences;
import android.util.Xml;
import com.topjohnwu.magisk.utils.Utils;
import com.topjohnwu.superuser.Shell;
import com.topjohnwu.superuser.ShellUtils;
import com.topjohnwu.superuser.io.SuFile;
import com.topjohnwu.superuser.io.SuFileInputStream;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.File;
import java.io.IOException;
import androidx.annotation.Nullable;
import androidx.collection.ArrayMap;
import static com.topjohnwu.magisk.ConfigLeanback.getPrefs;
import static com.topjohnwu.magisk.utils.XAndroidKt.getPackageName;
public final class Config {
private static final ArrayMap<String, Object> defs = new ArrayMap<>();
public static int magiskVersionCode = -1;
// Current status
public static String magiskVersionString = "";
// Update Info
public static String remoteMagiskVersionString = "";
public static int remoteMagiskVersionCode = -1;
public static String magiskLink = "";
public static String magiskNoteLink = "";
public static String magiskMD5 = "";
public static String remoteManagerVersionString = "";
public static int remoteManagerVersionCode = -1;
public static String managerLink = "";
public static String managerNoteLink = "";
public static String uninstallerLink = "";
// Install flags
public static boolean keepVerity = false;
public static boolean keepEnc = false;
public static boolean recovery = false;
public static int suLogTimeout = 14;
public static class Key {
// su configs
public static final String ROOT_ACCESS = "root_access";
public static final String SU_MULTIUSER_MODE = "multiuser_mode";
public static final String SU_MNT_NS = "mnt_ns";
public static final String SU_MANAGER = "requester";
public static final String SU_REQUEST_TIMEOUT = "su_request_timeout";
public static final String SU_AUTO_RESPONSE = "su_auto_response";
public static final String SU_NOTIFICATION = "su_notification";
public static final String SU_REAUTH = "su_reauth";
public static final String SU_FINGERPRINT = "su_fingerprint";
// prefs
public static final String CHECK_UPDATES = "check_update";
public static final String UPDATE_CHANNEL = "update_channel";
public static final String CUSTOM_CHANNEL = "custom_channel";
public static final String LOCALE = "locale";
public static final String DARK_THEME = "dark_theme";
public static final String ETAG_KEY = "ETag";
public static final String REPO_ORDER = "repo_order";
public static final String SHOW_SYSTEM_APP = "show_system";
// system state
public static final String UPDATE_SERVICE_VER = "update_service_version";
public static final String MAGISKHIDE = "magiskhide";
public static final String COREONLY = "disable";
}
public static class Value {
public static final int DEFAULT_CHANNEL = -1;
public static final int STABLE_CHANNEL = 0;
public static final int BETA_CHANNEL = 1;
public static final int CUSTOM_CHANNEL = 2;
public static final int CANARY_CHANNEL = 3;
public static final int CANARY_DEBUG_CHANNEL = 4;
public static final int ROOT_ACCESS_DISABLED = 0;
public static final int ROOT_ACCESS_APPS_ONLY = 1;
public static final int ROOT_ACCESS_ADB_ONLY = 2;
public static final int ROOT_ACCESS_APPS_AND_ADB = 3;
public static final int MULTIUSER_MODE_OWNER_ONLY = 0;
public static final int MULTIUSER_MODE_OWNER_MANAGED = 1;
public static final int MULTIUSER_MODE_USER = 2;
public static final int NAMESPACE_MODE_GLOBAL = 0;
public static final int NAMESPACE_MODE_REQUESTER = 1;
public static final int NAMESPACE_MODE_ISOLATE = 2;
public static final int NO_NOTIFICATION = 0;
public static final int NOTIFICATION_TOAST = 1;
public static final int SU_PROMPT = 0;
public static final int SU_AUTO_DENY = 1;
public static final int SU_AUTO_ALLOW = 2;
public static final int[] TIMEOUT_LIST = {0, -1, 10, 20, 30, 60};
public static final int ORDER_NAME = 0;
public static final int ORDER_DATE = 1;
}
private static boolean magiskHide = false;
public static void loadMagiskInfo() {
try {
magiskVersionString = ShellUtils.fastCmd("magisk -v").split(":")[0];
magiskVersionCode = Integer.parseInt(ShellUtils.fastCmd("magisk -V"));
magiskHide = Shell.su("magiskhide --status").exec().isSuccess();
} catch (NumberFormatException ignored) {
}
}
public static void export() {
// Flush prefs to disk
getPrefs().edit().apply();
Context context = ConfigLeanback.getProtectedContext();
File xml = new File(context.getFilesDir().getParent() + "/shared_prefs",
getPackageName() + "_preferences.xml");
Shell.su(Utils.fmt("cat %s > /data/adb/%s", xml, Const.MANAGER_CONFIGS)).exec();
}
private static final int PREF_INT = 0;
private static final int PREF_STR_INT = 1;
private static final int PREF_BOOL = 2;
private static final int PREF_STR = 3;
private static final int DB_INT = 4;
private static final int DB_BOOL = 5;
private static final int DB_STR = 6;
private static int getConfigType(String key) {
switch (key) {
case Key.REPO_ORDER:
return PREF_INT;
case Key.SU_REQUEST_TIMEOUT:
case Key.SU_AUTO_RESPONSE:
case Key.SU_NOTIFICATION:
case Key.UPDATE_CHANNEL:
return PREF_STR_INT;
case Key.DARK_THEME:
case Key.SU_REAUTH:
case Key.CHECK_UPDATES:
case Key.MAGISKHIDE:
case Key.COREONLY:
case Key.SHOW_SYSTEM_APP:
return PREF_BOOL;
case Key.CUSTOM_CHANNEL:
case Key.LOCALE:
case Key.ETAG_KEY:
return PREF_STR;
case Key.ROOT_ACCESS:
case Key.SU_MNT_NS:
case Key.SU_MULTIUSER_MODE:
return DB_INT;
case Key.SU_FINGERPRINT:
return DB_BOOL;
case Key.SU_MANAGER:
return DB_STR;
default:
throw new IllegalArgumentException();
}
}
public static void initialize() {
SharedPreferences pref = getPrefs();
SharedPreferences.Editor editor = pref.edit();
File config = SuFile.open("/data/adb", Const.MANAGER_CONFIGS);
if (config.exists()) {
try {
SuFileInputStream is = new SuFileInputStream(config);
XmlPullParser parser = Xml.newPullParser();
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false);
parser.setInput(is, "UTF-8");
parser.nextTag();
parser.require(XmlPullParser.START_TAG, null, "map");
while (parser.next() != XmlPullParser.END_TAG) {
if (parser.getEventType() != XmlPullParser.START_TAG)
continue;
String key = parser.getAttributeValue(null, "name");
String value = parser.getAttributeValue(null, "value");
switch (parser.getName()) {
case "string":
parser.require(XmlPullParser.START_TAG, null, "string");
editor.putString(key, parser.nextText());
parser.require(XmlPullParser.END_TAG, null, "string");
break;
case "boolean":
parser.require(XmlPullParser.START_TAG, null, "boolean");
editor.putBoolean(key, Boolean.parseBoolean(value));
parser.nextTag();
parser.require(XmlPullParser.END_TAG, null, "boolean");
break;
case "int":
parser.require(XmlPullParser.START_TAG, null, "int");
editor.putInt(key, Integer.parseInt(value));
parser.nextTag();
parser.require(XmlPullParser.END_TAG, null, "int");
break;
case "long":
parser.require(XmlPullParser.START_TAG, null, "long");
editor.putLong(key, Long.parseLong(value));
parser.nextTag();
parser.require(XmlPullParser.END_TAG, null, "long");
break;
case "float":
parser.require(XmlPullParser.START_TAG, null, "int");
editor.putFloat(key, Float.parseFloat(value));
parser.nextTag();
parser.require(XmlPullParser.END_TAG, null, "int");
break;
default:
parser.next();
}
}
} catch (IOException | XmlPullParserException e) {
e.printStackTrace();
}
editor.remove(Key.ETAG_KEY);
editor.apply();
editor = pref.edit();
config.delete();
}
// Set defaults if not set
setDefs(pref, editor);
// These settings are from actual device state
editor.putBoolean(Key.MAGISKHIDE, magiskHide)
.putBoolean(Key.COREONLY, Const.MAGISK_DISABLE_FILE.exists())
.putInt(Key.UPDATE_SERVICE_VER, Const.UPDATE_SERVICE_VER)
.apply();
}
@SuppressWarnings("unchecked")
public static <T> T get(String key) {
switch (getConfigType(key)) {
case PREF_INT:
return (T) (Integer) getPrefs().getInt(key, getDef(key));
case PREF_STR_INT:
return (T) (Integer) Utils.getPrefsInt(getPrefs(), key, getDef(key));
case PREF_BOOL:
return (T) (Boolean) getPrefs().getBoolean(key, getDef(key));
case PREF_STR:
return (T) getPrefs().getString(key, getDef(key));
case DB_INT:
return (T) (Integer) ConfigLeanback.get(key, (Integer) getDef(key));
case DB_BOOL:
return (T) (Boolean) (ConfigLeanback.get(key, getDef(key) ? 1 : 0) != 0);
case DB_STR:
return (T) ConfigLeanback.get(key, getDef(key));
}
/* Will never get here (IllegalArgumentException in getConfigType) */
return null;
}
public static void set(String key, Object val) {
switch (getConfigType(key)) {
case PREF_INT:
getPrefs().edit().putInt(key, (int) val).apply();
break;
case PREF_STR_INT:
getPrefs().edit().putString(key, String.valueOf(val)).apply();
break;
case PREF_BOOL:
getPrefs().edit().putBoolean(key, (boolean) val).apply();
break;
case PREF_STR:
getPrefs().edit().putString(key, (String) val).apply();
break;
case DB_INT:
ConfigLeanback.put(key, (int) val);
break;
case DB_BOOL:
ConfigLeanback.put(key, (boolean) val ? 1 : 0);
break;
case DB_STR:
ConfigLeanback.put(key, (String) val);
break;
}
}
public static void remove(String key) {
switch (getConfigType(key)) {
case PREF_INT:
case PREF_STR_INT:
case PREF_BOOL:
case PREF_STR:
getPrefs().edit().remove(key).apply();
break;
case DB_BOOL:
case DB_INT:
ConfigLeanback.delete(key);
break;
case DB_STR:
ConfigLeanback.put(key, null);
break;
}
}
static {
/* Set default configurations */
// prefs int
defs.put(Key.REPO_ORDER, Value.ORDER_DATE);
// prefs string int
defs.put(Key.SU_REQUEST_TIMEOUT, 10);
defs.put(Key.SU_AUTO_RESPONSE, Value.SU_PROMPT);
defs.put(Key.SU_NOTIFICATION, Value.NOTIFICATION_TOAST);
defs.put(Key.UPDATE_CHANNEL, Utils.isCanary() ?
Value.CANARY_DEBUG_CHANNEL : Value.DEFAULT_CHANNEL);
// prefs bool
defs.put(Key.CHECK_UPDATES, true);
defs.put(Key.DARK_THEME, true);
//defs.put(Key.SU_REAUTH, false);
//defs.put(Key.SHOW_SYSTEM_APP, false);
// prefs string
defs.put(Key.CUSTOM_CHANNEL, "");
defs.put(Key.LOCALE, "");
//defs.put(Key.ETAG_KEY, null);
// db int
defs.put(Key.ROOT_ACCESS, Value.ROOT_ACCESS_APPS_AND_ADB);
defs.put(Key.SU_MNT_NS, Value.NAMESPACE_MODE_REQUESTER);
defs.put(Key.SU_MULTIUSER_MODE, Value.MULTIUSER_MODE_OWNER_ONLY);
// db bool
//defs.put(Key.SU_FINGERPRINT, false);
// db strings
//defs.put(Key.SU_MANAGER, null);
}
@SuppressWarnings("unchecked")
@Nullable
private static <T> T getDef(String key) throws IllegalArgumentException {
Object val = defs.get(key);
switch (getConfigType(key)) {
case PREF_INT:
case DB_INT:
case PREF_STR_INT:
return val != null ? (T) val : (T) (Integer) 0;
case DB_BOOL:
case PREF_BOOL:
return val != null ? (T) val : (T) (Boolean) false;
case DB_STR:
case PREF_STR:
return (T) val;
}
/* Will never get here (IllegalArgumentException in getConfigType) */
return null;
}
private static void setDefs(SharedPreferences pref, SharedPreferences.Editor editor) {
for (String key : defs.keySet()) {
Object value = defs.get(key);
int type = getConfigType(key);
switch (type) {
case DB_INT:
editor.putString(key, String.valueOf(ConfigLeanback.get(key, (Integer) value)));
continue;
case DB_STR:
editor.putString(key, ConfigLeanback.get(key, String.valueOf(value)));
continue;
case DB_BOOL:
int bs = ConfigLeanback.get(key, -1);
editor.putBoolean(key, bs < 0 ? (Boolean) value : bs != 0);
continue;
}
if (pref.contains(key))
continue;
switch (type) {
case PREF_INT:
editor.putInt(key, (Integer) value);
break;
case PREF_STR_INT:
editor.putString(key, String.valueOf(value));
break;
case PREF_STR:
editor.putString(key, (String) value);
break;
case PREF_BOOL:
editor.putBoolean(key, (Boolean) value);
break;
}
}
}
}

View File

@@ -0,0 +1,227 @@
package com.topjohnwu.magisk
import android.content.Context
import android.content.SharedPreferences
import android.os.Environment
import android.util.Xml
import androidx.core.content.edit
import com.topjohnwu.magisk.data.database.SettingsDao
import com.topjohnwu.magisk.data.database.StringDao
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.superuser.Shell
import com.topjohnwu.superuser.io.SuFile
import com.topjohnwu.superuser.io.SuFileInputStream
import org.xmlpull.v1.XmlPullParser
import java.io.File
object Config : PreferenceModel, DBConfig {
override val stringDao: StringDao by inject()
override val settingsDao: SettingsDao by inject()
override val context: Context by inject(Protected)
object Key {
// db configs
const val ROOT_ACCESS = "root_access"
const val SU_MULTIUSER_MODE = "multiuser_mode"
const val SU_MNT_NS = "mnt_ns"
const val SU_BIOMETRIC = "su_biometric"
const val SU_MANAGER = "requester"
const val KEYSTORE = "keystore"
// prefs
const val SU_REQUEST_TIMEOUT = "su_request_timeout"
const val SU_AUTO_RESPONSE = "su_auto_response"
const val SU_NOTIFICATION = "su_notification"
const val SU_REAUTH = "su_reauth"
const val CHECK_UPDATES = "check_update"
const val UPDATE_CHANNEL = "update_channel"
const val CUSTOM_CHANNEL = "custom_channel"
const val LOCALE = "locale"
const val DARK_THEME = "dark_theme"
const val REPO_ORDER = "repo_order"
const val SHOW_SYSTEM_APP = "show_system"
const val DOWNLOAD_PATH = "download_path"
const val BOOT_ID = "boot_id"
// system state
const val MAGISKHIDE = "magiskhide"
const val COREONLY = "disable"
}
object Value {
// Update channels
const val DEFAULT_CHANNEL = -1
const val STABLE_CHANNEL = 0
const val BETA_CHANNEL = 1
const val CUSTOM_CHANNEL = 2
const val CANARY_CHANNEL = 3
const val CANARY_DEBUG_CHANNEL = 4
// root access mode
const val ROOT_ACCESS_DISABLED = 0
const val ROOT_ACCESS_APPS_ONLY = 1
const val ROOT_ACCESS_ADB_ONLY = 2
const val ROOT_ACCESS_APPS_AND_ADB = 3
// su multiuser
const val MULTIUSER_MODE_OWNER_ONLY = 0
const val MULTIUSER_MODE_OWNER_MANAGED = 1
const val MULTIUSER_MODE_USER = 2
// su mnt ns
const val NAMESPACE_MODE_GLOBAL = 0
const val NAMESPACE_MODE_REQUESTER = 1
const val NAMESPACE_MODE_ISOLATE = 2
// su notification
const val NO_NOTIFICATION = 0
const val NOTIFICATION_TOAST = 1
// su auto response
const val SU_PROMPT = 0
const val SU_AUTO_DENY = 1
const val SU_AUTO_ALLOW = 2
// su timeout
val TIMEOUT_LIST = intArrayOf(0, -1, 10, 20, 30, 60)
// repo order
const val ORDER_NAME = 0
const val ORDER_DATE = 1
}
private val defaultChannel =
if (Utils.isCanary) {
if (BuildConfig.DEBUG)
Value.CANARY_DEBUG_CHANNEL
else
Value.CANARY_CHANNEL
}
else Value.DEFAULT_CHANNEL
var bootId by preference(Key.BOOT_ID, "")
var downloadPath by preference(Key.DOWNLOAD_PATH, Environment.DIRECTORY_DOWNLOADS)
var repoOrder by preference(Key.REPO_ORDER, Value.ORDER_DATE)
var suDefaultTimeout by preferenceStrInt(Key.SU_REQUEST_TIMEOUT, 10)
var suAutoReponse by preferenceStrInt(Key.SU_AUTO_RESPONSE, Value.SU_PROMPT)
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 suReAuth by preference(Key.SU_REAUTH, false)
var checkUpdate by preference(Key.CHECK_UPDATES, true)
var magiskHide by preference(Key.MAGISKHIDE, true)
var coreOnly by preference(Key.COREONLY, false)
var showSystemApp by preference(Key.SHOW_SYSTEM_APP, false)
var customChannelUrl by preference(Key.CUSTOM_CHANNEL, "")
var locale by preference(Key.LOCALE, "")
var rootMode by dbSettings(Key.ROOT_ACCESS, Value.ROOT_ACCESS_APPS_AND_ADB)
var suMntNamespaceMode by dbSettings(Key.SU_MNT_NS, Value.NAMESPACE_MODE_REQUESTER)
var suMultiuserMode by dbSettings(Key.SU_MULTIUSER_MODE, Value.MULTIUSER_MODE_OWNER_ONLY)
var suBiometric by dbSettings(Key.SU_BIOMETRIC, false)
var suManager by dbStrings(Key.SU_MANAGER, "", true)
var keyStoreRaw by dbStrings(Key.KEYSTORE, "", true)
// Always return a path in external storage where we can write
val downloadDirectory get() =
Utils.ensureDownloadPath(downloadPath) ?: get<Context>().getExternalFilesDir(null)!!
private const val SU_FINGERPRINT = "su_fingerprint"
fun initialize() = prefs.also {
if (it.getBoolean(SU_FINGERPRINT, false)) {
suBiometric = true
}
}.edit {
parsePrefs(this)
// Legacy stuff
remove(SU_FINGERPRINT)
// Get actual state
putBoolean(Key.COREONLY, Const.MAGISK_DISABLE_FILE.exists())
// Write database configs
putString(Key.ROOT_ACCESS, rootMode.toString())
putString(Key.SU_MNT_NS, suMntNamespaceMode.toString())
putString(Key.SU_MULTIUSER_MODE, suMultiuserMode.toString())
putBoolean(Key.SU_BIOMETRIC, BiometricHelper.isEnabled)
}.also {
if (!prefs.contains(Key.UPDATE_CHANNEL))
prefs.edit().putString(Key.UPDATE_CHANNEL, defaultChannel.toString()).apply()
}
private fun parsePrefs(editor: SharedPreferences.Editor) = editor.apply {
val config = SuFile.open("/data/adb", Const.MANAGER_CONFIGS)
if (config.exists()) runCatching {
val input = SuFileInputStream(config)
val parser = Xml.newPullParser()
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false)
parser.setInput(input, "UTF-8")
parser.nextTag()
parser.require(XmlPullParser.START_TAG, null, "map")
while (parser.next() != XmlPullParser.END_TAG) {
if (parser.eventType != XmlPullParser.START_TAG)
continue
val key: String = parser.getAttributeValue(null, "name")
fun value() = parser.getAttributeValue(null, "value")!!
when (parser.name) {
"string" -> {
parser.require(XmlPullParser.START_TAG, null, "string")
putString(key, parser.nextText())
parser.require(XmlPullParser.END_TAG, null, "string")
}
"boolean" -> {
parser.require(XmlPullParser.START_TAG, null, "boolean")
putBoolean(key, value().toBoolean())
parser.nextTag()
parser.require(XmlPullParser.END_TAG, null, "boolean")
}
"int" -> {
parser.require(XmlPullParser.START_TAG, null, "int")
putInt(key, value().toInt())
parser.nextTag()
parser.require(XmlPullParser.END_TAG, null, "int")
}
"long" -> {
parser.require(XmlPullParser.START_TAG, null, "long")
putLong(key, value().toLong())
parser.nextTag()
parser.require(XmlPullParser.END_TAG, null, "long")
}
"float" -> {
parser.require(XmlPullParser.START_TAG, null, "int")
putFloat(key, value().toFloat())
parser.nextTag()
parser.require(XmlPullParser.END_TAG, null, "int")
}
else -> parser.next()
}
}
config.delete()
}
}
fun export() {
// Flush prefs to disk
prefs.edit().commit()
val context = get<Context>(Protected)
val xml = File(
"${context.filesDir.parent}/shared_prefs",
"${context.packageName}_preferences.xml"
)
Shell.su("cat $xml > /data/adb/${Const.MANAGER_CONFIGS}").exec()
}
}

View File

@@ -1,52 +0,0 @@
package com.topjohnwu.magisk
import android.content.Context
import android.content.SharedPreferences
import androidx.annotation.AnyThread
import androidx.annotation.WorkerThread
import com.skoumal.teanity.extensions.subscribeK
import com.topjohnwu.magisk.data.repository.SettingRepository
import com.topjohnwu.magisk.data.repository.StringRepository
import com.topjohnwu.magisk.di.Protected
import com.topjohnwu.magisk.utils.inject
object ConfigLeanback {
@JvmStatic
val protectedContext: Context by inject(Protected)
@JvmStatic
val prefs: SharedPreferences by inject()
private val settingRepo: SettingRepository by inject()
private val stringRepo: StringRepository by inject()
@JvmStatic
@AnyThread
fun put(key: String, value: Int) {
settingRepo.put(key, value).subscribeK()
}
@JvmStatic
@WorkerThread
fun get(key: String, defaultValue: Int): Int =
settingRepo.fetch(key, defaultValue).blockingGet()
@JvmStatic
@AnyThread
fun put(key: String, value: String?) {
val task = value?.let { stringRepo.put(key, it) } ?: stringRepo.delete(key)
task.subscribeK()
}
@JvmStatic
@WorkerThread
fun get(key: String, defaultValue: String?): String =
stringRepo.fetch(key, defaultValue.orEmpty()).blockingGet()
@JvmStatic
@AnyThread
fun delete(key: String) {
settingRepo.delete(key).subscribeK()
}
}

View File

@@ -1,47 +1,32 @@
package com.topjohnwu.magisk
import android.os.Environment
import android.os.Process
import java.io.File
object Const {
const val DEBUG_TAG = "MagiskManager"
// APK content
const val ANDROID_MANIFEST = "AndroidManifest.xml"
const val SU_KEYSTORE_KEY = "su_key"
// Paths
const val MAGISK_PATH = "/sbin/.magisk/img"
@JvmField
val EXTERNAL_PATH: File =
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
@JvmField
var MAGISK_DISABLE_FILE: File = File("xxx")
var MAGISK_DISABLE_FILE = File("xxx")
const val TMP_FOLDER_PATH = "/dev/tmp"
const val MAGISK_LOG = "/cache/magisk.log"
const val MANAGER_CONFIGS = ".tmp.magisk.config"
// Versions
const val UPDATE_SERVICE_VER = 1
const val SNET_EXT_VER = 12
const val SNET_EXT_VER = 13
const val SNET_REVISION = "a6c47f86f10b310358afa9dbe837037dd5d561df"
const val BOOTCTL_REVISION = "a6c47f86f10b310358afa9dbe837037dd5d561df"
@JvmField
// Misc
const val ANDROID_MANIFEST = "AndroidManifest.xml"
const val MAGISK_INSTALL_LOG_FILENAME = "magisk_install_log_%s.log"
const val MANAGER_CONFIGS = ".tmp.magisk.config"
val USER_ID = Process.myUid() / 100000
// Generic
const val MAGISK_INSTALL_LOG_FILENAME = "magisk_install_log_%s.log"
init {
EXTERNAL_PATH.mkdirs()
}
object MagiskVersion {
const val MIN_SUPPORT = 18000
object Version {
const val MIN_VERSION = "v18.0"
const val MIN_VERCODE = 18000
const val CONNECT_MODE = 20100
const val PROVIDER_CONNECT = 20102
}
object ID {
@@ -59,36 +44,28 @@ object Const {
}
object Url {
@Deprecated("This shouldn't be used. There's literally no need for it")
const val REPO_URL =
"https://api.github.com/users/Magisk-Modules-Repo/repos?per_page=100&sort=pushed&page=%d"
const val FILE_URL = "https://raw.githubusercontent.com/Magisk-Modules-Repo/%s/master/%s"
const val ZIP_URL = "https://github.com/Magisk-Modules-Repo/%s/archive/master.zip"
const val MODULE_INSTALLER =
"https://raw.githubusercontent.com/topjohnwu/Magisk/master/scripts/module_installer.sh"
const val PAYPAL_URL = "https://www.paypal.me/topjohnwu"
const val PATREON_URL = "https://www.patreon.com/topjohnwu"
const val TWITTER_URL = "https://twitter.com/topjohnwu"
const val XDA_THREAD = "http://forum.xda-developers.com/showthread.php?t=3432382"
const val SOURCE_CODE_URL = "https://github.com/topjohnwu/Magisk"
@JvmField
val SNET_URL = getRaw("b66b1a914978e5f4c4bbfd74a59f4ad371bac107", "snet.apk")
@JvmField
val BOOTCTL_URL = getRaw("9c5dfc1b8245c0b5b524901ef0ff0f8335757b77", "bootctl")
private fun getRaw(where: String, name: String) =
"https://raw.githubusercontent.com/topjohnwu/magisk_files/%s/%s".format(where, name)
const val GITHUB_RAW_URL = "https://raw.githubusercontent.com/"
const val GITHUB_API_URL = "https://api.github.com/users/Magisk-Modules-Repo/"
}
object Key {
// others
const val LINK_KEY = "Link"
const val IF_NONE_MATCH = "If-None-Match"
const val ETAG_KEY = "ETag"
// intents
const val OPEN_SECTION = "section"
const val INTENT_SET_NAME = "filename"
const val INTENT_SET_LINK = "link"
const val INTENT_SET_APP = "app_json"
const val FLASH_ACTION = "action"
const val FLASH_DATA = "additional_data"
const val DISMISS_ID = "dismiss_id"
const val BROADCAST_MANAGER_UPDATE = "manager_update"
const val BROADCAST_REBOOT = "reboot"
}
@@ -101,5 +78,4 @@ object Const {
const val UNINSTALL = "uninstall"
}
}

View File

@@ -1,20 +0,0 @@
package com.topjohnwu.magisk
import android.os.Process
object Constants {
// Paths
val MAGISK_PATH = "/sbin/.magisk/img"
val MAGISK_LOG = "/cache/magisk.log"
val USER_ID get() = Process.myUid() / 100000
const val SNET_REVISION = "b66b1a914978e5f4c4bbfd74a59f4ad371bac107"
const val BOOTCTL_REVISION = "9c5dfc1b8245c0b5b524901ef0ff0f8335757b77"
const val GITHUB_URL = "https://github.com/"
const val GITHUB_API_URL = "https://api.github.com/"
const val GITHUB_RAW_API_URL = "https://raw.githubusercontent.com/"
}

View File

@@ -0,0 +1,155 @@
@file:Suppress("DEPRECATION")
package com.topjohnwu.magisk
import android.annotation.SuppressLint
import android.app.job.JobInfo
import android.app.job.JobScheduler
import android.app.job.JobWorkItem
import android.content.ComponentName
import android.content.Context
import android.content.ContextWrapper
import android.content.Intent
import android.content.res.AssetManager
import android.content.res.Configuration
import android.content.res.Resources
import androidx.annotation.RequiresApi
import com.topjohnwu.magisk.extensions.forceGetDeclaredField
import com.topjohnwu.magisk.model.download.DownloadService
import com.topjohnwu.magisk.model.receiver.GeneralReceiver
import com.topjohnwu.magisk.model.update.UpdateCheckService
import com.topjohnwu.magisk.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.wrapJob(): Context = object : GlobalResContext(this) {
override fun getApplicationContext(): Context {
return this
}
@SuppressLint("NewApi")
override fun getSystemService(name: String): Any? {
return if (!isRunningAsStub) super.getSystemService(name) else
when (name) {
Context.JOB_SCHEDULER_SERVICE ->
JobSchedulerWrapper(super.getSystemService(name) as JobScheduler)
else -> super.getSystemService(name)
}
}
}
fun Class<*>.cmp(pkg: String): ComponentName {
val name = ClassMap[this].name
return ComponentName(pkg, Info.stub?.classToComponent?.get(name) ?: name)
}
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
override fun getResources(): Resources {
return mRes
}
override fun getClassLoader(): ClassLoader {
return javaClass.classLoader!!
}
override fun createConfigurationContext(config: Configuration): Context {
return ResContext(super.createConfigurationContext(config))
}
}
private class ResContext(base: Context) : GlobalResContext(base) {
override val mRes by lazy { base.resources.patch() }
private fun Resources.patch(): Resources {
updateConfig()
if (isRunningAsStub)
assets.addAssetPath(ResourceMgr.resApk)
return this
}
}
object ResourceMgr {
lateinit var resource: Resources
lateinit var resApk: String
fun init(context: Context) {
resource = context.resources
refreshLocale()
if (isRunningAsStub) {
resApk = DynAPK.current(context).path
resource.assets.addAssetPath(resApk)
}
}
}
@RequiresApi(28)
private class JobSchedulerWrapper(private val base: JobScheduler) : JobScheduler() {
override fun schedule(job: JobInfo): Int {
return base.schedule(job.patch())
}
override fun enqueue(job: JobInfo, work: JobWorkItem): Int {
return base.enqueue(job.patch(), work)
}
override fun cancel(jobId: Int) {
base.cancel(jobId)
}
override fun cancelAll() {
base.cancelAll()
}
override fun getAllPendingJobs(): List<JobInfo> {
return base.allPendingJobs
}
override fun getPendingJob(jobId: Int): JobInfo? {
return base.getPendingJob(jobId)
}
private fun JobInfo.patch(): JobInfo {
// We need to swap out the service of JobInfo
val name = service.className
val component = ComponentName(
service.packageName,
Info.stub!!.classToComponent[name] ?: name)
javaClass.forceGetDeclaredField("service")?.set(this, component)
return this
}
}
object ClassMap {
private val map = mapOf(
App::class.java to a.e::class.java,
MainActivity::class.java to a.b::class.java,
SplashActivity::class.java to a.c::class.java,
FlashActivity::class.java to a.f::class.java,
UpdateCheckService::class.java to a.g::class.java,
GeneralReceiver::class.java to a.h::class.java,
DownloadService::class.java to a.j::class.java,
SuRequestActivity::class.java to a.m::class.java,
ProcessPhoenix::class.java to a.r::class.java
)
operator fun get(c: Class<*>) = map.getOrElse(c) { c }
}

View File

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

View File

@@ -1,47 +0,0 @@
package com.topjohnwu.magisk
import android.content.Context
import com.topjohnwu.magisk.KConfig.UpdateChannel.STABLE
import com.topjohnwu.magisk.di.Protected
import com.topjohnwu.magisk.model.preference.PreferenceModel
import com.topjohnwu.magisk.utils.inject
object KConfig : PreferenceModel() {
override val context: Context by inject(Protected)
override val fileName: String = "${context.packageName}_preferences"
private var internalUpdateChannel by preference(Config.Key.UPDATE_CHANNEL, STABLE.id.toString())
var useCustomTabs by preference("useCustomTabs", true)
@JvmStatic
var customUpdateChannel by preference(Config.Key.CUSTOM_CHANNEL, "")
@JvmStatic
var updateChannel: UpdateChannel
get() = UpdateChannel.byId(internalUpdateChannel.toIntOrNull() ?: STABLE.id)
set(value) {
internalUpdateChannel = value.id.toString()
}
internal const val DEFAULT_CHANNEL = "topjohnwu/magisk_files"
enum class UpdateChannel(val id: Int) {
STABLE(Config.Value.STABLE_CHANNEL),
BETA(Config.Value.BETA_CHANNEL),
CANARY(Config.Value.CANARY_CHANNEL),
CANARY_DEBUG(Config.Value.CANARY_DEBUG_CHANNEL),
CUSTOM(Config.Value.CUSTOM_CHANNEL);
companion object {
fun byId(id: Int) = when (id) {
Config.Value.STABLE_CHANNEL -> STABLE
Config.Value.BETA_CHANNEL -> BETA
Config.Value.CUSTOM_CHANNEL -> CUSTOM
Config.Value.CANARY_CHANNEL -> CANARY
Config.Value.CANARY_DEBUG_CHANNEL -> CANARY_DEBUG
else -> STABLE
}
}
}
}

View File

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

View File

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

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

@@ -0,0 +1,17 @@
package com.topjohnwu.magisk.base
import android.content.BroadcastReceiver
import android.content.Context
import android.content.ContextWrapper
import android.content.Intent
import com.topjohnwu.magisk.wrap
import org.koin.core.KoinComponent
abstract class BaseReceiver : BroadcastReceiver(), KoinComponent {
final override fun onReceive(context: Context, intent: Intent?) {
onReceive(context.wrap() as ContextWrapper, intent)
}
abstract fun onReceive(context: ContextWrapper, intent: Intent?)
}

View File

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

View File

@@ -0,0 +1,59 @@
package com.topjohnwu.magisk.base
import android.content.Context
import android.net.Network
import android.net.Uri
import androidx.annotation.MainThread
import androidx.annotation.RequiresApi
import androidx.work.Data
import androidx.work.ListenableWorker
import com.google.common.util.concurrent.ListenableFuture
import java.util.*
abstract class DelegateWorker {
private lateinit var worker: ListenableWorker
val applicationContext: Context
get() = worker.applicationContext
val id: UUID
get() = worker.id
val inputData: Data
get() = worker.inputData
val tags: Set<String>
get() = worker.tags
val triggeredContentUris: List<Uri>
@RequiresApi(24)
get() = worker.triggeredContentUris
val triggeredContentAuthorities: List<String>
@RequiresApi(24)
get() = worker.triggeredContentAuthorities
val network: Network?
@RequiresApi(28)
get() = worker.network
val runAttemptCount: Int
get() = worker.runAttemptCount
val isStopped: Boolean
get() = worker.isStopped
abstract fun doWork(): ListenableWorker.Result
fun onStopped() {}
fun attachWorker(w: ListenableWorker) {
worker = w
}
@MainThread
fun startWork(): ListenableFuture<ListenableWorker.Result> {
return worker.startWork()
}
}

View File

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

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

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

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

@@ -0,0 +1,33 @@
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,34 +0,0 @@
package com.topjohnwu.magisk.data.database
import com.topjohnwu.magisk.Config
import com.topjohnwu.magisk.data.database.base.*
import com.topjohnwu.magisk.model.entity.MagiskLog
import com.topjohnwu.magisk.model.entity.toLog
import com.topjohnwu.magisk.model.entity.toMap
import java.util.concurrent.TimeUnit
class LogDao : BaseDao() {
override val table = DatabaseDefinition.Table.LOG
fun deleteOutdated(
suTimeout: Long = Config.suLogTimeout * TimeUnit.DAYS.toMillis(1)
) = query<Delete> {
condition {
lessThan("time", suTimeout.toString())
}
}.ignoreElement()
fun deleteAll() = query<Delete> {}.ignoreElement()
fun fetchAll() = query<Select> {
orderBy("time", Order.DESC)
}.flattenAsFlowable { it }
.map { it.toLog() }
.toList()
fun put(log: MagiskLog) = query<Insert> {
values(log.toMap())
}.ignoreElement()
}

View File

@@ -2,12 +2,16 @@ package com.topjohnwu.magisk.data.database
import android.content.Context
import android.content.pm.PackageManager
import com.topjohnwu.magisk.Constants
import com.topjohnwu.magisk.data.database.base.*
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.extensions.now
import com.topjohnwu.magisk.model.entity.MagiskPolicy
import com.topjohnwu.magisk.model.entity.toMap
import com.topjohnwu.magisk.model.entity.toPolicy
import com.topjohnwu.magisk.utils.now
import timber.log.Timber
import java.util.concurrent.TimeUnit
@@ -15,7 +19,7 @@ class PolicyDao(
private val context: Context
) : BaseDao() {
override val table: String = DatabaseDefinition.Table.POLICY
override val table: String = Table.POLICY
fun deleteOutdated(
nowSeconds: Long = TimeUnit.MILLISECONDS.toSeconds(now)
@@ -47,12 +51,7 @@ class PolicyDao(
condition {
equals("uid", uid)
}
}.map { it.firstOrNull()?.toPolicy(context.packageManager) }
.doOnError {
if (it is PackageManager.NameNotFoundException) {
delete(uid).subscribe()
}
}
}.map { it.first().toPolicySafe() }
fun update(policy: MagiskPolicy) = query<Replace> {
values(policy.toMap())
@@ -60,10 +59,20 @@ class PolicyDao(
fun fetchAll() = query<Select> {
condition {
equals("uid/100000", Constants.USER_ID)
equals("uid/100000", Const.USER_ID)
}
}.flattenAsFlowable { it }
.map { it.toPolicy(context.packageManager) }
.toList()
}.map { it.mapNotNull { it.toPolicySafe() } }
}
private fun Map<String, String>.toPolicySafe(): MagiskPolicy? {
return runCatching { toPolicy(context.packageManager) }.getOrElse {
Timber.e(it)
if (it is PackageManager.NameNotFoundException) {
val uid = getOrElse("uid") { null } ?: return null
delete(uid).subscribe()
}
null
}
}
}

View File

@@ -0,0 +1,69 @@
package com.topjohnwu.magisk.data.database
import androidx.room.*
import com.topjohnwu.magisk.Config
import com.topjohnwu.magisk.model.entity.module.Repo
@Database(version = 6, entities = [Repo::class, RepoEtag::class])
abstract class RepoDatabase : RoomDatabase() {
abstract fun repoDao() : RepoDao
}
@Dao
abstract class RepoDao(private val db: RepoDatabase) {
val repoIDList get() = getRepoID().map { it.id }
val repos: List<Repo> get() = when (Config.repoOrder) {
Config.Value.ORDER_NAME -> getReposNameOrder()
else -> getReposDateOrder()
}
var etagKey: String
set(value) = addEtagRaw(RepoEtag(0, value))
get() = etagRaw()?.key.orEmpty()
fun clear() = db.clearAllTables()
@Query("SELECT * FROM repos ORDER BY last_update DESC")
protected abstract fun getReposDateOrder(): List<Repo>
@Query("SELECT * FROM repos ORDER BY name COLLATE NOCASE")
protected abstract fun getReposNameOrder(): List<Repo>
@Insert(onConflict = OnConflictStrategy.REPLACE)
abstract fun addRepo(repo: Repo)
@Query("SELECT * FROM repos WHERE id = :id")
abstract fun getRepo(id: String): Repo?
@Query("SELECT id FROM repos")
protected abstract fun getRepoID(): List<RepoID>
@Delete
abstract fun removeRepo(repo: Repo)
@Query("DELETE FROM repos WHERE id = :id")
abstract fun removeRepo(id: String)
@Query("DELETE FROM repos WHERE id IN (:idList)")
abstract fun removeRepos(idList: Collection<String>)
@Query("SELECT * FROM etag")
protected abstract fun etagRaw(): RepoEtag?
@Insert(onConflict = OnConflictStrategy.REPLACE)
protected abstract fun addEtagRaw(etag: RepoEtag)
}
data class RepoID(
@PrimaryKey val id: String
)
@Entity(tableName = "etag")
data class RepoEtag(
@PrimaryKey val id: Int,
val key: String
)

View File

@@ -1,118 +0,0 @@
package com.topjohnwu.magisk.data.database;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import com.topjohnwu.magisk.Config;
import com.topjohnwu.magisk.model.entity.Repo;
import java.util.HashSet;
import java.util.Set;
@Deprecated
public class RepoDatabaseHelper extends SQLiteOpenHelper {
private static final int DATABASE_VER = 5;
private static final String TABLE_NAME = "repos";
private final SQLiteDatabase mDb;
@Deprecated
public RepoDatabaseHelper(Context context) {
super(context, "repo.db", null, DATABASE_VER);
mDb = getWritableDatabase();
}
@Override
public void onCreate(SQLiteDatabase db) {
onUpgrade(db, 0, DATABASE_VER);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
if (oldVersion != newVersion) {
// Nuke old DB and create new table
db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
db.execSQL(
"CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " " +
"(id TEXT, name TEXT, version TEXT, versionCode INT, " +
"author TEXT, description TEXT, last_update INT, PRIMARY KEY(id))");
Config.remove(Config.Key.ETAG_KEY);
}
}
@Override
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
onUpgrade(db, 0, DATABASE_VER);
}
@Deprecated
public void clearRepo() {
mDb.delete(TABLE_NAME, null, null);
}
@Deprecated
public void removeRepo(String id) {
mDb.delete(TABLE_NAME, "id=?", new String[]{id});
}
@Deprecated
public void removeRepo(Repo repo) {
removeRepo(repo.getId());
}
@Deprecated
public void removeRepo(Iterable<String> list) {
for (String id : list) {
if (id == null) continue;
mDb.delete(TABLE_NAME, "id=?", new String[]{id});
}
}
@Deprecated
public void addRepo(Repo repo) {
mDb.replace(TABLE_NAME, null, repo.getContentValues());
}
@Deprecated
public Repo getRepo(String id) {
try (Cursor c = mDb.query(TABLE_NAME, null, "id=?", new String[]{id}, null, null, null)) {
if (c.moveToNext()) {
return new Repo(c);
}
}
return null;
}
@Deprecated
public Cursor getRawCursor() {
return mDb.query(TABLE_NAME, null, null, null, null, null, null);
}
@Deprecated
public Cursor getRepoCursor() {
String orderBy = null;
switch ((int) Config.get(Config.Key.REPO_ORDER)) {
case Config.Value.ORDER_NAME:
orderBy = "name COLLATE NOCASE";
break;
case Config.Value.ORDER_DATE:
orderBy = "last_update DESC";
}
return mDb.query(TABLE_NAME, null, null, null, null, null, orderBy);
}
@Deprecated
public Set<String> getRepoIDSet() {
HashSet<String> set = new HashSet<>(300);
try (Cursor c = mDb.query(TABLE_NAME, null, null, null, null, null, null)) {
while (c.moveToNext()) {
set.add(c.getString(c.getColumnIndex("id")));
}
}
return set;
}
}

View File

@@ -1,21 +1,25 @@
package com.topjohnwu.magisk.data.database
import com.topjohnwu.magisk.data.database.base.*
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
class SettingsDao : BaseDao() {
override val table = DatabaseDefinition.Table.SETTINGS
override val table = Table.SETTINGS
fun delete(key: String) = query<Delete> {
condition { equals("key", key) }
}.ignoreElement()
fun put(key: String, value: Int) = query<Insert> {
values(key to value.toString())
fun put(key: String, value: Int) = query<Replace> {
values("key" to key, "value" to value)
}.ignoreElement()
fun fetch(key: String, default: Int = -1) = query<Select> {
fields("value")
condition { equals("key", key) }
}.map { it.firstOrNull()?.values?.firstOrNull()?.toIntOrNull() ?: default }
}
}

View File

@@ -1,17 +1,20 @@
package com.topjohnwu.magisk.data.database
import com.topjohnwu.magisk.data.database.base.*
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
class StringDao : BaseDao() {
override val table = DatabaseDefinition.Table.STRINGS
override val table = Table.STRINGS
fun delete(key: String) = query<Delete> {
condition { equals("key", key) }
}.ignoreElement()
fun put(key: String, value: String) = query<Insert> {
values(key to value)
fun put(key: String, value: String) = query<Replace> {
values("key" to key, "value" to value)
}.ignoreElement()
fun fetch(key: String, default: String = "") = query<Select> {
@@ -19,4 +22,4 @@ class StringDao : BaseDao() {
condition { equals("key", key) }
}.map { it.firstOrNull()?.values?.firstOrNull() ?: default }
}
}

View File

@@ -0,0 +1,36 @@
package com.topjohnwu.magisk.data.database
import androidx.room.*
import com.topjohnwu.magisk.model.entity.MagiskLog
import io.reactivex.Completable
import io.reactivex.Single
import java.util.*
@Database(version = 1, entities = [MagiskLog::class])
abstract class SuLogDatabase : RoomDatabase() {
abstract fun suLogDao(): SuLogDao
}
@Dao
abstract class SuLogDao(private val db: SuLogDatabase) {
private val twoWeeksAgo =
Calendar.getInstance().apply { add(Calendar.WEEK_OF_YEAR, -2) }.timeInMillis
fun deleteAll() = Completable.fromAction { db.clearAllTables() }
fun fetchAll() = deleteOutdated().andThen(fetch())
@Query("SELECT * FROM logs ORDER BY time DESC")
protected abstract fun fetch(): Single<MutableList<MagiskLog>>
@Insert
abstract fun insert(log: MagiskLog): Completable
@Query("DELETE FROM logs WHERE time < :timeout")
protected abstract fun deleteOutdated(
timeout: Long = twoWeeksAgo
): Completable
}

View File

@@ -1,15 +0,0 @@
package com.topjohnwu.magisk.data.database.base
abstract class BaseDao {
abstract val table: String
inline fun <reified Builder : MagiskQueryBuilder> query(builder: Builder.() -> Unit) =
Builder::class.java.newInstance()
.apply { table = this@BaseDao.table }
.apply(builder)
.toString()
.let { MagiskQuery(it) }
.query()
}

View File

@@ -1,30 +0,0 @@
package com.topjohnwu.magisk.data.database.base
import androidx.annotation.AnyThread
import com.topjohnwu.superuser.Shell
import io.reactivex.Single
object DatabaseDefinition {
object Table {
const val POLICY = "policies"
const val LOG = "logs"
const val SETTINGS = "settings"
const val STRINGS = "strings"
}
}
@AnyThread
fun MagiskQuery.query() = query.su()
fun String.suRaw() = Single.fromCallable { Shell.su(this).exec().out }
fun String.su() = suRaw().map { it.toMap() }
fun List<String>.toMap() = map { it.split(Regex("\\|")) }
.map { it.toMapInternal() }
private fun List<String>.toMapInternal() = map { it.split("=", limit = 2) }
.filter { it.size == 2 }
.map { Pair(it[0], it[1]) }
.toMap()

View File

@@ -1,5 +0,0 @@
package com.topjohnwu.magisk.data.database.base
inline class MagiskQuery(private val _query: String) {
val query get() = "magisk --sqlite '$_query'"
}

View File

@@ -0,0 +1,44 @@
package com.topjohnwu.magisk.data.database.magiskdb
import androidx.annotation.StringDef
import com.topjohnwu.superuser.Shell
import io.reactivex.Single
abstract class BaseDao {
object Table {
const val POLICY = "policies"
const val LOG = "logs"
const val SETTINGS = "settings"
const val STRINGS = "strings"
}
@StringDef(Table.POLICY, Table.LOG, Table.SETTINGS, Table.STRINGS)
@Retention(AnnotationRetention.SOURCE)
annotation class TableStrict
@TableStrict
abstract val table: String
inline fun <reified Builder : Query.Builder> query(builder: Builder.() -> Unit = {}) =
Builder::class.java.newInstance()
.apply { table = this@BaseDao.table }
.apply(builder)
.toString()
.let { Query(it) }
.query()
}
fun Query.query() = query.su()
private fun String.suRaw() = Single.fromCallable { Shell.su(this).exec().out }
private fun String.su() = suRaw().map { it.toMap() }
private fun List<String>.toMap() = map { it.split(Regex("\\|")) }
.map { it.toMapInternal() }
private fun List<String>.toMapInternal() = map { it.split("=", limit = 2) }
.filter { it.size == 2 }
.map { Pair(it[0], it[1]) }
.toMap()

View File

@@ -1,27 +1,17 @@
package com.topjohnwu.magisk.data.database.base
package com.topjohnwu.magisk.data.database.magiskdb
import androidx.annotation.StringDef
import com.topjohnwu.magisk.data.database.base.Order.Companion.ASC
import com.topjohnwu.magisk.data.database.base.Order.Companion.DESC
interface MagiskQueryBuilder {
class Query(private val _query: String) {
val query get() = "magisk --sqlite '$_query'"
val requestType: String
var table: String
companion object {
inline operator fun <reified Builder : MagiskQueryBuilder> invoke(builder: Builder.() -> Unit): MagiskQuery =
Builder::class.java.newInstance()
.apply(builder)
.toString()
.let {
MagiskQuery(it)
}
interface Builder {
val requestType: String
var table: String
}
}
class Delete : MagiskQueryBuilder {
class Delete : Query.Builder {
override val requestType: String = "DELETE FROM"
override var table = ""
@@ -32,15 +22,11 @@ class Delete : MagiskQueryBuilder {
}
override fun toString(): String {
return StringBuilder()
.appendln(requestType)
.appendln(table)
.appendln(condition)
.toString()
return listOf(requestType, table, condition).joinToString(" ")
}
}
class Select : MagiskQueryBuilder {
class Select : Query.Builder {
override val requestType: String get() = "SELECT $fields FROM"
override lateinit var table: String
@@ -65,13 +51,7 @@ class Select : MagiskQueryBuilder {
}
override fun toString(): String {
return StringBuilder()
.appendln(requestType)
.appendln(table)
.appendln(condition)
.appendln(orderField)
.toString()
.replace("\n", " ")
return listOf(requestType, table, condition, orderField).joinToString(" ")
}
}
@@ -79,28 +59,30 @@ class Replace : Insert() {
override val requestType: String = "REPLACE INTO"
}
open class Insert : MagiskQueryBuilder {
open class Insert : Query.Builder {
override val requestType: String = "INSERT INTO"
override lateinit var table: String
private val keys get() = _values.keys.joinToString(",")
private val values get() = _values.values.joinToString(",") { "\"$it\"" }
private var _values: Map<String, String> = mapOf()
private val values get() = _values.values.joinToString(",") {
when (it) {
is Boolean -> if (it) "1" else "0"
is Number -> it.toString()
else -> "\"$it\""
}
}
private var _values: Map<String, Any> = mapOf()
fun values(vararg pairs: Pair<String, String>) {
fun values(vararg pairs: Pair<String, Any>) {
_values = pairs.toMap()
}
fun values(values: Map<String, String>) {
fun values(values: Map<String, Any>) {
_values = values
}
override fun toString(): String {
return StringBuilder()
.appendln(requestType)
.appendln(table)
.appendln("($keys) VALUES($values)")
.toString()
return listOf(requestType, table, "($keys) VALUES($values)").joinToString(" ")
}
}
@@ -145,19 +127,11 @@ class Condition {
}
}
class Order {
@set:OrderStrict
var order = DESC
var field = ""
companion object {
const val ASC = "ASC"
const val DESC = "DESC"
}
object Order {
const val ASC = "ASC"
const val DESC = "DESC"
}
@StringDef(ASC, DESC)
@StringDef(Order.ASC, Order.DESC)
@Retention(AnnotationRetention.SOURCE)
annotation class OrderStrict
annotation class OrderStrict

View File

@@ -1,63 +0,0 @@
package com.topjohnwu.magisk.data.network
import com.topjohnwu.magisk.Constants
import com.topjohnwu.magisk.KConfig
import com.topjohnwu.magisk.model.entity.MagiskConfig
import io.reactivex.Single
import okhttp3.ResponseBody
import retrofit2.http.GET
import retrofit2.http.Path
import retrofit2.http.Streaming
import retrofit2.http.Url
interface GithubRawApiServices {
//region topjohnwu/magisk_files
@GET("$MAGISK_FILES/master/stable.json")
fun fetchConfig(): Single<MagiskConfig>
@GET("$MAGISK_FILES/master/beta.json")
fun fetchBetaConfig(): Single<MagiskConfig>
@GET("$MAGISK_FILES/master/canary_builds/release.json")
fun fetchCanaryConfig(): Single<MagiskConfig>
@GET("$MAGISK_FILES/master/canary_builds/canary.json")
fun fetchCanaryDebugConfig(): Single<MagiskConfig>
@GET
fun fetchCustomConfig(@Url url: String): Single<MagiskConfig>
@GET("$MAGISK_FILES/{$REVISION}/snet.apk")
@Streaming
fun fetchSafetynet(@Path(REVISION) revision: String = Constants.SNET_REVISION): Single<ResponseBody>
@GET("$MAGISK_FILES/{$REVISION}/bootctl")
@Streaming
fun fetchBootctl(@Path(REVISION) revision: String = Constants.BOOTCTL_REVISION): Single<ResponseBody>
//endregion
/**
* This method shall be used exclusively for fetching files from urls from previous requests.
* Him, who uses it in a wrong way, shall die in an eternal flame.
* */
@GET
@Streaming
fun fetchFile(@Url url: String): Single<ResponseBody>
companion object {
private const val REVISION = "revision"
private const val MODULE = "module"
private const val FILE = "file"
private const val MAGISK_FILES = KConfig.DEFAULT_CHANNEL
private const val MAGISK_MASTER = "topjohnwu/Magisk/master"
private const val MAGISK_MODULES = "Magisk-Modules-Repo"
}
}

View File

@@ -0,0 +1,81 @@
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 io.reactivex.Flowable
import io.reactivex.Single
import okhttp3.ResponseBody
import retrofit2.adapter.rxjava2.Result
import retrofit2.http.*
interface GithubRawServices {
//region topjohnwu/magisk_files
@GET("$MAGISK_FILES/master/stable.json")
fun fetchStableUpdate(): Single<UpdateInfo>
@GET("$MAGISK_FILES/master/beta.json")
fun fetchBetaUpdate(): Single<UpdateInfo>
@GET("$MAGISK_FILES/canary/release.json")
fun fetchCanaryUpdate(): Single<UpdateInfo>
@GET("$MAGISK_FILES/canary/debug.json")
fun fetchCanaryDebugUpdate(): Single<UpdateInfo>
@GET
fun fetchCustomUpdate(@Url url: String): Single<UpdateInfo>
@GET("$MAGISK_FILES/{$REVISION}/snet.jar")
@Streaming
fun fetchSafetynet(@Path(REVISION) revision: String = Const.SNET_REVISION): Single<ResponseBody>
@GET("$MAGISK_FILES/{$REVISION}/bootctl")
@Streaming
fun fetchBootctl(@Path(REVISION) revision: String = Const.BOOTCTL_REVISION): Single<ResponseBody>
@GET("$MAGISK_MASTER/scripts/module_installer.sh")
@Streaming
fun fetchInstaller(): Single<ResponseBody>
@GET("$MAGISK_MODULES/{$MODULE}/master/{$FILE}")
fun fetchModuleInfo(@Path(MODULE) id: String, @Path(FILE) file: String): Single<String>
//endregion
/**
* This method shall be used exclusively for fetching files from urls from previous requests.
* Him, who uses it in a wrong way, shall die in an eternal flame.
* */
@GET
@Streaming
fun fetchFile(@Url url: String): Single<ResponseBody>
@GET
fun fetchString(@Url url: String): Single<String>
companion object {
private const val REVISION = "revision"
private const val MODULE = "module"
private const val FILE = "file"
private const val MAGISK_FILES = "topjohnwu/magisk_files"
private const val MAGISK_MASTER = "topjohnwu/Magisk/master"
private const val MAGISK_MODULES = "Magisk-Modules-Repo"
}
}
interface GithubApiServices {
@GET("repos")
fun fetchRepos(@Query("page") page: Int,
@Header(Const.Key.IF_NONE_MATCH) etag: String,
@Query("sort") sort: String = "pushed",
@Query("per_page") count: Int = 100): Flowable<Result<List<GithubRepoInfo>>>
}

View File

@@ -1,15 +0,0 @@
package com.topjohnwu.magisk.data.repository
import com.topjohnwu.magisk.data.database.PolicyDao
import com.topjohnwu.magisk.model.entity.MagiskPolicy
class AppRepository(private val policyDao: PolicyDao) {
fun deleteOutdated() = policyDao.deleteOutdated()
fun delete(packageName: String) = policyDao.delete(packageName)
fun delete(uid: Int) = policyDao.delete(uid)
fun fetch(uid: Int) = policyDao.fetch(uid)
fun fetchAll() = policyDao.fetchAll()
fun update(policy: MagiskPolicy) = policyDao.update(policy)
}

View File

@@ -0,0 +1,106 @@
package com.topjohnwu.magisk.data.repository
import com.topjohnwu.magisk.data.database.SettingsDao
import com.topjohnwu.magisk.data.database.StringDao
import io.reactivex.schedulers.Schedulers
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
interface DBConfig {
val settingsDao: SettingsDao
val stringDao: StringDao
fun dbSettings(
name: String,
default: Int
) = DBSettingsValue(name, default)
fun dbSettings(
name: String,
default: Boolean
) = DBBoolSettings(name, default)
fun dbStrings(
name: String,
default: String,
sync: Boolean = false
) = DBStringsValue(name, default, sync)
}
class DBSettingsValue(
private val name: String,
private val default: Int
) : ReadWriteProperty<DBConfig, Int> {
private var value: Int? = null
@Synchronized
override fun getValue(thisRef: DBConfig, property: KProperty<*>): Int {
if (value == null)
value = thisRef.settingsDao.fetch(name, default).blockingGet()
return value!!
}
override fun setValue(thisRef: DBConfig, property: KProperty<*>, value: Int) {
synchronized(this) {
this.value = value
}
thisRef.settingsDao.put(name, value)
.subscribeOn(Schedulers.io())
.subscribe()
}
}
class DBBoolSettings(
name: String,
default: Boolean
) : ReadWriteProperty<DBConfig, Boolean> {
val base = DBSettingsValue(name, if (default) 1 else 0)
override fun getValue(thisRef: DBConfig, property: KProperty<*>): Boolean =
base.getValue(thisRef, property) != 0
override fun setValue(thisRef: DBConfig, property: KProperty<*>, value: Boolean) =
base.setValue(thisRef, property, if (value) 1 else 0)
}
class DBStringsValue(
private val name: String,
private val default: String,
private val sync: Boolean
) : ReadWriteProperty<DBConfig, String> {
private var value: String? = null
@Synchronized
override fun getValue(thisRef: DBConfig, property: KProperty<*>): String {
if (value == null)
value = thisRef.stringDao.fetch(name, default).blockingGet()
return value!!
}
override fun setValue(thisRef: DBConfig, property: KProperty<*>, value: String) {
synchronized(this) {
this.value = value
}
if (value.isEmpty()) {
if (sync) {
thisRef.stringDao.delete(name).blockingAwait()
} else {
thisRef.stringDao.delete(name)
.subscribeOn(Schedulers.io())
.subscribe()
}
} else {
if (sync) {
thisRef.stringDao.put(name, value).blockingAwait()
} else {
thisRef.stringDao.put(name, value)
.subscribeOn(Schedulers.io())
.subscribe()
}
}
}
}

View File

@@ -1,43 +1,38 @@
package com.topjohnwu.magisk.data.repository
import com.topjohnwu.magisk.Const
import com.topjohnwu.magisk.Constants
import com.topjohnwu.magisk.data.database.LogDao
import com.topjohnwu.magisk.data.database.base.suRaw
import com.topjohnwu.magisk.data.database.SuLogDao
import com.topjohnwu.magisk.model.entity.MagiskLog
import com.topjohnwu.magisk.model.entity.WrappedMagiskLog
import com.topjohnwu.magisk.utils.toSingle
import com.topjohnwu.superuser.Shell
import timber.log.Timber
import io.reactivex.Completable
import io.reactivex.Single
import java.util.concurrent.TimeUnit
class LogRepository(
private val logDao: LogDao
private val logDao: SuLogDao
) {
fun fetchLogs() = logDao.fetchAll()
.map { it.sortByDescending { it.date.time }; it }
.map { it.wrap() }
fun fetchLogs() = logDao.fetchAll().map { it.wrap() }
fun fetchMagiskLogs() = "tail -n 5000 ${Constants.MAGISK_LOG}".suRaw()
.filter { it.isNotEmpty() }
.map { Timber.i(it.toString()); it }
fun fetchMagiskLogs() = Single.fromCallable {
Shell.su("tail -n 5000 ${Const.MAGISK_LOG}").exec().out
}.flattenAsFlowable { it }.filter { it.isNotEmpty() }
fun clearLogs() = logDao.deleteAll()
fun clearOutdated() = logDao.deleteOutdated()
fun clearMagiskLogs() = Shell.su("echo -n > " + Const.MAGISK_LOG)
.toSingle()
.map { it.exec() }
fun clearMagiskLogs() = Completable.fromAction {
Shell.su("echo -n > ${Const.MAGISK_LOG}").exec()
}
fun put(log: MagiskLog) = logDao.put(log)
fun insert(log: MagiskLog) = logDao.insert(log)
private fun List<MagiskLog>.wrap(): List<WrappedMagiskLog> {
val day = TimeUnit.DAYS.toMillis(1)
return groupBy { it.date.time / day }
return groupBy { it.time / day }
.map { WrappedMagiskLog(it.key * day, it.value) }
}
}
}

View File

@@ -1,86 +1,48 @@
package com.topjohnwu.magisk.data.repository
import android.content.Context
import android.content.pm.PackageManager
import com.topjohnwu.magisk.App
import com.topjohnwu.magisk.Config
import com.topjohnwu.magisk.KConfig
import com.topjohnwu.magisk.data.database.base.su
import com.topjohnwu.magisk.data.database.base.suRaw
import com.topjohnwu.magisk.data.network.GithubRawApiServices
import com.topjohnwu.magisk.Info
import com.topjohnwu.magisk.data.network.GithubRawServices
import com.topjohnwu.magisk.extensions.getLabel
import com.topjohnwu.magisk.extensions.packageName
import com.topjohnwu.magisk.extensions.toSingle
import com.topjohnwu.magisk.model.entity.HideAppInfo
import com.topjohnwu.magisk.model.entity.HideTarget
import com.topjohnwu.magisk.model.entity.Version
import com.topjohnwu.magisk.utils.Utils
import com.topjohnwu.magisk.utils.inject
import com.topjohnwu.magisk.utils.toSingle
import com.topjohnwu.magisk.utils.writeToFile
import com.topjohnwu.superuser.Shell
import io.reactivex.Single
import io.reactivex.functions.BiFunction
class MagiskRepository(
private val context: Context,
private val apiRaw: GithubRawApiServices,
private val packageManager: PackageManager
private val apiRaw: GithubRawServices,
private val packageManager: PackageManager
) {
fun fetchMagisk() = fetchConfig()
.flatMap { apiRaw.fetchFile(it.magisk.link) }
.map { it.writeToFile(context, FILE_MAGISK_ZIP) }
fun fetchSafetynet() = apiRaw.fetchSafetynet()
fun fetchManager() = fetchConfig()
.flatMap { apiRaw.fetchFile(it.app.link) }
.map { it.writeToFile(context, FILE_MAGISK_APK) }
fun fetchUninstaller() = fetchConfig()
.flatMap { apiRaw.fetchFile(it.uninstaller.link) }
.map { it.writeToFile(context, FILE_UNINSTALLER_ZIP) }
fun fetchSafetynet() = apiRaw
.fetchSafetynet()
.map { it.writeToFile(context, FILE_SAFETY_NET_APK) }
fun fetchBootctl() = apiRaw
.fetchBootctl()
.map { it.writeToFile(context, FILE_BOOTCTL_SH) }
fun fetchConfig() = when (KConfig.updateChannel) {
KConfig.UpdateChannel.STABLE -> apiRaw.fetchConfig()
KConfig.UpdateChannel.BETA -> apiRaw.fetchBetaConfig()
KConfig.UpdateChannel.CANARY -> apiRaw.fetchCanaryConfig()
KConfig.UpdateChannel.CANARY_DEBUG -> apiRaw.fetchCanaryDebugConfig()
KConfig.UpdateChannel.CUSTOM -> apiRaw.fetchCustomConfig(KConfig.customUpdateChannel)
}
.doOnSuccess {
Config.remoteMagiskVersionCode = it.magisk.versionCode.toIntOrNull() ?: -1
Config.magiskLink = it.magisk.link
Config.magiskNoteLink = it.magisk.note
Config.magiskMD5 = it.magisk.hash
Config.remoteManagerVersionCode = it.app.versionCode.toIntOrNull() ?: -1
Config.remoteManagerVersionString = it.app.version
Config.managerLink = it.app.link
Config.managerNoteLink = it.app.note
Config.uninstallerLink = it.uninstaller.link
}
fun fetchMagiskVersion(): Single<Version> = Single.zip(
fetchMagiskVersionName(),
fetchMagiskVersionCode(),
BiFunction { versionName, versionCode ->
Version(versionName, versionCode)
fun fetchUpdate() = when (Config.updateChannel) {
Config.Value.DEFAULT_CHANNEL, Config.Value.STABLE_CHANNEL -> apiRaw.fetchStableUpdate()
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)
else -> throw IllegalArgumentException()
}.flatMap {
// If remote version is lower than current installed, try switching to beta
if (it.magisk.versionCode < Info.env.magiskVersionCode
&& Config.updateChannel == Config.Value.DEFAULT_CHANNEL) {
Config.updateChannel = Config.Value.BETA_CHANNEL
apiRaw.fetchBetaUpdate()
} else {
Single.just(it)
}
)
}.doOnSuccess { Info.remote = it }
fun fetchApps() =
Single.fromCallable { packageManager.getInstalledApplications(0) }
.flattenAsFlowable { it }
.filter { it.enabled && !blacklist.contains(it.packageName) }
.map {
val label = Utils.getAppLabel(it, packageManager)
val label = it.getLabel(packageManager)
val icon = it.loadIcon(packageManager)
HideAppInfo(it, label, icon)
}
@@ -93,30 +55,14 @@ class MagiskRepository(
.map { HideTarget(it) }
.toList()
private fun fetchMagiskVersionName() = "magisk -v".suRaw()
.map { it.first() }
.map { it.substring(0 until it.indexOf(":")) }
.onErrorReturn { "Unknown" }
private fun fetchMagiskVersionCode() = "magisk -V".suRaw()
.map { it.first() }
.map { it.toIntOrNull() ?: -1 }
.onErrorReturn { -1 }
fun toggleHide(isEnabled: Boolean, packageName: String, process: String) =
"magiskhide --%s %s %s".format(isEnabled.state, packageName, process).su().ignoreElement()
Shell.su("magiskhide --${isEnabled.state} $packageName $process").submit()
private val Boolean.state get() = if (this) "add" else "rm"
companion object {
const val FILE_MAGISK_ZIP = "magisk.zip"
const val FILE_MAGISK_APK = "magisk.apk"
const val FILE_UNINSTALLER_ZIP = "uninstaller.zip"
const val FILE_SAFETY_NET_APK = "safetynet.apk"
const val FILE_BOOTCTL_SH = "bootctl"
private val blacklist = listOf(
let { val app: App by inject(); app }.packageName,
private val blacklist by lazy { listOf(
packageName,
"android",
"com.android.chrome",
"com.chrome.beta",
@@ -124,7 +70,7 @@ class MagiskRepository(
"com.chrome.canary",
"com.android.webview",
"com.google.android.webview"
)
) }
}
}
}

View File

@@ -1,11 +0,0 @@
package com.topjohnwu.magisk.data.repository
import com.topjohnwu.magisk.data.database.SettingsDao
class SettingRepository(private val settingsDao: SettingsDao) {
fun fetch(key: String, default: Int) = settingsDao.fetch(key, default)
fun put(key: String, value: Int) = settingsDao.put(key, value)
fun delete(key: String) = settingsDao.delete(key)
}

View File

@@ -1,11 +1,15 @@
package com.topjohnwu.magisk.data.repository
import com.topjohnwu.magisk.data.database.StringDao
import com.topjohnwu.magisk.data.network.GithubRawServices
import com.topjohnwu.magisk.model.entity.module.Repo
class StringRepository(private val stringDao: StringDao) {
class StringRepository(
private val api: GithubRawServices
) {
fun fetch(key: String, default: String) = stringDao.fetch(key, default)
fun put(key: String, value: String) = stringDao.put(key, value)
fun delete(key: String) = stringDao.delete(key)
fun getString(url: String) = api.fetchString(url)
fun getMetadata(repo: Repo) = api.fetchModuleInfo(repo.id, "module.prop")
fun getReadme(repo: Repo) = api.fetchModuleInfo(repo.id, "README.md")
}

View File

@@ -0,0 +1,26 @@
package com.topjohnwu.magisk.databinding
import android.view.View
import androidx.core.view.isGone
import androidx.core.view.isInvisible
import androidx.databinding.BindingAdapter
@BindingAdapter("gone")
fun setGone(view: View, gone: Boolean) {
view.isGone = gone
}
@BindingAdapter("invisible")
fun setInvisible(view: View, invisible: Boolean) {
view.isInvisible = invisible
}
@BindingAdapter("goneUnless")
fun setGoneUnless(view: View, goneUnless: Boolean) {
setGone(view, goneUnless.not())
}
@BindingAdapter("invisibleUnless")
fun setInvisibleUnless(view: View, invisibleUnless: Boolean) {
setInvisible(view, invisibleUnless.not())
}

View File

@@ -0,0 +1,57 @@
package com.topjohnwu.magisk.databinding
import android.graphics.drawable.GradientDrawable
import android.graphics.drawable.InsetDrawable
import androidx.databinding.BindingAdapter
import androidx.recyclerview.widget.RecyclerView
import com.topjohnwu.magisk.extensions.startEndToLeftRight
import com.topjohnwu.magisk.extensions.toPx
import com.topjohnwu.magisk.utils.KItemDecoration
import kotlin.math.roundToInt
@BindingAdapter(
"dividerColor",
"dividerHorizontal",
"dividerSize",
"dividerAfterLast",
"dividerMarginStart",
"dividerMarginEnd",
"dividerMarginTop",
"dividerMarginBottom",
requireAll = false
)
fun setDivider(
view: RecyclerView,
color: Int,
horizontal: Boolean,
_size: Float,
_afterLast: Boolean?,
marginStartF: Float,
marginEndF: Float,
marginTopF: Float,
marginBottomF: Float
) {
val orientation = if (horizontal) RecyclerView.HORIZONTAL else RecyclerView.VERTICAL
val size = if (_size > 0) _size.roundToInt() else 1.toPx()
val (width, height) = if (horizontal) size to 1 else 1 to size
val afterLast = _afterLast ?: true
val marginStart = marginStartF.roundToInt()
val marginEnd = marginEndF.roundToInt()
val marginTop = marginTopF.roundToInt()
val marginBottom = marginBottomF.roundToInt()
val (marginLeft, marginRight) = view.context.startEndToLeftRight(marginStart, marginEnd)
val drawable = GradientDrawable().apply {
setSize(width, height)
shape = GradientDrawable.RECTANGLE
setColor(color)
}.let {
InsetDrawable(it, marginLeft, marginTop, marginRight, marginBottom)
}
val decoration = KItemDecoration(view.context, orientation)
.setDeco(drawable)
.apply { showAfterLast = afterLast }
view.addItemDecoration(decoration)
}

View File

@@ -0,0 +1,13 @@
package com.topjohnwu.magisk.databinding
import androidx.databinding.ViewDataBinding
import me.tatarka.bindingcollectionadapter2.BindingRecyclerViewAdapter
open class BindingBoundAdapter : BindingRecyclerViewAdapter<RvItem>() {
override fun onBindBinding(binding: ViewDataBinding, variableId: Int, layoutRes: Int, position: Int, item: RvItem) {
super.onBindBinding(binding, variableId, layoutRes, position, item)
item.onBindingBound(binding)
}
}

View File

@@ -0,0 +1,48 @@
package com.topjohnwu.magisk.databinding
import androidx.annotation.CallSuper
import androidx.databinding.ViewDataBinding
import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.utils.DiffObservableList
import me.tatarka.bindingcollectionadapter2.ItemBinding
abstract class RvItem {
abstract val layoutRes: Int
@CallSuper
open fun bind(binding: ItemBinding<*>) {
binding.set(BR.item, layoutRes)
}
/**
* This callback is useful if you want to manipulate your views directly.
* If you want to use this callback, you must set [me.tatarka.bindingcollectionadapter2.BindingRecyclerViewAdapter]
* on your RecyclerView and call it from there. You can use [BindingBoundAdapter] for your convenience.
*/
open fun onBindingBound(binding: ViewDataBinding) {}
}
abstract class ComparableRvItem<in T> : RvItem() {
abstract fun itemSameAs(other: T): Boolean
abstract fun contentSameAs(other: T): Boolean
@Suppress("UNCHECKED_CAST")
open fun genericItemSameAs(other: Any): Boolean = other::class == this::class && itemSameAs(other as T)
@Suppress("UNCHECKED_CAST")
open fun genericContentSameAs(other: Any): Boolean = other::class == this::class && contentSameAs(other as T)
companion object {
val callback = object : DiffObservableList.Callback<ComparableRvItem<*>> {
override fun areItemsTheSame(
oldItem: ComparableRvItem<*>,
newItem: ComparableRvItem<*>
) = oldItem.genericItemSameAs(newItem)
override fun areContentsTheSame(
oldItem: ComparableRvItem<*>,
newItem: ComparableRvItem<*>
) = oldItem.genericContentSameAs(newItem)
}
}
}

View File

@@ -1,18 +1,61 @@
package com.topjohnwu.magisk.di
import android.annotation.SuppressLint
import android.app.Activity
import android.app.Application
import android.content.Context
import android.os.Build
import android.os.Bundle
import androidx.preference.PreferenceManager
import com.skoumal.teanity.rxbus.RxBus
import com.topjohnwu.magisk.App
import com.topjohnwu.magisk.utils.RxBus
import org.koin.core.qualifier.named
import org.koin.dsl.module
val SUTimeout = named("su_timeout")
val Protected = named("protected")
val applicationModule = module {
single { RxBus() }
factory { get<Context>().resources }
factory { get<Context>() as App }
factory { get<Context>().packageManager }
factory(Protected) { get<App>().protectedContext }
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 }
}
private fun createDEContext(context: Context): Context {
return if (Build.VERSION.SDK_INT >= 24)
context.createDeviceProtectedStorageContext()
else context
}
class ActivityTracker : Application.ActivityLifecycleCallbacks {
@Volatile
var foreground: Activity? = null
override fun onActivityCreated(activity: Activity, bundle: Bundle?) {}
override fun onActivityStarted(activity: Activity) {}
@Synchronized
override fun onActivityResumed(activity: Activity) {
foreground = activity
}
@Synchronized
override fun onActivityPaused(activity: Activity) {
foreground = null
}
override fun onActivityStopped(activity: Activity) {}
override fun onActivitySaveInstanceState(activity: Activity, bundle: Bundle) {}
override fun onActivityDestroyed(activity: Activity) {}
}
@SuppressLint("Registered")
object NullActivity : Activity()

View File

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

View File

@@ -1,10 +0,0 @@
package com.topjohnwu.magisk.di
import org.koin.dsl.module
val miscModule = module {
// define miscs here
}

View File

@@ -5,6 +5,5 @@ val koinModules = listOf(
networkingModule,
databaseModule,
repositoryModule,
viewModelModules,
miscModule
viewModelModules
)

View File

@@ -1,6 +0,0 @@
package com.topjohnwu.magisk.di
import org.koin.core.qualifier.named
val SUTimeout = named("su_timeout")
val Protected = named("protected")

View File

@@ -1,69 +1,83 @@
package com.topjohnwu.magisk.di
import android.content.Context
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.Moshi
import com.topjohnwu.magisk.Constants
import com.topjohnwu.magisk.data.network.GithubRawApiServices
import com.topjohnwu.magisk.BuildConfig
import com.topjohnwu.magisk.Const
import com.topjohnwu.magisk.data.network.GithubApiServices
import com.topjohnwu.magisk.data.network.GithubRawServices
import com.topjohnwu.magisk.net.Networking
import com.topjohnwu.magisk.net.NoSSLv3SocketFactory
import io.noties.markwon.Markwon
import io.noties.markwon.html.HtmlPlugin
import io.noties.markwon.image.ImagesPlugin
import io.noties.markwon.image.network.OkHttpNetworkSchemeHandler
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import org.koin.dsl.module
import retrofit2.CallAdapter
import retrofit2.Converter
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() }
single { createConverterFactory() }
single { createCallAdapterFactory() }
single { createRetrofit(get(), get(), get()) }
single { createApiService<GithubRawApiServices>(get(), Constants.GITHUB_RAW_API_URL) }
single { createOkHttpClient(get()) }
single { createRetrofit(get()) }
single { createApiService<GithubRawServices>(get(), Const.Url.GITHUB_RAW_URL) }
single { createApiService<GithubApiServices>(get(), Const.Url.GITHUB_API_URL) }
single { createMarkwon(get(), get()) }
}
fun createOkHttpClient(): OkHttpClient {
val httpLoggingInterceptor = HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
@Suppress("DEPRECATION")
fun createOkHttpClient(context: Context): OkHttpClient {
val builder = OkHttpClient.Builder()
if (BuildConfig.DEBUG) {
val httpLoggingInterceptor = HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.HEADERS
}
builder.addInterceptor(httpLoggingInterceptor)
}
return OkHttpClient.Builder()
.addInterceptor(httpLoggingInterceptor)
.build()
if (!Networking.init(context)) {
builder.sslSocketFactory(NoSSLv3SocketFactory())
}
return builder.build()
}
fun createConverterFactory(): Converter.Factory {
fun createMoshiConverterFactory(): MoshiConverterFactory {
val moshi = Moshi.Builder()
.add(JsonAdapterFactory.INSTANCE)
.add(KotshiJsonAdapterFactory)
.build()
return MoshiConverterFactory.create(moshi)
}
fun createCallAdapterFactory(): CallAdapter.Factory {
return RxJava2CallAdapterFactory.create()
}
fun createRetrofit(
okHttpClient: OkHttpClient,
converterFactory: Converter.Factory,
callAdapterFactory: CallAdapter.Factory
): Retrofit.Builder {
fun createRetrofit(okHttpClient: OkHttpClient): Retrofit.Builder {
return Retrofit.Builder()
.addConverterFactory(converterFactory)
.addCallAdapterFactory(callAdapterFactory)
.addConverterFactory(ScalarsConverterFactory.create())
.addConverterFactory(createMoshiConverterFactory())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(okHttpClient)
}
@KotshiJsonAdapterFactory
abstract class JsonAdapterFactory : JsonAdapter.Factory {
companion object {
val INSTANCE: JsonAdapterFactory = KotshiJsonAdapterFactory
}
}
abstract class JsonAdapterFactory : JsonAdapter.Factory
inline fun <reified T> createApiService(retrofitBuilder: Retrofit.Builder, baseUrl: String): T {
return retrofitBuilder
.baseUrl(baseUrl)
.build()
.create(T::class.java)
}
}
fun createMarkwon(context: Context, okHttpClient: OkHttpClient): Markwon {
return Markwon.builder(context)
.usePlugin(HtmlPlugin.create())
.usePlugin(ImagesPlugin.create {
it.addSchemeHandler(OkHttpNetworkSchemeHandler.create(okHttpClient))
})
.build()
}

View File

@@ -1,13 +1,13 @@
package com.topjohnwu.magisk.di
import com.topjohnwu.magisk.data.repository.*
import com.topjohnwu.magisk.data.repository.LogRepository
import com.topjohnwu.magisk.data.repository.MagiskRepository
import com.topjohnwu.magisk.data.repository.StringRepository
import org.koin.dsl.module
val repositoryModule = module {
single { MagiskRepository(get(), get(), get()) }
single { MagiskRepository(get(), get()) }
single { LogRepository(get()) }
single { AppRepository(get()) }
single { SettingRepository(get()) }
single { StringRepository(get()) }
}

View File

@@ -20,6 +20,8 @@ val viewModelModules = module {
viewModel { HideViewModel(get(), get()) }
viewModel { ModuleViewModel(get(), get(), get()) }
viewModel { LogViewModel(get(), get()) }
viewModel { (action: String, uri: Uri?) -> FlashViewModel(action, uri, get()) }
viewModel { (action: String, file: Uri, additional: Uri) ->
FlashViewModel(action, file, additional, get())
}
viewModel { SuRequestViewModel(get(), get(), get(SUTimeout), get()) }
}

View File

@@ -0,0 +1,57 @@
package com.topjohnwu.magisk.extensions
import androidx.databinding.Observable
import androidx.databinding.ObservableBoolean
import androidx.databinding.ObservableField
import androidx.databinding.ObservableInt
fun <T> ObservableField<T>.addOnPropertyChangedCallback(
removeAfterChanged: Boolean = false,
callback: (T?) -> Unit
) {
addOnPropertyChangedCallback(object : Observable.OnPropertyChangedCallback() {
override fun onPropertyChanged(sender: Observable?, propertyId: Int) {
callback(get())
if (removeAfterChanged) removeOnPropertyChangedCallback(this)
}
})
}
fun ObservableInt.addOnPropertyChangedCallback(
removeAfterChanged: Boolean = false,
callback: (Int) -> Unit
) {
addOnPropertyChangedCallback(object : Observable.OnPropertyChangedCallback() {
override fun onPropertyChanged(sender: Observable?, propertyId: Int) {
callback(get())
if (removeAfterChanged) removeOnPropertyChangedCallback(this)
}
})
}
fun ObservableBoolean.addOnPropertyChangedCallback(
removeAfterChanged: Boolean = false,
callback: (Boolean) -> Unit
) {
addOnPropertyChangedCallback(object : Observable.OnPropertyChangedCallback() {
override fun onPropertyChanged(sender: Observable?, propertyId: Int) {
callback(get())
if (removeAfterChanged) removeOnPropertyChangedCallback(this)
}
})
}
inline fun <T> ObservableField<T>.update(block: (T?) -> Unit) {
set(get().apply(block))
}
inline fun <T> ObservableField<T>.updateNonNull(block: (T) -> Unit) {
update {
it ?: return@update
block(it)
}
}
inline fun ObservableInt.update(block: (Int) -> Unit) {
set(get().apply(block))
}

View File

@@ -0,0 +1,9 @@
package com.topjohnwu.magisk.extensions
import android.content.res.Resources
import kotlin.math.ceil
import kotlin.math.roundToInt
fun Int.toDp(): Int = ceil(this / Resources.getSystem().displayMetrics.density).roundToInt()
fun Int.toPx(): Int = (this * Resources.getSystem().displayMetrics.density).roundToInt()

View File

@@ -0,0 +1,202 @@
package com.topjohnwu.magisk.extensions
import androidx.databinding.ObservableField
import com.topjohnwu.magisk.utils.KObservableField
import com.topjohnwu.superuser.internal.UiThreadHandler
import io.reactivex.*
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposables
import io.reactivex.functions.BiFunction
import io.reactivex.schedulers.Schedulers
import androidx.databinding.Observable as BindingObservable
fun <T> Observable<T>.applySchedulers(
subscribeOn: Scheduler = Schedulers.io(),
observeOn: Scheduler = AndroidSchedulers.mainThread()
): Observable<T> = this.subscribeOn(subscribeOn).observeOn(observeOn)
fun <T> Flowable<T>.applySchedulers(
subscribeOn: Scheduler = Schedulers.io(),
observeOn: Scheduler = AndroidSchedulers.mainThread()
): Flowable<T> = this.subscribeOn(subscribeOn).observeOn(observeOn)
fun <T> Single<T>.applySchedulers(
subscribeOn: Scheduler = Schedulers.io(),
observeOn: Scheduler = AndroidSchedulers.mainThread()
): Single<T> = this.subscribeOn(subscribeOn).observeOn(observeOn)
fun <T> Maybe<T>.applySchedulers(
subscribeOn: Scheduler = Schedulers.io(),
observeOn: Scheduler = AndroidSchedulers.mainThread()
): Maybe<T> = this.subscribeOn(subscribeOn).observeOn(observeOn)
fun Completable.applySchedulers(
subscribeOn: Scheduler = Schedulers.io(),
observeOn: Scheduler = AndroidSchedulers.mainThread()
): Completable = this.subscribeOn(subscribeOn).observeOn(observeOn)
/*=== ALIASES FOR OBSERVABLES ===*/
typealias OnCompleteListener = () -> Unit
typealias OnSuccessListener<T> = (T) -> Unit
typealias OnErrorListener = (Throwable) -> Unit
/*=== ALIASES FOR OBSERVABLES ===*/
fun <T> Observable<T>.subscribeK(
onError: OnErrorListener = { it.printStackTrace() },
onComplete: OnCompleteListener = {},
onNext: OnSuccessListener<T> = {}
) = applySchedulers()
.subscribe(onNext, onError, onComplete)
fun <T> Single<T>.subscribeK(
onError: OnErrorListener = { it.printStackTrace() },
onSuccess: OnSuccessListener<T> = {}
) = applySchedulers()
.subscribe(onSuccess, onError)
fun <T> Maybe<T>.subscribeK(
onError: OnErrorListener = { it.printStackTrace() },
onComplete: OnCompleteListener = {},
onSuccess: OnSuccessListener<T> = {}
) = applySchedulers()
.subscribe(onSuccess, onError, onComplete)
fun <T> Flowable<T>.subscribeK(
onError: OnErrorListener = { it.printStackTrace() },
onComplete: OnCompleteListener = {},
onNext: OnSuccessListener<T> = {}
) = applySchedulers()
.subscribe(onNext, onError, onComplete)
fun Completable.subscribeK(
onError: OnErrorListener = { it.printStackTrace() },
onComplete: OnCompleteListener = {}
) = applySchedulers()
.subscribe(onComplete, onError)
fun <T> Observable<out T>.updateBy(
field: KObservableField<T?>
) = doOnNextUi { field.value = it }
.doOnErrorUi { field.value = null }
fun <T> Single<out T>.updateBy(
field: KObservableField<T?>
) = doOnSuccessUi { field.value = it }
.doOnErrorUi { field.value = null }
fun <T> Maybe<out T>.updateBy(
field: KObservableField<T?>
) = doOnSuccessUi { field.value = it }
.doOnErrorUi { field.value = null }
.doOnComplete { field.value = field.value }
fun <T> Flowable<out T>.updateBy(
field: KObservableField<T?>
) = doOnNextUi { field.value = it }
.doOnErrorUi { field.value = null }
fun Completable.updateBy(
field: KObservableField<Boolean>
) = doOnCompleteUi { field.value = true }
.doOnErrorUi { field.value = false }
fun <T> Observable<T>.doOnSubscribeUi(body: () -> Unit) =
doOnSubscribe { UiThreadHandler.run { body() } }
fun <T> Single<T>.doOnSubscribeUi(body: () -> Unit) =
doOnSubscribe { UiThreadHandler.run { body() } }
fun <T> Maybe<T>.doOnSubscribeUi(body: () -> Unit) =
doOnSubscribe { UiThreadHandler.run { body() } }
fun <T> Flowable<T>.doOnSubscribeUi(body: () -> Unit) =
doOnSubscribe { UiThreadHandler.run { body() } }
fun Completable.doOnSubscribeUi(body: () -> Unit) =
doOnSubscribe { UiThreadHandler.run { body() } }
fun <T> Observable<T>.doOnErrorUi(body: (Throwable) -> Unit) =
doOnError { UiThreadHandler.run { body(it) } }
fun <T> Single<T>.doOnErrorUi(body: (Throwable) -> Unit) =
doOnError { UiThreadHandler.run { body(it) } }
fun <T> Maybe<T>.doOnErrorUi(body: (Throwable) -> Unit) =
doOnError { UiThreadHandler.run { body(it) } }
fun <T> Flowable<T>.doOnErrorUi(body: (Throwable) -> Unit) =
doOnError { UiThreadHandler.run { body(it) } }
fun Completable.doOnErrorUi(body: (Throwable) -> Unit) =
doOnError { UiThreadHandler.run { body(it) } }
fun <T> Observable<T>.doOnNextUi(body: (T) -> Unit) =
doOnNext { UiThreadHandler.run { body(it) } }
fun <T> Flowable<T>.doOnNextUi(body: (T) -> Unit) =
doOnNext { UiThreadHandler.run { body(it) } }
fun <T> Single<T>.doOnSuccessUi(body: (T) -> Unit) =
doOnSuccess { UiThreadHandler.run { body(it) } }
fun <T> Maybe<T>.doOnSuccessUi(body: (T) -> Unit) =
doOnSuccess { UiThreadHandler.run { body(it) } }
fun <T> Maybe<T>.doOnCompleteUi(body: () -> Unit) =
doOnComplete { UiThreadHandler.run { body() } }
fun Completable.doOnCompleteUi(body: () -> Unit) =
doOnComplete { UiThreadHandler.run { body() } }
fun <T, R> Observable<List<T>>.mapList(
transformer: (T) -> R
) = flatMapIterable { it }
.map(transformer)
.toList()
fun <T, R> Single<List<T>>.mapList(
transformer: (T) -> R
) = flattenAsFlowable { it }
.map(transformer)
.toList()
fun <T, R> Maybe<List<T>>.mapList(
transformer: (T) -> R
) = flattenAsFlowable { it }
.map(transformer)
.toList()
fun <T, R> Flowable<List<T>>.mapList(
transformer: (T) -> R
) = flatMapIterable { it }
.map(transformer)
.toList()
fun <T> ObservableField<T>.toObservable(): Observable<T> {
val observableField = this
return Observable.create { emitter ->
observableField.get()?.let { emitter.onNext(it) }
val callback = object : BindingObservable.OnPropertyChangedCallback() {
override fun onPropertyChanged(sender: BindingObservable?, propertyId: Int) {
observableField.get()?.let { emitter.onNext(it) }
}
}
observableField.addOnPropertyChangedCallback(callback)
emitter.setDisposable(Disposables.fromAction {
observableField.removeOnPropertyChangedCallback(callback)
})
}
}
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) })

View File

@@ -0,0 +1,126 @@
package com.topjohnwu.magisk.extensions
import android.content.Context
import android.content.res.ColorStateList
import android.view.View
import android.widget.TextView
import androidx.annotation.ColorInt
import androidx.annotation.ColorRes
import androidx.annotation.StringRes
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.core.view.ViewCompat
import androidx.fragment.app.Fragment
import com.google.android.material.snackbar.Snackbar
fun AppCompatActivity.snackbar(
view: View,
@StringRes messageRes: Int,
length: Int = Snackbar.LENGTH_SHORT,
f: Snackbar.() -> Unit = {}
) {
snackbar(view, getString(messageRes), length, f)
}
fun AppCompatActivity.snackbar(
view: View,
message: String,
length: Int = Snackbar.LENGTH_SHORT,
f: Snackbar.() -> Unit = {}
) = Snackbar.make(view, message, length)
.apply(f)
.show()
fun Fragment.snackbar(
view: View,
@StringRes messageRes: Int,
length: Int = Snackbar.LENGTH_SHORT,
f: Snackbar.() -> Unit = {}
) {
snackbar(view, getString(messageRes), length, f)
}
fun Fragment.snackbar(
view: View,
message: String,
length: Int = Snackbar.LENGTH_SHORT,
f: Snackbar.() -> Unit = {}
) = Snackbar.make(view, message, length)
.apply(f)
.show()
fun Snackbar.action(init: KSnackbar.() -> Unit) = apply {
val config = KSnackbar().apply(init)
setAction(config.title(context), config.onClickListener)
when {
config.hasValidColor -> setActionTextColor(config.color(context) ?: return@apply)
config.hasValidColorStateList -> setActionTextColor(config.colorStateList(context) ?: return@apply)
}
}
class KSnackbar {
var colorRes: Int = -1
var colorStateListRes: Int = -1
var title: CharSequence = ""
var titleRes: Int = -1
internal var onClickListener: (View) -> Unit = {}
internal val hasValidColor get() = colorRes != -1
internal val hasValidColorStateList get() = colorStateListRes != -1
fun onClicked(listener: (View) -> Unit) {
onClickListener = listener
}
internal fun title(context: Context) = if (title.isBlank()) context.getString(titleRes) else title
internal fun colorStateList(context: Context) = context.colorStateListCompat(colorStateListRes)
internal fun color(context: Context) = context.colorCompat(colorRes)
}
@Deprecated("Kotlin DSL version is preferred", ReplaceWith("action {}"))
fun Snackbar.action(
@StringRes actionRes: Int,
@ColorRes colorRes: Int? = null,
listener: (View) -> Unit
) {
view.resources.getString(actionRes)
colorRes?.let { ContextCompat.getColor(view.context, colorRes) }
action {}
}
@Deprecated("Kotlin DSL version is preferred", ReplaceWith("action {}"))
fun Snackbar.action(action: String, @ColorInt color: Int? = null, listener: (View) -> Unit) {
setAction(action, listener)
color?.let { setActionTextColor(color) }
}
fun Snackbar.textColorRes(@ColorRes colorRes: Int) {
textColor(context.colorCompat(colorRes) ?: return)
}
fun Snackbar.textColor(@ColorInt color: Int) {
val tv = view.findViewById<TextView>(com.google.android.material.R.id.snackbar_text)
tv.setTextColor(color)
}
fun Snackbar.backgroundColorRes(@ColorRes colorRes: Int) {
backgroundColor(context.colorCompat(colorRes) ?: return)
}
fun Snackbar.backgroundColor(@ColorInt color: Int) {
ViewCompat.setBackgroundTintList(
view,
ColorStateList.valueOf(color)
)
}
fun Snackbar.alert() {
textColor(0xF44336)
}
fun Snackbar.success() {
textColor(0x4CAF50)
}

View File

@@ -0,0 +1,311 @@
package com.topjohnwu.magisk.extensions
import android.content.ComponentName
import android.content.Context
import android.content.ContextWrapper
import android.content.Intent
import android.content.pm.ApplicationInfo
import android.content.pm.ComponentInfo
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.content.pm.PackageManager.*
import android.content.res.Configuration
import android.content.res.Resources
import android.database.Cursor
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.drawable.AdaptiveIconDrawable
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.LayerDrawable
import android.net.Uri
import android.os.Build
import android.os.Build.VERSION.SDK_INT
import android.provider.OpenableColumns
import android.view.View
import androidx.annotation.ColorRes
import androidx.annotation.DrawableRes
import androidx.appcompat.content.res.AppCompatResources
import androidx.core.content.ContextCompat
import androidx.core.net.toFile
import androidx.core.net.toUri
import com.topjohnwu.magisk.Const
import com.topjohnwu.magisk.FileProvider
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
import java.lang.reflect.Array as JArray
val packageName: String get() = get<Context>().packageName
val PackageInfo.processes
get() = activities?.processNames.orEmpty() +
services?.processNames.orEmpty() +
receivers?.processNames.orEmpty() +
providers?.processNames.orEmpty()
val Array<out ComponentInfo>.processNames get() = mapNotNull { it.processName }
val ApplicationInfo.packageInfo: PackageInfo?
get() {
val pm: PackageManager by inject()
return try {
val request = GET_ACTIVITIES or
GET_SERVICES or
GET_RECEIVERS or
GET_PROVIDERS
pm.getPackageInfo(packageName, request)
} catch (e1: Exception) {
try {
pm.activities(packageName).apply {
services = pm.services(packageName)
receivers = pm.receivers(packageName)
providers = pm.providers(packageName)
}
} catch (e2: Exception) {
null
}
}
}
val Uri.fileName: String
get() {
var name: String? = null
get<Context>().contentResolver.query(this, null, null, null, null)?.use { c ->
val nameIndex = c.getColumnIndex(OpenableColumns.DISPLAY_NAME)
if (nameIndex != -1) {
c.moveToFirst()
name = c.getString(nameIndex)
}
}
if (name == null && path != null) {
val idx = path!!.lastIndexOf('/')
name = path!!.substring(idx + 1)
}
return name.orEmpty()
}
fun PackageManager.activities(packageName: String) =
getPackageInfo(packageName, GET_ACTIVITIES)
fun PackageManager.services(packageName: String) =
getPackageInfo(packageName, GET_SERVICES).services
fun PackageManager.receivers(packageName: String) =
getPackageInfo(packageName, GET_RECEIVERS).receivers
fun PackageManager.providers(packageName: String) =
getPackageInfo(packageName, GET_PROVIDERS).providers
fun Context.rawResource(id: Int) = resources.openRawResource(id)
fun Context.readUri(uri: Uri) =
contentResolver.openInputStream(uri) ?: throw FileNotFoundException()
fun Context.getBitmap(id: Int): Bitmap {
var drawable = AppCompatResources.getDrawable(this, id)!!
if (drawable is BitmapDrawable)
return drawable.bitmap
if (SDK_INT >= 26 && drawable is AdaptiveIconDrawable) {
drawable = LayerDrawable(arrayOf(drawable.background, drawable.foreground))
}
val bitmap = Bitmap.createBitmap(
drawable.intrinsicWidth, drawable.intrinsicHeight,
Bitmap.Config.ARGB_8888
)
val canvas = Canvas(bitmap)
drawable.setBounds(0, 0, canvas.width, canvas.height)
drawable.draw(canvas)
return bitmap
}
fun Intent.startActivity(context: Context) = context.startActivity(this)
fun Intent.startActivityWithRoot() {
val args = mutableListOf("am", "start", "--user", Const.USER_ID.toString())
val cmd = toCommand(args).joinToString(" ")
Shell.su(cmd).submit()
}
fun Intent.toCommand(args: MutableList<String> = mutableListOf()): MutableList<String> {
action?.also {
args.add("-a")
args.add(it)
}
component?.also {
args.add("-n")
args.add(it.flattenToString())
}
data?.also {
args.add("-d")
args.add(it.toString())
}
categories?.also {
for (cat in it) {
args.add("-c")
args.add(cat)
}
}
type?.also {
args.add("-t")
args.add(it)
}
extras?.also {
loop@ for (key in it.keySet()) {
val v = it[key] ?: continue
var value: Any = v
val arg: String
when {
v is String -> arg = "--es"
v is Boolean -> arg = "--ez"
v is Int -> arg = "--ei"
v is Long -> arg = "--el"
v is Float -> arg = "--ef"
v is Uri -> arg = "--eu"
v is ComponentName -> {
arg = "--ecn"
value = v.flattenToString()
}
v is List<*> -> {
if (v.isEmpty())
continue@loop
arg = if (v[0] is Int)
"--eial"
else if (v[0] is Long)
"--elal"
else if (v[0] is Float)
"--efal"
else if (v[0] is String)
"--esal"
else
continue@loop /* Unsupported */
val sb = StringBuilder()
for (o in v) {
sb.append(o.toString().replace(",", "\\,"))
sb.append(',')
}
// Remove trailing comma
sb.deleteCharAt(sb.length - 1)
value = sb
}
v.javaClass.isArray -> {
arg = if (v is IntArray)
"--eia"
else if (v is LongArray)
"--ela"
else if (v is FloatArray)
"--efa"
else if (v is Array<*> && v.isArrayOf<String>())
"--esa"
else
continue@loop /* Unsupported */
val sb = StringBuilder()
val len = JArray.getLength(v)
for (i in 0 until len) {
sb.append(JArray.get(v, i)!!.toString().replace(",", "\\,"))
sb.append(',')
}
// Remove trailing comma
sb.deleteCharAt(sb.length - 1)
value = sb
}
else -> continue@loop
} /* Unsupported */
args.add(arg)
args.add(key)
args.add(value.toString())
}
}
args.add("-f")
args.add(flags.toString())
return args
}
fun File.provide(context: Context = get()): Uri {
return FileProvider.getUriForFile(context, context.packageName + ".provider", this)
}
fun File.mv(destination: File) {
inputStream().writeTo(destination)
deleteRecursively()
}
fun String.toFile() = File(this)
fun Intent.chooser(title: String = "Pick an app") = Intent.createChooser(this, title)
fun Context.cachedFile(name: String) = File(cacheDir, name)
fun <Result> Cursor.toList(transformer: (Cursor) -> Result): List<Result> {
val out = mutableListOf<Result>()
while (moveToNext()) out.add(transformer(this))
return out
}
fun ApplicationInfo.getLabel(pm: PackageManager): String {
runCatching {
if (labelRes > 0) {
val res = pm.getResourcesForApplication(this)
val config = Configuration()
config.setLocale(currentLocale)
res.updateConfiguration(config, res.displayMetrics)
return res.getString(labelRes)
}
}
return loadLabel(pm).toString()
}
fun Intent.exists(packageManager: PackageManager) = resolveActivity(packageManager) != null
fun Context.colorCompat(@ColorRes id: Int) = try {
ContextCompat.getColor(this, id)
} catch (e: Resources.NotFoundException) {
null
}
fun Context.colorStateListCompat(@ColorRes id: Int) = try {
ContextCompat.getColorStateList(this, id)
} catch (e: Resources.NotFoundException) {
null
}
fun Context.drawableCompat(@DrawableRes id: Int) = ContextCompat.getDrawable(this, id)
/**
* Pass [start] and [end] dimensions, function will return left and right
* 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 &&
resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_RTL
) {
return end to start
}
return start to end
}
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)
fun Context.unwrap() : Context {
var context = this
while (true) {
if (context is ContextWrapper)
context = context.baseContext
else
break
}
return context
}
fun Uri.writeTo(file: File) = toFile().copyTo(file)

View File

@@ -0,0 +1,8 @@
package com.topjohnwu.magisk.extensions
import com.topjohnwu.magisk.utils.KObservableField
fun KObservableField<Boolean>.toggle() {
value = !value
}

View File

@@ -0,0 +1,130 @@
package com.topjohnwu.magisk.extensions
import android.os.Build
import java.io.File
import java.io.InputStream
import java.io.OutputStream
import java.lang.reflect.Field
import java.lang.reflect.Method
import java.util.*
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
import kotlin.NoSuchElementException
fun ZipInputStream.forEach(callback: (ZipEntry) -> Unit) {
var entry: ZipEntry? = nextEntry
while (entry != null) {
callback(entry)
entry = nextEntry
}
}
fun InputStream.writeTo(file: File) =
withStreams(this, file.outputStream()) { reader, writer -> reader.copyTo(writer) }
inline fun <In : InputStream, Out : OutputStream> withStreams(
inStream: In,
outStream: Out,
withBoth: (In, Out) -> Unit
) {
inStream.use { reader ->
outStream.use { writer ->
withBoth(reader, writer)
}
}
}
inline fun <T, R> List<T>.firstMap(mapper: (T) -> R?): R {
for (item: T in this) {
return mapper(item) ?: continue
}
throw NoSuchElementException("Collection contains no element matching the predicate.")
}
fun String.langTagToLocale(): Locale {
if (Build.VERSION.SDK_INT >= 21) {
return Locale.forLanguageTag(this)
} else {
val tok = split("-".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
if (tok.isEmpty()) {
return Locale("")
}
val language = when (tok[0]) {
"und" -> "" // Undefined
"fil" -> "tl" // Filipino
else -> tok[0]
}
if (language.length != 2 && language.length != 3)
return Locale("")
if (tok.size == 1)
return Locale(language)
val country = tok[1]
return if (country.length != 2 && country.length != 3) Locale(language)
else Locale(language, country)
}
}
fun Locale.toLangTag(): String {
if (Build.VERSION.SDK_INT >= 21) {
return toLanguageTag()
} else {
var language = language
var country = country
var variant = variant
when {
language.isEmpty() || !language.matches("\\p{Alpha}{2,8}".toRegex()) ->
language = "und" // Follow the Locale#toLanguageTag() implementation
language == "iw" -> language = "he" // correct deprecated "Hebrew"
language == "in" -> language = "id" // correct deprecated "Indonesian"
language == "ji" -> language = "yi" // correct deprecated "Yiddish"
}
// ensure valid country code, if not well formed, it's omitted
// variant subtags that begin with a letter must be at least 5 characters long
// ensure valid country code, if not well formed, it's omitted
if (!country.matches("\\p{Alpha}{2}|\\p{Digit}{3}".toRegex())) {
country = ""
}
// variant subtags that begin with a letter must be at least 5 characters long
if (!variant.matches("\\p{Alnum}{5,8}|\\p{Digit}\\p{Alnum}{3}".toRegex())) {
variant = ""
}
val tag = StringBuilder(language)
if (country.isNotEmpty())
tag.append('-').append(country)
if (variant.isNotEmpty())
tag.append('-').append(variant)
return tag.toString()
}
}
// Reflection hacks
private val loadClass = ClassLoader::class.java.getMethod("loadClass", String::class.java)
private val getDeclaredMethod = Class::class.java.getMethod("getDeclaredMethod",
String::class.java, arrayOf<Class<*>>()::class.java)
private val getDeclaredField = Class::class.java.getMethod("getDeclaredField", String::class.java)
fun ClassLoader.forceLoadClass(name: String) =
runCatching { loadClass.invoke(this, name) }.getOrNull() as Class<*>?
fun Class<*>.forceGetDeclaredMethod(name: String, vararg types: Class<*>) =
(runCatching { getDeclaredMethod.invoke(this, name, types) }.getOrNull() as Method?)?.also {
it.isAccessible = true
}
fun Class<*>.forceGetDeclaredField(name: String) =
(runCatching { getDeclaredField.invoke(this, name) }.getOrNull() as Field?)?.also {
it.isAccessible = true
}
inline fun <reified T> T.forceGetClass(name: String) =
T::class.java.classLoader?.forceLoadClass(name)
fun Class<*>.forceGetField(name: String): Field? =
forceGetDeclaredField(name) ?: superclass?.forceGetField(name)
fun Class<*>.forceGetMethod(name: String, vararg types: Class<*>): Method? =
forceGetDeclaredMethod(name, *types) ?: superclass?.forceGetMethod(name, *types)

View File

@@ -1,4 +1,4 @@
package com.topjohnwu.magisk.utils
package com.topjohnwu.magisk.extensions
import org.koin.core.context.GlobalContext
import org.koin.core.parameter.ParametersDefinition
@@ -6,12 +6,12 @@ import org.koin.core.qualifier.Qualifier
fun getKoin() = GlobalContext.get().koin
inline fun <reified T : Any> inject(
inline fun <reified T> inject(
qualifier: Qualifier? = null,
noinline parameters: ParametersDefinition? = null
) = lazy { get<T>(qualifier, parameters) }
inline fun <reified T : Any> get(
inline fun <reified T> get(
qualifier: Qualifier? = null,
noinline parameters: ParametersDefinition? = null
): T = getKoin().get(qualifier, parameters)

View File

@@ -1,8 +1,8 @@
package com.topjohnwu.magisk.utils
package com.topjohnwu.magisk.extensions
import androidx.collection.SparseArrayCompat
import androidx.databinding.ObservableList
import com.skoumal.teanity.extensions.subscribeK
import com.skoumal.teanity.util.DiffObservableList
import com.topjohnwu.magisk.utils.DiffObservableList
import io.reactivex.disposables.Disposable
fun <T> MutableList<T>.update(newList: List<T>) {
@@ -25,8 +25,8 @@ fun List<String>.toShellCmd(): String {
}
fun <T1, T2> ObservableList<T1>.sendUpdatesTo(
target: DiffObservableList<T2>,
mapper: (List<T1>) -> List<T2>
target: DiffObservableList<T2>,
mapper: (List<T1>) -> List<T2>
) = addOnListChangedCallback(object :
ObservableList.OnListChangedCallback<ObservableList<T1>>() {
override fun onChanged(sender: ObservableList<T1>?) {
@@ -76,4 +76,8 @@ fun <T1> ObservableList<T1>.copyNewInputInto(
val addedValues = sender?.slice(positionStart until positionEnd).orEmpty()
target.addAll(addedValues)
}
})
})
operator fun <E> SparseArrayCompat<E>.set(key: Int, value: E) {
put(key, value)
}

View File

@@ -0,0 +1,14 @@
package com.topjohnwu.magisk.extensions
import com.topjohnwu.magisk.Info
import com.topjohnwu.superuser.Shell
import com.topjohnwu.superuser.io.SuFileInputStream
import com.topjohnwu.superuser.io.SuFileOutputStream
import java.io.File
fun reboot(reason: String = if (Info.recovery) "recovery" else "") {
Shell.su("/system/bin/svc power reboot $reason || /system/bin/reboot $reason").submit()
}
fun File.suOutputStream() = SuFileOutputStream(this)
fun File.suInputStream() = SuFileInputStream(this)

View File

@@ -1,4 +1,4 @@
package com.topjohnwu.magisk.utils
package com.topjohnwu.magisk.extensions
import android.content.res.Resources
@@ -20,4 +20,10 @@ fun Int.res(vararg args: Any): String {
return resources.getString(this, *args)
}
fun String.trimEmptyToNull(): String? = if (isBlank()) null else this
fun String.trimEmptyToNull(): String? = if (isBlank()) null else this
fun String.legalFilename() = replace(" ", "_").replace("'", "").replace("\"", "")
.replace("$", "").replace("`", "").replace("*", "").replace("/", "_")
.replace("#", "").replace("@", "").replace("\\", "_")
fun String.isEmptyInternal() = isNullOrBlank()

View File

@@ -1,5 +1,6 @@
package com.topjohnwu.magisk.utils
package com.topjohnwu.magisk.extensions
import com.topjohnwu.magisk.utils.currentLocale
import java.text.DateFormat
import java.text.ParseException
import java.text.SimpleDateFormat
@@ -13,8 +14,7 @@ fun String.toTime(format: DateFormat) = try {
-1L
}
private val locale get() = LocaleManager.locale
val timeFormatFull by lazy { SimpleDateFormat("yyyy/MM/dd_HH:mm:ss", locale) }
val timeFormatStandard by lazy { SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", locale) }
val timeFormatMedium by lazy { DateFormat.getDateInstance(DateFormat.MEDIUM, LocaleManager.locale) }
val timeFormatTime by lazy { SimpleDateFormat("h:mm a", LocaleManager.locale) }
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) }

View File

@@ -1,4 +1,4 @@
package com.topjohnwu.magisk.utils
package com.topjohnwu.magisk.extensions
import android.view.View
import android.view.ViewTreeObserver

View File

@@ -0,0 +1,37 @@
package com.topjohnwu.magisk.model.binding
import androidx.databinding.ViewDataBinding
import androidx.recyclerview.widget.RecyclerView
import com.topjohnwu.magisk.databinding.ComparableRvItem
import com.topjohnwu.magisk.model.entity.recycler.LenientRvItem
import me.tatarka.bindingcollectionadapter2.BindingRecyclerViewAdapter
class BindingAdapter : BindingRecyclerViewAdapter<ComparableRvItem<*>>() {
private var recyclerView: RecyclerView? = null
override fun onBindBinding(
binding: ViewDataBinding,
variableId: Int,
layoutRes: Int,
position: Int,
item: ComparableRvItem<*>
) {
super.onBindBinding(binding, variableId, layoutRes, position, item)
when (item) {
is LenientRvItem -> {
val recycler = recyclerView ?: return
item.onBindingBound(binding)
item.onBindingBound(binding, recycler)
}
else -> item.onBindingBound(binding)
}
}
override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
super.onAttachedToRecyclerView(recyclerView)
this.recyclerView = recyclerView
}
}

View File

@@ -1,156 +0,0 @@
package com.topjohnwu.magisk.model.download;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.net.Uri;
import android.os.IBinder;
import com.topjohnwu.magisk.App;
import com.topjohnwu.magisk.ClassMap;
import com.topjohnwu.magisk.Const;
import com.topjohnwu.magisk.model.entity.Repo;
import com.topjohnwu.magisk.ui.flash.FlashActivity;
import com.topjohnwu.magisk.view.Notifications;
import com.topjohnwu.magisk.view.ProgressNotification;
import com.topjohnwu.net.Networking;
import com.topjohnwu.superuser.Shell;
import com.topjohnwu.superuser.ShellUtils;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
import androidx.annotation.Nullable;
public class DownloadModuleService extends Service {
private List<ProgressNotification> notifications;
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
notifications = new ArrayList<>();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Shell.EXECUTOR.execute(() -> {
Repo repo = intent.getParcelableExtra("repo");
boolean install = intent.getBooleanExtra("install", false);
dlProcessInstall(repo, install);
});
return START_REDELIVER_INTENT;
}
@Override
public synchronized void onTaskRemoved(Intent rootIntent) {
for (ProgressNotification n : notifications) {
Notifications.mgr.cancel(n.hashCode());
}
notifications.clear();
}
private synchronized void addNotification(ProgressNotification n) {
if (notifications.isEmpty()) {
// Start foreground
startForeground(n.hashCode(), n.getNotification());
}
notifications.add(n);
}
private synchronized void removeNotification(ProgressNotification n) {
notifications.remove(n);
if (notifications.isEmpty()) {
// No more tasks, stop service
stopForeground(true);
stopSelf();
} else {
// Pick another notification as our foreground notification
n = notifications.get(0);
startForeground(n.hashCode(), n.getNotification());
}
}
private void dlProcessInstall(Repo repo, boolean install) {
File output = new File(Const.EXTERNAL_PATH, repo.getDownloadFilename());
ProgressNotification progress = new ProgressNotification(output.getName());
addNotification(progress);
try {
InputStream in = Networking.get(repo.getZipUrl())
.setDownloadProgressListener(progress)
.execForInputStream().getResult();
OutputStream out = new BufferedOutputStream(new FileOutputStream(output));
processZip(in, out);
Intent intent = new Intent(this, ClassMap.get(FlashActivity.class));
intent.setData(Uri.fromFile(output))
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.putExtra(Const.Key.FLASH_ACTION, Const.Value.FLASH_ZIP);
synchronized (getApplication()) {
if (install && App.foreground() != null &&
!(App.foreground() instanceof FlashActivity)) {
/* Only start flashing if there is a foreground activity and the
* user is not also flashing another module at the same time */
App.foreground().startActivity(intent);
} else {
/* Or else we preset a notification notifying that we are done */
PendingIntent pi = PendingIntent.getActivity(this, progress.hashCode(), intent,
PendingIntent.FLAG_UPDATE_CURRENT);
progress.dlDone(pi);
}
}
} catch (Exception e) {
e.printStackTrace();
progress.dlFail();
}
removeNotification(progress);
}
private void processZip(InputStream in, OutputStream out)
throws IOException {
try (ZipInputStream zin = new ZipInputStream(in);
ZipOutputStream zout = new ZipOutputStream(out)) {
// Inject latest module-installer.sh as update-binary
zout.putNextEntry(new ZipEntry("META-INF/"));
zout.putNextEntry(new ZipEntry("META-INF/com/"));
zout.putNextEntry(new ZipEntry("META-INF/com/google/"));
zout.putNextEntry(new ZipEntry("META-INF/com/google/android/"));
zout.putNextEntry(new ZipEntry("META-INF/com/google/android/update-binary"));
try (InputStream update_bin = Networking.get(Const.Url.MODULE_INSTALLER)
.execForInputStream().getResult()) {
ShellUtils.pump(update_bin, zout);
}
zout.putNextEntry(new ZipEntry("META-INF/com/google/android/updater-script"));
zout.write("#MAGISK\n".getBytes("UTF-8"));
int off = -1;
ZipEntry entry;
while ((entry = zin.getNextEntry()) != null) {
if (off < 0)
off = entry.getName().indexOf('/') + 1;
String path = entry.getName().substring(off);
if (path.isEmpty())
continue;
if (path.startsWith("META-INF"))
continue;
zout.putNextEntry(new ZipEntry(path));
if (!entry.isDirectory())
ShellUtils.pump(zin, zout);
}
}
}
}

View File

@@ -0,0 +1,154 @@
package com.topjohnwu.magisk.model.download
import android.annotation.SuppressLint
import android.app.Notification
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.os.Build
import android.webkit.MimeTypeMap
import com.topjohnwu.magisk.R
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.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 org.koin.core.get
import java.io.File
import kotlin.random.Random.Default.nextInt
/* More of a facade for [RemoteFileService], but whatever... */
@SuppressLint("Registered")
open class DownloadService : RemoteFileService() {
private val context get() = this
private val File.type
get() = MimeTypeMap.getSingleton()
.getMimeTypeFromExtension(extension)
?: "resource/folder"
override fun onFinished(subject: DownloadSubject, id: Int) = when (subject) {
is Magisk -> onFinishedInternal(subject, id)
is Module -> onFinishedInternal(subject, id)
is Manager -> onFinishedInternal(subject, id)
}
private fun onFinishedInternal(
subject: Magisk,
id: Int
) = when (val conf = subject.configuration) {
Uninstall -> FlashActivity.uninstall(this, subject.file, id)
is Patch -> FlashActivity.patch(this, subject.file, conf.fileUri, id)
is Flash -> FlashActivity.flash(this, subject.file, conf is Secondary, id)
else -> Unit
}
private fun onFinishedInternal(
subject: Module,
id: Int
) = when (subject.configuration) {
is Flash -> FlashActivity.install(this, subject.file, id)
else -> Unit
}
private fun onFinishedInternal(
subject: Manager,
id: Int
) {
remove(id)
when (subject.configuration) {
is APK.Upgrade -> APKInstall.install(this, subject.file)
is APK.Restore -> Unit
}
}
// ---
override fun Notification.Builder.addActions(subject: DownloadSubject)
= when (subject) {
is Magisk -> addActionsInternal(subject)
is Module -> addActionsInternal(subject)
is Manager -> addActionsInternal(subject)
}
private fun Notification.Builder.addActionsInternal(subject: Magisk)
= when (val conf = subject.configuration) {
Download -> this.apply {
fileIntent(subject.file.parentFile!!)
.takeIf { it.exists(get()) }
?.let { addAction(0, R.string.download_open_parent, it.chooser()) }
fileIntent(subject.file)
.takeIf { it.exists(get()) }
?.let { addAction(0, R.string.download_open_self, it.chooser()) }
}
Uninstall -> setContentIntent(FlashActivity.uninstallIntent(context, subject.file))
is Flash -> setContentIntent(FlashActivity.flashIntent(context, subject.file, conf is Secondary))
is Patch -> setContentIntent(FlashActivity.patchIntent(context, subject.file, conf.fileUri))
else -> this
}
private fun Notification.Builder.addActionsInternal(subject: Module)
= when (subject.configuration) {
Download -> this.apply {
fileIntent(subject.file.parentFile!!)
.takeIf { it.exists(get()) }
?.let { addAction(0, R.string.download_open_parent, it.chooser()) }
fileIntent(subject.file)
.takeIf { it.exists(get()) }
?.let { addAction(0, R.string.download_open_self, it.chooser()) }
}
is Flash -> setContentIntent(FlashActivity.installIntent(context, subject.file))
else -> this
}
private fun Notification.Builder.addActionsInternal(subject: Manager)
= when (subject.configuration) {
APK.Upgrade -> setContentIntent(APKInstall.installIntent(context, subject.file))
else -> this
}
@Suppress("ReplaceSingleLineLet")
private fun Notification.Builder.setContentIntent(intent: Intent) =
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) =
PendingIntent.getActivity(context, nextInt(), intent, PendingIntent.FLAG_ONE_SHOT)
.let { addAction(icon, getString(title), it) }
// ---
private fun fileIntent(file: File): Intent {
return Intent(Intent.ACTION_VIEW)
.setDataAndType(file.provide(this), file.type)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
class Builder {
lateinit var subject: DownloadSubject
}
companion object {
inline operator fun invoke(context: Context, argBuilder: Builder.() -> Unit) {
val app = context.applicationContext
val builder = Builder().apply(argBuilder)
val intent = app.intent<DownloadService>().putExtra(ARG_URL, builder.subject)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
app.startForegroundService(intent)
} else {
app.startService(intent)
}
}
}
}

View File

@@ -0,0 +1,65 @@
package com.topjohnwu.magisk.model.download
import com.topjohnwu.magisk.*
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
private fun RemoteFileService.patch(apk: File, id: Int) {
if (packageName == BuildConfig.APPLICATION_ID)
return
update(id) { notification ->
notification.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())
apk.delete()
patched.renameTo(apk)
}
private fun RemoteFileService.upgrade(apk: File, id: Int) {
if (isRunningAsStub) {
// Move to upgrade location
apk.copyTo(DynAPK.update(this), overwrite = true)
apk.delete()
if (Info.stub!!.version < Info.remote.stub.versionCode) {
// We also want to upgrade stub
service.fetchFile(Info.remote.stub.link).blockingGet().byteStream().use {
it.writeTo(apk)
}
patch(apk, id)
} else {
// Simply relaunch the app
ProcessPhoenix.triggerRebirth(this, intent<ProcessPhoenix>())
}
} else {
patch(apk, id)
}
}
private fun RemoteFileService.restore(apk: File, id: Int) {
update(id) { notification ->
notification.setProgress(0, 0, true)
.setProgress(0, 0, true)
.setContentTitle(getString(R.string.restore_img_msg))
.setContentText("")
}
Config.export()
// Make it world readable
apk.setReadable(true, false)
Shell.su("pm install $apk && pm uninstall $packageName").exec()
}
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

@@ -0,0 +1,44 @@
package com.topjohnwu.magisk.model.download
import com.topjohnwu.magisk.extensions.withStreams
import java.io.File
import java.io.InputStream
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
import java.util.zip.ZipOutputStream
fun InputStream.toModule(file: File, installer: InputStream) {
val input = ZipInputStream(buffered())
val output = ZipOutputStream(file.outputStream().buffered())
withStreams(input, output) { zin, zout ->
zout.putNextEntry(ZipEntry("META-INF/"))
zout.putNextEntry(ZipEntry("META-INF/com/"))
zout.putNextEntry(ZipEntry("META-INF/com/google/"))
zout.putNextEntry(ZipEntry("META-INF/com/google/android/"))
zout.putNextEntry(ZipEntry("META-INF/com/google/android/update-binary"))
installer.copyTo(zout)
zout.putNextEntry(ZipEntry("META-INF/com/google/android/updater-script"))
zout.write("#MAGISK\n".toByteArray(charset("UTF-8")))
var off = -1
var entry: ZipEntry? = zin.nextEntry
while (entry != null) {
if (off < 0) {
off = entry.name.indexOf('/') + 1
}
val path = entry.name.substring(off)
if (path.isNotEmpty() && !path.startsWith("META-INF")) {
zout.putNextEntry(ZipEntry(path))
if (!entry.isDirectory) {
zin.copyTo(zout)
}
}
entry = zin.nextEntry
}
}
}

View File

@@ -0,0 +1,85 @@
package com.topjohnwu.magisk.model.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 org.koin.core.KoinComponent
import java.util.*
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>())
override fun onTaskRemoved(rootIntent: Intent?) {
super.onTaskRemoved(rootIntent)
notifications.forEach { cancel(it.key) }
notifications.clear()
}
// --
fun update(
id: Int,
body: (Notification.Builder) -> Unit = {}
) {
val notification = notifications.getOrPut(id) { defaultNotification }
notify(id, notification.also(body).build())
if (notifications.size == 1) {
updateForeground()
}
}
protected fun finishNotify(
id: Int,
editBody: (Notification.Builder) -> Notification.Builder? = { null }
) : Int {
val currentNotification = remove(id)?.run(editBody)
var newId = -1
currentNotification?.let {
newId = nextInt(Int.MAX_VALUE)
notify(newId, it.build())
}
if (!hasNotifications) {
stopSelf()
}
return newId
}
// ---
private fun notify(id: Int, notification: Notification) {
Notifications.mgr.notify(id, notification)
}
private fun cancel(id: Int) {
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
stopForeground(true)
}
// --
override fun onBind(p0: Intent?): IBinder? = null
}

View File

@@ -0,0 +1,118 @@
package com.topjohnwu.magisk.model.download
import android.app.Activity
import android.app.Notification
import android.content.Intent
import com.topjohnwu.magisk.R
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.superuser.ShellUtils
import io.reactivex.Completable
import okhttp3.ResponseBody
import org.koin.android.ext.android.inject
import timber.log.Timber
import java.io.InputStream
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
}
// ---
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)
}
}) {
val newId = finishNotify(subject)
if (get<Activity>() !is NullActivity) {
onFinished(subject, newId)
}
}
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)) {
"The given file does not match checksum"
}
}
}
private fun download(subject: DownloadSubject) = service.fetchFile(subject.url)
.map { it.toStream(subject.hashCode()) }
.flatMapCompletable { stream ->
when (subject) {
is Module -> service.fetchInstaller()
.doOnSuccess { stream.toModule(subject.file, it.byteStream()) }
.ignoreElement()
else -> Completable.fromAction { stream.writeTo(subject.file) }
}
}.doOnComplete {
if (subject is Manager)
handleAPK(subject)
}
private fun ResponseBody.toStream(id: Int): InputStream {
val maxRaw = contentLength()
val max = maxRaw / 1_000_000f
return ProgressInputStream(byteStream()) {
val progress = it / 1_000_000f
update(id) { notification ->
if (maxRaw > 0) {
notification
.setProgress(maxRaw.toInt(), it.toInt(), false)
.setContentText("%.2f / %.2f MB".format(progress, max))
} else {
notification.setContentText("%.2f MB / ??".format(progress))
}
}
}
}
private fun finishNotify(subject: DownloadSubject) = finishNotify(subject.hashCode()) {
it.addActions(subject)
.setContentText(getString(R.string.download_complete))
.setSmallIcon(android.R.drawable.stat_sys_download_done)
.setProgress(0, 0, false)
.setOngoing(false)
.setAutoCancel(true)
}
// ---
@Throws(Throwable::class)
protected abstract fun onFinished(subject: DownloadSubject, id: Int)
protected abstract fun Notification.Builder.addActions(subject: DownloadSubject)
: Notification.Builder
companion object {
const val ARG_URL = "arg_url"
}
}

View File

@@ -1,155 +0,0 @@
package com.topjohnwu.magisk.model.entity;
import android.content.ContentValues;
import android.database.Cursor;
import android.os.Parcel;
import android.os.Parcelable;
import java.util.List;
import androidx.annotation.NonNull;
public abstract class BaseModule implements Comparable<BaseModule>, Parcelable {
private String mId, mName, mVersion, mAuthor, mDescription;
private int mVersionCode = -1;
protected BaseModule() {
mId = mName = mVersion = mAuthor = mDescription = "";
}
protected BaseModule(Cursor c) {
mId = nonNull(c.getString(c.getColumnIndex("id")));
mName = nonNull(c.getString(c.getColumnIndex("name")));
mVersion = nonNull(c.getString(c.getColumnIndex("version")));
mVersionCode = c.getInt(c.getColumnIndex("versionCode"));
mAuthor = nonNull(c.getString(c.getColumnIndex("author")));
mDescription = nonNull(c.getString(c.getColumnIndex("description")));
}
protected BaseModule(Parcel p) {
mId = p.readString();
mName = p.readString();
mVersion = p.readString();
mAuthor = p.readString();
mDescription = p.readString();
mVersionCode = p.readInt();
}
protected BaseModule(MagiskModule m) {
mId = m.getId();
mName = m.getName();
mVersion = m.getVersion();
mAuthor = m.getAuthor();
mDescription = m.getDescription();
mVersionCode = Integer.parseInt(m.getVersionCode());
}
@Override
public int compareTo(@NonNull BaseModule module) {
return getName().toLowerCase().compareTo(module.getName().toLowerCase());
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(mId);
dest.writeString(mName);
dest.writeString(mVersion);
dest.writeString(mAuthor);
dest.writeString(mDescription);
dest.writeInt(mVersionCode);
}
private String nonNull(String s) {
return s == null ? "" : s;
}
public ContentValues getContentValues() {
ContentValues values = new ContentValues();
values.put("id", mId);
values.put("name", mName);
values.put("version", mVersion);
values.put("versionCode", mVersionCode);
values.put("author", mAuthor);
values.put("description", mDescription);
return values;
}
protected void parseProps(List<String> props) {
parseProps(props.toArray(new String[0]));
}
protected void parseProps(String[] props) throws NumberFormatException {
for (String line : props) {
String[] prop = line.split("=", 2);
if (prop.length != 2)
continue;
String key = prop[0].trim();
String value = prop[1].trim();
if (key.isEmpty() || key.charAt(0) == '#')
continue;
switch (key) {
case "id":
mId = value;
break;
case "name":
mName = value;
break;
case "version":
mVersion = value;
break;
case "versionCode":
mVersionCode = Integer.parseInt(value);
break;
case "author":
mAuthor = value;
break;
case "description":
mDescription = value;
break;
default:
break;
}
}
}
public String getName() {
return mName;
}
public void setName(String name) {
mName = name;
}
public String getVersion() {
return mVersion;
}
public String getAuthor() {
return mAuthor;
}
public String getId() {
return mId;
}
public void setId(String id) {
mId = id;
}
public String getDescription() {
return mDescription;
}
public int getVersionCode() {
return mVersionCode;
}
}

View File

@@ -2,8 +2,8 @@ package com.topjohnwu.magisk.model.entity
import android.content.pm.ApplicationInfo
import android.graphics.drawable.Drawable
import com.topjohnwu.magisk.utils.packageInfo
import com.topjohnwu.magisk.utils.processes
import com.topjohnwu.magisk.extensions.packageInfo
import com.topjohnwu.magisk.extensions.processes
class HideAppInfo(
val info: ApplicationInfo,

View File

@@ -1,11 +0,0 @@
package com.topjohnwu.magisk.model.entity
import se.ansman.kotshi.JsonSerializable
@JsonSerializable
data class MagiskApp(
val version: String,
val versionCode: String,
val link: String,
val note: String
)

View File

@@ -1,10 +0,0 @@
package com.topjohnwu.magisk.model.entity
import se.ansman.kotshi.JsonSerializable
@JsonSerializable
data class MagiskConfig(
val app: MagiskApp,
val uninstaller: MagiskLink,
val magisk: MagiskFlashable
)

View File

@@ -1,13 +0,0 @@
package com.topjohnwu.magisk.model.entity
import com.squareup.moshi.Json
import se.ansman.kotshi.JsonSerializable
@JsonSerializable
data class MagiskFlashable(
val version: String,
val versionCode: String,
val link: String,
val note: String,
@Json(name = "md5") val hash: String
)

View File

@@ -1,8 +0,0 @@
package com.topjohnwu.magisk.model.entity
import se.ansman.kotshi.JsonSerializable
@JsonSerializable
data class MagiskLink(
val link: String
)

View File

@@ -1,10 +1,14 @@
package com.topjohnwu.magisk.model.entity
import androidx.room.Entity
import androidx.room.Ignore
import androidx.room.PrimaryKey
import com.topjohnwu.magisk.extensions.now
import com.topjohnwu.magisk.extensions.timeFormatTime
import com.topjohnwu.magisk.extensions.toTime
import com.topjohnwu.magisk.model.entity.MagiskPolicy.Companion.ALLOW
import com.topjohnwu.magisk.utils.timeFormatTime
import com.topjohnwu.magisk.utils.toTime
import java.util.*
@Entity(tableName = "logs")
data class MagiskLog(
val fromUid: Int,
val toUid: Int,
@@ -13,9 +17,10 @@ data class MagiskLog(
val appName: String,
val command: String,
val action: Boolean,
val date: Date
val time: Long = -1
) {
val timeString = date.time.toTime(timeFormatTime)
@PrimaryKey(autoGenerate = true) var id: Int = 0
@Ignore val timeString = time.toTime(timeFormatTime)
}
data class WrappedMagiskLog(
@@ -23,36 +28,8 @@ data class WrappedMagiskLog(
val items: List<MagiskLog>
)
fun Map<String, String>.toLog(): MagiskLog {
return MagiskLog(
fromUid = get("from_uid")?.toIntOrNull() ?: -1,
toUid = get("to_uid")?.toIntOrNull() ?: -1,
fromPid = get("from_pid")?.toIntOrNull() ?: -1,
packageName = get("package_name").orEmpty(),
appName = get("app_name").orEmpty(),
command = get("command").orEmpty(),
action = get("action")?.toIntOrNull() != 0,
date = get("time")?.toLongOrNull()?.toDate() ?: Date()
)
}
fun Long.toDate() = Date(this)
fun MagiskLog.toMap() = mapOf(
"from_uid" to fromUid,
"to_uid" to toUid,
"from_pid" to fromPid,
"package_name" to packageName,
"app_name" to appName,
"command" to command,
"action" to action,
"time" to date
).mapValues { it.toString() }
fun MagiskPolicy.toLog(
toUid: Int,
fromPid: Int,
command: String,
date: Date
) = MagiskLog(uid, toUid, fromPid, packageName, appName, command, policy == ALLOW, date)
command: String
) = MagiskLog(uid, toUid, fromPid, packageName, appName, command, policy == ALLOW, now)

View File

@@ -1,86 +0,0 @@
package com.topjohnwu.magisk.model.entity
import android.os.Parcelable
import androidx.annotation.AnyThread
import androidx.annotation.NonNull
import androidx.annotation.WorkerThread
import androidx.room.Entity
import androidx.room.PrimaryKey
import com.topjohnwu.magisk.Constants
import com.topjohnwu.magisk.data.database.base.su
import io.reactivex.Single
import kotlinx.android.parcel.Parcelize
import okhttp3.ResponseBody
import java.io.File
interface MagiskModule : Parcelable {
val id: String
val name: String
val author: String
val version: String
val versionCode: String
val description: String
}
@Entity(tableName = "repos")
@Parcelize
data class Repository(
@PrimaryKey @NonNull
override val id: String,
override val name: String,
override val author: String,
override val version: String,
override val versionCode: String,
override val description: String,
val lastUpdate: Long
) : MagiskModule
@Parcelize
data class Module(
override val id: String,
override val name: String,
override val author: String,
override val version: String,
override val versionCode: String,
override val description: String,
val path: String
) : MagiskModule
@AnyThread
fun File.toModule(): Single<Module> {
val path = "${Constants.MAGISK_PATH}/$name"
return "dos2unix < $path/module.prop".su()
.map { it.first().toModule(path) }
}
fun Map<String, String>.toModule(path: String): Module {
return Module(
id = get("id").orEmpty(),
name = get("name").orEmpty(),
author = get("author").orEmpty(),
version = get("version").orEmpty(),
versionCode = get("versionCode").orEmpty(),
description = get("description").orEmpty(),
path = path
)
}
@WorkerThread
fun ResponseBody.toRepository(lastUpdate: Long) = string()
.split(Regex("\\n"))
.map { it.split("=", limit = 2) }
.filter { it.size == 2 }
.map { Pair(it[0], it[1]) }
.toMap()
.toRepository(lastUpdate)
@AnyThread
fun Map<String, String>.toRepository(lastUpdate: Long) = Repository(
id = get("id").orEmpty(),
name = get("name").orEmpty(),
author = get("author").orEmpty(),
version = get("version").orEmpty(),
versionCode = get("versionCode").orEmpty(),
description = get("description").orEmpty(),
lastUpdate = lastUpdate
)

View File

@@ -2,15 +2,16 @@ package com.topjohnwu.magisk.model.entity
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import com.topjohnwu.magisk.extensions.getLabel
import com.topjohnwu.magisk.model.entity.MagiskPolicy.Companion.INTERACTIVE
data class MagiskPolicy(
val uid: Int,
var uid: Int,
val packageName: String,
val appName: String,
val policy: Int = INTERACTIVE,
val until: Long = -1L,
var policy: Int = INTERACTIVE,
var until: Long = -1L,
val logging: Boolean = true,
val notification: Boolean = true,
val applicationInfo: ApplicationInfo
@@ -24,48 +25,20 @@ data class MagiskPolicy(
}
/*@Throws(PackageManager.NameNotFoundException::class)
fun ContentValues.toPolicy(pm: PackageManager): MagiskPolicy {
val uid = getAsInteger("uid")
val packageName = getAsString("package_name")
val info = pm.getApplicationInfo(packageName, 0)
if (info.uid != uid)
throw PackageManager.NameNotFoundException()
return MagiskPolicy(
uid = uid,
packageName = packageName,
policy = getAsInteger("policy"),
until = getAsInteger("until").toLong(),
logging = getAsInteger("logging") != 0,
notification = getAsInteger("notification") != 0,
applicationInfo = info,
appName = info.loadLabel(pm).toString()
)
}
fun MagiskPolicy.toContentValues() = ContentValues().apply {
put("uid", uid)
put("uid", uid)
put("package_name", packageName)
put("policy", policy)
put("until", until)
put("logging", if (logging) 1 else 0)
put("notification", if (notification) 1 else 0)
}*/
fun MagiskPolicy.toMap() = mapOf(
"uid" to uid,
"package_name" to packageName,
"policy" to policy,
"until" to until,
"logging" to if (logging) 1 else 0,
"notification" to if (notification) 1 else 0
).mapValues { it.value.toString() }
"logging" to logging,
"notification" to notification
)
@Throws(PackageManager.NameNotFoundException::class)
fun Map<String, String>.toPolicy(pm: PackageManager): MagiskPolicy {
val uid = get("uid")?.toIntOrNull() ?: -1
val packageName = get("package_name").orEmpty()
val info = pm.getApplicationInfo(packageName, 0)
val info = pm.getApplicationInfo(packageName, PackageManager.GET_UNINSTALLED_PACKAGES)
if (info.uid != uid)
throw PackageManager.NameNotFoundException()
@@ -78,19 +51,20 @@ fun Map<String, String>.toPolicy(pm: PackageManager): MagiskPolicy {
logging = get("logging")?.toIntOrNull() != 0,
notification = get("notification")?.toIntOrNull() != 0,
applicationInfo = info,
appName = info.loadLabel(pm).toString()
appName = info.getLabel(pm)
)
}
@Throws(PackageManager.NameNotFoundException::class)
fun Int.toPolicy(pm: PackageManager): MagiskPolicy {
fun Int.toPolicy(pm: PackageManager, policy: Int = INTERACTIVE): MagiskPolicy {
val pkg = pm.getPackagesForUid(this)?.firstOrNull()
?: throw PackageManager.NameNotFoundException()
val info = pm.getApplicationInfo(pkg, 0)
val info = pm.getApplicationInfo(pkg, PackageManager.GET_UNINSTALLED_PACKAGES)
return MagiskPolicy(
uid = this,
uid = info.uid,
packageName = pkg,
policy = policy,
applicationInfo = info,
appName = info.loadLabel(pm).toString()
appName = info.getLabel(pm)
)
}
}

View File

@@ -1,83 +0,0 @@
package com.topjohnwu.magisk.model.entity;
import android.os.Parcel;
import android.os.Parcelable;
import com.topjohnwu.superuser.Shell;
import com.topjohnwu.superuser.io.SuFile;
public class OldModule extends BaseModule {
public static final Parcelable.Creator<OldModule> CREATOR = new Creator<OldModule>() {
/* It won't be used at any place */
@Override
public OldModule createFromParcel(Parcel source) {
return null;
}
@Override
public OldModule[] newArray(int size) {
return null;
}
};
private final SuFile mRemoveFile;
private final SuFile mDisableFile;
private final SuFile mUpdateFile;
private final boolean mUpdated;
private boolean mEnable;
private boolean mRemove;
public OldModule(String path) {
try {
parseProps(Shell.su("dos2unix < " + path + "/module.prop").exec().getOut());
} catch (NumberFormatException ignored) {
}
mRemoveFile = new SuFile(path, "remove");
mDisableFile = new SuFile(path, "disable");
mUpdateFile = new SuFile(path, "update");
if (getId().isEmpty()) {
int sep = path.lastIndexOf('/');
setId(path.substring(sep + 1));
}
if (getName().isEmpty()) {
setName(getId());
}
mEnable = !mDisableFile.exists();
mRemove = mRemoveFile.exists();
mUpdated = mUpdateFile.exists();
}
public void createDisableFile() {
mEnable = !mDisableFile.createNewFile();
}
public void removeDisableFile() {
mEnable = mDisableFile.delete();
}
public boolean isEnabled() {
return mEnable;
}
public void createRemoveFile() {
mRemove = mRemoveFile.createNewFile();
}
public void deleteRemoveFile() {
mRemove = !mRemoveFile.delete();
}
public boolean willBeRemoved() {
return mRemove;
}
public boolean isUpdated() {
return mUpdated;
}
}

View File

@@ -1,61 +0,0 @@
package com.topjohnwu.magisk.model.entity;
import android.content.ContentValues;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import com.topjohnwu.magisk.utils.Utils;
import androidx.annotation.NonNull;
public class Policy implements Comparable<Policy>{
public static final int INTERACTIVE = 0;
public static final int DENY = 1;
public static final int ALLOW = 2;
public int uid, policy = INTERACTIVE;
public long until;
public boolean logging = true, notification = true;
public String packageName, appName;
public ApplicationInfo info;
public Policy(int uid, PackageManager pm) throws PackageManager.NameNotFoundException {
String[] pkgs = pm.getPackagesForUid(uid);
if (pkgs == null || pkgs.length == 0)
throw new PackageManager.NameNotFoundException();
this.uid = uid;
packageName = pkgs[0];
info = pm.getApplicationInfo(packageName, 0);
appName = Utils.getAppLabel(info, pm);
}
public Policy(ContentValues values, PackageManager pm) throws PackageManager.NameNotFoundException {
uid = values.getAsInteger("uid");
packageName = values.getAsString("package_name");
policy = values.getAsInteger("policy");
until = values.getAsInteger("until");
logging = values.getAsInteger("logging") != 0;
notification = values.getAsInteger("notification") != 0;
info = pm.getApplicationInfo(packageName, 0);
if (info.uid != uid)
throw new PackageManager.NameNotFoundException();
appName = info.loadLabel(pm).toString();
}
public ContentValues getContentValues() {
ContentValues values = new ContentValues();
values.put("uid", uid);
values.put("package_name", packageName);
values.put("policy", policy);
values.put("until", until);
values.put("logging", logging ? 1 : 0);
values.put("notification", notification ? 1 : 0);
return values;
}
@Override
public int compareTo(@NonNull Policy policy) {
return appName.toLowerCase().compareTo(policy.appName.toLowerCase());
}
}

View File

@@ -1,114 +0,0 @@
package com.topjohnwu.magisk.model.entity;
import android.content.ContentValues;
import android.database.Cursor;
import android.os.Parcel;
import android.os.Parcelable;
import com.topjohnwu.magisk.Const;
import com.topjohnwu.magisk.utils.Utils;
import java.text.DateFormat;
import java.util.Date;
public class Repo extends BaseModule {
private Date mLastUpdate;
public Repo(String id) {
setId(id);
}
public Repo(Cursor c) {
super(c);
mLastUpdate = new Date(c.getLong(c.getColumnIndex("last_update")));
}
public Repo(Parcel p) {
super(p);
mLastUpdate = new Date(p.readLong());
}
public Repo(Repository repo) {
super(repo);
mLastUpdate = new Date(repo.getLastUpdate());
}
public static final Parcelable.Creator<Repo> CREATOR = new Parcelable.Creator<Repo>() {
@Override
public Repo createFromParcel(Parcel source) {
return new Repo(source);
}
@Override
public Repo[] newArray(int size) {
return new Repo[size];
}
};
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeLong(mLastUpdate.getTime());
}
public void update() throws IllegalRepoException {
String[] props = Utils.dlString(getPropUrl()).split("\\n");
try {
parseProps(props);
} catch (NumberFormatException e) {
throw new IllegalRepoException("Repo [" + getId() + "] parse error: " + e.getMessage());
}
if (getVersionCode() < 0) {
throw new IllegalRepoException("Repo [" + getId() + "] does not contain versionCode");
}
}
public void update(Date lastUpdate) throws IllegalRepoException {
mLastUpdate = lastUpdate;
update();
}
@Override
public ContentValues getContentValues() {
ContentValues values = super.getContentValues();
values.put("last_update", mLastUpdate.getTime());
return values;
}
public String getZipUrl() {
return String.format(Const.Url.ZIP_URL, getId());
}
public String getPropUrl() {
return getFileUrl("module.prop");
}
public String getDetailUrl() {
return getFileUrl("README.md");
}
public String getFileUrl(String file) {
return String.format(Const.Url.FILE_URL, getId(), file);
}
public String getLastUpdateString() {
return DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM).format(mLastUpdate);
}
public Date getLastUpdate() {
return mLastUpdate;
}
public String getDownloadFilename() {
return Utils.getLegalFilename(getName() + "-" + getVersion() + ".zip");
}
public class IllegalRepoException extends Exception {
IllegalRepoException(String message) {
super(message);
}
}
}

View File

@@ -1,56 +0,0 @@
package com.topjohnwu.magisk.model.entity;
import android.content.ContentValues;
import com.topjohnwu.magisk.utils.LocaleManager;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
public class SuLogEntry {
public int fromUid, toUid, fromPid;
public String packageName, appName, command;
public boolean action;
public Date date;
public SuLogEntry(MagiskPolicy policy) {
fromUid = policy.getUid();
packageName = policy.getPackageName();
appName = policy.getAppName();
action = policy.getPolicy() == Policy.ALLOW;
}
public SuLogEntry(ContentValues values) {
fromUid = values.getAsInteger("from_uid");
packageName = values.getAsString("package_name");
appName = values.getAsString("app_name");
fromPid = values.getAsInteger("from_pid");
command = values.getAsString("command");
toUid = values.getAsInteger("to_uid");
action = values.getAsInteger("action") != 0;
date = new Date(values.getAsLong("time"));
}
public ContentValues getContentValues() {
ContentValues values = new ContentValues();
values.put("from_uid", fromUid);
values.put("package_name", packageName);
values.put("app_name", appName);
values.put("from_pid", fromPid);
values.put("command", command);
values.put("to_uid", toUid);
values.put("action", action ? 1 : 0);
values.put("time", date.getTime());
return values;
}
public String getDateString() {
return DateFormat.getDateInstance(DateFormat.MEDIUM, LocaleManager.getLocale()).format(date);
}
public String getTimeString() {
return new SimpleDateFormat("h:mm a", LocaleManager.getLocale()).format(date);
}
}

View File

@@ -0,0 +1,42 @@
package com.topjohnwu.magisk.model.entity
import android.os.Parcelable
import kotlinx.android.parcel.Parcelize
import se.ansman.kotshi.JsonSerializable
@JsonSerializable
data class UpdateInfo(
val app: ManagerJson = ManagerJson(),
val uninstaller: UninstallerJson = UninstallerJson(),
val magisk: MagiskJson = MagiskJson(),
val stub: StubJson = StubJson()
)
@JsonSerializable
data class UninstallerJson(
val link: String = ""
)
@JsonSerializable
data class MagiskJson(
val version: String = "",
val versionCode: Int = -1,
val link: String = "",
val note: String = "",
val md5: String = ""
)
@Parcelize
@JsonSerializable
data class ManagerJson(
val version: String = "",
val versionCode: Int = -1,
val link: String = "",
val note: String = ""
) : Parcelable
@JsonSerializable
data class StubJson(
val versionCode: Int = -1,
val link: String = ""
)

View File

@@ -1,3 +0,0 @@
package com.topjohnwu.magisk.model.entity
data class Version(val version: String, val versionCode: Int)

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