1
mirror of https://github.com/topjohnwu/Magisk synced 2025-10-30 09:00:52 +01:00

Compare commits

..

144 Commits

Author SHA1 Message Date
topjohnwu
7f8257152f Add v21.3 release notes 2021-01-16 04:55:44 -08:00
topjohnwu
0cd80f2556 Update app changelog 2021-01-16 04:42:14 -08:00
rydwhelchel
1717387876 Grammatical changes to the install docs 2021-01-15 21:32:29 -08:00
Mspy1
109363ebf6 Fixed typo 2021-01-15 21:31:58 -08:00
LLZN
716c4fa386 new update values-cs
update czech strings.xml
2021-01-15 21:31:17 -08:00
Arbri çoçka
9a09b4eb20 fix strings-sq 2021-01-15 21:29:53 -08:00
Rikka
95a5b57265 Remove "Flashing" overlay
Fix #3579, fix #3250
2021-01-15 21:28:59 -08:00
topjohnwu
13fbf397d1 Isolated processes might still be hide-able 2021-01-15 20:22:49 -08:00
vvb2060
20be99ec8a Restore mistakenly deleted codes 2021-01-15 19:59:55 -08:00
topjohnwu
04c53c3578 Legacy SAR: use a simpler method to detect is_two_stage 2021-01-15 02:44:40 -08:00
topjohnwu
51bc27a869 Avoid F2FS like a plague 2021-01-15 02:24:11 -08:00
topjohnwu
71b083794c Maintain global mount list 2021-01-14 21:14:54 -08:00
topjohnwu
b100d0c503 Revert DTB fstab changes 2021-01-14 19:48:00 -08:00
topjohnwu
76061296c9 Let MagiskBoot handle dtb fstab patching 2021-01-14 06:20:12 -08:00
topjohnwu
bb303d2da1 Remove old unused code 2021-01-14 05:59:53 -08:00
topjohnwu
c91c070343 Re-enable DTB table rebuilding 2021-01-14 05:45:05 -08:00
topjohnwu
aec06a6f61 Get proper total image size 2021-01-14 03:55:27 -08:00
topjohnwu
e8ba671fc2 Guard all injection features behind a global flag 2021-01-13 20:07:23 -08:00
topjohnwu
1860e5d133 Dynamically find libselinux.so path 2021-01-13 19:41:57 -08:00
topjohnwu
f2cb3c38fe Update mmap implementation
Always map memory as writable, but private when read-only
2021-01-12 22:50:55 -08:00
topjohnwu
9a28dd4f6e Implement MagiskHide through code injection 2021-01-12 03:28:00 -08:00
topjohnwu
d2acd59ea8 Minor code refactoring 2021-01-12 00:07:48 -08:00
topjohnwu
79dfdb29e7 Minor tweaks for patching tar files 2021-01-11 19:47:36 -08:00
topjohnwu
eb21c8b42e Code cleanups 2021-01-11 02:19:10 -08:00
topjohnwu
541bb53553 Update links in README 2021-01-10 20:36:58 -08:00
Hen Ry
fe8997efae Fix 2021-01-10 20:17:20 -08:00
Arbri çoçka
23455c722c fix in Values-sq 2021-01-10 20:16:57 -08:00
topjohnwu
5ce29c30d2 Fix sepolicy copying 2021-01-10 20:16:02 -08:00
topjohnwu
70d67728fd Add global toggle for ptrace monitor 2021-01-10 19:27:54 -08:00
topjohnwu
e546884b08 Remove isolated process handling in ptrace
Impossible to achieve only through ptrace
2021-01-10 17:18:42 -08:00
topjohnwu
b36e6d987d Reorganize MagiskHide code
Prepare for zygote injection hiding
2021-01-10 17:11:00 -08:00
topjohnwu
53c3dd5e8b Auto track JNI method hooks 2021-01-10 05:07:17 -08:00
topjohnwu
da723b207a Allow 3rd party code to load pre-specializing
Magisk's policy is to never allow 3rd party code to be loaded in the
zygote daemon process so we have 100% control over injection and hiding.
However, this makes it impossible for 3rd party modules to run anything
before process specialization, which includes the ability to modify the
arguments being sent to these original nativeForkAndXXX methods.

The trick here is to fork before calling the original nativeForkAndXXX
methods, and hook `fork` in libandroid_runtime.so to skip the next
invocation; basically, we're moving the responsibility of process
forking to our own hands.
2021-01-10 01:25:30 -08:00
topjohnwu
e050f77198 Don't hook SystemProperties#set
Doesn't seem necessary
2021-01-09 20:39:59 -08:00
topjohnwu
540b4b7ea9 Update pre/post hooks implementation 2021-01-09 17:41:25 -08:00
topjohnwu
bbef22daf7 More macro magic to automate more code 2021-01-09 04:28:26 -08:00
topjohnwu
9ed110c91b Add JNI hooks to critical methods 2021-01-08 05:25:44 -08:00
topjohnwu
a30d510eb1 Use xHook to hook functions in PLT 2021-01-08 00:53:24 -08:00
topjohnwu
ef98eaed8f Proper injection entry and unloading 2021-01-06 23:59:05 -08:00
topjohnwu
2a257f327c Sanitize /proc/PID/environ 2021-01-06 23:41:37 -08:00
topjohnwu
4060c2107c Add preliminary zygote code injection support
Prototyping the injection setup and a clean "self unloading" mechanism.
2021-01-06 22:21:17 -08:00
topjohnwu
cd23d27048 Fix remote_write implementation 2021-01-06 21:56:29 -08:00
topjohnwu
18b86e4fd2 Update Android.mk for test binary
Make Android Studio happy
2021-01-05 00:01:02 -08:00
topjohnwu
5f2e22a259 Support remote function call with ptrace
End up not used for anything, but keep it for good
2021-01-02 21:29:45 -08:00
topjohnwu
4e97b18977 Move libsystemproperties to external 2020-12-31 15:06:19 -08:00
topjohnwu
f9bde347bc Convert indentation to spaces
The tab war is lost
2020-12-30 22:11:24 -08:00
Billy Laws
947a7d6a2f Support rootwait cmdline parameter on legacy SAR
On devices where the primary storage is slow to probe it makes sense to
wait forever for the system partition to mount, this emulates the
kernel's behaviour when waiting for rootfs on SAR if the rootwait
parameter is supplied.

