Compare commits

..

297 Commits
v25.2 ... v26.0

Author SHA1 Message Date
topjohnwu
7cb0909c70 Release Magisk v26.0 2023-04-05 11:02:17 -07:00
topjohnwu
cc5ff36165 Revert "Cancel recursive bind"
This reverts commit a18a440236.
2023-04-05 10:47:13 -07:00
topjohnwu
18b1ef6c29 Only restore JNIEnv in constructor 2023-04-05 10:13:50 -07:00
LoveSy
7fe012347a Restore JNIEnv in advance for clean env to modules 2023-04-05 09:29:59 -07:00
vvb2060
5c165c9bb0 Fix avd hack 2023-04-05 04:01:32 -07:00
topjohnwu
6c3519923d Make things more obvious 2023-04-05 04:01:07 -07:00
topjohnwu
9ea859810d Update api.hpp copyright notice 2023-04-05 01:54:56 -07:00
LoveSy
8dae7b5451 Update installation guide 2023-04-05 01:50:45 -07:00
vvb2060
f827755aaf Skip getSessionInfo 2023-04-05 01:46:33 -07:00
topjohnwu
637a8af234 Add v26.0 release notes 2023-04-05 01:42:42 -07:00
LoveSy
b0fc580860 Avoid crash when calling abandonSession 2023-04-05 00:36:51 -07:00
vvb2060
9279f30e89 Upload mapping and native debug symbols 2023-04-05 00:14:51 -07:00
LoveSy
b505819ca2 Fix a typo 2023-04-04 12:28:08 -07:00
topjohnwu
39d1d23909 Release new canary build 2023-04-04 03:00:50 -07:00
vvb2060
69529ac59c Fix restorecon 2023-04-04 02:34:16 -07:00
vvb2060
a18a440236 Cancel recursive bind 2023-04-04 02:12:07 -07:00
LoveSy
aa7846c1c0 No need to mount ROOTMIR if tmp_dir != /sbin 2023-04-04 02:12:07 -07:00
topjohnwu
24ba4ab95b Better AVD support 2023-04-04 02:04:49 -07:00
topjohnwu
762b70ba9d Better string split implementation 2023-04-03 18:50:36 -07:00
topjohnwu
41b77e4f25 Make base as template argument for optimization 2023-04-03 18:32:11 -07:00
topjohnwu
2087e47300 Get random separately 2023-04-03 18:32:11 -07:00
vvb2060
46ce765860 Use stable random seed 2023-04-03 18:32:11 -07:00
topjohnwu
5117dc1a31 Reorganize code 2023-04-03 17:47:07 -07:00
Arbri çoçka
620fd7d124 Update sq strings.xml 2023-04-03 16:54:11 -07:00
kubalav
3e991dc003 Update Slovak translation 2023-04-03 16:53:59 -07:00
LoveSy
15cab86152 Make module mirror read only 2023-04-02 22:03:02 -07:00
LoveSy
aa785b5845 Show confirm dialog when installing local module
It can avoid miss click

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

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

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

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

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

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

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

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

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

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

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

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

19
.github/ccache.sh vendored
View File

@@ -1,19 +0,0 @@
OS=$(uname)
CCACHE_VER=4.4
case $OS in
Darwin )
brew install ccache
ln -s $(which ccache) ./ccache
;;
Linux )
sudo apt-get install -y ccache
ln -s $(which ccache) ./ccache
;;
* )
curl -OL https://github.com/ccache/ccache/releases/download/v${CCACHE_VER}/ccache-${CCACHE_VER}-windows-64.zip
unzip -j ccache-*-windows-64.zip '*/ccache.exe'
;;
esac
mkdir ./.ccache
./ccache -o compiler_check='%compiler% -dumpmachine; %compiler% -dumpversion'

View File

@@ -24,31 +24,44 @@ jobs:
matrix: matrix:
os: [ ubuntu-latest, windows-latest, macos-latest ] os: [ ubuntu-latest, windows-latest, macos-latest ]
env: env:
NDK_CCACHE: ${{ github.workspace }}/ccache NDK_CCACHE: ccache
CCACHE_DIR: ${{ github.workspace }}/.ccache CCACHE_DIR: ${{ github.workspace }}/.ccache
CCACHE_COMPILERCHECK: "%compiler% -dumpmachine; %compiler% -dumpversion"
RUSTC_WRAPPER: sccache
steps: steps:
- name: Check out - name: Check out
uses: actions/checkout@v2 uses: actions/checkout@v3
with: with:
submodules: 'recursive' submodules: 'recursive'
fetch-depth: 0 fetch-depth: 0
- name: Set up JDK 11 - name: Set up JDK 17
uses: actions/setup-java@v1 uses: actions/setup-java@v3
with: with:
java-version: '11' distribution: 'temurin'
java-version: '17'
- name: Set up Python 3 - name: Set up Python 3
uses: actions/setup-python@v2 uses: actions/setup-python@v4
with: with:
python-version: '3.x' python-version: '3.x'
- name: Set up ccache - name: Set up ccache
run: bash .github/ccache.sh uses: hendrikmuhs/ccache-action@v1.2
with:
key: ${{ runner.os }}-${{ github.sha }}
restore-keys: ${{ runner.os }}
- name: Set up sccache
uses: hendrikmuhs/ccache-action@v1.2
with:
variant: sccache
key: ${{ runner.os }}-${{ github.sha }}
restore-keys: ${{ runner.os }}
- name: Cache Gradle dependencies - name: Cache Gradle dependencies
uses: actions/cache@v2 uses: actions/cache@v3
with: with:
path: | path: |
~/.gradle/caches ~/.gradle/caches
@@ -58,10 +71,9 @@ jobs:
restore-keys: ${{ runner.os }}-gradle- restore-keys: ${{ runner.os }}-gradle-
- name: Cache build cache - name: Cache build cache
uses: actions/cache@v2 uses: actions/cache@v3
with: with:
path: | path: |
${{ github.workspace }}/.ccache
~/.gradle/caches/build-cache-* ~/.gradle/caches/build-cache-*
key: ${{ runner.os }}-build-cache-${{ github.sha }} key: ${{ runner.os }}-build-cache-${{ github.sha }}
restore-keys: ${{ runner.os }}-build-cache- restore-keys: ${{ runner.os }}-build-cache-
@@ -71,13 +83,11 @@ jobs:
- name: Build release - name: Build release
run: | run: |
./ccache -zp
python build.py -vr all python build.py -vr all
- name: Build debug - name: Build debug
run: | run: |
python build.py -v all python build.py -v all
./ccache -s
- name: Stop gradle daemon - name: Stop gradle daemon
run: ./gradlew --stop run: ./gradlew --stop
@@ -85,7 +95,14 @@ jobs:
# Only upload artifacts built on Linux # Only upload artifacts built on Linux
- name: Upload build artifact - name: Upload build artifact
if: runner.os == 'Linux' if: runner.os == 'Linux'
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v3
with: with:
name: ${{ github.sha }} name: ${{ github.sha }}
path: out path: out
- name: Upload mapping and native debug symbols
if: runner.os == 'Linux'
uses: actions/upload-artifact@v3
with:
name: ${{ github.sha }}-symbols
path: app/build/outputs

39
.gitmodules vendored
View File

@@ -1,45 +1,48 @@
[submodule "selinux"] [submodule "selinux"]
path = native/jni/external/selinux path = native/src/external/selinux
url = https://github.com/topjohnwu/selinux.git url = https://github.com/topjohnwu/selinux.git
[submodule "busybox"] [submodule "busybox"]
path = native/jni/external/busybox path = native/src/external/busybox
url = https://github.com/topjohnwu/ndk-busybox.git url = https://github.com/topjohnwu/ndk-busybox.git
[submodule "dtc"] [submodule "dtc"]
path = native/jni/external/dtc path = native/src/external/dtc
url = https://github.com/dgibson/dtc.git url = https://github.com/dgibson/dtc.git
[submodule "lz4"] [submodule "lz4"]
path = native/jni/external/lz4 path = native/src/external/lz4
url = https://github.com/lz4/lz4.git url = https://github.com/lz4/lz4.git
[submodule "bzip2"] [submodule "bzip2"]
path = native/jni/external/bzip2 path = native/src/external/bzip2
url = https://github.com/nemequ/bzip2.git url = https://github.com/nemequ/bzip2.git
[submodule "xz"] [submodule "xz"]
path = native/jni/external/xz path = native/src/external/xz
url = https://github.com/xz-mirror/xz.git url = https://github.com/xz-mirror/xz.git
[submodule "nanopb"] [submodule "nanopb"]
path = native/jni/external/nanopb path = native/src/external/nanopb
url = https://github.com/nanopb/nanopb.git url = https://github.com/nanopb/nanopb.git
[submodule "mincrypt"] [submodule "mincrypt"]
path = native/jni/external/mincrypt path = native/src/external/mincrypt
url = https://github.com/topjohnwu/mincrypt.git url = https://github.com/topjohnwu/mincrypt.git
[submodule "pcre"] [submodule "pcre"]
path = native/jni/external/pcre path = native/src/external/pcre
url = https://android.googlesource.com/platform/external/pcre url = https://android.googlesource.com/platform/external/pcre
[submodule "libcxx"] [submodule "libcxx"]
path = native/jni/external/libcxx path = native/src/external/libcxx
url = https://github.com/topjohnwu/libcxx.git url = https://github.com/topjohnwu/libcxx.git
[submodule "zlib"] [submodule "zlib"]
path = native/jni/external/zlib path = native/src/external/zlib
url = https://android.googlesource.com/platform/external/zlib url = https://android.googlesource.com/platform/external/zlib
[submodule "parallel-hashmap"] [submodule "parallel-hashmap"]
path = native/jni/external/parallel-hashmap path = native/src/external/parallel-hashmap
url = https://github.com/greg7mdp/parallel-hashmap.git url = https://github.com/greg7mdp/parallel-hashmap.git
[submodule "zopfli"]
path = native/src/external/zopfli
url = https://github.com/google/zopfli.git
[submodule "cxx-rs"]
path = native/src/external/cxx-rs
url = https://github.com/topjohnwu/cxx.git
[submodule "lsplt"]
path = native/src/external/lsplt
url = https://github.com/LSPosed/LSPlt.git
[submodule "termux-elf-cleaner"] [submodule "termux-elf-cleaner"]
path = tools/termux-elf-cleaner path = tools/termux-elf-cleaner
url = https://github.com/termux/termux-elf-cleaner.git url = https://github.com/termux/termux-elf-cleaner.git
[submodule "zopfli"]
path = native/jni/external/zopfli
url = https://github.com/google/zopfli.git
[submodule "cxx-rs"]
path = native/jni/external/cxx-rs
url = https://github.com/topjohnwu/cxx.git

View File

