Compare commits

...

136 Commits
v22.0 ... v23.0

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

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

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

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

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

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

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

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

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

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

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

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

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

View File

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

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

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

View File

@@ -78,6 +78,10 @@ jobs:
- name: Build release
run: python build.py -vr all
- name: Refresh flag
run: touch gradle.properties
shell: bash
- name: Build debug
run: python build.py -v all

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

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

8
.gitmodules vendored
View File

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

View File

@@ -1,27 +1,24 @@
![](docs/images/logo.png)
![ZIP Downloads](https://img.shields.io/badge/dynamic/json?color=blue&label=ZIP%20Downloads&query=magisk&url=https%3A%2F%2Fraw.githubusercontent.com%2Ftopjohnwu%2Fmagisk_files%2Fcount%2Fcount.json&cacheSeconds=1800)
![APK Downloads](https://img.shields.io/badge/dynamic/json?color=green&label=APK%20Downloads&query=manager&url=https%3A%2F%2Fraw.githubusercontent.com%2Ftopjohnwu%2Fmagisk_files%2Fcount%2Fcount.json&cacheSeconds=1800)
[![Downloads](https://img.shields.io/badge/dynamic/json?color=green&label=Downloads&query=totalString&url=https%3A%2F%2Fraw.githubusercontent.com%2Ftopjohnwu%2Fmagisk-files%2Fcount%2Fcount.json&cacheSeconds=1800)](https://raw.githubusercontent.com/topjohnwu/magisk-files/count/count.json)
## Introduction
Magisk is a suite of open source tools for customizing Android, supporting devices higher than Android 4.2. It covers fundamental parts of Android customization: root, boot scripts, SELinux patches, AVB2.0 / dm-verity / forceencrypt removals etc.
Magisk is a suite of open source software for customizing Android, supporting devices higher than Android 5.0.<br>
Here are some feature highlights:
- **MagiskSU**: Provide root access to your device
- **MagiskSU**: Provide root access for applications
- **Magisk Modules**: Modify read-only partitions by installing modules
- **MagiskHide**: Hide Magisk from root detections / system integrity checks
- **MagiskBoot**: The most complete tool for unpacking and repacking Android boot images
## Downloads
Please note that the only source of official Magisk information and downloads is [Github](https://github.com/topjohnwu/Magisk/). Beware of any other websites.
[Github](https://github.com/topjohnwu/Magisk/) is the only source where you can get official Magisk information and downloads.
[![](https://img.shields.io/badge/Magisk%20Manager-v8.0.7-green)](https://github.com/topjohnwu/Magisk/releases/download/manager-v8.0.7/MagiskManager-v8.0.7.apk)
[![](https://img.shields.io/badge/Magisk%20Manager-Canary-red)](https://raw.githubusercontent.com/topjohnwu/magisk_files/canary/app-debug.apk)
<br>
[![](https://img.shields.io/badge/Magisk-v21.4-blue)](https://github.com/topjohnwu/Magisk/releases/tag/v21.4)
[![](https://img.shields.io/badge/Magisk%20Beta-v21.4-blue)](https://github.com/topjohnwu/Magisk/releases/tag/v21.4)
[![](https://img.shields.io/badge/Magisk-v22.1-blue)](https://github.com/topjohnwu/Magisk/releases/tag/v22.1)
[![](https://img.shields.io/badge/Magisk%20Beta-v22.1-blue)](https://github.com/topjohnwu/Magisk/releases/tag/v22.1)
[![](https://img.shields.io/badge/Magisk-Canary-red)](https://raw.githubusercontent.com/topjohnwu/magisk-files/canary/app-debug.apk)
## Useful Links
@@ -30,23 +27,13 @@ Please note that the only source of official Magisk information and downloads is
- [Magisk Documentation](https://topjohnwu.github.io/Magisk/)
- [Magisk Troubleshoot Wiki](https://www.didgeridoohan.com/magisk/HomePage) (by [@Didgeridoohan](https://github.com/Didgeridoohan))
## Android Version Support
- Android 4.2+: MagiskSU and Magisk Modules Only
- Android 4.4+: All core features available
- Android 6.0+: Guaranteed MagiskHide support
- Android 7.0+: Full MagiskHide protection
- Android 9.0+: Magisk Manager stealth mode
## Bug Reports
Canary Channels are cutting edge builds for those adventurous. To access canary builds, install the Canary Magisk Manager, switch to the Canary Channel in settings and upgrade.
**Only bug reports from Canary builds will be accepted.**
For installation issues, upload both boot image and install logs.<br>
For Magisk issues, upload boot logcat or dmesg.<br>
For Magisk Manager crashes, record and upload the logcat when the crash occurs.
For Magisk app crashes, record and upload the logcat when the crash occurs.
## Building and Development
@@ -68,7 +55,7 @@ For each action, use `-h` to access help (e.g. `./build.py all -h`)
## Translation Contributions
Default string resources for Magisk Manager and its stub APK are located here:
Default string resources for the Magisk app and its stub APK are located here:
- `app/src/main/res/values/strings.xml`
- `stub/src/main/res/values/strings.xml`

View File

@@ -16,20 +16,18 @@ kapt {
javacOptions {
option("-Xmaxerrs", 1000)
}
arguments {
arg("room.incremental", "true")
}
}
android {
defaultConfig {
applicationId = "com.topjohnwu.magisk"
vectorDrawables.useSupportLibrary = true
multiDexEnabled = true
versionName = Config.version
versionCode = Config.versionCode
ndk.abiFilters("armeabi-v7a", "arm64-v8a", "x86", "x86_64")
javaCompileOptions.annotationProcessorOptions.arguments(
mapOf("room.incremental" to "true")
)
}
buildTypes {
@@ -185,6 +183,8 @@ android.applicationVariants.all {
dependencies {
implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar"))))
implementation(kotlin("stdlib"))
// Some dependencies request JDK 8 stdlib, specify manually here to prevent version mismatch
implementation(kotlin("stdlib-jdk8"))
implementation(project(":app:shared"))
implementation("com.github.topjohnwu:jtar:1.0.0")
@@ -201,40 +201,31 @@ dependencies {
implementation("${bindingAdapter}:${vBAdapt}")
implementation("${bindingAdapter}-recyclerview:${vBAdapt}")
val vMarkwon = "4.6.1"
val vMarkwon = "4.6.2"
implementation("io.noties.markwon:core:${vMarkwon}")
implementation("io.noties.markwon:html:${vMarkwon}")
implementation("io.noties.markwon:image:${vMarkwon}")
implementation("com.caverock:androidsvg:1.4")
val vLibsu = "3.1.1"
val vLibsu = "3.1.2"
implementation("com.github.topjohnwu.libsu:core:${vLibsu}")
implementation("com.github.topjohnwu.libsu:io:${vLibsu}")
val vKoin = "2.1.6"
implementation("org.koin:koin-core:${vKoin}")
implementation("org.koin:koin-android:${vKoin}")
implementation("org.koin:koin-androidx-viewmodel:${vKoin}")
val vRetrofit = "2.9.0"
implementation("com.squareup.retrofit2:retrofit:${vRetrofit}")
implementation("com.squareup.retrofit2:converter-moshi:${vRetrofit}")
implementation("com.squareup.retrofit2:converter-scalars:${vRetrofit}")
val vOkHttp = "3.12.12"
implementation("com.squareup.okhttp3:okhttp") {
version {
strictly(vOkHttp)
}
}
val vOkHttp = "4.9.1"
implementation("com.squareup.okhttp3:okhttp:${vOkHttp}")
implementation("com.squareup.okhttp3:logging-interceptor:${vOkHttp}")
implementation("com.squareup.okhttp3:okhttp-dnsoverhttps:${vOkHttp}")
val vMoshi = "1.11.0"
val vMoshi = "1.12.0"
implementation("com.squareup.moshi:moshi:${vMoshi}")
kapt("com.squareup.moshi:moshi-kotlin-codegen:${vMoshi}")
val vRoom = "2.3.0-beta01"
val vRoom = "2.3.0"
implementation("androidx.room:room-runtime:${vRoom}")
implementation("androidx.room:room-ktx:${vRoom}")
kapt("androidx.room:room-compiler:${vRoom}")
@@ -248,11 +239,10 @@ dependencies {
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
implementation("androidx.browser:browser:1.3.0")
implementation("androidx.preference:preference:1.1.1")
implementation("androidx.recyclerview:recyclerview:1.1.0")
implementation("androidx.fragment:fragment-ktx:1.2.5")
implementation("androidx.recyclerview:recyclerview:1.2.0")
implementation("androidx.fragment:fragment-ktx:1.3.3")
implementation("androidx.work:work-runtime-ktx:2.5.0")
implementation("androidx.transition:transition:1.4.0")
implementation("androidx.multidex:multidex:2.0.1")
implementation("androidx.transition:transition:1.4.1")
implementation("androidx.core:core-ktx:1.3.2")
implementation("com.google.android.material:material:1.3.0")
}

View File

@@ -25,12 +25,14 @@
# Snet
-keepclassmembers class com.topjohnwu.magisk.ui.safetynet.SafetyNetHelper { *; }
-keep,allowobfuscation interface com.topjohnwu.magisk.ui.safetynet.SafetyNetHelper$Callback
-keepclassmembers class * implements com.topjohnwu.magisk.ui.safetynet.SafetyNetHelper$Callback {
void onResponse(org.json.JSONObject);
}
-keepclassmembers class * implements com.topjohnwu.magisk.ui.safetynet.SafetyNetHelper$Callback { *; }
# Stub
-keep class com.topjohnwu.magisk.core.App { <init>(java.lang.Object); }
-keepclassmembers class androidx.appcompat.app.AppCompatDelegateImpl {
boolean mActivityHandlesUiModeChecked;
boolean mActivityHandlesUiMode;
}
# Strip Timber verbose and debug logging
-assumenosideeffects class timber.log.Timber$Tree {
@@ -42,7 +44,12 @@
-repackageclasses 'a'
-allowaccessmodification
# QOL
-dontnote **
-dontwarn com.caverock.androidsvg.**
-dontwarn ru.noties.markwon.**
-dontwarn org.bouncycastle.jsse.BCSSLParameters
-dontwarn org.bouncycastle.jsse.BCSSLSocket
-dontwarn org.bouncycastle.jsse.provider.BouncyCastleJsseProvider
-dontwarn org.commonmark.ext.gfm.strikethrough.Strikethrough
-dontwarn org.conscrypt.Conscrypt*
-dontwarn org.conscrypt.ConscryptHostnameVerifier
-dontwarn org.openjsse.javax.net.ssl.SSLParameters
-dontwarn org.openjsse.javax.net.ssl.SSLSocket
-dontwarn org.openjsse.net.ssl.OpenJSSE

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -15,6 +15,7 @@
<!-- Splash -->
<activity
android:name=".core.SplashActivity"
android:exported="true"
android:theme="@style/SplashTheme">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -49,10 +50,10 @@
android:exported="false">
<intent-filter>
<action android:name="android.intent.action.LOCALE_CHANGED" />
<action android:name="android.intent.action.UID_REMOVED" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.PACKAGE_REPLACED" />
<action android:name="android.intent.action.PACKAGE_FULLY_REMOVED" />
<data android:scheme="package" />
</intent-filter>

View File

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

View File

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

View File

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

View File

@@ -1,24 +1,21 @@
package com.topjohnwu.magisk.core
import android.annotation.SuppressLint
import android.app.Activity
import android.app.Application
import android.content.Context
import android.content.res.Configuration
import android.os.Bundle
import androidx.appcompat.app.AppCompatDelegate
import androidx.multidex.MultiDex
import androidx.work.WorkManager
import com.topjohnwu.magisk.BuildConfig
import com.topjohnwu.magisk.DynAPK
import com.topjohnwu.magisk.core.utils.AppShellInit
import com.topjohnwu.magisk.core.utils.BusyBoxInit
import com.topjohnwu.magisk.core.utils.IODispatcherExecutor
import com.topjohnwu.magisk.core.utils.updateConfig
import com.topjohnwu.magisk.di.koinModules
import com.topjohnwu.magisk.di.ServiceLocator
import com.topjohnwu.magisk.ktx.unwrap
import com.topjohnwu.superuser.Shell
import org.koin.android.ext.koin.androidContext
import org.koin.core.context.startKoin
import timber.log.Timber
import java.io.File
import kotlin.system.exitProcess
@@ -46,10 +43,6 @@ open class App() : Application() {
}
override fun attachBaseContext(base: Context) {
// Basic setup
if (BuildConfig.DEBUG)
MultiDex.install(base)
// Some context magic
val app: Application
val impl: Context
@@ -69,11 +62,7 @@ open class App() : Application() {
}.getOrNull() ?: info.nativeLibraryDir
Const.NATIVE_LIB_DIR = File(libDir)
// Normal startup
startKoin {
androidContext(wrapped)
modules(koinModules)
}
ServiceLocator.context = wrapped
AssetHack.init(impl)
app.registerActivityLifecycleCallbacks(ForegroundTracker)
WorkManager.initialize(impl.wrapJob(), androidx.work.Configuration.Builder().build())
@@ -91,6 +80,7 @@ open class App() : Application() {
}
}
@SuppressLint("StaticFieldLeak")
object ForegroundTracker : Application.ActivityLifecycleCallbacks {
@Volatile

View File

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

View File

@@ -8,18 +8,8 @@ import java.io.File
@Suppress("DEPRECATION")
object Const {
val CPU_ABI: String
val CPU_ABI_32: String
init {
if (Build.VERSION.SDK_INT >= 21) {
CPU_ABI = Build.SUPPORTED_ABIS[0]
CPU_ABI_32 = Build.SUPPORTED_32_BIT_ABIS.firstOrNull() ?: CPU_ABI
} else {
CPU_ABI = Build.CPU_ABI
CPU_ABI_32 = CPU_ABI
}
}
val CPU_ABI: String = Build.SUPPORTED_ABIS[0]
val CPU_ABI_32: String = Build.SUPPORTED_32_BIT_ABIS.firstOrNull() ?: CPU_ABI
// Paths
lateinit var MAGISKTMP: String
@@ -29,9 +19,9 @@ object Const {
const val MAGISK_LOG = "/cache/magisk.log"
// Versions
const val SNET_EXT_VER = 15
const val SNET_REVISION = "18ab78817087c337ae0edd1ecac38aec49217880"
const val BOOTCTL_REVISION = "18ab78817087c337ae0edd1ecac38aec49217880"
const val SNET_EXT_VER = 17
const val SNET_REVISION = "23.0"
const val BOOTCTL_REVISION = "22.0"
// Misc
val USER_ID = Process.myUid() / 100000
@@ -62,7 +52,7 @@ object Const {
const val GITHUB_RAW_URL = "https://raw.githubusercontent.com/"
const val GITHUB_API_URL = "https://api.github.com/"
const val GITHUB_PAGE_URL = "https://topjohnwu.github.io/magisk_files/"
const val GITHUB_PAGE_URL = "https://topjohnwu.github.io/magisk-files/"
const val JS_DELIVR_URL = "https://cdn.jsdelivr.net/gh/"
const val OFFICIAL_REPO = "https://magisk-modules-repo.github.io/submission/modules.json"
}

View File

@@ -129,7 +129,6 @@ val shouldKeepResources = listOf(
R.string.invalid_update_channel,
R.string.update_available,
R.string.safetynet_api_error,
R.raw.changelog,
R.drawable.ic_device,
R.drawable.ic_hide_select_md2,
R.drawable.ic_more,

View File

@@ -6,7 +6,7 @@ import com.topjohnwu.magisk.DynAPK
import com.topjohnwu.magisk.core.model.UpdateInfo
import com.topjohnwu.magisk.core.utils.net.NetworkObserver
import com.topjohnwu.magisk.data.repository.NetworkService
import com.topjohnwu.magisk.ktx.get
import com.topjohnwu.magisk.di.AppContext
import com.topjohnwu.magisk.ktx.getProperty
import com.topjohnwu.superuser.Shell
import com.topjohnwu.superuser.ShellUtils.fastCmd
@@ -43,7 +43,7 @@ object Info {
val isConnected by lazy {
ObservableBoolean(false).also { field ->
NetworkObserver.observe(get()) {
NetworkObserver.observe(AppContext) {
UiThreadHandler.run { field.set(it) }
}
}
@@ -66,7 +66,7 @@ object Info {
private fun loadState() = Env(
fastCmd("magisk -v").split(":".toRegex())[0],
runCatching { fastCmd("magisk -V").toInt() }.getOrDefault(-1),
Shell.su("magiskhide --status").exec().isSuccess
Shell.su("magiskhide status").exec().isSuccess
)
class Env(

View File

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

View File

@@ -7,8 +7,7 @@ import com.topjohnwu.magisk.BuildConfig.APPLICATION_ID
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.base.BaseActivity
import com.topjohnwu.magisk.core.tasks.HideAPK
import com.topjohnwu.magisk.data.repository.NetworkService
import com.topjohnwu.magisk.ktx.get
import com.topjohnwu.magisk.di.ServiceLocator
import com.topjohnwu.magisk.ui.MainActivity
import com.topjohnwu.magisk.view.MagiskDialog
import com.topjohnwu.magisk.view.Notifications
@@ -68,7 +67,7 @@ open class SplashActivity : BaseActivity() {
Shortcuts.setupDynamic(this)
// Pre-fetch network services
get<NetworkService>()
ServiceLocator.networkService
DONE = true
startActivity(redirect<MainActivity>())

View File

@@ -4,16 +4,14 @@ import android.annotation.SuppressLint
import android.content.Context
import androidx.work.*
import com.topjohnwu.magisk.BuildConfig
import com.topjohnwu.magisk.data.repository.NetworkService
import com.topjohnwu.magisk.di.ServiceLocator
import com.topjohnwu.magisk.view.Notifications
import org.koin.core.KoinComponent
import org.koin.core.inject
import java.util.concurrent.TimeUnit
class UpdateCheckService(context: Context, workerParams: WorkerParameters)
: CoroutineWorker(context, workerParams), KoinComponent {
: CoroutineWorker(context, workerParams) {
private val svc: NetworkService by inject()
private val svc get() = ServiceLocator.networkService
override suspend fun doWork(): Result {
return svc.fetchUpdate()?.run {

View File

@@ -7,6 +7,7 @@ import android.content.Intent
import android.content.pm.PackageManager
import android.content.res.Configuration
import android.os.Build
import android.os.Bundle
import android.widget.Toast
import androidx.annotation.CallSuper
import androidx.appcompat.app.AppCompatActivity
@@ -16,6 +17,7 @@ import androidx.core.content.ContextCompat
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.utils.currentLocale
import com.topjohnwu.magisk.core.wrap
import com.topjohnwu.magisk.ktx.reflectField
import com.topjohnwu.magisk.ktx.set
import com.topjohnwu.magisk.utils.Utils
import kotlin.random.Random
@@ -43,6 +45,15 @@ abstract class BaseActivity : AppCompatActivity() {
super.attachBaseContext(base.wrap(true))
}
override fun onCreate(savedInstanceState: Bundle?) {
// Overwrite private members to avoid nasty "false" stack traces being logged
val delegate = delegate
val clz = delegate.javaClass
clz.reflectField("mActivityHandlesUiModeChecked").set(delegate, true)
clz.reflectField("mActivityHandlesUiMode").set(delegate, false)
super.onCreate(savedInstanceState)
}
fun withPermission(permission: String, builder: PermissionRequestBuilder.() -> Unit) {
val request = PermissionRequestBuilder().apply(builder).build()
@@ -72,6 +83,7 @@ abstract class BaseActivity : AppCompatActivity() {
override fun onRequestPermissionsResult(
requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
var success = true
for (res in grantResults) {
if (res != PackageManager.PERMISSION_GRANTED) {

View File

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

View File

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

View File

@@ -9,15 +9,13 @@ import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.ForegroundTracker
import com.topjohnwu.magisk.core.base.BaseService
import com.topjohnwu.magisk.core.utils.ProgressInputStream
import com.topjohnwu.magisk.data.repository.NetworkService
import com.topjohnwu.magisk.di.ServiceLocator
import com.topjohnwu.magisk.view.Notifications
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import okhttp3.ResponseBody
import org.koin.android.ext.android.inject
import org.koin.core.KoinComponent
import timber.log.Timber
import java.io.IOException
import java.io.InputStream
@@ -25,13 +23,13 @@ import java.util.*
import kotlin.collections.HashMap
import kotlin.random.Random.Default.nextInt
abstract class BaseDownloader : BaseService(), KoinComponent {
abstract class BaseDownloader : BaseService() {
private val hasNotifications get() = notifications.isNotEmpty()
private val notifications = Collections.synchronizedMap(HashMap<Int, Notification.Builder>())
private val coroutineScope = CoroutineScope(Dispatchers.IO)
val service: NetworkService by inject()
val service get() = ServiceLocator.networkService
// -- Service overrides
@@ -174,10 +172,10 @@ abstract class BaseDownloader : BaseService(), KoinComponent {
// ---
companion object : KoinComponent {
companion object {
const val ACTION_KEY = "download_action"
private val progressBroadcast = MutableLiveData<Pair<Float, Subject>>()
private val progressBroadcast = MutableLiveData<Pair<Float, Subject>?>()
fun observeProgress(owner: LifecycleOwner, callback: (Float, Subject) -> Unit) {
progressBroadcast.value = null

View File

@@ -1,21 +1,22 @@
package com.topjohnwu.magisk.core.download
import android.annotation.SuppressLint
import android.app.Notification
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.os.Build
import androidx.core.net.toFile
import com.topjohnwu.magisk.arch.BaseUIActivity
import com.topjohnwu.magisk.core.ForegroundTracker
import com.topjohnwu.magisk.core.download.Action.Flash
import com.topjohnwu.magisk.core.download.Subject.Manager
import com.topjohnwu.magisk.core.download.Subject.Module
import com.topjohnwu.magisk.core.intent
import com.topjohnwu.magisk.ui.flash.FlashFragment
import com.topjohnwu.magisk.utils.APKInstall
import com.topjohnwu.superuser.internal.UiThreadHandler
import kotlin.random.Random.Default.nextInt
@SuppressLint("Registered")
open class DownloadService : BaseDownloader() {
private val context get() = this
@@ -26,7 +27,12 @@ open class DownloadService : BaseDownloader() {
}
private fun Module.onFinish(id: Int) = when (action) {
Flash -> FlashFragment.install(file, id)
Flash -> {
UiThreadHandler.run {
(ForegroundTracker.foreground as? BaseUIActivity<*, *>)
?.navigation?.navigate(FlashFragment.install(file, id))
}
}
else -> Unit
}

View File

@@ -1,6 +1,5 @@
package com.topjohnwu.magisk.core.download
import android.content.Context
import android.net.Uri
import android.os.Parcelable
import androidx.core.net.toUri
@@ -9,12 +8,12 @@ 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.utils.MediaStoreUtils
import com.topjohnwu.magisk.di.AppContext
import com.topjohnwu.magisk.ktx.cachedFile
import com.topjohnwu.magisk.ktx.get
import kotlinx.parcelize.IgnoredOnParcel
import kotlinx.parcelize.Parcelize
private fun cachedFile(name: String) = get<Context>().cachedFile(name).apply { delete() }.toUri()
private fun cachedFile(name: String) = AppContext.cachedFile(name).apply { delete() }.toUri()
sealed class Subject : Parcelable {

View File

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

View File

@@ -16,8 +16,7 @@ data class MagiskJson(
val version: String = "",
val versionCode: Int = -1,
val link: String = "",
val note: String = "",
val md5: String = ""
val note: String = ""
) : Parcelable
@Parcelize

View File

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

View File

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

View File

@@ -1,18 +1,16 @@
package com.topjohnwu.magisk.core.tasks
import android.content.Context
import android.net.Uri
import androidx.core.net.toFile
import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.displayName
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.inputStream
import com.topjohnwu.magisk.core.utils.unzip
import com.topjohnwu.magisk.di.AppContext
import com.topjohnwu.magisk.ktx.writeTo
import com.topjohnwu.superuser.Shell
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.koin.core.KoinComponent
import org.koin.core.inject
import timber.log.Timber
import java.io.File
import java.io.FileNotFoundException
@@ -22,10 +20,9 @@ open class FlashZip(
private val mUri: Uri,
private val console: MutableList<String>,
private val logs: MutableList<String>
): KoinComponent {
) {
private val context: Context by inject()
private val installDir = File(context.cacheDir, "flash")
private val installDir = File(AppContext.cacheDir, "flash")
private lateinit var zipFile: File
@Throws(IOException::class)
@@ -66,7 +63,7 @@ open class FlashZip(
console.add("- Installing ${mUri.displayName}")
return Shell.su("sh $installDir/update-binary dummy 1 \"$zipFile\"")
return Shell.su("sh $installDir/update-binary dummy 1 \'$zipFile\'")
.to(console, logs).exec().isSuccess
}

View File

@@ -14,8 +14,7 @@ import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.Provider
import com.topjohnwu.magisk.core.utils.AXML
import com.topjohnwu.magisk.core.utils.Keygen
import com.topjohnwu.magisk.data.repository.NetworkService
import com.topjohnwu.magisk.ktx.inject
import com.topjohnwu.magisk.di.ServiceLocator
import com.topjohnwu.magisk.ktx.writeTo
import com.topjohnwu.magisk.utils.APKInstall
import com.topjohnwu.magisk.utils.Utils
@@ -41,7 +40,7 @@ object HideAPK {
// Some arbitrary limit
const val MAX_LABEL_LENGTH = 32
private val svc: NetworkService by inject()
private val svc get() = ServiceLocator.networkService
private val Context.APK_URI get() = Provider.APK_URI(packageName)
private val Context.PREFS_URI get() = Provider.PREFS_URI(packageName)

View File

@@ -1,7 +1,7 @@
package com.topjohnwu.magisk.core.tasks
import android.content.Context
import android.net.Uri
import android.system.Os
import android.widget.Toast
import androidx.annotation.WorkerThread
import androidx.core.os.postDelayed
@@ -12,10 +12,8 @@ import com.topjohnwu.magisk.core.*
import com.topjohnwu.magisk.core.utils.MediaStoreUtils
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.inputStream
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream
import com.topjohnwu.magisk.data.repository.NetworkService
import com.topjohnwu.magisk.di.Protected
import com.topjohnwu.magisk.di.ServiceLocator
import com.topjohnwu.magisk.ktx.reboot
import com.topjohnwu.magisk.ktx.symlink
import com.topjohnwu.magisk.ktx.withStreams
import com.topjohnwu.magisk.ktx.writeTo
import com.topjohnwu.magisk.utils.Utils
@@ -34,8 +32,6 @@ import org.kamranzafar.jtar.TarEntry
import org.kamranzafar.jtar.TarHeader
import org.kamranzafar.jtar.TarInputStream
import org.kamranzafar.jtar.TarOutputStream
import org.koin.core.KoinComponent
import org.koin.core.inject
import timber.log.Timber
import java.io.*
import java.nio.ByteBuffer
@@ -46,14 +42,14 @@ import java.util.zip.ZipFile
abstract class MagiskInstallImpl protected constructor(
protected val console: MutableList<String> = NOPList.getInstance(),
private val logs: MutableList<String> = NOPList.getInstance()
) : KoinComponent {
) {
protected var installDir = File("xxx")
private lateinit var srcBoot: File
private val shell = Shell.getShell()
private val service: NetworkService by inject()
protected val context: Context by inject(Protected)
private val service get() = ServiceLocator.networkService
protected val context get() = ServiceLocator.deContext
private val useRootDir = shell.isRoot && Info.noDataExec
private fun findImage(): Boolean {
@@ -111,7 +107,7 @@ abstract class MagiskInstallImpl protected constructor(
} ?: emptyArray()
for (lib in libs) {
val name = lib.name.substring(3, lib.name.length - 3)
symlink(lib.path, "$installDir/$name")
Os.symlink(lib.path, "$installDir/$name")
}
}
@@ -216,6 +212,7 @@ abstract class MagiskInstallImpl protected constructor(
// Repack boot image to prevent auto restore
arrayOf(
"cd $installDir",
"chmod -R 755 .",
"./magiskboot unpack boot.img",
"./magiskboot repack boot.img",
"cat new-boot.img > boot.img",
@@ -255,7 +252,7 @@ abstract class MagiskInstallImpl protected constructor(
val alpha = "abcdefghijklmnopqrstuvwxyz"
val alphaNum = "$alpha${alpha.toUpperCase(Locale.ROOT)}0123456789"
val random = SecureRandom()
val filename = StringBuilder("magisk_patched_").run {
val filename = StringBuilder("magisk_patched-${BuildConfig.VERSION_CODE}_").run {
for (i in 1..5) {
append(alphaNum[random.nextInt(alphaNum.length)])
}
@@ -415,7 +412,22 @@ abstract class MagiskInstallImpl protected constructor(
@WorkerThread
protected abstract suspend fun operations(): Boolean
open suspend fun exec() = withContext(Dispatchers.IO) { operations() }
open suspend fun exec(): Boolean {
synchronized(Companion) {
if (haveActiveSession)
return false
haveActiveSession = true
}
val result = withContext(Dispatchers.IO) { operations() }
synchronized(Companion) {
haveActiveSession = false
}
return result
}
companion object {
private var haveActiveSession = false
}
}
abstract class MagiskInstaller(

View File

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

View File

@@ -8,8 +8,6 @@ import android.content.res.Resources
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.AssetHack
import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.ktx.langTagToLocale
import com.topjohnwu.magisk.ktx.toLangTag
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.util.*
@@ -40,7 +38,7 @@ withContext(Dispatchers.Default) {
// Then add all supported locales
addAll(Resources.getSystem().assets.locales)
}.map {
it.langTagToLocale()
Locale.forLanguageTag(it)
}.distinctBy {
res.updateLocale(it)
res.getString(compareId)
@@ -59,7 +57,7 @@ withContext(Dispatchers.Default) {
locales.forEach { locale ->
names.add(locale.getDisplayName(locale))
values.add(locale.toLangTag())
values.add(locale.toLanguageTag())
}
(names.toTypedArray() to values.toTypedArray()).also { cachedLocales = it }
@@ -79,7 +77,7 @@ fun refreshLocale() {
val localeConfig = Config.locale
currentLocale = when {
localeConfig.isEmpty() -> defaultLocale
else -> localeConfig.langTagToLocale()
else -> Locale.forLanguageTag(localeConfig)
}
Locale.setDefault(currentLocale)
AssetHack.resource.updateConfig()

View File

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

View File

@@ -78,7 +78,6 @@ class AppShellInit : BaseShellInit() {
fun getBool(name: String) = getVar(name).toBoolean()
shell.newJob().apply {
add("export API=${Build.VERSION.SDK_INT}")
add(context.rawResource(R.raw.manager))
if (shell.isRoot) {
add(context.assets.open("util_functions.sh"))

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,19 +0,0 @@
package com.topjohnwu.magisk.di
import android.content.Context
import androidx.preference.PreferenceManager
import com.topjohnwu.magisk.core.AssetHack
import com.topjohnwu.magisk.ktx.deviceProtectedContext
import org.koin.core.qualifier.named
import org.koin.dsl.module
val SUTimeout = named("su_timeout")
val Protected = named("protected")
val applicationModule = module {
factory { AssetHack.resource }
factory { get<Context>().packageManager }
factory(Protected) { get<Context>().deviceProtectedContext }
single(SUTimeout) { get<Context>(Protected).getSharedPreferences("su_timeout", 0) }
single { PreferenceManager.getDefaultSharedPreferences(get(Protected)) }
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,28 +1,25 @@
package com.topjohnwu.magisk.events.dialog
import android.content.Context
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.download.DownloadService
import com.topjohnwu.magisk.core.download.Subject
import com.topjohnwu.magisk.data.repository.NetworkService
import com.topjohnwu.magisk.di.AppContext
import com.topjohnwu.magisk.di.ServiceLocator
import com.topjohnwu.magisk.view.MagiskDialog
import org.koin.core.get
import org.koin.core.inject
import java.io.File
class ManagerInstallDialog : MarkDownDialog() {
private val svc: NetworkService by inject()
private val svc get() = ServiceLocator.networkService
override suspend fun getMarkdownText(): String {
val text = svc.fetchString(Info.remote.magisk.note)
// Cache the changelog
val context = get<Context>()
context.cacheDir.listFiles { _, name -> name.endsWith(".md") }.orEmpty().forEach {
AppContext.cacheDir.listFiles { _, name -> name.endsWith(".md") }.orEmpty().forEach {
it.delete()
}
File(context.cacheDir, "${Info.remote.magisk.versionCode}.md").writeText(text)
File(AppContext.cacheDir, "${Info.remote.magisk.versionCode}.md").writeText(text)
return text
}

View File

@@ -6,19 +6,15 @@ import androidx.annotation.CallSuper
import androidx.lifecycle.lifecycleScope
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.base.BaseActivity
import com.topjohnwu.magisk.di.ServiceLocator
import com.topjohnwu.magisk.view.MagiskDialog
import io.noties.markwon.Markwon
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.koin.core.KoinComponent
import org.koin.core.inject
import timber.log.Timber
import kotlin.coroutines.cancellation.CancellationException
abstract class MarkDownDialog : DialogEvent(), KoinComponent {
private val markwon: Markwon by inject()
abstract class MarkDownDialog : DialogEvent() {
abstract suspend fun getMarkdownText(): String
@@ -31,7 +27,7 @@ abstract class MarkDownDialog : DialogEvent(), KoinComponent {
val tv = view.findViewById<TextView>(R.id.md_txt)
withContext(Dispatchers.IO) {
try {
markwon.setMarkdown(tv, getMarkdownText())
ServiceLocator.markwon.setMarkdown(tv, getMarkdownText())
} catch (e: Exception) {
if (e is CancellationException)
throw e

View File

@@ -3,6 +3,7 @@ package com.topjohnwu.magisk.events.dialog
import android.app.ProgressDialog
import android.widget.Toast
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.arch.BaseUIActivity
import com.topjohnwu.magisk.ui.flash.FlashFragment
import com.topjohnwu.magisk.utils.Utils
import com.topjohnwu.magisk.view.MagiskDialog
@@ -41,7 +42,8 @@ class UninstallDialog : DialogEvent() {
}
private fun completeUninstall() {
FlashFragment.uninstall()
(dialog.ownerActivity as? BaseUIActivity<*, *>)
?.navigation?.navigate(FlashFragment.uninstall())
}
}

View File

@@ -22,7 +22,6 @@ import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.LayerDrawable
import android.net.Uri
import android.os.Build.VERSION.SDK_INT
import android.system.Os
import android.text.PrecomputedText
import android.view.View
import android.view.ViewGroup
@@ -57,26 +56,8 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import java.io.File
import java.lang.reflect.Method
import java.lang.reflect.Array as JArray
val packageName: String get() = get<Context>().packageName
private lateinit var osSymlink: Method
private lateinit var os: Any
fun symlink(oldPath: String, newPath: String) {
if (SDK_INT >= 21) {
Os.symlink(oldPath, newPath)
} else {
if (!::osSymlink.isInitialized) {
os = Class.forName("libcore.io.Libcore").getField("os").get(null)!!
osSymlink = os.javaClass.getMethod("symlink", String::class.java, String::class.java)
}
osSymlink.invoke(os, oldPath, newPath)
}
}
val ServiceInfo.isIsolated get() = (flags and FLAG_ISOLATED_PROCESS) != 0
@get:SuppressLint("InlinedApi")
@@ -264,8 +245,7 @@ fun Context.startEndToLeftRight(start: Int, end: Int): Pair<Int, Int> {
fun Context.openUrl(url: String) = Utils.openLink(this, url.toUri())
@Suppress("FunctionName")
inline fun <reified T> T.DynamicClassLoader(apk: File) =
inline fun <reified T> T.createClassLoader(apk: File) =
DynamicClassLoader(apk, T::class.java.classLoader)
fun Context.unwrap(): Context {
@@ -339,16 +319,6 @@ var TextView.precomputedText: CharSequence
set(value) {
val callback = tag as? Runnable
// Don't even bother pre 21
if (SDK_INT < 21) {
post {
text = value
isGone = false
callback?.run()
}
return
}
coroutineScope.launch(Dispatchers.IO) {
if (SDK_INT >= 29) {
// Internally PrecomputedTextCompat will use platform API on API 29+

View File

@@ -1,11 +1,11 @@
package com.topjohnwu.magisk.ktx
import android.os.Build
import androidx.collection.SparseArrayCompat
import timber.log.Timber
import java.io.File
import java.io.InputStream
import java.io.OutputStream
import java.lang.reflect.Field
import java.text.SimpleDateFormat
import java.util.*
import java.util.zip.ZipEntry
@@ -47,64 +47,8 @@ fun <T> MutableList<T>.synchronized() = Collections.synchronizedList(this)
fun <T> MutableSet<T>.synchronized() = Collections.synchronizedSet(this)
fun <K, V> MutableMap<K, V>.synchronized() = Collections.synchronizedMap(this)
fun String.langTagToLocale(): Locale {
if (Build.VERSION.SDK_INT >= 21) {
return Locale.forLanguageTag(this)
} else {
val tok = split("[-_]".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
if (tok.isEmpty()) {
return Locale("")
}
val language = when (tok[0]) {
"und" -> "" // Undefined
"fil" -> "tl" // Filipino
else -> tok[0]
}
if (language.length != 2 && language.length != 3)
return Locale("")
if (tok.size == 1)
return Locale(language)
val country = tok[1]
return if (country.length != 2 && country.length != 3) Locale(language)
else Locale(language, country)
}
}
fun Locale.toLangTag(): String {
if (Build.VERSION.SDK_INT >= 21) {
return toLanguageTag()
} else {
var language = language
var country = country
var variant = variant
when {
language.isEmpty() || !language.matches("\\p{Alpha}{2,8}".toRegex()) ->
language = "und" // Follow the Locale#toLanguageTag() implementation
language == "iw" -> language = "he" // correct deprecated "Hebrew"
language == "in" -> language = "id" // correct deprecated "Indonesian"
language == "ji" -> language = "yi" // correct deprecated "Yiddish"
}
// ensure valid country code, if not well formed, it's omitted
// variant subtags that begin with a letter must be at least 5 characters long
// ensure valid country code, if not well formed, it's omitted
if (!country.matches("\\p{Alpha}{2}|\\p{Digit}{3}".toRegex())) {
country = ""
}
// variant subtags that begin with a letter must be at least 5 characters long
if (!variant.matches("\\p{Alnum}{5,8}|\\p{Digit}\\p{Alnum}{3}".toRegex())) {
variant = ""
}
val tag = StringBuilder(language)
if (country.isNotEmpty())
tag.append('-').append(country)
if (variant.isNotEmpty())
tag.append('-').append(variant)
return tag.toString()
}
}
fun SimpleDateFormat.parseOrNull(date: String) =
runCatching { parse(date) }.onFailure { Timber.e(it) }.getOrNull()
fun Class<*>.reflectField(name: String): Field =
getDeclaredField(name).apply { isAccessible = true }

View File

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

View File

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

View File

@@ -2,7 +2,6 @@ package com.topjohnwu.magisk.ui
import android.content.Intent
import android.content.pm.ApplicationInfo
import android.os.Build
import android.os.Bundle
import android.view.MenuItem
import android.view.View
@@ -19,13 +18,13 @@ import com.topjohnwu.magisk.arch.BaseViewModel
import com.topjohnwu.magisk.arch.ReselectionTarget
import com.topjohnwu.magisk.core.*
import com.topjohnwu.magisk.databinding.ActivityMainMd2Binding
import com.topjohnwu.magisk.di.viewModel
import com.topjohnwu.magisk.ktx.startAnimations
import com.topjohnwu.magisk.ui.home.HomeFragmentDirections
import com.topjohnwu.magisk.utils.HideableBehavior
import com.topjohnwu.magisk.utils.Utils
import com.topjohnwu.magisk.view.MagiskDialog
import com.topjohnwu.magisk.view.Shortcuts
import org.koin.androidx.viewmodel.ext.android.viewModel
import java.io.File
class MainViewModel : BaseViewModel()
@@ -34,7 +33,7 @@ open class MainActivity : BaseUIActivity<MainViewModel, ActivityMainMd2Binding>(
override val layoutRes = R.layout.activity_main_md2
override val viewModel by viewModel<MainViewModel>()
override val navHost: Int = R.id.main_nav_host
override val navHostId: Int = R.id.main_nav_host
private var isRootFragment = true
@@ -123,10 +122,7 @@ open class MainActivity : BaseUIActivity<MainViewModel, ActivityMainMd2Binding>(
val topView = binding.mainToolbarWrapper
val bottomView = binding.mainBottomBar
if (
Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT &&
!binding.mainBottomBar.isAttachedToWindow
) {
if (!binding.mainBottomBar.isAttachedToWindow) {
binding.mainBottomBar.viewTreeObserver.addOnWindowAttachListener(object :
ViewTreeObserver.OnWindowAttachListener {
@@ -166,9 +162,9 @@ open class MainActivity : BaseUIActivity<MainViewModel, ActivityMainMd2Binding>(
private fun getScreen(name: String?): NavDirections? {
return when (name) {
Const.Nav.SUPERUSER -> HomeFragmentDirections.actionSuperuserFragment()
Const.Nav.HIDE -> HomeFragmentDirections.actionHideFragment()
Const.Nav.MODULES -> HomeFragmentDirections.actionModuleFragment()
Const.Nav.SUPERUSER -> MainDirections.actionSuperuserFragment()
Const.Nav.HIDE -> MainDirections.actionHideFragment()
Const.Nav.MODULES -> MainDirections.actionModuleFragment()
Const.Nav.SETTINGS -> HomeFragmentDirections.actionHomeFragmentToSettingsFragment()
else -> null
}

View File

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

View File

@@ -1,6 +1,5 @@
package com.topjohnwu.magisk.ui.flash
import android.net.Uri
import android.view.MenuItem
import androidx.databinding.Bindable
import androidx.lifecycle.LiveData
@@ -27,7 +26,7 @@ import com.topjohnwu.superuser.Shell
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class FlashViewModel(args: FlashFragmentArgs) : BaseViewModel() {
class FlashViewModel : BaseViewModel() {
@get:Bindable
var showReboot = Shell.rootAccess()
@@ -39,6 +38,7 @@ class FlashViewModel(args: FlashFragmentArgs) : BaseViewModel() {
val adapter = RvBindingAdapter<ConsoleItem>()
val items = diffListOf<ConsoleItem>()
val itemBinding = itemBindingOf<ConsoleItem>()
lateinit var args: FlashFragmentArgs
private val logItems = mutableListOf<String>().synchronized()
private val outItems = object : CallbackList<String>() {
@@ -49,14 +49,11 @@ class FlashViewModel(args: FlashFragmentArgs) : BaseViewModel() {
}
}
init {
fun startFlashing() {
val (action, uri, id) = args
if (id != -1)
Notifications.mgr.cancel(id)
startFlashing(action, uri)
}
private fun startFlashing(action: String, uri: Uri?) {
viewModelScope.launch {
val result = when (action) {
Const.Value.FLASH_ZIP -> {

View File

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

View File

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

View File

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

View File

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

View File

@@ -8,9 +8,9 @@ import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.arch.BaseUIFragment
import com.topjohnwu.magisk.core.download.BaseDownloader
import com.topjohnwu.magisk.databinding.FragmentHomeMd2Binding
import com.topjohnwu.magisk.di.viewModel
import com.topjohnwu.magisk.events.RebootEvent
import com.topjohnwu.superuser.Shell
import org.koin.androidx.viewmodel.ext.android.viewModel
class HomeFragment : BaseUIFragment<HomeViewModel, FragmentHomeMd2Binding>() {

View File

@@ -16,7 +16,7 @@ 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.utils.asTransitive
import com.topjohnwu.magisk.utils.asText
import com.topjohnwu.magisk.utils.set
import com.topjohnwu.superuser.Shell
import kotlinx.coroutines.launch
@@ -54,13 +54,13 @@ class HomeViewModel(
val magiskInstalledVersion get() = Info.env.run {
if (isActive)
"$magiskVersionString ($magiskVersionCode)".asTransitive()
"$magiskVersionString ($magiskVersionCode)".asText()
else
R.string.not_available.asTransitive()
R.string.not_available.asText()
}
@get:Bindable
var managerRemoteVersion = R.string.loading.asTransitive()
var managerRemoteVersion = R.string.loading.asText()
set(value) = set(value, field, { field = it }, BR.managerRemoteVersion)
val managerInstalledVersion = Info.stub?.let {
@@ -92,14 +92,14 @@ class HomeViewModel(
}
managerRemoteVersion =
"${magisk.version} (${magisk.versionCode}) (${stub.versionCode})".asTransitive()
"${magisk.version} (${magisk.versionCode}) (${stub.versionCode})".asText()
launch {
ensureEnv()
}
} ?: {
state = State.LOADING_FAILED
managerRemoteVersion = R.string.not_available.asTransitive()
managerRemoteVersion = R.string.not_available.asText()
}()
}
@@ -127,11 +127,11 @@ class HomeViewModel(
}
fun onMagiskPressed() = withExternalRW {
HomeFragmentDirections.actionHomeFragmentToInstallFragment().publish()
HomeFragmentDirections.actionHomeFragmentToInstallFragment().navigate()
}
fun onSafetyNetPressed() =
HomeFragmentDirections.actionHomeFragmentToSafetynetFragment().publish()
HomeFragmentDirections.actionHomeFragmentToSafetynetFragment().navigate()
fun hideNotice() {
Config.safetyNotice = false

View File

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

View File

@@ -8,8 +8,8 @@ import androidx.lifecycle.viewModelScope
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.arch.BaseUIFragment
import com.topjohnwu.magisk.databinding.FragmentInstallMd2Binding
import com.topjohnwu.magisk.di.viewModel
import com.topjohnwu.magisk.ktx.coroutineScope
import org.koin.androidx.viewmodel.ext.android.viewModel
class InstallFragment : BaseUIFragment<InstallViewModel, FragmentInstallMd2Binding>() {

View File

@@ -1,7 +1,6 @@
package com.topjohnwu.magisk.ui.install
import android.app.Activity
import android.content.Context
import android.net.Uri
import androidx.databinding.Bindable
import androidx.lifecycle.viewModelScope
@@ -12,13 +11,13 @@ import com.topjohnwu.magisk.arch.BaseViewModel
import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.data.repository.NetworkService
import com.topjohnwu.magisk.di.AppContext
import com.topjohnwu.magisk.events.MagiskInstallFileEvent
import com.topjohnwu.magisk.events.dialog.SecondSlotWarningDialog
import com.topjohnwu.magisk.ui.flash.FlashFragment
import com.topjohnwu.magisk.utils.set
import com.topjohnwu.superuser.Shell
import kotlinx.coroutines.launch
import org.koin.core.get
import timber.log.Timber
import java.io.File
import java.io.IOException
@@ -65,8 +64,7 @@ class InstallViewModel(
init {
viewModelScope.launch {
try {
val context = get<Context>()
File(context.cacheDir, "${BuildConfig.VERSION_CODE}.md").run {
File(AppContext.cacheDir, "${BuildConfig.VERSION_CODE}.md").run {
notes = when {
exists() -> readText()
Const.Url.CHANGELOG_URL.isEmpty() -> ""
@@ -89,9 +87,9 @@ class InstallViewModel(
fun install() {
when (method) {
R.id.method_patch -> FlashFragment.patch(data!!)
R.id.method_direct -> FlashFragment.flash(false)
R.id.method_inactive_slot -> FlashFragment.flash(true)
R.id.method_patch -> FlashFragment.patch(data!!).navigate()
R.id.method_direct -> FlashFragment.flash(false).navigate()
R.id.method_inactive_slot -> FlashFragment.flash(true).navigate()
else -> error("Unknown value")
}
state = State.LOADING

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,8 +1,10 @@
package com.topjohnwu.magisk.ui.settings
import android.content.Context
import android.content.res.Resources
import android.os.Build
import android.view.LayoutInflater
import android.view.View
import androidx.databinding.Bindable
import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.BuildConfig
@@ -19,10 +21,9 @@ import com.topjohnwu.magisk.core.utils.currentLocale
import com.topjohnwu.magisk.databinding.DialogSettingsAppNameBinding
import com.topjohnwu.magisk.databinding.DialogSettingsDownloadPathBinding
import com.topjohnwu.magisk.databinding.DialogSettingsUpdateChannelBinding
import com.topjohnwu.magisk.ktx.get
import com.topjohnwu.magisk.utils.TransitiveText
import com.topjohnwu.magisk.di.AppContext
import com.topjohnwu.magisk.utils.Utils
import com.topjohnwu.magisk.utils.asTransitive
import com.topjohnwu.magisk.utils.asText
import com.topjohnwu.magisk.utils.set
import com.topjohnwu.superuser.Shell
import kotlinx.coroutines.CoroutineScope
@@ -31,7 +32,7 @@ import kotlinx.coroutines.launch
// --- Customization
object Customization : BaseSettingsItem.Section() {
override val title = R.string.settings_customization.asTransitive()
override val title = R.string.settings_customization.asText()
}
object Language : BaseSettingsItem.Selector() {
@@ -40,9 +41,18 @@ object Language : BaseSettingsItem.Selector() {
Config.locale = entryValues[it]
}
override val title = R.string.language.asTransitive()
override var entries = emptyArray<String>()
override var entryValues = emptyArray<String>()
override val title = R.string.language.asText()
private var entries = emptyArray<String>()
private var entryValues = emptyArray<String>()
override fun entries(res: Resources) = entries
override fun descriptions(res: Resources) = entries
override fun onPressed(view: View, callback: Callback) {
if (entries.isEmpty()) return
super.onPressed(view, callback)
}
suspend fun loadLanguages(scope: CoroutineScope) {
scope.launch {
@@ -58,18 +68,18 @@ object Language : BaseSettingsItem.Selector() {
object Theme : BaseSettingsItem.Blank() {
override val icon = R.drawable.ic_paint
override val title = R.string.section_theme.asTransitive()
override val title = R.string.section_theme.asText()
}
// --- App
object AppSettings : BaseSettingsItem.Section() {
override val title = R.string.home_app_title.asTransitive()
override val title = R.string.home_app_title.asText()
}
object ClearRepoCache : BaseSettingsItem.Blank() {
override val title = R.string.settings_clear_cache_title.asTransitive()
override val description = R.string.settings_clear_cache_summary.asTransitive()
override val title = R.string.settings_clear_cache_title.asText()
override val description = R.string.settings_clear_cache_summary.asText()
override fun refresh() {
isEnabled = Info.env.isActive
@@ -77,8 +87,8 @@ object ClearRepoCache : BaseSettingsItem.Blank() {
}
object Hide : BaseSettingsItem.Input() {
override val title = R.string.settings_hide_app_title.asTransitive()
override val description = R.string.settings_hide_app_summary.asTransitive()
override val title = R.string.settings_hide_app_title.asText()
override val description = R.string.settings_hide_app_summary.asText()
override var value = ""
set(value) = setV(value, field, { field = it })
@@ -87,7 +97,7 @@ object Hide : BaseSettingsItem.Input() {
get() = if (isError) null else result
@get:Bindable
var result = "Manager"
var result = "Settings"
set(value) = set(value, field, { field = it }, BR.result, BR.error)
val maxLength
@@ -106,21 +116,21 @@ object Hide : BaseSettingsItem.Input() {
}
object Restore : BaseSettingsItem.Blank() {
override val title = R.string.settings_restore_app_title.asTransitive()
override val description = R.string.settings_restore_app_summary.asTransitive()
override val title = R.string.settings_restore_app_title.asText()
override val description = R.string.settings_restore_app_summary.asText()
}
object AddShortcut : BaseSettingsItem.Blank() {
override val title = R.string.add_shortcut_title.asTransitive()
override val description = R.string.setting_add_shortcut_summary.asTransitive()
override val title = R.string.add_shortcut_title.asText()
override val description = R.string.setting_add_shortcut_summary.asText()
}
object DownloadPath : BaseSettingsItem.Input() {
override var value = Config.downloadDir
set(value) = setV(value, field, { field = it }) { Config.downloadDir = it }
override val title = R.string.settings_download_path_title.asTransitive()
override val description get() = path.asTransitive()
override val title = R.string.settings_download_path_title.asText()
override val description get() = path.asText()
override val inputResult: String get() = result
@@ -137,29 +147,32 @@ object DownloadPath : BaseSettingsItem.Input() {
}
object UpdateChannel : BaseSettingsItem.Selector() {
override var value = Config.updateChannel
override var value = Config.updateChannel.let { if (it < 0) 0 else it }
set(value) = setV(value, field, { field = it }) {
Config.updateChannel = it
Info.remote = Info.EMPTY_REMOTE
}
override val title = R.string.settings_update_channel_title.asTransitive()
override val entries: Array<String> = resources.getStringArray(R.array.update_channel).apply {
if (BuildConfig.VERSION_CODE % 100 == 0)
toMutableList().apply { removeAt(Config.Value.CANARY_CHANNEL) }.toTypedArray()
override val title = R.string.settings_update_channel_title.asText()
override val entryRes = R.array.update_channel
override fun entries(res: Resources): Array<String> {
return super.entries(res).let {
if (!BuildConfig.DEBUG)
it.copyOfRange(0, Config.Value.CANARY_CHANNEL)
else it
}
}
override val description
get() = entries.getOrNull(value)?.asTransitive() ?: TransitiveText.String(entries[0])
}
object UpdateChannelUrl : BaseSettingsItem.Input() {
override val title = R.string.settings_update_custom.asTransitive()
override val title = R.string.settings_update_custom.asText()
override var value = Config.customChannelUrl
set(value) = setV(value, field, { field = it }) {
Config.customChannelUrl = it
Info.remote = Info.EMPTY_REMOTE
}
override val description get() = value.asTransitive()
override val description get() = value.asText()
override val inputResult get() = result
@@ -176,18 +189,18 @@ object UpdateChannelUrl : BaseSettingsItem.Input() {
}
object UpdateChecker : BaseSettingsItem.Toggle() {
override val title = R.string.settings_check_update_title.asTransitive()
override val description = R.string.settings_check_update_summary.asTransitive()
override val title = R.string.settings_check_update_title.asText()
override val description = R.string.settings_check_update_summary.asText()
override var value = Config.checkUpdate
set(value) = setV(value, field, { field = it }) {
Config.checkUpdate = it
UpdateCheckService.schedule(get())
UpdateCheckService.schedule(AppContext)
}
}
object DoHToggle : BaseSettingsItem.Toggle() {
override val title = R.string.settings_doh_title.asTransitive()
override val description = R.string.settings_doh_description.asTransitive()
override val title = R.string.settings_doh_title.asText()
override val description = R.string.settings_doh_description.asText()
override var value = Config.doh
set(value) = setV(value, field, { field = it }) {
Config.doh = it
@@ -196,35 +209,35 @@ object DoHToggle : BaseSettingsItem.Toggle() {
// check whether is module already installed beforehand?
object SystemlessHosts : BaseSettingsItem.Blank() {
override val title = R.string.settings_hosts_title.asTransitive()
override val description = R.string.settings_hosts_summary.asTransitive()
override val title = R.string.settings_hosts_title.asText()
override val description = R.string.settings_hosts_summary.asText()
}
object Tapjack : BaseSettingsItem.Toggle() {
override val title = R.string.settings_su_tapjack_title.asTransitive()
override var description = R.string.settings_su_tapjack_summary.asTransitive()
override val title = R.string.settings_su_tapjack_title.asText()
override var description = R.string.settings_su_tapjack_summary.asText()
override var value = Config.suTapjack
set(value) = setV(value, field, { field = it }) { Config.suTapjack = it }
}
object Biometrics : BaseSettingsItem.Toggle() {
override val title = R.string.settings_su_biometric_title.asTransitive()
override val title = R.string.settings_su_biometric_title.asText()
override var value = Config.suBiometric
set(value) = setV(value, field, { field = it }) { Config.suBiometric = it }
override var description = R.string.settings_su_biometric_summary.asTransitive()
override var description = R.string.settings_su_biometric_summary.asText()
override fun refresh() {
isEnabled = BiometricHelper.isSupported
if (!isEnabled) {
value = false
description = R.string.no_biometric.asTransitive()
description = R.string.no_biometric.asText()
}
}
}
object Reauthenticate : BaseSettingsItem.Toggle() {
override val title = R.string.settings_su_reauth_title.asTransitive()
override val description = R.string.settings_su_reauth_summary.asTransitive()
override val title = R.string.settings_su_reauth_title.asText()
override val description = R.string.settings_su_reauth_summary.asText()
override var value = Config.suReAuth
set(value) = setV(value, field, { field = it }) { Config.suReAuth = it }
@@ -236,16 +249,16 @@ object Reauthenticate : BaseSettingsItem.Toggle() {
// --- Magisk
object Magisk : BaseSettingsItem.Section() {
override val title = R.string.magisk.asTransitive()
override val title = R.string.magisk.asText()
}
object MagiskHide : BaseSettingsItem.Toggle() {
override val title = R.string.magiskhide.asTransitive()
override val description = R.string.settings_magiskhide_summary.asTransitive()
override val title = R.string.magiskhide.asText()
override val description = R.string.settings_magiskhide_summary.asText()
override var value = Config.magiskHide
set(value) = setV(value, field, { field = it }) {
val cmd = if (it) "enable" else "disable"
Shell.su("magiskhide --$cmd").submit { cb ->
Shell.su("magiskhide $cmd").submit { cb ->
if (cb.isSuccess) Config.magiskHide = it
else field = !it
}
@@ -255,11 +268,11 @@ object MagiskHide : BaseSettingsItem.Toggle() {
// --- Superuser
object Superuser : BaseSettingsItem.Section() {
override val title = R.string.superuser.asTransitive()
override val title = R.string.superuser.asText()
}
object AccessMode : BaseSettingsItem.Selector() {
override val title = R.string.superuser_access.asTransitive()
override val title = R.string.superuser_access.asText()
override val entryRes = R.array.su_access
override var value = Config.rootMode
@@ -269,37 +282,33 @@ object AccessMode : BaseSettingsItem.Selector() {
}
object MultiuserMode : BaseSettingsItem.Selector() {
override val title = R.string.multiuser_mode.asTransitive()
override val title = R.string.multiuser_mode.asText()
override val entryRes = R.array.multiuser_mode
override val descriptionRes = R.array.multiuser_summary
override var value = Config.suMultiuserMode
set(value) = setV(value, field, { field = it }) {
Config.suMultiuserMode = it
}
override val description
get() = resources.getStringArray(R.array.multiuser_summary)[value].asTransitive()
override fun refresh() {
isEnabled = Const.USER_ID == 0
}
}
object MountNamespaceMode : BaseSettingsItem.Selector() {
override val title = R.string.mount_namespace_mode.asTransitive()
override val title = R.string.mount_namespace_mode.asText()
override val entryRes = R.array.namespace
override val descriptionRes = R.array.namespace_summary
override var value = Config.suMntNamespaceMode
set(value) = setV(value, field, { field = it }) {
Config.suMntNamespaceMode = it
}
override val description
get() = resources.getStringArray(R.array.namespace_summary)[value].asTransitive()
}
object AutomaticResponse : BaseSettingsItem.Selector() {
override val title = R.string.auto_response.asTransitive()
override val title = R.string.auto_response.asText()
override val entryRes = R.array.auto_response
override var value = Config.suAutoResponse
@@ -309,21 +318,21 @@ object AutomaticResponse : BaseSettingsItem.Selector() {
}
object RequestTimeout : BaseSettingsItem.Selector() {
override val title = R.string.request_timeout.asTransitive()
override val title = R.string.request_timeout.asText()
override val entryRes = R.array.request_timeout
override val entryValRes = R.array.request_timeout_value
private val entryValues = listOf(10, 15, 20, 30, 45, 60)
override var value = selected
set(value) = setV(value, field, { field = it }) {
Config.suDefaultTimeout = entryValues[it].toInt()
Config.suDefaultTimeout = entryValues[it]
}
private val selected: Int
get() = entryValues.indexOfFirst { it.toInt() == Config.suDefaultTimeout }
get() = entryValues.indexOfFirst { it == Config.suDefaultTimeout }
}
object SUNotification : BaseSettingsItem.Selector() {
override val title = R.string.superuser_notification.asTransitive()
override val title = R.string.superuser_notification.asText()
override val entryRes = R.array.su_notification
override var value = Config.suNotification

View File

@@ -1,6 +1,5 @@
package com.topjohnwu.magisk.ui.settings
import android.content.Context
import android.os.Build
import android.view.View
import android.widget.Toast
@@ -18,6 +17,7 @@ import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.isRunningAsStub
import com.topjohnwu.magisk.core.tasks.HideAPK
import com.topjohnwu.magisk.data.database.RepoDao
import com.topjohnwu.magisk.di.AppContext
import com.topjohnwu.magisk.events.AddHomeIconEvent
import com.topjohnwu.magisk.events.RecreateEvent
import com.topjohnwu.magisk.events.dialog.BiometricEvent
@@ -25,7 +25,6 @@ import com.topjohnwu.magisk.ktx.activity
import com.topjohnwu.magisk.utils.Utils
import com.topjohnwu.superuser.Shell
import kotlinx.coroutines.launch
import org.koin.core.get
class SettingsViewModel(
private val repositoryDao: RepoDao
@@ -42,7 +41,7 @@ class SettingsViewModel(
}
private fun createItems(): List<BaseSettingsItem> {
val context = get<Context>()
val context = AppContext
val hidden = context.packageName != BuildConfig.APPLICATION_ID
// Customization
@@ -60,7 +59,7 @@ class SettingsViewModel(
))
if (Info.env.isActive) {
list.add(ClearRepoCache)
if (Build.VERSION.SDK_INT >= 21 && Const.USER_ID == 0) {
if (Const.USER_ID == 0) {
if (hidden)
list.add(Restore)
else if (Info.isConnected.get())
@@ -74,10 +73,6 @@ class SettingsViewModel(
Magisk,
MagiskHide, SystemlessHosts
))
if (Build.VERSION.SDK_INT < 19) {
// MagiskHide is only available on 4.4+
list.remove(MagiskHide)
}
}
// Superuser
@@ -103,7 +98,7 @@ class SettingsViewModel(
override fun onItemPressed(view: View, item: BaseSettingsItem, callback: () -> Unit) = when (item) {
is DownloadPath -> withExternalRW(callback)
is Biometrics -> authenticate(callback)
is Theme -> SettingsFragmentDirections.actionSettingsFragmentToThemeFragment().publish()
is Theme -> SettingsFragmentDirections.actionSettingsFragmentToThemeFragment().navigate()
is ClearRepoCache -> clearRepoCache()
is SystemlessHosts -> createHosts()
is Restore -> HideAPK.restore(view.activity)

View File

@@ -1,13 +0,0 @@
package com.topjohnwu.magisk.ui.superuser
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.databinding.ComparableRvItem
class SpinnerRvItem(val item: String) : ComparableRvItem<SpinnerRvItem>() {
override val layoutRes: Int = R.layout.item_spinner
override fun contentSameAs(other: SpinnerRvItem) = itemSameAs(other)
override fun itemSameAs(other: SpinnerRvItem) = item == other.item
}

View File

@@ -5,10 +5,10 @@ import android.view.View
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.arch.BaseUIFragment
import com.topjohnwu.magisk.databinding.FragmentSuperuserMd2Binding
import com.topjohnwu.magisk.di.viewModel
import com.topjohnwu.magisk.ktx.addSimpleItemDecoration
import com.topjohnwu.magisk.ktx.addVerticalPadding
import com.topjohnwu.magisk.ktx.fixEdgeEffect
import org.koin.androidx.viewmodel.ext.android.viewModel
class SuperuserFragment : BaseUIFragment<SuperuserViewModel, FragmentSuperuserMd2Binding>() {

View File

@@ -1,6 +1,5 @@
package com.topjohnwu.magisk.ui.superuser
import android.content.res.Resources
import androidx.databinding.ObservableArrayList
import androidx.lifecycle.viewModelScope
import com.topjohnwu.magisk.BR
@@ -19,6 +18,7 @@ import com.topjohnwu.magisk.events.SnackbarEvent
import com.topjohnwu.magisk.events.dialog.BiometricEvent
import com.topjohnwu.magisk.events.dialog.SuperuserRevokeDialog
import com.topjohnwu.magisk.utils.Utils
import com.topjohnwu.magisk.utils.asText
import com.topjohnwu.magisk.view.TappableHeadlineItem
import com.topjohnwu.magisk.view.TextItem
import kotlinx.coroutines.Dispatchers
@@ -27,8 +27,7 @@ import kotlinx.coroutines.withContext
import me.tatarka.bindingcollectionadapter2.collections.MergeObservableList
class SuperuserViewModel(
private val db: PolicyDao,
private val resources: Resources
private val db: PolicyDao
) : BaseViewModel(), TappableHeadlineItem.Listener {
private val itemNoData = TextItem(R.string.superuser_policy_none)
@@ -55,6 +54,7 @@ class SuperuserViewModel(
}
state = State.LOADING
val (policies, diff) = withContext(Dispatchers.Default) {
db.deleteOutdated()
val policies = db.fetchAll {
PolicyRvItem(it, it.icon, this@SuperuserViewModel)
}.sortedWith(compareBy(
@@ -79,7 +79,7 @@ class SuperuserViewModel(
}
private fun hidePressed() =
SuperuserFragmentDirections.actionSuperuserFragmentToHideFragment().publish()
SuperuserFragmentDirections.actionSuperuserFragmentToHideFragment().navigate()
fun deletePressed(item: PolicyRvItem) {
fun updateState() = viewModelScope.launch {
@@ -106,7 +106,7 @@ class SuperuserViewModel(
fun updatePolicy(policy: SuPolicy, isLogging: Boolean) = viewModelScope.launch {
db.update(policy)
val str = when {
val res = when {
isLogging -> when {
policy.logging -> R.string.su_snack_log_on
else -> R.string.su_snack_log_off
@@ -116,7 +116,7 @@ class SuperuserViewModel(
else -> R.string.su_snack_notif_off
}
}
SnackbarEvent(resources.getString(str, policy.appName)).publish()
SnackbarEvent(res.asText(policy.appName)).publish()
}
fun togglePolicy(item: PolicyRvItem, enable: Boolean) {
@@ -130,7 +130,7 @@ class SuperuserViewModel(
db.update(app)
val res = if (app.policy == SuPolicy.ALLOW) R.string.su_snack_grant
else R.string.su_snack_deny
SnackbarEvent(resources.getString(res).format(item.item.appName)).publish()
SnackbarEvent(res.asText(item.item.appName)).publish()
}
}

View File

@@ -3,7 +3,6 @@ package com.topjohnwu.magisk.ui.surequest
import android.content.Intent
import android.content.pm.ActivityInfo
import android.content.res.Resources
import android.os.Build
import android.os.Bundle
import android.view.Window
import android.view.WindowManager
@@ -13,7 +12,7 @@ import com.topjohnwu.magisk.arch.BaseUIActivity
import com.topjohnwu.magisk.core.su.SuCallbackHandler
import com.topjohnwu.magisk.core.su.SuCallbackHandler.REQUEST
import com.topjohnwu.magisk.databinding.ActivityRequestBinding
import org.koin.androidx.viewmodel.ext.android.viewModel
import com.topjohnwu.magisk.di.viewModel
open class SuRequestActivity : BaseUIActivity<SuRequestViewModel, ActivityRequestBinding>() {
@@ -62,9 +61,6 @@ open class SuRequestActivity : BaseUIActivity<SuRequestViewModel, ActivityReques
}
private fun lockOrientation() {
requestedOrientation = if (Build.VERSION.SDK_INT < 18)
resources.configuration.orientation
else
ActivityInfo.SCREEN_ORIENTATION_LOCKED
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LOCKED
}
}

View File

@@ -3,7 +3,6 @@ package com.topjohnwu.magisk.ui.surequest
import android.annotation.SuppressLint
import android.content.Intent
import android.content.SharedPreferences
import android.content.pm.PackageManager
import android.content.res.Resources
import android.graphics.drawable.Drawable
import android.os.Bundle
@@ -27,22 +26,20 @@ import com.topjohnwu.magisk.core.model.su.SuPolicy.Companion.ALLOW
import com.topjohnwu.magisk.core.model.su.SuPolicy.Companion.DENY
import com.topjohnwu.magisk.core.su.SuRequestHandler
import com.topjohnwu.magisk.core.utils.BiometricHelper
import com.topjohnwu.magisk.di.AppContext
import com.topjohnwu.magisk.events.DieEvent
import com.topjohnwu.magisk.events.ShowUIEvent
import com.topjohnwu.magisk.events.dialog.BiometricEvent
import com.topjohnwu.magisk.ui.superuser.SpinnerRvItem
import com.topjohnwu.magisk.utils.TextHolder
import com.topjohnwu.magisk.utils.Utils
import com.topjohnwu.magisk.utils.set
import kotlinx.coroutines.launch
import me.tatarka.bindingcollectionadapter2.BindingListViewAdapter
import me.tatarka.bindingcollectionadapter2.ItemBinding
import java.util.concurrent.TimeUnit.SECONDS
class SuRequestViewModel(
pm: PackageManager,
policyDB: PolicyDao,
private val timeoutPrefs: SharedPreferences,
private val res: Resources
private val timeoutPrefs: SharedPreferences
) : BaseViewModel() {
lateinit var icon: Drawable
@@ -50,8 +47,7 @@ class SuRequestViewModel(
lateinit var packageName: String
@get:Bindable
var denyText = res.getString(R.string.deny)
set(value) = set(value, field, { field = it }, BR.denyText)
val denyText = DenyText()
@get:Bindable
var selectedItemPosition = 0
@@ -74,15 +70,9 @@ class SuRequestViewModel(
false
}
private val items = res.getStringArray(R.array.allow_timeout).map { SpinnerRvItem(it) }
val adapter = BindingListViewAdapter<SpinnerRvItem>(1).apply {
itemBinding = ItemBinding.of { binding, _, item ->
item.bind(binding)
}
setItems(items)
}
val itemBinding = ItemBinding.of<String>(BR.item, R.layout.item_spinner)
private val handler = SuRequestHandler(pm, policyDB)
private val handler = SuRequestHandler(AppContext.packageManager, policyDB)
private lateinit var timer: CountDownTimer
fun grantPressed() {
@@ -143,7 +133,7 @@ class SuRequestViewModel(
private fun cancelTimer() {
timer.cancel()
denyText = res.getString(R.string.deny)
denyText.seconds = 0
}
private inner class SuTimer(
@@ -155,16 +145,28 @@ class SuRequestViewModel(
if (!grantEnabled && remains <= millis - 1000) {
grantEnabled = true
}
denyText = "${res.getString(R.string.deny)} (${(remains / 1000) + 1})"
denyText.seconds = (remains / 1000).toInt() + 1
}
override fun onFinish() {
denyText = res.getString(R.string.deny)
denyText.seconds = 0
respond(DENY)
}
}
inner class DenyText : TextHolder() {
var seconds = 0
set(value) = set(value, field, { field = it }, BR.denyText)
override fun getText(resources: Resources): CharSequence {
return if (seconds != 0)
"${resources.getString(R.string.deny)} ($seconds)"
else
resources.getString(R.string.deny)
}
}
// Invisible for accessibility services
object EmptyAccessibilityDelegate : View.AccessibilityDelegate() {
override fun sendAccessibilityEvent(host: View?, eventType: Int) {}

View File

@@ -11,7 +11,7 @@ import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.arch.BaseUIFragment
import com.topjohnwu.magisk.databinding.FragmentThemeMd2Binding
import com.topjohnwu.magisk.databinding.ItemThemeBindingImpl
import org.koin.androidx.viewmodel.ext.android.viewModel
import com.topjohnwu.magisk.di.viewModel
class ThemeFragment : BaseUIFragment<ThemeViewModel, FragmentThemeMd2Binding>() {

View File

@@ -0,0 +1,47 @@
package com.topjohnwu.magisk.utils
import android.content.res.Resources
import android.widget.TextView
import androidx.databinding.BindingAdapter
abstract class TextHolder {
open val isEmpty: Boolean get() = false
abstract fun getText(resources: Resources): CharSequence
// ---
class String(
private val value: CharSequence
) : TextHolder() {
override val isEmpty get() = value.isEmpty()
override fun getText(resources: Resources) = value
}
class Resource(
private val value: Int,
private vararg val params: Any
) : TextHolder() {
override val isEmpty get() = value == 0
override fun getText(resources: Resources) = resources.getString(value, *params)
}
// ---
companion object {
val EMPTY = String("")
}
}
fun Int.asText(vararg params: Any): TextHolder = TextHolder.Resource(this, *params)
fun CharSequence.asText(): TextHolder = TextHolder.String(this)
@BindingAdapter("android:text")
fun TextView.setText(text: TextHolder) {
this.text = text.getText(context.resources)
}

View File

@@ -1,54 +0,0 @@
package com.topjohnwu.magisk.utils
import android.content.res.Resources
import android.widget.TextView
import androidx.databinding.BindingAdapter
import androidx.databinding.InverseBindingAdapter
sealed class TransitiveText {
abstract val isEmpty: Boolean
abstract fun getText(resources: Resources): CharSequence
// ---
class String(
private val value: CharSequence
) : TransitiveText() {
override val isEmpty = value.isEmpty()
override fun getText(resources: Resources) = value
}
class Res(
private val value: Int,
private vararg val params: Any
) : TransitiveText() {
override val isEmpty = value == 0
override fun getText(resources: Resources) =
resources.getString(value, *params)
}
// ---
companion object {
val EMPTY = String("")
}
}
fun Int.asTransitive(vararg params: Any): TransitiveText = TransitiveText.Res(this, *params)
fun CharSequence.asTransitive(): TransitiveText = TransitiveText.String(this)
@BindingAdapter("android:text")
fun TextView.setText(text: TransitiveText) {
this.text = text.getText(context.resources)
}
@InverseBindingAdapter(attribute = "android:text", event = "android:textAttrChanged")
fun TextView.getTransitiveText() = text.asTransitive()

View File

@@ -8,17 +8,17 @@ import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.ktx.get
import com.topjohnwu.magisk.di.AppContext
import com.topjohnwu.superuser.internal.UiThreadHandler
object Utils {
fun toast(msg: CharSequence, duration: Int) {
UiThreadHandler.run { Toast.makeText(get(), msg, duration).show() }
UiThreadHandler.run { Toast.makeText(AppContext, msg, duration).show() }
}
fun toast(resId: Int, duration: Int) {
UiThreadHandler.run { Toast.makeText(get(), resId, duration).show() }
UiThreadHandler.run { Toast.makeText(AppContext, resId, duration).show() }
}
fun showSuperUser(): Boolean {

View File

@@ -13,13 +13,13 @@ import com.topjohnwu.magisk.core.Const.ID.PROGRESS_NOTIFICATION_CHANNEL
import com.topjohnwu.magisk.core.Const.ID.UPDATE_NOTIFICATION_CHANNEL
import com.topjohnwu.magisk.core.download.DownloadService
import com.topjohnwu.magisk.core.download.Subject
import com.topjohnwu.magisk.ktx.get
import com.topjohnwu.magisk.di.AppContext
import com.topjohnwu.magisk.ktx.getBitmap
@Suppress("DEPRECATION")
object Notifications {
val mgr by lazy { get<Context>().getSystemService<NotificationManager>()!! }
val mgr by lazy { AppContext.getSystemService<NotificationManager>()!! }
fun setup(context: Context) {
if (SDK_INT >= 26) {

View File

@@ -10,7 +10,6 @@ import org.bouncycastle.cms.CMSSignedData;
import org.bouncycastle.cms.CMSSignedDataGenerator;
import org.bouncycastle.cms.CMSTypedData;
import org.bouncycastle.cms.jcajce.JcaSignerInfoGeneratorBuilder;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
@@ -31,7 +30,6 @@ import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Security;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;

View File

@@ -196,6 +196,9 @@ public class SignBoot {
int secondSize = image.getInt();
image.getLong(); // second_addr + tags_addr
int pageSize = image.getInt();
if (pageSize >= 0x02000000) {
throw new IllegalArgumentException("Invalid image header: PXA header detected");
}
int length = pageSize // include the page aligned image header
+ ((kernelSize + pageSize - 1) / pageSize) * pageSize
+ ((ramdskSize + pageSize - 1) / pageSize) * pageSize
@@ -214,8 +217,7 @@ public class SignBoot {
image.getLong(); // dtb address
}
if (image.position() != headerSize) {
throw new IllegalArgumentException(
"Invalid image header: invalid header length");
throw new IllegalArgumentException("Invalid image header: invalid header length");
}
} else {
// headerVersion is 0 or actually dt/extra size in this case

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

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