This issue was encountered with some SD cards on the Nintendo Switch.
2020-12-30 16:43:28 -08:00
Björn Engel
872ab2e99b Change translation for next
Nächste sounds a little bit strange.
2020-12-30 16:41:22 -08:00
kubalav
90b8813bb7 Fixed typo 2020-12-30 16:41:01 -08:00
Arbri çoçka
88d0f63294 Fix text in strings_sq 2020-12-30 16:40:47 -08:00
topjohnwu
79fa0d3a90 Hide selection improvements 2020-12-30 16:40:22 -08:00
topjohnwu
8e61080a4a Preparation for hiding isolated processes 2020-12-30 15:55:53 -08:00
topjohnwu
3f9a64417b Disable gradle daemon on Windows CI 2020-12-29 02:46:57 -08:00
topjohnwu
eb959379e8 Prevent resource ID clash 2020-12-29 02:39:47 -08:00
topjohnwu
41a644afb9 Open source stub APK loader
Close #3537
2020-12-29 01:44:02 -08:00
topjohnwu
6b42db943d Better bug report details 2020-12-28 17:03:20 -08:00
topjohnwu
1c325459eb Only run CI when it matters 2020-12-28 16:38:25 -08:00
John Wu
6d88d8ad95 Add issue templates 2020-12-28 16:26:10 -08:00
topjohnwu
246997f273 Update links 2020-12-28 15:58:53 -08:00
topjohnwu
b6144ae582 Add v21.2 release notes 2020-12-28 15:35:09 -08:00
Arbri çoçka
afe17c73b4 Update strings.xml
Fix same text in Values-sq
2020-12-28 15:29:27 -08:00
topjohnwu
b51b884fc7 Fix module installs in recovery
Close #3494
2020-12-28 00:25:01 -08:00
topjohnwu
d3e4b29e62 Update README.md 2020-12-27 22:36:03 -08:00
dark-basic
24059e7403 Update Stub-es version 2020-12-27 22:09:03 -08:00
dark-basic
107a2a6682 Update String-es 2020-12-27 22:08:34 -08:00
Arbri çoçka
56b4ab6672 Fix any text in strings sq 2020-12-27 22:07:44 -08:00
topjohnwu
4662454938 More attempts to fix gradle cache on Windows 2020-12-27 20:13:50 -08:00
topjohnwu
db4f78d463 Unblock signals before executing commands 2020-12-27 15:05:39 -08:00
topjohnwu
880de21596 Update Github Actions
Disable gradle daemon on Windows and always upload artifacts
2020-12-27 04:02:20 -08:00
topjohnwu
622dd84c9e Fix uninstaller zip 2020-12-26 22:45:05 -08:00
topjohnwu
f983bfc883 Embed keys into dex files 2020-12-26 21:33:30 -08:00
topjohnwu
45cdb3fdb0 Update dependencies 2020-12-26 17:05:12 -08:00
topjohnwu
9a707236b8 Move signing code into main app sources 2020-12-26 17:03:10 -08:00
topjohnwu
e9e6ad3bb0 Sign zips with apksigner 2020-12-26 16:04:41 -08:00
topjohnwu
ab78a81d15 Fix GitHub actions 2020-12-25 15:54:47 -08:00
John Wu
18340099b7 Add GitHub actions
Enable GitHub actions to run CI on all 3 platforms
2020-12-25 15:01:02 -08:00
topjohnwu
a013696a41 Default to config.prop in buildSrc 2020-12-25 13:03:25 -08:00
topjohnwu
8a2a6d9232 Make versionCode unconfigurable 2020-12-25 05:34:15 -08:00
topjohnwu
12aa6d86e4 Make config.prop optional 2020-12-24 04:46:31 -08:00
topjohnwu
7d08969d28 Fix strings 2020-12-23 01:33:46 -08:00
Fs00
dda4aa8488 Translate missing Italian strings 2020-12-22 23:33:20 -08:00
binarynoise
cdaef3d801 Update install.md
I wanted to share my experiences with rooting my S10e.
2020-12-22 23:32:53 -08:00
amninder singh
9159166128 Update strings.xml
Updating strings.xml
regarding #3566 adding punjabi translation
2020-12-22 23:32:01 -08:00
Arbri çoçka
dc0882e043 fixing some errors in sq strings 2020-12-22 23:31:40 -08:00
amninder singh
c811f015ef Added Punjabi Translation
- Written in Gurmukhi Script containing different foreign punctuations both inscript and Phonetic
2020-12-22 23:31:12 -08:00
nkh0472
d8f0b66fe1 Improve Correctness and Clarity of guides.md 2020-12-22 23:30:37 -08:00
Mohd Faraz
dc3d57deba utils_functions: Added a check for the system_root
now on addon while flashing recovery usign mount point /system_root by which this is causing a flashing error.
Let's first check and unmount /system_root if mounted

Signed-off-by: Mohd Faraz <androiabledroid@gmail.com>
2020-12-22 23:30:14 -08:00
topjohnwu
d089698475 Don't use root for logging getprop
Might contain sensitive info that shouldn't be shared
2020-12-19 23:09:36 -08:00
vvb2060
8ed2dd6687 Skip query for log files and patched boot file 2020-12-19 22:26:10 -08:00
vvb2060
50305ca1fe Support save manager log 2020-12-19 22:25:44 -08:00
vvb2060
3e91567636 Add a suffix to magisk_patched.img
prevent it from being used as input file
2020-12-19 21:53:16 -08:00
vvb2060
0b4dd63d36 Stub module always use release build 2020-12-19 21:02:05 -08:00
vvb2060
38d0f85deb Avoid unnecessary builds 2020-12-19 15:57:11 -08:00
vvb2060
c5b452f369 Get boot config properly
https://android.googlesource.com/platform/system/core/+/refs/tags/android-11.0.0_r16/fs_mgr/fs_mgr_boot_config.cpp#93
2020-12-19 15:55:33 -08:00
vvb2060
6ce9225f52 Check block dev ro status
magisk is shared object, use static busybox instead
2020-12-19 14:12:12 -08:00
vvb2060
13a8820603 Double check $DATA_DE 2020-12-19 14:08:32 -08:00
vvb2060
503997a09a Trim out \r 2020-12-19 14:08:06 -08:00
vvb2060
17efdff134 remove_system_su only on recovery mode
We may mount su to /system/bin/su
2020-12-19 13:59:08 -08:00
vvb2060
984f32f994 Move copy_sepolicy_rules to manager
We don’t need it when patch boot
2020-12-19 13:58:53 -08:00
topjohnwu
eee7f097e3 Make post-fs-data scripts block at most 35 secs 2020-12-17 16:54:53 -08:00
topjohnwu
086059ec30 Make sure boot stages are mutually exclusive 2020-12-15 03:40:37 -08:00
topjohnwu
7ff22c68c7 Only try to install APK when no manager is active 2020-12-09 02:15:16 -08:00
topjohnwu
1232113772 Update preference migration implementation
Only try to read preference through content provider when the app
is fresh install and a previous package ID is set. Also catch all
Exceptions to prevent crashing the app.

This prevents malicious settings injection and crashes when multiple
manager is installed.

Fix #3542
2020-12-09 02:07:58 -08:00
vvb2060
039d4936cb Disable superuser fragment properly 2020-12-09 01:16:30 -08:00
topjohnwu
784dd80965 Update MediaStoreUtils 2020-12-09 01:15:56 -08:00
vvb2060
1ffe9bd83b Fix update channel without description on release build 2020-12-09 00:40:33 -08:00
topjohnwu
0c28b23224 Fix install_module command 2020-12-09 00:37:15 -08:00
vvb2060
ec1af9dc1e Delete useless arrays 2020-12-09 00:17:58 -08:00
vvb2060
ff4cea229a Check environment on emulator
We need to test modules on emulator.
2020-12-09 00:16:27 -08:00
vvb2060
3f81f9371f Disable installation while downloading metadata 2020-12-09 00:16:13 -08:00
vvb2060
60e89a7d22 Ignore manager not installed state 2020-12-09 00:15:58 -08:00
vvb2060
c50daa5c9e Allow restore boot when no network 2020-12-09 00:15:41 -08:00
topjohnwu
58d00ab863 Remove some warnings 2020-12-07 00:06:02 -08:00
topjohnwu
ce916459c5 Fix strings 2020-12-07 00:04:41 -08:00
Antikruk
4094d560ab Belarusian update 2020-12-06 23:50:33 -08:00
RikkaW
4dbf7eb04b Fix spacing in module filter list 2020-12-06 23:49:23 -08:00
RikkaW
a39577c44d Fix extra spacing in module list 2020-12-06 23:49:23 -08:00
osm0sis
125ee46685 scripts: fix find_manager_apk
- the strings fallback was broken when the preceding character changed from 5 to ! recently, this new regex should cover any preceding character going forward
2020-12-06 23:49:03 -08:00
osm0sis
ce84f1762c scripts: fix sloppy/unpredictable cmd && this || that statements
- be a bit more POSIX to avoid any potential issues when full shell stdout/err are redirected
- actual logic chains remain unchanged
2020-12-06 23:48:43 -08:00
Hafitz Setya
a687d1347b Tidying up IN 2020-12-06 23:48:01 -08:00
Arbri çoçka
6d9db20614 Create strings.xml 2020-12-06 23:46:18 -08:00
topjohnwu
c62dfc1bcc Make logging less error prone 2020-12-06 23:09:24 -08:00
topjohnwu
aabe2696fe Cleanup implementations 2020-12-06 03:07:47 -08:00
topjohnwu
ae0d605310 Make sure patch_rootdir does not cause crashes 2020-12-06 02:19:57 -08:00
topjohnwu
2a694596b5 Better error handling and logging 2020-12-05 10:23:49 -08:00
topjohnwu
ff0a76606e Detect 2SI after system_root mount on legacy SAR 2020-12-04 03:06:21 -08:00
topjohnwu
dead74801d Setup log file when manually starting daemon 2020-12-04 01:07:47 -08:00
topjohnwu
ab207a1bb3 va_list cannot be reused on x86 2020-12-03 20:53:19 -08:00
topjohnwu
f152e8c33d Directly log to log file 2020-12-03 20:15:18 -08:00
topjohnwu
797ba4fbf4 Make sure all logging ends with newline 2020-12-02 00:55:22 -08:00
topjohnwu
a848f10bba Update Kotlin 2020-11-23 12:35:24 -08:00
topjohnwu
552ec1eb35 Header v3 does not have name entry 2020-11-20 22:52:09 -08:00
topjohnwu
1385d2a4f4 Fix strings 2020-11-19 23:34:02 -08:00
RikkaW
3b5c9abf7a Remove filterTouchesWhenObscured in Magisk dialog
Fix #3363
2020-11-19 23:32:23 -08:00
tzagim
e0fa032bd3 Update HE strings and fix typos 2020-11-19 23:29:47 -08:00
omerakgoz34
7b69650fcd app: Update Turkish translations 2020-11-19 23:27:54 -08:00
kubalav
08a8df489f Slovak language formating 2020-11-19 23:27:07 -08:00
cristisilaghi
9f35a8a520 Update Romanian 2020-11-19 23:26:38 -08:00
RikkaW
0df891b336 Handle window insets with a new way
For example, switching pages in home should only have scale and alpha animations, but a "translate y" animation shows. This is because Data Binding is triggered later (like "in the next frame"), causing the animation runs before view attribute changes.