@@ -6,7 +6,7 @@
## Introduction ## Introduction
Magisk is a suite of open source software for customizing Android, supporting devices higher than Android 5.0.<br> Magisk is a suite of open source software for customizing Android, supporting devices higher than Android 6.0.<br>
Some highlight features: Some highlight features:
- **MagiskSU**: Provide root access for applications - **MagiskSU**: Provide root access for applications
@@ -18,8 +18,8 @@ Some highlight features:
[Github](https://github.com/topjohnwu/Magisk/) is the only source where you can get official Magisk information and downloads. [Github](https://github.com/topjohnwu/Magisk/) is the only source where you can get official Magisk information and downloads.
[![](https://img.shields.io/badge/Magisk-v25.1-blue)](https://github.com/topjohnwu/Magisk/releases/tag/v25.1) [![](https://img.shields.io/badge/Magisk-v25.2-blue)](https://github.com/topjohnwu/Magisk/releases/tag/v25.2)
[![](https://img.shields.io/badge/Magisk%20Beta-v25.1-blue)](https://github.com/topjohnwu/Magisk/releases/tag/v25.1) [![](https://img.shields.io/badge/Magisk%20Beta-v25.2-blue)](https://github.com/topjohnwu/Magisk/releases/tag/v25.2)
[![](https://img.shields.io/badge/Magisk-Canary-red)](https://raw.githubusercontent.com/topjohnwu/magisk-files/canary/app-release.apk) [![](https://img.shields.io/badge/Magisk-Canary-red)](https://raw.githubusercontent.com/topjohnwu/magisk-files/canary/app-release.apk)
[![](https://img.shields.io/badge/Magisk-Debug-red)](https://raw.githubusercontent.com/topjohnwu/magisk-files/canary/app-debug.apk) [![](https://img.shields.io/badge/Magisk-Debug-red)](https://raw.githubusercontent.com/topjohnwu/magisk-files/canary/app-debug.apk)
@@ -27,7 +27,6 @@ Some highlight features:
- [Installation Instruction](https://topjohnwu.github.io/Magisk/install.html) - [Installation Instruction](https://topjohnwu.github.io/Magisk/install.html)
- [Magisk Documentation](https://topjohnwu.github.io/Magisk/) - [Magisk Documentation](https://topjohnwu.github.io/Magisk/)
- [Magisk Troubleshoot Wiki](https://www.didgeridoohan.com/magisk/HomePage) (by [@Didgeridoohan](https://github.com/Didgeridoohan))
## Bug Reports ## Bug Reports
@@ -41,7 +40,7 @@ For Magisk app crashes, record and upload the logcat when the crash occurs.
- Magisk builds on any OS Android Studio supports. Install Android Studio and do the initial setups. - Magisk builds on any OS Android Studio supports. Install Android Studio and do the initial setups.
- Clone sources: `git clone --recurse-submodules https://github.com/topjohnwu/Magisk.git` - Clone sources: `git clone --recurse-submodules https://github.com/topjohnwu/Magisk.git`
- Install Python 3.6+ \ - Install Python 3.8+ \
(Windows only: select **'Add Python to PATH'** in installer, and run `pip install colorama` after install) (Windows only: select **'Add Python to PATH'** in installer, and run `pip install colorama` after install)
- Configure to use the JDK bundled in Android Studio: - Configure to use the JDK bundled in Android Studio:
- macOS: `export JAVA_HOME="/Applications/Android Studio.app/Contents/jre/Contents/Home"` - macOS: `export JAVA_HOME="/Applications/Android Studio.app/Contents/jre/Contents/Home"`

7
app/.gitignore vendored
View File

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

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,5 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
package="com.topjohnwu.shared"
android:installLocation="internalOnly"> android:installLocation="internalOnly">
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
@@ -9,6 +8,10 @@
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" /> <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.HIDE_OVERLAY_WINDOWS" /> <uses-permission android:name="android.permission.HIDE_OVERLAY_WINDOWS" />
<uses-permission android:name="android.permission.UPDATE_PACKAGES_WITHOUT_USER_ACTION" /> <uses-permission android:name="android.permission.UPDATE_PACKAGES_WITHOUT_USER_ACTION" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission
android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="29" />
<uses-permission <uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="29" android:maxSdkVersion="29"

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,12 +2,21 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"> xmlns:tools="http://schemas.android.com/tools">
<permission
android:name="${applicationId}.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION"
android:protectionLevel="signature"
tools:node="remove" />
<uses-permission
android:name="${applicationId}.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION"
tools:node="remove" />
<application <application
android:name=".core.App" android:name=".core.App"
android:extractNativeLibs="true"
android:icon="@drawable/ic_launcher" android:icon="@drawable/ic_launcher"
android:multiArch="true" android:multiArch="true"
tools:ignore="UnusedAttribute,GoogleAppIndexingWarning"> tools:ignore="UnusedAttribute,GoogleAppIndexingWarning"
tools:remove="android:appComponentFactory">
<activity <activity
android:name=".ui.MainActivity" android:name=".ui.MainActivity"
@@ -53,7 +62,8 @@
<service <service
android:name=".core.download.DownloadService" android:name=".core.download.DownloadService"
android:exported="false" /> android:exported="false"
android:foregroundServiceType="dataSync" />
<service <service
android:name=".core.JobService" android:name=".core.JobService"

View File

@@ -0,0 +1,12 @@
package androidx.lifecycle;
import android.content.Context;
import androidx.annotation.NonNull;
public class ProcessLifecycleAccessor {
public static void init(@NonNull Context context) {
LifecycleDispatcher.init(context);
ProcessLifecycleOwner.init(context);
}
}

View File

@@ -5,6 +5,7 @@ import android.view.KeyEvent
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.view.MenuProvider
import androidx.databinding.DataBindingUtil import androidx.databinding.DataBindingUtil
import androidx.databinding.OnRebindCallback import androidx.databinding.OnRebindCallback
import androidx.databinding.ViewDataBinding import androidx.databinding.ViewDataBinding
@@ -66,6 +67,8 @@ abstract class BaseFragment<Binding : ViewDataBinding> : Fragment(), ViewModelHo
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
if (this is MenuProvider)
activity?.addMenuProvider(this, viewLifecycleOwner)
binding.addOnRebindCallback(object : OnRebindCallback<Binding>() { binding.addOnRebindCallback(object : OnRebindCallback<Binding>() {
override fun onPreBind(binding: Binding): Boolean { override fun onPreBind(binding: Binding): Boolean {
this@BaseFragment.onPreBind(binding) this@BaseFragment.onPreBind(binding)

View File

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

View File

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

View File

@@ -14,7 +14,6 @@ import com.google.android.material.snackbar.Snackbar
import com.topjohnwu.magisk.BR import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.core.Config import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.core.base.BaseActivity import com.topjohnwu.magisk.core.base.BaseActivity
import com.topjohnwu.magisk.widget.Pre23CardViewBackgroundColorFixLayoutInflaterListener
import rikka.insets.WindowInsetsHelper import rikka.insets.WindowInsetsHelper
import rikka.layoutinflater.view.LayoutInflaterFactory import rikka.layoutinflater.view.LayoutInflaterFactory
@@ -23,6 +22,8 @@ abstract class UIActivity<Binding : ViewDataBinding> : BaseActivity(), ViewModel
protected lateinit var binding: Binding protected lateinit var binding: Binding
protected abstract val layoutRes: Int protected abstract val layoutRes: Int
protected val binded get() = ::binding.isInitialized
open val snackbarView get() = binding.root open val snackbarView get() = binding.root
open val snackbarAnchorView: View? get() = null open val snackbarAnchorView: View? get() = null
@@ -33,11 +34,6 @@ abstract class UIActivity<Binding : ViewDataBinding> : BaseActivity(), ViewModel
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
layoutInflater.factory2 = LayoutInflaterFactory(delegate) layoutInflater.factory2 = LayoutInflaterFactory(delegate)
.addOnViewCreatedListener(WindowInsetsHelper.LISTENER) .addOnViewCreatedListener(WindowInsetsHelper.LISTENER)
.apply {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
this.addOnViewCreatedListener(Pre23CardViewBackgroundColorFixLayoutInflaterListener.getInstance())
}
}
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)

View File

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

View File

@@ -5,10 +5,16 @@ import android.app.Application
import android.content.Context import android.content.Context
import android.content.res.Configuration import android.content.res.Configuration
import android.os.Bundle import android.os.Bundle
import androidx.lifecycle.ProcessLifecycleAccessor
import com.topjohnwu.magisk.StubApk import com.topjohnwu.magisk.StubApk
import com.topjohnwu.magisk.core.di.ServiceLocator import com.topjohnwu.magisk.core.di.ServiceLocator
import com.topjohnwu.magisk.core.utils.* import com.topjohnwu.magisk.core.utils.DispatcherExecutor
import com.topjohnwu.magisk.core.utils.RootUtils
import com.topjohnwu.magisk.core.utils.ShellInit
import com.topjohnwu.magisk.core.utils.refreshLocale
import com.topjohnwu.magisk.core.utils.setConfig
import com.topjohnwu.magisk.ui.surequest.SuRequestActivity import com.topjohnwu.magisk.ui.surequest.SuRequestActivity
import com.topjohnwu.magisk.view.Notifications
import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.Shell
import com.topjohnwu.superuser.internal.UiThreadHandler import com.topjohnwu.superuser.internal.UiThreadHandler
import com.topjohnwu.superuser.ipc.RootService import com.topjohnwu.superuser.ipc.RootService
@@ -70,6 +76,12 @@ open class App() : Application() {
refreshLocale() refreshLocale()
resources.patch() resources.patch()
Notifications.setup()
}
override fun onCreate() {
super.onCreate()
ProcessLifecycleAccessor.init(this)
} }
override fun onConfigurationChanged(newConfig: Configuration) { override fun onConfigurationChanged(newConfig: Configuration) {

View File

@@ -6,6 +6,7 @@ import android.util.Xml
import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.app.AppCompatDelegate
import androidx.core.content.edit import androidx.core.content.edit
import com.topjohnwu.magisk.BuildConfig import com.topjohnwu.magisk.BuildConfig
import com.topjohnwu.magisk.core.di.AppContext
import com.topjohnwu.magisk.core.di.ServiceLocator import com.topjohnwu.magisk.core.di.ServiceLocator
import com.topjohnwu.magisk.core.repository.BoolDBPropertyNoWrite import com.topjohnwu.magisk.core.repository.BoolDBPropertyNoWrite
import com.topjohnwu.magisk.core.repository.DBConfig import com.topjohnwu.magisk.core.repository.DBConfig
@@ -136,7 +137,15 @@ object Config : PreferenceConfig, DBConfig {
var themeOrdinal by preference(Key.THEME_ORDINAL, Theme.Piplup.ordinal) var themeOrdinal by preference(Key.THEME_ORDINAL, Theme.Piplup.ordinal)
var suReAuth by preference(Key.SU_REAUTH, false) var suReAuth by preference(Key.SU_REAUTH, false)
var suTapjack by preference(Key.SU_TAPJACK, true) var suTapjack by preference(Key.SU_TAPJACK, true)
var checkUpdate by preference(Key.CHECK_UPDATES, true) private var checkUpdatePrefs by preference(Key.CHECK_UPDATES, true)
var checkUpdate
get() = checkUpdatePrefs
set(value) {
if (checkUpdatePrefs != value) {
checkUpdatePrefs = value
JobService.schedule(AppContext)
}
}
var doh by preference(Key.DOH, false) var doh by preference(Key.DOH, false)
var showSystemApp by preference(Key.SHOW_SYSTEM_APP, false) var showSystemApp by preference(Key.SHOW_SYSTEM_APP, false)

View File

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

View File

@@ -7,7 +7,7 @@ import com.topjohnwu.magisk.StubApk
import com.topjohnwu.magisk.core.di.AppContext import com.topjohnwu.magisk.core.di.AppContext
import com.topjohnwu.magisk.core.model.UpdateInfo import com.topjohnwu.magisk.core.model.UpdateInfo
import com.topjohnwu.magisk.core.repository.NetworkService import com.topjohnwu.magisk.core.repository.NetworkService
import com.topjohnwu.magisk.core.utils.net.NetworkObserver import com.topjohnwu.magisk.core.utils.NetworkObserver
import com.topjohnwu.magisk.ktx.getProperty import com.topjohnwu.magisk.ktx.getProperty
import com.topjohnwu.superuser.ShellUtils.fastCmd import com.topjohnwu.superuser.ShellUtils.fastCmd
@@ -67,9 +67,10 @@ object Info {
) { ) {
val versionCode = when { val versionCode = when {
code < Const.Version.MIN_VERCODE -> -1 code < Const.Version.MIN_VERCODE -> -1
else -> if (isRooted) code else -1 isRooted -> code
else -> -1
} }
val isUnsupported = code > 0 && code < Const.Version.MIN_VERCODE val isUnsupported = code > 0 && code < Const.Version.MIN_VERCODE
val isActive = versionCode >= 0 val isActive = versionCode > 0
} }
} }

View File

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

View File

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

View File

@@ -1,5 +1,6 @@
package com.topjohnwu.magisk.core.base package com.topjohnwu.magisk.core.base
import android.Manifest.permission.POST_NOTIFICATIONS
import android.Manifest.permission.REQUEST_INSTALL_PACKAGES import android.Manifest.permission.REQUEST_INSTALL_PACKAGES
import android.Manifest.permission.WRITE_EXTERNAL_STORAGE import android.Manifest.permission.WRITE_EXTERNAL_STORAGE
import android.app.Activity import android.app.Activity
@@ -35,9 +36,11 @@ abstract class BaseActivity : AppCompatActivity() {
permissionCallback?.invoke(it) permissionCallback?.invoke(it)
permissionCallback = null permissionCallback = null
} }
private var installCallback: ((Boolean) -> Unit)? = null
private val requestInstall = registerForActivityResult(RequestInstall()) { private val requestInstall = registerForActivityResult(RequestInstall()) {
permissionCallback?.invoke(it) installCallback?.invoke(it)
permissionCallback = null installCallback = null
} }
private var contentCallback: ContentResultCallback? = null private var contentCallback: ContentResultCallback? = null
@@ -52,9 +55,7 @@ abstract class BaseActivity : AppCompatActivity() {
val realCallingPackage: String? get() { val realCallingPackage: String? get() {
callingPackage?.let { return it } callingPackage?.let { return it }
if (Build.VERSION.SDK_INT >= 22) { mReferrerField.get(this)?.let { return it as String }
mReferrerField.get(this)?.let { return it as String }
}
return null return null
} }
@@ -67,8 +68,8 @@ abstract class BaseActivity : AppCompatActivity() {
// Overwrite private members to avoid nasty "false" stack traces being logged // Overwrite private members to avoid nasty "false" stack traces being logged
val delegate = delegate val delegate = delegate
val clz = delegate.javaClass val clz = delegate.javaClass
clz.reflectField("mActivityHandlesUiModeChecked").set(delegate, true) clz.reflectField("mActivityHandlesConfigFlagsChecked").set(delegate, true)
clz.reflectField("mActivityHandlesUiMode").set(delegate, false) clz.reflectField("mActivityHandlesConfigFlags").set(delegate, 0)
} }
contentCallback = savedInstanceState?.getParcelable(CONTENT_CALLBACK_KEY) contentCallback = savedInstanceState?.getParcelable(CONTENT_CALLBACK_KEY)
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@@ -82,15 +83,23 @@ abstract class BaseActivity : AppCompatActivity() {
} }
fun withPermission(permission: String, callback: (Boolean) -> Unit) { fun withPermission(permission: String, callback: (Boolean) -> Unit) {
if (permission == WRITE_EXTERNAL_STORAGE && Build.VERSION.SDK_INT >= 30) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R &&
// We do not need external rw on 30+ permission == WRITE_EXTERNAL_STORAGE) {
// We do not need external rw on R+
callback(true)
return
}
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU &&
permission == POST_NOTIFICATIONS) {
// All apps have notification permissions before T
callback(true) callback(true)
return return
} }
permissionCallback = callback
if (permission == REQUEST_INSTALL_PACKAGES) { if (permission == REQUEST_INSTALL_PACKAGES) {
installCallback = callback
requestInstall.launch(Unit) requestInstall.launch(Unit)
} else { } else {
permissionCallback = callback
requestPermission.launch(permission) requestPermission.launch(permission)
} }
} }

View File

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

View File

@@ -39,7 +39,6 @@ object ServiceLocator {
NetworkService( NetworkService(
createApiService(retrofit, Const.Url.GITHUB_PAGE_URL), createApiService(retrofit, Const.Url.GITHUB_PAGE_URL),
createApiService(retrofit, Const.Url.GITHUB_RAW_URL), createApiService(retrofit, Const.Url.GITHUB_RAW_URL),
createApiService(retrofit, Const.Url.GITHUB_API_URL)
) )
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -131,7 +131,7 @@ abstract class MagiskInstallImpl protected constructor(
} }
// Extract scripts // Extract scripts
for (script in listOf("util_functions.sh", "boot_patch.sh", "addon.d.sh")) { for (script in listOf("util_functions.sh", "boot_patch.sh", "addon.d.sh", "stub.apk")) {
val dest = File(installDir, script) val dest = File(installDir, script)
context.assets.open(script).writeTo(dest) context.assets.open(script).writeTo(dest)
} }
@@ -190,6 +190,7 @@ abstract class MagiskInstallImpl protected constructor(
while (tarIn.nextEntry?.let { entry = it } != null) { while (tarIn.nextEntry?.let { entry = it } != null) {
if (entry.name.startsWith("boot.img") || if (entry.name.startsWith("boot.img") ||
entry.name.startsWith("init_boot.img") ||
(Config.recovery && entry.name.contains("recovery.img"))) { (Config.recovery && entry.name.contains("recovery.img"))) {
val name = entry.name.replace(".lz4", "") val name = entry.name.replace(".lz4", "")
console.add("-- Extracting: $name") console.add("-- Extracting: $name")
@@ -216,6 +217,7 @@ abstract class MagiskInstallImpl protected constructor(
} }
} }
val boot = installDir.getChildFile("boot.img") val boot = installDir.getChildFile("boot.img")
val initBoot = installDir.getChildFile("init_boot.img")
val recovery = installDir.getChildFile("recovery.img") val recovery = installDir.getChildFile("recovery.img")
if (Config.recovery && recovery.exists() && boot.exists()) { if (Config.recovery && recovery.exists() && boot.exists()) {
// Install to recovery // Install to recovery
@@ -236,11 +238,14 @@ abstract class MagiskInstallImpl protected constructor(
} }
boot.delete() boot.delete()
} else { } else {
if (!boot.exists()) { srcBoot = when {
console.add("! No boot image found") initBoot.exists() -> initBoot
throw IOException() boot.exists() -> boot
else -> {
console.add("! No boot image found")
throw IOException()
}
} }
srcBoot = boot
} }
return tarOut return tarOut
} }
@@ -300,7 +305,13 @@ abstract class MagiskInstallImpl protected constructor(
try { try {
val newBoot = installDir.getChildFile("new-boot.img") val newBoot = installDir.getChildFile("new-boot.img")
if (outStream is TarOutputStream) { if (outStream is TarOutputStream) {
val name = if (srcBoot.path.contains("recovery")) "recovery.img" else "boot.img" val name = with(srcBoot.path) {
when {
contains("recovery") -> "recovery.img"
contains("init_boot") -> "init_boot.img"
else -> "boot.img"
}
}
outStream.putNextEntry(newTarEntry(name, newBoot.length())) outStream.putNextEntry(newTarEntry(name, newBoot.length()))
} }
newBoot.newInputStream().cleanPump(outStream) newBoot.newInputStream().cleanPump(outStream)
@@ -355,6 +366,7 @@ abstract class MagiskInstallImpl protected constructor(
"KEEPVERITY=${Config.keepVerity} " + "KEEPVERITY=${Config.keepVerity} " +
"PATCHVBMETAFLAG=${Config.patchVbmeta} " + "PATCHVBMETAFLAG=${Config.patchVbmeta} " +
"RECOVERYMODE=${Config.recovery} " + "RECOVERYMODE=${Config.recovery} " +
"SYSTEM_ROOT=${Info.isSAR} " +
"sh boot_patch.sh $srcBoot") "sh boot_patch.sh $srcBoot")
if (!cmds.sh().isSuccess) if (!cmds.sh().isSuccess)

View File

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

View File

@@ -87,7 +87,7 @@ object MediaStoreUtils {
@Throws(IOException::class) @Throws(IOException::class)
fun getFile(displayName: String, skipQuery: Boolean = false): UriFile { fun getFile(displayName: String, skipQuery: Boolean = false): UriFile {
if (Build.VERSION.SDK_INT < 30) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
// Fallback to file based I/O pre Android 11 // Fallback to file based I/O pre Android 11
val parent = File(Environment.getExternalStorageDirectory(), relativePath) val parent = File(Environment.getExternalStorageDirectory(), relativePath)
parent.mkdirs() parent.mkdirs()

View File

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

View File

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

View File

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

View File

@@ -41,7 +41,7 @@ class ShellInit : Shell.Initializer() {
} }
if (shell.isRoot) { if (shell.isRoot) {
add("export MAGISKTMP=\$(magisk --path)/.magisk") add("export MAGISKTMP=\$(magisk --path)")
// Test if we can properly execute stuff in /data // Test if we can properly execute stuff in /data
Info.noDataExec = !shell.newJob().add("$localBB sh -c \"$localBB true\"").exec().isSuccess Info.noDataExec = !shell.newJob().add("$localBB sh -c \"$localBB true\"").exec().isSuccess
} }
@@ -49,12 +49,12 @@ class ShellInit : Shell.Initializer() {
if (Info.noDataExec) { if (Info.noDataExec) {
// Copy it out of /data to workaround Samsung bullshit // Copy it out of /data to workaround Samsung bullshit
add( add(
"if [ -x \$MAGISKTMP/busybox/busybox ]; then", "if [ -x \$MAGISKTMP/.magisk/busybox/busybox ]; then",
" cp -af $localBB \$MAGISKTMP/busybox/busybox", " cp -af $localBB \$MAGISKTMP/.magisk/busybox/busybox",
" exec \$MAGISKTMP/busybox/busybox sh", " exec \$MAGISKTMP/.magisk/busybox/busybox sh",
"else", "else",
" cp -af $localBB /dev/.busybox", " cp -af $localBB /dev/busybox",
" exec /dev/.busybox sh", " exec /dev/busybox sh",
"fi" "fi"
) )
} else { } else {
@@ -73,7 +73,6 @@ class ShellInit : Shell.Initializer() {
fun getVar(name: String) = fastCmd("echo \$$name") fun getVar(name: String) = fastCmd("echo \$$name")
fun getBool(name: String) = getVar(name).toBoolean() fun getBool(name: String) = getVar(name).toBoolean()
Const.MAGISKTMP = getVar("MAGISKTMP")
Info.isSAR = getBool("SYSTEM_ROOT") Info.isSAR = getBool("SYSTEM_ROOT")
Info.ramdisk = getBool("RAMDISKEXIST") Info.ramdisk = getBool("RAMDISKEXIST")
Info.vbmeta = getBool("VBMETAEXIST") Info.vbmeta = getBool("VBMETAEXIST")

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,16 +1,17 @@
package com.topjohnwu.magisk.events.dialog package com.topjohnwu.magisk.dialog
import android.app.ProgressDialog import android.app.ProgressDialog
import android.content.Context import android.content.Context
import android.widget.Toast import android.widget.Toast
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.arch.NavigationActivity import com.topjohnwu.magisk.arch.NavigationActivity
import com.topjohnwu.magisk.events.DialogBuilder
import com.topjohnwu.magisk.ui.flash.FlashFragment import com.topjohnwu.magisk.ui.flash.FlashFragment
import com.topjohnwu.magisk.utils.Utils import com.topjohnwu.magisk.utils.Utils
import com.topjohnwu.magisk.view.MagiskDialog import com.topjohnwu.magisk.view.MagiskDialog
import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.Shell
class UninstallDialog : DialogEvent() { class UninstallDialog : DialogBuilder {
override fun build(dialog: MagiskDialog) { override fun build(dialog: MagiskDialog) {
dialog.apply { dialog.apply {

View File

@@ -1,4 +1,4 @@
package com.topjohnwu.magisk.events.dialog package com.topjohnwu.magisk.events
import com.topjohnwu.magisk.arch.ActivityExecutor import com.topjohnwu.magisk.arch.ActivityExecutor
import com.topjohnwu.magisk.arch.UIActivity import com.topjohnwu.magisk.arch.UIActivity
@@ -9,8 +9,8 @@ class BiometricEvent(
builder: Builder.() -> Unit builder: Builder.() -> Unit
) : ViewEvent(), ActivityExecutor { ) : ViewEvent(), ActivityExecutor {
private var listenerOnFailure: GenericDialogListener = {} private var listenerOnFailure: () -> Unit = {}
private var listenerOnSuccess: GenericDialogListener = {} private var listenerOnSuccess: () -> Unit = {}
init { init {
builder(Builder()) builder(Builder())
@@ -26,11 +26,11 @@ class BiometricEvent(
inner class Builder internal constructor() { inner class Builder internal constructor() {
fun onFailure(listener: GenericDialogListener) { fun onFailure(listener: () -> Unit) {
listenerOnFailure = listener listenerOnFailure = listener
} }
fun onSuccess(listener: GenericDialogListener) { fun onSuccess(listener: () -> Unit) {
listenerOnSuccess = listener listenerOnSuccess = listener
} }
} }

View File

@@ -5,10 +5,15 @@ import android.view.View
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.navigation.NavDirections import androidx.navigation.NavDirections
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import com.topjohnwu.magisk.arch.* import com.topjohnwu.magisk.arch.ActivityExecutor
import com.topjohnwu.magisk.arch.ContextExecutor
import com.topjohnwu.magisk.arch.NavigationActivity
import com.topjohnwu.magisk.arch.UIActivity
import com.topjohnwu.magisk.arch.ViewEvent
import com.topjohnwu.magisk.core.base.ContentResultCallback import com.topjohnwu.magisk.core.base.ContentResultCallback
import com.topjohnwu.magisk.utils.TextHolder import com.topjohnwu.magisk.utils.TextHolder
import com.topjohnwu.magisk.utils.asText import com.topjohnwu.magisk.utils.asText
import com.topjohnwu.magisk.view.MagiskDialog
import com.topjohnwu.magisk.view.Shortcuts import com.topjohnwu.magisk.view.Shortcuts
class PermissionEvent( class PermissionEvent(
@@ -95,3 +100,15 @@ class SnackbarEvent(
activity.showSnackbar(msg.getText(activity.resources), length, builder) activity.showSnackbar(msg.getText(activity.resources), length, builder)
} }
} }
class DialogEvent(
private val builder: DialogBuilder
) : ViewEvent(), ActivityExecutor {
override fun invoke(activity: UIActivity<*>) {
MagiskDialog(activity).apply(builder::build).show()
}
}
interface DialogBuilder {
fun build(dialog: MagiskDialog)
}

View File

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

View File

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

View File

@@ -2,10 +2,7 @@ package com.topjohnwu.magisk.ktx
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.Activity import android.app.Activity
import android.content.ComponentName import android.content.*
import android.content.Context
import android.content.ContextWrapper
import android.content.Intent
import android.content.pm.ApplicationInfo import android.content.pm.ApplicationInfo
import android.content.pm.PackageInfo import android.content.pm.PackageInfo
import android.content.pm.PackageManager import android.content.pm.PackageManager
@@ -17,6 +14,7 @@ import android.graphics.drawable.AdaptiveIconDrawable
import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.LayerDrawable import android.graphics.drawable.LayerDrawable
import android.net.Uri import android.net.Uri
import android.os.Build
import android.os.Build.VERSION.SDK_INT import android.os.Build.VERSION.SDK_INT
import android.os.Process import android.os.Process
import android.view.View import android.view.View
@@ -32,6 +30,7 @@ import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.Const import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.utils.RootUtils import com.topjohnwu.magisk.core.utils.RootUtils
import com.topjohnwu.magisk.core.utils.currentLocale import com.topjohnwu.magisk.core.utils.currentLocale
import com.topjohnwu.magisk.utils.APKInstall
import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.Shell
import java.io.File import java.io.File
import kotlin.Array import kotlin.Array
@@ -44,7 +43,7 @@ fun Context.getBitmap(id: Int): Bitmap {
var drawable = AppCompatResources.getDrawable(this, id)!! var drawable = AppCompatResources.getDrawable(this, id)!!
if (drawable is BitmapDrawable) if (drawable is BitmapDrawable)
return drawable.bitmap return drawable.bitmap
if (SDK_INT >= 26 && drawable is AdaptiveIconDrawable) { if (SDK_INT >= Build.VERSION_CODES.O && drawable is AdaptiveIconDrawable) {
drawable = LayerDrawable(arrayOf(drawable.background, drawable.foreground)) drawable = LayerDrawable(arrayOf(drawable.background, drawable.foreground))
} }
val bitmap = Bitmap.createBitmap( val bitmap = Bitmap.createBitmap(
@@ -58,7 +57,7 @@ fun Context.getBitmap(id: Int): Bitmap {
} }
val Context.deviceProtectedContext: Context get() = val Context.deviceProtectedContext: Context get() =
if (SDK_INT >= 24) { if (SDK_INT >= Build.VERSION_CODES.N) {
createDeviceProtectedStorageContext() createDeviceProtectedStorageContext()
} else { this } } else { this }
@@ -184,12 +183,8 @@ fun ApplicationInfo.getLabel(pm: PackageManager): String {
fun Context.unwrap(): Context { fun Context.unwrap(): Context {
var context = this var context = this
while (true) { while (context is ContextWrapper)
if (context is ContextWrapper) context = context.baseContext
context = context.baseContext
else
break
}
return context return context
} }
@@ -265,3 +260,14 @@ fun PackageManager.getPackageInfo(uid: Int, pid: Int): PackageInfo? {
} }
throw PackageManager.NameNotFoundException() throw PackageManager.NameNotFoundException()
} }
fun Context.registerRuntimeReceiver(receiver: BroadcastReceiver, filter: IntentFilter) {
APKInstall.registerReceiver(this, receiver, filter)
}
fun Context.selfLaunchIntent(): Intent {
val pm = packageManager
val intent = pm.getLaunchIntentForPackage(packageName)!!
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
return intent
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -3,11 +3,12 @@ package com.topjohnwu.magisk.ui.deny
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES import android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES
import androidx.databinding.Bindable import androidx.databinding.Bindable
import androidx.lifecycle.viewModelScope
import com.topjohnwu.magisk.BR import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.arch.AsyncLoadViewModel import com.topjohnwu.magisk.arch.AsyncLoadViewModel
import com.topjohnwu.magisk.core.di.AppContext import com.topjohnwu.magisk.core.di.AppContext
import com.topjohnwu.magisk.databinding.FilterableDiffObservableList
import com.topjohnwu.magisk.databinding.bindExtra import com.topjohnwu.magisk.databinding.bindExtra
import com.topjohnwu.magisk.databinding.filterableListOf
import com.topjohnwu.magisk.databinding.set import com.topjohnwu.magisk.databinding.set
import com.topjohnwu.magisk.ktx.concurrentMap import com.topjohnwu.magisk.ktx.concurrentMap
import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.Shell
@@ -37,7 +38,7 @@ class DenyListViewModel : AsyncLoadViewModel() {
query() query()
} }
val items = filterableListOf<DenyListRvItem>() val items = FilterableDiffObservableList<DenyListRvItem>(viewModelScope)
val extraBindings = bindExtra { val extraBindings = bindExtra {
it.put(BR.viewModel, this) it.put(BR.viewModel, this)
} }
@@ -49,7 +50,7 @@ class DenyListViewModel : AsyncLoadViewModel() {
@SuppressLint("InlinedApi") @SuppressLint("InlinedApi")
override suspend fun doLoadWork() { override suspend fun doLoadWork() {
loading = true loading = true
val (apps, diff) = withContext(Dispatchers.Default) { withContext(Dispatchers.Default) {
val pm = AppContext.packageManager val pm = AppContext.packageManager
val denyList = Shell.cmd("magisk --denylist ls").exec().out val denyList = Shell.cmd("magisk --denylist ls").exec().out
.map { CmdlineListItem(it) } .map { CmdlineListItem(it) }
@@ -62,13 +63,12 @@ class DenyListViewModel : AsyncLoadViewModel() {
.toCollection(ArrayList(size)) .toCollection(ArrayList(size))
} }
apps.sort() apps.sort()
apps to items.calculateDiff(apps) items.update(apps)
} }
items.update(apps, diff)
query() query()
} }
fun query() { private fun query() {
items.filter { items.filter {
fun filterSystem() = isShowSystem || !it.info.isSystemApp() fun filterSystem() = isShowSystem || !it.info.isSystemApp()

View File

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

View File

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

View File

@@ -2,6 +2,7 @@ package com.topjohnwu.magisk.ui.flash
import android.view.MenuItem import android.view.MenuItem
import androidx.databinding.Bindable import androidx.databinding.Bindable
import androidx.databinding.ObservableArrayList
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Transformations import androidx.lifecycle.Transformations
@@ -15,7 +16,6 @@ import com.topjohnwu.magisk.core.tasks.FlashZip
import com.topjohnwu.magisk.core.tasks.MagiskInstaller import com.topjohnwu.magisk.core.tasks.MagiskInstaller
import com.topjohnwu.magisk.core.utils.MediaStoreUtils import com.topjohnwu.magisk.core.utils.MediaStoreUtils
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream
import com.topjohnwu.magisk.databinding.diffListOf
import com.topjohnwu.magisk.databinding.set import com.topjohnwu.magisk.databinding.set
import com.topjohnwu.magisk.events.SnackbarEvent import com.topjohnwu.magisk.events.SnackbarEvent
import com.topjohnwu.magisk.ktx.reboot import com.topjohnwu.magisk.ktx.reboot
@@ -40,7 +40,7 @@ class FlashViewModel : BaseViewModel() {
var showReboot = Info.isRooted var showReboot = Info.isRooted
set(value) = set(value, field, { field = it }, BR.showReboot) set(value) = set(value, field, { field = it }, BR.showReboot)
val items = diffListOf<ConsoleItem>() val items = ObservableArrayList<ConsoleItem>()
lateinit var args: FlashFragmentArgs lateinit var args: FlashFragmentArgs
private val logItems = mutableListOf<String>().synchronized() private val logItems = mutableListOf<String>().synchronized()

View File

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

View File

@@ -6,7 +6,11 @@ import androidx.databinding.Bindable
import com.topjohnwu.magisk.BR import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.BuildConfig import com.topjohnwu.magisk.BuildConfig
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.arch.* import com.topjohnwu.magisk.arch.ActivityExecutor
import com.topjohnwu.magisk.arch.AsyncLoadViewModel
import com.topjohnwu.magisk.arch.ContextExecutor
import com.topjohnwu.magisk.arch.UIActivity
import com.topjohnwu.magisk.arch.ViewEvent
import com.topjohnwu.magisk.core.Config import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.core.Info import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.download.Subject import com.topjohnwu.magisk.core.download.Subject
@@ -14,10 +18,10 @@ import com.topjohnwu.magisk.core.download.Subject.App
import com.topjohnwu.magisk.core.repository.NetworkService import com.topjohnwu.magisk.core.repository.NetworkService
import com.topjohnwu.magisk.databinding.bindExtra import com.topjohnwu.magisk.databinding.bindExtra
import com.topjohnwu.magisk.databinding.set import com.topjohnwu.magisk.databinding.set
import com.topjohnwu.magisk.dialog.EnvFixDialog
import com.topjohnwu.magisk.dialog.ManagerInstallDialog
import com.topjohnwu.magisk.dialog.UninstallDialog
import com.topjohnwu.magisk.events.SnackbarEvent import com.topjohnwu.magisk.events.SnackbarEvent
import com.topjohnwu.magisk.events.dialog.EnvFixDialog
import com.topjohnwu.magisk.events.dialog.ManagerInstallDialog
import com.topjohnwu.magisk.events.dialog.UninstallDialog
import com.topjohnwu.magisk.ktx.await import com.topjohnwu.magisk.ktx.await
import com.topjohnwu.magisk.utils.Utils import com.topjohnwu.magisk.utils.Utils
import com.topjohnwu.magisk.utils.asText import com.topjohnwu.magisk.utils.asText
@@ -43,6 +47,7 @@ class HomeViewModel(
val magiskState val magiskState
get() = when { get() = when {
Info.isRooted && Info.env.isUnsupported -> State.OUTDATED
!Info.env.isActive -> State.INVALID !Info.env.isActive -> State.INVALID
Info.env.versionCode < BuildConfig.VERSION_CODE -> State.OUTDATED Info.env.versionCode < BuildConfig.VERSION_CODE -> State.OUTDATED
else -> State.UP_TO_DATE else -> State.UP_TO_DATE
@@ -66,7 +71,6 @@ class HomeViewModel(
val managerInstalledVersion val managerInstalledVersion
get() = "${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})" + get() = "${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})" +
Info.stub?.let { " (${it.version})" }.orEmpty() +
if (BuildConfig.DEBUG) " (D)" else "" if (BuildConfig.DEBUG) " (D)" else ""
@get:Bindable @get:Bindable
@@ -91,7 +95,7 @@ class HomeViewModel(
val isDebug = Config.updateChannel == Config.Value.DEBUG_CHANNEL val isDebug = Config.updateChannel == Config.Value.DEBUG_CHANNEL
managerRemoteVersion = managerRemoteVersion =
("${magisk.version} (${magisk.versionCode}) (${stub.versionCode})" + ("${magisk.version} (${magisk.versionCode})" +
if (isDebug) " (D)" else "").asText() if (isDebug) " (D)" else "").asText()
} ?: run { } ?: run {
appState = State.INVALID appState = State.INVALID
@@ -111,14 +115,14 @@ class HomeViewModel(
override fun invoke(context: Context) = Utils.openLink(context, link.toUri()) override fun invoke(context: Context) = Utils.openLink(context, link.toUri())
}.publish() }.publish()
fun onDeletePressed() = UninstallDialog().publish() fun onDeletePressed() = UninstallDialog().show()
fun onManagerPressed() = when (appState) { fun onManagerPressed() = when (appState) {
State.LOADING -> SnackbarEvent(R.string.loading).publish() State.LOADING -> SnackbarEvent(R.string.loading).publish()
State.INVALID -> SnackbarEvent(R.string.no_connection).publish() State.INVALID -> SnackbarEvent(R.string.no_connection).publish()
else -> withExternalRW { else -> withExternalRW {
withInstallPermission { withInstallPermission {
ManagerInstallDialog().publish() ManagerInstallDialog().show()
} }
} }
} }
@@ -135,8 +139,9 @@ class HomeViewModel(
private suspend fun ensureEnv() { private suspend fun ensureEnv() {
if (magiskState == State.INVALID || checkedEnv) return if (magiskState == State.INVALID || checkedEnv) return
val cmd = "env_check ${Info.env.versionString} ${Info.env.versionCode}" val cmd = "env_check ${Info.env.versionString} ${Info.env.versionCode}"
if (!Shell.cmd(cmd).await().isSuccess) { val code = Shell.cmd(cmd).await().code
EnvFixDialog(this).publish() if (code != 0) {
EnvFixDialog(this, code).show()
} }
checkedEnv = true checkedEnv = true
} }

View File

@@ -1,4 +1,4 @@
package com.topjohnwu.magisk.events package com.topjohnwu.magisk.ui.home
import android.os.Build import android.os.Build
import android.os.PowerManager import android.os.PowerManager
@@ -10,7 +10,7 @@ import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.base.BaseActivity import com.topjohnwu.magisk.core.base.BaseActivity
import com.topjohnwu.magisk.ktx.reboot as systemReboot import com.topjohnwu.magisk.ktx.reboot as systemReboot
object RebootEvent { object RebootMenu {
private fun reboot(item: MenuItem): Boolean { private fun reboot(item: MenuItem): Boolean {
when (item.itemId) { when (item.itemId) {
@@ -25,14 +25,14 @@ object RebootEvent {
return true return true
} }
fun inflateMenu(activity: BaseActivity): PopupMenu { fun inflate(activity: BaseActivity): PopupMenu {
val themeWrapper = ContextThemeWrapper(activity, R.style.Foundation_PopupMenu) val themeWrapper = ContextThemeWrapper(activity, R.style.Foundation_PopupMenu)
val menu = PopupMenu(themeWrapper, activity.findViewById(R.id.action_reboot)) val menu = PopupMenu(themeWrapper, activity.findViewById(R.id.action_reboot))
activity.menuInflater.inflate(R.menu.menu_reboot, menu.menu) activity.menuInflater.inflate(R.menu.menu_reboot, menu.menu)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R &&
activity.getSystemService<PowerManager>()?.isRebootingUserspaceSupported == true) activity.getSystemService<PowerManager>()?.isRebootingUserspaceSupported == true)
menu.menu.findItem(R.id.action_reboot_userspace).isVisible = true menu.menu.findItem(R.id.action_reboot_userspace).isVisible = true
menu.setOnMenuItemClickListener(::reboot) menu.setOnMenuItemClickListener(RebootMenu::reboot)
return menu return menu
} }

View File

@@ -22,8 +22,8 @@ import com.topjohnwu.magisk.core.di.AppContext
import com.topjohnwu.magisk.core.di.ServiceLocator import com.topjohnwu.magisk.core.di.ServiceLocator
import com.topjohnwu.magisk.core.repository.NetworkService import com.topjohnwu.magisk.core.repository.NetworkService
import com.topjohnwu.magisk.databinding.set import com.topjohnwu.magisk.databinding.set
import com.topjohnwu.magisk.dialog.SecondSlotWarningDialog
import com.topjohnwu.magisk.events.GetContentEvent import com.topjohnwu.magisk.events.GetContentEvent
import com.topjohnwu.magisk.events.dialog.SecondSlotWarningDialog
import com.topjohnwu.magisk.ui.flash.FlashFragment import com.topjohnwu.magisk.ui.flash.FlashFragment
import com.topjohnwu.magisk.utils.Utils import com.topjohnwu.magisk.utils.Utils
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@@ -57,7 +57,7 @@ class InstallViewModel(
GetContentEvent("*/*", UriCallback()).publish() GetContentEvent("*/*", UriCallback()).publish()
} }
R.id.method_inactive_slot -> { R.id.method_inactive_slot -> {
SecondSlotWarningDialog().publish() SecondSlotWarningDialog().show()
} }
} }
} }

View File

@@ -5,6 +5,7 @@ import android.view.Menu
import android.view.MenuInflater import android.view.MenuInflater
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import androidx.core.view.MenuProvider
import androidx.core.view.isVisible import androidx.core.view.isVisible
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.arch.BaseFragment import com.topjohnwu.magisk.arch.BaseFragment
@@ -16,7 +17,7 @@ import rikka.recyclerview.addEdgeSpacing
import rikka.recyclerview.addItemSpacing import rikka.recyclerview.addItemSpacing
import rikka.recyclerview.fixEdgeEffect import rikka.recyclerview.fixEdgeEffect
class LogFragment : BaseFragment<FragmentLogMd2Binding>() { class LogFragment : BaseFragment<FragmentLogMd2Binding>(), MenuProvider {
override val layoutRes = R.layout.fragment_log_md2 override val layoutRes = R.layout.fragment_log_md2
override val viewModel by viewModel<LogViewModel>() override val viewModel by viewModel<LogViewModel>()
@@ -40,8 +41,7 @@ class LogFragment : BaseFragment<FragmentLogMd2Binding>() {
override fun onStart() { override fun onStart() {
super.onStart() super.onStart()
setHasOptionsMenu(true) activity?.setTitle(R.string.logs)
activity?.title = resources.getString(R.string.logs)
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@@ -58,15 +58,14 @@ class LogFragment : BaseFragment<FragmentLogMd2Binding>() {
} }
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { override fun onCreateMenu(menu: Menu, inflater: MenuInflater) {
super.onCreateOptionsMenu(menu, inflater)
inflater.inflate(R.menu.menu_log_md2, menu) inflater.inflate(R.menu.menu_log_md2, menu)
actionSave = menu.findItem(R.id.action_save)?.also { actionSave = menu.findItem(R.id.action_save)?.also {
it.isVisible = !isMagiskLogVisible it.isVisible = !isMagiskLogVisible
} }
} }
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onMenuItemSelected(item: MenuItem): Boolean {
when (item.itemId) { when (item.itemId) {
R.id.action_save -> viewModel.saveMagiskLog() R.id.action_save -> viewModel.saveMagiskLog()
R.id.action_clear -> R.id.action_clear ->

View File

@@ -1,30 +1,28 @@
package com.topjohnwu.magisk.ui.log package com.topjohnwu.magisk.ui.log
import androidx.databinding.Bindable import androidx.databinding.ViewDataBinding
import com.topjohnwu.magisk.BR import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.textview.MaterialTextView
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.model.su.SuLog import com.topjohnwu.magisk.databinding.DiffItem
import com.topjohnwu.magisk.databinding.ObservableDiffRvItem import com.topjohnwu.magisk.databinding.ItemWrapper
import com.topjohnwu.magisk.databinding.RvContainer import com.topjohnwu.magisk.databinding.ObservableRvItem
import com.topjohnwu.magisk.databinding.set import com.topjohnwu.magisk.databinding.ViewAwareItem
import com.topjohnwu.magisk.ktx.timeDateFormat
import com.topjohnwu.magisk.ktx.toTime
class LogRvItem( class LogRvItem(
override val item: SuLog override val item: String
) : ObservableDiffRvItem<LogRvItem>(), RvContainer<SuLog> { ) : ObservableRvItem(), DiffItem<LogRvItem>, ItemWrapper<String>, ViewAwareItem {
override val layoutRes = R.layout.item_log_access_md2 override val layoutRes = R.layout.item_log_textview
val date = item.time.toTime(timeDateFormat) override fun onBind(binding: ViewDataBinding, recyclerView: RecyclerView) {
val view = binding.root as MaterialTextView
@get:Bindable view.measure(0, 0)
var isTop = false val desiredWidth = view.measuredWidth
set(value) = set(value, field, { field = it }, BR.top) val layoutParams = view.layoutParams
layoutParams.width = desiredWidth
@get:Bindable if (recyclerView.width < desiredWidth) {
var isBottom = false recyclerView.requestLayout()
set(value) = set(value, field, { field = it }, BR.bottom) }
}
override fun itemSameAs(other: LogRvItem) = item.appName == other.item.appName
} }

View File

@@ -1,5 +1,6 @@
package com.topjohnwu.magisk.ui.log package com.topjohnwu.magisk.ui.log
import android.system.Os
import androidx.databinding.Bindable import androidx.databinding.Bindable
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.topjohnwu.magisk.BR import com.topjohnwu.magisk.BR
@@ -10,8 +11,8 @@ import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.repository.LogRepository import com.topjohnwu.magisk.core.repository.LogRepository
import com.topjohnwu.magisk.core.utils.MediaStoreUtils import com.topjohnwu.magisk.core.utils.MediaStoreUtils
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream
import com.topjohnwu.magisk.databinding.DiffObservableList
import com.topjohnwu.magisk.databinding.bindExtra import com.topjohnwu.magisk.databinding.bindExtra
import com.topjohnwu.magisk.databinding.diffListOf
import com.topjohnwu.magisk.databinding.set import com.topjohnwu.magisk.databinding.set
import com.topjohnwu.magisk.events.SnackbarEvent import com.topjohnwu.magisk.events.SnackbarEvent
import com.topjohnwu.magisk.ktx.timeFormatStandard import com.topjohnwu.magisk.ktx.timeFormatStandard
@@ -25,6 +26,9 @@ import java.io.FileInputStream
class LogViewModel( class LogViewModel(
private val repo: LogRepository private val repo: LogRepository
) : AsyncLoadViewModel() { ) : AsyncLoadViewModel() {
@get:Bindable
var loading = true
private set(value) = set(value, field, { field = it }, BR.loading)
// --- empty view // --- empty view
@@ -33,27 +37,32 @@ class LogViewModel(
// --- su log // --- su log
val items = diffListOf<LogRvItem>() val items = DiffObservableList<SuLogRvItem>()
val extraBindings = bindExtra { val extraBindings = bindExtra {
it.put(BR.viewModel, this) it.put(BR.viewModel, this)
} }
// --- magisk log // --- magisk log
@get:Bindable val logs = DiffObservableList<LogRvItem>()
var consoleText = " " var magiskLogRaw = " "
set(value) = set(value, field, { field = it }, BR.consoleText)
override suspend fun doLoadWork() { override suspend fun doLoadWork() {
consoleText = repo.fetchMagiskLogs() loading = true
val (suLogs, diff) = withContext(Dispatchers.Default) {
val suLogs = repo.fetchSuLogs().map { LogRvItem(it) } val (suLogs, suDiff) = withContext(Dispatchers.Default) {
magiskLogRaw = repo.fetchMagiskLogs()
val newLogs = magiskLogRaw.split('\n').map { LogRvItem(it) }
logs.update(newLogs)
val suLogs = repo.fetchSuLogs().map { SuLogRvItem(it) }
suLogs to items.calculateDiff(suLogs) suLogs to items.calculateDiff(suLogs)
} }
items.firstOrNull()?.isTop = false items.firstOrNull()?.isTop = false
items.lastOrNull()?.isBottom = false items.lastOrNull()?.isBottom = false
items.update(suLogs, diff) items.update(suLogs, suDiff)
items.firstOrNull()?.isTop = true items.firstOrNull()?.isTop = true
items.lastOrNull()?.isBottom = true items.lastOrNull()?.isBottom = true
loading = false
} }
fun saveMagiskLog() = withExternalRW { fun saveMagiskLog() = withExternalRW {
@@ -66,17 +75,22 @@ class LogViewModel(
file.write("isAB=${Info.isAB}\n") file.write("isAB=${Info.isAB}\n")
file.write("isSAR=${Info.isSAR}\n") file.write("isSAR=${Info.isSAR}\n")
file.write("ramdisk=${Info.ramdisk}\n") file.write("ramdisk=${Info.ramdisk}\n")
val uname = Os.uname()
file.write("kernel=${uname.sysname} ${uname.machine} ${uname.release} ${uname.version}\n")
file.write("\n\n---System Properties---\n\n") file.write("\n\n---System Properties---\n\n")
ProcessBuilder("getprop").start() ProcessBuilder("getprop").start()
.inputStream.reader().use { it.copyTo(file) } .inputStream.reader().use { it.copyTo(file) }
file.write("\n\n---Environment Variables---\n\n")
System.getenv().forEach { (key, value) -> file.write("${key}=${value}\n") }
file.write("\n\n---System MountInfo---\n\n") file.write("\n\n---System MountInfo---\n\n")
FileInputStream("/proc/self/mountinfo").reader().use { it.copyTo(file) } FileInputStream("/proc/self/mountinfo").reader().use { it.copyTo(file) }
file.write("\n---Magisk Logs---\n") file.write("\n---Magisk Logs---\n")
file.write("${Info.env.versionString} (${Info.env.versionCode})\n\n") file.write("${Info.env.versionString} (${Info.env.versionCode})\n\n")
file.write(consoleText) if (Info.env.isActive) file.write(magiskLogRaw)
file.write("\n---Manager Logs---\n") file.write("\n---Manager Logs---\n")
file.write("${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})\n\n") file.write("${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})\n\n")

View File

@@ -0,0 +1,28 @@
package com.topjohnwu.magisk.ui.log
import androidx.databinding.Bindable
import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.model.su.SuLog
import com.topjohnwu.magisk.databinding.DiffItem
import com.topjohnwu.magisk.databinding.ObservableRvItem
import com.topjohnwu.magisk.databinding.set
import com.topjohnwu.magisk.ktx.timeDateFormat
import com.topjohnwu.magisk.ktx.toTime
class SuLogRvItem(val log: SuLog) : ObservableRvItem(), DiffItem<SuLogRvItem> {
override val layoutRes = R.layout.item_log_access_md2
val date = log.time.toTime(timeDateFormat)
@get:Bindable
var isTop = false
set(value) = set(value, field, { field = it }, BR.top)
@get:Bindable
var isBottom = false
set(value) = set(value, field, { field = it }, BR.bottom)
override fun itemSameAs(other: SuLogRvItem) = log.appName == other.log.appName
}

View File

@@ -2,11 +2,9 @@ package com.topjohnwu.magisk.ui.module
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import com.topjohnwu.magisk.MainDirections
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.arch.BaseFragment import com.topjohnwu.magisk.arch.BaseFragment
import com.topjohnwu.magisk.arch.viewModel import com.topjohnwu.magisk.arch.viewModel
import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.databinding.FragmentModuleMd2Binding import com.topjohnwu.magisk.databinding.FragmentModuleMd2Binding
import rikka.recyclerview.addEdgeSpacing import rikka.recyclerview.addEdgeSpacing
import rikka.recyclerview.addInvalidateItemDecorationsObserver import rikka.recyclerview.addInvalidateItemDecorationsObserver
@@ -23,7 +21,7 @@ class ModuleFragment : BaseFragment<FragmentModuleMd2Binding>() {
activity?.title = resources.getString(R.string.modules) activity?.title = resources.getString(R.string.modules)
viewModel.data.observe(this) { viewModel.data.observe(this) {
it ?: return@observe it ?: return@observe
MainDirections.actionFlashFragment(Const.Value.FLASH_ZIP, it).navigate() viewModel.requestInstallLocalModule(it)
viewModel.data.value = null viewModel.data.value = null
} }
} }

View File

@@ -5,20 +5,21 @@ import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.Info import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.model.module.LocalModule import com.topjohnwu.magisk.core.model.module.LocalModule
import com.topjohnwu.magisk.databinding.DiffRvItem import com.topjohnwu.magisk.databinding.DiffItem
import com.topjohnwu.magisk.databinding.ObservableDiffRvItem import com.topjohnwu.magisk.databinding.ItemWrapper
import com.topjohnwu.magisk.databinding.RvContainer import com.topjohnwu.magisk.databinding.ObservableRvItem
import com.topjohnwu.magisk.databinding.RvItem
import com.topjohnwu.magisk.databinding.set import com.topjohnwu.magisk.databinding.set
import com.topjohnwu.magisk.utils.TextHolder import com.topjohnwu.magisk.utils.TextHolder
import com.topjohnwu.magisk.utils.asText import com.topjohnwu.magisk.utils.asText
object InstallModule : DiffRvItem<InstallModule>() { object InstallModule : RvItem(), DiffItem<InstallModule> {
override val layoutRes = R.layout.item_module_download override val layoutRes = R.layout.item_module_download
} }
class LocalModuleRvItem( class LocalModuleRvItem(
override val item: LocalModule override val item: LocalModule
) : ObservableDiffRvItem<LocalModuleRvItem>(), RvContainer<LocalModule> { ) : ObservableRvItem(), DiffItem<LocalModuleRvItem>, ItemWrapper<LocalModule> {
override val layoutRes = R.layout.item_module_md2 override val layoutRes = R.layout.item_module_md2

View File

@@ -10,10 +10,15 @@ import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.base.ContentResultCallback import com.topjohnwu.magisk.core.base.ContentResultCallback
import com.topjohnwu.magisk.core.model.module.LocalModule import com.topjohnwu.magisk.core.model.module.LocalModule
import com.topjohnwu.magisk.core.model.module.OnlineModule import com.topjohnwu.magisk.core.model.module.OnlineModule
import com.topjohnwu.magisk.databinding.* import com.topjohnwu.magisk.databinding.DiffObservableList
import com.topjohnwu.magisk.databinding.MergeObservableList
import com.topjohnwu.magisk.databinding.RvItem
import com.topjohnwu.magisk.databinding.bindExtra
import com.topjohnwu.magisk.databinding.set
import com.topjohnwu.magisk.dialog.LocalModuleInstallDialog
import com.topjohnwu.magisk.dialog.OnlineModuleInstallDialog
import com.topjohnwu.magisk.events.GetContentEvent import com.topjohnwu.magisk.events.GetContentEvent
import com.topjohnwu.magisk.events.SnackbarEvent import com.topjohnwu.magisk.events.SnackbarEvent
import com.topjohnwu.magisk.events.dialog.ModuleInstallDialog
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
@@ -22,7 +27,7 @@ class ModuleViewModel : AsyncLoadViewModel() {
val bottomBarBarrierIds = intArrayOf(R.id.module_update, R.id.module_remove) val bottomBarBarrierIds = intArrayOf(R.id.module_update, R.id.module_remove)
private val itemsInstalled = diffListOf<LocalModuleRvItem>() private val itemsInstalled = DiffObservableList<LocalModuleRvItem>()
val items = MergeObservableList<RvItem>() val items = MergeObservableList<RvItem>()
val extraBindings = bindExtra { val extraBindings = bindExtra {
@@ -35,16 +40,17 @@ class ModuleViewModel : AsyncLoadViewModel() {
var loading = true var loading = true
private set(value) = set(value, field, { field = it }, BR.loading) private set(value) = set(value, field, { field = it }, BR.loading)
init {
if (Info.env.isActive && LocalModule.loaded()) {
items.insertItem(InstallModule)
.insertList(itemsInstalled)
}
}
override suspend fun doLoadWork() { override suspend fun doLoadWork() {
loading = true loading = true
loadInstalled() val moduleLoaded = Info.env.isActive &&
withContext(Dispatchers.IO) { LocalModule.loaded() }
if (moduleLoaded) {
loadInstalled()
if (items.isEmpty()) {
items.insertItem(InstallModule)
.insertList(itemsInstalled)
}
}
loading = false loading = false
loadUpdateInfo() loadUpdateInfo()
} }
@@ -52,11 +58,10 @@ class ModuleViewModel : AsyncLoadViewModel() {
override fun onNetworkChanged(network: Boolean) = startLoading() override fun onNetworkChanged(network: Boolean) = startLoading()
private suspend fun loadInstalled() { private suspend fun loadInstalled() {
val installed = LocalModule.installed().map { LocalModuleRvItem(it) } withContext(Dispatchers.Default) {
val diff = withContext(Dispatchers.Default) { val installed = LocalModule.installed().map { LocalModuleRvItem(it) }
itemsInstalled.calculateDiff(installed) itemsInstalled.update(installed)
} }
itemsInstalled.update(installed, diff)
} }
private suspend fun loadUpdateInfo() { private suspend fun loadUpdateInfo() {
@@ -70,7 +75,7 @@ class ModuleViewModel : AsyncLoadViewModel() {
fun downloadPressed(item: OnlineModule?) = fun downloadPressed(item: OnlineModule?) =
if (item != null && Info.isConnected.value == true) { if (item != null && Info.isConnected.value == true) {
withExternalRW { ModuleInstallDialog(item).publish() } withExternalRW { OnlineModuleInstallDialog(item).show() }
} else { } else {
SnackbarEvent(R.string.no_connection).publish() SnackbarEvent(R.string.no_connection).publish()
} }
@@ -79,6 +84,10 @@ class ModuleViewModel : AsyncLoadViewModel() {
GetContentEvent("application/zip", UriCallback()).publish() GetContentEvent("application/zip", UriCallback()).publish()
} }
fun requestInstallLocalModule(uri: Uri) {
LocalModuleInstallDialog(this, uri).show()
}
@Parcelize @Parcelize
class UriCallback : ContentResultCallback { class UriCallback : ContentResultCallback {
override fun onActivityResult(result: Uri) { override fun onActivityResult(result: Uri) {

View File

@@ -8,6 +8,7 @@ import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.databinding.ObservableRvItem import com.topjohnwu.magisk.databinding.ObservableRvItem
import com.topjohnwu.magisk.databinding.set import com.topjohnwu.magisk.databinding.set
import com.topjohnwu.magisk.ktx.activity
import com.topjohnwu.magisk.utils.TextHolder import com.topjohnwu.magisk.utils.TextHolder
import com.topjohnwu.magisk.view.MagiskDialog import com.topjohnwu.magisk.view.MagiskDialog
@@ -57,6 +58,8 @@ sealed class BaseSettingsItem : ObservableRvItem() {
set(checked, value, { onPressed(view, handler) }) set(checked, value, { onPressed(view, handler) })
override fun onPressed(view: View, handler: Handler) { override fun onPressed(view: View, handler: Handler) {
// Make sure the checked state is synced
notifyPropertyChanged(BR.checked)
handler.onItemPressed(view, this) { handler.onItemPressed(view, this) {
value = !value value = !value
notifyPropertyChanged(BR.checked) notifyPropertyChanged(BR.checked)
@@ -72,7 +75,7 @@ sealed class BaseSettingsItem : ObservableRvItem() {
override fun onPressed(view: View, handler: Handler) { override fun onPressed(view: View, handler: Handler) {
handler.onItemPressed(view, this) { handler.onItemPressed(view, this) {
MagiskDialog(view.context).apply { MagiskDialog(view.activity).apply {
setTitle(title.getText(view.resources)) setTitle(title.getText(view.resources))
setView(getView(view.context)) setView(getView(view.context))
setButton(MagiskDialog.ButtonType.POSITIVE) { setButton(MagiskDialog.ButtonType.POSITIVE) {
@@ -115,7 +118,7 @@ sealed class BaseSettingsItem : ObservableRvItem() {
override fun onPressed(view: View, handler: Handler) { override fun onPressed(view: View, handler: Handler) {
handler.onItemPressed(view, this) { handler.onItemPressed(view, this) {
MagiskDialog(view.context).apply { MagiskDialog(view.activity).apply {
setTitle(title.getText(view.resources)) setTitle(title.getText(view.resources))
setButton(MagiskDialog.ButtonType.NEGATIVE) { setButton(MagiskDialog.ButtonType.NEGATIVE) {
text = android.R.string.cancel text = android.R.string.cancel

View File

@@ -12,8 +12,6 @@ import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.Config import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.core.Const import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.Info import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.JobService
import com.topjohnwu.magisk.core.di.AppContext
import com.topjohnwu.magisk.core.tasks.HideAPK import com.topjohnwu.magisk.core.tasks.HideAPK
import com.topjohnwu.magisk.core.utils.BiometricHelper import com.topjohnwu.magisk.core.utils.BiometricHelper
import com.topjohnwu.magisk.core.utils.MediaStoreUtils import com.topjohnwu.magisk.core.utils.MediaStoreUtils
@@ -23,6 +21,7 @@ import com.topjohnwu.magisk.databinding.DialogSettingsAppNameBinding
import com.topjohnwu.magisk.databinding.DialogSettingsDownloadPathBinding import com.topjohnwu.magisk.databinding.DialogSettingsDownloadPathBinding
import com.topjohnwu.magisk.databinding.DialogSettingsUpdateChannelBinding import com.topjohnwu.magisk.databinding.DialogSettingsUpdateChannelBinding
import com.topjohnwu.magisk.databinding.set import com.topjohnwu.magisk.databinding.set
import com.topjohnwu.magisk.ktx.activity
import com.topjohnwu.magisk.utils.Utils import com.topjohnwu.magisk.utils.Utils
import com.topjohnwu.magisk.utils.asText import com.topjohnwu.magisk.utils.asText
import com.topjohnwu.magisk.view.MagiskDialog import com.topjohnwu.magisk.view.MagiskDialog
@@ -111,7 +110,7 @@ object Restore : BaseSettingsItem.Blank() {
override fun onPressed(view: View, handler: Handler) { override fun onPressed(view: View, handler: Handler) {
handler.onItemPressed(view, this) { handler.onItemPressed(view, this) {
MagiskDialog(view.context).apply { MagiskDialog(view.activity).apply {
setTitle(R.string.settings_restore_app_title) setTitle(R.string.settings_restore_app_title)
setMessage(R.string.restore_app_confirmation) setMessage(R.string.restore_app_confirmation)
setButton(MagiskDialog.ButtonType.POSITIVE) { setButton(MagiskDialog.ButtonType.POSITIVE) {
@@ -201,12 +200,7 @@ object UpdateChannelUrl : BaseSettingsItem.Input() {
object UpdateChecker : BaseSettingsItem.Toggle() { object UpdateChecker : BaseSettingsItem.Toggle() {
override val title = R.string.settings_check_update_title.asText() override val title = R.string.settings_check_update_title.asText()
override val description = R.string.settings_check_update_summary.asText() override val description = R.string.settings_check_update_summary.asText()
override var value override var value by Config::checkUpdate
get() = Config.checkUpdate
set(value) {
Config.checkUpdate = value
JobService.schedule(AppContext)
}
} }
object DoHToggle : BaseSettingsItem.Toggle() { object DoHToggle : BaseSettingsItem.Toggle() {

View File

@@ -16,8 +16,8 @@ import com.topjohnwu.magisk.core.isRunningAsStub
import com.topjohnwu.magisk.core.tasks.HideAPK import com.topjohnwu.magisk.core.tasks.HideAPK
import com.topjohnwu.magisk.databinding.bindExtra import com.topjohnwu.magisk.databinding.bindExtra
import com.topjohnwu.magisk.events.AddHomeIconEvent import com.topjohnwu.magisk.events.AddHomeIconEvent
import com.topjohnwu.magisk.events.BiometricEvent
import com.topjohnwu.magisk.events.SnackbarEvent import com.topjohnwu.magisk.events.SnackbarEvent
import com.topjohnwu.magisk.events.dialog.BiometricEvent
import com.topjohnwu.magisk.ktx.activity import com.topjohnwu.magisk.ktx.activity
import com.topjohnwu.magisk.utils.Utils import com.topjohnwu.magisk.utils.Utils
import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.Shell
@@ -53,7 +53,7 @@ class SettingsViewModel : BaseViewModel(), BaseSettingsItem.Handler {
AppSettings, AppSettings,
UpdateChannel, UpdateChannelUrl, DoHToggle, UpdateChecker, DownloadPath UpdateChannel, UpdateChannelUrl, DoHToggle, UpdateChecker, DownloadPath
)) ))
if (Build.VERSION.SDK_INT >= 22 && Info.env.isActive && Const.USER_ID == 0) { if (Info.env.isActive && Const.USER_ID == 0) {
if (hidden) list.add(Restore) else list.add(Hide) if (hidden) list.add(Restore) else list.add(Hide)
} }
@@ -75,15 +75,11 @@ class SettingsViewModel : BaseViewModel(), BaseSettingsItem.Handler {
Tapjack, Biometrics, AccessMode, MultiuserMode, MountNamespaceMode, Tapjack, Biometrics, AccessMode, MultiuserMode, MountNamespaceMode,
AutomaticResponse, RequestTimeout, SUNotification AutomaticResponse, RequestTimeout, SUNotification
)) ))
if (Build.VERSION.SDK_INT < 23) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
// Biometric is only available on 6.0+
list.remove(Biometrics)
}
if (Build.VERSION.SDK_INT < 26) {
// Re-authenticate is not feasible on 8.0+ // Re-authenticate is not feasible on 8.0+
list.add(Reauthenticate) list.add(Reauthenticate)
} }
if (Build.VERSION.SDK_INT >= 31) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
// Can hide overlay windows on 12.0+ // Can hide overlay windows on 12.0+
list.remove(Tapjack) list.remove(Tapjack)
} }
@@ -95,6 +91,7 @@ class SettingsViewModel : BaseViewModel(), BaseSettingsItem.Handler {
override fun onItemPressed(view: View, item: BaseSettingsItem, andThen: () -> Unit) { override fun onItemPressed(view: View, item: BaseSettingsItem, andThen: () -> Unit) {
when (item) { when (item) {
DownloadPath -> withExternalRW(andThen) DownloadPath -> withExternalRW(andThen)
UpdateChecker -> withPostNotificationPermission(andThen)
Biometrics -> authenticate(andThen) Biometrics -> authenticate(andThen)
Theme -> SettingsFragmentDirections.actionSettingsFragmentToThemeFragment().navigate() Theme -> SettingsFragmentDirections.actionSettingsFragmentToThemeFragment().navigate()
DenyListConfig -> SettingsFragmentDirections.actionSettingsFragmentToDenyFragment().navigate() DenyListConfig -> SettingsFragmentDirections.actionSettingsFragmentToDenyFragment().navigate()

View File

@@ -5,8 +5,9 @@ import androidx.databinding.Bindable
import com.topjohnwu.magisk.BR import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.model.su.SuPolicy import com.topjohnwu.magisk.core.model.su.SuPolicy
import com.topjohnwu.magisk.databinding.ObservableDiffRvItem import com.topjohnwu.magisk.databinding.DiffItem
import com.topjohnwu.magisk.databinding.RvContainer import com.topjohnwu.magisk.databinding.ItemWrapper
import com.topjohnwu.magisk.databinding.ObservableRvItem
import com.topjohnwu.magisk.databinding.set import com.topjohnwu.magisk.databinding.set
class PolicyRvItem( class PolicyRvItem(
@@ -16,7 +17,7 @@ class PolicyRvItem(
private val isSharedUid: Boolean, private val isSharedUid: Boolean,
val icon: Drawable, val icon: Drawable,
val appName: String val appName: String
) : ObservableDiffRvItem<PolicyRvItem>(), RvContainer<SuPolicy> { ) : ObservableRvItem(), DiffItem<PolicyRvItem>, ItemWrapper<SuPolicy> {
override val layoutRes = R.layout.item_policy_md2 override val layoutRes = R.layout.item_policy_md2
@@ -36,6 +37,7 @@ class PolicyRvItem(
var isEnabled var isEnabled
get() = item.policy == SuPolicy.ALLOW get() = item.policy == SuPolicy.ALLOW
set(value) = setImpl(value, isEnabled) { set(value) = setImpl(value, isEnabled) {
notifyPropertyChanged(BR.enabled)
viewModel.togglePolicy(this, value) viewModel.togglePolicy(this, value)
} }

View File

@@ -16,9 +16,9 @@ import com.topjohnwu.magisk.core.model.su.SuPolicy
import com.topjohnwu.magisk.core.utils.BiometricHelper import com.topjohnwu.magisk.core.utils.BiometricHelper
import com.topjohnwu.magisk.core.utils.currentLocale import com.topjohnwu.magisk.core.utils.currentLocale
import com.topjohnwu.magisk.databinding.* import com.topjohnwu.magisk.databinding.*
import com.topjohnwu.magisk.dialog.SuperuserRevokeDialog
import com.topjohnwu.magisk.events.BiometricEvent
import com.topjohnwu.magisk.events.SnackbarEvent import com.topjohnwu.magisk.events.SnackbarEvent
import com.topjohnwu.magisk.events.dialog.BiometricEvent
import com.topjohnwu.magisk.events.dialog.SuperuserRevokeDialog
import com.topjohnwu.magisk.ktx.getLabel import com.topjohnwu.magisk.ktx.getLabel
import com.topjohnwu.magisk.utils.Utils import com.topjohnwu.magisk.utils.Utils
import com.topjohnwu.magisk.utils.asText import com.topjohnwu.magisk.utils.asText
@@ -34,9 +34,9 @@ class SuperuserViewModel(
private val itemNoData = TextItem(R.string.superuser_policy_none) private val itemNoData = TextItem(R.string.superuser_policy_none)
private val itemsHelpers = ObservableArrayList<TextItem>() private val itemsHelpers = ObservableArrayList<TextItem>()
private val itemsPolicies = diffListOf<PolicyRvItem>() private val itemsPolicies = DiffObservableList<PolicyRvItem>()
val items = MergeObservableList<AnyDiffRvItem>() val items = MergeObservableList<RvItem>()
.insertList(itemsHelpers) .insertList(itemsHelpers)
.insertList(itemsPolicies) .insertList(itemsPolicies)
val extraBindings = bindExtra { val extraBindings = bindExtra {
@@ -54,7 +54,7 @@ class SuperuserViewModel(
return return
} }
loading = true loading = true
val (policies, diff) = withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
db.deleteOutdated() db.deleteOutdated()
db.delete(AppContext.applicationInfo.uid) db.delete(AppContext.applicationInfo.uid)
val policies = ArrayList<PolicyRvItem>() val policies = ArrayList<PolicyRvItem>()
@@ -91,9 +91,8 @@ class SuperuserViewModel(
{ it.appName.lowercase(currentLocale) }, { it.appName.lowercase(currentLocale) },
{ it.packageName } { it.packageName }
)) ))
policies to itemsPolicies.calculateDiff(policies) itemsPolicies.update(policies)
} }
itemsPolicies.update(policies, diff)
if (itemsPolicies.isNotEmpty()) if (itemsPolicies.isNotEmpty())
itemsHelpers.clear() itemsHelpers.clear()
else if (itemsHelpers.isEmpty()) else if (itemsHelpers.isEmpty())
@@ -117,10 +116,7 @@ class SuperuserViewModel(
onSuccess { updateState() } onSuccess { updateState() }
}.publish() }.publish()
} else { } else {
SuperuserRevokeDialog { SuperuserRevokeDialog(item.title) { updateState() }.show()
appName = item.title
onSuccess { updateState() }
}.publish()
} }
} }

View File

@@ -27,9 +27,9 @@ import com.topjohnwu.magisk.core.model.su.SuPolicy.Companion.DENY
import com.topjohnwu.magisk.core.su.SuRequestHandler import com.topjohnwu.magisk.core.su.SuRequestHandler
import com.topjohnwu.magisk.core.utils.BiometricHelper import com.topjohnwu.magisk.core.utils.BiometricHelper
import com.topjohnwu.magisk.databinding.set import com.topjohnwu.magisk.databinding.set
import com.topjohnwu.magisk.events.BiometricEvent
import com.topjohnwu.magisk.events.DieEvent import com.topjohnwu.magisk.events.DieEvent
import com.topjohnwu.magisk.events.ShowUIEvent import com.topjohnwu.magisk.events.ShowUIEvent
import com.topjohnwu.magisk.events.dialog.BiometricEvent
import com.topjohnwu.magisk.ktx.getLabel import com.topjohnwu.magisk.ktx.getLabel
import com.topjohnwu.magisk.utils.TextHolder import com.topjohnwu.magisk.utils.TextHolder
import com.topjohnwu.magisk.utils.Utils import com.topjohnwu.magisk.utils.Utils
@@ -189,15 +189,15 @@ class SuRequestViewModel(
// Invisible for accessibility services // Invisible for accessibility services
object EmptyAccessibilityDelegate : View.AccessibilityDelegate() { object EmptyAccessibilityDelegate : View.AccessibilityDelegate() {
override fun sendAccessibilityEvent(host: View?, eventType: Int) {} override fun sendAccessibilityEvent(host: View, eventType: Int) {}
override fun performAccessibilityAction(host: View?, action: Int, args: Bundle?) = true override fun performAccessibilityAction(host: View, action: Int, args: Bundle?) = true
override fun sendAccessibilityEventUnchecked(host: View?, event: AccessibilityEvent?) {} override fun sendAccessibilityEventUnchecked(host: View, event: AccessibilityEvent) {}
override fun dispatchPopulateAccessibilityEvent(host: View?, event: AccessibilityEvent?) = true override fun dispatchPopulateAccessibilityEvent(host: View, event: AccessibilityEvent) = true
override fun onPopulateAccessibilityEvent(host: View?, event: AccessibilityEvent?) {} override fun onPopulateAccessibilityEvent(host: View, event: AccessibilityEvent) {}
override fun onInitializeAccessibilityEvent(host: View?, event: AccessibilityEvent?) {} override fun onInitializeAccessibilityEvent(host: View, event: AccessibilityEvent) {}
override fun onInitializeAccessibilityNodeInfo(host: View, info: AccessibilityNodeInfo) {} override fun onInitializeAccessibilityNodeInfo(host: View, info: AccessibilityNodeInfo) {}
override fun addExtraDataToAccessibilityNodeInfo(host: View, info: AccessibilityNodeInfo, extraDataKey: String, arguments: Bundle?) {} override fun addExtraDataToAccessibilityNodeInfo(host: View, info: AccessibilityNodeInfo, extraDataKey: String, arguments: Bundle?) {}
override fun onRequestSendAccessibilityEvent(host: ViewGroup?, child: View?, event: AccessibilityEvent?): Boolean = false override fun onRequestSendAccessibilityEvent(host: ViewGroup, child: View, event: AccessibilityEvent): Boolean = false
override fun getAccessibilityNodeProvider(host: View?): AccessibilityNodeProvider? = null override fun getAccessibilityNodeProvider(host: View): AccessibilityNodeProvider? = null
} }
} }

View File

@@ -2,8 +2,8 @@ package com.topjohnwu.magisk.ui.theme
import com.topjohnwu.magisk.arch.BaseViewModel import com.topjohnwu.magisk.arch.BaseViewModel
import com.topjohnwu.magisk.core.Config import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.dialog.DarkThemeDialog
import com.topjohnwu.magisk.events.RecreateEvent import com.topjohnwu.magisk.events.RecreateEvent
import com.topjohnwu.magisk.events.dialog.DarkThemeDialog
import com.topjohnwu.magisk.view.TappableHeadlineItem import com.topjohnwu.magisk.view.TappableHeadlineItem
class ThemeViewModel : BaseViewModel(), TappableHeadlineItem.Listener { class ThemeViewModel : BaseViewModel(), TappableHeadlineItem.Listener {
@@ -11,7 +11,7 @@ class ThemeViewModel : BaseViewModel(), TappableHeadlineItem.Listener {
val themeHeadline = TappableHeadlineItem.ThemeMode val themeHeadline = TappableHeadlineItem.ThemeMode
override fun onItemPressed(item: TappableHeadlineItem) = when (item) { override fun onItemPressed(item: TappableHeadlineItem) = when (item) {
is TappableHeadlineItem.ThemeMode -> DarkThemeDialog().publish() is TappableHeadlineItem.ThemeMode -> DarkThemeDialog().show()
} }
fun saveTheme(theme: Theme) { fun saveTheme(theme: Theme) {

View File

@@ -1,6 +1,6 @@
package com.topjohnwu.magisk.view package com.topjohnwu.magisk.view
import android.content.Context import android.app.Activity
import android.content.DialogInterface import android.content.DialogInterface
import android.content.res.ColorStateList import android.content.res.ColorStateList
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
@@ -21,22 +21,33 @@ import com.google.android.material.color.MaterialColors
import com.google.android.material.shape.MaterialShapeDrawable import com.google.android.material.shape.MaterialShapeDrawable
import com.topjohnwu.magisk.BR import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.databinding.* import com.topjohnwu.magisk.core.base.BaseActivity
import com.topjohnwu.magisk.databinding.DialogMagiskBaseBinding
import com.topjohnwu.magisk.databinding.DiffItem
import com.topjohnwu.magisk.databinding.ItemWrapper
import com.topjohnwu.magisk.databinding.ObservableHost
import com.topjohnwu.magisk.databinding.RvItem
import com.topjohnwu.magisk.databinding.bindExtra
import com.topjohnwu.magisk.databinding.set
import com.topjohnwu.magisk.databinding.setAdapter
import com.topjohnwu.magisk.view.MagiskDialog.DialogClickListener import com.topjohnwu.magisk.view.MagiskDialog.DialogClickListener
typealias DialogButtonClickListener = (DialogInterface) -> Unit typealias DialogButtonClickListener = (DialogInterface) -> Unit
class MagiskDialog( class MagiskDialog(
context: Context, theme: Int = 0 context: Activity, theme: Int = 0
) : AppCompatDialog(context, theme) { ) : AppCompatDialog(context, theme) {
private val binding: DialogMagiskBaseBinding = private val binding: DialogMagiskBaseBinding =
DialogMagiskBaseBinding.inflate(LayoutInflater.from(context)) DialogMagiskBaseBinding.inflate(LayoutInflater.from(context))
private val data = Data() private val data = Data()
val activity: BaseActivity get() = ownerActivity as BaseActivity
init { init {
binding.setVariable(BR.data, data) binding.setVariable(BR.data, data)
setCancelable(true) setCancelable(true)
setOwnerActivity(context)
} }
inner class Data : ObservableHost { inner class Data : ObservableHost {
@@ -162,7 +173,7 @@ class MagiskDialog(
class DialogItem( class DialogItem(
override val item: CharSequence, override val item: CharSequence,
val position: Int val position: Int
) : DiffRvItem<DialogItem>(), RvContainer<CharSequence> { ) : RvItem(), DiffItem<DialogItem>, ItemWrapper<CharSequence> {
override val layoutRes = R.layout.item_list_single_line override val layoutRes = R.layout.item_list_single_line
} }

View File

@@ -5,8 +5,7 @@ import android.app.Notification
import android.app.NotificationChannel import android.app.NotificationChannel
import android.app.NotificationManager import android.app.NotificationManager
import android.app.PendingIntent import android.app.PendingIntent
import android.content.Context import android.os.Build
import android.content.Intent
import android.os.Build.VERSION.SDK_INT import android.os.Build.VERSION.SDK_INT
import androidx.core.content.getSystemService import androidx.core.content.getSystemService
import androidx.core.graphics.drawable.toIcon import androidx.core.graphics.drawable.toIcon
@@ -15,6 +14,7 @@ import com.topjohnwu.magisk.core.di.AppContext
import com.topjohnwu.magisk.core.download.DownloadService import com.topjohnwu.magisk.core.download.DownloadService
import com.topjohnwu.magisk.core.download.Subject import com.topjohnwu.magisk.core.download.Subject
import com.topjohnwu.magisk.ktx.getBitmap import com.topjohnwu.magisk.ktx.getBitmap
import com.topjohnwu.magisk.ktx.selfLaunchIntent
import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicInteger
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
@@ -22,83 +22,82 @@ object Notifications {
val mgr by lazy { AppContext.getSystemService<NotificationManager>()!! } val mgr by lazy { AppContext.getSystemService<NotificationManager>()!! }
private const val APP_UPDATED_NOTIFICATION_ID = 4 private const val APP_UPDATED_ID = 4
private const val APP_UPDATE_NOTIFICATION_ID = 5 private const val APP_UPDATE_AVAILABLE_ID = 5
private const val UPDATE_CHANNEL = "update" private const val UPDATE_CHANNEL = "update"
private const val PROGRESS_CHANNEL = "progress" private const val PROGRESS_CHANNEL = "progress"
private const val UPDATED_CHANNEL = "updated" private const val UPDATED_CHANNEL = "updated"
private val nextId = AtomicInteger(APP_UPDATE_NOTIFICATION_ID) private val nextId = AtomicInteger(APP_UPDATE_AVAILABLE_ID)
fun setup(context: Context) { fun setup() {
if (SDK_INT >= 26) { AppContext.apply {
val channel = NotificationChannel(UPDATE_CHANNEL, if (SDK_INT >= Build.VERSION_CODES.O) {
context.getString(R.string.update_channel), NotificationManager.IMPORTANCE_DEFAULT) val channel = NotificationChannel(UPDATE_CHANNEL,
val channel2 = NotificationChannel(PROGRESS_CHANNEL, getString(R.string.update_channel), NotificationManager.IMPORTANCE_DEFAULT)
context.getString(R.string.progress_channel), NotificationManager.IMPORTANCE_LOW) val channel2 = NotificationChannel(PROGRESS_CHANNEL,
val channel3 = NotificationChannel(UPDATED_CHANNEL, getString(R.string.progress_channel), NotificationManager.IMPORTANCE_LOW)
context.getString(R.string.updated_channel), NotificationManager.IMPORTANCE_HIGH) val channel3 = NotificationChannel(UPDATED_CHANNEL,
mgr.createNotificationChannels(listOf(channel, channel2, channel3)) getString(R.string.updated_channel), NotificationManager.IMPORTANCE_HIGH)
mgr.createNotificationChannels(listOf(channel, channel2, channel3))
}
} }
} }
fun selfLaunchIntent(context: Context): Intent {
val pm = context.packageManager
val intent = pm.getLaunchIntentForPackage(context.packageName)!!
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
return intent
}
@SuppressLint("InlinedApi") @SuppressLint("InlinedApi")
fun updateDone(context: Context) { fun updateDone() {
setup(context) AppContext.apply {
val flag = PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT val flag = PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
val pending = PendingIntent.getActivity(context, 0, selfLaunchIntent(context), flag) val pending = PendingIntent.getActivity(this, 0, selfLaunchIntent(), flag)
val builder = if (SDK_INT >= 26) { val builder = if (SDK_INT >= Build.VERSION_CODES.O) {
Notification.Builder(context, UPDATED_CHANNEL) Notification.Builder(this, UPDATED_CHANNEL)
.setSmallIcon(context.getBitmap(R.drawable.ic_magisk_outline).toIcon()) .setSmallIcon(getBitmap(R.drawable.ic_magisk_outline).toIcon())
} else { } else {
Notification.Builder(context).setPriority(Notification.PRIORITY_HIGH) Notification.Builder(this).setPriority(Notification.PRIORITY_HIGH)
.setSmallIcon(R.drawable.ic_magisk_outline) .setSmallIcon(R.drawable.ic_magisk_outline)
}
.setContentIntent(pending)
.setContentTitle(getText(R.string.updated_title))
.setContentText(getText(R.string.updated_text))
.setAutoCancel(true)
mgr.notify(APP_UPDATED_ID, builder.build())
} }
.setContentIntent(pending)
.setContentTitle(context.getText(R.string.updated_title))
.setContentText(context.getText(R.string.updated_text))
.setAutoCancel(true)
mgr.notify(APP_UPDATED_NOTIFICATION_ID, builder.build())
} }
fun updateAvailable(context: Context) { fun updateAvailable() {
val intent = DownloadService.getPendingIntent(context, Subject.App()) AppContext.apply {
val intent = DownloadService.getPendingIntent(this, Subject.App())
val bitmap = getBitmap(R.drawable.ic_magisk_outline)
val builder = if (SDK_INT >= Build.VERSION_CODES.O) {
Notification.Builder(this, UPDATE_CHANNEL)
.setSmallIcon(bitmap.toIcon())
} else {
Notification.Builder(this)
.setSmallIcon(R.drawable.ic_magisk_outline)
}
.setLargeIcon(bitmap)
.setContentTitle(getString(R.string.magisk_update_title))
.setContentText(getString(R.string.manager_download_install))
.setAutoCancel(true)
.setContentIntent(intent)
val bitmap = context.getBitmap(R.drawable.ic_magisk_outline) mgr.notify(APP_UPDATE_AVAILABLE_ID, builder.build())
val builder = if (SDK_INT >= 26) {
Notification.Builder(context, UPDATE_CHANNEL)
.setSmallIcon(bitmap.toIcon())
} else {
Notification.Builder(context)
.setSmallIcon(R.drawable.ic_magisk_outline)
} }
.setLargeIcon(bitmap)
.setContentTitle(context.getString(R.string.magisk_update_title))
.setContentText(context.getString(R.string.manager_download_install))
.setAutoCancel(true)
.setContentIntent(intent)
mgr.notify(APP_UPDATE_NOTIFICATION_ID, builder.build())
} }
fun progress(context: Context, title: CharSequence): Notification.Builder { fun startProgress(title: CharSequence): Notification.Builder {
val builder = if (SDK_INT >= 26) { val builder = if (SDK_INT >= Build.VERSION_CODES.O) {
Notification.Builder(context, PROGRESS_CHANNEL) Notification.Builder(AppContext, PROGRESS_CHANNEL)
} else { } else {
Notification.Builder(context).setPriority(Notification.PRIORITY_LOW) Notification.Builder(AppContext).setPriority(Notification.PRIORITY_LOW)
} }
.setSmallIcon(android.R.drawable.stat_sys_download) .setSmallIcon(android.R.drawable.stat_sys_download)
.setContentTitle(title) .setContentTitle(title)
.setProgress(0, 0, true) .setProgress(0, 0, true)
.setOngoing(true) .setOngoing(true)
if (SDK_INT >= Build.VERSION_CODES.S)
builder.setForegroundServiceBehavior(Notification.FOREGROUND_SERVICE_IMMEDIATE)
return builder return builder
} }

View File

@@ -19,7 +19,7 @@ import com.topjohnwu.magisk.utils.Utils
object Shortcuts { object Shortcuts {
fun setupDynamic(context: Context) { fun setupDynamic(context: Context) {
if (Build.VERSION.SDK_INT >= 25) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
val manager = context.getSystemService<ShortcutManager>() ?: return val manager = context.getSystemService<ShortcutManager>() ?: return
manager.dynamicShortcuts = getShortCuts(context) manager.dynamicShortcuts = getShortCuts(context)
} }
@@ -36,13 +36,12 @@ object Shortcuts {
} }
private fun Context.getIconCompat(id: Int): IconCompat { private fun Context.getIconCompat(id: Int): IconCompat {
return if (Build.VERSION.SDK_INT >= 26) return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
IconCompat.createWithAdaptiveBitmap(getBitmap(id)) IconCompat.createWithAdaptiveBitmap(getBitmap(id))
else else
IconCompat.createWithBitmap(getBitmap(id)) IconCompat.createWithBitmap(getBitmap(id))
} }
@RequiresApi(api = 23)
private fun Context.getIcon(id: Int) = getIconCompat(id).toIcon(this) private fun Context.getIcon(id: Int) = getIconCompat(id).toIcon(this)
@RequiresApi(api = 25) @RequiresApi(api = 25)

View File

@@ -1,9 +1,10 @@
package com.topjohnwu.magisk.view package com.topjohnwu.magisk.view
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.databinding.DiffRvItem import com.topjohnwu.magisk.databinding.DiffItem
import com.topjohnwu.magisk.databinding.RvItem
sealed class TappableHeadlineItem : DiffRvItem<TappableHeadlineItem>() { sealed class TappableHeadlineItem : RvItem(), DiffItem<TappableHeadlineItem> {
abstract val title: Int abstract val title: Int
abstract val icon: Int abstract val icon: Int

View File

@@ -1,10 +1,10 @@
package com.topjohnwu.magisk.view package com.topjohnwu.magisk.view
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.databinding.DiffRvItem import com.topjohnwu.magisk.databinding.DiffItem
import com.topjohnwu.magisk.databinding.ItemWrapper
import com.topjohnwu.magisk.databinding.RvItem
class TextItem(val text: Int) : DiffRvItem<TextItem>() { class TextItem(override val item: Int) : RvItem(), DiffItem<TextItem>, ItemWrapper<Int> {
override val layoutRes = R.layout.item_text override val layoutRes = R.layout.item_text
override fun contentSameAs(other: TextItem) = text == other.text
} }

View File

@@ -23,8 +23,6 @@ public class ConcealableBottomNavigationView extends BottomNavigationView {
}; };
private boolean isHidden; private boolean isHidden;
private int lastHeight = -1;
public ConcealableBottomNavigationView(@NonNull Context context) { public ConcealableBottomNavigationView(@NonNull Context context) {
this(context, null); this(context, null);
} }
@@ -42,14 +40,6 @@ public class ConcealableBottomNavigationView extends BottomNavigationView {
} }
private void recreateAnimator(int height) { private void recreateAnimator(int height) {
if (lastHeight == height) return;
lastHeight = height;
// End the current animation before setting a new one
// otherwise it crashes on Android 5.0
StateListAnimator lastAnimator = getStateListAnimator();
if (lastAnimator != null) lastAnimator.jumpToCurrentState();
Animator toHidden = ObjectAnimator.ofFloat(this, "translationY", height); Animator toHidden = ObjectAnimator.ofFloat(this, "translationY", height);
toHidden.setDuration(175); toHidden.setDuration(175);
toHidden.setInterpolator(new FastOutLinearInInterpolator()); toHidden.setInterpolator(new FastOutLinearInInterpolator());

View File

@@ -1,41 +0,0 @@
package com.topjohnwu.magisk.widget;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.ColorStateList;
import android.util.AttributeSet;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.TintTypedArray;
import androidx.cardview.widget.CardView;
import rikka.layoutinflater.view.LayoutInflaterFactory;
public class Pre23CardViewBackgroundColorFixLayoutInflaterListener implements LayoutInflaterFactory.OnViewCreatedListener {
private final static Pre23CardViewBackgroundColorFixLayoutInflaterListener INSTANCE = new Pre23CardViewBackgroundColorFixLayoutInflaterListener();
public static Pre23CardViewBackgroundColorFixLayoutInflaterListener getInstance() {
return INSTANCE;
}
@SuppressLint("RestrictedApi")
@Override
public void onViewCreated(@NonNull View view, @Nullable View parent, @NonNull String name, @NonNull Context context, @NonNull AttributeSet attrs) {
if (!(view instanceof CardView)) {
return;
}
TintTypedArray a = TintTypedArray.obtainStyledAttributes(context, attrs, androidx.cardview.R.styleable.CardView);
if (a.hasValue(androidx.cardview.R.styleable.CardView_cardBackgroundColor)) {
ColorStateList colorStateList = a.getColorStateList(androidx.cardview.R.styleable.CardView_cardBackgroundColor);
if (colorStateList != null) {
((CardView) view).setCardBackgroundColor(colorStateList);
}
}
a.recycle();
}
}

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