mirror of
https://github.com/topjohnwu/Magisk.git
synced 2025-12-12 12:29:53 +00:00
Compare commits
183 Commits
canary-270
...
canary-281
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b62835cbeb | ||
|
|
6ea740b5ab | ||
|
|
7ab98dd5ac | ||
|
|
fc8b3400fc | ||
|
|
54428ba415 | ||
|
|
95d1e69d8e | ||
|
|
a0f13ab49f | ||
|
|
c3e8405020 | ||
|
|
a93593ea66 | ||
|
|
23eff70883 | ||
|
|
110dd4a8b9 | ||
|
|
d9c2bffc9f | ||
|
|
049db49dc8 | ||
|
|
7c1d2ec61e | ||
|
|
a1b2830c06 | ||
|
|
82d1d19267 | ||
|
|
4d4195c02d | ||
|
|
5637a258fc | ||
|
|
ee6810f417 | ||
|
|
7098248c64 | ||
|
|
0d31d356ef | ||
|
|
b782e7dcb7 | ||
|
|
a4671b4698 | ||
|
|
7edd8be169 | ||
|
|
24650eefe4 | ||
|
|
8e1a44e7eb | ||
|
|
2722875190 | ||
|
|
3ca6d06f69 | ||
|
|
10e47248de | ||
|
|
e73ff679ac | ||
|
|
53e401fa2d | ||
|
|
d2768357da | ||
|
|
a6c2ba7c1e | ||
|
|
aae5b466fb | ||
|
|
2b7be8b949 | ||
|
|
b6511a510d | ||
|
|
704541aef2 | ||
|
|
005560a4c5 | ||
|
|
231a5d1853 | ||
|
|
9e2b59060d | ||
|
|
08ea937f7c | ||
|
|
2baedf74d1 | ||
|
|
32faa4ced6 | ||
|
|
ccdb0b5d13 | ||
|
|
8506b672ad | ||
|
|
ce2e33bb20 | ||
|
|
6707b72260 | ||
|
|
5885b8c20d | ||
|
|
820710c086 | ||
|
|
51cf196bf7 | ||
|
|
dadba44cf9 | ||
|
|
2ce4a5543b | ||
|
|
9112a3a4f5 | ||
|
|
24615afda1 | ||
|
|
c5778f398b | ||
|
|
4eb4097b9b | ||
|
|
c512496847 | ||
|
|
506961a10d | ||
|
|
3414415907 | ||
|
|
dc2ae7cfd1 | ||
|
|
2e86d21c29 | ||
|
|
2654382c43 | ||
|
|
9e26b73813 | ||
|
|
10cd13bf80 | ||
|
|
f10ee5f887 | ||
|
|
47cc532d96 | ||
|
|
218327f92b | ||
|
|
4eae66a1a7 | ||
|
|
b09ceeb43c | ||
|
|
4fb539c110 | ||
|
|
849b284da5 | ||
|
|
895b5f6cbf | ||
|
|
cb3d4ea514 | ||
|
|
0d89a2a97d | ||
|
|
3ca5913055 | ||
|
|
df6b808f49 | ||
|
|
09c7ac754b | ||
|
|
805da67c23 | ||
|
|
3c6889505b | ||
|
|
c8e9ce7627 | ||
|
|
837c679a31 | ||
|
|
06616659b8 | ||
|
|
a34c04f999 | ||
|
|
da43ac89a0 | ||
|
|
830fc758b9 | ||
|
|
0f3cfef278 | ||
|
|
b32d7bfafd | ||
|
|
c0899f2939 | ||
|
|
082330808f | ||
|
|
024da05888 | ||
|
|
377b6d0cc2 | ||
|
|
c661009b31 | ||
|
|
613f2d31c5 | ||
|
|
7dbb973db5 | ||
|
|
f4502f8be8 | ||
|
|
455b13b83c | ||
|
|
8b98709743 | ||
|
|
1b12f45f39 | ||
|
|
a5cad532ff | ||
|
|
070719db50 | ||
|
|
28cccdf7aa | ||
|
|
b7e0986a5c | ||
|
|
da709745dd | ||
|
|
8b6771d487 | ||
|
|
e1b847fbc5 | ||
|
|
7188de1205 | ||
|
|
44fb7dbcbe | ||
|
|
8086b5933c | ||
|
|
7f675f4bf7 | ||
|
|
5e6b53e0da | ||
|
|
5b29fefc65 | ||
|
|
16a168535d | ||
|
|
33f70f8f6d | ||
|
|
4f18a66d73 | ||
|
|
250dc16007 | ||
|
|
7af273e047 | ||
|
|
e381aebaa0 | ||
|
|
45d91c9658 | ||
|
|
4a9158f667 | ||
|
|
0d9ee89e7f | ||
|
|
abaff72304 | ||
|
|
b828e2d0b2 | ||
|
|
53d7cbc11b | ||
|
|
310be7ab47 | ||
|
|
60894e458f | ||
|
|
fbebb6ac10 | ||
|
|
a9f8c20703 | ||
|
|
ae0b15d197 | ||
|
|
869aa62328 | ||
|
|
dcd3bc58a3 | ||
|
|
a82f17c594 | ||
|
|
b38fd1ca5f | ||
|
|
8e82113bce | ||
|
|
f723ef153b | ||
|
|
1dc723fb6d | ||
|
|
8f271c2575 | ||
|
|
7cf56b4406 | ||
|
|
c2eb603957 | ||
|
|
e6bd2ff60f | ||
|
|
5604074eba | ||
|
|
3f061c1a1e | ||
|
|
55e78a7b1a | ||
|
|
000f1d6041 | ||
|
|
2cbec20238 | ||
|
|
4b724c7257 | ||
|
|
ab04c6ab39 | ||
|
|
821a6c6954 | ||
|
|
5f27a62221 | ||
|
|
c76cc4c6bd | ||
|
|
52b75c53b6 | ||
|
|
9db2e99086 | ||
|
|
e9e2ecf2dd | ||
|
|
9a9e617c35 | ||
|
|
3a0becc783 | ||
|
|
1f974cb220 | ||
|
|
1db80228e8 | ||
|
|
838e1e254d | ||
|
|
554eda8fe1 | ||
|
|
2bdc047c4d | ||
|
|
e64f59ce5b | ||
|
|
b8140ad4e6 | ||
|
|
5a55483698 | ||
|
|
2d341863f5 | ||
|
|
278046becb | ||
|
|
5c0497354f | ||
|
|
98c258df93 | ||
|
|
c578cccfd5 | ||
|
|
07835a3e0e | ||
|
|
09131aca89 | ||
|
|
9ce998a6df | ||
|
|
ca36b42d79 | ||
|
|
37df39ec37 | ||
|
|
1701361a73 | ||
|
|
4c14ae33f5 | ||
|
|
d4a9ef7b7f | ||
|
|
1539cfe888 | ||
|
|
9093be1329 | ||
|
|
606d076251 | ||
|
|
46a34e19bc | ||
|
|
5ac7dc0b37 | ||
|
|
3b27de3715 | ||
|
|
939bfac920 | ||
|
|
f601bf12d5 |
94
.github/actions/setup/action.yml
vendored
94
.github/actions/setup/action.yml
vendored
@@ -1,44 +1,100 @@
|
||||
name: Magisk Setup
|
||||
inputs:
|
||||
is-asset-build:
|
||||
required: false
|
||||
default: false
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Set up JDK 17
|
||||
- name: Set up JDK 21
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: "temurin"
|
||||
java-version: "17"
|
||||
java-version: "21"
|
||||
|
||||
- name: Set up Python 3
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.x"
|
||||
|
||||
- name: Set up sccache
|
||||
uses: hendrikmuhs/ccache-action@v1.2
|
||||
- name: Install GNU make
|
||||
if: runner.os == 'macOS'
|
||||
shell: bash
|
||||
run: |
|
||||
brew install make
|
||||
echo 'GNUMAKE=gmake' >> "$GITHUB_ENV"
|
||||
|
||||
- name: Cache sccache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
variant: sccache
|
||||
key: ${{ runner.os }}-${{ github.sha }}
|
||||
restore-keys: ${{ runner.os }}
|
||||
max-size: 10000M
|
||||
path: .sccache
|
||||
key: sccache-${{ runner.os }}-${{ github.sha }}
|
||||
restore-keys: sccache-${{ runner.os }}-
|
||||
|
||||
- name: Set up sccache
|
||||
shell: bash
|
||||
env:
|
||||
SCCACHE_DIRECT: false
|
||||
SCCACHE_DIR: ${{ github.workspace }}/.sccache
|
||||
SCCACHE_CACHE_SIZE: 2G
|
||||
SCCACHE_IDLE_TIMEOUT: 0
|
||||
run: |
|
||||
bash $GITHUB_ACTION_PATH/sccache.sh
|
||||
sccache --start-server
|
||||
sccache -z
|
||||
|
||||
- name: Show sccache stats
|
||||
uses: gacts/run-and-post-run@v1
|
||||
with:
|
||||
run: sccache -s
|
||||
post: sccache -s
|
||||
|
||||
- name: Set GRADLE_USER_HOME
|
||||
shell: bash
|
||||
run: echo "GRADLE_USER_HOME=$GITHUB_WORKSPACE/.gradle" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Cache Gradle dependencies
|
||||
uses: actions/cache@v4
|
||||
if: inputs.is-asset-build == 'true'
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
~/.gradle/wrapper
|
||||
!~/.gradle/caches/build-cache-*
|
||||
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle.kts') }}
|
||||
restore-keys: ${{ runner.os }}-gradle-
|
||||
.gradle/caches
|
||||
.gradle/wrapper
|
||||
!.gradle/caches/build-cache-*
|
||||
key: gradle-cache-${{ hashFiles('gradle/**') }}
|
||||
restore-keys: gradle-cache-
|
||||
|
||||
- name: Cache build cache
|
||||
uses: actions/cache@v4
|
||||
- name: Restore Gradle dependencies
|
||||
uses: actions/cache/restore@v4
|
||||
if: inputs.is-asset-build == 'false'
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches/build-cache-*
|
||||
key: ${{ runner.os }}-build-cache-${{ github.sha }}
|
||||
restore-keys: ${{ runner.os }}-build-cache-
|
||||
.gradle/caches
|
||||
.gradle/wrapper
|
||||
!.gradle/caches/build-cache-*
|
||||
key: gradle-cache-${{ hashFiles('gradle/**') }}
|
||||
restore-keys: gradle-cache-
|
||||
enableCrossOsArchive: true
|
||||
|
||||
- name: Cache Gradle build cache
|
||||
uses: actions/cache@v4
|
||||
if: inputs.is-asset-build == 'true'
|
||||
with:
|
||||
path: |
|
||||
.gradle/caches/build-cache-*
|
||||
key: gradle-build-cache-${{ github.sha }}
|
||||
restore-keys: gradle-build-cache-
|
||||
|
||||
- name: Restore Gradle build cache
|
||||
uses: actions/cache/restore@v4
|
||||
if: inputs.is-asset-build == 'false'
|
||||
with:
|
||||
path: |
|
||||
.gradle/caches/build-cache-*
|
||||
key: gradle-build-cache-${{ github.sha }}
|
||||
restore-keys: gradle-build-cache-
|
||||
enableCrossOsArchive: true
|
||||
|
||||
- name: Set up NDK
|
||||
run: python build.py -v ndk
|
||||
shell: bash
|
||||
run: python build.py -v ndk
|
||||
|
||||
25
.github/actions/setup/sccache.sh
vendored
Executable file
25
.github/actions/setup/sccache.sh
vendored
Executable file
@@ -0,0 +1,25 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Get latest sccache version
|
||||
get_sccache_ver() {
|
||||
curl -sL 'https://api.github.com/repos/mozilla/sccache/releases/latest' | jq -r .name
|
||||
}
|
||||
|
||||
# $1=variant
|
||||
# $2=install_dir
|
||||
# $3=exe
|
||||
install_from_gh() {
|
||||
local ver=$(curl -sL 'https://api.github.com/repos/mozilla/sccache/releases/latest' | jq -r .name)
|
||||
local url="https://github.com/mozilla/sccache/releases/download/${ver}/sccache-${ver}-$1.tar.gz"
|
||||
local dest="$2/$3"
|
||||
curl -L "$url" | tar xz -O --wildcards "*/$3" > $dest
|
||||
chmod +x $dest
|
||||
}
|
||||
|
||||
if [ $RUNNER_OS = "macOS" ]; then
|
||||
brew install sccache
|
||||
elif [ $RUNNER_OS = "Linux" ]; then
|
||||
install_from_gh x86_64-unknown-linux-musl /usr/local/bin sccache
|
||||
elif [ $RUNNER_OS = "Windows" ]; then
|
||||
install_from_gh x86_64-pc-windows-msvc $USERPROFILE/.cargo/bin sccache.exe
|
||||
fi
|
||||
1
.github/ci.prop
vendored
Normal file
1
.github/ci.prop
vendored
Normal file
@@ -0,0 +1 @@
|
||||
abiList=arm64-v8a
|
||||
73
.github/workflows/build.yml
vendored
73
.github/workflows/build.yml
vendored
@@ -1,15 +1,6 @@
|
||||
name: Magisk Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
paths:
|
||||
- "app/**"
|
||||
- "native/**"
|
||||
- "buildSrc/**"
|
||||
- "build.py"
|
||||
- "gradle.properties"
|
||||
- ".github/workflows/build.yml"
|
||||
pull_request:
|
||||
branches: [master]
|
||||
workflow_dispatch:
|
||||
@@ -17,9 +8,7 @@ on:
|
||||
jobs:
|
||||
build:
|
||||
name: Build Magisk artifacts
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
SCCACHE_DIRECT: false
|
||||
runs-on: macos-15
|
||||
strategy:
|
||||
fail-fast: false
|
||||
steps:
|
||||
@@ -27,10 +16,11 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: "recursive"
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup environment
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
is-asset-build: true
|
||||
|
||||
- name: Build release
|
||||
run: ./build.py -vr all
|
||||
@@ -58,46 +48,43 @@ jobs:
|
||||
test-build:
|
||||
name: Test building on ${{ matrix.os }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
env:
|
||||
SCCACHE_DIRECT: false
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [windows-latest, macos-14]
|
||||
os: [windows-2025, ubuntu-24.04]
|
||||
steps:
|
||||
- name: Check out
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: "recursive"
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup environment
|
||||
uses: ./.github/actions/setup
|
||||
|
||||
- name: Build debug
|
||||
run: python build.py -v all
|
||||
- name: Test build
|
||||
run: python build.py -v -c .github/ci.prop all
|
||||
|
||||
- name: Stop gradle daemon
|
||||
run: ./gradlew --stop
|
||||
|
||||
avd-test:
|
||||
name: Test API ${{ matrix.version }} (x86_64)
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
needs: build
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
version: [23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34]
|
||||
version: [23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35]
|
||||
type: [""]
|
||||
include:
|
||||
- version: 35
|
||||
- version: "Baklava"
|
||||
type: "google_apis"
|
||||
- version: "Baklava"
|
||||
type: "google_apis_ps16k"
|
||||
|
||||
steps:
|
||||
- name: Check out
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Download build artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
@@ -114,12 +101,21 @@ jobs:
|
||||
- name: Run AVD test
|
||||
timeout-minutes: 10
|
||||
env:
|
||||
AVD_TEST_VERBOSE: 1
|
||||
AVD_TEST_LOG: 1
|
||||
run: scripts/avd_test.sh ${{ matrix.version }} ${{ matrix.type }}
|
||||
|
||||
- name: Upload logs on error
|
||||
if: ${{ failure() }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: "avd-logs-${{ matrix.version }}"
|
||||
path: |
|
||||
kernel.log
|
||||
logcat.log
|
||||
|
||||
avd-test-32:
|
||||
name: Test API ${{ matrix.version }} (x86)
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
needs: build
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -129,8 +125,6 @@ jobs:
|
||||
steps:
|
||||
- name: Check out
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Download build artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
@@ -148,11 +142,20 @@ jobs:
|
||||
timeout-minutes: 10
|
||||
env:
|
||||
FORCE_32_BIT: 1
|
||||
AVD_TEST_VERBOSE: 1
|
||||
AVD_TEST_LOG: 1
|
||||
run: scripts/avd_test.sh ${{ matrix.version }}
|
||||
|
||||
cf_test:
|
||||
name: Test ${{ matrix.branch }} (${{ matrix.target }})
|
||||
- name: Upload logs on error
|
||||
if: ${{ failure() }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: "avd32-logs-${{ matrix.version }}"
|
||||
path: |
|
||||
kernel.log
|
||||
logcat.log
|
||||
|
||||
cf-test:
|
||||
name: Test ${{ matrix.device }}
|
||||
runs-on: ubuntu-24.04
|
||||
needs: build
|
||||
env:
|
||||
@@ -162,13 +165,11 @@ jobs:
|
||||
matrix:
|
||||
include:
|
||||
- branch: "aosp-main"
|
||||
target: "aosp_cf_x86_64_phone-trunk_staging-userdebug"
|
||||
device: "aosp_cf_x86_64_phone"
|
||||
|
||||
steps:
|
||||
- name: Check out
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Download build artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
@@ -179,7 +180,7 @@ jobs:
|
||||
- name: Setup Cuttlefish environment
|
||||
run: |
|
||||
scripts/cuttlefish.sh setup
|
||||
scripts/cuttlefish.sh download ${{ matrix.branch }} ${{ matrix.target }}
|
||||
scripts/cuttlefish.sh download ${{ matrix.branch }} ${{ matrix.device }}
|
||||
|
||||
- name: Run Cuttlefish test
|
||||
timeout-minutes: 10
|
||||
@@ -189,7 +190,7 @@ jobs:
|
||||
if: ${{ failure() }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: "cvd-logs"
|
||||
name: "cvd-logs-${{ matrix.device }}"
|
||||
path: |
|
||||
/home/runner/aosp_cf_phone/cuttlefish/instances/cvd-1/logs
|
||||
/home/runner/aosp_cf_phone/cuttlefish/instances/cvd-1/cuttlefish_config.json
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -13,7 +13,8 @@ native/out
|
||||
# Android Studio / Gradle
|
||||
*.iml
|
||||
.gradle
|
||||
.idea
|
||||
.kotlin
|
||||
/local.properties
|
||||
/.idea
|
||||
/build
|
||||
/captures
|
||||
|
||||
@@ -20,9 +20,9 @@ Some highlight features:
|
||||
|
||||
Click the icon below to download Magisk apk.
|
||||
|
||||
[](https://github.com/topjohnwu/Magisk/releases/tag/v27.0)
|
||||
[](https://github.com/topjohnwu/Magisk/releases/tag/v27.0)
|
||||
[](https://github.com/topjohnwu/Magisk/releases/tag/canary-27006)
|
||||
[](https://github.com/topjohnwu/Magisk/releases/tag/v28.1)
|
||||
[](https://github.com/topjohnwu/Magisk/releases/tag/v28.1)
|
||||
[](https://github.com/topjohnwu/Magisk/releases/tag/canary-28102)
|
||||
|
||||
## Useful Links
|
||||
|
||||
|
||||
@@ -6,63 +6,53 @@ plugins {
|
||||
id("androidx.navigation.safeargs.kotlin")
|
||||
}
|
||||
|
||||
setupAppCommon()
|
||||
setupMainApk()
|
||||
|
||||
kapt {
|
||||
correctErrorTypes = true
|
||||
useBuildCache = true
|
||||
mapDiagnosticLocations = true
|
||||
javacOptions {
|
||||
option("-Xmaxerrs", 1000)
|
||||
option("-Xmaxerrs", "1000")
|
||||
}
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.topjohnwu.magisk"
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "com.topjohnwu.magisk"
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
versionName = Config.version
|
||||
versionCode = Config.versionCode
|
||||
ndk {
|
||||
abiFilters += listOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64", "riscv64")
|
||||
debugSymbolLevel = "FULL"
|
||||
buildFeatures {
|
||||
dataBinding = true
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
isCoreLibraryDesugaringEnabled = true
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
isMinifyEnabled = true
|
||||
isShrinkResources = true
|
||||
proguardFiles("proguard-rules.pro")
|
||||
}
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
dataBinding = true
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":app:core"))
|
||||
coreLibraryDesugaring(libs.jdk.libs)
|
||||
|
||||
implementation("com.github.topjohnwu:indeterminate-checkbox:1.0.7")
|
||||
implementation("dev.rikka.rikkax.layoutinflater:layoutinflater:1.3.0")
|
||||
implementation("dev.rikka.rikkax.insets:insets:1.3.0")
|
||||
implementation("dev.rikka.rikkax.recyclerview:recyclerview-ktx:1.3.2")
|
||||
implementation(libs.indeterminate.checkbox)
|
||||
implementation(libs.rikka.layoutinflater)
|
||||
implementation(libs.rikka.insets)
|
||||
implementation(libs.rikka.recyclerview)
|
||||
|
||||
val vNav = "2.7.7"
|
||||
implementation("androidx.navigation:navigation-fragment-ktx:${vNav}")
|
||||
implementation("androidx.navigation:navigation-ui-ktx:${vNav}")
|
||||
implementation(libs.navigation.fragment.ktx)
|
||||
implementation(libs.navigation.ui.ktx)
|
||||
|
||||
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
|
||||
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
|
||||
implementation("androidx.recyclerview:recyclerview:1.3.2")
|
||||
implementation("androidx.transition:transition:1.5.1")
|
||||
implementation("androidx.fragment:fragment-ktx:1.8.2")
|
||||
implementation("androidx.appcompat:appcompat:1.7.0")
|
||||
implementation("com.google.android.material:material:1.12.0")
|
||||
implementation(libs.constraintlayout)
|
||||
implementation(libs.swiperefreshlayout)
|
||||
implementation(libs.recyclerview)
|
||||
implementation(libs.transition)
|
||||
implementation(libs.fragment.ktx)
|
||||
implementation(libs.appcompat)
|
||||
implementation(libs.material)
|
||||
|
||||
// Make sure kapt runs with a proper kotlin-stdlib
|
||||
kapt(kotlin("stdlib"))
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
package com.topjohnwu.magisk.dialog
|
||||
|
||||
import android.widget.Toast
|
||||
import androidx.core.os.postDelayed
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.topjohnwu.magisk.core.BuildConfig
|
||||
import com.topjohnwu.magisk.core.Info
|
||||
import com.topjohnwu.magisk.core.R
|
||||
import com.topjohnwu.magisk.core.ktx.reboot
|
||||
import com.topjohnwu.magisk.core.ktx.toast
|
||||
import com.topjohnwu.magisk.core.tasks.MagiskInstaller
|
||||
import com.topjohnwu.magisk.events.DialogBuilder
|
||||
import com.topjohnwu.magisk.ui.home.HomeViewModel
|
||||
import com.topjohnwu.magisk.view.MagiskDialog
|
||||
import com.topjohnwu.superuser.internal.UiThreadHandler
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class EnvFixDialog(private val vm: HomeViewModel, private val code: Int) : DialogBuilder {
|
||||
@@ -27,9 +32,15 @@ class EnvFixDialog(private val vm: HomeViewModel, private val code: Int) : Dialo
|
||||
setCancelable(false)
|
||||
}
|
||||
dialog.activity.lifecycleScope.launch {
|
||||
MagiskInstaller.FixEnv {
|
||||
MagiskInstaller.FixEnv().exec { success ->
|
||||
dialog.dismiss()
|
||||
}.exec()
|
||||
context.toast(
|
||||
if (success) R.string.reboot_delay_toast else R.string.setup_fail,
|
||||
Toast.LENGTH_LONG
|
||||
)
|
||||
if (success)
|
||||
UiThreadHandler.handler.postDelayed(5000) { reboot() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.topjohnwu.magisk.dialog
|
||||
|
||||
import android.content.Context
|
||||
import com.topjohnwu.magisk.core.R
|
||||
import com.topjohnwu.magisk.core.di.ServiceLocator
|
||||
import com.topjohnwu.magisk.core.download.DownloadEngine
|
||||
@@ -7,6 +8,8 @@ import com.topjohnwu.magisk.core.download.Subject
|
||||
import com.topjohnwu.magisk.core.model.module.OnlineModule
|
||||
import com.topjohnwu.magisk.ui.flash.FlashFragment
|
||||
import com.topjohnwu.magisk.view.MagiskDialog
|
||||
import com.topjohnwu.magisk.view.Notifications
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
class OnlineModuleInstallDialog(private val item: OnlineModule) : MarkDownDialog() {
|
||||
|
||||
@@ -17,14 +20,21 @@ class OnlineModuleInstallDialog(private val item: OnlineModule) : MarkDownDialog
|
||||
return if (str.length > 1000) str.substring(0, 1000) else str
|
||||
}
|
||||
|
||||
@Parcelize
|
||||
class Module(
|
||||
override val module: OnlineModule,
|
||||
override val autoLaunch: Boolean,
|
||||
override val notifyId: Int = Notifications.nextId()
|
||||
) : Subject.Module() {
|
||||
override fun pendingIntent(context: Context) = FlashFragment.installIntent(context, file)
|
||||
}
|
||||
|
||||
override fun build(dialog: MagiskDialog) {
|
||||
super.build(dialog)
|
||||
dialog.apply {
|
||||
|
||||
fun download(install: Boolean) {
|
||||
val module = Subject.Module(item, install)
|
||||
module.piCreator = FlashFragment::installIntent
|
||||
DownloadEngine.startWithActivity(activity, module)
|
||||
DownloadEngine.startWithActivity(activity, Module(item, install))
|
||||
}
|
||||
|
||||
val title = context.getString(R.string.repo_install_title,
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
package com.topjohnwu.magisk.dialog
|
||||
|
||||
import android.app.ProgressDialog
|
||||
import android.content.Context
|
||||
import android.widget.Toast
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.topjohnwu.magisk.arch.NavigationActivity
|
||||
import com.topjohnwu.magisk.arch.UIActivity
|
||||
import com.topjohnwu.magisk.core.R
|
||||
import com.topjohnwu.magisk.core.ktx.toast
|
||||
import com.topjohnwu.magisk.core.tasks.MagiskInstaller
|
||||
import com.topjohnwu.magisk.events.DialogBuilder
|
||||
import com.topjohnwu.magisk.ui.flash.FlashFragment
|
||||
import com.topjohnwu.magisk.view.MagiskDialog
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class UninstallDialog : DialogBuilder {
|
||||
|
||||
@@ -19,7 +21,7 @@ class UninstallDialog : DialogBuilder {
|
||||
setMessage(R.string.uninstall_magisk_msg)
|
||||
setButton(MagiskDialog.ButtonType.POSITIVE) {
|
||||
text = R.string.restore_img
|
||||
onClick { restore(dialog.context) }
|
||||
onClick { restore(dialog.activity) }
|
||||
}
|
||||
setButton(MagiskDialog.ButtonType.NEGATIVE) {
|
||||
text = R.string.complete_uninstall
|
||||
@@ -29,18 +31,20 @@ class UninstallDialog : DialogBuilder {
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
private fun restore(context: Context) {
|
||||
val dialog = ProgressDialog(context).apply {
|
||||
setMessage(context.getString(R.string.restore_img_msg))
|
||||
private fun restore(activity: UIActivity<*>) {
|
||||
val dialog = ProgressDialog(activity).apply {
|
||||
setMessage(activity.getString(R.string.restore_img_msg))
|
||||
show()
|
||||
}
|
||||
|
||||
Shell.cmd("restore_imgs").submit { result ->
|
||||
activity.lifecycleScope.launch {
|
||||
MagiskInstaller.Restore().exec { success ->
|
||||
dialog.dismiss()
|
||||
if (result.isSuccess) {
|
||||
context.toast(R.string.restore_done, Toast.LENGTH_SHORT)
|
||||
if (success) {
|
||||
activity.toast(R.string.restore_done, Toast.LENGTH_SHORT)
|
||||
} else {
|
||||
context.toast(R.string.restore_fail, Toast.LENGTH_LONG)
|
||||
activity.toast(R.string.restore_fail, Toast.LENGTH_LONG)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ class FlashFragment : BaseFragment<FragmentFlashMd2Binding>(), MenuProvider {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
defaultOrientation = activity?.requestedOrientation ?: -1
|
||||
activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_NOSENSOR
|
||||
activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LOCKED
|
||||
if (savedInstanceState == null) {
|
||||
viewModel.startFlashing()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
package com.topjohnwu.magisk.ui.module
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.pm.ActivityInfo
|
||||
import android.os.Bundle
|
||||
import android.view.KeyEvent
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewTreeObserver
|
||||
import android.widget.Toast
|
||||
import androidx.core.view.MenuProvider
|
||||
import androidx.core.view.isVisible
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.arch.BaseFragment
|
||||
import com.topjohnwu.magisk.arch.viewModel
|
||||
import com.topjohnwu.magisk.core.ktx.toast
|
||||
import com.topjohnwu.magisk.databinding.FragmentActionMd2Binding
|
||||
import com.topjohnwu.magisk.core.R as CoreR
|
||||
|
||||
class ActionFragment : BaseFragment<FragmentActionMd2Binding>(), MenuProvider {
|
||||
|
||||
override val layoutRes = R.layout.fragment_action_md2
|
||||
override val viewModel by viewModel<ActionViewModel>()
|
||||
override val snackbarView: View get() = binding.snackbarContainer
|
||||
|
||||
private var defaultOrientation = -1
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
viewModel.args = ActionFragmentArgs.fromBundle(requireArguments())
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
activity?.setTitle(viewModel.args.name)
|
||||
binding.closeBtn.setOnClickListener {
|
||||
activity?.onBackPressed()
|
||||
}
|
||||
|
||||
viewModel.state.observe(this) {
|
||||
if (it != ActionViewModel.State.RUNNING) {
|
||||
binding.closeBtn.apply {
|
||||
if (!this.isVisible) this.show()
|
||||
if (!this.isFocused) this.requestFocus()
|
||||
}
|
||||
}
|
||||
if (it != ActionViewModel.State.SUCCESS) return@observe
|
||||
view?.viewTreeObserver?.addOnWindowFocusChangeListener(
|
||||
object : ViewTreeObserver.OnWindowFocusChangeListener {
|
||||
override fun onWindowFocusChanged(hasFocus: Boolean) {
|
||||
if (hasFocus) return
|
||||
view?.viewTreeObserver?.removeOnWindowFocusChangeListener(this)
|
||||
view?.context?.apply {
|
||||
toast(
|
||||
getString(CoreR.string.done_action, viewModel.args.name),
|
||||
Toast.LENGTH_SHORT
|
||||
)
|
||||
}
|
||||
viewModel.back()
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateMenu(menu: Menu, inflater: MenuInflater) {
|
||||
inflater.inflate(R.menu.menu_flash, menu)
|
||||
}
|
||||
|
||||
override fun onMenuItemSelected(item: MenuItem): Boolean {
|
||||
return viewModel.onMenuItemClicked(item)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
defaultOrientation = activity?.requestedOrientation ?: -1
|
||||
activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LOCKED
|
||||
if (savedInstanceState == null) {
|
||||
viewModel.startRunAction()
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("WrongConstant")
|
||||
override fun onDestroyView() {
|
||||
if (defaultOrientation != -1) {
|
||||
activity?.requestedOrientation = defaultOrientation
|
||||
}
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
override fun onKeyEvent(event: KeyEvent): Boolean {
|
||||
return when (event.keyCode) {
|
||||
KeyEvent.KEYCODE_VOLUME_UP, KeyEvent.KEYCODE_VOLUME_DOWN -> true
|
||||
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBackPressed(): Boolean {
|
||||
if (viewModel.state.value == ActionViewModel.State.RUNNING) return true
|
||||
return super.onBackPressed()
|
||||
}
|
||||
|
||||
override fun onPreBind(binding: FragmentActionMd2Binding) = Unit
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package com.topjohnwu.magisk.ui.module
|
||||
|
||||
import android.view.MenuItem
|
||||
import androidx.databinding.ObservableArrayList
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.arch.BaseViewModel
|
||||
import com.topjohnwu.magisk.core.ktx.synchronized
|
||||
import com.topjohnwu.magisk.core.ktx.timeFormatStandard
|
||||
import com.topjohnwu.magisk.core.ktx.toTime
|
||||
import com.topjohnwu.magisk.core.utils.MediaStoreUtils
|
||||
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream
|
||||
import com.topjohnwu.magisk.events.SnackbarEvent
|
||||
import com.topjohnwu.magisk.ui.flash.ConsoleItem
|
||||
import com.topjohnwu.superuser.CallbackList
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
import java.io.IOException
|
||||
|
||||
class ActionViewModel : BaseViewModel() {
|
||||
|
||||
enum class State {
|
||||
RUNNING, SUCCESS, FAILED
|
||||
}
|
||||
|
||||
private val _state = MutableLiveData(State.RUNNING)
|
||||
val state: LiveData<State> get() = _state
|
||||
|
||||
val items = ObservableArrayList<ConsoleItem>()
|
||||
lateinit var args: ActionFragmentArgs
|
||||
|
||||
private val logItems = mutableListOf<String>().synchronized()
|
||||
private val outItems = object : CallbackList<String>() {
|
||||
override fun onAddElement(e: String?) {
|
||||
e ?: return
|
||||
items.add(ConsoleItem(e))
|
||||
logItems.add(e)
|
||||
}
|
||||
}
|
||||
|
||||
fun startRunAction() = viewModelScope.launch {
|
||||
onResult(withContext(Dispatchers.IO) {
|
||||
try {
|
||||
Shell.cmd("run_action \'${args.id}\'")
|
||||
.to(outItems, logItems)
|
||||
.exec().isSuccess
|
||||
} catch (e: IOException) {
|
||||
Timber.e(e)
|
||||
false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun onResult(success: Boolean) {
|
||||
_state.value = if (success) State.SUCCESS else State.FAILED
|
||||
}
|
||||
|
||||
fun onMenuItemClicked(item: MenuItem): Boolean {
|
||||
when (item.itemId) {
|
||||
R.id.action_save -> savePressed()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private fun savePressed() = withExternalRW {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
val name = "%s_action_log_%s.log".format(
|
||||
args.name,
|
||||
System.currentTimeMillis().toTime(timeFormatStandard)
|
||||
)
|
||||
val file = MediaStoreUtils.getFile(name)
|
||||
file.uri.outputStream().bufferedWriter().use { writer ->
|
||||
synchronized(logItems) {
|
||||
logItems.forEach {
|
||||
writer.write(it)
|
||||
writer.newLine()
|
||||
}
|
||||
}
|
||||
}
|
||||
SnackbarEvent(file.toString()).publish()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,7 @@ class LocalModuleRvItem(
|
||||
override val layoutRes = R.layout.item_module_md2
|
||||
|
||||
val showNotice: Boolean
|
||||
val showAction: Boolean
|
||||
val noticeText: TextHolder
|
||||
|
||||
init {
|
||||
@@ -35,6 +36,7 @@ class LocalModuleRvItem(
|
||||
showNotice = zygiskUnloaded ||
|
||||
(Info.isZygiskEnabled && isRiru) ||
|
||||
(!Info.isZygiskEnabled && isZygisk)
|
||||
showAction = item.hasAction && !showNotice
|
||||
noticeText =
|
||||
when {
|
||||
zygiskUnloaded -> CoreR.string.zygisk_module_unloaded.asText()
|
||||
|
||||
@@ -4,8 +4,10 @@ import android.net.Uri
|
||||
import androidx.databinding.Bindable
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import com.topjohnwu.magisk.BR
|
||||
import com.topjohnwu.magisk.MainDirections
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.arch.AsyncLoadViewModel
|
||||
import com.topjohnwu.magisk.core.Const
|
||||
import com.topjohnwu.magisk.core.Info
|
||||
import com.topjohnwu.magisk.core.base.ContentResultCallback
|
||||
import com.topjohnwu.magisk.core.model.module.LocalModule
|
||||
@@ -96,6 +98,10 @@ class ModuleViewModel : AsyncLoadViewModel() {
|
||||
}
|
||||
}
|
||||
|
||||
fun runAction(id: String, name: String) {
|
||||
MainDirections.actionActionFragment(id, name).navigate()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val uri = MutableLiveData<Uri?>()
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import com.topjohnwu.magisk.BR
|
||||
import com.topjohnwu.magisk.arch.BaseViewModel
|
||||
import com.topjohnwu.magisk.core.AppContext
|
||||
import com.topjohnwu.magisk.core.BuildConfig
|
||||
import com.topjohnwu.magisk.core.Config
|
||||
import com.topjohnwu.magisk.core.Const
|
||||
import com.topjohnwu.magisk.core.Info
|
||||
import com.topjohnwu.magisk.core.R
|
||||
@@ -92,7 +93,7 @@ class SettingsViewModel : BaseViewModel(), BaseSettingsItem.Handler {
|
||||
DownloadPath -> withExternalRW(doAction)
|
||||
UpdateChecker -> withPostNotificationPermission(doAction)
|
||||
Authentication -> AuthEvent(doAction).publish()
|
||||
Hide, Restore -> withInstallPermission(doAction)
|
||||
AutomaticResponse -> if (Config.suAuth) AuthEvent(doAction).publish() else doAction()
|
||||
else -> doAction()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,8 +78,8 @@ class SuperuserViewModel(
|
||||
this@SuperuserViewModel, policy,
|
||||
info.packageName,
|
||||
info.sharedUserId != null,
|
||||
info.applicationInfo.loadIcon(pm),
|
||||
info.applicationInfo.getLabel(pm)
|
||||
info.applicationInfo?.loadIcon(pm) ?: pm.defaultActivityIcon,
|
||||
info.applicationInfo?.getLabel(pm) ?: info.packageName
|
||||
)
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
null
|
||||
|
||||
@@ -111,7 +111,7 @@ class SuRequestViewModel(
|
||||
// shared UID. We have no way to know where this request comes from.
|
||||
icon = pm.defaultActivityIcon
|
||||
title = "[SharedUID] ${info.sharedUserId}"
|
||||
packageName = info.sharedUserId
|
||||
packageName = info.sharedUserId.toString()
|
||||
} else {
|
||||
val prefix = if (info.sharedUserId == null) "" else "[SharedUID] "
|
||||
icon = app.loadIcon(pm)
|
||||
|
||||
5
app/apk/src/main/res/drawable/ic_action_md2.xml
Normal file
5
app/apk/src/main/res/drawable/ic_action_md2.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
|
||||
|
||||
<path android:fillColor="@android:color/white" android:pathData="M8,5v14l11,-7z"/>
|
||||
|
||||
</vector>
|
||||
68
app/apk/src/main/res/layout/fragment_action_md2.xml
Normal file
68
app/apk/src/main/res/layout/fragment_action_md2.xml
Normal file
@@ -0,0 +1,68 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<data>
|
||||
|
||||
<variable
|
||||
name="viewModel"
|
||||
type="com.topjohnwu.magisk.ui.module.ActionViewModel" />
|
||||
|
||||
</data>
|
||||
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<HorizontalScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginTop="@dimen/internal_action_bar_size"
|
||||
app:layout_fitsSystemWindowsInsets="top"
|
||||
tools:layout_marginTop="@dimen/internal_action_bar_size">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/flash_content"
|
||||
scrollToLast="@{true}"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:clipToPadding="false"
|
||||
android:orientation="vertical"
|
||||
app:fitsSystemWindowsInsets="start|end|bottom"
|
||||
app:items="@{viewModel.items}"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior"
|
||||
tools:listitem="@layout/item_console_md2" />
|
||||
|
||||
</HorizontalScrollView>
|
||||
|
||||
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||
android:id="@+id/close_btn"
|
||||
android:visibility="gone"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:layout_margin="@dimen/l1"
|
||||
android:layout_marginBottom="@dimen/l1"
|
||||
android:clickable="true"
|
||||
android:enabled="true"
|
||||
android:focusable="true"
|
||||
android:text="@string/close"
|
||||
android:textAllCaps="false"
|
||||
android:textColor="?colorOnPrimary"
|
||||
android:textStyle="bold"
|
||||
app:backgroundTint="?colorPrimary"
|
||||
app:icon="@drawable/ic_close_md2"
|
||||
app:iconTint="?colorOnPrimary"
|
||||
app:layout_fitsSystemWindowsInsets="bottom" />
|
||||
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
android:id="@+id/snackbar_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:fitsSystemWindowsInsets="top|bottom" />
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
||||
</layout>
|
||||
@@ -189,12 +189,32 @@
|
||||
android:textColor="?colorError"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/module_remove"
|
||||
app:layout_constraintEnd_toStartOf="@+id/bottom_bar_barrier"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/module_config"
|
||||
app:layout_constraintTop_toTopOf="@+id/module_remove"
|
||||
tools:lines="2"
|
||||
tools:text="@tools:sample/lorem/random"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/module_config"
|
||||
style="@style/WidgetFoundation.Button.Text"
|
||||
goneUnless="@{item.showAction}"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:clickable="true"
|
||||
android:enabled="@{item.enabled}"
|
||||
android:focusable="true"
|
||||
android:onClick="@{() -> viewModel.runAction(item.item.id, item.item.name)}"
|
||||
android:text="@string/module_action"
|
||||
android:textAllCaps="false"
|
||||
android:visibility="gone"
|
||||
app:icon="@drawable/ic_action_md2"
|
||||
app:iconGravity="textStart"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/module_remove"
|
||||
app:layout_constraintTop_toTopOf="@+id/module_remove"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:srcCompat="@drawable/ic_download_md2" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
@@ -53,6 +53,21 @@
|
||||
|
||||
</fragment>
|
||||
|
||||
<fragment
|
||||
android:id="@+id/actionFragment"
|
||||
android:name="com.topjohnwu.magisk.ui.module.ActionFragment"
|
||||
android:label="ActionFragment"
|
||||
tools:layout="@layout/fragment_action_md2" >
|
||||
|
||||
<argument
|
||||
android:name="id"
|
||||
app:argType="string" />
|
||||
|
||||
<argument
|
||||
android:name="name"
|
||||
app:argType="string" />
|
||||
</fragment>
|
||||
|
||||
<fragment
|
||||
android:id="@+id/installFragment"
|
||||
android:name="com.topjohnwu.magisk.ui.install.InstallFragment"
|
||||
@@ -152,4 +167,12 @@
|
||||
app:popEnterAnim="@anim/fragment_enter_pop"
|
||||
app:popExitAnim="@anim/fragment_exit_pop" />
|
||||
|
||||
<action
|
||||
android:id="@+id/action_actionFragment"
|
||||
app:destination="@id/actionFragment"
|
||||
app:enterAnim="@anim/fragment_enter"
|
||||
app:exitAnim="@anim/fragment_exit"
|
||||
app:popEnterAnim="@anim/fragment_enter_pop"
|
||||
app:popExitAnim="@anim/fragment_exit_pop" />
|
||||
|
||||
</navigation>
|
||||
|
||||
@@ -19,6 +19,7 @@ android {
|
||||
buildConfigField("int", "APP_VERSION_CODE", "${Config.versionCode}")
|
||||
buildConfigField("String", "APP_VERSION_NAME", "\"${Config.version}\"")
|
||||
buildConfigField("int", "STUB_VERSION", Config.stubVersion)
|
||||
consumerProguardFile("proguard-rules.pro")
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
@@ -30,39 +31,39 @@ android {
|
||||
dependencies {
|
||||
api(project(":app:shared"))
|
||||
|
||||
api("com.jakewharton.timber:timber:5.0.1")
|
||||
api("io.noties.markwon:core:4.6.2")
|
||||
implementation("org.bouncycastle:bcpkix-jdk18on:1.78.1")
|
||||
implementation("org.apache.commons:commons-compress:1.26.2")
|
||||
api(libs.timber)
|
||||
api(libs.markwon.core)
|
||||
implementation(libs.bcpkix)
|
||||
implementation(libs.commons.compress)
|
||||
|
||||
val vLibsu = "6.0.0"
|
||||
api("com.github.topjohnwu.libsu:core:${vLibsu}")
|
||||
api("com.github.topjohnwu.libsu:service:${vLibsu}")
|
||||
api("com.github.topjohnwu.libsu:nio:${vLibsu}")
|
||||
api(libs.libsu.core)
|
||||
api(libs.libsu.service)
|
||||
api(libs.libsu.nio)
|
||||
|
||||
val vRetrofit = "2.11.0"
|
||||
implementation("com.squareup.retrofit2:retrofit:${vRetrofit}")
|
||||
implementation("com.squareup.retrofit2:converter-moshi:${vRetrofit}")
|
||||
implementation("com.squareup.retrofit2:converter-scalars:${vRetrofit}")
|
||||
implementation(libs.retrofit)
|
||||
implementation(libs.retrofit.moshi)
|
||||
implementation(libs.retrofit.scalars)
|
||||
|
||||
val vOkHttp = "4.12.0"
|
||||
implementation("com.squareup.okhttp3:okhttp:${vOkHttp}")
|
||||
implementation("com.squareup.okhttp3:logging-interceptor:${vOkHttp}")
|
||||
implementation("com.squareup.okhttp3:okhttp-dnsoverhttps:${vOkHttp}")
|
||||
implementation(libs.okhttp)
|
||||
implementation(libs.okhttp.logging)
|
||||
implementation(libs.okhttp.dnsoverhttps)
|
||||
|
||||
val vMoshi = "1.15.1"
|
||||
implementation("com.squareup.moshi:moshi:${vMoshi}")
|
||||
ksp("com.squareup.moshi:moshi-kotlin-codegen:${vMoshi}")
|
||||
implementation(libs.moshi)
|
||||
ksp(libs.moshi.codegen)
|
||||
|
||||
val vRoom = "2.6.1"
|
||||
implementation("androidx.room:room-runtime:${vRoom}")
|
||||
implementation("androidx.room:room-ktx:${vRoom}")
|
||||
ksp("androidx.room:room-compiler:${vRoom}")
|
||||
implementation(libs.room.runtime)
|
||||
implementation(libs.room.ktx)
|
||||
ksp(libs.room.compiler)
|
||||
|
||||
implementation("androidx.core:core-splashscreen:1.0.1")
|
||||
implementation("androidx.core:core-ktx:1.13.1")
|
||||
implementation("androidx.activity:activity:1.9.1")
|
||||
implementation("androidx.collection:collection-ktx:1.4.2")
|
||||
implementation("androidx.profileinstaller:profileinstaller:1.3.1")
|
||||
implementation("androidx.lifecycle:lifecycle-process:2.8.4")
|
||||
implementation(libs.core.splashscreen)
|
||||
implementation(libs.core.ktx)
|
||||
implementation(libs.activity)
|
||||
implementation(libs.collection.ktx)
|
||||
implementation(libs.profileinstaller)
|
||||
|
||||
// We also implement all our tests in this module.
|
||||
// However, we don't want to bundle test dependencies.
|
||||
// That's why we make it compileOnly.
|
||||
compileOnly(libs.test.junit)
|
||||
compileOnly(libs.test.uiautomator)
|
||||
}
|
||||
|
||||
@@ -22,42 +22,19 @@
|
||||
int mActivityHandlesConfigFlags;
|
||||
}
|
||||
|
||||
# main
|
||||
-keep,allowoptimization public class com.topjohnwu.magisk.signing.SignBoot {
|
||||
public static void main(java.lang.String[]);
|
||||
}
|
||||
|
||||
# Strip Timber verbose and debug logging
|
||||
-assumenosideeffects class timber.log.Timber$Tree {
|
||||
public void v(**);
|
||||
public void d(**);
|
||||
}
|
||||
|
||||
# https://github.com/square/retrofit/issues/3751#issuecomment-1192043644
|
||||
# Keep generic signature of Call, Response (R8 full mode strips signatures from non-kept items).
|
||||
-keep,allowobfuscation,allowshrinking interface retrofit2.Call
|
||||
-keep,allowobfuscation,allowshrinking class retrofit2.Response
|
||||
|
||||
# With R8 full mode generic signatures are stripped for classes that are not
|
||||
# kept. Suspend functions are wrapped in continuations where the type argument
|
||||
# is used.
|
||||
-keep,allowobfuscation,allowshrinking class kotlin.coroutines.Continuation
|
||||
|
||||
|
||||
# Excessive obfuscation
|
||||
-repackageclasses 'a'
|
||||
-flattenpackagehierarchy
|
||||
-allowaccessmodification
|
||||
|
||||
-obfuscationdictionary ../dict.txt
|
||||
-classobfuscationdictionary ../dict.txt
|
||||
-packageobfuscationdictionary ../dict.txt
|
||||
|
||||
-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
|
||||
-dontwarn org.junit.**
|
||||
@@ -16,7 +16,6 @@ import com.topjohnwu.magisk.StubApk
|
||||
import com.topjohnwu.magisk.core.base.UntrackedActivity
|
||||
import com.topjohnwu.magisk.core.utils.LocaleSetting
|
||||
import com.topjohnwu.magisk.core.utils.NetworkObserver
|
||||
import com.topjohnwu.magisk.core.utils.ProcessLifecycle
|
||||
import com.topjohnwu.magisk.core.utils.RootUtils
|
||||
import com.topjohnwu.magisk.core.utils.ShellInit
|
||||
import com.topjohnwu.superuser.Shell
|
||||
@@ -40,6 +39,7 @@ object AppContext : ContextWrapper(null),
|
||||
|
||||
private var ref = WeakReference<Activity>(null)
|
||||
private lateinit var application: Application
|
||||
private lateinit var networkObserver: NetworkObserver
|
||||
|
||||
init {
|
||||
// Always log full stack trace with Timber
|
||||
@@ -56,6 +56,10 @@ object AppContext : ContextWrapper(null),
|
||||
LocaleSetting.instance.updateResource(resources)
|
||||
}
|
||||
|
||||
override fun onActivityStarted(activity: Activity) {
|
||||
networkObserver.postCurrentState()
|
||||
}
|
||||
|
||||
override fun onActivityResumed(activity: Activity) {
|
||||
if (activity is UntrackedActivity) return
|
||||
ref = WeakReference(activity)
|
||||
@@ -102,8 +106,7 @@ object AppContext : ContextWrapper(null),
|
||||
val lm = getSystemService(LocaleManager::class.java)
|
||||
lm.overrideLocaleConfig = LocaleSetting.localeConfig
|
||||
}
|
||||
ProcessLifecycle.init(this)
|
||||
NetworkObserver.init(this)
|
||||
networkObserver = NetworkObserver.init(this)
|
||||
if (!BuildConfig.DEBUG && !isRunningAsStub) {
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
ProfileInstaller.writeProfile(this@AppContext)
|
||||
@@ -120,7 +123,6 @@ object AppContext : ContextWrapper(null),
|
||||
}
|
||||
|
||||
override fun onActivityCreated(activity: Activity, bundle: Bundle?) {}
|
||||
override fun onActivityStarted(activity: Activity) {}
|
||||
override fun onActivityStopped(activity: Activity) {}
|
||||
override fun onActivitySaveInstanceState(activity: Activity, bundle: Bundle) {}
|
||||
override fun onActivityDestroyed(activity: Activity) {}
|
||||
|
||||
@@ -83,7 +83,7 @@ object Config : PreferenceConfig, DBConfig {
|
||||
const val SU_AUTO_ALLOW = 2
|
||||
|
||||
// su timeout
|
||||
val TIMEOUT_LIST = intArrayOf(0, -1, 10, 20, 30, 60)
|
||||
val TIMEOUT_LIST = longArrayOf(0, -1, 10, 20, 30, 60)
|
||||
}
|
||||
|
||||
private val defaultChannel =
|
||||
|
||||
@@ -11,6 +11,7 @@ import androidx.core.content.getSystemService
|
||||
import com.topjohnwu.magisk.core.base.BaseJobService
|
||||
import com.topjohnwu.magisk.core.di.ServiceLocator
|
||||
import com.topjohnwu.magisk.core.download.DownloadEngine
|
||||
import com.topjohnwu.magisk.core.download.DownloadSession
|
||||
import com.topjohnwu.magisk.core.download.Subject
|
||||
import com.topjohnwu.magisk.view.Notifications
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -25,7 +26,7 @@ class JobService : BaseJobService() {
|
||||
@TargetApi(value = 34)
|
||||
inner class Session(
|
||||
private var params: JobParameters
|
||||
) : DownloadEngine.Session {
|
||||
) : DownloadSession {
|
||||
|
||||
override val context get() = this@JobService
|
||||
val engine = DownloadEngine(this)
|
||||
|
||||
@@ -3,7 +3,6 @@ package com.topjohnwu.magisk.core
|
||||
import android.os.Bundle
|
||||
import com.topjohnwu.magisk.core.base.BaseProvider
|
||||
import com.topjohnwu.magisk.core.su.SuCallbackHandler
|
||||
import com.topjohnwu.magisk.core.su.TestHandler
|
||||
|
||||
class Provider : BaseProvider() {
|
||||
|
||||
@@ -13,7 +12,7 @@ class Provider : BaseProvider() {
|
||||
SuCallbackHandler.run(context!!, method, extras)
|
||||
Bundle.EMPTY
|
||||
}
|
||||
else -> TestHandler.run(method)
|
||||
else -> Bundle.EMPTY
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,9 +7,10 @@ import androidx.core.app.ServiceCompat
|
||||
import androidx.core.content.IntentCompat
|
||||
import com.topjohnwu.magisk.core.base.BaseService
|
||||
import com.topjohnwu.magisk.core.download.DownloadEngine
|
||||
import com.topjohnwu.magisk.core.download.DownloadSession
|
||||
import com.topjohnwu.magisk.core.download.Subject
|
||||
|
||||
class Service : BaseService(), DownloadEngine.Session {
|
||||
class Service : BaseService(), DownloadSession {
|
||||
|
||||
private var mEngine: DownloadEngine? = null
|
||||
override val context get() = this
|
||||
|
||||
@@ -7,9 +7,13 @@ import kotlinx.coroutines.withContext
|
||||
|
||||
open class MagiskDB {
|
||||
|
||||
suspend fun <R> exec(
|
||||
class Literal(
|
||||
val str: String
|
||||
)
|
||||
|
||||
suspend inline fun <R> exec(
|
||||
query: String,
|
||||
mapper: suspend (Map<String, String>) -> R
|
||||
crossinline mapper: (Map<String, String>) -> R
|
||||
): List<R> {
|
||||
return withContext(Dispatchers.IO) {
|
||||
val out = Shell.cmd("magisk --sqlite '$query'").await().out
|
||||
@@ -18,13 +22,15 @@ open class MagiskDB {
|
||||
.map { it.split("=", limit = 2) }
|
||||
.filter { it.size == 2 }
|
||||
.associate { it[0] to it[1] }
|
||||
.let { mapper(it) }
|
||||
.let(mapper)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend inline fun exec(query: String) {
|
||||
exec(query) {}
|
||||
suspend fun exec(query: String) {
|
||||
withContext(Dispatchers.IO) {
|
||||
Shell.cmd("magisk --sqlite '$query'").await()
|
||||
}
|
||||
}
|
||||
|
||||
fun Map<String, Any>.toQuery(): String {
|
||||
@@ -33,6 +39,7 @@ open class MagiskDB {
|
||||
when (it) {
|
||||
is Boolean -> if (it) "1" else "0"
|
||||
is Number -> it.toString()
|
||||
is Literal -> it.str
|
||||
else -> "\"$it\""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,24 +3,24 @@ package com.topjohnwu.magisk.core.data.magiskdb
|
||||
import com.topjohnwu.magisk.core.AppContext
|
||||
import com.topjohnwu.magisk.core.Const
|
||||
import com.topjohnwu.magisk.core.model.su.SuPolicy
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
private const val SELECT_QUERY = "SELECT (until - strftime(\"%s\", \"now\")) AS remain, *"
|
||||
|
||||
class PolicyDao : MagiskDB() {
|
||||
|
||||
suspend fun deleteOutdated() {
|
||||
val nowSeconds = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis())
|
||||
val query = "DELETE FROM ${Table.POLICY} WHERE " +
|
||||
"(until > 0 AND until < $nowSeconds) OR until < 0"
|
||||
"(until > 0 AND until < strftime(\"%s\", \"now\")) OR until < 0"
|
||||
exec(query)
|
||||
}
|
||||
|
||||
suspend fun delete(uid: Int) {
|
||||
val query = "DELETE FROM ${Table.POLICY} WHERE uid == $uid"
|
||||
val query = "DELETE FROM ${Table.POLICY} WHERE uid=$uid"
|
||||
exec(query)
|
||||
}
|
||||
|
||||
suspend fun fetch(uid: Int): SuPolicy? {
|
||||
val query = "SELECT * FROM ${Table.POLICY} WHERE uid == $uid LIMIT = 1"
|
||||
val query = "$SELECT_QUERY FROM ${Table.POLICY} WHERE uid=$uid LIMIT 1"
|
||||
return exec(query, ::toPolicy).firstOrNull()
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ class PolicyDao : MagiskDB() {
|
||||
}
|
||||
|
||||
suspend fun fetchAll(): List<SuPolicy> {
|
||||
val query = "SELECT * FROM ${Table.POLICY} WHERE uid/100000 == ${Const.USER_ID}"
|
||||
val query = "$SELECT_QUERY FROM ${Table.POLICY} WHERE uid/100000=${Const.USER_ID}"
|
||||
return exec(query, ::toPolicy).filterNotNull()
|
||||
}
|
||||
|
||||
@@ -43,8 +43,15 @@ class PolicyDao : MagiskDB() {
|
||||
val uid = map["uid"]?.toInt() ?: return null
|
||||
val policy = SuPolicy(uid)
|
||||
|
||||
map["until"]?.toLong()?.let { until ->
|
||||
if (until <= 0) {
|
||||
policy.remain = until
|
||||
} else {
|
||||
map["remain"]?.toLong()?.let { policy.remain = it }
|
||||
}
|
||||
}
|
||||
|
||||
map["policy"]?.toInt()?.let { policy.policy = it }
|
||||
map["until"]?.toLong()?.let { policy.until = it }
|
||||
map["logging"]?.toInt()?.let { policy.logging = it != 0 }
|
||||
map["notification"]?.toInt()?.let { policy.notification = it != 0 }
|
||||
return policy
|
||||
|
||||
@@ -3,7 +3,7 @@ package com.topjohnwu.magisk.core.data.magiskdb
|
||||
class SettingsDao : MagiskDB() {
|
||||
|
||||
suspend fun delete(key: String) {
|
||||
val query = "DELETE FROM ${Table.SETTINGS} WHERE key == \"$key\""
|
||||
val query = "DELETE FROM ${Table.SETTINGS} WHERE key=\"$key\""
|
||||
exec(query)
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ class SettingsDao : MagiskDB() {
|
||||
}
|
||||
|
||||
suspend fun fetch(key: String, default: Int = -1): Int {
|
||||
val query = "SELECT value FROM ${Table.SETTINGS} WHERE key == \"$key\" LIMIT 1"
|
||||
val query = "SELECT value FROM ${Table.SETTINGS} WHERE key=\"$key\" LIMIT 1"
|
||||
return exec(query) { it["value"]?.toInt() }.firstOrNull() ?: default
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ package com.topjohnwu.magisk.core.data.magiskdb
|
||||
class StringDao : MagiskDB() {
|
||||
|
||||
suspend fun delete(key: String) {
|
||||
val query = "DELETE FROM ${Table.STRINGS} WHERE key == \"$key\""
|
||||
val query = "DELETE FROM ${Table.STRINGS} WHERE key=\"$key\""
|
||||
exec(query)
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ class StringDao : MagiskDB() {
|
||||
}
|
||||
|
||||
suspend fun fetch(key: String, default: String = ""): String {
|
||||
val query = "SELECT value FROM ${Table.STRINGS} WHERE key == \"$key\" LIMIT 1"
|
||||
val query = "SELECT value FROM ${Table.STRINGS} WHERE key=\"$key\" LIMIT 1"
|
||||
return exec(query) { it["value"] }.firstOrNull() ?: default
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import android.app.PendingIntent
|
||||
import android.app.job.JobInfo
|
||||
import android.app.job.JobScheduler
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
@@ -16,7 +15,6 @@ import androidx.collection.isNotEmpty
|
||||
import androidx.core.content.getSystemService
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import com.topjohnwu.magisk.StubApk
|
||||
import com.topjohnwu.magisk.core.AppContext
|
||||
import com.topjohnwu.magisk.core.Const
|
||||
import com.topjohnwu.magisk.core.JobService
|
||||
@@ -25,18 +23,8 @@ import com.topjohnwu.magisk.core.base.IActivityExtension
|
||||
import com.topjohnwu.magisk.core.cmp
|
||||
import com.topjohnwu.magisk.core.di.ServiceLocator
|
||||
import com.topjohnwu.magisk.core.intent
|
||||
import com.topjohnwu.magisk.core.isRunningAsStub
|
||||
import com.topjohnwu.magisk.core.ktx.cachedFile
|
||||
import com.topjohnwu.magisk.core.ktx.copyAll
|
||||
import com.topjohnwu.magisk.core.ktx.copyAndClose
|
||||
import com.topjohnwu.magisk.core.ktx.forEach
|
||||
import com.topjohnwu.magisk.core.ktx.set
|
||||
import com.topjohnwu.magisk.core.ktx.withStreams
|
||||
import com.topjohnwu.magisk.core.ktx.writeTo
|
||||
import com.topjohnwu.magisk.core.tasks.AppMigration
|
||||
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream
|
||||
import com.topjohnwu.magisk.core.utils.ProgressInputStream
|
||||
import com.topjohnwu.magisk.utils.APKInstall
|
||||
import com.topjohnwu.magisk.view.Notifications
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -44,13 +32,7 @@ import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
import okhttp3.ResponseBody
|
||||
import timber.log.Timber
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipFile
|
||||
import java.util.zip.ZipInputStream
|
||||
import java.util.zip.ZipOutputStream
|
||||
|
||||
/**
|
||||
* This class drives the execution of file downloads and notification management.
|
||||
@@ -69,16 +51,7 @@ import java.util.zip.ZipOutputStream
|
||||
* For API 23 - 33, we use a foreground service as a session.
|
||||
* For API 34 and higher, we use user-initiated job services as a session.
|
||||
*/
|
||||
class DownloadEngine(
|
||||
private val session: Session
|
||||
) {
|
||||
|
||||
interface Session {
|
||||
val context: Context
|
||||
|
||||
fun attachNotification(id: Int, builder: Notification.Builder)
|
||||
fun onDownloadComplete()
|
||||
}
|
||||
class DownloadEngine(session: DownloadSession) : DownloadSession by session, DownloadNotifier {
|
||||
|
||||
companion object {
|
||||
const val ACTION = "com.topjohnwu.magisk.DOWNLOAD"
|
||||
@@ -99,35 +72,37 @@ class DownloadEngine(
|
||||
}
|
||||
}
|
||||
|
||||
private fun createIntent(context: Context, subject: Subject) =
|
||||
if (Build.VERSION.SDK_INT >= 34) {
|
||||
private fun createBroadcastIntent(context: Context, subject: Subject) =
|
||||
context.intent<com.topjohnwu.magisk.core.Receiver>()
|
||||
.setAction(ACTION)
|
||||
.putExtra(SUBJECT_KEY, subject)
|
||||
} else {
|
||||
|
||||
private fun createServiceIntent(context: Context, subject: Subject) =
|
||||
context.intent<com.topjohnwu.magisk.core.Service>()
|
||||
.setAction(ACTION)
|
||||
.putExtra(SUBJECT_KEY, subject)
|
||||
}
|
||||
|
||||
@SuppressLint("InlinedApi")
|
||||
fun getPendingIntent(context: Context, subject: Subject): PendingIntent {
|
||||
val flag = PendingIntent.FLAG_IMMUTABLE or
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or
|
||||
PendingIntent.FLAG_ONE_SHOT
|
||||
val intent = createIntent(context, subject)
|
||||
return if (Build.VERSION.SDK_INT >= 34) {
|
||||
// On API 34+, download tasks are handled with a user-initiated job.
|
||||
// However, there is no way to schedule a new job directly with a pending intent.
|
||||
// As a workaround, we send the subject to a broadcast receiver and have it
|
||||
// schedule the job for us.
|
||||
val intent = createBroadcastIntent(context, subject)
|
||||
PendingIntent.getBroadcast(context, REQUEST_CODE, intent, flag)
|
||||
} else if (Build.VERSION.SDK_INT >= 26) {
|
||||
} else {
|
||||
val intent = createServiceIntent(context, subject)
|
||||
if (Build.VERSION.SDK_INT >= 26) {
|
||||
PendingIntent.getForegroundService(context, REQUEST_CODE, intent, flag)
|
||||
} else {
|
||||
PendingIntent.getService(context, REQUEST_CODE, intent, flag)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("InlinedApi")
|
||||
fun <T> startWithActivity(
|
||||
@@ -140,6 +115,7 @@ class DownloadEngine(
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
fun start(context: Context, subject: Subject) {
|
||||
if (Build.VERSION.SDK_INT >= 34) {
|
||||
val scheduler = context.getSystemService<JobScheduler>()!!
|
||||
@@ -152,24 +128,29 @@ class DownloadEngine(
|
||||
.setTransientExtras(extras)
|
||||
.build()
|
||||
scheduler.schedule(info)
|
||||
} else if (Build.VERSION.SDK_INT >= 26) {
|
||||
context.startForegroundService(createIntent(context, subject))
|
||||
} else {
|
||||
context.startService(createIntent(context, subject))
|
||||
val intent = createServiceIntent(context, subject)
|
||||
if (Build.VERSION.SDK_INT >= 26) {
|
||||
context.startForegroundService(intent)
|
||||
} else {
|
||||
context.startService(intent)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val notifications = SparseArrayCompat<Notification.Builder>()
|
||||
private var attachedId = -1
|
||||
private val job = Job()
|
||||
private val processor = DownloadProcessor(this)
|
||||
private val network get() = ServiceLocator.networkService
|
||||
|
||||
fun download(subject: Subject) {
|
||||
notifyUpdate(subject.notifyId)
|
||||
CoroutineScope(job + Dispatchers.IO).launch {
|
||||
try {
|
||||
val stream = network.fetchFile(subject.url).toProgressStream(subject)
|
||||
when (subject) {
|
||||
is Subject.App -> handleApp(stream, subject)
|
||||
is Subject.Module -> handleModule(stream, subject.file)
|
||||
else -> stream.copyAndClose(subject.file.outputStream())
|
||||
}
|
||||
processor.handle(stream, subject)
|
||||
val activity = AppContext.foregroundActivity
|
||||
if (activity != null && subject.autoLaunch) {
|
||||
notifyRemove(subject.notifyId)
|
||||
@@ -187,16 +168,13 @@ class DownloadEngine(
|
||||
@Synchronized
|
||||
fun reattach() {
|
||||
val builder = notifications[attachedId] ?: return
|
||||
session.attachNotification(attachedId, builder)
|
||||
attachNotification(attachedId, builder)
|
||||
}
|
||||
|
||||
private val notifications = SparseArrayCompat<Notification.Builder>()
|
||||
private var attachedId = -1
|
||||
|
||||
private val job = Job()
|
||||
|
||||
private val context get() = session.context
|
||||
private val network get() = ServiceLocator.networkService
|
||||
private fun attach(id: Int, notification: Notification.Builder) {
|
||||
attachedId = id
|
||||
attachNotification(id, notification)
|
||||
}
|
||||
|
||||
private fun finalNotify(id: Int, editor: (Notification.Builder) -> Unit): Int {
|
||||
val notification = notifyRemove(id)?.also(editor) ?: return -1
|
||||
@@ -223,19 +201,14 @@ class DownloadEngine(
|
||||
subject.pendingIntent(context)?.let { intent -> it.setContentIntent(intent) }
|
||||
}
|
||||
|
||||
private fun attachNotification(id: Int, notification: Notification.Builder) {
|
||||
attachedId = id
|
||||
session.attachNotification(id, notification)
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
private fun notifyUpdate(id: Int, editor: (Notification.Builder) -> Unit = {}) {
|
||||
override fun notifyUpdate(id: Int, editor: (Notification.Builder) -> Unit) {
|
||||
val notification = (notifications[id] ?: Notifications.startProgress("").also {
|
||||
notifications[id] = it
|
||||
}).apply(editor)
|
||||
|
||||
if (attachedId < 0)
|
||||
attachNotification(id, notification)
|
||||
attach(id, notification)
|
||||
else
|
||||
Notifications.mgr.notify(id, notification.build())
|
||||
}
|
||||
@@ -255,11 +228,11 @@ class DownloadEngine(
|
||||
// There are still remaining notifications, pick one and attach to the session
|
||||
val anotherId = notifications.keyAt(0)
|
||||
val notification = notifications.valueAt(0)
|
||||
attachNotification(anotherId, notification)
|
||||
attach(anotherId, notification)
|
||||
} else {
|
||||
// No more notifications left, terminate the session
|
||||
attachedId = -1
|
||||
session.onDownloadComplete()
|
||||
onDownloadComplete()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -268,90 +241,6 @@ class DownloadEngine(
|
||||
return n
|
||||
}
|
||||
|
||||
private suspend fun handleApp(stream: InputStream, subject: Subject.App) {
|
||||
val external = subject.file.outputStream()
|
||||
|
||||
if (isRunningAsStub) {
|
||||
val updateApk = StubApk.update(context)
|
||||
try {
|
||||
// Download full APK to stub update path
|
||||
stream.copyAndClose(TeeOutputStream(external, updateApk.outputStream()))
|
||||
|
||||
// Also upgrade stub
|
||||
notifyUpdate(subject.notifyId) {
|
||||
it.setProgress(0, 0, true)
|
||||
.setContentTitle(context.getString(R.string.hide_app_title))
|
||||
.setContentText("")
|
||||
}
|
||||
|
||||
// Extract stub
|
||||
val zf = ZipFile(updateApk)
|
||||
val apk = context.cachedFile("stub.apk")
|
||||
apk.delete()
|
||||
zf.getInputStream(zf.getEntry("assets/stub.apk")).writeTo(apk)
|
||||
zf.close()
|
||||
|
||||
// Patch and install
|
||||
subject.intent = AppMigration.upgradeStub(context, apk)
|
||||
?: throw IOException("HideAPK patch error")
|
||||
apk.delete()
|
||||
} catch (e: Exception) {
|
||||
// If any error occurred, do not let stub load the new APK
|
||||
updateApk.delete()
|
||||
throw e
|
||||
}
|
||||
} else {
|
||||
val session = APKInstall.startSession(context)
|
||||
stream.copyAndClose(TeeOutputStream(external, session.openStream(context)))
|
||||
subject.intent = session.waitIntent()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun handleModule(src: InputStream, file: Uri) {
|
||||
val input = ZipInputStream(src)
|
||||
val output = ZipOutputStream(file.outputStream())
|
||||
|
||||
withStreams(input, output) { zin, zout ->
|
||||
zout.putNextEntry(ZipEntry("META-INF/"))
|
||||
zout.putNextEntry(ZipEntry("META-INF/com/"))
|
||||
zout.putNextEntry(ZipEntry("META-INF/com/google/"))
|
||||
zout.putNextEntry(ZipEntry("META-INF/com/google/android/"))
|
||||
zout.putNextEntry(ZipEntry("META-INF/com/google/android/update-binary"))
|
||||
context.assets.open("module_installer.sh").use { it.copyAll(zout) }
|
||||
|
||||
zout.putNextEntry(ZipEntry("META-INF/com/google/android/updater-script"))
|
||||
zout.write("#MAGISK\n".toByteArray())
|
||||
|
||||
zin.forEach { entry ->
|
||||
val path = entry.name
|
||||
if (path.isNotEmpty() && !path.startsWith("META-INF")) {
|
||||
zout.putNextEntry(ZipEntry(path))
|
||||
if (!entry.isDirectory) {
|
||||
zin.copyAll(zout)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class TeeOutputStream(
|
||||
private val o1: OutputStream,
|
||||
private val o2: OutputStream
|
||||
) : OutputStream() {
|
||||
override fun write(b: Int) {
|
||||
o1.write(b)
|
||||
o2.write(b)
|
||||
}
|
||||
override fun write(b: ByteArray?, off: Int, len: Int) {
|
||||
o1.write(b, off, len)
|
||||
o2.write(b, off, len)
|
||||
}
|
||||
override fun close() {
|
||||
o1.close()
|
||||
o2.close()
|
||||
}
|
||||
}
|
||||
|
||||
private fun ResponseBody.toProgressStream(subject: Subject): InputStream {
|
||||
val max = contentLength()
|
||||
val total = max.toFloat() / 1048576
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
package com.topjohnwu.magisk.core.download
|
||||
|
||||
import android.net.Uri
|
||||
import com.topjohnwu.magisk.StubApk
|
||||
import com.topjohnwu.magisk.core.R
|
||||
import com.topjohnwu.magisk.core.isRunningAsStub
|
||||
import com.topjohnwu.magisk.core.ktx.cachedFile
|
||||
import com.topjohnwu.magisk.core.ktx.copyAll
|
||||
import com.topjohnwu.magisk.core.ktx.copyAndClose
|
||||
import com.topjohnwu.magisk.core.ktx.withInOut
|
||||
import com.topjohnwu.magisk.core.ktx.writeTo
|
||||
import com.topjohnwu.magisk.core.tasks.AppMigration
|
||||
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream
|
||||
import com.topjohnwu.magisk.utils.APKInstall
|
||||
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry
|
||||
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream
|
||||
import org.apache.commons.compress.archivers.zip.ZipFile
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
|
||||
class DownloadProcessor(notifier: DownloadNotifier) : DownloadNotifier by notifier {
|
||||
|
||||
suspend fun handle(stream: InputStream, subject: Subject) {
|
||||
when (subject) {
|
||||
is Subject.App -> handleApp(stream, subject)
|
||||
is Subject.Module -> handleModule(stream, subject.file)
|
||||
else -> stream.copyAndClose(subject.file.outputStream())
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun handleApp(stream: InputStream, subject: Subject.App) {
|
||||
val external = subject.file.outputStream()
|
||||
|
||||
if (isRunningAsStub) {
|
||||
val updateApk = StubApk.update(context)
|
||||
try {
|
||||
// Download full APK to stub update path
|
||||
stream.copyAndClose(TeeOutputStream(external, updateApk.outputStream()))
|
||||
|
||||
// Also upgrade stub
|
||||
notifyUpdate(subject.notifyId) {
|
||||
it.setProgress(0, 0, true)
|
||||
.setContentTitle(context.getString(R.string.hide_app_title))
|
||||
.setContentText("")
|
||||
}
|
||||
|
||||
// Extract stub
|
||||
val apk = context.cachedFile("stub.apk")
|
||||
ZipFile.Builder().setFile(updateApk).get().use { zf ->
|
||||
apk.delete()
|
||||
zf.getInputStream(zf.getEntry("assets/stub.apk")).writeTo(apk)
|
||||
}
|
||||
|
||||
// Patch and install
|
||||
subject.intent = AppMigration.upgradeStub(context, apk)
|
||||
?: throw IOException("HideAPK patch error")
|
||||
apk.delete()
|
||||
} catch (e: Exception) {
|
||||
// If any error occurred, do not let stub load the new APK
|
||||
updateApk.delete()
|
||||
throw e
|
||||
}
|
||||
} else {
|
||||
val session = APKInstall.startSession(context)
|
||||
stream.copyAndClose(TeeOutputStream(external, session.openStream(context)))
|
||||
subject.intent = session.waitIntent()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun handleModule(src: InputStream, file: Uri) {
|
||||
val tmp = context.cachedFile("module.zip")
|
||||
try {
|
||||
// First download the entire zip into cache so we can process it
|
||||
src.writeTo(tmp)
|
||||
|
||||
val input = ZipFile.Builder().setFile(tmp).get()
|
||||
val output = ZipArchiveOutputStream(file.outputStream())
|
||||
withInOut(input, output) { zin, zout ->
|
||||
zout.putArchiveEntry(ZipArchiveEntry("META-INF/"))
|
||||
zout.closeArchiveEntry()
|
||||
zout.putArchiveEntry(ZipArchiveEntry("META-INF/com/"))
|
||||
zout.closeArchiveEntry()
|
||||
zout.putArchiveEntry(ZipArchiveEntry("META-INF/com/google/"))
|
||||
zout.closeArchiveEntry()
|
||||
zout.putArchiveEntry(ZipArchiveEntry("META-INF/com/google/android/"))
|
||||
zout.closeArchiveEntry()
|
||||
|
||||
zout.putArchiveEntry(ZipArchiveEntry("META-INF/com/google/android/update-binary"))
|
||||
context.assets.open("module_installer.sh").use { it.copyAll(zout) }
|
||||
zout.closeArchiveEntry()
|
||||
|
||||
zout.putArchiveEntry(ZipArchiveEntry("META-INF/com/google/android/updater-script"))
|
||||
zout.write("#MAGISK\n".toByteArray())
|
||||
zout.closeArchiveEntry()
|
||||
|
||||
// Then simply copy all entries to output
|
||||
zin.copyRawEntries(zout) { entry -> !entry.name.startsWith("META-INF") }
|
||||
}
|
||||
} finally {
|
||||
tmp.delete()
|
||||
}
|
||||
}
|
||||
|
||||
private class TeeOutputStream(
|
||||
private val o1: OutputStream,
|
||||
private val o2: OutputStream
|
||||
) : OutputStream() {
|
||||
override fun write(b: Int) {
|
||||
o1.write(b)
|
||||
o2.write(b)
|
||||
}
|
||||
override fun write(b: ByteArray?, off: Int, len: Int) {
|
||||
o1.write(b, off, len)
|
||||
o2.write(b, off, len)
|
||||
}
|
||||
override fun close() {
|
||||
o1.close()
|
||||
o2.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.topjohnwu.magisk.core.download
|
||||
|
||||
import android.app.Notification
|
||||
import android.content.Context
|
||||
|
||||
interface DownloadSession {
|
||||
val context: Context
|
||||
fun attachNotification(id: Int, builder: Notification.Builder)
|
||||
fun onDownloadComplete()
|
||||
}
|
||||
|
||||
interface DownloadNotifier {
|
||||
val context: Context
|
||||
fun notifyUpdate(id: Int, editor: (Notification.Builder) -> Unit = {})
|
||||
}
|
||||
@@ -17,7 +17,7 @@ import kotlinx.parcelize.Parcelize
|
||||
import java.io.File
|
||||
import java.util.UUID
|
||||
|
||||
sealed class Subject : Parcelable {
|
||||
abstract class Subject : Parcelable {
|
||||
|
||||
abstract val url: String
|
||||
abstract val file: Uri
|
||||
@@ -27,24 +27,13 @@ sealed class Subject : Parcelable {
|
||||
|
||||
open fun pendingIntent(context: Context): PendingIntent? = null
|
||||
|
||||
@Parcelize
|
||||
class Module(
|
||||
private val module: OnlineModule,
|
||||
override val autoLaunch: Boolean,
|
||||
override val notifyId: Int = Notifications.nextId()
|
||||
) : Subject() {
|
||||
override val url: String get() = module.zipUrl
|
||||
override val title: String get() = module.downloadFilename
|
||||
|
||||
@IgnoredOnParcel
|
||||
override val file by lazy {
|
||||
abstract class Module : Subject() {
|
||||
abstract val module: OnlineModule
|
||||
final override val url: String get() = module.zipUrl
|
||||
final override val title: String get() = module.downloadFilename
|
||||
final override val file by lazy {
|
||||
MediaStoreUtils.getFile(title).uri
|
||||
}
|
||||
|
||||
@IgnoredOnParcel
|
||||
var piCreator: ((Context, Uri) -> PendingIntent)? = null
|
||||
|
||||
override fun pendingIntent(context: Context) = piCreator?.invoke(context, file)
|
||||
}
|
||||
|
||||
@Parcelize
|
||||
|
||||
@@ -2,7 +2,11 @@ package com.topjohnwu.magisk.core.ktx
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.content.*
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.ContextWrapper
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.content.pm.PackageInfo
|
||||
import android.content.pm.PackageManager
|
||||
@@ -23,7 +27,6 @@ import com.topjohnwu.magisk.core.utils.RootUtils
|
||||
import com.topjohnwu.magisk.utils.APKInstall
|
||||
import com.topjohnwu.superuser.internal.UiThreadHandler
|
||||
import java.io.File
|
||||
import kotlin.String
|
||||
|
||||
fun Context.getBitmap(id: Int): Bitmap {
|
||||
var drawable = getDrawable(id)!!
|
||||
|
||||
@@ -8,6 +8,7 @@ import kotlinx.coroutines.flow.flatMapMerge
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.Closeable
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
@@ -17,24 +18,14 @@ import java.text.DateFormat
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Collections
|
||||
import java.util.Locale
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipInputStream
|
||||
|
||||
inline fun ZipInputStream.forEach(callback: (ZipEntry) -> Unit) {
|
||||
var entry: ZipEntry? = nextEntry
|
||||
while (entry != null) {
|
||||
callback(entry)
|
||||
entry = nextEntry
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <In : InputStream, Out : OutputStream> withStreams(
|
||||
inStream: In,
|
||||
outStream: Out,
|
||||
inline fun <In : Closeable, Out : Closeable> withInOut(
|
||||
input: In,
|
||||
output: Out,
|
||||
withBoth: (In, Out) -> Unit
|
||||
) {
|
||||
inStream.use { reader ->
|
||||
outStream.use { writer ->
|
||||
input.use { reader ->
|
||||
output.use { writer ->
|
||||
withBoth(reader, writer)
|
||||
}
|
||||
}
|
||||
@@ -64,7 +55,7 @@ suspend inline fun InputStream.copyAndClose(
|
||||
out: OutputStream,
|
||||
bufferSize: Int = DEFAULT_BUFFER_SIZE,
|
||||
dispatcher: CoroutineDispatcher = Dispatchers.IO
|
||||
) = withStreams(this, out) { i, o -> i.copyAll(o, bufferSize, dispatcher) }
|
||||
) = withInOut(this, out) { i, o -> i.copyAll(o, bufferSize, dispatcher) }
|
||||
|
||||
@Throws(IOException::class)
|
||||
suspend inline fun InputStream.writeTo(
|
||||
|
||||
@@ -9,7 +9,7 @@ import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
import java.io.IOException
|
||||
import java.util.*
|
||||
import java.util.Locale
|
||||
|
||||
data class LocalModule(
|
||||
private val path: String,
|
||||
@@ -37,6 +37,7 @@ data class LocalModule(
|
||||
val isRiru: Boolean get() = (id == "riru-core") || riruFolder.exists()
|
||||
val isZygisk: Boolean get() = zygiskFolder.exists()
|
||||
val zygiskUnloaded: Boolean get() = unloaded.exists()
|
||||
val hasAction: Boolean;
|
||||
|
||||
var enable: Boolean
|
||||
get() = !disableFile.exists()
|
||||
@@ -100,6 +101,8 @@ data class LocalModule(
|
||||
if (name.isEmpty()) {
|
||||
name = id
|
||||
}
|
||||
|
||||
hasAction = RootUtils.fs.getFile(path, "action.sh").exists()
|
||||
}
|
||||
|
||||
suspend fun fetch(): Boolean {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.topjohnwu.magisk.core.model.su
|
||||
|
||||
import android.content.pm.PackageInfo
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.content.pm.PackageManager
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
@@ -24,7 +24,7 @@ class SuLog(
|
||||
}
|
||||
|
||||
fun PackageManager.createSuLog(
|
||||
info: PackageInfo,
|
||||
info: ApplicationInfo,
|
||||
toUid: Int,
|
||||
fromPid: Int,
|
||||
command: String,
|
||||
@@ -33,13 +33,12 @@ fun PackageManager.createSuLog(
|
||||
context: String,
|
||||
gids: String,
|
||||
): SuLog {
|
||||
val appInfo = info.applicationInfo
|
||||
return SuLog(
|
||||
fromUid = appInfo.uid,
|
||||
fromUid = info.uid,
|
||||
toUid = toUid,
|
||||
fromPid = fromPid,
|
||||
packageName = getNameForUid(appInfo.uid)!!,
|
||||
appName = appInfo.getLabel(this),
|
||||
packageName = getNameForUid(info.uid)!!,
|
||||
appName = info.getLabel(this),
|
||||
command = command,
|
||||
action = policy,
|
||||
target = target,
|
||||
|
||||
@@ -1,22 +1,32 @@
|
||||
package com.topjohnwu.magisk.core.model.su
|
||||
|
||||
class SuPolicy(val uid: Int) {
|
||||
import com.topjohnwu.magisk.core.data.magiskdb.MagiskDB
|
||||
|
||||
class SuPolicy(
|
||||
val uid: Int,
|
||||
var policy: Int = INTERACTIVE,
|
||||
var remain: Long = -1L,
|
||||
var logging: Boolean = true,
|
||||
var notification: Boolean = true,
|
||||
) {
|
||||
companion object {
|
||||
const val INTERACTIVE = 0
|
||||
const val DENY = 1
|
||||
const val ALLOW = 2
|
||||
}
|
||||
|
||||
var policy: Int = INTERACTIVE
|
||||
var until: Long = -1L
|
||||
var logging: Boolean = true
|
||||
var notification: Boolean = true
|
||||
|
||||
fun toMap(): MutableMap<String, Any> = mutableMapOf(
|
||||
fun toMap(): MutableMap<String, Any> {
|
||||
val until = if (remain <= 0) {
|
||||
remain
|
||||
} else {
|
||||
MagiskDB.Literal("(strftime(\"%s\", \"now\") + $remain)")
|
||||
}
|
||||
return mutableMapOf(
|
||||
"uid" to uid,
|
||||
"policy" to policy,
|
||||
"until" to until,
|
||||
"logging" to logging,
|
||||
"notification" to notification
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ object SuCallbackHandler {
|
||||
val pm = context.packageManager
|
||||
|
||||
val log = runCatching {
|
||||
pm.getPackageInfo(fromUid, pid)?.let {
|
||||
pm.getPackageInfo(fromUid, pid)?.applicationInfo?.let {
|
||||
pm.createSuLog(it, toUid, pid, command, policy, target, seContext, gids)
|
||||
}
|
||||
}.getOrNull() ?: createSuLog(fromUid, toUid, pid, command, policy, target, seContext, gids)
|
||||
|
||||
@@ -62,7 +62,7 @@ class SuRequestHandler(
|
||||
return false
|
||||
}
|
||||
output = File(fifo)
|
||||
policy = SuPolicy(uid)
|
||||
policy = policyDB.fetch(uid) ?: SuPolicy(uid)
|
||||
try {
|
||||
pkgInfo = pm.getPackageInfo(uid, pid) ?: PackageInfo().apply {
|
||||
val name = pm.getNameForUid(uid) ?: throw PackageManager.NameNotFoundException()
|
||||
@@ -81,15 +81,13 @@ class SuRequestHandler(
|
||||
return true
|
||||
}
|
||||
|
||||
suspend fun respond(action: Int, time: Int) {
|
||||
val until = if (time > 0)
|
||||
TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()) +
|
||||
TimeUnit.MINUTES.toSeconds(time.toLong())
|
||||
else
|
||||
time.toLong()
|
||||
|
||||
suspend fun respond(action: Int, time: Long) {
|
||||
policy.policy = action
|
||||
policy.until = until
|
||||
if (time >= 0) {
|
||||
policy.remain = TimeUnit.MINUTES.toSeconds(time)
|
||||
} else {
|
||||
policy.remain = time
|
||||
}
|
||||
|
||||
withContext(Dispatchers.IO) {
|
||||
try {
|
||||
@@ -100,7 +98,7 @@ class SuRequestHandler(
|
||||
} catch (e: IOException) {
|
||||
Timber.e(e)
|
||||
}
|
||||
if (until >= 0) {
|
||||
if (time >= 0) {
|
||||
policyDB.update(policy)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
package com.topjohnwu.magisk.core.su
|
||||
|
||||
import android.os.Bundle
|
||||
import com.topjohnwu.magisk.core.Config
|
||||
import com.topjohnwu.magisk.core.Info
|
||||
import com.topjohnwu.magisk.core.di.ServiceLocator
|
||||
import com.topjohnwu.magisk.core.tasks.MagiskInstaller
|
||||
import com.topjohnwu.magisk.core.utils.RootUtils
|
||||
import com.topjohnwu.superuser.CallbackList
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import kotlinx.coroutines.Runnable
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import timber.log.Timber
|
||||
|
||||
object TestHandler {
|
||||
|
||||
object LogList : CallbackList<String>(Runnable::run) {
|
||||
override fun onAddElement(e: String) {
|
||||
Timber.i(e)
|
||||
}
|
||||
}
|
||||
|
||||
fun run(method: String): Bundle {
|
||||
var reason: String? = null
|
||||
|
||||
fun prerequisite(): Boolean {
|
||||
// Make sure the Magisk app can get root
|
||||
val shell = Shell.getShell()
|
||||
if (!shell.isRoot) {
|
||||
reason = "shell not root"
|
||||
return false
|
||||
}
|
||||
|
||||
// Make sure the root service is running
|
||||
RootUtils.Connection.await()
|
||||
return true
|
||||
}
|
||||
|
||||
fun setup(): Boolean {
|
||||
return runBlocking {
|
||||
MagiskInstaller.Emulator(LogList, LogList).exec()
|
||||
}
|
||||
}
|
||||
|
||||
fun test(): Boolean {
|
||||
// Make sure Zygisk works correctly
|
||||
if (!Info.isZygiskEnabled) {
|
||||
reason = "zygisk not enabled"
|
||||
return false
|
||||
}
|
||||
|
||||
// Clear existing grant for ADB shell
|
||||
runBlocking {
|
||||
ServiceLocator.policyDB.delete(2000)
|
||||
Config.suAutoResponse = Config.Value.SU_AUTO_ALLOW
|
||||
Config.prefs.edit().commit()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
val result = prerequisite() && runCatching {
|
||||
when (method) {
|
||||
"setup" -> setup()
|
||||
"test" -> test()
|
||||
else -> {
|
||||
reason = "unknown method"
|
||||
false
|
||||
}
|
||||
}
|
||||
}.getOrElse {
|
||||
reason = it.stackTraceToString()
|
||||
false
|
||||
}
|
||||
|
||||
return Bundle().apply {
|
||||
putBoolean("result", result)
|
||||
if (reason != null) putString("reason", reason)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import android.app.Activity
|
||||
import android.app.ActivityOptions
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import android.widget.Toast
|
||||
import com.topjohnwu.magisk.StubApk
|
||||
@@ -13,7 +14,6 @@ import com.topjohnwu.magisk.core.Config
|
||||
import com.topjohnwu.magisk.core.Const
|
||||
import com.topjohnwu.magisk.core.R
|
||||
import com.topjohnwu.magisk.core.ktx.await
|
||||
import com.topjohnwu.magisk.core.ktx.copyAndClose
|
||||
import com.topjohnwu.magisk.core.ktx.toast
|
||||
import com.topjohnwu.magisk.core.ktx.writeTo
|
||||
import com.topjohnwu.magisk.core.signing.JarMap
|
||||
@@ -23,11 +23,9 @@ import com.topjohnwu.magisk.core.utils.Keygen
|
||||
import com.topjohnwu.magisk.utils.APKInstall
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Runnable
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.IOException
|
||||
import java.io.OutputStream
|
||||
import java.security.SecureRandom
|
||||
@@ -38,6 +36,7 @@ object AppMigration {
|
||||
private const val ALPHA = "abcdefghijklmnopqrstuvwxyz"
|
||||
private const val ALPHADOTS = "$ALPHA....."
|
||||
private const val ANDROID_MANIFEST = "AndroidManifest.xml"
|
||||
private const val TEST_PKG_NAME = "$APP_PACKAGE_NAME.test"
|
||||
|
||||
// Some arbitrary limit
|
||||
const val MAX_LABEL_LENGTH = 32
|
||||
@@ -125,28 +124,23 @@ object AppMigration {
|
||||
apk: File, out: OutputStream,
|
||||
pkg: String, label: CharSequence
|
||||
): Boolean {
|
||||
val info = context.packageManager.getPackageArchiveInfo(apk.path, 0) ?: return false
|
||||
val origLabel = info.applicationInfo.nonLocalizedLabel.toString()
|
||||
val pm = context.packageManager
|
||||
val info = pm.getPackageArchiveInfo(apk.path, 0)?.applicationInfo ?: return false
|
||||
val origLabel = info.nonLocalizedLabel.toString()
|
||||
try {
|
||||
JarMap.open(apk, true).use { jar ->
|
||||
val je = jar.getJarEntry(ANDROID_MANIFEST)
|
||||
val xml = AXML(jar.getRawData(je))
|
||||
val generator = classNameGenerator()
|
||||
|
||||
if (!xml.patchStrings {
|
||||
for (i in it.indices) {
|
||||
val s = it[i]
|
||||
if (s.contains(APP_PACKAGE_NAME)) {
|
||||
it[i] = s.replace(APP_PACKAGE_NAME, pkg)
|
||||
} else if (s.contains(PLACEHOLDER)) {
|
||||
it[i] = generator.next()
|
||||
} else if (s == origLabel) {
|
||||
it[i] = label.toString()
|
||||
val p = xml.patchStrings {
|
||||
when {
|
||||
it.contains(APP_PACKAGE_NAME) -> it.replace(APP_PACKAGE_NAME, pkg)
|
||||
it.contains(PLACEHOLDER) -> generator.next()
|
||||
it == origLabel -> label.toString()
|
||||
else -> it
|
||||
}
|
||||
}
|
||||
}) {
|
||||
return false
|
||||
}
|
||||
if (!p) return false
|
||||
|
||||
// Write apk changes
|
||||
jar.getOutputStream(je).use { it.write(xml.bytes) }
|
||||
@@ -160,51 +154,87 @@ object AppMigration {
|
||||
}
|
||||
}
|
||||
|
||||
private fun launchApp(activity: Activity, pkg: String) {
|
||||
val intent = activity.packageManager.getLaunchIntentForPackage(pkg) ?: return
|
||||
private fun patchTest(apk: File, out: File, pkg: String): Boolean {
|
||||
try {
|
||||
JarMap.open(apk, true).use { jar ->
|
||||
val je = jar.getJarEntry(ANDROID_MANIFEST)
|
||||
val xml = AXML(jar.getRawData(je))
|
||||
val p = xml.patchStrings {
|
||||
when (it) {
|
||||
APP_PACKAGE_NAME -> pkg
|
||||
TEST_PKG_NAME -> "$pkg.test"
|
||||
else -> it
|
||||
}
|
||||
}
|
||||
if (!p) return false
|
||||
|
||||
// Write apk changes
|
||||
jar.getOutputStream(je).use { it.write(xml.bytes) }
|
||||
val keys = Keygen()
|
||||
out.outputStream().use { SignApk.sign(keys.cert, keys.key, jar, it) }
|
||||
return true
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
private fun launchApp(context: Context, pkg: String) {
|
||||
val intent = context.packageManager.getLaunchIntentForPackage(pkg) ?: return
|
||||
intent.putExtra(Const.Key.PREV_CONFIG, Config.toBundle())
|
||||
val options = ActivityOptions.makeBasic()
|
||||
if (Build.VERSION.SDK_INT >= 34) {
|
||||
options.setShareIdentityEnabled(true)
|
||||
}
|
||||
activity.startActivity(intent, options.toBundle())
|
||||
activity.finish()
|
||||
context.startActivity(intent, options.toBundle())
|
||||
if (context is Activity) {
|
||||
context.finish()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun patchAndHide(activity: Activity, label: String, onFailure: Runnable): Boolean {
|
||||
val stub = File(activity.cacheDir, "stub.apk")
|
||||
suspend fun patchAndHide(context: Context, label: String, pkg: String? = null): Boolean {
|
||||
val stub = File(context.cacheDir, "stub.apk")
|
||||
try {
|
||||
activity.assets.open("stub.apk").writeTo(stub)
|
||||
context.assets.open("stub.apk").writeTo(stub)
|
||||
} catch (e: IOException) {
|
||||
Timber.e(e)
|
||||
return false
|
||||
}
|
||||
|
||||
// Generate a new random package name and signature
|
||||
val repack = File(activity.cacheDir, "patched.apk")
|
||||
val pkg = genPackageName()
|
||||
// Generate a new random signature and package name if needed
|
||||
val pkg = pkg ?: genPackageName()
|
||||
Config.keyStoreRaw = ""
|
||||
|
||||
if (!patch(activity, stub, FileOutputStream(repack), pkg, label))
|
||||
// Check and patch the test APK
|
||||
try {
|
||||
val info = context.packageManager.getApplicationInfo(TEST_PKG_NAME, 0)
|
||||
val testApk = File(info.sourceDir)
|
||||
val testRepack = File(context.cacheDir, "test.apk")
|
||||
if (!patchTest(testApk, testRepack, pkg))
|
||||
return false
|
||||
val cmd = "adb_pm_install $testRepack $pkg.test"
|
||||
if (!Shell.cmd(cmd).exec().isSuccess)
|
||||
return false
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
}
|
||||
|
||||
val repack = File(context.cacheDir, "patched.apk")
|
||||
repack.outputStream().use {
|
||||
if (!patch(context, stub, it, pkg, label))
|
||||
return false
|
||||
}
|
||||
|
||||
// Install and auto launch app
|
||||
val session = APKInstall.startSession(activity, pkg, onFailure) {
|
||||
launchApp(activity, pkg)
|
||||
}
|
||||
|
||||
val cmd = "adb_pm_install $repack $pkg"
|
||||
if (Shell.cmd(cmd).exec().isSuccess) {
|
||||
Config.suManager = pkg
|
||||
val cmd = "touch $AppApkPath; adb_pm_install $repack $pkg"
|
||||
if (Shell.cmd(cmd).exec().isSuccess) return true
|
||||
|
||||
try {
|
||||
repack.inputStream().copyAndClose(session.openStream(activity))
|
||||
} catch (e: IOException) {
|
||||
Timber.e(e)
|
||||
Shell.cmd("touch $AppApkPath").exec()
|
||||
launchApp(context, pkg)
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
session.waitIntent()?.let { activity.startActivity(it) } ?: return false
|
||||
return true
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
@@ -215,14 +245,25 @@ object AppMigration {
|
||||
setCancelable(false)
|
||||
show()
|
||||
}
|
||||
val onFailure = Runnable {
|
||||
val success = withContext(Dispatchers.IO) {
|
||||
patchAndHide(activity, label)
|
||||
}
|
||||
if (!success) {
|
||||
dialog.dismiss()
|
||||
activity.toast(R.string.failure, Toast.LENGTH_LONG)
|
||||
}
|
||||
val success = withContext(Dispatchers.IO) {
|
||||
patchAndHide(activity, label, onFailure)
|
||||
}
|
||||
if (!success) onFailure.run()
|
||||
|
||||
suspend fun restoreApp(context: Context): Boolean {
|
||||
val apk = StubApk.current(context)
|
||||
val cmd = "adb_pm_install $apk $APP_PACKAGE_NAME"
|
||||
if (Shell.cmd(cmd).await().isSuccess) {
|
||||
Config.suManager = ""
|
||||
Shell.cmd("touch $AppApkPath").exec()
|
||||
launchApp(context, APP_PACKAGE_NAME)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
@@ -233,30 +274,11 @@ object AppMigration {
|
||||
setCancelable(false)
|
||||
show()
|
||||
}
|
||||
val onFailure = Runnable {
|
||||
dialog.dismiss()
|
||||
if (!restoreApp(activity)) {
|
||||
activity.toast(R.string.failure, Toast.LENGTH_LONG)
|
||||
}
|
||||
val apk = StubApk.current(activity)
|
||||
val session = APKInstall.startSession(activity, APP_PACKAGE_NAME, onFailure) {
|
||||
launchApp(activity, APP_PACKAGE_NAME)
|
||||
dialog.dismiss()
|
||||
}
|
||||
Config.suManager = ""
|
||||
val cmd = "touch $AppApkPath; adb_pm_install $apk $APP_PACKAGE_NAME"
|
||||
if (Shell.cmd(cmd).await().isSuccess) return
|
||||
val success = withContext(Dispatchers.IO) {
|
||||
try {
|
||||
apk.inputStream().copyAndClose(session.openStream(activity))
|
||||
} catch (e: IOException) {
|
||||
Timber.e(e)
|
||||
return@withContext false
|
||||
}
|
||||
session.waitIntent()?.let { activity.startActivity(it) } ?: return@withContext false
|
||||
return@withContext true
|
||||
}
|
||||
if (!success) onFailure.run()
|
||||
}
|
||||
|
||||
suspend fun upgradeStub(context: Context, apk: File): Intent? {
|
||||
val label = context.applicationInfo.nonLocalizedLabel
|
||||
|
||||
@@ -7,7 +7,6 @@ import com.topjohnwu.magisk.core.Const
|
||||
import com.topjohnwu.magisk.core.ktx.writeTo
|
||||
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.superuser.Shell
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
@@ -47,20 +46,14 @@ open class FlashZip(
|
||||
}
|
||||
}
|
||||
|
||||
val isValid = try {
|
||||
zipFile.unzip(installDir, "META-INF/com/google/android", true)
|
||||
val script = File(installDir, "updater-script")
|
||||
script.readText().contains("#MAGISK")
|
||||
try {
|
||||
val binary = File(installDir, "update-binary")
|
||||
AppContext.assets.open("module_installer.sh").use { it.writeTo(binary) }
|
||||
} catch (e: IOException) {
|
||||
console.add("! Unzip error")
|
||||
throw e
|
||||
}
|
||||
|
||||
if (!isValid) {
|
||||
console.add("! This zip is not a Magisk module!")
|
||||
return false
|
||||
}
|
||||
|
||||
console.add("- Installing ${mUri.displayName}")
|
||||
|
||||
return Shell.cmd("sh $installDir/update-binary dummy 1 \'$zipFile\'")
|
||||
|
||||
@@ -6,7 +6,6 @@ import android.system.ErrnoException
|
||||
import android.system.Os
|
||||
import android.system.OsConstants
|
||||
import android.system.OsConstants.O_WRONLY
|
||||
import android.widget.Toast
|
||||
import androidx.annotation.WorkerThread
|
||||
import androidx.core.os.postDelayed
|
||||
import com.topjohnwu.magisk.StubApk
|
||||
@@ -15,13 +14,10 @@ import com.topjohnwu.magisk.core.BuildConfig
|
||||
import com.topjohnwu.magisk.core.Config
|
||||
import com.topjohnwu.magisk.core.Const
|
||||
import com.topjohnwu.magisk.core.Info
|
||||
import com.topjohnwu.magisk.core.R
|
||||
import com.topjohnwu.magisk.core.di.ServiceLocator
|
||||
import com.topjohnwu.magisk.core.isRunningAsStub
|
||||
import com.topjohnwu.magisk.core.ktx.copyAll
|
||||
import com.topjohnwu.magisk.core.ktx.copyAndClose
|
||||
import com.topjohnwu.magisk.core.ktx.reboot
|
||||
import com.topjohnwu.magisk.core.ktx.toast
|
||||
import com.topjohnwu.magisk.core.ktx.writeTo
|
||||
import com.topjohnwu.magisk.core.utils.DummyList
|
||||
import com.topjohnwu.magisk.core.utils.MediaStoreUtils
|
||||
@@ -134,12 +130,14 @@ abstract class MagiskInstallImpl protected constructor(
|
||||
|
||||
val abi32 = Const.CPU_ABI_32
|
||||
if (Process.is64Bit() && abi32 != null) {
|
||||
val magisk32 = File(installDir, "magisk32")
|
||||
val entry = zf.getEntry("lib/$abi32/libmagisk.so")
|
||||
if (entry != null) {
|
||||
val magisk32 = File(installDir, "magisk32")
|
||||
zf.getInputStream(entry).writeTo(magisk32)
|
||||
magisk32.setExecutable(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val info = context.applicationInfo
|
||||
val libs = File(info.nativeLibraryDir).listFiles { _, name ->
|
||||
@@ -583,6 +581,8 @@ abstract class MagiskInstallImpl protected constructor(
|
||||
|
||||
protected suspend fun fixEnv() = extractFiles() && "fix_env $installDir".sh().isSuccess
|
||||
|
||||
protected fun restore() = findImage() && "restore_imgs $srcBoot".sh().isSuccess
|
||||
|
||||
protected fun uninstall() = "run_uninstaller $AppApkPath".sh().isSuccess
|
||||
|
||||
@WorkerThread
|
||||
@@ -597,6 +597,8 @@ abstract class MagiskInstallImpl protected constructor(
|
||||
if (result)
|
||||
return true
|
||||
|
||||
// Not every operation initializes installDir
|
||||
if (::installDir.isInitialized)
|
||||
Shell.cmd("rm -rf $installDir").submit()
|
||||
return false
|
||||
}
|
||||
@@ -606,11 +608,10 @@ abstract class MagiskInstallImpl protected constructor(
|
||||
}
|
||||
}
|
||||
|
||||
abstract class MagiskInstaller(
|
||||
abstract class ConsoleInstaller(
|
||||
console: MutableList<String>,
|
||||
logs: MutableList<String>
|
||||
) : MagiskInstallImpl(console, logs) {
|
||||
|
||||
override suspend fun exec(): Boolean {
|
||||
val success = super.exec()
|
||||
if (success) {
|
||||
@@ -620,40 +621,51 @@ abstract class MagiskInstaller(
|
||||
}
|
||||
return success
|
||||
}
|
||||
}
|
||||
|
||||
abstract class CallBackInstaller : MagiskInstallImpl(DummyList, DummyList) {
|
||||
suspend fun exec(callback: (Boolean) -> Unit): Boolean {
|
||||
val success = exec()
|
||||
callback(success)
|
||||
return success
|
||||
}
|
||||
}
|
||||
|
||||
class MagiskInstaller {
|
||||
|
||||
class Patch(
|
||||
private val uri: Uri,
|
||||
console: MutableList<String>,
|
||||
logs: MutableList<String>
|
||||
) : MagiskInstaller(console, logs) {
|
||||
) : ConsoleInstaller(console, logs) {
|
||||
override suspend fun operations() = patchFile(uri)
|
||||
}
|
||||
|
||||
class SecondSlot(
|
||||
console: MutableList<String>,
|
||||
logs: MutableList<String>
|
||||
) : MagiskInstaller(console, logs) {
|
||||
) : ConsoleInstaller(console, logs) {
|
||||
override suspend fun operations() = secondSlot()
|
||||
}
|
||||
|
||||
class Direct(
|
||||
console: MutableList<String>,
|
||||
logs: MutableList<String>
|
||||
) : MagiskInstaller(console, logs) {
|
||||
) : ConsoleInstaller(console, logs) {
|
||||
override suspend fun operations() = direct()
|
||||
}
|
||||
|
||||
class Emulator(
|
||||
console: MutableList<String>,
|
||||
logs: MutableList<String>
|
||||
) : MagiskInstaller(console, logs) {
|
||||
) : ConsoleInstaller(console, logs) {
|
||||
override suspend fun operations() = fixEnv()
|
||||
}
|
||||
|
||||
class Uninstall(
|
||||
console: MutableList<String>,
|
||||
logs: MutableList<String>
|
||||
) : MagiskInstallImpl(console, logs) {
|
||||
) : ConsoleInstaller(console, logs) {
|
||||
override suspend fun operations() = uninstall()
|
||||
|
||||
override suspend fun exec(): Boolean {
|
||||
@@ -667,19 +679,11 @@ abstract class MagiskInstaller(
|
||||
}
|
||||
}
|
||||
|
||||
class FixEnv(private val callback: () -> Unit) : MagiskInstallImpl(DummyList, DummyList) {
|
||||
override suspend fun operations() = fixEnv()
|
||||
|
||||
override suspend fun exec(): Boolean {
|
||||
val success = super.exec()
|
||||
callback()
|
||||
context.toast(
|
||||
if (success) R.string.reboot_delay_toast else R.string.setup_fail,
|
||||
Toast.LENGTH_LONG
|
||||
)
|
||||
if (success)
|
||||
UiThreadHandler.handler.postDelayed(5000) { reboot() }
|
||||
return success
|
||||
class Restore : CallBackInstaller() {
|
||||
override suspend fun operations() = restore()
|
||||
}
|
||||
|
||||
class FixEnv : CallBackInstaller() {
|
||||
override suspend fun operations() = fixEnv()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ class AXML(b: ByteArray) {
|
||||
* Followed by an array of uint32_t with size = number of strings
|
||||
* Each entry points to an offset into the string data
|
||||
*/
|
||||
fun patchStrings(patchFn: (Array<String>) -> Unit): Boolean {
|
||||
fun patchStrings(mapFn: (String) -> String): Boolean {
|
||||
val buffer = ByteBuffer.wrap(bytes).order(LITTLE_ENDIAN)
|
||||
|
||||
fun findStringPool(): Int {
|
||||
@@ -65,7 +65,9 @@ class AXML(b: ByteArray) {
|
||||
}
|
||||
|
||||
val strArr = strList.toTypedArray()
|
||||
patchFn(strArr)
|
||||
for (i in strArr.indices) {
|
||||
strArr[i] = mapFn(strArr[i])
|
||||
}
|
||||
|
||||
// Write everything before string data, will patch values later
|
||||
val baos = RawByteStream()
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.topjohnwu.magisk.core.utils;
|
||||
|
||||
import android.os.Build;
|
||||
|
||||
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
|
||||
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
|
||||
import org.apache.commons.compress.archivers.zip.ZipUtil;
|
||||
|
||||
import java.nio.file.attribute.FileTime;
|
||||
import java.util.zip.ZipEntry;
|
||||
|
||||
public class Desugar {
|
||||
public static FileTime getLastModifiedTime(ZipEntry entry) {
|
||||
if (Build.VERSION.SDK_INT >= 26) {
|
||||
return entry.getLastModifiedTime();
|
||||
} else {
|
||||
return FileTime.fromMillis(entry.getTime());
|
||||
}
|
||||
}
|
||||
|
||||
public static FileTime getLastAccessTime(ZipEntry entry) {
|
||||
if (Build.VERSION.SDK_INT >= 26) {
|
||||
return entry.getLastAccessTime();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static FileTime getCreationTime(ZipEntry entry) {
|
||||
if (Build.VERSION.SDK_INT >= 26) {
|
||||
return entry.getCreationTime();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Within {@link ZipArchiveOutputStream#copyFromZipInputStream}, we redirect the method call
|
||||
* {@link ZipUtil#checkRequestedFeatures} to this method. This is safe because the only usage
|
||||
* of copyFromZipInputStream is in {@link ZipArchiveOutputStream#addRawArchiveEntry},
|
||||
* which does not need to actually understand the content of the zip entry. By removing
|
||||
* this feature check, we can modify zip files using unsupported compression methods.
|
||||
*/
|
||||
public static void checkRequestedFeatures(final ZipArchiveEntry ze) {
|
||||
// No-op
|
||||
}
|
||||
}
|
||||
@@ -11,13 +11,10 @@ import android.net.NetworkRequest
|
||||
import android.os.PowerManager
|
||||
import androidx.collection.ArraySet
|
||||
import androidx.core.content.getSystemService
|
||||
import androidx.lifecycle.DefaultLifecycleObserver
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.ProcessLifecycleOwner
|
||||
import com.topjohnwu.magisk.core.Info
|
||||
import com.topjohnwu.magisk.core.ktx.registerRuntimeReceiver
|
||||
|
||||
class NetworkObserver(context: Context): DefaultLifecycleObserver {
|
||||
class NetworkObserver(context: Context) {
|
||||
private val manager = context.getSystemService<ConnectivityManager>()!!
|
||||
|
||||
private val networkCallback = object : ConnectivityManager.NetworkCallback() {
|
||||
@@ -55,16 +52,13 @@ class NetworkObserver(context: Context): DefaultLifecycleObserver {
|
||||
manager.registerNetworkCallback(request, networkCallback)
|
||||
val filter = IntentFilter(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED)
|
||||
context.applicationContext.registerRuntimeReceiver(receiver, filter)
|
||||
ProcessLifecycleOwner.get().lifecycle.addObserver(this)
|
||||
}
|
||||
|
||||
override fun onStart(owner: LifecycleOwner) {
|
||||
postCurrentState()
|
||||
}
|
||||
|
||||
private fun postCurrentState() {
|
||||
postValue(manager.getNetworkCapabilities(manager.activeNetwork)
|
||||
?.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) ?: false)
|
||||
fun postCurrentState() {
|
||||
postValue(
|
||||
manager.getNetworkCapabilities(manager.activeNetwork)
|
||||
?.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) == true
|
||||
)
|
||||
}
|
||||
|
||||
private fun postValue(b: Boolean) {
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
package com.topjohnwu.magisk.core.utils;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.LifecycleDispatcher;
|
||||
import androidx.lifecycle.ProcessLifecycleOwner;
|
||||
|
||||
// Use Java to bypass Kotlin internal visibility modifier
|
||||
public class ProcessLifecycle {
|
||||
public static void init(@NonNull Context context) {
|
||||
LifecycleDispatcher.init(context);
|
||||
ProcessLifecycleOwner.init$lifecycle_process_release(context);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.topjohnwu.magisk.test
|
||||
|
||||
import android.os.ParcelFileDescriptor.AutoCloseInputStream
|
||||
import androidx.annotation.Keep
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.uiautomator.By
|
||||
import androidx.test.uiautomator.Until
|
||||
import org.junit.After
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assume.assumeTrue
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.regex.Pattern
|
||||
|
||||
@Keep
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class AdditionalTest : BaseTest {
|
||||
|
||||
companion object {
|
||||
private const val SHELL_PKG = "com.android.shell"
|
||||
private const val LSPOSED_CATEGORY = "org.lsposed.manager.LAUNCH_MANAGER"
|
||||
private const val LSPOSED_PKG = "org.lsposed.manager"
|
||||
}
|
||||
|
||||
@After
|
||||
fun teardown() {
|
||||
device.pressHome()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testLaunchLsposedManager() {
|
||||
assumeTrue(Environment.lsposed())
|
||||
|
||||
uiAutomation.executeShellCommand(
|
||||
"am start -c $LSPOSED_CATEGORY $SHELL_PKG/.BugreportWarningActivity"
|
||||
).let { pfd -> AutoCloseInputStream(pfd).use { it.readBytes() } }
|
||||
|
||||
val pattern = Pattern.compile("$LSPOSED_PKG:id/.*")
|
||||
assertNotNull(
|
||||
"LSPosed manager launch failed",
|
||||
device.wait(Until.hasObject(By.res(pattern)), TimeUnit.SECONDS.toMillis(10))
|
||||
)
|
||||
}
|
||||
}
|
||||
26
app/core/src/main/java/com/topjohnwu/magisk/test/BaseTest.kt
Normal file
26
app/core/src/main/java/com/topjohnwu/magisk/test/BaseTest.kt
Normal file
@@ -0,0 +1,26 @@
|
||||
package com.topjohnwu.magisk.test
|
||||
|
||||
import android.app.Instrumentation
|
||||
import android.app.UiAutomation
|
||||
import android.content.Context
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.uiautomator.UiDevice
|
||||
import com.topjohnwu.magisk.core.utils.RootUtils
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import org.junit.Assert.assertTrue
|
||||
|
||||
interface BaseTest {
|
||||
val instrumentation: Instrumentation
|
||||
get() = InstrumentationRegistry.getInstrumentation()
|
||||
val context: Context get() = instrumentation.targetContext
|
||||
val uiAutomation: UiAutomation get() = instrumentation.uiAutomation
|
||||
val device: UiDevice get() = UiDevice.getInstance(instrumentation)
|
||||
|
||||
companion object {
|
||||
fun prerequisite() {
|
||||
assertTrue("Should have root access", Shell.getShell().isRoot)
|
||||
// Make sure the root service is running
|
||||
RootUtils.Connection.await()
|
||||
}
|
||||
}
|
||||
}
|
||||
102
app/core/src/main/java/com/topjohnwu/magisk/test/Environment.kt
Normal file
102
app/core/src/main/java/com/topjohnwu/magisk/test/Environment.kt
Normal file
@@ -0,0 +1,102 @@
|
||||
package com.topjohnwu.magisk.test
|
||||
|
||||
import android.app.Notification
|
||||
import android.os.Build
|
||||
import androidx.annotation.Keep
|
||||
import androidx.core.net.toUri
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.topjohnwu.magisk.core.BuildConfig.APP_PACKAGE_NAME
|
||||
import com.topjohnwu.magisk.core.di.ServiceLocator
|
||||
import com.topjohnwu.magisk.core.download.DownloadNotifier
|
||||
import com.topjohnwu.magisk.core.download.DownloadProcessor
|
||||
import com.topjohnwu.magisk.core.ktx.cachedFile
|
||||
import com.topjohnwu.magisk.core.tasks.AppMigration
|
||||
import com.topjohnwu.magisk.core.tasks.FlashZip
|
||||
import com.topjohnwu.magisk.core.tasks.MagiskInstaller
|
||||
import com.topjohnwu.superuser.CallbackList
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Assume.assumeTrue
|
||||
import org.junit.BeforeClass
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import timber.log.Timber
|
||||
|
||||
@Keep
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class Environment : BaseTest {
|
||||
|
||||
companion object {
|
||||
@BeforeClass
|
||||
@JvmStatic
|
||||
fun before() = BaseTest.prerequisite()
|
||||
|
||||
fun lsposed(): Boolean {
|
||||
return Build.VERSION.SDK_INT >= 27 && Build.VERSION.SDK_INT <= 34
|
||||
}
|
||||
|
||||
private const val LSPOSED_URL =
|
||||
"https://github.com/LSPosed/LSPosed/releases/download/v1.9.2/LSPosed-v1.9.2-7024-zygisk-release.zip"
|
||||
}
|
||||
|
||||
object TimberLog : CallbackList<String>(Runnable::run) {
|
||||
override fun onAddElement(e: String) {
|
||||
Timber.i(e)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun setupMagisk() {
|
||||
runBlocking {
|
||||
assertTrue(
|
||||
"Magisk setup failed",
|
||||
MagiskInstaller.Emulator(TimberLog, TimberLog).exec()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun setupLsposed() {
|
||||
assumeTrue(lsposed())
|
||||
|
||||
val notify = object : DownloadNotifier {
|
||||
override val context = this@Environment.context
|
||||
override fun notifyUpdate(id: Int, editor: (Notification.Builder) -> Unit) {}
|
||||
}
|
||||
val processor = DownloadProcessor(notify)
|
||||
val zip = context.cachedFile("lsposed.zip")
|
||||
runBlocking {
|
||||
ServiceLocator.networkService.fetchFile(LSPOSED_URL).byteStream().use {
|
||||
processor.handleModule(it, zip.toUri())
|
||||
}
|
||||
assertTrue(
|
||||
"LSPosed installation failed",
|
||||
FlashZip(zip.toUri(), TimberLog, TimberLog).exec()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun setupAppHide() {
|
||||
runBlocking {
|
||||
assertTrue(
|
||||
"App hiding failed",
|
||||
AppMigration.patchAndHide(
|
||||
context = context,
|
||||
label = "Settings",
|
||||
pkg = "repackaged.$APP_PACKAGE_NAME"
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun setupAppRestore() {
|
||||
runBlocking {
|
||||
assertTrue(
|
||||
"App restoration failed",
|
||||
AppMigration.restoreApp(context)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package com.topjohnwu.magisk.test
|
||||
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.os.Build
|
||||
import android.os.ParcelFileDescriptor.AutoCloseInputStream
|
||||
import androidx.annotation.Keep
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.topjohnwu.magisk.core.Config
|
||||
import com.topjohnwu.magisk.core.Info
|
||||
import com.topjohnwu.magisk.core.di.ServiceLocator
|
||||
import com.topjohnwu.magisk.core.model.su.SuPolicy
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.BeforeClass
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
@Keep
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class MagiskAppTest : BaseTest {
|
||||
|
||||
companion object {
|
||||
@BeforeClass
|
||||
@JvmStatic
|
||||
fun before() = BaseTest.prerequisite()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testZygisk() {
|
||||
assertTrue("Zygisk should be enabled", Info.isZygiskEnabled)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSuRequest() {
|
||||
// Bypass the need to actually show a dialog
|
||||
Config.suAutoResponse = Config.Value.SU_AUTO_ALLOW
|
||||
Config.prefs.edit().commit()
|
||||
|
||||
// Inject an undetermined + mute logging policy for ADB shell
|
||||
val policy = SuPolicy(
|
||||
uid = 2000,
|
||||
logging = false,
|
||||
notification = false,
|
||||
remain = 0L
|
||||
)
|
||||
runBlocking {
|
||||
ServiceLocator.policyDB.update(policy)
|
||||
}
|
||||
|
||||
val filter = IntentFilter(Intent.ACTION_VIEW)
|
||||
filter.addCategory(Intent.CATEGORY_DEFAULT)
|
||||
val monitor = instrumentation.addMonitor(filter, null, false)
|
||||
|
||||
// Try to call su from ADB shell
|
||||
val cmd = if (Build.VERSION.SDK_INT < 24) {
|
||||
// API 23 runs executeShellCommand as root
|
||||
"/system/xbin/su 2000 su -c id"
|
||||
} else {
|
||||
"su -c id"
|
||||
}
|
||||
val pfd = uiAutomation.executeShellCommand(cmd)
|
||||
|
||||
// Make sure SuRequestActivity is launched
|
||||
val suRequest = monitor.waitForActivityWithTimeout(TimeUnit.SECONDS.toMillis(10))
|
||||
assertNotNull("SuRequestActivity is not launched", suRequest)
|
||||
|
||||
// Check that the request went through
|
||||
AutoCloseInputStream(pfd).reader().use {
|
||||
assertTrue(
|
||||
"Cannot grant root permission from shell",
|
||||
it.readText().contains("uid=0")
|
||||
)
|
||||
}
|
||||
|
||||
// Check that the database is updated
|
||||
runBlocking {
|
||||
val policy = ServiceLocator.policyDB.fetch(2000)
|
||||
?: throw AssertionError("PolicyDB is invalid")
|
||||
assertEquals("Policy for shell is incorrect", SuPolicy.ALLOW, policy.policy)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
<string name="modules">Módulos</string>
|
||||
<string name="superuser">Superusuariu</string>
|
||||
<string name="logs">Rexistru</string>
|
||||
<string name="settings">Axustes</string>
|
||||
<string name="settings">Configuración</string>
|
||||
<string name="install">Instalar</string>
|
||||
<string name="section_home">Aniciu</string>
|
||||
<string name="section_theme">Estilos</string>
|
||||
@@ -15,7 +15,7 @@
|
||||
<string name="loading">Cargando…</string>
|
||||
<string name="update">Anovar</string>
|
||||
<string name="not_available">N/D</string>
|
||||
<string name="hide">Anubrir</string>
|
||||
<string name="hide">Esconder</string>
|
||||
<string name="home_package">Paquete</string>
|
||||
<string name="home_app_title">Aplicación</string>
|
||||
<string name="home_notice_content">Baxa Magisk NAMÁS dende la páxina oficial de GitHub. ¡Los ficheros de fontes desconocíes puen ser maliciosos!</string>
|
||||
@@ -39,10 +39,10 @@
|
||||
<string name="manager_download_install">Primi equí pa baxalu ya instalalu</string>
|
||||
<string name="direct_install">Instalación direuta (aconséyase)</string>
|
||||
<string name="install_inactive_slot">Instalar na ralura inactiva (darréu del OTA)</string>
|
||||
<string name="install_inactive_slot_msg">¡El preséu va arrincar OBLIGATORIAMENTE na ralura inactiva darréu de reaniciar!\nUsa esta opción namás dempués d\'acabar l\'anovamientu per OTA.\n¿Quies siguir?</string>
|
||||
<string name="install_inactive_slot_msg">¡El preséu va arrincar OBLIGATORIAMENTE na ralura inactiva dempués de reaniciar!\nUsa esta opción namás dempués d\'acabar l\'anovamientu per OTA.\n¿Quies siguir?</string>
|
||||
<string name="setup_title">Configuración adicional</string>
|
||||
<string name="select_patch_file">Seleicionar y parchiar un ficheru</string>
|
||||
<string name="patch_file_msg">Seleiciona una imaxe en bruto (*.img) o un archivu d\'ODIN (*.tar)</string>
|
||||
<string name="patch_file_msg">Seleiciona una imaxe en bruto (*.img), un archivu d\'ODIN (*.tar) o un ficheru payload.bin (*.bin)</string>
|
||||
<string name="reboot_delay_toast">Reaniciando en 5 segundos…</string>
|
||||
<string name="flash_screen_title">Instalación</string>
|
||||
<!--Superuser-->
|
||||
@@ -81,6 +81,9 @@
|
||||
<string name="logs_cleared">El rexistru borróse correutamente</string>
|
||||
<string name="pid">PID: %1$d</string>
|
||||
<string name="target_uid">UID de destín: %1$d</string>
|
||||
<string name="target_pid">Mount ns target PID: %s</string>
|
||||
<string name="selinux_context">Contestu de SELinux: %s</string>
|
||||
<string name="supp_group">Grupu suplementariu: %s</string>
|
||||
<!--SafetyNet-->
|
||||
<!--MagiskHide-->
|
||||
<string name="show_system_app">Aplicaciones del sistema</string>
|
||||
@@ -94,15 +97,19 @@
|
||||
<string name="reboot_bootloader">Reaniciar al cargador d\'arrinque</string>
|
||||
<string name="reboot_download">Reaniciar al mou de descarga</string>
|
||||
<string name="reboot_edl">Reaniciar al mou EDL</string>
|
||||
<string name="reboot_safe_mode">Mou seguru</string>
|
||||
<string name="module_version_author">%1$s por %2$s</string>
|
||||
<string name="module_state_remove">Quitar</string>
|
||||
<string name="module_action">Aición</string>
|
||||
<string name="module_state_restore">Restaurar</string>
|
||||
<string name="module_action_install_external">Instalar dende l\'almacenamientu</string>
|
||||
<string name="update_available">Hai un anovamientu disponible</string>
|
||||
<string name="suspend_text_riru">Suspendióse\'l módulu porque s\'activó «%1$s»</string>
|
||||
<string name="suspend_text_zygisk">Suspendióse\'l módulu porque nun s\'activó «%1$s»</string>
|
||||
<string name="zygisk_module_unloaded">El módulu de Zygisk nun cargó pola mor d\'haber incompatibilidaes</string>
|
||||
<string name="zygisk_module_unloaded">El módulu de Zygisk nun cargó por haber incompatibilidaes</string>
|
||||
<string name="module_empty">Nun hai nengún módulu instaláu</string>
|
||||
<string name="confirm_install">¿Quies instalar el módulu «%1$s»?</string>
|
||||
<string name="confirm_install_title">Confirmación de la instalación</string>
|
||||
<!--Settings-->
|
||||
<string name="settings_dark_mode_title">Mou del estilu</string>
|
||||
<string name="settings_dark_mode_message">¡Seleiciona\'l mou que meyor s\'adaute al to estilu!</string>
|
||||
@@ -111,7 +118,7 @@
|
||||
<string name="settings_dark_mode_dark">Escuridá</string>
|
||||
<string name="settings_download_path_title">Camín de les descargues</string>
|
||||
<string name="settings_download_path_message">Los ficheros van guardase en «%1$s»</string>
|
||||
<string name="settings_hide_app_title">Anubrir Magisk</string>
|
||||
<string name="settings_hide_app_title">Esconder Magisk</string>
|
||||
<string name="settings_hide_app_summary">Instala una aplicación intermedia con una ID y una etiqueta al debalu</string>
|
||||
<string name="settings_restore_app_title">Restaurar el mou visible</string>
|
||||
<string name="settings_restore_app_summary">Fai que l\'aplicación orixinal vuelva ser visible</string>
|
||||
@@ -149,14 +156,19 @@
|
||||
<string name="auto_response">Rempuesta automática</string>
|
||||
<string name="request_timeout">Tiempu d\'espera de les solicitúes</string>
|
||||
<string name="superuser_notification">Avisu de superusuariu</string>
|
||||
<string name="settings_su_reauth_title">Volver autenticar darréu d\'anovar</string>
|
||||
<string name="settings_su_reauth_title">Volver autenticar dempués d\'anovar</string>
|
||||
<string name="settings_su_reauth_summary">Vuelve pidir los permisos de superusuariu dempués d\'anovar les aplicaciones</string>
|
||||
<string name="settings_su_tapjack_title">Proteición escontra\'l tapjacking</string>
|
||||
<string name="settings_su_tapjack_summary">El diálogu de concesión de permisos de superusuariu nun respuende a la entrada mentanto lu torgue otra ventana o superposición</string>
|
||||
<string name="settings_su_auth_title">Autenticación d\'usuariu</string>
|
||||
<string name="settings_su_auth_summary">Pide l\'autenticación demientres les solicitúes de superusuariu</string>
|
||||
<string name="settings_su_auth_insecure">Nun se configuró nengún métodu d\'autenticación nel preséu</string>
|
||||
<string name="settings_customization">Personalización</string>
|
||||
<string name="setting_add_shortcut_summary">Amiesta un atayu a la pantalla d\'aniciu en casu de que\'l nome y l\'iconu seyan difíciles de reconocer darréu d\'anubrir l\'aplicación</string>
|
||||
<string name="setting_add_shortcut_summary">Amiesta un atayu a la pantalla d\'aniciu en casu de que\'l nome y l\'iconu seyan difíciles de reconocer dempués d\'esconder l\'aplicación</string>
|
||||
<string name="settings_doh_title">DNS per HTTPS</string>
|
||||
<string name="settings_doh_description">Una igua alternativa pal envelenamientu de DNS en dalgunos países</string>
|
||||
<string name="settings_random_name_title">Nome de la salida aleatoriu</string>
|
||||
<string name="settings_random_name_description">Fai que\'l nome de ficheru de la salida de les imáxenes parchiaes y los ficheros tar seya aleatoriu pa impidir la deteición</string>
|
||||
<string name="multiuser_mode">Mou de multiusuariu</string>
|
||||
<string name="settings_owner_only">Namás el propietariu del preséu</string>
|
||||
<string name="settings_owner_manage">El propietariu xestionáu del preséu</string>
|
||||
@@ -186,11 +198,14 @@
|
||||
<string name="repo_install_title">Instalación de: %1$s %2$s (%3$d)</string>
|
||||
<string name="download">Baxar</string>
|
||||
<string name="reboot">Reaniciar</string>
|
||||
<string name="close">Zarrar</string>
|
||||
<string name="release_notes">Notes de la versión</string>
|
||||
<string name="flashing">Flaxando…</string>
|
||||
<string name="running">Executando…</string>
|
||||
<string name="done">¡Fecho!</string>
|
||||
<string name="done_action">Completóse l\'aición de: %1$s</string>
|
||||
<string name="failure">¡Falló!</string>
|
||||
<string name="hide_app_title">Anubriendo l\'aplicación Magisk…</string>
|
||||
<string name="hide_app_title">Escondiendo l\'aplicación Magisk…</string>
|
||||
<string name="open_link_failed_toast">Nun s\'atopó nenguna aplicación p\'abrir l\'enllaz</string>
|
||||
<string name="complete_uninstall">Desinstalar dafechu</string>
|
||||
<string name="restore_img">Restaurar les imáxenes</string>
|
||||
@@ -200,6 +215,7 @@
|
||||
<string name="setup_fail">La configuración falló</string>
|
||||
<string name="env_fix_title">Configuración adicional</string>
|
||||
<string name="env_fix_msg">El preséu precisa una configuración adicional pa que Magisk funcione afayadizamente. ¿Quies siguir y reaniciar?</string>
|
||||
<string name="env_full_fix_msg">El preséu precisa volver flaxar Magisk pa que funcione afayadizamente. Volvi instalar Magisk dientro de l\'aplicación porque\'l mou de recuperación nun pue consiguir la información correuta del preséu.</string>
|
||||
<string name="setup_msg">Executando la configuración del entornu…</string>
|
||||
<string name="unsupport_magisk_title">Versión non compatible</string>
|
||||
<string name="unsupport_magisk_msg">Esta versión de l\'aplicación nun ye compatible coles versiones de Magisk anteriores a la %1$s.\n\nL\'aplicación va comportase como si Magisk nun tuviere instaláu, anueva Magisk namás que puedas.</string>
|
||||
@@ -207,12 +223,13 @@
|
||||
<string name="unsupport_system_app_msg">Esta aplicación nun se pue executar nel espaciu del sistema. Volvi instalala mas nel espaciu del usuariu.</string>
|
||||
<string name="unsupport_other_su_msg">Detectóse un binariu «su» que nun ye de Magisk. Quita cualesquier solución de root y/o volvi instalar Magisk.</string>
|
||||
<string name="unsupport_external_storage_msg">Magisk ta instaláu nel almacenamientu esternu. Movi l\'aplicación al almacenamientu internu, por favor.</string>
|
||||
<string name="unsupport_nonroot_stub_msg">Magisk nun pue siguir funcionando nel mou anubríu darréu que se perdió\'l root. Restaura\'l mou visible de l\'aplicación.</string>
|
||||
<string name="unsupport_nonroot_stub_msg">Magisk nun pue siguir funcionando nel mou escondíu darréu que se perdió\'l root. Restaura\'l mou visible de l\'aplicación.</string>
|
||||
<string name="unsupport_nonroot_stub_title">@string/settings_restore_app_title</string>
|
||||
<string name="external_rw_permission_denied">Concede\'l permisu d\'almacenamientu p\'activar esta funcionalidá</string>
|
||||
<string name="post_notifications_denied">Concede\'l permisu de los avisos p\'activar esta función</string>
|
||||
<string name="install_unknown_denied">Permite la instalación d\'aplicaciones desconocíes p\'activar esta funcionalidá</string>
|
||||
<string name="add_shortcut_title">Amestar un atayu a la pantalla d\'aniciu</string>
|
||||
<string name="add_shortcut_msg">Darréu d\'anubrir esta aplicación, el so nome ya iconu van ser difíciles de reconocer. ¿Quies amestar un atayu a la pantalla d\'aniciu?</string>
|
||||
<string name="add_shortcut_msg">Dempués d\'esconder esta aplicación, el so nome ya iconu van ser difíciles de reconocer. ¿Quies amestar un atayu a la pantalla d\'aniciu?</string>
|
||||
<string name="app_not_found">Nun s\'atopó nenguna aplicación pa remanar esta aición</string>
|
||||
<string name="reboot_apply_change">Reanicia p\'aplicar los cambeos</string>
|
||||
<string name="restore_app_confirmation">Esta aición va restaurar l\'aplicación orixinal y desanicia la intermedia. ¿De xuru que quies facelo?</string>
|
||||
|
||||
@@ -10,10 +10,9 @@
|
||||
<string name="section_theme">עיצוב</string>
|
||||
<string name="denylist">רשימת דחייה</string>
|
||||
|
||||
|
||||
<!--Home-->
|
||||
<string name="no_connection">אין חיבור זמין</string>
|
||||
<string name="app_changelog">רשימת שינויים</string>
|
||||
<string name="app_changelog">יומן שינויים</string>
|
||||
<string name="loading">טוען…</string>
|
||||
<string name="update">עדכון</string>
|
||||
<string name="not_available">ל/ז</string>
|
||||
@@ -45,16 +44,16 @@
|
||||
<string name="install_inactive_slot_msg">ההתקן שלך ייאלץ אתחול לחריץ הלא פעיל הנוכחי שלך לאחר הפעלה מחדש!\nיש להשתמש באפשרות זו רק לאחר ביצוע OTA בלבד.\nלהמשיך?</string>
|
||||
<string name="setup_title">התקנה נוספת</string>
|
||||
<string name="select_patch_file">בחירה והתקנת קובץ</string>
|
||||
<string name="patch_file_msg">בחירת תמונה גולמית (*.img) או ODIN קובץ tar (*.tar)</string>
|
||||
<string name="patch_file_msg">בחירת תמונה גולמית (*.img) או ODIN tarfile (*.tar) או payload.bin (*.bin)</string>
|
||||
<string name="reboot_delay_toast">מאתחל בעוד 5 שניות…</string>
|
||||
<string name="flash_screen_title">התקנה</string>
|
||||
|
||||
<!--Superuser-->
|
||||
<string name="su_request_title">בקשות משתמש על</string>
|
||||
<string name="touch_filtered_warning">מכיוון שיישום מסתיר בקשה של משתמש על, Magisk לא יכול לאמת את תגובתך</string>
|
||||
<string name="deny">דחה</string>
|
||||
<string name="deny">דחייה</string>
|
||||
<string name="prompt">מיידי</string>
|
||||
<string name="grant">הענק</string>
|
||||
<string name="grant">הענקה</string>
|
||||
<string name="su_warning">מעניק גישה מלאה להתקן שלך.\nיש לדחות באי וודאות!</string>
|
||||
<string name="forever">לצמיתות</string>
|
||||
<string name="once">פעם אחת</string>
|
||||
@@ -62,24 +61,24 @@
|
||||
<string name="twentymin">20 דקות</string>
|
||||
<string name="thirtymin">חצי שעה</string>
|
||||
<string name="sixtymin">שעה</string>
|
||||
<string name="su_allow_toast">%1$s הוענקו הרשאות משתמש עבור</string>
|
||||
<string name="su_deny_toast">%1$s נשללו הרשאות משתמש עבור</string>
|
||||
<string name="su_allow_toast">%1$s קיבל הרשאות משתמש על</string>
|
||||
<string name="su_deny_toast">%1$s נשללו הרשאות משתמש על</string>
|
||||
<string name="su_snack_grant">הרשאות משתמש על עבור %1$s הוענקו</string>
|
||||
<string name="su_snack_deny">הרשאות משתמש על עבור %1$s נשללו</string>
|
||||
<string name="su_snack_notif_on">התראות עבור %1$s פועלות</string>
|
||||
<string name="su_snack_notif_off">התראות עבור %1$s כבויות</string>
|
||||
<string name="su_snack_notif_on">התראות של %1$s מופעלות</string>
|
||||
<string name="su_snack_notif_off">התראות של %1$s מושבתות</string>
|
||||
<string name="su_snack_log_on">יומני רישום עבור %1$s פועלות</string>
|
||||
<string name="su_snack_log_off">יומני רישום עבור %1$s כבויות</string>
|
||||
<string name="su_snack_log_off">יומני רישום עבור %1$s מושבתות</string>
|
||||
<string name="su_revoke_title">להסיר?</string>
|
||||
<string name="su_revoke_msg">נא לאשר שלילת הרשאות עבור %1$s?</string>
|
||||
<string name="toast">הרמת כוסית</string>
|
||||
<string name="none">ללא</string>
|
||||
<string name="superuser_toggle_notification">התראות</string>
|
||||
<string name="superuser_toggle_revoke">הסרה</string>
|
||||
<string name="superuser_policy_none">לא נתבקשו הרשאות משתמש על על ידי שום יישום</string>
|
||||
<string name="superuser_policy_none">טרם נתבקשו הרשאות משתמש על על ידי יישומים</string>
|
||||
|
||||
<!--Logs-->
|
||||
<string name="log_data_none">הינך ללא יומן רישום, יש לנסות להשתמש ביישומים מותאמים יותר למשתמש העל שלך</string>
|
||||
<string name="log_data_none">הינך ללא יומן, יש לנסות להשתמש יותר ביישומי השורש שלך</string>
|
||||
<string name="log_data_magisk_none">יומני רישום Magisk ריקים, זה מוזר</string>
|
||||
<string name="menuSaveLog">שמירת יומן רישום</string>
|
||||
<string name="menuClearLog">ניקוי יומן רישום כעת</string>
|
||||
@@ -92,7 +91,7 @@
|
||||
|
||||
<!--SafetyNet-->
|
||||
|
||||
<!-- MagiskHide -->
|
||||
<!--MagiskHide-->
|
||||
<string name="show_system_app">הצגת יישומי מערכת</string>
|
||||
<string name="show_os_app">הצגת יישומי מערכת הפעלה</string>
|
||||
<string name="hide_filter_hint">סינון לפי שם</string>
|
||||
@@ -100,14 +99,16 @@
|
||||
|
||||
<!--Module-->
|
||||
<string name="no_info_provided">(לא סופק מידע)</string>
|
||||
<string name="reboot_userspace">אתחול מהיר</string>
|
||||
<string name="reboot_userspace">אתחול רך</string>
|
||||
<string name="reboot_recovery">אתחול למצב שחזור</string>
|
||||
<string name="reboot_bootloader">אתחול מצב מנהל האתחול</string>
|
||||
<string name="reboot_download">אתחול מצב הורדה</string>
|
||||
<string name="reboot_bootloader">אתחול לטוען האתחול</string>
|
||||
<string name="reboot_download">אתחול למצב הורדה</string>
|
||||
<string name="reboot_edl">אתחול למצב EDL</string>
|
||||
<string name="reboot_safe_mode">מצב בטוח</string>
|
||||
<string name="module_version_author">%1$s מאת %2$s</string>
|
||||
<string name="module_state_remove">הסרה</string>
|
||||
<string name="module_state_restore">שיחזור</string>
|
||||
<string name="module_action">פעולה</string>
|
||||
<string name="module_state_restore">שחזור</string>
|
||||
<string name="module_action_install_external">התקנה מהאחסון</string>
|
||||
<string name="update_available">עדכונים זמינים</string>
|
||||
<string name="suspend_text_riru">מודול מושעה כי %1$s מופעל</string>
|
||||
@@ -117,7 +118,7 @@
|
||||
<string name="confirm_install">להתקין מודול %1$s?</string>
|
||||
<string name="confirm_install_title">אישור התקנה</string>
|
||||
|
||||
<!--Settings -->
|
||||
<!--Settings-->
|
||||
<string name="settings_dark_mode_title">מצב עיצוב</string>
|
||||
<string name="settings_dark_mode_message">נא לבחור מצב המתאים ביותר לסגנון שלך!</string>
|
||||
<string name="settings_dark_mode_light">תמיד בהיר</string>
|
||||
@@ -128,11 +129,11 @@
|
||||
<string name="settings_hide_app_title">הסתרת היישום Magisk</string>
|
||||
<string name="settings_hide_app_summary">התקנת יישום מתווך עם מזהה חבילה אקראי ותווית שם מותאמת אישית</string>
|
||||
<string name="settings_restore_app_title">שיחזור היישום Magisk</string>
|
||||
<string name="settings_restore_app_summary">יש לבטל את הסתרת היישום ולשחזור אותו ל-APK המקורי</string>
|
||||
<string name="settings_restore_app_summary">ביטול הסתרת היישום ושחזור אל ה-APK המקורי</string>
|
||||
<string name="language">שפה</string>
|
||||
<string name="system_default">(ברירת מחדל מערכת)</string>
|
||||
<string name="settings_check_update_title">בדיקת עדכונים</string>
|
||||
<string name="settings_check_update_summary">בדוק מעת לעת ברקע אם יש עדכונים</string>
|
||||
<string name="settings_check_update_summary">בדיקה מעת לעת ברקע אם יש עדכונים</string>
|
||||
<string name="settings_update_channel_title">ערוץ עדכון</string>
|
||||
<string name="settings_update_stable">יציב</string>
|
||||
<string name="settings_update_beta">בטא</string>
|
||||
@@ -161,12 +162,12 @@
|
||||
<string name="settings_su_request_60">60 שניות</string>
|
||||
<string name="superuser_access">גישת משתמש על</string>
|
||||
<string name="auto_response">תגובה אוטומטית</string>
|
||||
<string name="request_timeout">בקש פסק זמן</string>
|
||||
<string name="request_timeout">בקשת פסק זמן</string>
|
||||
<string name="superuser_notification">התראות משתמש על</string>
|
||||
<string name="settings_su_reauth_title">אימות מחדש לאחר שדרוג</string>
|
||||
<string name="settings_su_reauth_summary">אימות מחדש הרשאות של משתמש על לאחר שדרוג יישום</string>
|
||||
<string name="settings_su_tapjack_title">הפעלת הגנת Tapjacking</string>
|
||||
<string name="settings_su_tapjack_summary">תיבת הדו שיח של משתמש העל לא תגיב לקלט כשהיא מוסתרת על ידי חלון או כיסוי אחר</string>
|
||||
<string name="settings_su_tapjack_title">הגנת Tapjacking</string>
|
||||
<string name="settings_su_tapjack_summary">תיבת הדו שיח של משתמש העל לא תגיב לקלט כשהיא מוסתרת על ידי חלון או שכבת על אחרת</string>
|
||||
<string name="settings_su_auth_title">אימות משתמש</string>
|
||||
<string name="settings_su_auth_summary">בקשת אימות משתמש במהלך בקשות משתמש על</string>
|
||||
<string name="settings_su_auth_insecure">לא מוגדרת שיטת אימות בהתקן</string>
|
||||
@@ -174,6 +175,8 @@
|
||||
<string name="setting_add_shortcut_summary">הוספת קיצור דרך יפה במסך הבית למקרה שקשה לזהות את השם ואת הסמל לאחר הסתרת היישום</string>
|
||||
<string name="settings_doh_title">DNS על HTTPS</string>
|
||||
<string name="settings_doh_description">עקיפת DNS מורעל במדינות מסוימות</string>
|
||||
<string name="settings_random_name_title">שם פלט אקראי</string>
|
||||
<string name="settings_random_name_description">שם אקראי לקובץ הפלט של תמונות מתוקנות וקבצי tar כדי למנוע זיהוי</string>
|
||||
<string name="multiuser_mode">מצב מרובה משתמשים</string>
|
||||
<string name="settings_owner_only">בעל ההתקן בלבד</string>
|
||||
<string name="settings_owner_manage">אחראי ניהול ההתקן</string>
|
||||
@@ -205,10 +208,13 @@
|
||||
<string name="repo_install_title">מתקין %1$s %2$s(%3$d)</string>
|
||||
<string name="download">הורדה</string>
|
||||
<string name="reboot">הפעלה מחדש</string>
|
||||
<string name="close">סגירה</string>
|
||||
<string name="release_notes">הערות שחרור</string>
|
||||
<string name="flashing">צורב…</string>
|
||||
<string name="running">רץ…</string>
|
||||
<string name="done">בוצע!</string>
|
||||
<string name="failure">נכשל</string>
|
||||
<string name="done_action">בוצעה ריצת פעולה של %1$s</string>
|
||||
<string name="failure">נכשל!</string>
|
||||
<string name="hide_app_title">מסתיר את יישום Magisk…</string>
|
||||
<string name="open_link_failed_toast">לא נמצאו יישומים לפתיחת קישור זה</string>
|
||||
<string name="complete_uninstall">הסרה מלאה</string>
|
||||
@@ -237,4 +243,5 @@
|
||||
<string name="app_not_found">לא נמצא יישום לטיפול בפעולה זו</string>
|
||||
<string name="reboot_apply_change">ייש להפעיל מחדש כדי להחיל שינויים</string>
|
||||
<string name="restore_app_confirmation">פעולה זו תשחזר את היישום המוסתר חזרה ליישום המקורי. האם בוודאות ברצונך לעשות את זה?</string>
|
||||
|
||||
</resources>
|
||||
|
||||
252
app/core/src/main/res/values-ku/strings.xml
Normal file
252
app/core/src/main/res/values-ku/strings.xml
Normal file
@@ -0,0 +1,252 @@
|
||||
<resources>
|
||||
|
||||
<!--Sections-->
|
||||
<string name="modules">زیادکراوەکان</string>
|
||||
<string name="superuser">سوپەر یوسەر</string>
|
||||
<string name="logs">تۆمارەکان</string>
|
||||
<string name="settings">ڕێکخستنەکان</string>
|
||||
<string name="install">دامەزراندن</string>
|
||||
<string name="section_home">ماڵەوە</string>
|
||||
<string name="section_theme">ڕووکارەکان</string>
|
||||
<string name="denylist">پێڕستی ڕێگەپێنەدراوەکان</string>
|
||||
|
||||
<!--Home-->
|
||||
<string name="no_connection">هێڵ بەردەست نییە</string>
|
||||
<string name="app_changelog">گۆڕانکارییەکان</string>
|
||||
<string name="loading">کردنەوە…</string>
|
||||
<string name="update">بەرزکردنەوە</string>
|
||||
<string name="not_available">نییە</string>
|
||||
<string name="hide">شاردنەوە</string>
|
||||
<string name="home_package">پاکێج</string>
|
||||
<string name="home_app_title">ئەپ</string>
|
||||
|
||||
<string name="home_notice_content">تەنها لە گیتهەبی فەرمی ماجیسک دابگرە، لە شوێنی تر لەوانەیە زیانبەخش بێت</string>
|
||||
<string name="home_support_title">پشتگیریمان بکە</string>
|
||||
<string name="home_follow_title">شوێنمان بکەوە</string>
|
||||
<string name="home_item_source">سەرچاوە</string>
|
||||
<string name="home_support_content">ماجیسک بە خۆڕاییە و هەر واش ئەمێنێتەوە، بەهەرحاڵ ئەتوانیت پشتگیرییەکمان بکەی بۆ گرنگی پێدان</string>
|
||||
<string name="home_installed_version">داگیراوە</string>
|
||||
<string name="home_latest_version">دوایین وەشان</string>
|
||||
<string name="invalid_update_channel">کەناڵێکی نوێکردنەوەی هەڵە</string>
|
||||
<string name="uninstall_magisk_title">سڕینەوەی ماجیسک</string>
|
||||
<string name="uninstall_magisk_msg">هەموو زیادکراوەکان دەسڕێنەوە، ڕۆت دەسڕێتەوە، و هەر ڕەمزێنراوێک بەهۆی ماجیسک کرابێ لادەچێت!</string>
|
||||
|
||||
<!--Install-->
|
||||
<string name="keep_force_encryption">ڕەمزاندنی بەزۆر بهێڵەوە</string>
|
||||
<string name="keep_dm_verity">بهێڵەوە AVB 2.0/dm-verity</string>
|
||||
<string name="recovery_mode">دۆخی ڕیکەڤەڕی</string>
|
||||
<string name="install_options_title">هەڵبژاردنەکان</string>
|
||||
<string name="install_method_title">ڕێگای</string>
|
||||
<string name="install_next">دواتر</string>
|
||||
<string name="install_start">با بیکەین</string>
|
||||
<string name="manager_download_install">بۆ داگرتن و ڕێکخستن کرتە بکە</string>
|
||||
<string name="direct_install">داگرتنی ڕاستەوخۆ(پێشنیارکراوە)</string>
|
||||
<string name="install_inactive_slot">دایبگرە بۆ خانەی ناچالاک(پاش OTA)</string>
|
||||
<string name="install_inactive_slot_msg">ئێستا ئامێرەکەت دەچێتە خانە ناچالاکەکە، ئەمە بەکاربهێنە تەنها دوای ئەپدەیت کردن لە ڕێگەی OTA، بەردەوام دەبیت؟</string>
|
||||
<string name="setup_title">ڕێکخستنی زیاتر</string>
|
||||
<string name="select_patch_file">فایلێک هەڵبژێرە و پینەی بکە</string>
|
||||
<string name="patch_file_msg">تکایە فایلێکی Tar یان img یان payload.bin هەڵبژێرە</string>
|
||||
<string name="reboot_delay_toast">ڕێستارت کردنەوە لە ماوەی ٥ چرکە…</string>
|
||||
<string name="flash_screen_title">ڕێکخستن</string>
|
||||
|
||||
<!--Superuser-->
|
||||
<string name="su_request_title">داواکاری سوپەریوسەر</string>
|
||||
<string name="touch_filtered_warning">ئەپێک لە سەر شاشەکەیە، ناتوانین دڵنیا بینەوە</string>
|
||||
<string name="deny">ڕەتکردنەوە</string>
|
||||
<string name="prompt">داواکاری</string>
|
||||
<string name="grant">ڕێگەپێدان</string>
|
||||
<string name="su_warning">ڕێگەپێدان بۆ تەواوی ئامێرەکەت، گەر دڵنیا نیت ڕەتی بکەوە</string>
|
||||
<string name="forever">بۆ هەمیشە</string>
|
||||
<string name="once">بۆ یەکجار</string>
|
||||
<string name="tenmin">بۆ ١٠ خولەک</string>
|
||||
<string name="twentymin">بۆ ٢٠ خولەک</string>
|
||||
<string name="thirtymin">بۆ ٣٠ خولەک</string>
|
||||
<string name="sixtymin">بۆ ٦٠ خولەک</string>
|
||||
<string name="su_allow_toast">%1$s ڕێگەپێدانی سوپەریوسەری بۆ زیادکرا</string>
|
||||
<string name="su_deny_toast">%1$s ڕێگەپێدانی سوپەریوسەر ڕەتکرایەوە</string>
|
||||
<string name="su_snack_grant">ڕێگەپێدانی سوپەریوسەری %1$s بۆ درا</string>
|
||||
<string name="su_snack_deny">ڕێگەپێدانی سوپەریوسەر %1$s ڕەتکرایەوە</string>
|
||||
<string name="su_snack_notif_on">ئاگەدارکردنەوەکانی %1$s کارا کراوە</string>
|
||||
<string name="su_snack_notif_off">ئاگەدارکردنەوەکانی %1$s کوژاوەتەوە</string>
|
||||
<string name="su_snack_log_on">تۆمارەکانی %1$s کراوەتەوە</string>
|
||||
<string name="su_snack_log_off">تۆمارەکانی %1$s کوژاوەتەوە</string>
|
||||
<string name="su_revoke_title">لابردن؟</string>
|
||||
<string name="su_revoke_msg">دڵنیابەوە بۆ لابردنی سوپەریوسەر بۆ %1$s </string>
|
||||
<string name="toast">هێنانەسەر</string>
|
||||
<string name="none">هیچ</string>
|
||||
|
||||
<string name="superuser_toggle_notification">ئاگادارییەکان</string>
|
||||
<string name="superuser_toggle_revoke">لابردن</string>
|
||||
<string name="superuser_policy_none">هیچ ئەپێک تا ئێستا داوای سوپەریوسەری نەکردووە</string>
|
||||
|
||||
<!--Logs-->
|
||||
<string name="log_data_none">هیچ تۆمارێک نییە، ئەو ئەپانەی ڕۆتیان پێویستە زوزو بەکاریبێنە</string>
|
||||
<string name="log_data_magisk_none">تۆمارەکانی ماجیسک بەتاڵن، باشە بۆ؟</string>
|
||||
<string name="menuSaveLog">تۆمارەکان هەڵبگرە</string>
|
||||
<string name="menuClearLog">تۆمارەکان بسڕەوە</string>
|
||||
<string name="logs_cleared">بەسەرکەوتویی تۆمارەکان سڕانەوە</string>
|
||||
<string name="pid">PID: %1$d</string>
|
||||
<string name="target_uid">ئامانج UID: %1$d</string>
|
||||
<string name="target_pid">Mount ns target PID: %s</string>
|
||||
<string name="selinux_context">SELinux context: %s</string>
|
||||
<string name="supp_group">Supplementary group: %s</string>
|
||||
|
||||
<!--SafetyNet-->
|
||||
|
||||
<!--MagiskHide-->
|
||||
<string name="show_system_app">پیشاندانی ئەپەکانی سیستەم</string>
|
||||
<string name="show_os_app">پیشاندانی ئەپەکان</string>
|
||||
<string name="hide_filter_hint">پاڵاوتنی بەپێی ناو</string>
|
||||
<string name="hide_search">گەڕان</string>
|
||||
|
||||
<!--Module-->
|
||||
<string name="no_info_provided">(هیچ زانیارییەک نییە)</string>
|
||||
<string name="reboot_userspace">ڕێستارت کردنەوە</string>
|
||||
<string name="reboot_recovery">چوونە ناو ڕیکەڤەری</string>
|
||||
<string name="reboot_bootloader">چوونە ناو بووتلۆدەر</string>
|
||||
<string name="reboot_download">چوونە ناو داونلۆد</string>
|
||||
<string name="reboot_edl">چوونە ناو EDL</string>
|
||||
<string name="reboot_safe_mode">دۆخی پارێزراو</string>
|
||||
<string name="module_version_author">%1$s by %2$s</string>
|
||||
<string name="module_state_remove">سڕینەوە</string>
|
||||
<string name="module_action">کارا</string>
|
||||
<string name="module_state_restore">گەڕاندنەوە</string>
|
||||
<string name="module_action_install_external">لە بیرگەکەتەوە ڕێکی بخە</string>
|
||||
<string name="update_available">وەشانی نوێ بەردەستە</string>
|
||||
<string name="suspend_text_riru">زیادکراوەکە کار ناکات چونکە %1$s کراوەتەوە</string>
|
||||
<string name="suspend_text_zygisk">زیادکراوەکە کارناکات چونکە %1$s نەکراوەتەوە</string>
|
||||
<string name="zygisk_module_unloaded">زیادکراوی Zygisk بەهۆی نەگونجان کارناکات</string>
|
||||
<string name="module_empty">هیچ زیادکراوێک دانەبەزیوە</string>
|
||||
<string name="confirm_install">دابەزاندنی زیادکراو %1$s?</string>
|
||||
<string name="confirm_install_title">دڵنیابوونەوە لە دابەزاندن</string>
|
||||
|
||||
<!--Settings-->
|
||||
<string name="settings_dark_mode_title">جۆری ڕووکار</string>
|
||||
<string name="settings_dark_mode_message">حەزت لە کامەی بوو ئەوە هەڵبژێرە</string>
|
||||
<string name="settings_dark_mode_light">هەمیشە دۆخی ڕوناک</string>
|
||||
<string name="settings_dark_mode_system">با بەگوێرەی سیستەمەکە بێت!</string>
|
||||
<string name="settings_dark_mode_dark">هەمیشە دۆخی تاریک</string>
|
||||
<string name="settings_download_path_title">شوێنی داگرتنەکە</string>
|
||||
<string name="settings_download_path_message">فایلەکان هەڵدەگیرێن لە %1$s</string>
|
||||
<string name="settings_hide_app_title">شاردنەوەی ئەپی ماجیسک</string>
|
||||
<string name="settings_hide_app_summary">داگرتنی ماجیسک بە ناوی جیاوە</string>
|
||||
<string name="settings_restore_app_title">ئەپە ڕەسەنەکە بهێنەوە</string>
|
||||
<string name="settings_restore_app_summary">ئەپەکە دەربخەوە و ڕەسەنڵ</string>
|
||||
<string name="language">زمان</string>
|
||||
<string name="system_default">(وەک هی ئامێرەکە)</string>
|
||||
<string name="settings_check_update_title">گەڕان بەدوای نوێکاری</string>
|
||||
<string name="settings_check_update_summary">گەڕان بەدوای نوێکاری خۆکارانە</string>
|
||||
<string name="settings_update_channel_title">کەناڵی نوێکاری</string>
|
||||
<string name="settings_update_stable">جێگیر</string>
|
||||
<string name="settings_update_beta">پێشوەختە(بێتا)</string>
|
||||
<string name="settings_update_custom">تایبەت</string>
|
||||
<string name="settings_update_custom_msg">بەستەرێکی تایبەت دابنێ</string>
|
||||
<string name="settings_zygisk_summary">کارپێکردنی بەشێکی ماجیسک لە zygote daemon</string>
|
||||
<string name="settings_denylist_title">پێڕستی نەرێنی کراوەکان</string>
|
||||
<string name="settings_denylist_summary">هەر ئەپێک لە پێڕستی نەرێنییەکان کاریگەریەکانی ماجیسکی لەسەر نییە</string>
|
||||
<string name="settings_denylist_config_title">دەستکاریکردنی پێڕستی نەرێنیکراوەکان</string>
|
||||
<string name="settings_denylist_config_summary">ئەو ئەپە هەڵبژێرە کە دەتەوێت نەرێنیی بکەیت</string>
|
||||
<string name="settings_hosts_title">هۆستی ناسیستەمی</string>
|
||||
<string name="settings_hosts_summary"> هۆستی ناسیستەمی بۆ لابردنی ڕیکلامەکان</string>
|
||||
<string name="settings_hosts_toast"> هۆستی ناسیستەمی زیادکرا</string>
|
||||
<string name="settings_app_name_hint">ناوی نوێ</string>
|
||||
<string name="settings_app_name_helper">ئەپەکە بەم ناوەوە دروست دەکرێتەوە</string>
|
||||
<string name="settings_app_name_error">هەڵەیە</string>
|
||||
<string name="settings_su_app_adb">ئەپەکان و ADB</string>
|
||||
<string name="settings_su_app">تەنها ئەپەکان</string>
|
||||
<string name="settings_su_adb">ADB تەنها</string>
|
||||
<string name="settings_su_disable">ناچالاک کراوە</string>
|
||||
<string name="settings_su_request_10">10 چرکە</string>
|
||||
<string name="settings_su_request_15">15 چرکە</string>
|
||||
<string name="settings_su_request_20">20 چرکە</string>
|
||||
<string name="settings_su_request_30">30 چرکە</string>
|
||||
<string name="settings_su_request_45">45 چرکە</string>
|
||||
<string name="settings_su_request_60">60 چرکە</string>
|
||||
<string name="superuser_access">دەسەڵاتی سوپەریوسەر</string>
|
||||
<string name="auto_response">وەڵامدانەوەی خۆکارانە</string>
|
||||
<string name="request_timeout">ماوەی وەڵامدانەوە</string>
|
||||
<string name="superuser_notification">ئاگەدارییەکانی سوپەریوسەر</string>
|
||||
<string name="settings_su_reauth_title">پرسیاربکەوە دوای هەر نوێکردنەوەیەک</string>
|
||||
<string name="settings_su_reauth_summary">دوای نوێکردنەوەی ئەپەکان دووبارە پرسیار بکەوە بۆ دەسەڵاتی سوپەریوسەر</string>
|
||||
<string name="settings_su_tapjack_title">پارێزگاری کردن لە ئەگەری دەستلێدانی تر</string>
|
||||
<string name="settings_su_tapjack_summary"> کاتێک ئەپێکی تر بەسەر شاشەکەوەیە، سوپەر یوسەر وەڵام ناداتەوە لە کردنی هەر بژاردەیەک لەبەر پارێزراوی</string>
|
||||
<string name="settings_su_auth_title">دڵنیاکردنەوەی کەسی</string>
|
||||
<string name="settings_su_auth_summary">داواکاری بکە بۆ دڵنیاکردنەوەی کەسی لەکاتی داواکاری سوپەریوسەر</string>
|
||||
<string name="settings_su_auth_insecure">هیچ دڵنیاکردنەوەیەک نییە</string>
|
||||
<string name="settings_customization">دەستکاریکردن</string>
|
||||
<string name="setting_add_shortcut_summary">یەک ئایکۆنی جوان زیادبکە بۆ سەر شاشەکە ئەگەر قورس بوو ئەوەی خۆی بدۆزیتەوە</string>
|
||||
<string name="settings_doh_title">DNS بەسەر HTTPS</string>
|
||||
<string name="settings_doh_description">Workaround DNS خراپە لە هەندێک شوێن</string>
|
||||
<string name="settings_random_name_title">ناوێک لەخۆیەوە</string>
|
||||
<string name="settings_random_name_description">دانانی ناوێک لەخۆوە تاوەکوو ئاشکرا نەبێت</string>
|
||||
|
||||
<string name="multiuser_mode">دۆخی فرەبەکارهێنەر</string>
|
||||
<string name="settings_owner_only">تەنها خاوەنی ئامێر</string>
|
||||
<string name="settings_owner_manage">خاوەنی ئامێر</string>
|
||||
<string name="settings_user_independent">بەکارهێنەری سەربەخۆ</string>
|
||||
<string name="owner_only_summary">تەنها خاوەنەکە دۆخی ڕۆتی هەیە</string>
|
||||
<string name="owner_manage_summary">تەنها خاوەنەکە دەسەڵاتی بەکارهێنانی ڕۆتی هەیە</string>
|
||||
<string name="user_independent_summary">هەر بەکارهێنەرێک یاسای جیاوازی هەیە</string>
|
||||
|
||||
<string name="mount_namespace_mode">چونە دۆخی بۆشایی ناو</string>
|
||||
<string name="settings_ns_global">بۆشاییناوی گشتی</string>
|
||||
<string name="settings_ns_requester">بۆشایی ناوی خۆیی</string>
|
||||
<string name="settings_ns_isolate">بۆشایی ناوی جیا</string>
|
||||
<string name="global_summary">هەمو ڕۆتەکان ناوی گشتی بەکار ئەهێنن</string>
|
||||
<string name="requester_summary">هەمو ڕۆتەکان ناوی خۆیی بەکار ئەهێنن</string>
|
||||
<string name="isolate_summary">هەمو ڕۆتەکان ناوی جیا بەکار ئەهێنن</string>
|
||||
|
||||
<!--Notifications-->
|
||||
<string name="update_channel">نوێکردنەوەکانی ماجیسک</string>
|
||||
<string name="progress_channel">ئاگادارییە کاراکان</string>
|
||||
<string name="updated_channel">نوێکردنەوە سەرکەوتووبوو</string>
|
||||
<string name="download_complete">داگرتن سەرکەوتووبوو</string>
|
||||
<string name="download_file_error">هەڵەیەک رووی دا لەکاتی داگرتنی فایلەکە</string>
|
||||
<string name="magisk_update_title">وەشانی نوێی ماجیسک ئامادەیە!</string>
|
||||
<string name="updated_title">ماجیسک نوێکراوە!</string>
|
||||
<string name="updated_text">کرتە بکە بۆ کردنەوەی ئەپ</string>
|
||||
|
||||
<!--Toasts, Dialogs-->
|
||||
<string name="yes">بەڵێ</string>
|
||||
<string name="no">نەخێر</string>
|
||||
<string name="repo_install_title">داگرتن %1$s %2$s(%3$d)</string>
|
||||
<string name="download">داگرتن</string>
|
||||
<string name="reboot">ڕێستارت</string>
|
||||
<string name="close">داخستن</string>
|
||||
<string name="release_notes">نێبینییەکان</string>
|
||||
<string name="flashing">فلاش کردن</string>
|
||||
<string name="running">کار کردن...</string>
|
||||
<string name="done">تەواو!</string>
|
||||
<string name="done_action">کارکردنی %1$s تەواو بوو</string>
|
||||
<string name="failure">Failed!</string>
|
||||
<string name="hide_app_title">شاردنەوەی ئەپی ماجیسک…</string>
|
||||
<string name="open_link_failed_toast">هیچ ئەپێک تییە تا لینکەکەی پێ بکرێتەوە</string>
|
||||
<string name="complete_uninstall">سڕینەوەی تەواوی</string>
|
||||
<string name="restore_img">هێنانەوەی img</string>
|
||||
<string name="restore_img_msg">هێنانەوە…</string>
|
||||
<string name="restore_done">هاتەوە!</string>
|
||||
<string name="restore_fail">هیچ فایلێکی هەڵگیراوت نیە!</string>
|
||||
<string name="setup_fail">ڕێکخستن شکستی هێنا</string>
|
||||
<string name="env_fix_title">پێویستی بە ڕێکخستنی زیاترە</string>
|
||||
<string name="env_fix_msg">مۆبایلەکەت پێویستی بە ڕێکخستنی زیاترە، ئایا ئەتەوێت بەردەوام بیت و ڕێستارتی بکەیتەوە؟</string>
|
||||
<string name="env_full_fix_msg"> پێویستە دوبارە ماجیسک دابگریتەوە بۆ ئەوەی بەباشی کاربکات تکایە ماجیسک دابگرەوە لەناو ئەپەکە خۆی چونکە لە ڕیکەڤەرییەوە ناتوانرێ زانیاری تەواو لەسەر ئامێرەکە دەستبخرێت </string>
|
||||
<string name="setup_msg">دەستپێکردن....</string>
|
||||
<string name="unsupport_magisk_title">وەشانی ماجیسک پاڵپشتینەکراوە</string>
|
||||
<string name="unsupport_magisk_msg">وەشانی ماجیسکەکەت زۆر کۆنە وەک ئەوە وایە هەر نەبێت، تکایە نوێی بکەوە بە زوترین کات</string>
|
||||
<string name="unsupport_general_title">باری نائاسایی</string>
|
||||
<string name="unsupport_system_app_msg">ئەم ئەپە وەکو ئەپی سیستەم کارناکات، تکایە بیگۆڕەوە بۆ ئەپی ئاسایی</string>
|
||||
<string name="unsupport_other_su_msg"> \"su\" binary یەکی بێگانە دۆزرایەوە، تکایە جگە لە ماجیسک ئەپی تر بەکارمەهێنە بۆ ڕۆت کردن </string>
|
||||
<string name="unsupport_external_storage_msg">ماجیسک لە بیرگەی دەرەکی داگیراوە، تکایە بیبەوە بۆ ناوەکی</string>
|
||||
<string name="unsupport_nonroot_stub_msg">ئەپە شاراوەکە کار ناکات چونکە ڕۆتەکە نەماوە، تکایە ئەپە ڕەسەنەکە بگەڕێنەوە</string>
|
||||
<string name="unsupport_nonroot_stub_title">@string/settings_restore_app_title</string>
|
||||
<string name="external_rw_permission_denied">ڕەزامەندی بیرگە بدە تاوەکوو ئەمە کار بکات</string>
|
||||
<string name="post_notifications_denied">ڕەزامەندی ئاگاداری بکە تاوەکوو ئەمە کار بکات</string>
|
||||
<string name="install_unknown_denied">ڕەزامەندی "install unknown apps" تاوەکوو کار بکات</string>
|
||||
<string name="add_shortcut_title">زیادی بکە بۆ سەر شاشە</string>
|
||||
<string name="add_shortcut_msg">دوای شاردنەوەی ئەم ئەپە، ئەتەوێت یەک ئایکۆنی جوان زیادبکەیت بۆ سەر شاشەکە ئەگەر قورس بوو ئەوەی خۆی بدۆزیتەوە؟</string>
|
||||
<string name="app_not_found">هیچ ئەپێک نەدۆزرایەوە تاوەکوو ئەم کارەی پێ بکرێت</string>
|
||||
<string name="reboot_apply_change">ڕێستارت بکە تاوەکوو کاریگەریەکان کار بکەن</string>
|
||||
<string name="restore_app_confirmation">ئەمە ئەپە ڕەسەنەکە ئەهێنێتەوە، دڵنیایت لە کردنی؟</string>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -105,6 +105,7 @@
|
||||
<string name="reboot_safe_mode">Modo de segurança</string>
|
||||
<string name="module_version_author">%1$s por %2$s</string>
|
||||
<string name="module_state_remove">Remover</string>
|
||||
<string name="module_action">Ação</string>
|
||||
<string name="module_state_restore">Restaurar</string>
|
||||
<string name="module_action_install_external">Instalar a partir do armazenamento</string>
|
||||
<string name="update_available">Atualização disponível</string>
|
||||
@@ -173,7 +174,7 @@
|
||||
<string name="settings_doh_title">DNS sobre HTTPS</string>
|
||||
<string name="settings_doh_description">Solução alternativa para envenenamento de DNS em alguns países</string>
|
||||
<string name="settings_random_name_title">Randomizar nome de saída</string>
|
||||
<string name="settings_random_name_description">Randomize o nome do arquivo de saída de imagens corrigidas e arquivos tar para evitar a detecção</string>
|
||||
<string name="settings_random_name_description">Randomize o nome do arquivo de saída de imagens corrigidas e arquivos tar (*.tar) para evitar a detecção</string>
|
||||
<string name="multiuser_mode">Modo multiusuário</string>
|
||||
<string name="settings_owner_only">Somente proprietário do dispositivo</string>
|
||||
<string name="settings_owner_manage">Gerenciado pelo proprietário do dispositivo</string>
|
||||
@@ -205,9 +206,12 @@
|
||||
<string name="repo_install_title">Instalar %1$s %2$s(%3$d)</string>
|
||||
<string name="download">Baixar</string>
|
||||
<string name="reboot">Reiniciar</string>
|
||||
<string name="close">Fechar</string>
|
||||
<string name="release_notes">Notas da atualização</string>
|
||||
<string name="flashing">Flashando…</string>
|
||||
<string name="running">Executando…</string>
|
||||
<string name="done">Concluído!</string>
|
||||
<string name="done_action">Ação de execução de %1$s concluída</string>
|
||||
<string name="failure">Falhou!</string>
|
||||
<string name="hide_app_title">Ocultando o app do Magisk…</string>
|
||||
<string name="open_link_failed_toast">Nenhum app encontrado para abrir o link</string>
|
||||
|
||||
@@ -105,6 +105,7 @@
|
||||
<string name="reboot_safe_mode">Modo de segurança</string>
|
||||
<string name="module_version_author">%1$s por %2$s</string>
|
||||
<string name="module_state_remove">Remover</string>
|
||||
<string name="module_action">Ação</string>
|
||||
<string name="module_state_restore">Restaurar</string>
|
||||
<string name="module_action_install_external">Instalar a partir do armazenamento</string>
|
||||
<string name="update_available">Atualização disponível</string>
|
||||
@@ -173,7 +174,7 @@
|
||||
<string name="settings_doh_title">DNS sobre HTTPS</string>
|
||||
<string name="settings_doh_description">Solução alternativa para envenenamento de DNS em alguns países</string>
|
||||
<string name="settings_random_name_title">Randomizar nome de saída</string>
|
||||
<string name="settings_random_name_description">Randomize o nome do arquivo de saída de imagens corrigidas e arquivos tar para evitar a detecção</string>
|
||||
<string name="settings_random_name_description">Randomize o nome do arquivo de saída de imagens corrigidas e arquivos tar (*.tar) para evitar a detecção</string>
|
||||
<string name="multiuser_mode">Modo multiusuário</string>
|
||||
<string name="settings_owner_only">Somente proprietário do dispositivo</string>
|
||||
<string name="settings_owner_manage">Gerenciado pelo proprietário do dispositivo</string>
|
||||
@@ -205,9 +206,12 @@
|
||||
<string name="repo_install_title">Instalar %1$s %2$s(%3$d)</string>
|
||||
<string name="download">Baixar</string>
|
||||
<string name="reboot">Reiniciar</string>
|
||||
<string name="close">Fechar</string>
|
||||
<string name="release_notes">Notas da atualização</string>
|
||||
<string name="flashing">Flashando…</string>
|
||||
<string name="running">Executando…</string>
|
||||
<string name="done">Concluído!</string>
|
||||
<string name="done_action">Ação de execução de %1$s concluída</string>
|
||||
<string name="failure">Falhou!</string>
|
||||
<string name="hide_app_title">Ocultando o app do Magisk…</string>
|
||||
<string name="open_link_failed_toast">Nenhum app encontrado para abrir o link</string>
|
||||
|
||||
@@ -87,6 +87,9 @@
|
||||
<string name="logs_cleared">Логи успешно очищены</string>
|
||||
<string name="pid">PID: %1$d</string>
|
||||
<string name="target_uid">Целевой UID: %1$d</string>
|
||||
<string name="target_pid">Целевой PID пространства имён: %s</string>
|
||||
<string name="selinux_context">Контекст SELinux: %s</string>
|
||||
<string name="supp_group">Дополнительная группа: %s</string>
|
||||
|
||||
<!--SafetyNet-->
|
||||
|
||||
@@ -164,11 +167,16 @@
|
||||
<string name="settings_su_reauth_title">Повторная аутентификация</string>
|
||||
<string name="settings_su_reauth_summary">Повторный запрос прав суперпользователя после обновления приложений</string>
|
||||
<string name="settings_su_tapjack_title">Защита от перехвата нажатий</string>
|
||||
<string name="settings_su_auth_title">Аутентификация пользователя</string>
|
||||
<string name="settings_su_auth_summary">Требовать аутентификацию пользователя при запросах Superuser</string>
|
||||
<string name="settings_su_auth_insecure">На устройстве не настроен метод аутентификации</string>
|
||||
<string name="settings_su_tapjack_summary">Окно запроса прав суперпользователя будет неактивно пока активированы наложения экрана</string>
|
||||
<string name="settings_customization">Персонализация</string>
|
||||
<string name="setting_add_shortcut_summary">Добавить ярлык на рабочий стол для удобного восприятия приложения после его скрытия</string>
|
||||
<string name="settings_doh_title">DNS поверх HTTPS</string>
|
||||
<string name="settings_doh_description">Активировать DoH (используйте при проблемах с подключением к сети)</string>
|
||||
<string name="settings_random_name_title">Случайное имя образа</string>
|
||||
<string name="settings_random_name_description">Генерировать случайные имена для патченных образов и tar-файлов для предотвращения обнаружения</string>
|
||||
|
||||
<string name="multiuser_mode">Многопользовательский режим</string>
|
||||
<string name="settings_owner_only">Только администратор</string>
|
||||
|
||||
@@ -74,7 +74,6 @@
|
||||
<string name="su_revoke_msg">Konfirmo për të hequr të drejtat e %1$s?</string>
|
||||
<string name="toast">Dolli</string>
|
||||
<string name="none">Asnjë</string>
|
||||
|
||||
<string name="superuser_toggle_notification">Njoftimet</string>
|
||||
<string name="superuser_toggle_revoke">Të drejtat</string>
|
||||
<string name="superuser_policy_none">Asnjë aplikacion nuk ka kërkuar akoma akses për super-përdoruesin.</string>
|
||||
@@ -91,8 +90,6 @@
|
||||
<string name="selinux_context">Konteksti SELinux: %s</string>
|
||||
<string name="supp_group">Grupi suplementar: %s</string>
|
||||
|
||||
<!--SafetyNet-->
|
||||
|
||||
<!--MagiskHide-->
|
||||
<string name="show_system_app">Shfaq aplikacionet e sistemit</string>
|
||||
<string name="show_os_app">Shfaq aplikacionet e sistemit operativ</string>
|
||||
@@ -109,6 +106,7 @@
|
||||
<string name="reboot_safe_mode">Rinis në safe mode</string>
|
||||
<string name="module_version_author">%1$s nga %2$s</string>
|
||||
<string name="module_state_remove">Hiqe</string>
|
||||
<string name="module_action">Veprim</string>
|
||||
<string name="module_state_restore">Rikëthe</string>
|
||||
<string name="module_action_install_external">Instaloni nga sdcard</string>
|
||||
<string name="update_available">Përditësimi në dispozicion</string>
|
||||
@@ -207,9 +205,12 @@
|
||||
<string name="repo_install_title">Instalo %1$s %2$s(%3$d)</string>
|
||||
<string name="download">Shkarko</string>
|
||||
<string name="reboot">Rinis</string>
|
||||
<string name="close">Mbylle</string>
|
||||
<string name="release_notes">Shënimet e lëshimit</string>
|
||||
<string name="flashing">Duke flashuar…</string>
|
||||
<string name="running">Duke vepruar...</string>
|
||||
<string name="done">U krye!</string>
|
||||
<string name="done_action">Veprimi i ekzekutimit të %1$s u krye</string>
|
||||
<string name="failure">Dështoi!</string>
|
||||
<string name="hide_app_title">Fshehja e aplikacionit Magisk…</string>
|
||||
<string name="open_link_failed_toast">Nuk u gjet asnjë aplikacion për të hapur lidhjen</string>
|
||||
|
||||
@@ -12,21 +12,21 @@
|
||||
|
||||
<!--Home-->
|
||||
<string name="no_connection">Bağlantı yok</string>
|
||||
<string name="app_changelog">Değişiklik günlüğü</string>
|
||||
<string name="app_changelog">Değişiklik Günlüğü</string>
|
||||
<string name="loading">Yükleniyor…</string>
|
||||
<string name="update">Güncelle</string>
|
||||
<string name="not_available">Yüklü değil</string>
|
||||
<string name="not_available">Yok</string>
|
||||
<string name="hide">Gizle</string>
|
||||
<string name="home_package">Paket</string>
|
||||
<string name="home_app_title">Uygulama</string>
|
||||
|
||||
<string name="home_notice_content">Magisk\'i YALNIZCA resmi GitHub sayfasından indirin. Bilinmeyen kaynaklardan gelen dosyalar kötü amaçlı olabilir!</string>
|
||||
<string name="home_notice_content">Magisk\'i YALNIZCA resmi GitHub sayfasından indirin. Bilinmeyen kaynaklardan gelen dosyalar zararlı olabilir!</string>
|
||||
<string name="home_support_title">Bizi Destekleyin</string>
|
||||
<string name="home_follow_title">Bizi Takip Edin</string>
|
||||
<string name="home_item_source">Kaynak</string>
|
||||
<string name="home_support_content">Magisk ücretsiz ve açık kaynaktır ve her zaman öyle kalacaktır. Ancak bir bağış yaparak bize destek olduğunuzu gösterebilirsiniz.</string>
|
||||
<string name="home_installed_version">Durum</string>
|
||||
<string name="home_latest_version">En son sürüm</string>
|
||||
<string name="home_support_content">Magisk her zaman ücretsiz ve açık kaynak olacaktır. Ancak, bir bağış yaparak bize destek olabilirsiniz.</string>
|
||||
<string name="home_installed_version">Yüklü Sürüm</string>
|
||||
<string name="home_latest_version">En Son Sürüm</string>
|
||||
<string name="invalid_update_channel">Geçersiz Güncelleme Kanalı</string>
|
||||
<string name="uninstall_magisk_title">Magisk\'i Kaldır</string>
|
||||
<string name="uninstall_magisk_msg">Tüm modüller devre dışı bırakılacak/kaldırılacak!\nKök kaldırılacak!\nMagisk kullanılarak şifrelenmemiş herhangi bir dahili depolama yeniden şifrelenecek!</string>
|
||||
@@ -38,14 +38,14 @@
|
||||
<string name="install_options_title">Seçenekler</string>
|
||||
<string name="install_method_title">Yöntem</string>
|
||||
<string name="install_next">Sonraki</string>
|
||||
<string name="install_start">Haydi başlayalım</string>
|
||||
<string name="install_start">Hadi başlayalım</string>
|
||||
<string name="manager_download_install">İndirmek ve yüklemek için basın</string>
|
||||
<string name="direct_install">Doğrudan Kurulum (Önerilir)</string>
|
||||
<string name="install_inactive_slot">Etkin Olmayan Yuvaya Yükle (OTA\'dan Sonra)</string>
|
||||
<string name="install_inactive_slot_msg">Yeniden başlatmanın ardından cihazınız mevcut etkin olmayan yuvaya önyükleme yapmaya ZORLANACAK!\nBu seçeneği yalnızca OTA tamamlandıktan sonra kullanın.\nDevam edilsin mi?</string>
|
||||
<string name="direct_install">Doğrudan Yükleme (Önerilir)</string>
|
||||
<string name="install_inactive_slot">Etkin Olmayan Slot\'a Yükle (OTA Sonrası)</string>
|
||||
<string name="install_inactive_slot_msg">Cihazınız yeniden başlatıldıktan sonra zorunlu olarak mevcut etkin olmayan slota önyükleme yapılacaktır!\nBu seçeneği yalnızca OTA tamamlandıktan sonra kullanın.\nDevam etmek istiyor musunuz?</string>
|
||||
<string name="setup_title">Ek Kurulum</string>
|
||||
<string name="select_patch_file">Bir Dosya Seç ve Yama Yap</string>
|
||||
<string name="patch_file_msg">Bir ham görüntü (*.img) veya bir ODIN tar dosyası (*.tar) veya bir payload.bin (*.bin) seçin</string>
|
||||
<string name="patch_file_msg">Ham bir görüntü (*.img) veya bir ODIN tar dosyası (*.tar) veya bir payload.bin (*.bin) seçin</string>
|
||||
<string name="reboot_delay_toast">5 saniye içinde yeniden başlatılıyor…</string>
|
||||
<string name="flash_screen_title">Yükleniyor</string>
|
||||
|
||||
@@ -53,42 +53,42 @@
|
||||
<string name="su_request_title">Süper Kullanıcı İsteği</string>
|
||||
<string name="touch_filtered_warning">Bir uygulama bir Süper Kullanıcı isteğini engellediği için Magisk yanıtınızı doğrulayamıyor</string>
|
||||
<string name="deny">Reddet</string>
|
||||
<string name="prompt">Sor</string>
|
||||
<string name="grant">İzin ver</string>
|
||||
<string name="su_warning">Cihazınıza tam erişim sağlar.\nEğer emin değilseniz reddedin!</string>
|
||||
<string name="prompt">İstem</string>
|
||||
<string name="grant">İzin Ver</string>
|
||||
<string name="su_warning">Cihazınıza tam erişim sağlar.\nEmin değilseniz reddedin!</string>
|
||||
<string name="forever">Daima</string>
|
||||
<string name="once">Bir kez</string>
|
||||
<string name="tenmin">10 dakika</string>
|
||||
<string name="twentymin">20 dakika</string>
|
||||
<string name="thirtymin">30 dakika</string>
|
||||
<string name="sixtymin">60 dakika</string>
|
||||
<string name="su_allow_toast">%1$s uygulamasının Süper Kullanıcı izni verildi</string>
|
||||
<string name="su_deny_toast">%1$s uygulamasının Süper Kullanıcı izni reddedildi</string>
|
||||
<string name="su_snack_grant">%1$s uygulamasının Süper Kullanıcı izni verildi</string>
|
||||
<string name="su_snack_deny">%1$s uygulamasının Süper Kullanıcı izni reddedildi</string>
|
||||
<string name="su_allow_toast">%1$s uygulamasının süper kullanıcı hakları verildi</string>
|
||||
<string name="su_deny_toast">%1$s uygulamasının süper kullanıcı hakları reddedildi</string>
|
||||
<string name="su_snack_grant">%1$s uygulamasının süper kullanıcı hakları verildi</string>
|
||||
<string name="su_snack_deny">%1$s uygulamasının süper kullanıcı hakları reddedildi</string>
|
||||
<string name="su_snack_notif_on">%1$s uygulamasının bildirimleri etkinleştirildi</string>
|
||||
<string name="su_snack_notif_off">%1$s uygulamasının bildirimleri devre dışı bırakıldı</string>
|
||||
<string name="su_snack_log_on">%1$s uygulamasının günlüğü etkinleştirildi</string>
|
||||
<string name="su_snack_log_off">%1$s uygulamasının günlüğü devre dışı bırakıldı</string>
|
||||
<string name="su_revoke_title">İptal et?</string>
|
||||
<string name="su_revoke_msg">%1$s uygulamasının Süper Kullanıcı haklarını iptal etmeyi onaylayın</string>
|
||||
<string name="toast">Tost</string>
|
||||
<string name="none">Hiçbiri</string>
|
||||
<string name="su_revoke_title">İptal Et?</string>
|
||||
<string name="su_revoke_msg">%1$s uygulamasının süper kullanıcı haklarını iptal etmek istediğinize emin misiniz?</string>
|
||||
<string name="toast">Bildirim</string>
|
||||
<string name="none">Yok</string>
|
||||
|
||||
<string name="superuser_toggle_notification">Bildirimler</string>
|
||||
<string name="superuser_toggle_revoke">İptal et</string>
|
||||
<string name="superuser_toggle_revoke">İptal Et</string>
|
||||
<string name="superuser_policy_none">Henüz hiçbir uygulama Süper Kullanıcı izni istemedi.</string>
|
||||
|
||||
<!--Logs-->
|
||||
<string name="log_data_none">Günlük kullanmıyorsunuz, kök uygulamalarınızı daha fazla kullanmayı deneyin</string>
|
||||
<string name="log_data_magisk_none">Magisk günlükleri boş, bu garip</string>
|
||||
<string name="log_data_none">Günlük kullanmıyorsunuz, root (kök) uygulamalarınızı daha çok kullanmayı deneyin</string>
|
||||
<string name="log_data_magisk_none">Magisk günlükleri boş, bu tuhaf</string>
|
||||
<string name="menuSaveLog">Günlüğü kaydet</string>
|
||||
<string name="menuClearLog">Günlüğü şimdi temizle</string>
|
||||
<string name="logs_cleared">Günlük kaydı başarıyla temizlendi</string>
|
||||
<string name="pid">PID: %1$d</string>
|
||||
<string name="target_uid">Hedef UID: %1$d</string>
|
||||
<string name="target_pid">Ns hedef PID\'sini bağla: %s</string>
|
||||
<string name="selinux_context">SELinux içeriği: %s</string>
|
||||
<string name="target_pid">Mount ns hedef PID: %s</string>
|
||||
<string name="selinux_context">SELinux bağlamı: %s</string>
|
||||
<string name="supp_group">Ek grup: %s</string>
|
||||
|
||||
<!--SafetyNet-->
|
||||
@@ -96,7 +96,7 @@
|
||||
<!--MagiskHide-->
|
||||
<string name="show_system_app">Sistem uygulamalarını göster</string>
|
||||
<string name="show_os_app">İşletim sistemi uygulamalarını göster</string>
|
||||
<string name="hide_filter_hint">Ada göre filtrele</string>
|
||||
<string name="hide_filter_hint">İsme göre filtrele</string>
|
||||
<string name="hide_search">Ara</string>
|
||||
|
||||
<!--Module-->
|
||||
@@ -106,13 +106,14 @@
|
||||
<string name="reboot_bootloader">Önyükleyici modunda yeniden başlat</string>
|
||||
<string name="reboot_download">İndirme modunda yeniden başlat</string>
|
||||
<string name="reboot_edl">EDL modunda yeniden başlat</string>
|
||||
<string name="reboot_safe_mode">Güvenli mod</string>
|
||||
<string name="module_version_author">%1$s / %2$s</string>
|
||||
<string name="module_state_remove">Kaldır</string>
|
||||
<string name="module_state_restore">Geri yükle</string>
|
||||
<string name="module_state_restore">Geri Yükle</string>
|
||||
<string name="module_action_install_external">Depolamadan yükle</string>
|
||||
<string name="update_available">Güncelleme Mevcut</string>
|
||||
<string name="suspend_text_riru">%1$s etkinleştirildiği için modül askıya alındı</string>
|
||||
<string name="suspend_text_zygisk">%1$s etkinleştirilmediği için modül askıya alındı</string>
|
||||
<string name="suspend_text_riru">Modül, %1$s etkin olduğu için askıya alındı</string>
|
||||
<string name="suspend_text_zygisk">Modül, %1$s etkin olmadığı için askıya alındı</string>
|
||||
<string name="zygisk_module_unloaded">Uyumsuzluk nedeniyle Zygisk modülü yüklenmedi</string>
|
||||
<string name="module_empty">Yüklü modül yok</string>
|
||||
<string name="confirm_install">%1$s modülü yüklensin mi?</string>
|
||||
@@ -121,39 +122,39 @@
|
||||
<!--Settings-->
|
||||
<string name="settings_dark_mode_title">Tema Modu</string>
|
||||
<string name="settings_dark_mode_message">Tarzınıza en uygun modu seçin!</string>
|
||||
<string name="settings_dark_mode_light">Daima Açık</string>
|
||||
<string name="settings_dark_mode_light">Her Zaman Aydınlık</string>
|
||||
<string name="settings_dark_mode_system">Sistemi Takip Et</string>
|
||||
<string name="settings_dark_mode_dark">Daima Koyu</string>
|
||||
<string name="settings_download_path_title">İndirme yolu</string>
|
||||
<string name="settings_dark_mode_dark">Her Zaman Karanlık</string>
|
||||
<string name="settings_download_path_title">İndirme Yolu</string>
|
||||
<string name="settings_download_path_message">Dosyalar %1$s konumuna kaydedilecek</string>
|
||||
<string name="settings_hide_app_title">Magisk uygulamasını gizle</string>
|
||||
<string name="settings_hide_app_summary">Rastgele bir paket kimliği ve özel uygulama etiketi olan bir proxy uygulaması yükleyin</string>
|
||||
<string name="settings_hide_app_summary">Rastgele bir paket kimliği ve özel uygulama etiketi olan bir vekil (proxy) uygulaması yükleyin</string>
|
||||
<string name="settings_restore_app_title">Magisk uygulamasını geri yükle</string>
|
||||
<string name="settings_restore_app_summary">Uygulamayı göster ve orijinal APK\'yı geri yükle</string>
|
||||
<string name="language">Dil</string>
|
||||
<string name="system_default">(Sistem Varsayılanı)</string>
|
||||
<string name="settings_check_update_title">Güncellemeleri Kontrol Et</string>
|
||||
<string name="settings_check_update_summary">Arka plandaki güncellemeleri düzenli olarak kontrol et</string>
|
||||
<string name="settings_check_update_summary">Arka planda düzenli olarak güncellemeleri kontrol et</string>
|
||||
<string name="settings_update_channel_title">Güncelleme Kanalı</string>
|
||||
<string name="settings_update_stable">Stabil</string>
|
||||
<string name="settings_update_stable">Kararlı</string>
|
||||
<string name="settings_update_beta">Beta</string>
|
||||
<string name="settings_update_custom">Özel</string>
|
||||
<string name="settings_update_custom_msg">Özel bir kanal bağlantısı ekle</string>
|
||||
<string name="settings_zygisk_summary">Zygisk arka plan programında Magisk\'in bazı bölümlerini çalıştır</string>
|
||||
<string name="settings_update_custom_msg">Özel kanal URL\'si girin</string>
|
||||
<string name="settings_zygisk_summary">Magisk\'in bazı bölümlerini zygote daemon\'unda çalıştır</string>
|
||||
<string name="settings_denylist_title">Reddetme Listesini Zorla</string>
|
||||
<string name="settings_denylist_summary">Reddetme listesindeki işlemlerde tüm Magisk değişiklikleri geri alınır</string>
|
||||
<string name="settings_denylist_summary">Reddetme Listesindeki işlemler tüm Magisk değişikliklerini geri alacak</string>
|
||||
<string name="settings_denylist_config_title">Reddetme Listesini Yapılandır</string>
|
||||
<string name="settings_denylist_config_summary">Reddetme listesine dahil edilecek işlemleri seç</string>
|
||||
<string name="settings_denylist_config_summary">Reddetme Listesine dahil edilecek işlemleri seçin</string>
|
||||
<string name="settings_hosts_title">Sistemsiz ana makineler (systemless hosts)</string>
|
||||
<string name="settings_hosts_summary">Reklam engelleme uygulamaları için sistemsiz ana makineler (systemless hosts) desteği</string>
|
||||
<string name="settings_hosts_toast">Sistemsiz ana makineler (systemless hosts) modülü eklendi</string>
|
||||
<string name="settings_app_name_hint">Yeni ad</string>
|
||||
<string name="settings_app_name_helper">Uygulama bu adla yeniden paketlenecek</string>
|
||||
<string name="settings_app_name_helper">Uygulama bu isimle yeniden paketlenecek</string>
|
||||
<string name="settings_app_name_error">Geçersiz format</string>
|
||||
<string name="settings_su_app_adb">Uygulamalar ve ADB</string>
|
||||
<string name="settings_su_app">Yalnızca uygulamalar</string>
|
||||
<string name="settings_su_adb">Yalnızca ADB</string>
|
||||
<string name="settings_su_disable">Devre dışı</string>
|
||||
<string name="settings_su_app">Sadece Uygulamalar</string>
|
||||
<string name="settings_su_adb">Sadece ADB</string>
|
||||
<string name="settings_su_disable">Devre Dışı</string>
|
||||
<string name="settings_su_request_10">10 saniye</string>
|
||||
<string name="settings_su_request_15">15 saniye</string>
|
||||
<string name="settings_su_request_20">20 saniye</string>
|
||||
@@ -164,39 +165,41 @@
|
||||
<string name="auto_response">Otomatik Yanıt</string>
|
||||
<string name="request_timeout">İstek Zaman Aşımı</string>
|
||||
<string name="superuser_notification">Süper Kullanıcı Bildirimi</string>
|
||||
<string name="settings_su_reauth_title">Yükseltmeden sonra yeniden kimlik doğrulaması yap</string>
|
||||
<string name="settings_su_reauth_summary">Uygulamaları yükselttikten sonra Süper Kullanıcı izinlerini tekrar iste</string>
|
||||
<string name="settings_su_tapjack_title">Sahte Ekran (Tapjacking) Koruması</string>
|
||||
<string name="settings_su_tapjack_summary">Süper Kullanıcı bilgi istemi iletişim kutusu, herhangi bir başka pencere veya yer paylaşımı tarafından engellendiğinde girişe yanıt vermeyecektir.</string>
|
||||
<string name="settings_su_auth_title">Kullanıcı Kimlik Doğrulaması</string>
|
||||
<string name="settings_su_auth_summary">Süper Kullanıcı istekleri sırasında kullanıcı kimlik doğrulaması iste</string>
|
||||
<string name="settings_su_auth_insecure">Cihazda hiçbir kimlik doğrulama yöntemi yapılandırılmamış</string>
|
||||
<string name="settings_su_reauth_title">Yükseltme sonrası yeniden doğrulama yap</string>
|
||||
<string name="settings_su_reauth_summary">Uygulama güncellemelerinden sonra Süper Kullanıcı izinlerini tekrar iste</string>
|
||||
<string name="settings_su_tapjack_title">Tapjacking Koruması</string>
|
||||
<string name="settings_su_tapjack_summary">Süper Kullanıcı istemi diyalogu, başka bir pencere veya katman tarafından gizlendiğinde girdilere yanıt vermeyecektir</string>
|
||||
<string name="settings_su_auth_title">Kullanıcı Kimlik Doğrulama</string>
|
||||
<string name="settings_su_auth_summary">Süper Kullanıcı isteklerinde kullanıcı kimlik doğrulaması iste</string>
|
||||
<string name="settings_su_auth_insecure">Cihazda yapılandırılmış bir kimlik doğrulama yöntemi yok</string>
|
||||
<string name="settings_customization">Özelleştir</string>
|
||||
<string name="setting_add_shortcut_summary">Uygulamayı gizledikten sonra adın ve simgenin tanınmasının zor olması durumunda ana ekrana güzel bir kısayol ekle</string>
|
||||
<string name="settings_doh_title">HTTPS üzerinden DNS</string>
|
||||
<string name="settings_doh_description">Bazı ülkelerde DNS zehirlenmesine geçici çözüm</string>
|
||||
<string name="setting_add_shortcut_summary">Uygulamayı gizledikten sonra adı ve simgeyi tanımakta zorlanıyorsanız ana ekrana güzel bir kısayol ekle</string>
|
||||
<string name="settings_doh_title">DNS üzerinden HTTPS</string>
|
||||
<string name="settings_doh_description">Bazı ülkelerde DNS zehirlemesine karşı geçici çözüm</string>
|
||||
<string name="settings_random_name_title">Çıkış adını rastgele seç</string>
|
||||
<string name="settings_random_name_description">Algılamayı önlemek için yamalı resimlerin ve tar dosyalarının çıkış dosya adını rastgele seç</string>
|
||||
|
||||
<string name="multiuser_mode">Çok Kullanıcılı Mod</string>
|
||||
<string name="settings_owner_only">Yalnızca Cihaz Sahibi</string>
|
||||
<string name="settings_owner_manage">Cihaz Sahibi Tarafından Yönetildi</string>
|
||||
<string name="settings_owner_manage">Cihaz Sahibi Yönetiminde</string>
|
||||
<string name="settings_user_independent">Kullanıcıdan Bağımsız</string>
|
||||
<string name="owner_only_summary">Kök erişimi yalnızca sahibine aittir</string>
|
||||
<string name="owner_manage_summary">Kök erişimini yalnızca sahip yönetebilir ve istek istemlerini alabilir</string>
|
||||
<string name="user_independent_summary">Her kullanıcının kendi ayrı kök kuralları vardır</string>
|
||||
<string name="owner_only_summary">Yalnızca sahip kök (root) erişimine sahiptir</string>
|
||||
<string name="owner_manage_summary">Yalnızca sahip kök (root) erişimini yönetebilir ve istek uyarılarını alabilir</string>
|
||||
<string name="user_independent_summary">Her kullanıcının kendi ayrı kök (root) kuralları vardır</string>
|
||||
|
||||
<string name="mount_namespace_mode">Bağlama Ad Alanı Modu</string>
|
||||
<string name="settings_ns_global">Küresel Ad Alanı</string>
|
||||
<string name="settings_ns_requester">Ad Alanını Devral</string>
|
||||
<string name="settings_ns_isolate">İzole Edilmiş Ad Alanı</string>
|
||||
<string name="global_summary">Tüm kök oturumları, global bağlama ad alanını kullanır</string>
|
||||
<string name="requester_summary">Kök oturumları, istek sahibinin ad alanını devralır</string>
|
||||
<string name="isolate_summary">Her kök oturumun kendi izole edilmiş ad alanı olacaktır</string>
|
||||
<string name="settings_ns_isolate">İzolasyon Ad Alanı</string>
|
||||
<string name="global_summary">Tüm kök (root) oturumları küresel bağlama ad alanını kullanır</string>
|
||||
<string name="requester_summary">Kök (root) oturumları isteyicisinin ad alanını devralacak</string>
|
||||
<string name="isolate_summary">Her kök (root) oturumu kendi izole ad alanına sahip olacak</string>
|
||||
|
||||
<!--Notifications-->
|
||||
<string name="update_channel">Magisk Güncellemeleri</string>
|
||||
<string name="progress_channel">İlerleme Bildirimleri</string>
|
||||
<string name="updated_channel">Güncelleme Tamamlandı</string>
|
||||
<string name="download_complete">İndirme Tamamlandı</string>
|
||||
<string name="download_complete">İndirme tamamlandı</string>
|
||||
<string name="download_file_error">Dosya indirilirken hata oluştu</string>
|
||||
<string name="magisk_update_title">Magisk Güncellemesi Mevcut!</string>
|
||||
<string name="updated_title">Magisk Güncellendi</string>
|
||||
@@ -204,41 +207,41 @@
|
||||
|
||||
<!--Toasts, Dialogs-->
|
||||
<string name="yes">Mevcut</string>
|
||||
<string name="no">Mevcut değil</string>
|
||||
<string name="repo_install_title">%1$s %2$s(%3$d) yükle</string>
|
||||
<string name="no">Mevcut Değil</string>
|
||||
<string name="repo_install_title">%1$s %2$s(%3$d) Kur</string>
|
||||
<string name="download">İndir</string>
|
||||
<string name="reboot">Yeniden başlat</string>
|
||||
<string name="release_notes">Sürüm notları</string>
|
||||
<string name="flashing">Flaşlanıyor…</string>
|
||||
<string name="reboot">Yeniden Başlat</string>
|
||||
<string name="release_notes">Sürüm Notları</string>
|
||||
<string name="flashing">Yükleniyor...</string>
|
||||
<string name="done">Tamamlandı!</string>
|
||||
<string name="failure">Başarısız!</string>
|
||||
<string name="hide_app_title">Magisk uygulaması gizleniyor…</string>
|
||||
<string name="open_link_failed_toast">Bağlantıyı açacak uygulama bulunamadı</string>
|
||||
<string name="complete_uninstall">Kaldırmayı Tamamla</string>
|
||||
<string name="open_link_failed_toast">Bağlantıyı açmak için uygulama bulunamadı</string>
|
||||
<string name="complete_uninstall">Tamamen Kaldır</string>
|
||||
<string name="restore_img">Görüntüleri Geri Yükle</string>
|
||||
<string name="restore_img_msg">Geri yükleniyor…</string>
|
||||
<string name="restore_img_msg">Geri Yükleniyor...</string>
|
||||
<string name="restore_done">Geri yükleme tamamlandı!</string>
|
||||
<string name="restore_fail">Stok yedeği mevcut değil!</string>
|
||||
<string name="setup_fail">Kurulum başarısız oldu</string>
|
||||
<string name="env_fix_title">Ek Kurulum Gerekiyor</string>
|
||||
<string name="env_fix_msg">Magisk\'in düzgün çalışması için cihazınızın ek kuruluma ihtiyacı var. Devam etmek ve yeniden başlatmak istiyor musunuz?</string>
|
||||
<string name="env_full_fix_msg">Cihazınızın düzgün çalışması için Magisk\'in yeniden başlatılması gerekiyor. Lütfen Magisk\'i uygulama içinden yeniden yükleyin, kurtarma modu doğru cihaz bilgilerini alamıyor.</string>
|
||||
<string name="setup_msg">Ortam kurulumu çalıştırılıyor…</string>
|
||||
<string name="env_fix_title">Ek Ayar Gerekiyor</string>
|
||||
<string name="env_fix_msg">Magisk\'in düzgün çalışabilmesi için cihazınızın ek ayarlar yapması gerekiyor. Devam etmek ve yeniden başlatmak istiyor musunuz?</string>
|
||||
<string name="env_full_fix_msg">Cihazınızın düzgün çalışabilmesi için Magisk\'in yeniden yüklenmesi gerekiyor. Lütfen Magisk\'i uygulama içinde yeniden yükleyin, kurtarma modu doğru cihaz bilgilerini alamaz.</string>
|
||||
<string name="setup_msg">Ortam kurulumu yapılıyor...</string>
|
||||
<string name="unsupport_magisk_title">Desteklenmeyen Magisk Sürümü</string>
|
||||
<string name="unsupport_magisk_msg">Uygulamanın bu sürümü %1$s altındaki Magisk sürümlerini desteklemiyor.\n\nUygulama Magisk yüklenmemiş gibi davranacak, lütfen en kısa sürede Magisk\'i yükseltin.</string>
|
||||
<string name="unsupport_magisk_msg">Bu uygulama sürümü, %1$s altındaki Magisk sürümlerini desteklemiyor.\n\nUygulama, Magisk yüklü değilmiş gibi davranacaktır, lütfen en kısa sürede Magisk\'i güncelleyin.</string>
|
||||
<string name="unsupport_general_title">Anormal Durum</string>
|
||||
<string name="unsupport_system_app_msg">Bu uygulamanın sistem uygulaması olarak çalıştırılması desteklenmiyor. Lütfen uygulamayı bir kullanıcı uygulamasına geri yükleyin.</string>
|
||||
<string name="unsupport_other_su_msg">Magisk\'ten olmayan bir \"su\" ikilisi algılandı. Lütfen rakip kök çözümlerini kaldırın ve/veya Magisk\'i yeniden yükleyin.</string>
|
||||
<string name="unsupport_external_storage_msg">Magisk harici depolamaya yüklenmiş. Lütfen uygulamayı dahili depolamaya taşıyın.</string>
|
||||
<string name="unsupport_nonroot_stub_msg">Kök (root) kaybolduğu için gizli Magisk uygulaması çalışmaya devam edemiyor. Lütfen orijinal APK\'yı geri yükleyin.</string>
|
||||
<string name="unsupport_system_app_msg">Bu uygulamanın sistem uygulaması olarak çalıştırılması desteklenmiyor. Lütfen uygulamayı kullanıcı uygulamasına geri döndürün.</string>
|
||||
<string name="unsupport_other_su_msg">Magisk\'ten gelmeyen bir "su" ikili dosyası tespit edildi. Lütfen herhangi bir rakip kök (root) çözümünü kaldırın ve/veya Magisk\'i yeniden yükleyin.</string>
|
||||
<string name="unsupport_external_storage_msg">Magisk harici depolamaya yüklendi. Lütfen uygulamayı dahili depolamaya taşıyın.</string>
|
||||
<string name="unsupport_nonroot_stub_msg">Gizli Magisk uygulaması kök (root) erişimi kaybolduğu için çalışmaya devam edemez. Lütfen orijinal APK\'yı geri yükleyin.</string>
|
||||
<string name="unsupport_nonroot_stub_title">@string/settings_restore_app_title</string>
|
||||
<string name="external_rw_permission_denied">Bu işlevi etkinleştirmek için depolama izni veriniz.</string>
|
||||
<string name="post_notifications_denied">Bu işlevi etkinleştirmek için bildirim izni veriniz.</string>
|
||||
<string name="install_unknown_denied">Bu işlevi etkinleştirmek için "Bilinmeyen uygulamaları yükle" ayarına izin veriniz.</string>
|
||||
<string name="external_rw_permission_denied">Bu işlevselliği etkinleştirmek için depolama izni verin</string>
|
||||
<string name="post_notifications_denied">Bu işlevselliği etkinleştirmek için bildirim izni verin</string>
|
||||
<string name="install_unknown_denied">Bu işlevselliği etkinleştirmek için "bilinmeyen uygulamaları yükle" iznini verin</string>
|
||||
<string name="add_shortcut_title">Ana ekrana kısayol ekle</string>
|
||||
<string name="add_shortcut_msg">Bu uygulamayı gizledikten sonra adını ve simgesini tanımak zorlaşabilir. Ana ekrana güzel bir kısayol eklemek ister misiniz?</string>
|
||||
<string name="app_not_found">Bu eylemi gerçekleştirecek uygulama bulunamadı</string>
|
||||
<string name="add_shortcut_msg">Bu uygulamayı gizledikten sonra adı ve simgesi tanınmayabilir. Ana ekrana güzel bir kısayol eklemek ister misiniz?</string>
|
||||
<string name="app_not_found">Bu işlemi gerçekleştirecek uygulama bulunamadı</string>
|
||||
<string name="reboot_apply_change">Değişiklikleri uygulamak için yeniden başlatın</string>
|
||||
<string name="restore_app_confirmation">Bu işlem, gizli uygulamayı orijinal uygulama ile değiştirecektir. Bu işlemi yapmak istediğinizden emin misiniz?</string>
|
||||
<string name="restore_app_confirmation">Bu, gizli uygulamayı orijinal uygulamaya geri yükleyecektir. Gerçekten bunu yapmak istiyor musunuz?</string>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -130,8 +130,8 @@
|
||||
<string name="settings_update_custom">Власний</string>
|
||||
<string name="settings_update_custom_msg">Вставте власний URL</string>
|
||||
<string name="settings_zygisk_summary">Запускати частини Magisk в сервісі zygote</string>
|
||||
<string name="settings_denylist_title">Enforce DenyList</string>
|
||||
<string name="settings_denylist_summary">Processes on the denylist will have all Magisk modifications reverted</string>
|
||||
<string name="settings_denylist_title">Увімкнути DenyList</string>
|
||||
<string name="settings_denylist_summary">Всі зміни, внесені Magisk, будуть приховані від процесів, позначених у DenyList</string>
|
||||
<string name="settings_denylist_config_title">Налаштувати DenyList</string>
|
||||
<string name="settings_denylist_config_summary">Вибрати процеси, які будуть додані до denylist</string>
|
||||
<string name="settings_hosts_title">Позасистемні хости</string>
|
||||
@@ -156,7 +156,7 @@
|
||||
<string name="superuser_notification">Сповіщення суперкористувача</string>
|
||||
<string name="settings_su_reauth_title">Повторна автентифікація</string>
|
||||
<string name="settings_su_reauth_summary">Перевидача прав суперкористувача після оновлення застосунку</string>
|
||||
<string name="settings_su_tapjack_title">Увімкнути захист від Tapjack</string>
|
||||
<string name="settings_su_tapjack_title">Увімкнути захист від підміни натискань</string>
|
||||
<string name="settings_su_tapjack_summary">Діалогове вікно суперкористувача не буде отримувати ввід від користувача, коли вікно перекрито іншим застосунком чи вікном</string>
|
||||
<string name="settings_customization">Оформлення</string>
|
||||
<string name="setting_add_shortcut_summary">Додати ярлик на домашній екран для зручного сприйняття застосунку після його приховування</string>
|
||||
|
||||
@@ -109,6 +109,7 @@
|
||||
<string name="reboot_safe_mode">安全模式</string>
|
||||
<string name="module_version_author">%1$s,作者 %2$s</string>
|
||||
<string name="module_state_remove">移除</string>
|
||||
<string name="module_action">操作</string>
|
||||
<string name="module_state_restore">还原</string>
|
||||
<string name="module_action_install_external">从本地安装</string>
|
||||
<string name="update_available">可更新</string>
|
||||
@@ -211,9 +212,12 @@
|
||||
<string name="repo_install_title">安装 %1$s %2$s(%3$d)</string>
|
||||
<string name="download">下载</string>
|
||||
<string name="reboot">重启</string>
|
||||
<string name="close">关闭</string>
|
||||
<string name="release_notes">发布说明</string>
|
||||
<string name="flashing">正在刷入</string>
|
||||
<string name="running">运行中……</string>
|
||||
<string name="done">完成!</string>
|
||||
<string name="done_action">%1$s 操作运行完成</string>
|
||||
<string name="failure">失败</string>
|
||||
<string name="hide_app_title">正在隐藏 Magisk 应用</string>
|
||||
<string name="open_link_failed_toast">找不到能打开此链接的应用</string>
|
||||
|
||||
@@ -109,6 +109,7 @@
|
||||
<string name="reboot_safe_mode">Safe mode</string>
|
||||
<string name="module_version_author">%1$s by %2$s</string>
|
||||
<string name="module_state_remove">Remove</string>
|
||||
<string name="module_action">Action</string>
|
||||
<string name="module_state_restore">Restore</string>
|
||||
<string name="module_action_install_external">Install from storage</string>
|
||||
<string name="update_available">Update Available</string>
|
||||
@@ -211,9 +212,12 @@
|
||||
<string name="repo_install_title">Install %1$s %2$s(%3$d)</string>
|
||||
<string name="download">Download</string>
|
||||
<string name="reboot">Reboot</string>
|
||||
<string name="close">Close</string>
|
||||
<string name="release_notes">Release notes</string>
|
||||
<string name="flashing">Flashing…</string>
|
||||
<string name="running">Running…</string>
|
||||
<string name="done">Done!</string>
|
||||
<string name="done_action">Done running action of %1$s</string>
|
||||
<string name="failure">Failed!</string>
|
||||
<string name="hide_app_title">Hiding the Magisk app…</string>
|
||||
<string name="open_link_failed_toast">No app found to open the link</string>
|
||||
|
||||
@@ -1,13 +1,22 @@
|
||||
package com.topjohnwu.magisk;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
|
||||
public class ProviderInstaller {
|
||||
|
||||
private static final String GMS_PACKAGE_NAME = "com.google.android.gms";
|
||||
|
||||
public static boolean install(Context context) {
|
||||
try {
|
||||
// Check if gms is a system app
|
||||
ApplicationInfo appInfo = context.getPackageManager().getApplicationInfo(GMS_PACKAGE_NAME, 0);
|
||||
if ((appInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Try installing new SSL provider from Google Play Service
|
||||
Context gms = context.createPackageContext("com.google.android.gms",
|
||||
Context gms = context.createPackageContext(GMS_PACKAGE_NAME,
|
||||
Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY);
|
||||
gms.getClassLoader()
|
||||
.loadClass("com.google.android.gms.common.security.ProviderInstallerImpl")
|
||||
|
||||
@@ -12,7 +12,7 @@ import dalvik.system.BaseDexClassLoader;
|
||||
public class DynamicClassLoader extends BaseDexClassLoader {
|
||||
|
||||
public DynamicClassLoader(File apk) {
|
||||
this(apk, getSystemClassLoader());
|
||||
this(apk, DynamicClassLoader.class.getClassLoader());
|
||||
}
|
||||
|
||||
public DynamicClassLoader(File apk, ClassLoader parent) {
|
||||
|
||||
@@ -15,7 +15,7 @@ android {
|
||||
val canary = !Config.version.contains(".")
|
||||
|
||||
val url = if (canary) null
|
||||
else "https://cdn.jsdelivr.net/gh/topjohnwu/magisk-files@${Config.version}/app-release.apk"
|
||||
else "https://github.com/topjohnwu/Magisk/releases/download/v${Config.version}/Magisk-v${Config.version}.apk"
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "com.topjohnwu.magisk"
|
||||
@@ -27,9 +27,9 @@ android {
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
proguardFiles("proguard-rules.pro")
|
||||
isMinifyEnabled = true
|
||||
isShrinkResources = false
|
||||
proguardFiles("proguard-rules.pro")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
setupStub()
|
||||
setupStubApk()
|
||||
|
||||
dependencies {
|
||||
implementation(project(":app:shared"))
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="upgrade_msg">عليك الترقية ماجـيسك Manager لإكمال تهيئة التطبيق.هل اكمل؟</string>
|
||||
<string name="no_internet_msg">يرجى الاتصال بالانترنيت! ترقية ماجـيسك مطلوب...</string>
|
||||
<string name="upgrade_msg">عليك الترقية Magisk لإكمال تهيئة التطبيق. هل تريد التنزيل والتثبيت؟</string>
|
||||
<string name="no_internet_msg">يرجى اللإتصال بالإنترنت! ترقية Magisk مطلوبة.</string>
|
||||
<string name="dling">جارٍ التنزيل</string>
|
||||
<string name="relaunch_app">يرجى إعادة تشغيل التطبيق يدوياً</string>
|
||||
</resources>
|
||||
|
||||
7
app/stub/src/main/res/values-ku/strings.xml
Normal file
7
app/stub/src/main/res/values-ku/strings.xml
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="upgrade_msg">ماجیسکەکەت بەرزبکەوە بۆ وەشانی تەواوەتی، دەتەوێت دایبگریت و ڕێکیبخەیت؟</string>
|
||||
<string name="no_internet_msg">تکایە پەیوەست ببە بە ئینتەرنێتەوە، پێویستە ماجیسکەکەت ڕێک بخەیت.</string>
|
||||
<string name="dling">داگرتن</string>
|
||||
<string name="relaunch_app">تکایە دووبارە ئەپەکە بکەوە</string>
|
||||
</resources>
|
||||
1
app/test/.gitignore
vendored
Normal file
1
app/test/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/build
|
||||
30
app/test/build.gradle.kts
Normal file
30
app/test/build.gradle.kts
Normal file
@@ -0,0 +1,30 @@
|
||||
plugins {
|
||||
id("com.android.application")
|
||||
kotlin("android")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.topjohnwu.magisk.test"
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "com.topjohnwu.magisk.test"
|
||||
versionCode = 1
|
||||
versionName = "1.0"
|
||||
proguardFile("proguard-rules.pro")
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
isMinifyEnabled = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setupAppCommon()
|
||||
|
||||
dependencies {
|
||||
implementation(libs.test.runner)
|
||||
implementation(libs.test.rules)
|
||||
implementation(libs.test.junit)
|
||||
implementation(libs.test.uiautomator)
|
||||
}
|
||||
13
app/test/proguard-rules.pro
vendored
Normal file
13
app/test/proguard-rules.pro
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
# Keep all test dependencies
|
||||
-keep class org.junit.** { *; }
|
||||
-keep class androidx.test.** { *; }
|
||||
|
||||
# Make sure the classloader constructor is kept
|
||||
-keepclassmembers class com.topjohnwu.magisk.test.TestClassLoader { <init>(); }
|
||||
|
||||
# Repackage dependencies
|
||||
-repackageclasses 'deps'
|
||||
-allowaccessmodification
|
||||
|
||||
# Keep attributes for stacktrace
|
||||
-keepattributes *
|
||||
23
app/test/src/main/AndroidManifest.xml
Normal file
23
app/test/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<uses-permission
|
||||
android:name="android.permission.QUERY_ALL_PACKAGES"
|
||||
tools:ignore="QueryAllPackagesPermission" />
|
||||
|
||||
<queries tools:node="removeAll" />
|
||||
|
||||
<application tools:node="replace">
|
||||
<uses-library android:name="android.test.runner" />
|
||||
</application>
|
||||
|
||||
<instrumentation
|
||||
android:name="com.topjohnwu.magisk.test.AppTestRunner"
|
||||
android:targetPackage="com.topjohnwu.magisk" />
|
||||
|
||||
<instrumentation
|
||||
android:name="com.topjohnwu.magisk.test.TestRunner"
|
||||
android:targetPackage="com.topjohnwu.magisk.test" />
|
||||
|
||||
</manifest>
|
||||
@@ -0,0 +1,88 @@
|
||||
package com.topjohnwu.magisk.test
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.os.ParcelFileDescriptor.AutoCloseInputStream
|
||||
import androidx.annotation.Keep
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import org.junit.After
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
@Keep
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class AppMigrationTest {
|
||||
|
||||
companion object {
|
||||
private const val APP_PKG = "com.topjohnwu.magisk"
|
||||
private const val STUB_PKG = "repackaged.$APP_PKG"
|
||||
private const val RECEIVER_TIMEOUT = 20L
|
||||
}
|
||||
|
||||
private val instrumentation get() = InstrumentationRegistry.getInstrumentation()
|
||||
private val context get() = instrumentation.context
|
||||
private val uiAutomation get() = instrumentation.uiAutomation
|
||||
private val registeredReceivers = mutableListOf<BroadcastReceiver>()
|
||||
|
||||
class PackageRemoveMonitor(
|
||||
context: Context,
|
||||
private val packageName: String
|
||||
) : BroadcastReceiver() {
|
||||
|
||||
val latch = CountDownLatch(1)
|
||||
|
||||
init {
|
||||
val filter = IntentFilter(Intent.ACTION_PACKAGE_REMOVED)
|
||||
filter.addDataScheme("package")
|
||||
context.registerReceiver(this, filter)
|
||||
}
|
||||
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
if (intent.action != Intent.ACTION_PACKAGE_REMOVED)
|
||||
return
|
||||
val data = intent.data ?: return
|
||||
val pkg = data.schemeSpecificPart
|
||||
if (pkg == packageName) latch.countDown()
|
||||
}
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
registeredReceivers.forEach(context::unregisterReceiver)
|
||||
}
|
||||
|
||||
private fun testAppMigration(pkg: String, method: String) {
|
||||
val receiver = PackageRemoveMonitor(context, pkg)
|
||||
registeredReceivers.add(receiver)
|
||||
|
||||
// Trigger the test to run migration
|
||||
val pfd = uiAutomation.executeShellCommand(
|
||||
"am instrument -w --user 0 -e class .Environment#$method " +
|
||||
"$pkg.test/${AppTestRunner::class.java.name}"
|
||||
)
|
||||
val output = AutoCloseInputStream(pfd).reader().use { it.readText() }
|
||||
assertTrue("$method failed, inst out: $output", output.contains("OK ("))
|
||||
|
||||
// Wait for migration to complete
|
||||
assertTrue(
|
||||
"$pkg uninstallation failed",
|
||||
receiver.latch.await(RECEIVER_TIMEOUT, TimeUnit.SECONDS)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testAppHide() {
|
||||
testAppMigration(APP_PKG, "setupAppHide")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testAppRestore() {
|
||||
testAppMigration(STUB_PKG, "setupAppRestore")
|
||||
}
|
||||
}
|
||||
35
app/test/src/main/java/com/topjohnwu/magisk/test/Runners.kt
Normal file
35
app/test/src/main/java/com/topjohnwu/magisk/test/Runners.kt
Normal file
@@ -0,0 +1,35 @@
|
||||
package com.topjohnwu.magisk.test
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.runner.AndroidJUnitRunner
|
||||
|
||||
open class TestRunner : AndroidJUnitRunner() {
|
||||
override fun onCreate(arguments: Bundle) {
|
||||
// Support short-hand ".ClassName"
|
||||
arguments.getString("class")?.let {
|
||||
val classArg = it.split(",").joinToString(separator = ",") { clz ->
|
||||
if (clz.startsWith(".")) {
|
||||
"com.topjohnwu.magisk.test$clz"
|
||||
} else {
|
||||
clz
|
||||
}
|
||||
}
|
||||
arguments.putString("class", classArg)
|
||||
}
|
||||
super.onCreate(arguments)
|
||||
}
|
||||
}
|
||||
|
||||
class AppTestRunner : TestRunner() {
|
||||
override fun onCreate(arguments: Bundle) {
|
||||
// Force using the target context's classloader to run tests
|
||||
arguments.putString("classLoader", TestClassLoader::class.java.name)
|
||||
super.onCreate(arguments)
|
||||
}
|
||||
}
|
||||
|
||||
private val targetClassLoader inline get() =
|
||||
InstrumentationRegistry.getInstrumentation().targetContext.classLoader
|
||||
|
||||
class TestClassLoader : ClassLoader(targetClassLoader)
|
||||
517
build.py
517
build.py
@@ -1,10 +1,12 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import copy
|
||||
import glob
|
||||
import lzma
|
||||
import multiprocessing
|
||||
import os
|
||||
import platform
|
||||
import re
|
||||
import shutil
|
||||
import stat
|
||||
import subprocess
|
||||
@@ -38,6 +40,7 @@ def vprint(str):
|
||||
print(str)
|
||||
|
||||
|
||||
# Environment checks and detection
|
||||
is_windows = os.name == "nt"
|
||||
EXE_EXT = ".exe" if is_windows else ""
|
||||
|
||||
@@ -51,7 +54,6 @@ if is_windows:
|
||||
# We can't do ANSI color codes in terminal on Windows without colorama
|
||||
no_color = True
|
||||
|
||||
# Environment checks
|
||||
if not sys.version_info >= (3, 8):
|
||||
error("Requires Python 3.8+")
|
||||
|
||||
@@ -63,42 +65,40 @@ except KeyError:
|
||||
except KeyError:
|
||||
error("Please set Android SDK path to environment variable ANDROID_HOME")
|
||||
|
||||
if shutil.which("sccache") is not None:
|
||||
os.environ["RUSTC_WRAPPER"] = "sccache"
|
||||
os.environ["NDK_CCACHE"] = "sccache"
|
||||
os.environ["CARGO_INCREMENTAL"] = "0"
|
||||
if shutil.which("ccache") is not None:
|
||||
os.environ["NDK_CCACHE"] = "ccache"
|
||||
|
||||
cpu_count = multiprocessing.cpu_count()
|
||||
os_name = platform.system().lower()
|
||||
|
||||
archs = ["armeabi-v7a", "x86", "arm64-v8a", "x86_64", "riscv64"]
|
||||
triples = [
|
||||
"armv7a-linux-androideabi",
|
||||
"i686-linux-android",
|
||||
"aarch64-linux-android",
|
||||
"x86_64-linux-android",
|
||||
"riscv64-linux-android",
|
||||
]
|
||||
# Common constants
|
||||
support_abis = {
|
||||
"armeabi-v7a": "thumbv7neon-linux-androideabi",
|
||||
"x86": "i686-linux-android",
|
||||
"arm64-v8a": "aarch64-linux-android",
|
||||
"x86_64": "x86_64-linux-android",
|
||||
"riscv64": "riscv64-linux-android",
|
||||
}
|
||||
default_targets = {"magisk", "magiskinit", "magiskboot", "magiskpolicy"}
|
||||
support_targets = default_targets | {"resetprop"}
|
||||
rust_targets = {"magisk", "magiskinit", "magiskboot", "magiskpolicy"}
|
||||
|
||||
# Common paths
|
||||
ndk_root = sdk_path / "ndk"
|
||||
ndk_path = ndk_root / "magisk"
|
||||
ndk_build = ndk_path / "ndk-build"
|
||||
rust_bin = ndk_path / "toolchains" / "rust" / "bin"
|
||||
llvm_bin = ndk_path / "toolchains" / "llvm" / "prebuilt" / f"{os_name}-x86_64" / "bin"
|
||||
cargo = rust_bin / f"cargo{EXE_EXT}"
|
||||
gradlew = Path("gradlew" + (".bat" if is_windows else "")).resolve()
|
||||
adb_path = sdk_path / "platform-tools" / f"adb{EXE_EXT}"
|
||||
cargo = rust_bin / "cargo"
|
||||
gradlew = Path.cwd() / "gradlew"
|
||||
adb_path = sdk_path / "platform-tools" / "adb"
|
||||
native_gen_path = Path("native", "out", "generated").resolve()
|
||||
|
||||
# Global vars
|
||||
config = {}
|
||||
STDOUT = None
|
||||
build_tools = None
|
||||
args = {}
|
||||
build_abis = {}
|
||||
|
||||
###################
|
||||
# Helper functions
|
||||
###################
|
||||
|
||||
|
||||
def mv(source: Path, target: Path):
|
||||
@@ -137,20 +137,26 @@ def rm_on_error(func, path, _):
|
||||
|
||||
def rm_rf(path: Path):
|
||||
vprint(f"rm -rf {path}")
|
||||
if sys.version_info >= (3, 12):
|
||||
shutil.rmtree(path, ignore_errors=False, onexc=rm_on_error)
|
||||
else:
|
||||
shutil.rmtree(path, ignore_errors=False, onerror=rm_on_error)
|
||||
|
||||
|
||||
def execv(cmd, env=None):
|
||||
return subprocess.run(cmd, stdout=STDOUT, env=env)
|
||||
def execv(cmds: list, env=None):
|
||||
out = None if args.force_out or args.verbose > 0 else subprocess.DEVNULL
|
||||
# Use shell on Windows to support PATHEXT
|
||||
return subprocess.run(cmds, stdout=out, env=env, shell=is_windows)
|
||||
|
||||
|
||||
def system(cmd):
|
||||
return subprocess.run(cmd, shell=True, stdout=STDOUT)
|
||||
|
||||
|
||||
def cmd_out(cmd, env=None):
|
||||
def cmd_out(cmds: list):
|
||||
return (
|
||||
subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, env=env)
|
||||
subprocess.run(
|
||||
cmds,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.DEVNULL,
|
||||
shell=is_windows,
|
||||
)
|
||||
.stdout.strip()
|
||||
.decode("utf-8")
|
||||
)
|
||||
@@ -160,51 +166,9 @@ def xz(data):
|
||||
return lzma.compress(data, preset=9, check=lzma.CHECK_NONE)
|
||||
|
||||
|
||||
def parse_props(file):
|
||||
props = {}
|
||||
with open(file, "r") as f:
|
||||
for line in [l.strip(" \t\r\n") for l in f]:
|
||||
if line.startswith("#") or len(line) == 0:
|
||||
continue
|
||||
prop = line.split("=")
|
||||
if len(prop) != 2:
|
||||
continue
|
||||
value = prop[1].strip(" \t\r\n")
|
||||
if len(value) == 0:
|
||||
continue
|
||||
props[prop[0].strip(" \t\r\n")] = value
|
||||
return props
|
||||
|
||||
|
||||
def load_config(args):
|
||||
commit_hash = cmd_out(["git", "rev-parse", "--short=8", "HEAD"])
|
||||
|
||||
# Default values
|
||||
config["version"] = commit_hash
|
||||
config["versionCode"] = 1000000
|
||||
config["outdir"] = "out"
|
||||
|
||||
args.config = Path(args.config)
|
||||
|
||||
# Load prop files
|
||||
if args.config.exists():
|
||||
config.update(parse_props(args.config))
|
||||
|
||||
if Path("gradle.properties").exists():
|
||||
for key, value in parse_props("gradle.properties").items():
|
||||
if key.startswith("magisk."):
|
||||
config[key[7:]] = value
|
||||
|
||||
try:
|
||||
config["versionCode"] = int(config["versionCode"])
|
||||
except ValueError:
|
||||
error('Config error: "versionCode" is required to be an integer')
|
||||
|
||||
config["outdir"] = Path(config["outdir"])
|
||||
|
||||
config["outdir"].mkdir(mode=0o755, parents=True, exist_ok=True)
|
||||
global STDOUT
|
||||
STDOUT = None if args.verbose > 0 else subprocess.DEVNULL
|
||||
###############
|
||||
# Build Native
|
||||
###############
|
||||
|
||||
|
||||
def clean_elf():
|
||||
@@ -225,26 +189,28 @@ def clean_elf():
|
||||
elf_cleaner,
|
||||
]
|
||||
)
|
||||
args = [elf_cleaner, "--api-level", "23"]
|
||||
args.extend(
|
||||
Path("native", "out", arch, bin)
|
||||
for arch in archs
|
||||
for bin in ["magisk", "magiskpolicy"]
|
||||
)
|
||||
execv(args)
|
||||
cmds = [elf_cleaner, "--api-level", "23"]
|
||||
cmds.extend(glob.glob("native/out/*/magisk"))
|
||||
cmds.extend(glob.glob("native/out/*/magiskpolicy"))
|
||||
execv(cmds)
|
||||
|
||||
|
||||
def run_ndk_build(args, flags):
|
||||
def run_ndk_build(cmds: list):
|
||||
os.chdir("native")
|
||||
flags = "NDK_PROJECT_PATH=. NDK_APPLICATION_MK=src/Application.mk " + flags
|
||||
cmds.append("NDK_PROJECT_PATH=.")
|
||||
cmds.append("NDK_APPLICATION_MK=src/Application.mk")
|
||||
cmds.append(f"APP_ABI={' '.join(build_abis.keys())}")
|
||||
cmds.append(f"-j{cpu_count}")
|
||||
if args.verbose > 1:
|
||||
flags = "V=1 " + flags
|
||||
proc = system(f"{ndk_build} {flags} -j{cpu_count}")
|
||||
cmds.append("V=1")
|
||||
if not args.release:
|
||||
cmds.append("MAGISK_DEBUG=1")
|
||||
proc = execv([ndk_build, *cmds])
|
||||
if proc.returncode != 0:
|
||||
error("Build binary failed!")
|
||||
os.chdir("..")
|
||||
|
||||
for arch in archs:
|
||||
for arch in build_abis.keys():
|
||||
arch_dir = Path("native", "libs", arch)
|
||||
out_dir = Path("native", "out", arch)
|
||||
for source in arch_dir.iterdir():
|
||||
@@ -252,40 +218,38 @@ def run_ndk_build(args, flags):
|
||||
mv(source, target)
|
||||
|
||||
|
||||
def build_cpp_src(args, targets: set):
|
||||
dump_flag_header()
|
||||
|
||||
flags = ""
|
||||
def build_cpp_src(targets: set):
|
||||
cmds = []
|
||||
clean = False
|
||||
|
||||
if "magisk" in targets:
|
||||
flags += " B_MAGISK=1"
|
||||
cmds.append("B_MAGISK=1")
|
||||
clean = True
|
||||
|
||||
if "magiskpolicy" in targets:
|
||||
flags += " B_POLICY=1"
|
||||
cmds.append("B_POLICY=1")
|
||||
clean = True
|
||||
|
||||
if "magiskinit" in targets:
|
||||
flags += " B_PRELOAD=1"
|
||||
cmds.append("B_PRELOAD=1")
|
||||
|
||||
if "resetprop" in targets:
|
||||
flags += " B_PROP=1"
|
||||
cmds.append("B_PROP=1")
|
||||
|
||||
if flags:
|
||||
run_ndk_build(args, flags)
|
||||
if cmds:
|
||||
run_ndk_build(cmds)
|
||||
|
||||
flags = ""
|
||||
cmds.clear()
|
||||
|
||||
if "magiskinit" in targets:
|
||||
flags += " B_INIT=1"
|
||||
cmds.append("B_INIT=1")
|
||||
|
||||
if "magiskboot" in targets:
|
||||
flags += " B_BOOT=1"
|
||||
cmds.append("B_BOOT=1")
|
||||
|
||||
if flags:
|
||||
flags += " B_CRT0=1"
|
||||
run_ndk_build(args, flags)
|
||||
if cmds:
|
||||
cmds.append("B_CRT0=1")
|
||||
run_ndk_build(cmds)
|
||||
|
||||
if clean:
|
||||
clean_elf()
|
||||
@@ -295,11 +259,11 @@ def run_cargo(cmds):
|
||||
env = os.environ.copy()
|
||||
env["PATH"] = f'{rust_bin}{os.pathsep}{env["PATH"]}'
|
||||
env["CARGO_BUILD_RUSTC"] = str(rust_bin / f"rustc{EXE_EXT}")
|
||||
env["RUSTFLAGS"] = f"-Clinker-plugin-lto -Zthreads={min(8, cpu_count)}"
|
||||
env["CARGO_BUILD_RUSTFLAGS"] = f"-Z threads={min(8, cpu_count)}"
|
||||
return execv([cargo, *cmds], env)
|
||||
|
||||
|
||||
def build_rust_src(args, targets: set):
|
||||
def build_rust_src(targets: set):
|
||||
targets = targets.copy()
|
||||
if "resetprop" in targets:
|
||||
targets.add("magisk")
|
||||
@@ -309,28 +273,21 @@ def build_rust_src(args, targets: set):
|
||||
|
||||
os.chdir(Path("native", "src"))
|
||||
|
||||
native_out = Path("..", "out")
|
||||
native_out.mkdir(mode=0o755, exist_ok=True)
|
||||
|
||||
# Start building the actual build commands
|
||||
# Start building the build commands
|
||||
cmds = ["build", "-p", ""]
|
||||
rust_out = "debug"
|
||||
if args.release:
|
||||
cmds.append("-r")
|
||||
rust_out = "release"
|
||||
profile = "release"
|
||||
else:
|
||||
profile = "debug"
|
||||
if args.verbose == 0:
|
||||
cmds.append("-q")
|
||||
elif args.verbose > 1:
|
||||
cmds.append("--verbose")
|
||||
|
||||
for triple in build_abis.values():
|
||||
cmds.append("--target")
|
||||
cmds.append("")
|
||||
|
||||
for arch, triple in zip(archs, triples):
|
||||
rust_triple = (
|
||||
"thumbv7neon-linux-androideabi" if triple.startswith("armv7") else triple
|
||||
)
|
||||
cmds[-1] = rust_triple
|
||||
cmds.append(triple)
|
||||
|
||||
for tgt in targets:
|
||||
cmds[2] = tgt
|
||||
@@ -338,25 +295,18 @@ def build_rust_src(args, targets: set):
|
||||
if proc.returncode != 0:
|
||||
error("Build binary failed!")
|
||||
|
||||
os.chdir(Path("..", ".."))
|
||||
|
||||
native_out = Path("native", "out")
|
||||
rust_out = native_out / "rust"
|
||||
for arch, triple in build_abis.items():
|
||||
arch_out = native_out / arch
|
||||
arch_out.mkdir(mode=0o755, exist_ok=True)
|
||||
for tgt in targets:
|
||||
source = Path("target", rust_triple, rust_out, f"lib{tgt}.a")
|
||||
source = rust_out / triple / profile / f"lib{tgt}.a"
|
||||
target = arch_out / f"lib{tgt}-rs.a"
|
||||
mv(source, target)
|
||||
|
||||
os.chdir(Path("..", ".."))
|
||||
|
||||
|
||||
def run_cargo_cmd(args):
|
||||
global STDOUT
|
||||
STDOUT = None
|
||||
if len(args.commands) >= 1 and args.commands[0] == "--":
|
||||
args.commands = args.commands[1:]
|
||||
os.chdir(Path("native", "src"))
|
||||
run_cargo(args.commands)
|
||||
os.chdir(Path("..", ".."))
|
||||
|
||||
|
||||
def write_if_diff(file_name: Path, text: str):
|
||||
do_write = True
|
||||
@@ -384,10 +334,14 @@ def dump_flag_header():
|
||||
flag_txt += f"#define MAGISK_DEBUG {0 if args.release else 1}\n"
|
||||
|
||||
native_gen_path.mkdir(mode=0o755, parents=True, exist_ok=True)
|
||||
write_if_diff(Path(native_gen_path, "flags.h"), flag_txt)
|
||||
write_if_diff(native_gen_path / "flags.h", flag_txt)
|
||||
|
||||
rust_flag_txt = f'pub const MAGISK_VERSION: &str = "{config["version"]}";\n'
|
||||
rust_flag_txt += f'pub const MAGISK_VER_CODE: i32 = {config["versionCode"]};\n'
|
||||
write_if_diff(native_gen_path / "flags.rs", rust_flag_txt)
|
||||
|
||||
|
||||
def build_binary(args):
|
||||
def build_native():
|
||||
# Verify NDK install
|
||||
try:
|
||||
with open(Path(ndk_path, "ONDK_VERSION"), "r") as ondk_ver:
|
||||
@@ -402,10 +356,23 @@ def build_binary(args):
|
||||
if not targets:
|
||||
return
|
||||
|
||||
header("* Building binaries: " + " ".join(targets))
|
||||
header("* Building: " + " ".join(targets))
|
||||
|
||||
build_rust_src(args, targets)
|
||||
build_cpp_src(args, targets)
|
||||
if sccache := shutil.which("sccache"):
|
||||
os.environ["RUSTC_WRAPPER"] = sccache
|
||||
os.environ["NDK_CCACHE"] = sccache
|
||||
os.environ["CARGO_INCREMENTAL"] = "0"
|
||||
if ccache := shutil.which("ccache"):
|
||||
os.environ["NDK_CCACHE"] = ccache
|
||||
|
||||
dump_flag_header()
|
||||
build_rust_src(targets)
|
||||
build_cpp_src(targets)
|
||||
|
||||
|
||||
############
|
||||
# Build App
|
||||
############
|
||||
|
||||
|
||||
def find_jdk():
|
||||
@@ -440,7 +407,7 @@ def find_jdk():
|
||||
return env
|
||||
|
||||
|
||||
def build_apk(args, module):
|
||||
def build_apk(module: str):
|
||||
env = find_jdk()
|
||||
|
||||
build_type = "Release" if args.release else "Debug"
|
||||
@@ -463,19 +430,20 @@ def build_apk(args, module):
|
||||
source = Path(*paths, "build", "outputs", "apk", build_type, apk)
|
||||
target = config["outdir"] / apk
|
||||
mv(source, target)
|
||||
header(f"Output: {target}")
|
||||
return target
|
||||
|
||||
|
||||
def build_app(args):
|
||||
def build_app():
|
||||
header("* Building the Magisk app")
|
||||
build_apk(args, ":app:apk")
|
||||
apk = build_apk(":app:apk")
|
||||
|
||||
build_type = "release" if args.release else "debug"
|
||||
|
||||
# Rename apk-variant.apk to app-variant.apk
|
||||
source = config["outdir"] / f"apk-{build_type}.apk"
|
||||
target = config["outdir"] / f"app-{build_type}.apk"
|
||||
source = apk
|
||||
target = apk.parent / apk.name.replace("apk-", "app-")
|
||||
mv(source, target)
|
||||
header(f"Output: {target}")
|
||||
|
||||
# Stub building is directly integrated into the main app
|
||||
# build process. Copy the stub APK into output directory.
|
||||
@@ -484,13 +452,34 @@ def build_app(args):
|
||||
cp(source, target)
|
||||
|
||||
|
||||
def build_stub(args):
|
||||
def build_stub():
|
||||
header("* Building the stub app")
|
||||
build_apk(args, ":app:stub")
|
||||
apk = build_apk(":app:stub")
|
||||
header(f"Output: {apk}")
|
||||
|
||||
|
||||
def cleanup(args):
|
||||
support_targets = {"native", "cpp", "rust", "java"}
|
||||
def build_test():
|
||||
global args
|
||||
args_bak = copy.copy(args)
|
||||
# Test APK has to be built as release to prevent classname clash
|
||||
args.release = True
|
||||
try:
|
||||
header("* Building the test app")
|
||||
source = build_apk(":app:test")
|
||||
target = source.parent / "test.apk"
|
||||
mv(source, target)
|
||||
header(f"Output: {target}")
|
||||
finally:
|
||||
args = args_bak
|
||||
|
||||
|
||||
################
|
||||
# Build General
|
||||
################
|
||||
|
||||
|
||||
def cleanup():
|
||||
support_targets = {"native", "cpp", "rust", "app"}
|
||||
if args.targets:
|
||||
targets = set(args.targets) & support_targets
|
||||
if "native" in targets:
|
||||
@@ -503,7 +492,6 @@ def cleanup(args):
|
||||
header("* Cleaning C++")
|
||||
rm_rf(Path("native", "libs"))
|
||||
rm_rf(Path("native", "obj"))
|
||||
rm_rf(Path("native", "out"))
|
||||
|
||||
if "rust" in targets:
|
||||
header("* Cleaning Rust")
|
||||
@@ -513,12 +501,35 @@ def cleanup(args):
|
||||
for rs_gen in glob.glob("native/**/*-rs.*pp", recursive=True):
|
||||
rm(rs_gen)
|
||||
|
||||
if "java" in targets:
|
||||
header("* Cleaning java")
|
||||
if "native" in targets:
|
||||
rm_rf(Path("native", "out"))
|
||||
|
||||
if "app" in targets:
|
||||
header("* Cleaning app")
|
||||
execv([gradlew, ":app:clean"], env=find_jdk())
|
||||
|
||||
|
||||
def setup_ndk(args):
|
||||
def build_all():
|
||||
build_native()
|
||||
build_app()
|
||||
build_test()
|
||||
|
||||
|
||||
############
|
||||
# Utilities
|
||||
############
|
||||
|
||||
|
||||
def cargo_cli():
|
||||
args.force_out = True
|
||||
if len(args.commands) >= 1 and args.commands[0] == "--":
|
||||
args.commands = args.commands[1:]
|
||||
os.chdir(Path("native", "src"))
|
||||
run_cargo(args.commands)
|
||||
os.chdir(Path("..", ".."))
|
||||
|
||||
|
||||
def setup_ndk():
|
||||
ndk_ver = config["ondkVersion"]
|
||||
url = f"https://github.com/topjohnwu/ondk/releases/download/{ndk_ver}/ondk-{ndk_ver}-{os_name}.tar.xz"
|
||||
ndk_archive = url.split("/")[-1]
|
||||
@@ -537,7 +548,7 @@ def setup_ndk(args):
|
||||
mv(ondk_path, ndk_path)
|
||||
|
||||
|
||||
def push_files(args, script):
|
||||
def push_files(script):
|
||||
abi = cmd_out([adb_path, "shell", "getprop", "ro.product.cpu.abi"])
|
||||
if not abi:
|
||||
error("Cannot detect emulator ABI")
|
||||
@@ -565,22 +576,22 @@ def push_files(args, script):
|
||||
error("adb push failed!")
|
||||
|
||||
|
||||
def setup_avd(args):
|
||||
def setup_avd():
|
||||
if not args.skip:
|
||||
build_all(args)
|
||||
build_all()
|
||||
|
||||
header("* Setting up emulator")
|
||||
|
||||
push_files(args, Path("scripts", "avd_magisk.sh"))
|
||||
push_files(Path("scripts", "avd_magisk.sh"))
|
||||
|
||||
proc = execv([adb_path, "shell", "sh", "/data/local/tmp/avd_magisk.sh"])
|
||||
if proc.returncode != 0:
|
||||
error("avd_magisk.sh failed!")
|
||||
|
||||
|
||||
def patch_avd_file(args):
|
||||
def patch_avd_file():
|
||||
if not args.skip:
|
||||
build_all(args)
|
||||
build_all()
|
||||
|
||||
input = Path(args.image)
|
||||
if args.output:
|
||||
@@ -593,7 +604,7 @@ def patch_avd_file(args):
|
||||
|
||||
header(f"* Patching {input.name}")
|
||||
|
||||
push_files(args, Path("scripts", "avd_patch.sh"))
|
||||
push_files(Path("scripts", "avd_patch.sh"))
|
||||
|
||||
proc = execv([adb_path, "push", input, "/data/local/tmp"])
|
||||
if proc.returncode != 0:
|
||||
@@ -610,12 +621,7 @@ def patch_avd_file(args):
|
||||
header(f"Output: {output}")
|
||||
|
||||
|
||||
def build_all(args):
|
||||
build_binary(args)
|
||||
build_app(args)
|
||||
|
||||
|
||||
def setup_rustup(args):
|
||||
def setup_rustup():
|
||||
wrapper_dir = Path(args.wrapper_dir)
|
||||
rm_rf(wrapper_dir)
|
||||
wrapper_dir.mkdir(mode=0o755, parents=True, exist_ok=True)
|
||||
@@ -631,10 +637,10 @@ def setup_rustup(args):
|
||||
# Build rustup_wrapper
|
||||
wrapper_src = Path("tools", "rustup_wrapper")
|
||||
cargo_toml = wrapper_src / "Cargo.toml"
|
||||
execv(
|
||||
[cargo, "build", "--release", f"--manifest-path={cargo_toml}"]
|
||||
+ (["--verbose"] if args.verbose > 1 else [])
|
||||
)
|
||||
cmds = ["build", "--release", f"--manifest-path={cargo_toml}"]
|
||||
if args.verbose > 1:
|
||||
cmds.append("--verbose")
|
||||
run_cargo(cmds)
|
||||
|
||||
# Replace rustup with wrapper
|
||||
wrapper = wrapper_dir / (f"rustup{EXE_EXT}")
|
||||
@@ -643,77 +649,152 @@ def setup_rustup(args):
|
||||
wrapper.chmod(0o755)
|
||||
|
||||
|
||||
parser = argparse.ArgumentParser(description="Magisk build script")
|
||||
parser.set_defaults(func=lambda x: None)
|
||||
parser.add_argument(
|
||||
##################
|
||||
# Config and args
|
||||
##################
|
||||
|
||||
|
||||
def parse_props(file):
|
||||
props = {}
|
||||
with open(file, "r") as f:
|
||||
for line in [l.strip(" \t\r\n") for l in f]:
|
||||
if line.startswith("#") or len(line) == 0:
|
||||
continue
|
||||
prop = line.split("=")
|
||||
if len(prop) != 2:
|
||||
continue
|
||||
key = prop[0].strip(" \t\r\n")
|
||||
value = prop[1].strip(" \t\r\n")
|
||||
if not key or not value:
|
||||
continue
|
||||
props[key] = value
|
||||
return props
|
||||
|
||||
|
||||
def load_config():
|
||||
commit_hash = cmd_out(["git", "rev-parse", "--short=8", "HEAD"])
|
||||
|
||||
# Default values
|
||||
config["version"] = commit_hash
|
||||
config["versionCode"] = 1000000
|
||||
config["outdir"] = "out"
|
||||
|
||||
args.config = Path(args.config)
|
||||
|
||||
# Load prop files
|
||||
if args.config.exists():
|
||||
config.update(parse_props(args.config))
|
||||
|
||||
if Path("gradle.properties").exists():
|
||||
for key, value in parse_props("gradle.properties").items():
|
||||
if key.startswith("magisk."):
|
||||
config[key[7:]] = value
|
||||
|
||||
try:
|
||||
config["versionCode"] = int(config["versionCode"])
|
||||
except ValueError:
|
||||
error('Config error: "versionCode" is required to be an integer')
|
||||
|
||||
config["outdir"] = Path(config["outdir"])
|
||||
config["outdir"].mkdir(mode=0o755, parents=True, exist_ok=True)
|
||||
|
||||
if "abiList" in config:
|
||||
abiList = re.split("\\s*,\\s*", config["abiList"])
|
||||
archs = set(abiList) & support_abis.keys()
|
||||
else:
|
||||
archs = {"armeabi-v7a", "x86", "arm64-v8a", "x86_64"}
|
||||
|
||||
triples = map(support_abis.get, archs)
|
||||
|
||||
global build_abis
|
||||
build_abis = dict(zip(archs, triples))
|
||||
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser(description="Magisk build script")
|
||||
parser.set_defaults(func=lambda x: None)
|
||||
parser.add_argument(
|
||||
"-r", "--release", action="store_true", help="compile in release mode"
|
||||
)
|
||||
parser.add_argument("-v", "--verbose", action="count", default=0, help="verbose output")
|
||||
parser.add_argument(
|
||||
)
|
||||
parser.add_argument(
|
||||
"-v", "--verbose", action="count", default=0, help="verbose output"
|
||||
)
|
||||
parser.add_argument(
|
||||
"-c",
|
||||
"--config",
|
||||
default="config.prop",
|
||||
help="custom config file (default: config.prop)",
|
||||
)
|
||||
subparsers = parser.add_subparsers(title="actions")
|
||||
)
|
||||
subparsers = parser.add_subparsers(title="actions")
|
||||
|
||||
all_parser = subparsers.add_parser("all", help="build everything")
|
||||
all_parser.set_defaults(func=build_all)
|
||||
all_parser = subparsers.add_parser("all", help="build everything")
|
||||
|
||||
binary_parser = subparsers.add_parser("binary", help="build binaries")
|
||||
binary_parser.add_argument(
|
||||
native_parser = subparsers.add_parser("native", help="build native binaries")
|
||||
native_parser.add_argument(
|
||||
"targets",
|
||||
nargs="*",
|
||||
help=f"{', '.join(support_targets)}, \
|
||||
or empty for defaults ({', '.join(default_targets)})",
|
||||
)
|
||||
binary_parser.set_defaults(func=build_binary)
|
||||
)
|
||||
|
||||
cargo_parser = subparsers.add_parser("cargo", help="run cargo with proper environment")
|
||||
cargo_parser.add_argument("commands", nargs=argparse.REMAINDER)
|
||||
cargo_parser.set_defaults(func=run_cargo_cmd)
|
||||
app_parser = subparsers.add_parser("app", help="build the Magisk app")
|
||||
|
||||
rustup_parser = subparsers.add_parser("rustup", help="setup rustup wrapper")
|
||||
rustup_parser.add_argument("wrapper_dir", help="path to setup rustup wrapper binaries")
|
||||
rustup_parser.set_defaults(func=setup_rustup)
|
||||
stub_parser = subparsers.add_parser("stub", help="build the stub app")
|
||||
|
||||
app_parser = subparsers.add_parser("app", help="build the Magisk app")
|
||||
app_parser.set_defaults(func=build_app)
|
||||
test_parser = subparsers.add_parser("test", help="build the test app")
|
||||
|
||||
stub_parser = subparsers.add_parser("stub", help="build the stub app")
|
||||
stub_parser.set_defaults(func=build_stub)
|
||||
|
||||
avd_parser = subparsers.add_parser("emulator", help="setup AVD for development")
|
||||
avd_parser.add_argument(
|
||||
"-s", "--skip", action="store_true", help="skip building binaries and the app"
|
||||
)
|
||||
avd_parser.set_defaults(func=setup_avd)
|
||||
|
||||
avd_patch_parser = subparsers.add_parser(
|
||||
"avd_patch", help="patch AVD ramdisk.img or init_boot.img"
|
||||
)
|
||||
avd_patch_parser.add_argument("image", help="path to ramdisk.img or init_boot.img")
|
||||
avd_patch_parser.add_argument("output", help="optional output file name", nargs="?")
|
||||
avd_patch_parser.add_argument(
|
||||
"-s", "--skip", action="store_true", help="skip building binaries and the app"
|
||||
)
|
||||
avd_patch_parser.set_defaults(func=patch_avd_file)
|
||||
|
||||
clean_parser = subparsers.add_parser("clean", help="cleanup")
|
||||
clean_parser.add_argument(
|
||||
clean_parser = subparsers.add_parser("clean", help="cleanup")
|
||||
clean_parser.add_argument(
|
||||
"targets", nargs="*", help="native, cpp, rust, java, or empty to clean all"
|
||||
)
|
||||
clean_parser.set_defaults(func=cleanup)
|
||||
)
|
||||
|
||||
ndk_parser = subparsers.add_parser("ndk", help="setup Magisk NDK")
|
||||
ndk_parser.set_defaults(func=setup_ndk)
|
||||
ndk_parser = subparsers.add_parser("ndk", help="setup Magisk NDK")
|
||||
|
||||
if len(sys.argv) == 1:
|
||||
emu_parser = subparsers.add_parser("emulator", help="setup AVD for development")
|
||||
emu_parser.add_argument(
|
||||
"-s", "--skip", action="store_true", help="skip building binaries and the app"
|
||||
)
|
||||
|
||||
avd_patch_parser = subparsers.add_parser(
|
||||
"avd_patch", help="patch AVD ramdisk.img or init_boot.img"
|
||||
)
|
||||
avd_patch_parser.add_argument("image", help="path to ramdisk.img or init_boot.img")
|
||||
avd_patch_parser.add_argument("output", help="optional output file name", nargs="?")
|
||||
avd_patch_parser.add_argument(
|
||||
"-s", "--skip", action="store_true", help="skip building binaries and the app"
|
||||
)
|
||||
|
||||
cargo_parser = subparsers.add_parser(
|
||||
"cargo", help="call 'cargo' commands against the project"
|
||||
)
|
||||
cargo_parser.add_argument("commands", nargs=argparse.REMAINDER)
|
||||
|
||||
rustup_parser = subparsers.add_parser("rustup", help="setup rustup wrapper")
|
||||
rustup_parser.add_argument(
|
||||
"wrapper_dir", help="path to setup rustup wrapper binaries"
|
||||
)
|
||||
|
||||
# Set callbacks
|
||||
all_parser.set_defaults(func=build_all)
|
||||
native_parser.set_defaults(func=build_native)
|
||||
cargo_parser.set_defaults(func=cargo_cli)
|
||||
rustup_parser.set_defaults(func=setup_rustup)
|
||||
app_parser.set_defaults(func=build_app)
|
||||
stub_parser.set_defaults(func=build_stub)
|
||||
test_parser.set_defaults(func=build_test)
|
||||
emu_parser.set_defaults(func=setup_avd)
|
||||
avd_patch_parser.set_defaults(func=patch_avd_file)
|
||||
clean_parser.set_defaults(func=cleanup)
|
||||
ndk_parser.set_defaults(func=setup_ndk)
|
||||
|
||||
if len(sys.argv) == 1:
|
||||
parser.print_help()
|
||||
sys.exit(1)
|
||||
|
||||
args = parser.parse_args()
|
||||
load_config(args)
|
||||
return parser.parse_args()
|
||||
|
||||
# Call corresponding functions
|
||||
args.func(args)
|
||||
|
||||
args = parse_args()
|
||||
load_config()
|
||||
vars(args)["force_out"] = False
|
||||
args.func()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||
import org.jetbrains.kotlin.gradle.dsl.KotlinVersion
|
||||
|
||||
plugins {
|
||||
`kotlin-dsl`
|
||||
@@ -18,17 +18,17 @@ gradlePlugin {
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType<KotlinCompile>().configureEach {
|
||||
kotlinOptions {
|
||||
languageVersion = "2.0"
|
||||
kotlin {
|
||||
compilerOptions {
|
||||
languageVersion = KotlinVersion.KOTLIN_2_0
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(kotlin("gradle-plugin", "2.0.0"))
|
||||
implementation("com.android.tools.build:gradle:8.5.1")
|
||||
implementation("com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin:2.0.0-1.0.23")
|
||||
implementation("androidx.navigation:navigation-safe-args-gradle-plugin:2.7.7")
|
||||
implementation("org.lsposed.lsparanoid:gradle-plugin:0.6.0")
|
||||
implementation("org.eclipse.jgit:org.eclipse.jgit:6.10.0.202406032230-r")
|
||||
implementation(kotlin("gradle-plugin", libs.versions.kotlin.get()))
|
||||
implementation(libs.android.gradle.plugin)
|
||||
implementation(libs.ksp.plugin)
|
||||
implementation(libs.navigation.safe.args.plugin)
|
||||
implementation(libs.lsparanoid.plugin)
|
||||
implementation(libs.jgit)
|
||||
}
|
||||
|
||||
7
buildSrc/settings.gradle.kts
Normal file
7
buildSrc/settings.gradle.kts
Normal file
@@ -0,0 +1,7 @@
|
||||
dependencyResolutionManagement {
|
||||
versionCatalogs {
|
||||
create("libs") {
|
||||
from(files("../gradle/libs.versions.toml"))
|
||||
}
|
||||
}
|
||||
}
|
||||
122
buildSrc/src/main/java/DesugarClassVisitorFactory.kt
Normal file
122
buildSrc/src/main/java/DesugarClassVisitorFactory.kt
Normal file
@@ -0,0 +1,122 @@
|
||||
import com.android.build.api.instrumentation.AsmClassVisitorFactory
|
||||
import com.android.build.api.instrumentation.ClassContext
|
||||
import com.android.build.api.instrumentation.ClassData
|
||||
import com.android.build.api.instrumentation.InstrumentationParameters
|
||||
import org.objectweb.asm.ClassVisitor
|
||||
import org.objectweb.asm.MethodVisitor
|
||||
import org.objectweb.asm.Opcodes
|
||||
import org.objectweb.asm.Opcodes.ASM9
|
||||
|
||||
private const val DESUGAR_CLASS_NAME = "com.topjohnwu.magisk.core.utils.Desugar"
|
||||
private const val ZIP_ENTRY_CLASS_NAME = "java.util.zip.ZipEntry"
|
||||
private const val ZIP_OUT_STREAM_CLASS_NAME = "org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream"
|
||||
private const val ZIP_UTIL_CLASS_NAME = "org/apache/commons/compress/archivers/zip/ZipUtil"
|
||||
private const val ZIP_ENTRY_GET_TIME_DESC = "()Ljava/nio/file/attribute/FileTime;"
|
||||
private const val DESUGAR_GET_TIME_DESC =
|
||||
"(Ljava/util/zip/ZipEntry;)Ljava/nio/file/attribute/FileTime;"
|
||||
|
||||
private fun ClassData.isTypeOf(name: String) = className == name || superClasses.contains(name)
|
||||
|
||||
abstract class DesugarClassVisitorFactory : AsmClassVisitorFactory<InstrumentationParameters.None> {
|
||||
override fun createClassVisitor(
|
||||
classContext: ClassContext,
|
||||
nextClassVisitor: ClassVisitor
|
||||
): ClassVisitor {
|
||||
return if (classContext.currentClassData.className == ZIP_OUT_STREAM_CLASS_NAME) {
|
||||
ZipEntryPatcher(classContext, ZipOutputStreamPatcher(nextClassVisitor))
|
||||
} else {
|
||||
ZipEntryPatcher(classContext, nextClassVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
override fun isInstrumentable(classData: ClassData) = classData.className != DESUGAR_CLASS_NAME
|
||||
|
||||
// Patch ALL references to ZipEntry#getXXXTime
|
||||
class ZipEntryPatcher(
|
||||
private val classContext: ClassContext,
|
||||
cv: ClassVisitor
|
||||
) : ClassVisitor(ASM9, cv) {
|
||||
override fun visitMethod(
|
||||
access: Int,
|
||||
name: String?,
|
||||
descriptor: String?,
|
||||
signature: String?,
|
||||
exceptions: Array<out String>?
|
||||
) = MethodPatcher(super.visitMethod(access, name, descriptor, signature, exceptions))
|
||||
|
||||
inner class MethodPatcher(mv: MethodVisitor?) : MethodVisitor(ASM9, mv) {
|
||||
override fun visitMethodInsn(
|
||||
opcode: Int,
|
||||
owner: String,
|
||||
name: String,
|
||||
descriptor: String,
|
||||
isInterface: Boolean
|
||||
) {
|
||||
if (!process(owner, name, descriptor)) {
|
||||
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface)
|
||||
}
|
||||
}
|
||||
|
||||
private fun process(owner: String, name: String, descriptor: String): Boolean {
|
||||
val classData = classContext.loadClassData(owner.replace("/", ".")) ?: return false
|
||||
if (!classData.isTypeOf(ZIP_ENTRY_CLASS_NAME))
|
||||
return false
|
||||
if (descriptor != ZIP_ENTRY_GET_TIME_DESC)
|
||||
return false
|
||||
return when (name) {
|
||||
"getLastModifiedTime", "getLastAccessTime", "getCreationTime" -> {
|
||||
mv.visitMethodInsn(
|
||||
Opcodes.INVOKESTATIC,
|
||||
DESUGAR_CLASS_NAME.replace('.', '/'),
|
||||
name,
|
||||
DESUGAR_GET_TIME_DESC,
|
||||
false
|
||||
)
|
||||
true
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Patch ZipArchiveOutputStream#copyFromZipInputStream
|
||||
class ZipOutputStreamPatcher(cv: ClassVisitor) : ClassVisitor(ASM9, cv) {
|
||||
override fun visitMethod(
|
||||
access: Int,
|
||||
name: String,
|
||||
descriptor: String,
|
||||
signature: String?,
|
||||
exceptions: Array<out String?>?
|
||||
): MethodVisitor? {
|
||||
return if (name == "copyFromZipInputStream") {
|
||||
MethodPatcher(super.visitMethod(access, name, descriptor, signature, exceptions))
|
||||
} else {
|
||||
super.visitMethod(access, name, descriptor, signature, exceptions)
|
||||
}
|
||||
}
|
||||
|
||||
class MethodPatcher(mv: MethodVisitor?) : MethodVisitor(ASM9, mv) {
|
||||
override fun visitMethodInsn(
|
||||
opcode: Int,
|
||||
owner: String,
|
||||
name: String,
|
||||
descriptor: String?,
|
||||
isInterface: Boolean
|
||||
) {
|
||||
if (owner == ZIP_UTIL_CLASS_NAME && name == "checkRequestedFeatures") {
|
||||
// Redirect
|
||||
mv.visitMethodInsn(
|
||||
Opcodes.INVOKESTATIC,
|
||||
DESUGAR_CLASS_NAME.replace('.', '/'),
|
||||
name,
|
||||
descriptor,
|
||||
false
|
||||
)
|
||||
} else {
|
||||
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,8 @@ import java.util.Properties
|
||||
|
||||
private val props = Properties()
|
||||
private var commitHash = ""
|
||||
private val supportAbis = setOf("armeabi-v7a", "x86", "arm64-v8a", "x86_64", "riscv64")
|
||||
private val defaultAbis = setOf("armeabi-v7a", "x86", "arm64-v8a", "x86_64")
|
||||
|
||||
object Config {
|
||||
operator fun get(key: String): String? {
|
||||
@@ -20,6 +22,10 @@ object Config {
|
||||
val version: String get() = get("version") ?: commitHash
|
||||
val versionCode: Int get() = get("magisk.versionCode")!!.toInt()
|
||||
val stubVersion: String get() = get("magisk.stubVersion")!!
|
||||
val abiList: Set<String> get() {
|
||||
val abiList = get("abiList") ?: return defaultAbis
|
||||
return abiList.split(Regex("\\s*,\\s*")).toSet() intersect supportAbis
|
||||
}
|
||||
}
|
||||
|
||||
class MagiskPlugin : Plugin<Project> {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import com.android.build.api.artifact.ArtifactTransformationRequest
|
||||
import com.android.build.api.artifact.SingleArtifact
|
||||
import com.android.build.api.dsl.ApkSigningConfig
|
||||
import com.android.build.api.instrumentation.FramesComputationMode.COMPUTE_FRAMES_FOR_INSTRUMENTED_METHODS
|
||||
import com.android.build.api.instrumentation.InstrumentationScope
|
||||
import com.android.build.api.variant.ApplicationAndroidComponentsExtension
|
||||
import com.android.build.gradle.BaseExtension
|
||||
import com.android.build.gradle.LibraryExtension
|
||||
@@ -69,18 +71,18 @@ private val Project.androidComponents
|
||||
|
||||
fun Project.setupCommon() {
|
||||
androidBase {
|
||||
compileSdkVersion(34)
|
||||
buildToolsVersion = "34.0.0"
|
||||
compileSdkVersion(35)
|
||||
buildToolsVersion = "35.0.1"
|
||||
ndkPath = "$sdkDirectory/ndk/magisk"
|
||||
ndkVersion = "27.0.11902837"
|
||||
ndkVersion = "28.0.12674087"
|
||||
|
||||
defaultConfig {
|
||||
minSdk = 23
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
sourceCompatibility = JavaVersion.VERSION_21
|
||||
targetCompatibility = JavaVersion.VERSION_21
|
||||
}
|
||||
|
||||
packagingOptions {
|
||||
@@ -108,22 +110,24 @@ fun Project.setupCommon() {
|
||||
|
||||
tasks.withType<KotlinCompile> {
|
||||
compilerOptions {
|
||||
jvmTarget = JvmTarget.JVM_17
|
||||
jvmTarget = JvmTarget.JVM_21
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const val BUSYBOX_DOWNLOAD_URL =
|
||||
"https://github.com/topjohnwu/magisk-files/releases/download/files/busybox-1.36.1.0.zip"
|
||||
"https://github.com/topjohnwu/magisk-files/releases/download/files/busybox-1.36.1.1.zip"
|
||||
const val BUSYBOX_ZIP_CHECKSUM =
|
||||
"ea4f3019b0087dcb68130b32ab59dc2db0ee0af11d8396124a94c4231c5ea441"
|
||||
"b4d0551feabaf314e53c79316c980e8f66432e9fb91a69dbbf10a93564b40951"
|
||||
|
||||
fun Project.setupCoreLib() {
|
||||
setupCommon()
|
||||
|
||||
val abiList = Config.abiList
|
||||
|
||||
val syncLibs by tasks.registering(Sync::class) {
|
||||
into("src/main/jniLibs")
|
||||
for (abi in arrayOf("armeabi-v7a", "x86", "arm64-v8a", "x86_64", "riscv64")) {
|
||||
for (abi in abiList) {
|
||||
into(abi) {
|
||||
from(rootProject.file("native/out/$abi")) {
|
||||
include("magiskboot", "magiskinit", "magiskpolicy", "magisk", "libinit-ld.so")
|
||||
@@ -132,7 +136,7 @@ fun Project.setupCoreLib() {
|
||||
}
|
||||
}
|
||||
onlyIf {
|
||||
if (inputs.sourceFiles.files.size != 25)
|
||||
if (inputs.sourceFiles.files.size != abiList.size * 5)
|
||||
throw StopExecutionException("Please build binaries first! (./build.py binary)")
|
||||
true
|
||||
}
|
||||
@@ -158,6 +162,7 @@ fun Project.setupCoreLib() {
|
||||
}
|
||||
}
|
||||
from(zipTree(bb))
|
||||
include(abiList.map { "$it/libbusybox.so" })
|
||||
into("src/main/jniLibs")
|
||||
}
|
||||
|
||||
@@ -293,7 +298,10 @@ fun Project.setupAppCommon() {
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
targetSdk = 34
|
||||
targetSdk = 35
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt")
|
||||
)
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
@@ -345,7 +353,34 @@ fun Project.setupAppCommon() {
|
||||
}
|
||||
}
|
||||
|
||||
fun Project.setupStub() {
|
||||
fun Project.setupMainApk() {
|
||||
setupAppCommon()
|
||||
|
||||
android {
|
||||
namespace = "com.topjohnwu.magisk"
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "com.topjohnwu.magisk"
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
versionName = Config.version
|
||||
versionCode = Config.versionCode
|
||||
ndk {
|
||||
abiFilters += listOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64", "riscv64")
|
||||
debugSymbolLevel = "FULL"
|
||||
}
|
||||
}
|
||||
|
||||
androidComponents.onVariants { variant ->
|
||||
variant.instrumentation.apply {
|
||||
setAsmFramesComputationMode(COMPUTE_FRAMES_FOR_INSTRUMENTED_METHODS)
|
||||
transformClassesWith(
|
||||
DesugarClassVisitorFactory::class.java, InstrumentationScope.ALL) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun Project.setupStubApk() {
|
||||
setupAppCommon()
|
||||
|
||||
androidComponents.onVariants { variant ->
|
||||
@@ -372,8 +407,8 @@ fun Project.setupStub() {
|
||||
val outAppClassDir = layout.buildDirectory.file("generated/source/app/${variantLowered}").get().asFile
|
||||
val outResDir = layout.buildDirectory.dir("generated/source/res/${variantLowered}").get().asFile
|
||||
val aapt = File(androidApp.sdkDirectory, "build-tools/${androidApp.buildToolsVersion}/aapt2")
|
||||
val apk = layout.buildDirectory.file("intermediates/processed_res/" +
|
||||
"${variantLowered}/process${variantCapped}Resources/out/resources-${variantLowered}.ap_").get().asFile
|
||||
val apk = layout.buildDirectory.file("intermediates/linked_resources_binary_format/" +
|
||||
"${variantLowered}/process${variantCapped}Resources/linked-resources-binary-format-${variantLowered}.ap_").get().asFile
|
||||
|
||||
val genManifestTask = tasks.register("generate${variantCapped}ObfuscatedClass") {
|
||||
inputs.property("seed", RAND_SEED)
|
||||
@@ -414,8 +449,8 @@ fun Project.setupStub() {
|
||||
registerJavaGeneratingTask(processResourcesTask, outResDir)
|
||||
}
|
||||
// Override optimizeReleaseResources task
|
||||
val apk = layout.buildDirectory.file("intermediates/processed_res/" +
|
||||
"release/processReleaseResources/out/resources-release.ap_").get().asFile
|
||||
val apk = layout.buildDirectory.file("intermediates/linked_resources_binary_format/" +
|
||||
"release/processReleaseResources/linked-resources-binary-format-release.ap_").get().asFile
|
||||
val optRes = layout.buildDirectory.file("intermediates/optimized_processed_res/" +
|
||||
"release/optimizeReleaseResources/resources-release-optimize.ap_").get().asFile
|
||||
afterEvaluate {
|
||||
|
||||
@@ -9,6 +9,10 @@ version=string
|
||||
# Output path. Default: out
|
||||
outdir=string
|
||||
|
||||
# List of ABIs to build, separated with ','
|
||||
# Default: armeabi-v7a,x86,arm64-v8a,x86_64
|
||||
abiList=[string]
|
||||
|
||||
#####################################################
|
||||
# Signing configs for signing zips and APKs
|
||||
# These 4 variables has to be either all set or not
|
||||
|
||||
@@ -1,5 +1,37 @@
|
||||
# Magisk Changelog
|
||||
|
||||
### v28.1
|
||||
|
||||
- [App] Fix stub APK download link
|
||||
- [App] Fix support for Android lower than 8.0
|
||||
- [General] Fix support for MTK Samsung devices
|
||||
- [MagiskInit] Fix a regression for 2SI devices
|
||||
- [MagiskPolicy] Fix a regression causing `overlay.d` replaced files to be not accessible
|
||||
|
||||
### v28.0
|
||||
|
||||
- [General] Support 16k page size
|
||||
- [General] Add basic support for RISC-V (not built in releases)
|
||||
- [General] Use a minimal libc to build static executables (`magiskinit` and `magiskboot`) for smaller sizes
|
||||
- [Core] Remove unnecessary mirror for magic mount
|
||||
- [Core] Update boot image detection logic to support more devices
|
||||
- [MagiskInit] Rewrite 2SI logic for injecting `magiskinit` as `init`
|
||||
- [MagiskInit] Update preinit partition detection
|
||||
- [Zygisk] Update internal JNI hooking implementation
|
||||
- [MagiskPolicy] Preserve sepolicy config flag after patching
|
||||
- [MagiskPolicy] Optimize patching rules to reduce the amount of new rules being injected
|
||||
- [DenyList] Support enforcing denylist when Zygisk is disabled
|
||||
- [Resetprop] Improve implementation to workaround several property modification detections
|
||||
- [Resetprop] Update to properly work with property overlays
|
||||
- [App] Major internal code refactoring
|
||||
- [App] Support patching Samsung firmware with images larger than 8GiB
|
||||
- [App] Use user-initiated job instead of foreground services on Android 14
|
||||
- [App] Support Android 13+ built-in per-app language preferences
|
||||
- [App] Add `action.sh` support to allow modules to define an action triggered from UI
|
||||
- [MagiskBoot] Support spliting kernel images without decompression
|
||||
- [MagiskBoot] Properly support vendor boot images
|
||||
- [MagiskBoot] Disable Samsung PROCA from kernel image
|
||||
|
||||
### v27.0
|
||||
|
||||
- [Zygisk] Introduce new code injection mechanism
|
||||
|
||||
@@ -54,6 +54,7 @@ A Magisk module is a folder placed in `/data/adb/modules` with the structure bel
|
||||
│ ├── post-fs-data.sh <--- This script will be executed in post-fs-data
|
||||
│ ├── service.sh <--- This script will be executed in late_start service
|
||||
| ├── uninstall.sh <--- This script will be executed when Magisk removes your module
|
||||
| ├── action.sh <--- This script will be executed when user click the action button in Magisk app
|
||||
│ ├── system.prop <--- Properties in this file will be loaded as system properties by resetprop
|
||||
│ ├── sepolicy.rule <--- Additional custom sepolicy rules
|
||||
│ │
|
||||
@@ -123,6 +124,8 @@ If you place a file named `.replace` in any of the folders, instead of merging i
|
||||
|
||||
If you want to replace files in `/vendor`, `/product`, or `/system_ext`, please place them under `system/vendor`, `system/product`, and `system/system_ext` respectively. Magisk will transparently handle whether these partitions are in a separate partition or not.
|
||||
|
||||
If you want to remove a specific file or folder, please place a dummy character device with major number 0 and minor number 0 in the same path. For example, if you want to remove `/system/app/GoogleCamera`, you can `mknod GoogleCamera c 0 0` in `$MODDIR/system/app`.
|
||||
|
||||
#### Zygisk
|
||||
|
||||
Zygisk is a feature of Magisk that allows advanced module developers to run code directly in every Android applications' processes before they are specialized and running. For more details about the Zygisk API and building a Zygisk module, please checkout the [Zygisk Module Sample](https://github.com/topjohnwu/zygisk-module-sample) project.
|
||||
@@ -137,7 +140,7 @@ If your module requires some additional sepolicy patches, please add those rules
|
||||
|
||||
## Magisk Module Installer
|
||||
|
||||
A Magisk module installer is a Magisk module packaged in a zip file that can be flashed in the Magisk app or custom recoveries such as TWRP. The simplest Magisk module installer is just a Magisk module packed as a zip file, in addition to the following files:
|
||||
A Magisk module installer is a Magisk module packaged in a zip file that can be flashed in the Magisk app or custom recoveries such as TWRP. The simplest Magisk module installer is just a Magisk module packed as a zip file, in addition to the following files only if the module supports flashing in recovery:
|
||||
|
||||
- `update-binary`: Download the latest [module_installer.sh](https://github.com/topjohnwu/Magisk/blob/master/scripts/module_installer.sh) and rename/copy that script as `update-binary`
|
||||
- `updater-script`: This file should only contain the string `#MAGISK`
|
||||
@@ -147,7 +150,7 @@ The module installer script will setup the environment, extract the module files
|
||||
```
|
||||
module.zip
|
||||
│
|
||||
├── META-INF
|
||||
├── META-INF <--- Only needed for flashing in recovery
|
||||
│ └── com
|
||||
│ └── google
|
||||
│ └── android
|
||||
@@ -211,7 +214,7 @@ set_perm_recursive <directory> <owner> <group> <dirpermission> <filepermission>
|
||||
|
||||
For convenience, you can also declare a list of folders you want to replace in the variable name `REPLACE`. The module installer script will create the `.replace` file into the folders listed in `REPLACE`. For example:
|
||||
|
||||
```
|
||||
```sh
|
||||
REPLACE="
|
||||
/system/app/YouTube
|
||||
/system/app/Bloatware
|
||||
@@ -220,6 +223,17 @@ REPLACE="
|
||||
|
||||
The list above will result in the following files being created: `$MODPATH/system/app/YouTube/.replace` and `$MODPATH/system/app/Bloatware/.replace`.
|
||||
|
||||
For convenience, you can also declare a list of files/folders you want to remove in the variable name `REMOVE`. The module installer script will create the corresponding dummy devices. For example:
|
||||
|
||||
```sh
|
||||
REMOVE="
|
||||
/system/app/YouTube
|
||||
/system/fonts/Roboto.ttf
|
||||
"
|
||||
```
|
||||
|
||||
The list above will result in the following dummy devices being created: `$MODPATH/system/app/YouTube` and `$MODPATH/system/fonts/Roboto.ttf`.
|
||||
|
||||
#### Notes
|
||||
|
||||
- When your module is downloaded with the Magisk app, `update-binary` will be **forcefully** replaced with the latest [`module_installer.sh`](https://github.com/topjohnwu/Magisk/blob/master/scripts/module_installer.sh). **DO NOT** try to add any custom logic in `update-binary`.
|
||||
@@ -231,7 +245,7 @@ The list above will result in the following files being created: `$MODPATH/syste
|
||||
In Magisk, you can run boot scripts in 2 different modes: **post-fs-data** and **late_start service** mode.
|
||||
|
||||
- post-fs-data mode
|
||||
- This stage is BLOCKING. The boot process is paused before execution is done, or 10 seconds have passed.
|
||||
- This stage is BLOCKING. The boot process is paused before execution is done, or 40 seconds have passed.
|
||||
- Scripts run before any modules are mounted. This allows a module developer to dynamically adjust their modules before it gets mounted.
|
||||
- This stage happens before Zygote is started, which pretty much means everything in Android
|
||||
- **WARNING:** using `setprop` will deadlock the boot process! Please use `resetprop -n <prop_name> <prop_value>` instead.
|
||||
|
||||
25
docs/releases/28000.md
Normal file
25
docs/releases/28000.md
Normal file
@@ -0,0 +1,25 @@
|
||||
## 2024.10.10 Magisk v28.0
|
||||
|
||||
- [General] Support 16k page size
|
||||
- [General] Add basic support for RISC-V (not built in releases)
|
||||
- [General] Use a minimal libc to build static executables (`magiskinit` and `magiskboot`) for smaller sizes
|
||||
- [Core] Remove unnecessary mirror for magic mount
|
||||
- [Core] Update boot image detection logic to support more devices
|
||||
- [MagiskInit] Rewrite 2SI logic for injecting `magiskinit` as `init`
|
||||
- [MagiskInit] Update preinit partition detection
|
||||
- [Zygisk] Update internal JNI hooking implementation
|
||||
- [MagiskPolicy] Preserve sepolicy config flag after patching
|
||||
- [MagiskPolicy] Optimize patching rules to reduce the amount of new rules being injected
|
||||
- [DenyList] Support enforcing denylist when Zygisk is disabled
|
||||
- [Resetprop] Improve implementation to workaround several property modification detections
|
||||
- [Resetprop] Update to properly work with property overlays
|
||||
- [App] Major internal code refactoring
|
||||
- [App] Support patching Samsung firmware with images larger than 8GiB
|
||||
- [App] Use user-initiated job instead of foreground services on Android 14
|
||||
- [App] Support Android 13+ built-in per-app language preferences
|
||||
- [App] Add `action.sh` support to allow modules to define an action triggered from UI
|
||||
- [MagiskBoot] Support spliting kernel images without decompression
|
||||
- [MagiskBoot] Properly support vendor boot images
|
||||
- [MagiskBoot] Disable Samsung PROCA from kernel image
|
||||
|
||||
### Full Changelog: [here](https://topjohnwu.github.io/Magisk/changes.html)
|
||||
33
docs/releases/28100.md
Normal file
33
docs/releases/28100.md
Normal file
@@ -0,0 +1,33 @@
|
||||
## 2024.12.6 Magisk v28.1
|
||||
|
||||
- [App] Fix stub APK download link
|
||||
- [App] Fix support for Android lower than 8.0
|
||||
- [General] Fix support for MTK Samsung devices
|
||||
- [MagiskInit] Fix a regression for 2SI devices
|
||||
- [MagiskPolicy] Fix a regression causing `overlay.d` replaced files to be not accessible
|
||||
|
||||
## Magisk v28.0 Changes
|
||||
|
||||
- [General] Support 16k page size
|
||||
- [General] Add basic support for RISC-V (not built in releases)
|
||||
- [General] Use a minimal libc to build static executables (`magiskinit` and `magiskboot`) for smaller sizes
|
||||
- [Core] Remove unnecessary mirror for magic mount
|
||||
- [Core] Update boot image detection logic to support more devices
|
||||
- [MagiskInit] Rewrite 2SI logic for injecting `magiskinit` as `init`
|
||||
- [MagiskInit] Update preinit partition detection
|
||||
- [Zygisk] Update internal JNI hooking implementation
|
||||
- [MagiskPolicy] Preserve sepolicy config flag after patching
|
||||
- [MagiskPolicy] Optimize patching rules to reduce the amount of new rules being injected
|
||||
- [DenyList] Support enforcing denylist when Zygisk is disabled
|
||||
- [Resetprop] Improve implementation to workaround several property modification detections
|
||||
- [Resetprop] Update to properly work with property overlays
|
||||
- [App] Major internal code refactoring
|
||||
- [App] Support patching Samsung firmware with images larger than 8GiB
|
||||
- [App] Use user-initiated job instead of foreground services on Android 14
|
||||
- [App] Support Android 13+ built-in per-app language preferences
|
||||
- [App] Add `action.sh` support to allow modules to define an action triggered from UI
|
||||
- [MagiskBoot] Support spliting kernel images without decompression
|
||||
- [MagiskBoot] Properly support vendor boot images
|
||||
- [MagiskBoot] Disable Samsung PROCA from kernel image
|
||||
|
||||
### Full Changelog: [here](https://topjohnwu.github.io/Magisk/changes.html)
|
||||
@@ -1,5 +1,7 @@
|
||||
# Release Notes
|
||||
|
||||
- [v28.1](28100.md)
|
||||
- [v28.0](28000.md)
|
||||
- [v27.0](27000.md)
|
||||
- [v26.4](26400.md)
|
||||
- [v26.3](26300.md)
|
||||
|
||||
@@ -41,7 +41,9 @@ Supported actions:
|
||||
|
||||
repack [-n] <origbootimg> [outbootimg]
|
||||
Repack boot image components using files from the current directory
|
||||
to [outbootimg], or 'new-boot.img' if not specified.
|
||||
to [outbootimg], or 'new-boot.img' if not specified. Current directory
|
||||
should only contain required files for [outbootimg], or incorrect
|
||||
[outbootimg] may be produced.
|
||||
<origbootimg> is the original boot image used to unpack the components.
|
||||
By default, each component will be automatically compressed using its
|
||||
corresponding format detected in <origbootimg>. If a component file
|
||||
|
||||
@@ -30,5 +30,5 @@ android.nonFinalResIds=false
|
||||
|
||||
# Magisk
|
||||
magisk.stubVersion=40
|
||||
magisk.versionCode=27006
|
||||
magisk.ondkVersion=r27.2
|
||||
magisk.versionCode=28102
|
||||
magisk.ondkVersion=r28.2
|
||||
|
||||
69
gradle/libs.versions.toml
Normal file
69
gradle/libs.versions.toml
Normal file
@@ -0,0 +1,69 @@
|
||||
[versions]
|
||||
kotlin = "2.1.0"
|
||||
android = "8.8.0"
|
||||
ksp = "2.1.0-1.0.29"
|
||||
rikka = "1.3.0"
|
||||
navigation = "2.8.4"
|
||||
libsu = "6.0.0"
|
||||
moshi = "1.15.1"
|
||||
okhttp = "4.12.0"
|
||||
retrofit = "2.11.0"
|
||||
room = "2.6.1"
|
||||
|
||||
[libraries]
|
||||
bcpkix = { module = "org.bouncycastle:bcpkix-jdk18on", version = "1.80" }
|
||||
commons-compress = { module = "org.apache.commons:commons-compress", version = "1.27.1" }
|
||||
retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" }
|
||||
retrofit-moshi = { module = "com.squareup.retrofit2:converter-moshi", version.ref = "retrofit" }
|
||||
retrofit-scalars = { module = "com.squareup.retrofit2:converter-scalars", version.ref = "retrofit" }
|
||||
markwon-core = { module = "io.noties.markwon:core", version = "4.6.2" }
|
||||
okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
|
||||
okhttp-dnsoverhttps = { module = "com.squareup.okhttp3:okhttp-dnsoverhttps", version.ref = "okhttp" }
|
||||
okhttp-logging = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "okhttp" }
|
||||
moshi = { module = "com.squareup.moshi:moshi", version.ref = "moshi" }
|
||||
moshi-codegen = { module = "com.squareup.moshi:moshi-kotlin-codegen", version.ref = "moshi" }
|
||||
timber = { module = "com.jakewharton.timber:timber", version = "5.0.1" }
|
||||
jgit = { module = "org.eclipse.jgit:org.eclipse.jgit", version = "7.1.0.202411261347-r" }
|
||||
|
||||
# AndroidX
|
||||
activity = { module = "androidx.activity:activity", version = "1.10.0" }
|
||||
appcompat = { module = "androidx.appcompat:appcompat", version = "1.7.0" }
|
||||
core-ktx = { module = "androidx.core:core-ktx", version = "1.15.0" }
|
||||
core-splashscreen = { module = "androidx.core:core-splashscreen", version = "1.0.1" }
|
||||
constraintlayout = { module = "androidx.constraintlayout:constraintlayout", version = "2.2.0" }
|
||||
fragment-ktx = { module = "androidx.fragment:fragment-ktx", version = "1.8.5" }
|
||||
navigation-fragment-ktx = { module = "androidx.navigation:navigation-fragment-ktx", version.ref = "navigation" }
|
||||
navigation-ui-ktx = { module = "androidx.navigation:navigation-ui-ktx", version.ref = "navigation" }
|
||||
profileinstaller = { module = "androidx.profileinstaller:profileinstaller", version = "1.4.1" }
|
||||
recyclerview = { module = "androidx.recyclerview:recyclerview", version = "1.4.0" }
|
||||
room-ktx = { module = "androidx.room:room-ktx", version.ref = "room" }
|
||||
room-runtime = { module = "androidx.room:room-runtime", version.ref = "room" }
|
||||
room-compiler = { module = "androidx.room:room-compiler", version.ref = "room" }
|
||||
swiperefreshlayout = { module = "androidx.swiperefreshlayout:swiperefreshlayout", version = "1.1.0" }
|
||||
transition = { module = "androidx.transition:transition", version = "1.5.1" }
|
||||
collection-ktx = { module = "androidx.collection:collection-ktx", version = "1.4.5" }
|
||||
material = { module = "com.google.android.material:material", version = "1.12.0" }
|
||||
jdk-libs = { module = "com.android.tools:desugar_jdk_libs_nio", version = "2.1.3" }
|
||||
test-runner = { module = "androidx.test:runner", version = "1.6.2" }
|
||||
test-rules = { module = "androidx.test:rules", version = "1.6.1" }
|
||||
test-junit = { module = "androidx.test.ext:junit", version = "1.2.1" }
|
||||
test-uiautomator = { module = "androidx.test.uiautomator:uiautomator", version = "2.3.0" }
|
||||
|
||||
# topjohnwu
|
||||
indeterminate-checkbox = { module = "com.github.topjohnwu:indeterminate-checkbox", version = "1.0.7" }
|
||||
libsu-core = { module = "com.github.topjohnwu.libsu:core", version.ref = "libsu" }
|
||||
libsu-service = { module = "com.github.topjohnwu.libsu:service", version.ref = "libsu" }
|
||||
libsu-nio = { module = "com.github.topjohnwu.libsu:nio", version.ref = "libsu" }
|
||||
|
||||
# Rikka
|
||||
rikka-recyclerview = { module = "dev.rikka.rikkax.recyclerview:recyclerview-ktx", version = "1.3.2" }
|
||||
rikka-layoutinflater = { module = "dev.rikka.rikkax.layoutinflater:layoutinflater", version.ref = "rikka" }
|
||||
rikka-insets = { module = "dev.rikka.rikkax.insets:insets", version.ref = "rikka" }
|
||||
|
||||
# Build plugins
|
||||
android-gradle-plugin = { module = "com.android.tools.build:gradle", version.ref = "android" }
|
||||
ksp-plugin = { module = "com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin", version.ref = "ksp" }
|
||||
navigation-safe-args-plugin = { module = "androidx.navigation:navigation-safe-args-gradle-plugin", version.ref = "navigation" }
|
||||
lsparanoid-plugin = { module = "org.lsposed.lsparanoid:gradle-plugin", version = "0.6.0" }
|
||||
|
||||
[plugins]
|
||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
3
gradlew
vendored
3
gradlew
vendored
@@ -86,8 +86,7 @@ done
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
|
||||
' "$PWD" ) || exit
|
||||
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
[build]
|
||||
# Choose arm64 as the default target to make the IDE happy.
|
||||
# Set arm64 as the default target
|
||||
# The actual compilation will have the target overriden by command-line.
|
||||
target = "aarch64-linux-android"
|
||||
# Enable cross language LTO, and explicitly set dwarf-version for ThinLTO
|
||||
rustflags = ["-Z", "dwarf-version=4", "-C", "linker-plugin-lto"]
|
||||
target-dir = "../out/rust"
|
||||
|
||||
[unstable]
|
||||
build-std = ["std", "panic_abort"]
|
||||
|
||||
2
native/src/.gitignore
vendored
2
native/src/.gitignore
vendored
@@ -1,2 +0,0 @@
|
||||
test.cpp
|
||||
target/
|
||||
@@ -4,35 +4,43 @@ LOCAL_PATH := $(call my-dir)
|
||||
# Rust compilation outputs
|
||||
###########################
|
||||
|
||||
LIBRARY_PATH = ../out/$(TARGET_ARCH_ABI)/libmagisk-rs.a
|
||||
ifneq (,$(wildcard $(LOCAL_PATH)/$(LIBRARY_PATH)))
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := magisk-rs
|
||||
LOCAL_EXPORT_C_INCLUDES := src/core/include
|
||||
LOCAL_SRC_FILES := $(LIBRARY_PATH)
|
||||
LOCAL_LIB = ../out/$(TARGET_ARCH_ABI)/libmagisk-rs.a
|
||||
ifneq (,$(wildcard $(LOCAL_PATH)/$(LOCAL_LIB)))
|
||||
LOCAL_SRC_FILES := $(LOCAL_LIB)
|
||||
include $(PREBUILT_STATIC_LIBRARY)
|
||||
else
|
||||
include $(BUILD_STATIC_LIBRARY)
|
||||
endif
|
||||
|
||||
LIBRARY_PATH = ../out/$(TARGET_ARCH_ABI)/libmagiskboot-rs.a
|
||||
ifneq (,$(wildcard $(LOCAL_PATH)/$(LIBRARY_PATH)))
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := boot-rs
|
||||
LOCAL_SRC_FILES := $(LIBRARY_PATH)
|
||||
LOCAL_LIB = ../out/$(TARGET_ARCH_ABI)/libmagiskboot-rs.a
|
||||
ifneq (,$(wildcard $(LOCAL_PATH)/$(LOCAL_LIB)))
|
||||
LOCAL_SRC_FILES := $(LOCAL_LIB)
|
||||
include $(PREBUILT_STATIC_LIBRARY)
|
||||
else
|
||||
include $(BUILD_STATIC_LIBRARY)
|
||||
endif
|
||||
|
||||
LIBRARY_PATH = ../out/$(TARGET_ARCH_ABI)/libmagiskinit-rs.a
|
||||
ifneq (,$(wildcard $(LOCAL_PATH)/$(LIBRARY_PATH)))
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := init-rs
|
||||
LOCAL_SRC_FILES := $(LIBRARY_PATH)
|
||||
LOCAL_LIB = ../out/$(TARGET_ARCH_ABI)/libmagiskinit-rs.a
|
||||
ifneq (,$(wildcard $(LOCAL_PATH)/$(LOCAL_LIB)))
|
||||
LOCAL_SRC_FILES := $(LOCAL_LIB)
|
||||
include $(PREBUILT_STATIC_LIBRARY)
|
||||
else
|
||||
include $(BUILD_STATIC_LIBRARY)
|
||||
endif
|
||||
|
||||
LIBRARY_PATH = ../out/$(TARGET_ARCH_ABI)/libmagiskpolicy-rs.a
|
||||
ifneq (,$(wildcard $(LOCAL_PATH)/$(LIBRARY_PATH)))
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := policy-rs
|
||||
LOCAL_SRC_FILES := $(LIBRARY_PATH)
|
||||
LOCAL_LIB = ../out/$(TARGET_ARCH_ABI)/libmagiskpolicy-rs.a
|
||||
ifneq (,$(wildcard $(LOCAL_PATH)/$(LOCAL_LIB)))
|
||||
LOCAL_SRC_FILES := $(LOCAL_LIB)
|
||||
include $(PREBUILT_STATIC_LIBRARY)
|
||||
else
|
||||
include $(BUILD_STATIC_LIBRARY)
|
||||
endif
|
||||
|
||||
@@ -20,10 +20,9 @@ LOCAL_SRC_FILES := \
|
||||
core/daemon.cpp \
|
||||
core/bootstages.cpp \
|
||||
core/socket.cpp \
|
||||
core/db.cpp \
|
||||
core/package.cpp \
|
||||
core/scripting.cpp \
|
||||
core/selinux.cpp \
|
||||
core/sqlite.cpp \
|
||||
core/module.cpp \
|
||||
core/thread.cpp \
|
||||
core/core-rs.cpp \
|
||||
@@ -169,6 +168,7 @@ LOCAL_SRC_FILES := \
|
||||
sepolicy/policy-rs.cpp
|
||||
include $(BUILD_STATIC_LIBRARY)
|
||||
|
||||
include src/Android-rs.mk
|
||||
include src/base/Android.mk
|
||||
include src/external/Android.mk
|
||||
CWD := $(LOCAL_PATH)
|
||||
include $(CWD)/Android-rs.mk
|
||||
include $(CWD)/base/Android.mk
|
||||
include $(CWD)/external/Android.mk
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user