This commit introduces WindowInsetsHelper class and use it to handle all window insets. With the help of LayoutInflaterFactory from the previous commit, we can control insets behavior by adding our attributes to the XML and anything is done by WindowInsetsHelper class.

As changes are highly coupling, this commit also contains new ItemDecoration for lists, replacing the random combination of padding and empty drawable. And "fixEdgeEffect" extension for RecyclerView, making edge effects respect padding.
2020-11-19 23:24:39 -08:00
RikkaW
385853a290 Introduce LayoutInflaterFactory
This add the ability touch layout XML instantiates process. And most importantly, we can access AttributeSet, making custom view attribute possible.

Some other changes requires this.
2020-11-19 23:24:39 -08:00
RikkaW
fa3ef8a1c1 Significantly simplify MagiskDialog layout
The goal of original implementation, wrap view again and again, seems to be use the shadow and customizable round corners from MaterialCardView. But this can be done with use MaterialShapeDrawable which used in MaterialCardView directly. This will significantly simplify the layout and MagiskDialog class.
2020-11-19 23:21:36 -08:00
RikkaW
c93ada03c7 Implement Edge-to-edge with newer APIs
The implementation adds a "Base" family styles, making creating themes across multiple API versions more clearer and easier.
2020-11-19 23:21:36 -08:00
topjohnwu
0064b01ae0 Trim out \r from string
Fix #3490
2020-11-15 06:30:29 -08:00
topjohnwu
1469b82aa2 Update README 2020-11-13 04:38:17 -08:00
259 changed files with 15722 additions and 12375 deletions

28
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,28 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
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 a crash of Magisk Manager, dump the full `logcat` **when the crash happens**. **DO NOT** upload `magisk.log`.
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)
**DO NOT** open issues regarding root detection.
**DO NOT** ask for instructions.
**DO NOT** report issues if you have any modules installed.
Without following the rules above, your issue will be closed without explanation.

94
.github/workflows/build.yml vendored Normal file
View File

@@ -0,0 +1,94 @@
name: Magisk Build
on:
push:
branches: [ master ]
paths:
- 'app/**'
- 'native/**'
- 'stub/**'
- 'buildSrc/**'
- 'build.py'
- 'gradle.properties'
pull_request:
branches: [ master ]
workflow_dispatch:
jobs:
build:
name: Build on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ ubuntu-latest, windows-latest, macOS-latest ]
steps:
- name: Check out
uses: actions/checkout@v2
with:
submodules: 'recursive'
fetch-depth: 0
- name: Set up JDK 11
uses: actions/setup-java@v1
with:
java-version: '11'
- name: Set up Python 3
uses: actions/setup-python@v2
with:
python-version: '3.x'
- name: Set up GitHub env (Windows)
if: runner.os == 'Windows'
run: |
$oldAndroidPath = $env:ANDROID_SDK_ROOT
$sdk_root = "C:\Android"
New-Item -Path $sdk_root -ItemType SymbolicLink -Value $oldAndroidPath
$ndk_ver = Select-String -Path "gradle.properties" -Pattern "^magisk.fullNdkVersion=" | % { $_ -replace ".*=" }
echo "ANDROID_SDK_ROOT=$sdk_root" >> $env:GITHUB_ENV
echo "ANDROID_HOME=$sdk_root" >> $env:GITHUB_ENV
echo "MAGISK_NDK_VERSION=$ndk_ver" >> $env:GITHUB_ENV
echo "GRADLE_OPTS=-Dorg.gradle.daemon=false" >> $env:GITHUB_ENV
- name: Set up GitHub env (Unix)
if: runner.os != 'Windows'
run: |
ndk_ver=$(sed -n 's/^magisk.fullNdkVersion=//p' gradle.properties)
echo ANDROID_SDK_ROOT=$ANDROID_SDK_ROOT >> $GITHUB_ENV
echo MAGISK_NDK_VERSION=$ndk_ver >> $GITHUB_ENV
- name: Cache Gradle
uses: actions/cache@v2
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle.kts') }}
restore-keys: ${{ runner.os }}-gradle-
- name: Cache NDK
id: ndk-cache
uses: actions/cache@v2
with:
path: ${{ env.ANDROID_SDK_ROOT }}/ndk/magisk
key: ${{ runner.os }}-ndk-${{ env.MAGISK_NDK_VERSION }}
- name: Set up NDK
if: steps.ndk-cache.outputs.cache-hit != 'true'
run: python build.py ndk
- name: Build release
run: python build.py -vr all
- name: Build debug
run: python build.py -v all
# Only upload artifacts built on Linux
- name: Upload build artifact
if: runner.os == 'Linux'
uses: actions/upload-artifact@v2
with:
name: ${{ github.sha }}
path: out

3
.gitmodules vendored
View File

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

View File

@@ -15,11 +15,11 @@ Here are some feature highlights:
## Downloads
[![](https://img.shields.io/badge/Magisk%20Manager-v8.0.2-green)](https://github.com/topjohnwu/Magisk/releases/download/manager-v8.0.2/MagiskManager-v8.0.2.apk)
[![](https://img.shields.io/badge/Magisk%20Manager-v8.0.5-green)](https://github.com/topjohnwu/Magisk/releases/download/manager-v8.0.5/MagiskManager-v8.0.5.apk)
[![](https://img.shields.io/badge/Magisk%20Manager-Canary-red)](https://raw.githubusercontent.com/topjohnwu/magisk_files/canary/app-debug.apk)
<br>
[![](https://img.shields.io/badge/Magisk-v20.4-blue)](https://github.com/topjohnwu/Magisk/releases/tag/v20.4)
[![](https://img.shields.io/badge/Magisk%20Beta-v21.0-blue)](https://github.com/topjohnwu/Magisk/releases/tag/v21.0)
[![](https://img.shields.io/badge/Magisk-v21.2-blue)](https://github.com/topjohnwu/Magisk/releases/tag/v21.2)
[![](https://img.shields.io/badge/Magisk%20Beta-v21.2-blue)](https://github.com/topjohnwu/Magisk/releases/tag/v21.2)
## Useful Links
@@ -58,11 +58,11 @@ For Magisk Manager crashes, record and upload the logcat when the crash occurs.
- Windows: Add `C:\Path\To\Android Studio\jre\bin` to environment variable `PATH`
- Set environment variable `ANDROID_SDK_ROOT` to the Android SDK folder (can be found in Android Studio settings)
- Run `./build.py ndk` to let the script download and install NDK for you
- Set configurations in `config.prop`. A sample `config.prop.sample` is provided.
- To start building, run `build.py` to see your options. \
For each action, use `-h` to access help (e.g. `./build.py all -h`)
- To start development, open the project in Android Studio. Both app (Kotlin/Java) and native (C++/C) source code can be properly developed using the IDE, but *always* use `build.py` for building.
- `build.py` builds in debug mode by default. If you want release builds (with `-r, --release`), you need a Java Keystore to sign APKs and zips. For more information, check [Google's Documentation](https://developer.android.com/studio/publish/app-signing.html#generate-key).
- Optionally, set custom configs with `config.prop`. A sample `config.prop.sample` is provided.
- To sign APKs and zips with your own private keys, set signing configs in `config.prop`. For more info, check [Google's Documentation](https://developer.android.com/studio/publish/app-signing.html#generate-key).
## Translation Contributions

View File

@@ -1,7 +1,9 @@
import java.io.PrintStream
plugins {
id("com.android.application")
kotlin("android")
kotlin("android.extensions")
kotlin("plugin.parcelize")
kotlin("kapt")
id("androidx.navigation.safeargs.kotlin")
}
@@ -20,12 +22,11 @@ android {
applicationId = "com.topjohnwu.magisk"
vectorDrawables.useSupportLibrary = true
multiDexEnabled = true
versionName = Config["appVersion"]
versionCode = Config["appVersionCode"]?.toInt()
buildConfigField("int", "LATEST_MAGISK", Config["versionCode"] ?: "Integer.MAX_VALUE")
versionName = Config.appVersion
versionCode = Config.appVersionCode
javaCompileOptions.annotationProcessorOptions.arguments(
mapOf("room.incremental" to "true")
mapOf("room.incremental" to "true")
)
}
@@ -64,28 +65,61 @@ android {
}
}
androidExtensions {
isExperimental = true
}
val copyUtils = tasks.register("copyUtils", Copy::class) {
tasks["preBuild"]?.dependsOn(tasks.register("copyUtils", Copy::class) {
from(rootProject.file("scripts/util_functions.sh"))
into("src/main/res/raw")
}
})
tasks["preBuild"]?.dependsOn(copyUtils)
android.applicationVariants.all {
val keysDir = rootProject.file("tools/keys")
val outSrcDir = File(buildDir, "generated/source/keydata/$name")
val outSrc = File(outSrcDir, "com/topjohnwu/signing/KeyData.java")
fun PrintStream.newField(name: String, file: File) {
println("public static byte[] $name() {")
print("byte[] buf = {")
val bytes = file.readBytes()
print(bytes.joinToString(",") { "(byte)(${it.toInt() and 0xff})" })
println("};")
println("return buf;")
println("}")
}
val genSrcTask = tasks.register("generate${name.capitalize()}KeyData") {
inputs.dir(keysDir)
outputs.file(outSrc)
doLast {
outSrc.parentFile.mkdirs()
PrintStream(outSrc).use {
it.println("package com.topjohnwu.signing;")
it.println("public final class KeyData {")
it.newField("testCert", File(keysDir, "testkey.x509.pem"))
it.newField("testKey", File(keysDir, "testkey.pk8"))
it.newField("verityCert", File(keysDir, "verity.x509.pem"))
it.newField("verityKey", File(keysDir, "verity.pk8"))
it.println("}")
}
}
}
registerJavaGeneratingTask(genSrcTask.get(), outSrcDir)
}
dependencies {
implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar"))))
implementation(kotlin("stdlib"))
implementation(project(":app:shared"))
implementation(project(":app:signing"))
implementation("com.github.topjohnwu:jtar:1.0.0")
implementation("com.github.topjohnwu:indeterminate-checkbox:1.0.7")
implementation("com.github.topjohnwu:lz4-java:1.7.1")
implementation("com.jakewharton.timber:timber:4.7.1")
val vBC = "1.68"
implementation("org.bouncycastle:bcprov-jdk15on:${vBC}")
implementation("org.bouncycastle:bcpkix-jdk15on:${vBC}")
val vBAdapt = "4.0.0"
val bindingAdapter = "me.tatarka.bindingcollectionadapter2:bindingcollectionadapter"
implementation("${bindingAdapter}:${vBAdapt}")
@@ -124,7 +158,7 @@ dependencies {
implementation("com.squareup.moshi:moshi:${vMoshi}")
kapt("com.squareup.moshi:moshi-kotlin-codegen:${vMoshi}")
val vRoom = "2.3.0-alpha03"
val vRoom = "2.3.0-alpha04"
implementation("androidx.room:room-runtime:${vRoom}")
implementation("androidx.room:room-ktx:${vRoom}")
kapt("androidx.room:room-compiler:${vRoom}")
@@ -136,7 +170,7 @@ dependencies {
implementation("androidx.biometric:biometric:1.0.1")
implementation("androidx.constraintlayout:constraintlayout:2.0.4")
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
implementation("androidx.browser:browser:1.2.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")

View File

@@ -1 +0,0 @@
/build

View File

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

View File

@@ -1,49 +0,0 @@
package com.topjohnwu.signing;
import java.io.FileInputStream;
import java.io.InputStream;
public class BootSigner {
public static void main(String[] args) throws Exception {
if (args.length > 0 && "-verify".equals(args[0])) {
String certPath = "";
if (args.length >= 2) {
/* args[1] is the path to a public key certificate */
certPath = args[1];
}
boolean signed = SignBoot.verifySignature(System.in,
certPath.isEmpty() ? null : new FileInputStream(certPath));
System.exit(signed ? 0 : 1);
} else if (args.length > 0 && "-sign".equals(args[0])) {
InputStream cert = null;
InputStream key = null;
String name = "/boot";
if (args.length >= 3) {
cert = new FileInputStream(args[1]);
key = new FileInputStream(args[2]);
}
if (args.length == 2) {
name = args[1];
} else if (args.length >= 4) {
name = args[3];
}
boolean success = SignBoot.doSignature(name, System.in, System.out, cert, key);
System.exit(success ? 0 : 1);
} else {
System.err.println(
"BootSigner <actions> [args]\n" +
"Input from stdin, outputs to stdout\n" +
"\n" +
"Actions:\n" +
" -verify [x509.pem]\n" +
" verify image, cert is optional\n" +
" -sign [x509.pem] [pk8] [name]\n" +
" sign image, name, cert and key pair are optional\n" +
" name should be /boot (default) or /recovery\n"
);
}
}
}

View File

@@ -1,81 +0,0 @@
package com.topjohnwu.signing;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Security;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
public class ZipSigner {
private static void usage() {
System.err.println("ZipSigner usage:");
System.err.println(" zipsigner.jar input.jar output.jar");
System.err.println(" sign jar with AOSP test keys");
System.err.println(" zipsigner.jar x509.pem pk8 input.jar output.jar");
System.err.println(" sign jar with certificate / private key pair");
System.err.println(" zipsigner.jar keyStore keyStorePass alias keyPass input.jar output.jar");
System.err.println(" sign jar with Java KeyStore");
System.exit(2);
}
private static void sign(JarMap input, FileOutputStream output) throws Exception {
sign(SignApk.class.getResourceAsStream("/keys/testkey.x509.pem"),
SignApk.class.getResourceAsStream("/keys/testkey.pk8"), input, output);
}
private static void sign(InputStream certIs, InputStream keyIs,
JarMap input, FileOutputStream output) throws Exception {
X509Certificate cert = CryptoUtils.readCertificate(certIs);
PrivateKey key = CryptoUtils.readPrivateKey(keyIs);
SignApk.sign(cert, key, input, output);
}
private static void sign(String keyStore, String keyStorePass, String alias, String keyPass,
JarMap in, FileOutputStream out) throws Exception {
KeyStore ks;
try {
ks = KeyStore.getInstance("JKS");
try (InputStream is = new FileInputStream(keyStore)) {
ks.load(is, keyStorePass.toCharArray());
}
} catch (KeyStoreException|IOException|CertificateException|NoSuchAlgorithmException e) {
ks = KeyStore.getInstance("PKCS12");
try (InputStream is = new FileInputStream(keyStore)) {
ks.load(is, keyStorePass.toCharArray());
}
}
X509Certificate cert = (X509Certificate) ks.getCertificate(alias);
PrivateKey key = (PrivateKey) ks.getKey(alias, keyPass.toCharArray());
SignApk.sign(cert, key, in, out);
}
public static void main(String[] args) throws Exception {
if (args.length != 2 && args.length != 4 && args.length != 6)
usage();
Security.insertProviderAt(new BouncyCastleProvider(), 1);
try (JarMap in = JarMap.open(args[args.length - 2], false);
FileOutputStream out = new FileOutputStream(args[args.length - 1])) {
if (args.length == 2) {
sign(in, out);
} else if (args.length == 4) {
try (InputStream cert = new FileInputStream(args[0]);
InputStream key = new FileInputStream(args[1])) {
sign(cert, key, in, out);
}
} else if (args.length == 6) {
sign(args[0], args[1], args[2], args[3], in, out);
}
}
}
}

View File

@@ -12,7 +12,7 @@
<application
android:icon="@drawable/ic_launcher"
android:name="a.e"
android:allowBackup="true"
android:allowBackup="false"
tools:ignore="UnusedAttribute,GoogleAppIndexingWarning">
<!-- Splash -->

View File

@@ -8,10 +8,10 @@ import com.topjohnwu.magisk.core.SplashActivity
import com.topjohnwu.magisk.core.download.DownloadService
import com.topjohnwu.magisk.ui.MainActivity
import com.topjohnwu.magisk.ui.surequest.SuRequestActivity
import com.topjohnwu.signing.BootSigner
import com.topjohnwu.signing.SignBoot
fun main(args: Array<String>) {
BootSigner.main(args)
SignBoot.main(args)
}
class b : MainActivity()

View File

@@ -1,5 +1,8 @@
package com.topjohnwu.magisk.arch
import android.content.res.Resources
import android.graphics.Color
import android.os.Build
import android.os.Bundle
import android.view.KeyEvent
import android.view.View
@@ -14,6 +17,7 @@ import androidx.navigation.fragment.NavHostFragment
import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.core.base.BaseActivity
import com.topjohnwu.magisk.ui.inflater.LayoutInflaterFactory
import com.topjohnwu.magisk.ui.theme.Theme
abstract class BaseUIActivity<VM : BaseViewModel, Binding : ViewDataBinding> :
@@ -41,6 +45,8 @@ abstract class BaseUIActivity<VM : BaseViewModel, Binding : ViewDataBinding> :
}
override fun onCreate(savedInstanceState: Bundle?) {
layoutInflater.factory2 = LayoutInflaterFactory(delegate)
setTheme(themeRes)
super.onCreate(savedInstanceState)
@@ -59,6 +65,31 @@ abstract class BaseUIActivity<VM : BaseViewModel, Binding : ViewDataBinding> :
directionsDispatcher.value = null
}
}
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
}
}
}
}
}
}
fun setContentView() {
@@ -66,8 +97,6 @@ abstract class BaseUIActivity<VM : BaseViewModel, Binding : ViewDataBinding> :
it.setVariable(BR.viewModel, viewModel)
it.lifecycleOwner = this
}
ensureInsets()
}
fun setAccessibilityDelegate(delegate: View.AccessibilityDelegate?) {

View File

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

View File

@@ -24,8 +24,6 @@ abstract class BaseUIFragment<VM : BaseViewModel, Binding : ViewDataBinding> :
override val viewRoot: View get() = binding.root
private val navigation get() = activity.navigation
override fun consumeSystemWindowInsets(insets: Insets) = insets
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
startObserveEvents()
@@ -43,6 +41,11 @@ abstract class BaseUIFragment<VM : BaseViewModel, Binding : ViewDataBinding> :
return binding.root
}
override fun onStart() {
super.onStart()
activity.supportActionBar?.subtitle = null
}
override fun onEventDispatched(event: ViewEvent) = when(event) {
is ContextExecutor -> event(requireContext())
is ActivityExecutor -> event(activity)
@@ -65,7 +68,6 @@ abstract class BaseUIFragment<VM : BaseViewModel, Binding : ViewDataBinding> :
return true
}
})
ensureInsets()
}
override fun onResume() {

View File

@@ -18,7 +18,6 @@ import com.topjohnwu.magisk.ktx.inject
import com.topjohnwu.magisk.ui.theme.Theme
import org.xmlpull.v1.XmlPullParser
import java.io.File
import java.io.IOException
import java.io.InputStream
object Config : PreferenceModel, DBConfig {
@@ -159,12 +158,13 @@ object Config : PreferenceModel, DBConfig {
private const val SU_FINGERPRINT = "su_fingerprint"
fun load(pkg: String) {
try {
fun load(pkg: String?) {
// Only try to load prefs when fresh install and a previous package name is set
if (pkg != null && prefs.all.isEmpty()) runCatching {
context.contentResolver.openInputStream(Provider.PREFS_URI(pkg))?.use {
prefs.edit { parsePrefs(it) }
}
} catch (e: IOException) {}
}
prefs.edit {
// Settings migration

View File

@@ -16,8 +16,6 @@ object Const {
const val BOOTCTL_REVISION = "18ab78817087c337ae0edd1ecac38aec49217880"
// Misc
const val ANDROID_MANIFEST = "AndroidManifest.xml"
const val MAGISK_INSTALL_LOG_FILENAME = "magisk_install_log_%s.log"
val USER_ID = Process.myUid() / 100000
object Version {
@@ -27,6 +25,7 @@ object Const {
fun atLeast_20_2() = Info.env.magiskVersionCode >= 20200 || isCanary()
fun atLeast_20_4() = Info.env.magiskVersionCode >= 20400 || isCanary()
fun atLeast_21_0() = Info.env.magiskVersionCode >= 21000 || isCanary()
fun atLeast_21_2() = Info.env.magiskVersionCode >= 21200 || isCanary()
fun isCanary() = Info.env.magiskVersionCode % 100 != 0
}
@@ -38,7 +37,6 @@ object Const {
// notifications
const val MAGISK_UPDATE_NOTIFICATION_ID = 4
const val APK_UPDATE_NOTIFICATION_ID = 5
const val HIDE_MANAGER_NOTIFICATION_ID = 8
const val UPDATE_NOTIFICATION_CHANNEL = "update"
const val PROGRESS_NOTIFICATION_CHANNEL = "progress"
const val CHECK_MAGISK_UPDATE_WORKER_ID = "magisk_update"
@@ -58,7 +56,7 @@ object Const {
object Key {
// intents
const val OPEN_SECTION = "section"
const val HIDDEN_PKG = "hidden_pkg"
const val PREV_PKG = "prev_pkg"
}
object Value {

View File

@@ -48,10 +48,10 @@ open class SplashActivity : Activity() {
// Pre-initialize root shell
Shell.getShell()
val hiddenPackage = intent.getStringExtra(Const.Key.HIDDEN_PKG)
val prevPkg = intent.getStringExtra(Const.Key.PREV_PKG)
Config.load(hiddenPackage ?: APPLICATION_ID)
handleRepackage(hiddenPackage)
Config.load(prevPkg)
handleRepackage(prevPkg)
Notifications.setup(this)
UpdateCheckService.schedule(this)
Shortcuts.setupDynamic(this)

View File

@@ -2,7 +2,7 @@ package com.topjohnwu.magisk.core.download
import android.net.Uri
import android.os.Parcelable
import kotlinx.android.parcel.Parcelize
import kotlinx.parcelize.Parcelize
sealed class Action : Parcelable {

View File

@@ -12,8 +12,8 @@ import com.topjohnwu.magisk.core.model.module.OnlineModule
import com.topjohnwu.magisk.core.utils.MediaStoreUtils
import com.topjohnwu.magisk.ktx.cachedFile
import com.topjohnwu.magisk.ktx.get
import kotlinx.android.parcel.IgnoredOnParcel
import kotlinx.android.parcel.Parcelize
import kotlinx.parcelize.IgnoredOnParcel
import kotlinx.parcelize.Parcelize
private fun cachedFile(name: String) = get<Context>().cachedFile(name).apply { delete() }.toUri()
@@ -101,7 +101,6 @@ sealed class Subject : Parcelable {
Action.Download -> Download()
Action.Uninstall -> Uninstall()
Action.EnvFix, is Action.Flash, is Action.Patch -> Internal(config)
else -> throw IllegalArgumentException()
}
}

View File

@@ -2,7 +2,7 @@ package com.topjohnwu.magisk.core.model
import android.os.Parcelable
import com.squareup.moshi.JsonClass
import kotlinx.android.parcel.Parcelize
import kotlinx.parcelize.Parcelize
@JsonClass(generateAdapter = true)
data class UpdateInfo(

View File

@@ -27,13 +27,13 @@ class LocalModule(path: String) : Module() {
val dir = "$PERSIST/$id"
if (enable) {
disableFile.delete()
if (Const.Version.isCanary())
if (Const.Version.atLeast_21_2())
Shell.su("copy_sepolicy_rules").submit()
else
Shell.su("mkdir -p $dir", "cp -af $ruleFile $dir").submit()
} else {
!disableFile.createNewFile()
if (Const.Version.isCanary())
if (Const.Version.atLeast_21_2())
Shell.su("copy_sepolicy_rules").submit()
else
Shell.su("rm -rf $dir").submit()
@@ -45,13 +45,13 @@ class LocalModule(path: String) : Module() {
set(remove) {
if (remove) {
removeFile.createNewFile()
if (Const.Version.isCanary())
if (Const.Version.atLeast_21_2())
Shell.su("copy_sepolicy_rules").submit()
else
Shell.su("rm -rf $PERSIST/$id").submit()
} else {
!removeFile.delete()
if (Const.Version.isCanary())
if (Const.Version.atLeast_21_2())
Shell.su("copy_sepolicy_rules").submit()
else
Shell.su("cp -af $ruleFile $PERSIST/$id").submit()

View File

@@ -7,7 +7,7 @@ import com.topjohnwu.magisk.core.model.ModuleJson
import com.topjohnwu.magisk.data.repository.NetworkService
import com.topjohnwu.magisk.ktx.get
import com.topjohnwu.magisk.ktx.legalFilename
import kotlinx.android.parcel.Parcelize
import kotlinx.parcelize.Parcelize
import java.text.DateFormat
import java.util.*

View File

@@ -33,6 +33,7 @@ object HideAPK {
private const val ALPHA = "abcdefghijklmnopqrstuvwxyz"
private const val ALPHADOTS = "$ALPHA....."
private const val APP_NAME = "Magisk Manager"
private const val ANDROID_MANIFEST = "AndroidManifest.xml"
// Some arbitrary limit
const val MAX_LABEL_LENGTH = 32
@@ -71,7 +72,7 @@ object HideAPK {
): Boolean {
try {
val jar = JarMap.open(apk)
val je = jar.getJarEntry(Const.ANDROID_MANIFEST)
val je = jar.getJarEntry(ANDROID_MANIFEST)
val xml = AXML(jar.getRawData(je))
if (!xml.findAndPatch(APPLICATION_ID to pkg, APP_NAME to label.toString()))
@@ -123,6 +124,7 @@ object HideAPK {
Config.suManager = pkg
grantUriPermission(pkg, APK_URI, Intent.FLAG_GRANT_READ_URI_PERMISSION)
grantUriPermission(pkg, PREFS_URI, Intent.FLAG_GRANT_READ_URI_PERMISSION)
intent.putExtra(Const.Key.PREV_PKG, packageName)
startActivity(intent)
}
@@ -167,7 +169,7 @@ object HideAPK {
Config.suManager = ""
grantUriPermission(APPLICATION_ID, APK_URI, Intent.FLAG_GRANT_READ_URI_PERMISSION)
grantUriPermission(APPLICATION_ID, PREFS_URI, Intent.FLAG_GRANT_READ_URI_PERMISSION)
intent.putExtra(Const.Key.HIDDEN_PKG, packageName)
intent.putExtra(Const.Key.PREV_PKG, packageName)
startActivity(intent)
}

View File

@@ -8,8 +8,10 @@ import android.widget.Toast
import androidx.annotation.WorkerThread
import androidx.core.os.postDelayed
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import com.topjohnwu.magisk.BuildConfig
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.utils.MediaStoreUtils
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.inputStream
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream
@@ -40,6 +42,8 @@ import org.koin.core.inject
import timber.log.Timber
import java.io.*
import java.nio.ByteBuffer
import java.security.SecureRandom
import java.util.*
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
@@ -107,6 +111,8 @@ abstract class MagiskInstallImpl : KoinComponent {
}
console.add("- Device platform: " + Build.CPU_ABI)
console.add("- Magisk Manager: ${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})")
console.add("- Install target: ${Info.remote.magisk.version} (${Info.remote.magisk.versionCode})")
try {
ZipInputStream(zipUri.inputStream().buffered()).use { zi ->
@@ -185,9 +191,10 @@ abstract class MagiskInstallImpl : KoinComponent {
if (rawData.size < 256)
continue
// Patch flags to AVB_VBMETA_IMAGE_FLAGS_VERIFICATION_DISABLED
// Patch flags to AVB_VBMETA_IMAGE_FLAGS_HASHTREE_DISABLED |
// AVB_VBMETA_IMAGE_FLAGS_VERIFICATION_DISABLED
console.add("-- Patching: vbmeta.img")
ByteBuffer.wrap(rawData).putInt(120, 2)
ByteBuffer.wrap(rawData).putInt(120, 3)
tarOut.putNextEntry(newEntry("vbmeta.img", rawData.size.toLong()))
tarOut.write(rawData)
} else {
@@ -198,7 +205,7 @@ abstract class MagiskInstallImpl : KoinComponent {
}
val boot = SuFile.open(installDir, "boot.img")
val recovery = SuFile.open(installDir, "recovery.img")
if (recovery.exists() && boot.exists()) {
if (Config.recovery && recovery.exists() && boot.exists()) {
// Install Magisk to recovery
srcBoot = recovery.path
// Repack boot image to prevent restore
@@ -225,7 +232,7 @@ abstract class MagiskInstallImpl : KoinComponent {
private fun handleFile(uri: Uri): Boolean {
val outStream: OutputStream
val outFile: MediaStoreUtils.UriFile
var outFile: MediaStoreUtils.UriFile? = null
// Process input file
try {
@@ -237,27 +244,40 @@ abstract class MagiskInstallImpl : KoinComponent {
return false
}
src.reset()
val alpha = "abcdefghijklmnopqrstuvwxyz"
val alphaNum = "$alpha${alpha.toUpperCase(Locale.ROOT)}0123456789"
val random = SecureRandom()
val suffix = StringBuilder()
for (i in 1..5) {
suffix.append(alphaNum[random.nextInt(alphaNum.length)])
}
val filename = "magisk_patched_$suffix"
outStream = if (magic.contentEquals("ustar".toByteArray())) {
outFile = MediaStoreUtils.getFile("magisk_patched.tar")
handleTar(src, outFile.uri.outputStream())
outFile = MediaStoreUtils.getFile("$filename.tar", true)
handleTar(src, outFile!!.uri.outputStream())
} else {
// Raw image
srcBoot = File(installDir, "boot.img").path
console.add("- Copying image to cache")
FileOutputStream(srcBoot).use { src.copyTo(it) }
outFile = MediaStoreUtils.getFile("magisk_patched.img")
outFile.uri.outputStream()
outFile = MediaStoreUtils.getFile("$filename.img", true)
outFile!!.uri.outputStream()
}
}
} catch (e: IOException) {
console.add("! Process error")
outFile?.delete()
Timber.e(e)
return false
}
// Patch file
if (!patchBoot())
if (!patchBoot()) {
outFile!!.delete()
return false
}
// Output file
try {
@@ -276,6 +296,7 @@ abstract class MagiskInstallImpl : KoinComponent {
console.add("****************************")
} catch (e: IOException) {
console.add("! Failed to output to $outFile")
outFile!!.delete()
Timber.e(e)
return false
}
@@ -325,7 +346,7 @@ abstract class MagiskInstallImpl : KoinComponent {
val signed = File(installDir, "signed.img")
try {
withStreams(SuFileInputStream(patched), signed.outputStream().buffered()) {
input, out -> SignBoot.doSignature("/boot", input, out, null, null)
input, out -> SignBoot.doSignature(null, null, input, out, "/boot")
}
} catch (e: IOException) {
console.add("! Unable to sign image")
@@ -339,6 +360,13 @@ abstract class MagiskInstallImpl : KoinComponent {
return true
}
private fun copySepolicyRules(): Boolean {
if (Info.remote.magisk.versionCode >= 21100) return true
// Copy existing rules for migration
"copy_sepolicy_rules".sh()
return true
}
private fun flashBoot(): Boolean {
if (!"direct_install $installDir $srcBoot".sh().isSuccess)
return false
@@ -373,10 +401,11 @@ abstract class MagiskInstallImpl : KoinComponent {
protected fun doPatchFile(patchFile: Uri) = extractZip() && handleFile(patchFile)
protected fun direct() = findImage() && extractZip() && patchBoot() && flashBoot()
protected fun direct() = findImage() && extractZip() && patchBoot() &&
copySepolicyRules() && flashBoot()
protected suspend fun secondSlot() =
findSecondaryImage() && extractZip() && patchBoot() && flashBoot() && postOTA()
protected suspend fun secondSlot() = findSecondaryImage() && extractZip() &&
patchBoot() && copySepolicyRules() && flashBoot() && postOTA()
protected fun fixEnv(zip: Uri): Boolean {
installDir = SuFile("/data/adb/magisk")

View File

@@ -7,10 +7,12 @@ import android.util.Base64OutputStream
import com.topjohnwu.magisk.core.Config
import com.topjohnwu.signing.CryptoUtils.readCertificate
import com.topjohnwu.signing.CryptoUtils.readPrivateKey
import com.topjohnwu.signing.KeyData
import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.math.BigInteger
import java.security.KeyPairGenerator
@@ -58,10 +60,10 @@ class Keygen(context: Context) : CertKeyProvider {
class TestProvider : CertKeyProvider {
override val cert by lazy {
readCertificate(javaClass.getResourceAsStream("/keys/testkey.x509.pem"))
readCertificate(ByteArrayInputStream(KeyData.testCert()))
}
override val key by lazy {
readPrivateKey(javaClass.getResourceAsStream("/keys/testkey.pk8"))
readPrivateKey(ByteArrayInputStream(KeyData.testKey()))
}
}

View File

@@ -1,6 +1,5 @@
package com.topjohnwu.magisk.core.utils
import android.annotation.SuppressLint
import android.content.ContentResolver
import android.content.ContentUris
import android.content.ContentValues
@@ -40,15 +39,17 @@ object MediaStoreUtils {
private val relativePath get() = relativePath(Config.downloadDir)
@RequiresApi(api = 29)
@RequiresApi(api = 30)
@Throws(IOException::class)
private fun insertFile(displayName: String): MediaStoreFile {
val values = ContentValues()
values.put(MediaStore.MediaColumns.RELATIVE_PATH, relativePath)
values.put(MediaStore.MediaColumns.DISPLAY_NAME, displayName)
// before Android 11, MediaStore can not rename new file when file exists,
// insert will return null. use newFile() instead.
// When a file with the same name exists and was not created by us:
// - Before Android 11, insert will return null
// - On Android 11+, the system will automatically create a new name
// Thus the reason to restrict this method call to API 30+
val fileUri = cr.insert(tableUri, values) ?: throw IOException("Can't insert $displayName.")
val projection = arrayOf(MediaStore.MediaColumns._ID, MediaStore.MediaColumns.DATA)
@@ -65,14 +66,8 @@ object MediaStoreUtils {
throw IOException("Can't insert $displayName.")
}
@RequiresApi(api = 29)
private fun queryFile(displayName: String): UriFile? {
if (Build.VERSION.SDK_INT < 30) {
// Fallback to file based I/O pre Android 11
val parent = File(Environment.getExternalStorageDirectory(), relativePath)
parent.mkdirs()
return LegacyUriFile(File(parent, displayName))
}
val projection = arrayOf(MediaStore.MediaColumns._ID, MediaStore.MediaColumns.DATA)
// Before Android 10, we wrote the DISPLAY_NAME field when insert, so it can be used.
val selection = "${MediaStore.MediaColumns.DISPLAY_NAME} == ?"
@@ -92,11 +87,17 @@ object MediaStoreUtils {
return null
}
@SuppressLint("NewApi")
@Throws(IOException::class)
fun getFile(displayName: String): UriFile {
return queryFile(displayName) ?:
/* this code path will never happen pre 29 */ insertFile(displayName)
fun getFile(displayName: String, skipQuery: Boolean = false): UriFile {
if (Build.VERSION.SDK_INT < 30) {
// Fallback to file based I/O pre Android 11
val parent = File(Environment.getExternalStorageDirectory(), relativePath)
parent.mkdirs()
return LegacyUriFile(File(parent, displayName))
}
return if (skipQuery) insertFile(displayName)
else queryFile(displayName) ?: insertFile(displayName)
}
fun Uri.inputStream() = cr.openInputStream(this) ?: throw FileNotFoundException()

View File

@@ -29,6 +29,6 @@ val viewModelModules = module {
viewModel { MainViewModel() }
// Legacy
viewModel { (args: FlashFragmentArgs) -> FlashViewModel(args, get()) }
viewModel { (args: FlashFragmentArgs) -> FlashViewModel(args) }
viewModel { SuRequestViewModel(get(), get(), get(SUTimeout), get()) }
}

View File

@@ -0,0 +1,193 @@
@file:Suppress("unused")
package com.topjohnwu.magisk.ktx
import android.graphics.Canvas
import android.graphics.Rect
import android.view.View
import android.widget.EdgeEffect
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.topjohnwu.magisk.R
fun RecyclerView.addInvalidateItemDecorationsObserver() {
adapter?.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
invalidateItemDecorations()
}
override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) {
invalidateItemDecorations()
}
override fun onItemRangeMoved(fromPosition: Int, toPosition: Int, itemCount: Int) {
invalidateItemDecorations()
}
})
}
fun RecyclerView.addVerticalPadding(paddingTop: Int = 0, paddingBottom: Int = 0) {
addItemDecoration(VerticalPaddingDecoration(paddingTop, paddingBottom))
}
private class VerticalPaddingDecoration(private val paddingTop: Int = 0, private val paddingBottom: Int = 0) : RecyclerView.ItemDecoration() {
private var allowTop: Boolean = true
private var allowBottom: Boolean = true
override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
val adapter = parent.adapter ?: return
val position = parent.getChildAdapterPosition(view)
val count = adapter.itemCount
if (position == 0 && allowTop) {
outRect.top = paddingTop
} else if (position == count - 1 && allowBottom) {
outRect.bottom = paddingBottom
}
}
}
fun RecyclerView.addSimpleItemDecoration(
left: Int = 0,
top: Int = 0,
right: Int = 0,
bottom: Int = 0,
) {
addItemDecoration(SimpleItemDecoration(left, top, right, bottom))
}
private class SimpleItemDecoration(
private val left: Int = 0,
private val top: Int = 0,
private val right: Int = 0,
private val bottom: Int = 0
) : RecyclerView.ItemDecoration() {
private var allowLeft: Boolean = true
private var allowTop: Boolean = true
private var allowRight: Boolean = true
private var allowBottom: Boolean = true
override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
if (parent.adapter == null) {
return
}
if (allowLeft) {
outRect.left = left
}
if (allowTop) {
outRect.top = top
}
if (allowRight) {
outRect.right = right
}
if (allowBottom) {
outRect.top = bottom
}
}
}
fun RecyclerView.fixEdgeEffect(overScrollIfContentScrolls: Boolean = true, alwaysClipToPadding: Boolean = true) {
if (overScrollIfContentScrolls) {
val listener = OverScrollIfContentScrollsListener()
addOnLayoutChangeListener(listener)
setTag(R.id.tag_rikka_recyclerView_OverScrollIfContentScrollsListener, listener)
} else {
val listener = getTag(R.id.tag_rikka_recyclerView_OverScrollIfContentScrollsListener) as? OverScrollIfContentScrollsListener
if (listener != null) {
removeOnLayoutChangeListener(listener)
setTag(R.id.tag_rikka_recyclerView_OverScrollIfContentScrollsListener, null)
}
}
edgeEffectFactory = if (alwaysClipToPadding && !clipToPadding) {
AlwaysClipToPaddingEdgeEffectFactory()
} else {
RecyclerView.EdgeEffectFactory()
}
}
private class OverScrollIfContentScrollsListener : View.OnLayoutChangeListener {
private var show = true
override fun onLayoutChange(v: View, left: Int, top: Int, right: Int, bottom: Int, oldLeft: Int, oldTop: Int, oldRight: Int, oldBottom: Int) {
if (shouldDrawOverScroll(v as RecyclerView) != show) {
show = !show
if (show) {
v.setOverScrollMode(View.OVER_SCROLL_IF_CONTENT_SCROLLS)
} else {
v.setOverScrollMode(View.OVER_SCROLL_NEVER)
}
}
}
fun shouldDrawOverScroll(recyclerView: RecyclerView): Boolean {
if (recyclerView.layoutManager == null || recyclerView.adapter == null || recyclerView.adapter!!.itemCount == 0) {
return false
}
if (recyclerView.layoutManager is LinearLayoutManager) {
val itemCount = recyclerView.layoutManager!!.itemCount
val firstPosition: Int = (recyclerView.layoutManager as LinearLayoutManager?)!!.findFirstCompletelyVisibleItemPosition()
val lastPosition: Int = (recyclerView.layoutManager as LinearLayoutManager?)!!.findLastCompletelyVisibleItemPosition()
return firstPosition != 0 || lastPosition != itemCount - 1
}
return true
}
}
private class AlwaysClipToPaddingEdgeEffectFactory : RecyclerView.EdgeEffectFactory() {
override fun createEdgeEffect(view: RecyclerView, direction: Int): EdgeEffect {
return object : EdgeEffect(view.context) {
private var ensureSize = false
private fun ensureSize() {
if (ensureSize) return
ensureSize = true
when (direction) {
DIRECTION_LEFT -> {
setSize(view.measuredHeight - view.paddingTop - view.paddingBottom,
view.measuredWidth - view.paddingLeft - view.paddingRight)
}
DIRECTION_TOP -> {
setSize(view.measuredWidth - view.paddingLeft - view.paddingRight,
view.measuredHeight - view.paddingTop - view.paddingBottom)
}
DIRECTION_RIGHT -> {
setSize(view.measuredHeight - view.paddingTop - view.paddingBottom,
view.measuredWidth - view.paddingLeft - view.paddingRight)
}
DIRECTION_BOTTOM -> {
setSize(view.measuredWidth - view.paddingLeft - view.paddingRight,
view.measuredHeight - view.paddingTop - view.paddingBottom)
}
}
}
override fun draw(c: Canvas): Boolean {
ensureSize()
val restore = c.save()
when (direction) {
DIRECTION_LEFT -> {
c.translate(view.paddingBottom.toFloat(), 0f)
}
DIRECTION_TOP -> {
c.translate(view.paddingLeft.toFloat(), view.paddingTop.toFloat())
}
DIRECTION_RIGHT -> {
c.translate(-view.paddingTop.toFloat(), 0f)
}
DIRECTION_BOTTOM -> {
c.translate(view.paddingRight.toFloat(), view.paddingBottom.toFloat())
}
}
val res = super.draw(c)
c.restoreToCount(restore)
return res
}
}
}
}

View File

@@ -7,9 +7,11 @@ import android.content.Context
import android.content.ContextWrapper
import android.content.Intent
import android.content.pm.ApplicationInfo
import android.content.pm.ComponentInfo
import android.content.pm.PackageManager
import android.content.pm.PackageManager.*
import android.content.pm.ServiceInfo
import android.content.pm.ServiceInfo.FLAG_ISOLATED_PROCESS
import android.content.pm.ServiceInfo.FLAG_USE_APP_ZYGOTE
import android.content.res.Configuration
import android.content.res.Resources
import android.database.Cursor
@@ -57,32 +59,10 @@ import java.lang.reflect.Array as JArray
val packageName: String get() = get<Context>().packageName
val ApplicationInfo.processes: List<String> @SuppressLint("InlinedApi") get() {
val pm = get<PackageManager>()
val appProcessName = processName ?: packageName
val baseFlag = MATCH_DISABLED_COMPONENTS or MATCH_UNINSTALLED_PACKAGES
val packageInfo = try {
val request = GET_ACTIVITIES or GET_SERVICES or GET_RECEIVERS or GET_PROVIDERS
pm.getPackageInfo(packageName, baseFlag or request)
} catch (e: NameNotFoundException) { // EdXposed hooked, issue#3276
return listOf(appProcessName)
} catch (e: Exception) {
// Exceed binder data transfer limit, fetch each component type separately
pm.getPackageInfo(packageName, baseFlag).apply {
runCatching { activities = pm.getPackageInfo(packageName, GET_ACTIVITIES).activities }
runCatching { services = pm.getPackageInfo(packageName, GET_SERVICES).services }
runCatching { receivers = pm.getPackageInfo(packageName, GET_RECEIVERS).receivers }
runCatching { providers = pm.getPackageInfo(packageName, GET_PROVIDERS).providers }
}
}
fun Array<out ComponentInfo>.processNames() = map { it.processName ?: appProcessName }
return with(packageInfo) {
activities?.processNames().orEmpty() +
services?.processNames().orEmpty() +
receivers?.processNames().orEmpty() +
providers?.processNames().orEmpty()
}
}
val ServiceInfo.isIsolated get() = (flags and FLAG_ISOLATED_PROCESS) != 0
@get:SuppressLint("InlinedApi")
val ServiceInfo.useAppZygote get() = (flags and FLAG_USE_APP_ZYGOTE) != 0
fun Context.rawResource(id: Int) = resources.openRawResource(id)

View File

@@ -10,7 +10,6 @@ import android.view.WindowManager
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.content.pm.ShortcutManagerCompat
import androidx.core.view.forEach
import androidx.core.view.setPadding
import androidx.core.view.updateLayoutParams
import androidx.navigation.NavDirections
import com.google.android.material.card.MaterialCardView
@@ -26,9 +25,9 @@ import com.topjohnwu.magisk.ui.home.HomeFragmentDirections
import com.topjohnwu.magisk.utils.HideBottomViewOnScrollBehavior
import com.topjohnwu.magisk.utils.HideTopViewOnScrollBehavior
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 com.topjohnwu.superuser.Shell
import org.koin.androidx.viewmodel.ext.android.viewModel
class MainViewModel : BaseViewModel()
@@ -39,14 +38,6 @@ open class MainActivity : BaseUIActivity<MainViewModel, ActivityMainMd2Binding>(
override val viewModel by viewModel<MainViewModel>()
override val navHost: Int = R.id.main_nav_host
//This temporarily fixes unwanted feature of BottomNavigationView - where the view applies
//padding on itself given insets are not consumed beforehand. Unfortunately the listener
//implementation doesn't favor us against the design library, so on re-create it's often given
//upper hand.
private val navObserver = ViewTreeObserver.OnGlobalLayoutListener {
binding.mainNavigation.setPadding(0)
}
private var isRootFragment = true
override fun onCreate(savedInstanceState: Bundle?) {
@@ -100,8 +91,6 @@ open class MainActivity : BaseUIActivity<MainViewModel, ActivityMainMd2Binding>(
(currentFragment as? ReselectionTarget)?.onReselected()
}
binding.mainNavigation.viewTreeObserver.addOnGlobalLayoutListener(navObserver)
val section = if (intent.action == ACTION_APPLICATION_PREFERENCES) Const.Nav.SETTINGS
else intent.getStringExtra(Const.Key.OPEN_SECTION)
getScreen(section)?.navigate()
@@ -116,16 +105,11 @@ open class MainActivity : BaseUIActivity<MainViewModel, ActivityMainMd2Binding>(
override fun onResume() {
super.onResume()
binding.mainNavigation.menu.apply {
findItem(R.id.superuserFragment)?.isEnabled = Info.env.isActive
findItem(R.id.superuserFragment)?.isEnabled = Utils.showSuperUser()
findItem(R.id.logFragment)?.isEnabled = Info.env.isActive
}
}
override fun onDestroy() {
binding.mainNavigation.viewTreeObserver.removeOnGlobalLayoutListener(navObserver)
super.onDestroy()
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
android.R.id.home -> onBackPressed()

View File

@@ -32,6 +32,10 @@ class FlashFragment : BaseUIFragment<FlashViewModel, FragmentFlashMd2Binding>()
super.onStart()
setHasOptionsMenu(true)
activity.setTitle(R.string.flash_screen_title)
viewModel.subtitle.observe(this) {
activity.supportActionBar?.setSubtitle(it)
}
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {

View File

@@ -1,9 +1,10 @@
package com.topjohnwu.magisk.ui.flash
import android.content.res.Resources
import android.net.Uri
import android.view.MenuItem
import androidx.databinding.Bindable
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.R
@@ -13,31 +14,28 @@ import com.topjohnwu.magisk.arch.itemBindingOf
import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.tasks.FlashZip
import com.topjohnwu.magisk.core.tasks.MagiskInstaller
import com.topjohnwu.magisk.core.utils.MediaStoreUtils
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream
import com.topjohnwu.magisk.databinding.RvBindingAdapter
import com.topjohnwu.magisk.events.SnackbarEvent
import com.topjohnwu.magisk.ktx.*
import com.topjohnwu.magisk.core.utils.MediaStoreUtils
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream
import com.topjohnwu.magisk.utils.set
import com.topjohnwu.magisk.view.Notifications
import com.topjohnwu.superuser.CallbackList
import com.topjohnwu.superuser.Shell
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class FlashViewModel(
args: FlashFragmentArgs,
private val resources: Resources
args: FlashFragmentArgs
) : BaseViewModel() {
@get:Bindable
var showReboot = Shell.rootAccess()
set(value) = set(value, field, { field = it }, BR.showReboot)
@get:Bindable
var behaviorText = resources.getString(R.string.flashing)
set(value) = set(value, field, { field = it }, BR.behaviorText)
private val _subtitle = MutableLiveData(R.string.flashing)
val subtitle get() = _subtitle as LiveData<Int>
val adapter = RvBindingAdapter<ConsoleItem>()
val items = diffListOf<ConsoleItem>()
@@ -92,9 +90,9 @@ class FlashViewModel(
private fun onResult(success: Boolean) {
state = if (success) State.LOADED else State.LOADING_FAILED
behaviorText = when {
success -> resources.getString(R.string.done)
else -> resources.getString(R.string.failure)
when {
success -> _subtitle.postValue(R.string.done)
else -> _subtitle.postValue(R.string.failure)
}
}
@@ -106,18 +104,16 @@ class FlashViewModel(
}
private fun savePressed() = withExternalRW {
viewModelScope.launch {
withContext(Dispatchers.IO) {
val name = Const.MAGISK_INSTALL_LOG_FILENAME.format(now.toTime(timeFormatStandard))
val file = MediaStoreUtils.getFile(name)
file.uri.outputStream().bufferedWriter().use { writer ->
logItems.forEach {
writer.write(it)
writer.newLine()
}
viewModelScope.launch(Dispatchers.IO) {
val name = "magisk_install_log_%s.log".format(now.toTime(timeFormatStandard))
val file = MediaStoreUtils.getFile(name, true)
file.uri.outputStream().bufferedWriter().use { writer ->
logItems.forEach {
writer.write(it)
writer.newLine()
}
SnackbarEvent(file.toString()).publish()
}
SnackbarEvent(file.toString()).publish()
}
}

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