2 Commits
v3.0.1 ... v3.0

85 changed files with 847 additions and 2519 deletions

View File

@@ -1,137 +0,0 @@
name: GKI Kernel Build
on:
workflow_call:
inputs:
version_name:
required: true
type: string
description: >
With SUBLEVEL of kernel,
for example: android12-5.10.66
arch:
required: true
type: string
description: >
Build arch: aarch64/x86_64
debug:
required: false
type: boolean
default: true
manifest_name:
required: false
type: string
description: >
Local repo manifest xml path,
typically for AVD kernel build.
secrets:
BOOT_SIGN_KEY:
required: false
CHAT_ID:
required: false
BOT_TOKEN:
required: false
MESSAGE_THREAD_ID:
required: false
jobs:
build:
name: Build ${{ inputs.version_name }}
runs-on: ubuntu-22.04
steps:
- name: Maximize build space
uses: easimon/maximize-build-space@master
with:
root-reserve-mb: 8192
temp-reserve-mb: 2048
remove-dotnet: 'true'
remove-android: 'true'
remove-haskell: 'true'
remove-codeql: 'true'
- uses: actions/checkout@v4
with:
path: KernelSU
fetch-depth: 0
- name: Setup need_upload
id: need_upload
run: |
if [ ! -z "${{ secrets.BOT_TOKEN }}" ]; then
echo "UPLOAD=true" >> $GITHUB_OUTPUT
else
echo "UPLOAD=false" >> $GITHUB_OUTPUT
fi
- name: Setup kernel source
run: |
echo "Free space:"
df -h
cd $GITHUB_WORKSPACE
sudo apt-get install repo -y
mkdir android-kernel && cd android-kernel
repo init --depth=1 -u https://android.googlesource.com/kernel/manifest -m "$GITHUB_WORKSPACE/KernelSU/.github/manifests/${{ inputs.manifest_name }}" --repo-rev=v2.16
repo --version
repo --trace sync -c -j$(nproc --all) --no-tags
df -h
- name: Setup KernelSU
env:
PATCH_PATH: ${{ inputs.patch_path }}
IS_DEBUG_KERNEL: ${{ inputs.debug }}
run: |
cd $GITHUB_WORKSPACE/android-kernel
echo "[+] KernelSU setup"
GKI_ROOT=$(pwd)
echo "[+] GKI_ROOT: $GKI_ROOT"
echo "[+] Copy KernelSU driver to $GKI_ROOT/common/drivers"
ln -sf $GITHUB_WORKSPACE/KernelSU/kernel $GKI_ROOT/common/drivers/kernelsu
echo "[+] Add KernelSU driver to Makefile"
DRIVER_MAKEFILE=$GKI_ROOT/common/drivers/Makefile
DRIVER_KCONFIG=$GKI_ROOT/common/drivers/Kconfig
grep -q "kernelsu" "$DRIVER_MAKEFILE" || printf "\nobj-\$(CONFIG_KSU) += kernelsu/\n" >> "$DRIVER_MAKEFILE"
grep -q "kernelsu" "$DRIVER_KCONFIG" || sed -i "/endmenu/i\\source \"drivers/kernelsu/Kconfig\"" "$DRIVER_KCONFIG"
echo "[+] Apply KernelSU patches"
cd $GKI_ROOT/common/ && git apply $GITHUB_WORKSPACE/KernelSU/.github/patches/$PATCH_PATH/*.patch || echo "[-] No patch found"
if [ "$IS_DEBUG_KERNEL" = "true" ]; then
echo "[+] Enable debug features for kernel"
printf "\nccflags-y += -DCONFIG_KSU_DEBUG\n" >> $GITHUB_WORKSPACE/KernelSU/kernel/Makefile
fi
repo status
echo "[+] KernelSU setup done."
cd $GITHUB_WORKSPACE/KernelSU
VERSION=$(($(git rev-list --count HEAD) + 10200))
echo "VERSION: $VERSION"
echo "kernelsu_version=$VERSION" >> $GITHUB_ENV
- name: Make working directory clean to avoid dirty
working-directory: android-kernel
run: |
rm common/android/abi_gki_protected_exports_* || echo "No protected exports!"
git config --global user.email "bot@kernelsu.org"
git config --global user.name "KernelSUBot"
cd common/ && git add -A && git commit -a -m "Add KernelSU"
repo status
- name: Build kernel
working-directory: android-kernel
run: |
if [ ! -z ${{ vars.EXPECTED_SIZE }} ] && [ ! -z ${{ vars.EXPECTED_HASH }} ]; then
export KSU_EXPECTED_SIZE=${{ vars.EXPECTED_SIZE }}
export KSU_EXPECTED_HASH=${{ vars.EXPECTED_HASH }}
fi
tools/bazel run --config=fast --config=stamp --lto=thin //common-modules/virtual-device:virtual_device_${{ inputs.arch }}_dist -- --dist_dir=dist
NAME=kernel-${{ inputs.arch }}-avd-${{ inputs.version_name }}-${{ env.kernelsu_version }}
TARGET_IMAGE=dist/bzImage
if [ ! -e $TARGET_IMAGE ]; then
TARGET_IMAGE=dist/Image
fi
mv $TARGET_IMAGE $NAME
echo "file_path=android-kernel/$NAME" >> $GITHUB_ENV
- name: Upload Kernel
uses: actions/upload-artifact@v4
with:
name: kernel-${{ inputs.arch }}-avd-${{ inputs.version_name }}-${{ env.kernelsu_version }}
path: "${{ env.file_path }}"

View File

@@ -1,74 +0,0 @@
name: Build LKM for KernelSU
on:
workflow_call:
inputs:
upload:
required: true
type: boolean
default: true
description: "Whether to upload to branch"
secrets:
# username:github_pat
TOKEN:
required: true
workflow_dispatch:
inputs:
upload:
required: true
type: boolean
default: true
description: "Whether to upload to branch"
jobs:
build-lkm:
strategy:
matrix:
include:
- version: "android12-5.10"
sub_level: 233
os_patch_level: 2025-02
- version: "android13-5.10"
sub_level: 234
os_patch_level: 2025-03
- version: "android13-5.15"
sub_level: 178
os_patch_level: 2025-03
- version: "android14-5.15"
sub_level: 178
os_patch_level: 2025-03
- version: "android14-6.1"
sub_level: 129
os_patch_level: 2025-04
- version: "android15-6.6"
sub_level: 82
os_patch_level: 2025-04
# uses: ./.github/workflows/gki-kernel-mock.yml when debugging
uses: ./.github/workflows/gki-kernel.yml
with:
version: ${{ matrix.version }}
version_name: ${{ matrix.version }}.${{ matrix.sub_level }}
tag: ${{ matrix.version }}-${{ matrix.os_patch_level }}
os_patch_level: ${{ matrix.os_patch_level }}
build_lkm: true
push-to-branch:
needs: [build-lkm]
runs-on: ubuntu-latest
if: ${{ inputs.upload }}
steps:
- name: Download all workflow run artifacts
uses: actions/download-artifact@v4
with:
path: bin/
merge-multiple: true
- name: Push to branch LKM
run: |
cd bin
git config --global init.defaultBranch lkm
git init
git remote add origin https://${{ secrets.TOKEN }}@github.com/${{ github.repository }}
git config --local user.name "github-actions[bot]"
git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com"
find . -type f
git add .
git commit -m "Upload LKM from ${{ github.sha }}" -m "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
git push --force --set-upstream origin lkm

View File

@@ -16,76 +16,8 @@ on:
- 'manager/**' - 'manager/**'
workflow_call: workflow_call:
workflow_dispatch: workflow_dispatch:
inputs:
build_lkm:
required: true
type: choice
default: "auto"
options:
- "true"
- "false"
- "auto"
description: "Whether to build lkm"
upload_lkm:
required: true
type: boolean
default: true
description: "Whether to upload lkm"
jobs: jobs:
check-build-lkm:
runs-on: ubuntu-latest
outputs:
build_lkm: ${{ steps.check-build.outputs.build_lkm }}
upload_lkm: ${{ steps.check-build.outputs.upload_lkm }}
steps:
- name: check build
id: check-build
run: |
if [ "${{ github.event_name }}" == "workflow_dispatch" ] && [ "${{ inputs.build_lkm }}" != "auto" ]; then
kernel_changed="${{ inputs.build_lkm }}"
else
kernel_changed=true
mkdir tmp
cd tmp
git config --global init.defaultBranch bot
git config --global user.name 'Bot'
git config --global user.email 'bot@github.shirkneko.io'
git init .
git remote add origin https://github.com/${{ github.repository }}
CURRENT_COMMIT="${{ github.event.head_commit.id }}"
git fetch origin $CURRENT_COMMIT --depth=1
git fetch origin lkm --depth=1
LKM_COMMIT="$(git log --format=%B -n 1 origin/lkm | head -n 1)"
LKM_COMMIT="${LKM_COMMIT#Upload LKM from }"
LKM_COMMIT=$(echo "$LKM_COMMIT" | tr -d '[:space:]')
echo "LKM_COMMIT=$LKM_COMMIT"
git fetch origin "$LKM_COMMIT" --depth=1
git diff --quiet "$LKM_COMMIT" "$CURRENT_COMMIT" -- kernel :!kernel/setup.sh .github/workflows/build-lkm.yml .github/workflows/build-kernel-*.yml && kernel_changed=false
cd ..
rm -rf tmp
fi
if [ "${{ github.event_name }}" == "push" ] && [ "${{ github.ref }}" == 'refs/heads/main' ]; then
need_upload=true
elif [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
need_upload="${{ inputs.upload_lkm }}"
else
need_upload=false
fi
echo "kernel changed: $kernel_changed"
echo "need upload: $need_upload"
echo "build_lkm=$kernel_changed" >> "$GITHUB_OUTPUT"
echo "upload_lkm=$need_upload" >> "$GITHUB_OUTPUT"
build-lkm:
needs: check-build-lkm
uses: ./.github/workflows/build-lkm.yml
if: ${{ needs.check-build-lkm.outputs.build_lkm == 'true' }}
with:
upload: ${{ needs.check-build-lkm.outputs.upload_lkm == 'true' }}
secrets: inherit
build-susfs: build-susfs:
if: ${{ always() }}
needs: [ check-build-lkm, build-lkm ]
strategy: strategy:
matrix: matrix:
include: include:
@@ -97,8 +29,7 @@ jobs:
os: ${{ matrix.os }} os: ${{ matrix.os }}
build-kpmmgr: build-kpmmgr:
if: ${{ always() }} needs: build-susfs
needs: [ check-build-lkm, build-lkm ]
strategy: strategy:
matrix: matrix:
include: include:
@@ -110,8 +41,7 @@ jobs:
os: ${{ matrix.os }} os: ${{ matrix.os }}
build-ksud: build-ksud:
if: ${{ always() }} needs: build-kpmmgr
needs: [ check-build-lkm, build-lkm ]
strategy: strategy:
matrix: matrix:
include: include:
@@ -123,8 +53,6 @@ jobs:
with: with:
target: ${{ matrix.target }} target: ${{ matrix.target }}
os: ${{ matrix.os }} os: ${{ matrix.os }}
pack_lkm: true
pull_lkm: ${{ needs.check-build-lkm.outputs.build_lkm != 'true' }}
build-manager: build-manager:
if: ${{ always() }} if: ${{ always() }}

View File

@@ -1,79 +0,0 @@
name: GKI Kernel Build
on:
workflow_call:
inputs:
version:
required: true
type: string
description: >
Output directory of gki,
for example: android12-5.10
version_name:
required: true
type: string
description: >
With SUBLEVEL of kernel,
for example: android12-5.10.66
tag:
required: true
type: string
description: >
Part of branch name of common kernel manifest,
for example: android12-5.10-2021-11
os_patch_level:
required: false
type: string
description: >
Patch level of common kernel manifest,
for example: 2021-11
default: 2022-05
patch_path:
required: false
type: string
description: >
Directory name of .github/patches/<patch_path>
for example: 5.10
use_cache:
required: false
type: boolean
default: true
embed_ksud:
required: false
type: string
default: ksud-aarch64-linux-android
description: >
Artifact name of prebuilt ksud to be embedded
for example: ksud-aarch64-linux-android
debug:
required: false
type: boolean
default: false
build_lkm:
required: false
type: boolean
default: false
secrets:
BOOT_SIGN_KEY:
required: false
CHAT_ID:
required: false
BOT_TOKEN:
required: false
MESSAGE_THREAD_ID:
required: false
jobs:
mock_build:
name: Mock build ${{ inputs.version_name }}
runs-on: ubuntu-latest
steps:
- name: Create mocking ko
run: |
echo "${{ inputs.version }}_kernelsu.ko" > ${{ inputs.version }}_kernelsu.ko
- name: Upload LKM
uses: actions/upload-artifact@v4
if: ${{ inputs.build_lkm == true }}
with:
name: ${{ inputs.version }}-lkm
path: ./*_kernelsu.ko

View File

@@ -1,258 +0,0 @@
name: GKI Kernel Build
on:
workflow_call:
inputs:
version:
required: true
type: string
description: >
Output directory of gki,
for example: android12-5.10
version_name:
required: true
type: string
description: >
With SUBLEVEL of kernel,
for example: android12-5.10.66
tag:
required: true
type: string
description: >
Part of branch name of common kernel manifest,
for example: android12-5.10-2021-11
os_patch_level:
required: false
type: string
description: >
Patch level of common kernel manifest,
for example: 2021-11
default: 2022-05
patch_path:
required: false
type: string
description: >
Directory name of .github/patches/<patch_path>
for example: 5.10
use_cache:
required: false
type: boolean
default: true
embed_ksud:
required: false
type: string
default: ksud-aarch64-linux-android
description: >
Artifact name of prebuilt ksud to be embedded
for example: ksud-aarch64-linux-android
debug:
required: false
type: boolean
default: false
build_lkm:
required: false
type: boolean
default: false
secrets:
BOOT_SIGN_KEY:
required: false
CHAT_ID:
required: false
BOT_TOKEN:
required: false
MESSAGE_THREAD_ID:
required: false
jobs:
build:
name: Build ${{ inputs.version_name }}
runs-on: ubuntu-latest
env:
CCACHE_COMPILERCHECK: "%compiler% -dumpmachine; %compiler% -dumpversion"
CCACHE_NOHASHDIR: "true"
CCACHE_HARDLINK: "true"
steps:
- name: Maximize build space
uses: easimon/maximize-build-space@master
with:
root-reserve-mb: 8192
temp-reserve-mb: 2048
remove-dotnet: 'true'
remove-android: 'true'
remove-haskell: 'true'
remove-codeql: 'true'
- uses: actions/checkout@v4
with:
path: KernelSU
fetch-depth: 0
- name: Setup need_upload
id: need_upload
run: |
if [ ! -z "${{ secrets.BOT_TOKEN }}" ]; then
echo "UPLOAD=true" >> $GITHUB_OUTPUT
else
echo "UPLOAD=false" >> $GITHUB_OUTPUT
fi
- name: Setup kernel source
run: |
echo "Free space:"
df -h
cd $GITHUB_WORKSPACE
sudo apt-get install repo -y
mkdir android-kernel && cd android-kernel
repo init --depth=1 --u https://android.googlesource.com/kernel/manifest -b common-${{ inputs.tag }} --repo-rev=v2.35
REMOTE_BRANCH=$(git ls-remote https://android.googlesource.com/kernel/common ${{ inputs.tag }})
DEFAULT_MANIFEST_PATH=.repo/manifests/default.xml
if grep -q deprecated <<< $REMOTE_BRANCH; then
echo "Found deprecated branch: ${{ inputs.tag }}"
sed -i 's/"${{ inputs.tag }}"/"deprecated\/${{ inputs.tag }}"/g' $DEFAULT_MANIFEST_PATH
cat $DEFAULT_MANIFEST_PATH
fi
repo --version
repo --trace sync -c -j$(nproc --all) --no-tags
df -h
- name: Setup KernelSU
env:
PATCH_PATH: ${{ inputs.patch_path }}
IS_DEBUG_KERNEL: ${{ inputs.debug }}
run: |
cd $GITHUB_WORKSPACE/android-kernel
echo "[+] KernelSU setup"
GKI_ROOT=$(pwd)
echo "[+] GKI_ROOT: $GKI_ROOT"
echo "[+] Copy KernelSU driver to $GKI_ROOT/common/drivers"
ln -sf $GITHUB_WORKSPACE/KernelSU/kernel $GKI_ROOT/common/drivers/kernelsu
echo "[+] Add KernelSU driver to Makefile"
DRIVER_MAKEFILE=$GKI_ROOT/common/drivers/Makefile
DRIVER_KCONFIG=$GKI_ROOT/common/drivers/Kconfig
grep -q "kernelsu" "$DRIVER_MAKEFILE" || printf "\nobj-\$(CONFIG_KSU) += kernelsu/\n" >> "$DRIVER_MAKEFILE"
grep -q "kernelsu" "$DRIVER_KCONFIG" || sed -i "/endmenu/i\\source \"drivers/kernelsu/Kconfig\"" "$DRIVER_KCONFIG"
echo "[+] Apply Compilation Patches"
if [ ! -e build/build.sh ]; then
GLIBC_VERSION=$(ldd --version 2>/dev/null | head -n 1 | awk '{print $NF}')
echo "GLIBC_VERSION: $GLIBC_VERSION"
if [ "$(printf '%s\n' "2.38" "$GLIBC_VERSION" | sort -V | head -n1)" = "2.38" ]; then
echo "Patching resolve_btfids/Makefile"
cd $GKI_ROOT/common/ && sed -i '/\$(Q)\$(MAKE) -C \$(SUBCMD_SRC) OUTPUT=\$(abspath \$(dir \$@))\/ \$(abspath \$@)/s//$(Q)$(MAKE) -C $(SUBCMD_SRC) EXTRA_CFLAGS="$(CFLAGS)" OUTPUT=$(abspath $(dir $@))\/ $(abspath $@)/' tools/bpf/resolve_btfids/Makefile || echo "No patch needed."
fi
fi
if [ "$IS_DEBUG_KERNEL" = "true" ]; then
echo "[+] Enable debug features for kernel"
printf "\nccflags-y += -DCONFIG_KSU_DEBUG\n" >> $GITHUB_WORKSPACE/KernelSU/kernel/Makefile
fi
repo status
echo "[+] KernelSU setup done."
- name: Symbol magic
run: |
echo "[+] Export all symbol from abi_gki_aarch64.xml"
COMMON_ROOT=$GITHUB_WORKSPACE/android-kernel/common
KSU_ROOT=$GITHUB_WORKSPACE/KernelSU
ABI_XML=$COMMON_ROOT/android/abi_gki_aarch64.xml
SYMBOL_LIST=$COMMON_ROOT/android/abi_gki_aarch64
# python3 $KSU_ROOT/scripts/abi_gki_all.py $ABI_XML > $SYMBOL_LIST
echo "[+] Add KernelSU symbols"
cat $KSU_ROOT/kernel/export_symbol.txt | awk '{sub("[ \t]+","");print " "$0}' >> $SYMBOL_LIST
- name: Setup ccache
if: inputs.use_cache == true
uses: hendrikmuhs/ccache-action@v1
with:
key: gki-kernel-aarch64-${{ inputs.version_name }}
max-size: 2G
save: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
- name: Setup for LKM
if: ${{ inputs.build_lkm == true }}
working-directory: android-kernel
run: |
pip install ast-grep-cli
sudo apt-get install llvm-15 -y
ast-grep -U -p '$$$ check_exports($$$) {$$$}' -r '' common/scripts/mod/modpost.c
ast-grep -U -p 'check_exports($$$);' -r '' common/scripts/mod/modpost.c
sed -i '/config KSU/,/help/{s/default y/default m/}' common/drivers/kernelsu/Kconfig
echo "drivers/kernelsu/kernelsu.ko" >> common/android/gki_aarch64_modules
# bazel build, android14-5.15, android14-6.1 use bazel
if [ ! -e build/build.sh ]; then
sed -i 's/needs unknown symbol/Dont abort when unknown symbol/g' build/kernel/*.sh || echo "No unknown symbol scripts found"
if [ -e common/modules.bzl ]; then
sed -i 's/_COMMON_GKI_MODULES_LIST = \[/_COMMON_GKI_MODULES_LIST = \[ "drivers\/kernelsu\/kernelsu.ko",/g' common/modules.bzl
fi
else
TARGET_FILE="build/kernel/build.sh"
if [ ! -e "$TARGET_FILE" ]; then
TARGET_FILE="build/build.sh"
fi
sed -i 's/needs unknown symbol/Dont abort when unknown symbol/g' $TARGET_FILE || echo "No unknown symbol in $TARGET_FILE"
sed -i 's/if ! diff -u "\${KERNEL_DIR}\/\${MODULES_ORDER}" "\${OUT_DIR}\/modules\.order"; then/if false; then/g' $TARGET_FILE
sed -i 's@${ROOT_DIR}/build/abi/compare_to_symbol_list@echo@g' $TARGET_FILE
sed -i 's/needs unknown symbol/Dont abort when unknown symbol/g' build/kernel/*.sh || echo "No unknown symbol scripts found"
fi
- name: Make working directory clean to avoid dirty
working-directory: android-kernel
run: |
rm common/android/abi_gki_protected_exports_* || echo "No protected exports!"
git config --global user.email "bot@kernelsu.org"
git config --global user.name "KernelSUBot"
cd common/ && git add -A && git commit -a -m "Add KernelSU"
repo status
- name: Build Kernel/LKM
working-directory: android-kernel
run: |
if [ ! -z ${{ vars.EXPECTED_SIZE }} ] && [ ! -z ${{ vars.EXPECTED_HASH }} ]; then
export KSU_EXPECTED_SIZE=${{ vars.EXPECTED_SIZE }}
export KSU_EXPECTED_HASH=${{ vars.EXPECTED_HASH }}
fi
if [ -e build/build.sh ]; then
LTO=thin BUILD_CONFIG=common/build.config.gki.aarch64 build/build.sh CC="/usr/bin/ccache clang"
else
tools/bazel run --disk_cache=/home/runner/.cache/bazel --config=fast --config=stamp --lto=thin //common:kernel_aarch64_dist -- --dist_dir=dist
fi
- name: Prepare artifacts
id: prepareArtifacts
run: |
OUTDIR=android-kernel/out/${{ inputs.version }}/dist
if [ ! -e $OUTDIR ]; then
OUTDIR=android-kernel/dist
fi
mkdir output
if [ "${{ inputs.build_lkm}}" = "true" ]; then
llvm-strip-15 -d $OUTDIR/kernelsu.ko
mv $OUTDIR/kernelsu.ko ./output/${{ inputs.version }}_kernelsu.ko
else
cp $OUTDIR/Image ./output/
cp $OUTDIR/Image.lz4 ./output/
git clone https://github.com/Kernel-SU/AnyKernel3
rm -rf ./AnyKernel3/.git
cp $OUTDIR/Image ./AnyKernel3/
fi
- name: Upload Image and Image.gz
uses: actions/upload-artifact@v4
if: ${{ inputs.build_lkm == false }}
with:
name: Image-${{ inputs.version_name }}_${{ inputs.os_patch_level }}
path: ./output/*
- name: Upload AnyKernel3
if: ${{ inputs.build_lkm == false }}
uses: actions/upload-artifact@v4
with:
name: AnyKernel3-${{ inputs.version_name }}_${{ inputs.os_patch_level }}
path: ./AnyKernel3/*
- name: Upload LKM
uses: actions/upload-artifact@v4
if: ${{ inputs.build_lkm == true }}
with:
name: ${{ inputs.version }}-lkm
path: ./output/*_kernelsu.ko

View File

@@ -1,4 +1,4 @@
# SukiSU Ultra # SukiSU
**English** | [简体中文](README.md) | [日本語](README-ja.md) **English** | [简体中文](README.md) | [日本語](README-ja.md)
@@ -100,7 +100,6 @@ Projects compiled based on Sukisu and susfs
- [zaoqi123](https://github.com/zaoqi123) It's not a bad idea to buy me a milk tea - [zaoqi123](https://github.com/zaoqi123) It's not a bad idea to buy me a milk tea
- [wswzgdg](https://github.com/wswzgdg) Many thanks for supporting this project - [wswzgdg](https://github.com/wswzgdg) Many thanks for supporting this project
- [yspbwx2010](https://github.com/yspbwx2010) Many thanks - [yspbwx2010](https://github.com/yspbwx2010) Many thanks
- [DARKWWEE](https://github.com/DARKWWEE) Thanks for the 100 USDT Lao

View File

@@ -1,4 +1,4 @@
# SukiSU Ultra # SukiSU
**日本語** | [简体中文](README.md) | [English](README-en.md) **日本語** | [简体中文](README.md) | [English](README-en.md)
@@ -93,7 +93,6 @@ SukiSU と susfs をベースにコンパイルされたプロジェクトです
- [zaoqi123](https://github.com/zaoqi123) ミルクティーを買ってあげるのも良い考えですね。 - [zaoqi123](https://github.com/zaoqi123) ミルクティーを買ってあげるのも良い考えですね。
- [wswzgdg](https://github.com/wswzgdg) このプロジェクトを支援していただき、ありがとうございます。 - [wswzgdg](https://github.com/wswzgdg) このプロジェクトを支援していただき、ありがとうございます。
- [yspbwx2010](https://github.com/yspbwx2010) どうもありがとう。 - [yspbwx2010](https://github.com/yspbwx2010) どうもありがとう。
- [DARKWWEE](https://github.com/DARKWWEE) ラオウ100USDTありがとう

View File

@@ -101,7 +101,6 @@ KPM模板地址: https://github.com/udochina/KPM-Build-Anywhere
- [zaoqi123](https://github.com/zaoqi123) 请我喝奶茶也不错 - [zaoqi123](https://github.com/zaoqi123) 请我喝奶茶也不错
- [wswzgdg](https://github.com/wswzgdg) 非常感谢对此项目的支持 - [wswzgdg](https://github.com/wswzgdg) 非常感谢对此项目的支持
- [yspbwx2010](https://github.com/yspbwx2010) 非常感谢 - [yspbwx2010](https://github.com/yspbwx2010) 非常感谢
- [DARKWWEE](https://github.com/DARKWWEE) 感谢老哥的 100 USDT

View File

@@ -25,11 +25,11 @@ $(shell cd $(srctree)/$(src); /usr/bin/env PATH="$$PATH":/usr/bin:/usr/local/bin
KSU_GIT_VERSION := $(shell cd $(srctree)/$(src); /usr/bin/env PATH="$$PATH":/usr/bin:/usr/local/bin git rev-list --count main) KSU_GIT_VERSION := $(shell cd $(srctree)/$(src); /usr/bin/env PATH="$$PATH":/usr/bin:/usr/local/bin git rev-list --count main)
# ksu_version: major * 10000 + git version + 606 for historical reasons # ksu_version: major * 10000 + git version + 606 for historical reasons
$(eval KSU_VERSION=$(shell expr 10000 + $(KSU_GIT_VERSION) + 606)) $(eval KSU_VERSION=$(shell expr 10000 + $(KSU_GIT_VERSION) + 606))
$(info -- KernelSU version: $(KSU_VERSION)) $(info -- SukiSU version: $(KSU_VERSION))
ccflags-y += -DKSU_VERSION=$(KSU_VERSION) ccflags-y += -DKSU_VERSION=$(KSU_VERSION)
else # If there is no .git file, the default version will be passed. else # If there is no .git file, the default version will be passed.
$(warning "KSU_GIT_VERSION not defined! It is better to make KernelSU a git submodule!") $(warning "KSU_GIT_VERSION not defined! It is better to make SukiSU a git submodule!")
ccflags-y += -DKSU_VERSION=16 ccflags-y += -DKSU_VERSION=12800
endif endif
ifndef KSU_EXPECTED_SIZE ifndef KSU_EXPECTED_SIZE
@@ -42,26 +42,13 @@ endif
ifdef KSU_MANAGER_PACKAGE ifdef KSU_MANAGER_PACKAGE
ccflags-y += -DKSU_MANAGER_PACKAGE=\"$(KSU_MANAGER_PACKAGE)\" ccflags-y += -DKSU_MANAGER_PACKAGE=\"$(KSU_MANAGER_PACKAGE)\"
$(info -- SukiSU Manager package name: $(KSU_MANAGER_PACKAGE)) $(info -- KernelSU Manager package name: $(KSU_MANAGER_PACKAGE))
endif endif
$(info -- SukiSU Manager signature size: $(KSU_EXPECTED_SIZE)) $(info -- KernelSU Manager signature size: $(KSU_EXPECTED_SIZE))
$(info -- SukiSU Manager signature hash: $(KSU_EXPECTED_HASH)) $(info -- KernelSU Manager signature hash: $(KSU_EXPECTED_HASH))
$(info -- Supported Unofficial Manager: 5ec1cff (GKI) ShirkNeko udochina (GKI and KPM)) $(info -- Supported Unofficial Manager: 5ec1cff (GKI) ShirkNeko udochina (GKI and KPM))
KERNEL_VERSION := $(VERSION).$(PATCHLEVEL) KERNEL_VERSION := $(VERSION).$(PATCHLEVEL)
KERNEL_TYPE := Non-GKI
# Check for GKI 2.0 (5.10+ or 6.x+)
ifneq ($(shell test \( $(VERSION) -ge 5 -a $(PATCHLEVEL) -ge 10 \) -o $(VERSION) -ge 6; echo $$?),0)
# Check for GKI 1.0 (5.4)
ifeq ($(shell test $(VERSION)-$(PATCHLEVEL) = 5-4; echo $$?),0)
KERNEL_TYPE := GKI 1.0
endif
else
KERNEL_TYPE := GKI 2.0
endif
$(info -- KERNEL_VERSION: $(KERNEL_VERSION))
$(info -- KERNEL_TYPE: $(KERNEL_TYPE))
$(info -- KERNEL_VERSION: $(KERNEL_VERSION)) $(info -- KERNEL_VERSION: $(KERNEL_VERSION))
ifeq ($(CONFIG_KPM),y) ifeq ($(CONFIG_KPM),y)
$(info -- KPM is enabled) $(info -- KPM is enabled)

View File

@@ -25,7 +25,7 @@ apksign {
} }
android { android {
namespace = "com.sukisu.ultra" namespace = "zako.zako.zako"
buildTypes { buildTypes {
release { release {

View File

@@ -1,4 +1,4 @@
package com.sukisu.zako; package zako.zako.zako;
import android.content.pm.PackageInfo; import android.content.pm.PackageInfo;
import rikka.parcelablelist.ParcelableListSlice; import rikka.parcelablelist.ParcelableListSlice;

Binary file not shown.

View File

@@ -12,7 +12,7 @@
extern "C" extern "C"
JNIEXPORT jboolean JNICALL JNIEXPORT jboolean JNICALL
Java_com_sukisu_ultra_Natives_becomeManager(JNIEnv *env, jobject, jstring pkg) { Java_zako_zako_zako_Natives_becomeManager(JNIEnv *env, jobject, jstring pkg) {
auto cpkg = env->GetStringUTFChars(pkg, nullptr); auto cpkg = env->GetStringUTFChars(pkg, nullptr);
auto result = become_manager(cpkg); auto result = become_manager(cpkg);
env->ReleaseStringUTFChars(pkg, cpkg); env->ReleaseStringUTFChars(pkg, cpkg);
@@ -21,13 +21,13 @@ Java_com_sukisu_ultra_Natives_becomeManager(JNIEnv *env, jobject, jstring pkg) {
extern "C" extern "C"
JNIEXPORT jint JNICALL JNIEXPORT jint JNICALL
Java_com_sukisu_ultra_Natives_getVersion(JNIEnv *env, jobject) { Java_zako_zako_zako_Natives_getVersion(JNIEnv *env, jobject) {
return get_version(); return get_version();
} }
extern "C" extern "C"
JNIEXPORT jintArray JNICALL JNIEXPORT jintArray JNICALL
Java_com_sukisu_ultra_Natives_getAllowList(JNIEnv *env, jobject) { Java_zako_zako_zako_Natives_getAllowList(JNIEnv *env, jobject) {
int uids[1024]; int uids[1024];
int size = 0; int size = 0;
bool result = get_allow_list(uids, &size); bool result = get_allow_list(uids, &size);
@@ -42,13 +42,13 @@ Java_com_sukisu_ultra_Natives_getAllowList(JNIEnv *env, jobject) {
extern "C" extern "C"
JNIEXPORT jboolean JNICALL JNIEXPORT jboolean JNICALL
Java_com_sukisu_ultra_Natives_isSafeMode(JNIEnv *env, jclass clazz) { Java_zako_zako_zako_Natives_isSafeMode(JNIEnv *env, jclass clazz) {
return is_safe_mode(); return is_safe_mode();
} }
extern "C" extern "C"
JNIEXPORT jboolean JNICALL JNIEXPORT jboolean JNICALL
Java_com_sukisu_ultra_Natives_isLkmMode(JNIEnv *env, jclass clazz) { Java_zako_zako_zako_Natives_isLkmMode(JNIEnv *env, jclass clazz) {
return is_lkm_mode(); return is_lkm_mode();
} }
@@ -111,7 +111,7 @@ static void fillArrayWithList(JNIEnv *env, jobject list, int *data, int count) {
extern "C" extern "C"
JNIEXPORT jobject JNICALL JNIEXPORT jobject JNICALL
Java_com_sukisu_ultra_Natives_getAppProfile(JNIEnv *env, jobject, jstring pkg, jint uid) { Java_zako_zako_zako_Natives_getAppProfile(JNIEnv *env, jobject, jstring pkg, jint uid) {
if (env->GetStringLength(pkg) > KSU_MAX_PACKAGE_NAME) { if (env->GetStringLength(pkg) > KSU_MAX_PACKAGE_NAME) {
return nullptr; return nullptr;
} }
@@ -129,7 +129,7 @@ Java_com_sukisu_ultra_Natives_getAppProfile(JNIEnv *env, jobject, jstring pkg, j
bool useDefaultProfile = !get_app_profile(key, &profile); bool useDefaultProfile = !get_app_profile(key, &profile);
auto cls = env->FindClass("com/sukisu/ultra/Natives$Profile"); auto cls = env->FindClass("zako/zako/zako/Natives$Profile");
auto constructor = env->GetMethodID(cls, "<init>", "()V"); auto constructor = env->GetMethodID(cls, "<init>", "()V");
auto obj = env->NewObject(cls, constructor); auto obj = env->NewObject(cls, constructor);
auto keyField = env->GetFieldID(cls, "name", "Ljava/lang/String;"); auto keyField = env->GetFieldID(cls, "name", "Ljava/lang/String;");
@@ -207,8 +207,8 @@ Java_com_sukisu_ultra_Natives_getAppProfile(JNIEnv *env, jobject, jstring pkg, j
extern "C" extern "C"
JNIEXPORT jboolean JNICALL JNIEXPORT jboolean JNICALL
Java_com_sukisu_ultra_Natives_setAppProfile(JNIEnv *env, jobject clazz, jobject profile) { Java_zako_zako_zako_Natives_setAppProfile(JNIEnv *env, jobject clazz, jobject profile) {
auto cls = env->FindClass("com/sukisu/ultra/Natives$Profile"); auto cls = env->FindClass("zako/zako/zako/Natives$Profile");
auto keyField = env->GetFieldID(cls, "name", "Ljava/lang/String;"); auto keyField = env->GetFieldID(cls, "name", "Ljava/lang/String;");
auto currentUidField = env->GetFieldID(cls, "currentUid", "I"); auto currentUidField = env->GetFieldID(cls, "currentUid", "I");
@@ -293,16 +293,16 @@ Java_com_sukisu_ultra_Natives_setAppProfile(JNIEnv *env, jobject clazz, jobject
} }
extern "C" extern "C"
JNIEXPORT jboolean JNICALL JNIEXPORT jboolean JNICALL
Java_com_sukisu_ultra_Natives_uidShouldUmount(JNIEnv *env, jobject thiz, jint uid) { Java_zako_zako_zako_Natives_uidShouldUmount(JNIEnv *env, jobject thiz, jint uid) {
return uid_should_umount(uid); return uid_should_umount(uid);
} }
extern "C" extern "C"
JNIEXPORT jboolean JNICALL JNIEXPORT jboolean JNICALL
Java_com_sukisu_ultra_Natives_isSuEnabled(JNIEnv *env, jobject thiz) { Java_zako_zako_zako_Natives_isSuEnabled(JNIEnv *env, jobject thiz) {
return is_su_enabled(); return is_su_enabled();
} }
extern "C" extern "C"
JNIEXPORT jboolean JNICALL JNIEXPORT jboolean JNICALL
Java_com_sukisu_ultra_Natives_setSuEnabled(JNIEnv *env, jobject thiz, jboolean enabled) { Java_zako_zako_zako_Natives_setSuEnabled(JNIEnv *env, jobject thiz, jboolean enabled) {
return set_su_enabled(enabled); return set_su_enabled(enabled);
} }

View File

@@ -1,423 +0,0 @@
package com.sukisu.ultra.flash
import android.app.Activity
import android.content.Context
import android.net.Uri
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.CheckCircle
import androidx.compose.material.icons.filled.Error
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.documentfile.provider.DocumentFile
import com.sukisu.ultra.R
import com.sukisu.ultra.utils.AssetsUtil
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
data class FlashState(
val isFlashing: Boolean = false,
val isCompleted: Boolean = false,
val progress: Float = 0f,
val currentStep: String = "",
val logs: List<String> = emptyList(),
val error: String = ""
)
class HorizonKernelState {
private val _state = MutableStateFlow(FlashState())
val state: StateFlow<FlashState> = _state.asStateFlow()
fun updateProgress(progress: Float) {
_state.update { it.copy(progress = progress) }
}
fun updateStep(step: String) {
_state.update { it.copy(currentStep = step) }
}
fun addLog(log: String) {
_state.update {
it.copy(logs = it.logs + log)
}
}
fun setError(error: String) {
_state.update { it.copy(error = error) }
}
fun startFlashing() {
_state.update {
it.copy(
isFlashing = true,
isCompleted = false,
progress = 0f,
currentStep = "under preparation...",
logs = emptyList(),
error = ""
)
}
}
fun completeFlashing() {
_state.update { it.copy(isCompleted = true, progress = 1f) }
}
fun reset() {
_state.value = FlashState()
}
}
class HorizonKernelWorker(
private val context: Context,
private val state: HorizonKernelState,
private val slot: String? = null
) : Thread() {
var uri: Uri? = null
private lateinit var filePath: String
private lateinit var binaryPath: String
private var onFlashComplete: (() -> Unit)? = null
private var originalSlot: String? = null
fun setOnFlashCompleteListener(listener: () -> Unit) {
onFlashComplete = listener
}
override fun run() {
state.startFlashing()
state.updateStep(context.getString(R.string.horizon_preparing))
filePath = "${context.filesDir.absolutePath}/${DocumentFile.fromSingleUri(context, uri!!)?.name}"
binaryPath = "${context.filesDir.absolutePath}/META-INF/com/google/android/update-binary"
try {
state.updateStep(context.getString(R.string.horizon_cleaning_files))
state.updateProgress(0.1f)
cleanup()
if (!rootAvailable()) {
state.setError(context.getString(R.string.root_required))
return
}
state.updateStep(context.getString(R.string.horizon_copying_files))
state.updateProgress(0.2f)
copy()
if (!File(filePath).exists()) {
state.setError(context.getString(R.string.horizon_copy_failed))
return
}
state.updateStep(context.getString(R.string.horizon_extracting_tool))
state.updateProgress(0.4f)
getBinary()
state.updateStep(context.getString(R.string.horizon_patching_script))
state.updateProgress(0.6f)
patch()
state.updateStep(context.getString(R.string.horizon_flashing))
state.updateProgress(0.7f)
// 获取原始槽位信息
if (slot != null) {
state.updateStep(context.getString(R.string.horizon_getting_original_slot))
state.updateProgress(0.72f)
originalSlot = runCommandGetOutput(true, "getprop ro.boot.slot_suffix")
}
// 设置目标槽位
if (!slot.isNullOrEmpty()) {
state.updateStep(context.getString(R.string.horizon_setting_target_slot))
state.updateProgress(0.74f)
runCommand(true, "resetprop -n ro.boot.slot_suffix _$slot")
}
flash()
// 恢复原始槽位
if (!originalSlot.isNullOrEmpty()) {
state.updateStep(context.getString(R.string.horizon_restoring_original_slot))
state.updateProgress(0.8f)
runCommand(true, "resetprop ro.boot.slot_suffix $originalSlot")
}
state.updateStep(context.getString(R.string.horizon_flash_complete_status))
state.completeFlashing()
(context as? Activity)?.runOnUiThread {
onFlashComplete?.invoke()
}
} catch (e: Exception) {
state.setError(e.message ?: context.getString(R.string.horizon_unknown_error))
// 恢复原始槽位
if (!originalSlot.isNullOrEmpty()) {
state.updateStep(context.getString(R.string.horizon_restoring_original_slot))
state.updateProgress(0.8f)
runCommand(true, "resetprop ro.boot.slot_suffix $originalSlot")
}
}
}
private fun cleanup() {
runCommand(false, "find ${context.filesDir.absolutePath} -type f ! -name '*.jpg' ! -name '*.png' -delete")
}
private fun copy() {
uri?.let { safeUri ->
context.contentResolver.openInputStream(safeUri)?.use { input ->
FileOutputStream(File(filePath)).use { output ->
input.copyTo(output)
}
}
}
}
private fun getBinary() {
runCommand(false, "unzip \"$filePath\" \"*/update-binary\" -d ${context.filesDir.absolutePath}")
if (!File(binaryPath).exists()) {
throw IOException("Failed to extract update-binary")
}
}
private fun patch() {
val mkbootfsPath = "${context.filesDir.absolutePath}/mkbootfs"
AssetsUtil.exportFiles(context, "mkbootfs", mkbootfsPath)
runCommand(false, "sed -i '/chmod -R 755 tools bin;/i cp -f $mkbootfsPath \$AKHOME/tools;' $binaryPath")
}
private fun flash() {
val process = ProcessBuilder("su")
.redirectErrorStream(true)
.start()
try {
process.outputStream.bufferedWriter().use { writer ->
writer.write("export POSTINSTALL=${context.filesDir.absolutePath}\n")
// 写入槽位信息到临时文件
slot?.let { selectedSlot ->
writer.write("echo \"$selectedSlot\" > ${context.filesDir.absolutePath}/bootslot\n")
}
// 构建刷写命令
val flashCommand = buildString {
append("sh $binaryPath 3 1 \"$filePath\"")
if (slot != null) {
append(" \"$(cat ${context.filesDir.absolutePath}/bootslot)\"")
}
append(" && touch ${context.filesDir.absolutePath}/done\n")
}
writer.write(flashCommand)
writer.write("exit\n")
writer.flush()
}
process.inputStream.bufferedReader().use { reader ->
reader.lineSequence().forEach { line ->
if (line.startsWith("ui_print")) {
val logMessage = line.removePrefix("ui_print").trim()
state.addLog(logMessage)
when {
logMessage.contains("extracting", ignoreCase = true) -> {
state.updateProgress(0.75f)
}
logMessage.contains("installing", ignoreCase = true) -> {
state.updateProgress(0.85f)
}
logMessage.contains("complete", ignoreCase = true) -> {
state.updateProgress(0.95f)
}
}
}
}
}
} finally {
process.destroy()
}
if (!File("${context.filesDir.absolutePath}/done").exists()) {
throw IOException(context.getString(R.string.flash_failed_message))
}
}
private fun runCommand(su: Boolean, cmd: String): Int {
val process = ProcessBuilder(if (su) "su" else "sh")
.redirectErrorStream(true)
.start()
return try {
process.outputStream.bufferedWriter().use { writer ->
writer.write("$cmd\n")
writer.write("exit\n")
writer.flush()
}
process.waitFor()
} finally {
process.destroy()
}
}
private fun runCommandGetOutput(su: Boolean, cmd: String): String {
val process = ProcessBuilder(if (su) "su" else "sh")
.redirectErrorStream(true)
.start()
return try {
process.outputStream.bufferedWriter().use { writer ->
writer.write("$cmd\n")
writer.write("exit\n")
writer.flush()
}
process.inputStream.bufferedReader().use { reader ->
reader.readText().trim()
}
} catch (_: Exception) {
""
} finally {
process.destroy()
}
}
private fun rootAvailable(): Boolean {
return try {
val process = Runtime.getRuntime().exec("su -c id")
val exitValue = process.waitFor()
exitValue == 0
} catch (_: Exception) {
false
}
}
}
@Composable
fun HorizonKernelFlashProgress(state: FlashState) {
Card(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.surfaceVariant
)
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = stringResource(id = R.string.horizon_flash_title),
style = MaterialTheme.typography.titleMedium,
modifier = Modifier.padding(bottom = 8.dp)
)
LinearProgressIndicator(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 8.dp),
progress = { state.progress },
)
Text(
text = state.currentStep,
style = MaterialTheme.typography.bodyMedium,
modifier = Modifier.padding(vertical = 4.dp)
)
if (state.logs.isNotEmpty()) {
Text(
text = stringResource(id = R.string.horizon_logs_label),
style = MaterialTheme.typography.labelMedium,
modifier = Modifier
.align(Alignment.Start)
.padding(top = 8.dp, bottom = 4.dp)
)
Surface(
modifier = Modifier
.fillMaxWidth()
.heightIn(max = 150.dp)
.padding(vertical = 4.dp),
color = MaterialTheme.colorScheme.surface,
tonalElevation = 1.dp,
shape = MaterialTheme.shapes.small
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp)
.verticalScroll(rememberScrollState())
) {
state.logs.forEach { log ->
Text(
text = log,
style = MaterialTheme.typography.bodySmall,
modifier = Modifier.padding(vertical = 2.dp),
overflow = TextOverflow.Ellipsis,
maxLines = 1
)
}
}
}
}
if (state.error.isNotEmpty()) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.padding(top = 8.dp)
) {
Icon(
imageVector = Icons.Default.Error,
contentDescription = null,
tint = MaterialTheme.colorScheme.error,
modifier = Modifier.padding(end = 8.dp)
)
Text(
text = state.error,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.error
)
}
} else if (state.isCompleted) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.padding(top = 8.dp)
) {
Icon(
imageVector = Icons.Default.CheckCircle,
contentDescription = null,
tint = MaterialTheme.colorScheme.primary,
modifier = Modifier.padding(end = 8.dp)
)
Text(
text = stringResource(id = R.string.horizon_flash_complete),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.primary
)
}
}
}
}
}

View File

@@ -1,224 +0,0 @@
package com.sukisu.ultra.ui.component
import android.net.Uri
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.detectTransformGestures
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Check
import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.Fullscreen
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import coil.compose.AsyncImage
import coil.request.ImageRequest
import com.sukisu.ultra.R
import com.sukisu.ultra.ui.util.BackgroundTransformation
import com.sukisu.ultra.ui.util.saveTransformedBackground
import kotlinx.coroutines.launch
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.layout.onSizeChanged
import kotlin.math.max
@Composable
fun ImageEditorDialog(
imageUri: Uri,
onDismiss: () -> Unit,
onConfirm: (Uri) -> Unit
) {
var scale by remember { mutableFloatStateOf(1f) }
var offsetX by remember { mutableFloatStateOf(0f) }
var offsetY by remember { mutableFloatStateOf(0f) }
val context = LocalContext.current
val scope = rememberCoroutineScope()
val density = LocalDensity.current
var lastScale by remember { mutableFloatStateOf(1f) }
var lastOffsetX by remember { mutableFloatStateOf(0f) }
var lastOffsetY by remember { mutableFloatStateOf(0f) }
var imageSize by remember { mutableStateOf(Size.Zero) }
var screenSize by remember { mutableStateOf(Size.Zero) }
val animatedScale by animateFloatAsState(
targetValue = scale,
label = "ScaleAnimation"
)
val animatedOffsetX by animateFloatAsState(
targetValue = offsetX,
label = "OffsetXAnimation"
)
val animatedOffsetY by animateFloatAsState(
targetValue = offsetY,
label = "OffsetYAnimation"
)
val updateTransformation = remember {
{ newScale: Float, newOffsetX: Float, newOffsetY: Float ->
val scaleDiff = kotlin.math.abs(newScale - lastScale)
val offsetXDiff = kotlin.math.abs(newOffsetX - lastOffsetX)
val offsetYDiff = kotlin.math.abs(newOffsetY - lastOffsetY)
if (scaleDiff > 0.01f || offsetXDiff > 1f || offsetYDiff > 1f) {
scale = newScale
offsetX = newOffsetX
offsetY = newOffsetY
lastScale = newScale
lastOffsetX = newOffsetX
lastOffsetY = newOffsetY
}
}
}
val scaleToFullScreen = remember {
{
if (imageSize.height > 0 && screenSize.height > 0) {
val newScale = screenSize.height / imageSize.height
updateTransformation(newScale, 0f, 0f)
}
}
}
Dialog(
onDismissRequest = onDismiss,
properties = DialogProperties(
dismissOnBackPress = true,
dismissOnClickOutside = false,
usePlatformDefaultWidth = false
)
) {
Box(
modifier = Modifier
.fillMaxSize()
.background(Color.Black.copy(alpha = 0.9f))
.onSizeChanged { size ->
screenSize = Size(size.width.toFloat(), size.height.toFloat())
}
) {
AsyncImage(
model = ImageRequest.Builder(LocalContext.current)
.data(imageUri)
.crossfade(true)
.build(),
contentDescription = stringResource(R.string.settings_custom_background),
contentScale = ContentScale.Fit,
modifier = Modifier
.fillMaxSize()
.graphicsLayer(
scaleX = animatedScale,
scaleY = animatedScale,
translationX = animatedOffsetX,
translationY = animatedOffsetY
)
.pointerInput(Unit) {
detectTransformGestures { _, pan, zoom, _ ->
scope.launch {
try {
val newScale = (scale * zoom).coerceIn(0.5f, 3f)
val maxOffsetX = max(0f, size.width * (newScale - 1) / 2)
val maxOffsetY = max(0f, size.height * (newScale - 1) / 2)
val newOffsetX = if (maxOffsetX > 0) {
(offsetX + pan.x).coerceIn(-maxOffsetX, maxOffsetX)
} else {
0f
}
val newOffsetY = if (maxOffsetY > 0) {
(offsetY + pan.y).coerceIn(-maxOffsetY, maxOffsetY)
} else {
0f
}
updateTransformation(newScale, newOffsetX, newOffsetY)
} catch (e: Exception) {
updateTransformation(lastScale, lastOffsetX, lastOffsetY)
}
}
}
}
.onSizeChanged { size ->
imageSize = Size(size.width.toFloat(), size.height.toFloat())
}
)
Row(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
.align(Alignment.TopCenter),
horizontalArrangement = Arrangement.SpaceBetween
) {
IconButton(
onClick = onDismiss,
modifier = Modifier
.clip(RoundedCornerShape(8.dp))
.background(Color.Black.copy(alpha = 0.6f))
) {
Icon(
imageVector = Icons.Default.Close,
contentDescription = stringResource(R.string.cancel),
tint = Color.White
)
}
IconButton(
onClick = { scaleToFullScreen() },
modifier = Modifier
.clip(RoundedCornerShape(8.dp))
.background(Color.Black.copy(alpha = 0.6f))
) {
Icon(
imageVector = Icons.Default.Fullscreen,
contentDescription = stringResource(R.string.reprovision),
tint = Color.White
)
}
IconButton(
onClick = {
scope.launch {
try {
val transformation = BackgroundTransformation(scale, offsetX, offsetY)
val savedUri = context.saveTransformedBackground(imageUri, transformation)
savedUri?.let { onConfirm(it) }
} catch (e: Exception) {
""
}
}
},
modifier = Modifier
.clip(RoundedCornerShape(8.dp))
.background(Color.Black.copy(alpha = 0.6f))
) {
Icon(
imageVector = Icons.Default.Check,
contentDescription = stringResource(R.string.confirm),
tint = Color.White
)
}
}
Box(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
.clip(RoundedCornerShape(8.dp))
.background(Color.Black.copy(alpha = 0.6f))
.padding(16.dp)
.align(Alignment.BottomCenter)
) {
Text(
text = stringResource(id = R.string.image_editor_hint),
color = Color.White,
style = MaterialTheme.typography.bodyMedium
)
}
}
}
}

View File

@@ -1,210 +0,0 @@
package com.sukisu.ultra.ui.component
import android.content.Context
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.material3.HorizontalDivider
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import com.sukisu.ultra.R
import com.sukisu.ultra.ui.theme.ThemeConfig
import com.sukisu.ultra.ui.theme.getCardElevation
import androidx.compose.foundation.shape.CornerSize
/**
* 槽位选择对话框组件
* 用于HorizonKernel刷写时选择目标槽位
*/
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SlotSelectionDialog(
show: Boolean,
onDismiss: () -> Unit,
onSlotSelected: (String) -> Unit
) {
val context = LocalContext.current
var currentSlot by remember { mutableStateOf<String?>(null) }
var errorMessage by remember { mutableStateOf<String?>(null) }
LaunchedEffect(Unit) {
try {
currentSlot = getCurrentSlot(context)
errorMessage = null
} catch (e: Exception) {
errorMessage = e.message
currentSlot = null
}
}
if (show) {
val backgroundColor = if (!ThemeConfig.useDynamicColor) {
ThemeConfig.currentTheme.ButtonContrast.copy(alpha = 1.0f)
} else {
MaterialTheme.colorScheme.secondaryContainer.copy(alpha = 1.0f)
}
Dialog(onDismissRequest = onDismiss) {
Card(
shape = MaterialTheme.shapes.medium.copy(
topStart = CornerSize(16.dp),
topEnd = CornerSize(16.dp),
bottomEnd = CornerSize(16.dp),
bottomStart = CornerSize(16.dp)
),
colors = CardDefaults.cardColors(
containerColor = backgroundColor
),
elevation = CardDefaults.cardElevation(defaultElevation = getCardElevation())
) {
Column(
modifier = Modifier
.padding(24.dp)
.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = stringResource(id = R.string.select_slot_title),
style = MaterialTheme.typography.headlineSmall,
textAlign = TextAlign.Center,
modifier = Modifier.padding(bottom = 16.dp)
)
if (errorMessage != null) {
Text(
text = "Error: $errorMessage",
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.error,
textAlign = TextAlign.Center
)
} else {
Text(
text = stringResource(
id = R.string.current_slot,
currentSlot ?: "Unknown"
),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
textAlign = TextAlign.Center
)
}
Spacer(modifier = Modifier.height(12.dp))
Text(
text = stringResource(id = R.string.select_slot_description),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
textAlign = TextAlign.Center
)
Spacer(modifier = Modifier.height(24.dp))
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(6.dp)
) {
val isDefaultSlotA = currentSlot == "_a" || currentSlot == "a"
Button(
onClick = { onSlotSelected("a") },
modifier = Modifier.weight(1f),
colors = ButtonDefaults.buttonColors(
containerColor = if (isDefaultSlotA)
MaterialTheme.colorScheme.primary
else
MaterialTheme.colorScheme.primaryContainer,
contentColor = if (isDefaultSlotA)
MaterialTheme.colorScheme.onPrimary
else
MaterialTheme.colorScheme.onPrimaryContainer
)
) {
Text(
text = stringResource(id = R.string.slot_a),
style = MaterialTheme.typography.labelLarge
)
}
val isDefaultSlotB = currentSlot == "_b" || currentSlot == "b"
Button(
onClick = { onSlotSelected("b") },
modifier = Modifier.weight(1f),
colors = ButtonDefaults.buttonColors(
containerColor = if (isDefaultSlotB)
MaterialTheme.colorScheme.secondary
else
MaterialTheme.colorScheme.secondaryContainer,
contentColor = if (isDefaultSlotB)
MaterialTheme.colorScheme.onSecondary
else
MaterialTheme.colorScheme.onSecondaryContainer
)
) {
Text(
text = stringResource(id = R.string.slot_b),
style = MaterialTheme.typography.labelLarge
)
}
}
Spacer(modifier = Modifier.height(24.dp))
HorizontalDivider(
modifier = Modifier.fillMaxWidth(),
thickness = 1.dp,
color = MaterialTheme.colorScheme.outline
)
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
) {
TextButton(
onClick = onDismiss,
modifier = Modifier.weight(1f)
) {
Text(text = stringResource(id = android.R.string.cancel))
}
TextButton(
onClick = {
currentSlot?.let { onSlotSelected(it) }
onDismiss()
},
modifier = Modifier.weight(1f)
) {
Text(text = stringResource(id = android.R.string.ok))
}
}
}
}
}
}
}
// 获取当前槽位信息
private fun getCurrentSlot(context: Context): String? {
return runCommandGetOutput(true, "getprop ro.boot.slot_suffix")?.let {
if (it.startsWith("_")) it.substring(1) else it
}
}
private fun runCommandGetOutput(su: Boolean, cmd: String): String? {
return try {
val process = ProcessBuilder(if (su) "su" else "sh").start()
process.outputStream.bufferedWriter().use { writer ->
writer.write("$cmd\n")
writer.write("exit\n")
writer.flush()
}
process.inputStream.bufferedReader().use { reader ->
reader.readText().trim()
}
} catch (_: Exception) {
null
}
}

View File

@@ -1,114 +0,0 @@
package com.sukisu.ultra.ui.util
import android.content.ContentResolver
import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Canvas
import android.graphics.Matrix
import android.net.Uri
import android.util.Log
import java.io.File
import java.io.FileOutputStream
import java.io.InputStream
import androidx.core.graphics.createBitmap
data class BackgroundTransformation(
val scale: Float = 1f,
val offsetX: Float = 0f,
val offsetY: Float = 0f
)
fun Context.getImageBitmap(uri: Uri): Bitmap? {
return try {
val contentResolver: ContentResolver = contentResolver
val inputStream: InputStream = contentResolver.openInputStream(uri) ?: return null
val bitmap = BitmapFactory.decodeStream(inputStream)
inputStream.close()
bitmap
} catch (e: Exception) {
Log.e("BackgroundUtils", "Failed to get image bitmap: ${e.message}")
null
}
}
fun Context.applyTransformationToBitmap(bitmap: Bitmap, transformation: BackgroundTransformation): Bitmap {
val width = bitmap.width
val height = bitmap.height
// 创建与屏幕比例相同的目标位图
val displayMetrics = resources.displayMetrics
val screenWidth = displayMetrics.widthPixels
val screenHeight = displayMetrics.heightPixels
val screenRatio = screenHeight.toFloat() / screenWidth.toFloat()
// 计算目标宽高
val targetWidth: Int
val targetHeight: Int
if (width.toFloat() / height.toFloat() > screenRatio) {
targetHeight = height
targetWidth = (height / screenRatio).toInt()
} else {
targetWidth = width
targetHeight = (width * screenRatio).toInt()
}
// 创建与目标相同大小的位图
val scaledBitmap = createBitmap(targetWidth, targetHeight)
val canvas = Canvas(scaledBitmap)
val matrix = Matrix()
// 确保缩放值有效
val safeScale = maxOf(0.1f, transformation.scale)
matrix.postScale(safeScale, safeScale)
// 计算中心点
val centerX = targetWidth / 2f
val centerY = targetHeight / 2f
// 计算偏移量,确保不会出现负最大值的问题
val widthDiff = (bitmap.width * safeScale - targetWidth)
val heightDiff = (bitmap.height * safeScale - targetHeight)
// 安全计算偏移量边界
val maxOffsetX = maxOf(0f, widthDiff / 2)
val maxOffsetY = maxOf(0f, heightDiff / 2)
// 限制偏移范围
val safeOffsetX = if (maxOffsetX > 0)
transformation.offsetX.coerceIn(-maxOffsetX, maxOffsetX) else 0f
val safeOffsetY = if (maxOffsetY > 0)
transformation.offsetY.coerceIn(-maxOffsetY, maxOffsetY) else 0f
// 应用偏移量到矩阵
val translationX = -widthDiff / 2 + safeOffsetX
val translationY = -heightDiff / 2 + safeOffsetY
matrix.postTranslate(translationX, translationY)
// 将原始位图绘制到新位图上
canvas.drawBitmap(bitmap, matrix, null)
return scaledBitmap
}
fun Context.saveTransformedBackground(uri: Uri, transformation: BackgroundTransformation): Uri? {
try {
val bitmap = getImageBitmap(uri) ?: return null
val transformedBitmap = applyTransformationToBitmap(bitmap, transformation)
val fileName = "custom_background_transformed.jpg"
val file = File(filesDir, fileName)
val outputStream = FileOutputStream(file)
transformedBitmap.compress(Bitmap.CompressFormat.JPEG, 90, outputStream)
outputStream.flush()
outputStream.close()
return Uri.fromFile(file)
} catch (e: Exception) {
Log.e("BackgroundUtils", "Failed to save transformed image: ${e.message}", e)
return null
}
}

View File

@@ -1,21 +0,0 @@
package io.sukisu.ultra;
import static com.sukisu.ultra.ui.util.KsuCliKt.getKpmmgrPath;
import static com.sukisu.ultra.ui.util.KsuCliKt.getSuSFSDaemonPath;
public class UltraToolInstall {
private static final String OUTSIDE_KPMMGR_PATH = "/data/adb/ksu/bin/kpmmgr";
private static final String OUTSIDE_SUSFSD_PATH = "/data/adb/ksu/bin/susfsd";
public static void tryToInstall() {
String kpmmgrPath = getKpmmgrPath();
if (UltraShellHelper.isPathExists(OUTSIDE_KPMMGR_PATH)) {
UltraShellHelper.CopyFileTo(kpmmgrPath, OUTSIDE_KPMMGR_PATH);
UltraShellHelper.runCmd("chmod a+rx " + OUTSIDE_KPMMGR_PATH);
}
String SuSFSDaemonPath = getSuSFSDaemonPath();
if (UltraShellHelper.isPathExists(OUTSIDE_SUSFSD_PATH)) {
UltraShellHelper.CopyFileTo(SuSFSDaemonPath, OUTSIDE_SUSFSD_PATH);
UltraShellHelper.runCmd("chmod a+rx " + OUTSIDE_SUSFSD_PATH);
}
}
}

View File

@@ -1,8 +1,8 @@
package io.sukisu.ultra; package io.zako.zako;
import java.util.ArrayList; import java.util.ArrayList;
import com.sukisu.ultra.ui.util.KsuCli; import zako.zako.zako.ui.util.KsuCli;
public class UltraShellHelper { public class UltraShellHelper {
public static String runCmd(String cmds) { public static String runCmd(String cmds) {
@@ -19,7 +19,7 @@ public class UltraShellHelper {
} }
public static boolean isPathExists(String path) { public static boolean isPathExists(String path) {
return runCmd("file " + path).contains("No such file or directory"); return !runCmd("file " + path).contains("No such file or directory");
} }
public static void CopyFileTo(String path, String target) { public static void CopyFileTo(String path, String target) {

View File

@@ -0,0 +1,14 @@
package io.zako.zako;
import static zako.zako.zako.ui.util.KsuCliKt.getKpmmgrPath;
public class UltraToolInstall {
private static final String OUTSIDE_KPMMGR_PATH = "/data/adb/ksu/bin/kpmmgr";
public static void tryToInstall() {
String kpmmgrPath = getKpmmgrPath();
if (!UltraShellHelper.isPathExists(OUTSIDE_KPMMGR_PATH)) {
UltraShellHelper.CopyFileTo(kpmmgrPath, OUTSIDE_KPMMGR_PATH);
UltraShellHelper.runCmd("chmod a+rx " + OUTSIDE_KPMMGR_PATH);
}
}
}

View File

@@ -1,4 +1,4 @@
package com.sukisu.ultra package zako.zako.zako
import android.app.Application import android.app.Application
import coil.Coil import coil.Coil

View File

@@ -1,4 +1,4 @@
package com.sukisu.ultra package zako.zako.zako
import android.system.Os import android.system.Os

View File

@@ -1,4 +1,4 @@
package com.sukisu.ultra package zako.zako.zako
import android.os.Parcelable import android.os.Parcelable
import androidx.annotation.Keep import androidx.annotation.Keep
@@ -16,14 +16,14 @@ object Natives {
// 10946: add capabilities // 10946: add capabilities
// 10977: change groups_count and groups to avoid overflow write // 10977: change groups_count and groups to avoid overflow write
// 11071: Fix the issue of failing to set a custom SELinux type. // 11071: Fix the issue of failing to set a custom SELinux type.
const val MINIMAL_SUPPORTED_KERNEL = 12800 const val MINIMAL_SUPPORTED_KERNEL = 11071
// 11640: Support query working mode, LKM or GKI // 11640: Support query working mode, LKM or GKI
// when MINIMAL_SUPPORTED_KERNEL > 11640, we can remove this constant. // when MINIMAL_SUPPORTED_KERNEL > 11640, we can remove this constant.
const val MINIMAL_SUPPORTED_KERNEL_LKM = 12800 const val MINIMAL_SUPPORTED_KERNEL_LKM = 11648
// 12040: Support disable sucompat mode // 12040: Support disable sucompat mode
const val MINIMAL_SUPPORTED_SU_COMPAT = 12800 const val MINIMAL_SUPPORTED_SU_COMPAT = 12040
const val KERNEL_SU_DOMAIN = "u:r:su:s0" const val KERNEL_SU_DOMAIN = "u:r:su:s0"
const val ROOT_UID = 0 const val ROOT_UID = 0
@@ -91,14 +91,6 @@ object Natives {
return version < MINIMAL_SUPPORTED_KERNEL return version < MINIMAL_SUPPORTED_KERNEL
} }
fun isKsuValid(pkgName: String?): Boolean {
if (becomeManager(pkgName)) {
return true
} else {
return false
}
}
@Immutable @Immutable
@Parcelize @Parcelize
@Keep @Keep

View File

@@ -1,4 +1,4 @@
package com.sukisu.ultra.profile package zako.zako.zako.profile
/** /**
* @author weishu * @author weishu

View File

@@ -1,4 +1,4 @@
package com.sukisu.ultra.profile package zako.zako.zako.profile
/** /**
* https://cs.android.com/android/platform/superproject/main/+/main:system/core/libcutils/include/private/android_filesystem_config.h * https://cs.android.com/android/platform/superproject/main/+/main:system/core/libcutils/include/private/android_filesystem_config.h

View File

@@ -1,4 +1,4 @@
package com.sukisu.ultra.ui; package zako.zako.zako.ui;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
@@ -17,7 +17,7 @@ import java.lang.reflect.Method;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import com.sukisu.zako.IKsuInterface; import zako.zako.zako.IKsuInterface;
import rikka.parcelablelist.ParcelableListSlice; import rikka.parcelablelist.ParcelableListSlice;
/** /**

View File

@@ -1,17 +1,34 @@
package com.sukisu.ultra.ui package zako.zako.zako.ui
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge import androidx.activity.enableEdgeToEdge
import androidx.compose.animation.* import androidx.compose.animation.AnimatedContentTransitionScope
import androidx.compose.animation.EnterTransition
import androidx.compose.animation.ExitTransition
import androidx.compose.animation.core.tween import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut import androidx.compose.animation.fadeOut
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.material3.* import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.runtime.* import androidx.compose.foundation.layout.displayCutout
import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.systemBars
import androidx.compose.foundation.layout.union
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.navigation.NavBackStackEntry import androidx.navigation.NavBackStackEntry
@@ -20,16 +37,21 @@ import androidx.navigation.compose.rememberNavController
import com.ramcosta.composedestinations.DestinationsNavHost import com.ramcosta.composedestinations.DestinationsNavHost
import com.ramcosta.composedestinations.animations.NavHostAnimatedDestinationStyle import com.ramcosta.composedestinations.animations.NavHostAnimatedDestinationStyle
import com.ramcosta.composedestinations.generated.NavGraphs import com.ramcosta.composedestinations.generated.NavGraphs
import com.ramcosta.composedestinations.spec.NavHostGraphSpec
import com.ramcosta.composedestinations.spec.RouteOrDirection
import com.ramcosta.composedestinations.utils.isRouteOnBackStackAsState import com.ramcosta.composedestinations.utils.isRouteOnBackStackAsState
import com.ramcosta.composedestinations.utils.rememberDestinationsNavigator import com.ramcosta.composedestinations.utils.rememberDestinationsNavigator
import io.sukisu.ultra.UltraToolInstall import io.zako.zako.UltraToolInstall
import com.sukisu.ultra.Natives import zako.zako.zako.Natives
import com.sukisu.ultra.ksuApp import zako.zako.zako.ksuApp
import com.sukisu.ultra.ui.screen.BottomBarDestination import zako.zako.zako.ui.screen.BottomBarDestination
import com.sukisu.ultra.ui.theme.* import zako.zako.zako.ui.theme.CardConfig
import com.sukisu.ultra.ui.util.* import zako.zako.zako.ui.theme.KernelSUTheme
import zako.zako.zako.ui.theme.loadCustomBackground
import zako.zako.zako.ui.theme.loadThemeMode
import zako.zako.zako.ui.util.LocalSnackbarHost
import zako.zako.zako.ui.util.getKpmVersion
import zako.zako.zako.ui.util.rootAvailable
import zako.zako.zako.ui.util.install
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@@ -67,7 +89,7 @@ class MainActivity : ComponentActivity() {
) { ) {
DestinationsNavHost( DestinationsNavHost(
modifier = Modifier.padding(innerPadding), modifier = Modifier.padding(innerPadding),
navGraph = NavGraphs.root as NavHostGraphSpec, navGraph = NavGraphs.root,
navController = navController, navController = navController,
defaultTransitions = object : NavHostAnimatedDestinationStyle() { defaultTransitions = object : NavHostAnimatedDestinationStyle() {
override val enterTransition: AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition override val enterTransition: AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition
@@ -114,7 +136,7 @@ private fun BottomBar(navController: NavHostController) {
navigator.popBackStack(destination.direction, false) navigator.popBackStack(destination.direction, false)
} }
navigator.navigate(destination.direction) { navigator.navigate(destination.direction) {
popUpTo(NavGraphs.root as RouteOrDirection) { popUpTo(NavGraphs.root) {
saveState = true saveState = true
} }
launchSingleTop = true launchSingleTop = true
@@ -130,7 +152,7 @@ private fun BottomBar(navController: NavHostController) {
}, },
label = { Text(stringResource(destination.label)) }, label = { Text(stringResource(destination.label)) },
alwaysShowLabel = false, alwaysShowLabel = false,
colors = NavigationBarItemDefaults.colors( colors = androidx.compose.material3.NavigationBarItemDefaults.colors(
unselectedTextColor = MaterialTheme.colorScheme.onSurfaceVariant unselectedTextColor = MaterialTheme.colorScheme.onSurfaceVariant
) )
) )
@@ -145,7 +167,7 @@ private fun BottomBar(navController: NavHostController) {
navigator.popBackStack(destination.direction, false) navigator.popBackStack(destination.direction, false)
} }
navigator.navigate(destination.direction) { navigator.navigate(destination.direction) {
popUpTo(NavGraphs.root as RouteOrDirection) { popUpTo(NavGraphs.root) {
saveState = true saveState = true
} }
launchSingleTop = true launchSingleTop = true

View File

@@ -1,4 +1,4 @@
package com.sukisu.ultra.ui.component package zako.zako.zako.ui.component
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
@@ -31,8 +31,8 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.Dialog
import com.sukisu.ultra.BuildConfig import zako.zako.zako.BuildConfig
import com.sukisu.ultra.R import zako.zako.zako.R
@Preview @Preview
@Composable @Composable

View File

@@ -1,4 +1,4 @@
package com.sukisu.ultra.ui.component package zako.zako.zako.ui.component
import android.graphics.text.LineBreaker import android.graphics.text.LineBreaker
import android.os.Build import android.os.Build

View File

@@ -1,4 +1,4 @@
package com.sukisu.ultra.ui.component package zako.zako.zako.ui.component
import androidx.compose.foundation.focusable import androidx.compose.foundation.focusable
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box

View File

@@ -1,4 +1,4 @@
package com.sukisu.ultra.ui.component package zako.zako.zako.ui.component
import android.util.Log import android.util.Log
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
@@ -42,7 +42,7 @@ import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.sukisu.ultra.ui.theme.CardConfig import zako.zako.zako.ui.theme.CardConfig
private const val TAG = "SearchBar" private const val TAG = "SearchBar"

View File

@@ -1,4 +1,4 @@
package com.sukisu.ultra.ui.component package zako.zako.zako.ui.component
import androidx.compose.foundation.LocalIndication import androidx.compose.foundation.LocalIndication
import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.MutableInteractionSource

View File

@@ -1,4 +1,4 @@
package com.sukisu.ultra.ui.component package zako.zako.zako.ui.component
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.ListItem import androidx.compose.material3.ListItem

View File

@@ -1,4 +1,4 @@
package com.sukisu.ultra.ui.component.profile package zako.zako.zako.ui.component.profile
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextField
@@ -11,9 +11,9 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import com.sukisu.ultra.Natives import zako.zako.zako.Natives
import com.sukisu.ultra.R import zako.zako.zako.R
import com.sukisu.ultra.ui.component.SwitchItem import zako.zako.zako.ui.component.SwitchItem
@Composable @Composable
fun AppProfileConfig( fun AppProfileConfig(

View File

@@ -1,4 +1,4 @@
package com.sukisu.ultra.ui.component.profile package zako.zako.zako.ui.component.profile
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
@@ -42,12 +42,12 @@ import com.maxkeppeler.sheets.input.models.ValidationResult
import com.maxkeppeler.sheets.list.ListDialog import com.maxkeppeler.sheets.list.ListDialog
import com.maxkeppeler.sheets.list.models.ListOption import com.maxkeppeler.sheets.list.models.ListOption
import com.maxkeppeler.sheets.list.models.ListSelection import com.maxkeppeler.sheets.list.models.ListSelection
import com.sukisu.ultra.Natives import zako.zako.zako.Natives
import com.sukisu.ultra.R import zako.zako.zako.R
import com.sukisu.ultra.profile.Capabilities import zako.zako.zako.profile.Capabilities
import com.sukisu.ultra.profile.Groups import zako.zako.zako.profile.Groups
import com.sukisu.ultra.ui.component.rememberCustomDialog import zako.zako.zako.ui.component.rememberCustomDialog
import com.sukisu.ultra.ui.util.isSepolicyValid import zako.zako.zako.ui.util.isSepolicyValid
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable

View File

@@ -1,4 +1,4 @@
package com.sukisu.ultra.ui.component.profile package zako.zako.zako.ui.component.profile
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
@@ -23,11 +23,11 @@ import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import com.sukisu.ultra.Natives import zako.zako.zako.Natives
import com.sukisu.ultra.R import zako.zako.zako.R
import com.sukisu.ultra.ui.util.listAppProfileTemplates import zako.zako.zako.ui.util.listAppProfileTemplates
import com.sukisu.ultra.ui.util.setSepolicy import zako.zako.zako.ui.util.setSepolicy
import com.sukisu.ultra.ui.viewmodel.getTemplateInfoById import zako.zako.zako.ui.viewmodel.getTemplateInfoById
/** /**
* @author weishu * @author weishu

View File

@@ -1,4 +1,4 @@
package com.sukisu.ultra.ui.screen package zako.zako.zako.ui.screen
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.compose.animation.Crossfade import androidx.compose.animation.Crossfade
@@ -64,20 +64,20 @@ import com.ramcosta.composedestinations.generated.destinations.AppProfileTemplat
import com.ramcosta.composedestinations.generated.destinations.TemplateEditorScreenDestination import com.ramcosta.composedestinations.generated.destinations.TemplateEditorScreenDestination
import com.ramcosta.composedestinations.navigation.DestinationsNavigator import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import com.sukisu.ultra.Natives import zako.zako.zako.Natives
import com.sukisu.ultra.R import zako.zako.zako.R
import com.sukisu.ultra.ui.component.SwitchItem import zako.zako.zako.ui.component.SwitchItem
import com.sukisu.ultra.ui.component.profile.AppProfileConfig import zako.zako.zako.ui.component.profile.AppProfileConfig
import com.sukisu.ultra.ui.component.profile.RootProfileConfig import zako.zako.zako.ui.component.profile.RootProfileConfig
import com.sukisu.ultra.ui.component.profile.TemplateConfig import zako.zako.zako.ui.component.profile.TemplateConfig
import com.sukisu.ultra.ui.util.LocalSnackbarHost import zako.zako.zako.ui.util.LocalSnackbarHost
import com.sukisu.ultra.ui.util.forceStopApp import zako.zako.zako.ui.util.forceStopApp
import com.sukisu.ultra.ui.util.getSepolicy import zako.zako.zako.ui.util.getSepolicy
import com.sukisu.ultra.ui.util.launchApp import zako.zako.zako.ui.util.launchApp
import com.sukisu.ultra.ui.util.restartApp import zako.zako.zako.ui.util.restartApp
import com.sukisu.ultra.ui.util.setSepolicy import zako.zako.zako.ui.util.setSepolicy
import com.sukisu.ultra.ui.viewmodel.SuperUserViewModel import zako.zako.zako.ui.viewmodel.SuperUserViewModel
import com.sukisu.ultra.ui.viewmodel.getTemplateInfoById import zako.zako.zako.ui.viewmodel.getTemplateInfoById
/** /**
* @author weishu * @author weishu

View File

@@ -1,4 +1,4 @@
package com.sukisu.ultra.ui.screen package zako.zako.zako.ui.screen
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
@@ -11,7 +11,7 @@ import com.ramcosta.composedestinations.generated.destinations.SuperUserScreenDe
import com.ramcosta.composedestinations.generated.destinations.SettingScreenDestination import com.ramcosta.composedestinations.generated.destinations.SettingScreenDestination
import com.ramcosta.composedestinations.generated.destinations.KpmScreenDestination import com.ramcosta.composedestinations.generated.destinations.KpmScreenDestination
import com.ramcosta.composedestinations.spec.DirectionDestinationSpec import com.ramcosta.composedestinations.spec.DirectionDestinationSpec
import com.sukisu.ultra.R import zako.zako.zako.R
enum class BottomBarDestination( enum class BottomBarDestination(
val direction: DirectionDestinationSpec, val direction: DirectionDestinationSpec,

View File

@@ -1,4 +1,4 @@
package com.sukisu.ultra.ui.screen package zako.zako.zako.ui.screen
import android.os.Environment import android.os.Environment
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
@@ -37,10 +37,10 @@ import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import com.sukisu.ultra.R import zako.zako.zako.R
import com.sukisu.ultra.ui.component.KeyEventBlocker import zako.zako.zako.ui.component.KeyEventBlocker
import com.sukisu.ultra.ui.util.LocalSnackbarHost import zako.zako.zako.ui.util.LocalSnackbarHost
import com.sukisu.ultra.ui.util.runModuleAction import zako.zako.zako.ui.util.runModuleAction
import java.io.File import java.io.File
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Date import java.util.Date

View File

@@ -1,4 +1,4 @@
package com.sukisu.ultra.ui.screen package zako.zako.zako.ui.screen
import android.net.Uri import android.net.Uri
import android.os.Environment import android.os.Environment
@@ -30,9 +30,9 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import com.sukisu.ultra.ui.component.KeyEventBlocker import zako.zako.zako.ui.component.KeyEventBlocker
import com.sukisu.ultra.ui.util.* import zako.zako.zako.ui.util.*
import com.sukisu.ultra.R import zako.zako.zako.R
import java.io.File import java.io.File
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
@@ -45,6 +45,10 @@ enum class FlashingStatus {
private var currentFlashingStatus = mutableStateOf(FlashingStatus.FLASHING) private var currentFlashingStatus = mutableStateOf(FlashingStatus.FLASHING)
fun getFlashingStatus(): FlashingStatus {
return currentFlashingStatus.value
}
fun setFlashingStatus(status: FlashingStatus) { fun setFlashingStatus(status: FlashingStatus) {
currentFlashingStatus.value = status currentFlashingStatus.value = status
} }

View File

@@ -1,4 +1,4 @@
package com.sukisu.ultra.ui.screen package zako.zako.zako.ui.screen
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Context import android.content.Context
@@ -33,15 +33,15 @@ import com.ramcosta.composedestinations.generated.destinations.SettingScreenDest
import com.ramcosta.composedestinations.navigation.DestinationsNavigator import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import com.sukisu.ultra.* import zako.zako.zako.*
import com.sukisu.ultra.R import zako.zako.zako.R
import com.sukisu.ultra.ui.component.rememberConfirmDialog import zako.zako.zako.ui.component.rememberConfirmDialog
import com.sukisu.ultra.ui.util.* import zako.zako.zako.ui.util.*
import com.sukisu.ultra.ui.util.module.LatestVersionInfo import zako.zako.zako.ui.util.module.LatestVersionInfo
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import com.sukisu.ultra.ui.theme.getCardColors import zako.zako.zako.ui.theme.getCardColors
import com.sukisu.ultra.ui.theme.getCardElevation import zako.zako.zako.ui.theme.getCardElevation
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
@@ -49,8 +49,7 @@ import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.fadeOut import androidx.compose.animation.fadeOut
import androidx.compose.animation.shrinkVertically import androidx.compose.animation.shrinkVertically
import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.graphics.vector.ImageVector import zako.zako.zako.ui.theme.CardConfig
import com.sukisu.ultra.ui.theme.CardConfig
import androidx.core.content.edit import androidx.core.content.edit
import java.io.BufferedReader import java.io.BufferedReader
import java.io.InputStreamReader import java.io.InputStreamReader
@@ -93,9 +92,11 @@ fun HomeScreen(navigator: DestinationsNavigator) {
val isManager = Natives.becomeManager(ksuApp.packageName) val isManager = Natives.becomeManager(ksuApp.packageName)
val deviceModel = getDeviceModel(context) val deviceModel = getDeviceModel(context)
val ksuVersion = if (isManager) Natives.version else null val ksuVersion = if (isManager) Natives.version else null
val zako = "一.*加.*A.*c.*e.*5.*P.*r.*o".toRegex().matches(deviceModel) val managerVersion = getManagerVersion(context).second
val Zako = "一.*加.*A.*c.*e.*5.*P.*r.*o".toRegex().matches(deviceModel)
val isVersion = ksuVersion == 12777 val isVersion = ksuVersion == 12777
val shouldTriggerRestart = zako && kernelVersion.isGKI() && (isVersion) val isManagerVersionValid = managerVersion > (ksuVersion ?: 0) + 33
val shouldTriggerRestart = Zako && kernelVersion.isGKI() && (isVersion || isManagerVersionValid)
LaunchedEffect(shouldTriggerRestart) { LaunchedEffect(shouldTriggerRestart) {
if (shouldTriggerRestart) { if (shouldTriggerRestart) {
@@ -284,18 +285,14 @@ private fun TopBar(
} }
var showDropdown by remember { mutableStateOf(false) } var showDropdown by remember { mutableStateOf(false) }
if (Natives.isKsuValid(ksuApp.packageName)) {
IconButton(onClick = { showDropdown = true }) { IconButton(onClick = { showDropdown = true }) {
Icon(Icons.Filled.Refresh, stringResource(R.string.reboot)) Icon(Icons.Filled.Refresh, stringResource(R.string.reboot))
DropdownMenu( DropdownMenu(expanded = showDropdown, onDismissRequest = { showDropdown = false }
expanded = showDropdown,
onDismissRequest = { showDropdown = false }
) { ) {
RebootDropdownItem(id = R.string.reboot) RebootDropdownItem(id = R.string.reboot)
val pm = val pm = LocalContext.current.getSystemService(Context.POWER_SERVICE) as PowerManager?
LocalContext.current.getSystemService(Context.POWER_SERVICE) as PowerManager?
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && pm?.isRebootingUserspaceSupported == true) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && pm?.isRebootingUserspaceSupported == true) {
RebootDropdownItem(id = R.string.reboot_userspace, reason = "userspace") RebootDropdownItem(id = R.string.reboot_userspace, reason = "userspace")
@@ -306,14 +303,12 @@ private fun TopBar(
RebootDropdownItem(id = R.string.reboot_edl, reason = "edl") RebootDropdownItem(id = R.string.reboot_edl, reason = "edl")
} }
} }
}
}, },
windowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal), windowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal),
scrollBehavior = scrollBehavior scrollBehavior = scrollBehavior
) )
} }
@Composable @Composable
private fun StatusCard( private fun StatusCard(
kernelVersion: KernelVersion, kernelVersion: KernelVersion,
@@ -562,7 +557,6 @@ fun DonateCard() {
@Composable @Composable
private fun InfoCard() { private fun InfoCard() {
val lkmMode = Natives.isLkmMode
val context = LocalContext.current val context = LocalContext.current
val isSimpleMode = LocalContext.current.getSharedPreferences("settings", Context.MODE_PRIVATE) val isSimpleMode = LocalContext.current.getSharedPreferences("settings", Context.MODE_PRIVATE)
.getBoolean("is_simple_mode", false) .getBoolean("is_simple_mode", false)
@@ -574,7 +568,7 @@ private fun InfoCard() {
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(start = 24.dp, top = 24.dp, end = 24.dp, bottom = 16.dp), .padding(start = 24.dp, top = 24.dp, end = 24.dp, bottom = 16.dp)
) withContext@{ ) withContext@{
val contents = StringBuilder() val contents = StringBuilder()
val uname = Os.uname() val uname = Os.uname()
@@ -583,44 +577,42 @@ private fun InfoCard() {
fun InfoCardItem( fun InfoCardItem(
label: String, label: String,
content: String, content: String,
icon: ImageVector = Icons.Default.Info
) { ) {
contents.appendLine(label).appendLine(content).appendLine() contents.appendLine(label).appendLine(content).appendLine()
Row(verticalAlignment = Alignment.CenterVertically) {
Icon(
imageVector = icon,
contentDescription = label,
modifier = Modifier.size(24.dp)
)
Spacer(modifier = Modifier.width(16.dp))
Column {
Text(text = label, style = MaterialTheme.typography.bodyLarge) Text(text = label, style = MaterialTheme.typography.bodyLarge)
Text(text = content, style = MaterialTheme.typography.bodyMedium) Text(text = content, style = MaterialTheme.typography.bodyMedium)
} }
}
}
InfoCardItem(stringResource(R.string.home_kernel), uname.release, icon = Icons.Default.Memory) InfoCardItem(stringResource(R.string.home_kernel), uname.release)
if (!isSimpleMode) { if (!isSimpleMode) {
Spacer(Modifier.height(16.dp)) Spacer(Modifier.height(16.dp))
val androidVersion = Build.VERSION.RELEASE val androidVersion = Build.VERSION.RELEASE
InfoCardItem(stringResource(R.string.home_android_version), androidVersion, icon = Icons.Default.Android) InfoCardItem(stringResource(R.string.home_android_version), androidVersion)
} }
Spacer(Modifier.height(16.dp)) Spacer(Modifier.height(16.dp))
val deviceModel = getDeviceModel(context) val deviceModel = getDeviceModel(context)
InfoCardItem(stringResource(R.string.home_device_model), deviceModel, icon = Icons.Default.PhoneAndroid) InfoCardItem(stringResource(R.string.home_device_model), deviceModel)
Spacer(Modifier.height(16.dp)) Spacer(Modifier.height(16.dp))
val managerVersion = getManagerVersion(context) val managerVersion = getManagerVersion(context)
InfoCardItem(stringResource(R.string.home_manager_version), "${managerVersion.first} (${managerVersion.second})", icon = Icons.Default.Settings) InfoCardItem(
stringResource(R.string.home_manager_version),
"${managerVersion.first} (${managerVersion.second})"
)
Spacer(Modifier.height(16.dp)) Spacer(Modifier.height(16.dp))
InfoCardItem(stringResource(R.string.home_selinux_status), getSELinuxStatus(), icon = Icons.Default.Security) InfoCardItem(stringResource(R.string.home_selinux_status), getSELinuxStatus())
if (!isSimpleMode) { if (!isSimpleMode) {
if (lkmMode != true) {
val kpmVersion = getKpmVersion() val kpmVersion = getKpmVersion()
var displayVersion: String var displayVersion: String
val isKpmConfigured = checkKpmConfigured() val isKpmConfigured = checkKpmConfigured()
@@ -636,8 +628,7 @@ private fun InfoCard() {
displayVersion = "${stringResource(R.string.supported)} ($kpmVersion)" displayVersion = "${stringResource(R.string.supported)} ($kpmVersion)"
} }
Spacer(Modifier.height(16.dp)) Spacer(Modifier.height(16.dp))
InfoCardItem(stringResource(R.string.home_kpm_version), displayVersion, icon = Icons.Default.Code) InfoCardItem(stringResource(R.string.home_kpm_version), displayVersion)
}
} }
val isHideSusfsStatus = LocalContext.current.getSharedPreferences("settings", Context.MODE_PRIVATE) val isHideSusfsStatus = LocalContext.current.getSharedPreferences("settings", Context.MODE_PRIVATE)
@@ -662,7 +653,9 @@ private fun InfoCard() {
} }
} }
InfoCardItem( InfoCardItem(
stringResource(R.string.home_susfs_version), infoText, icon = Icons.Default.Storage) stringResource(R.string.home_susfs_version),
infoText
)
} }
} }
} }
@@ -716,7 +709,7 @@ private fun getDeviceModel(context: Context): String {
} }
} }
Build.DEVICE Build.DEVICE
} catch (_: Exception) { } catch (e: Exception) {
Build.DEVICE Build.DEVICE
} }
} }

View File

@@ -1,17 +1,13 @@
package com.sukisu.ultra.ui.screen package zako.zako.zako.ui.screen
import android.app.Activity import android.app.Activity
import android.content.Context
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.widget.Toast import android.widget.Toast
import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.expandVertically
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.shrinkVertically
import androidx.compose.foundation.LocalIndication import androidx.compose.foundation.LocalIndication
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.MutableInteractionSource
@@ -32,25 +28,26 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.Role
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.documentfile.provider.DocumentFile
import com.maxkeppeler.sheets.list.models.ListOption import com.maxkeppeler.sheets.list.models.ListOption
import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.annotation.RootGraph import com.ramcosta.composedestinations.annotation.RootGraph
import com.ramcosta.composedestinations.generated.destinations.FlashScreenDestination import com.ramcosta.composedestinations.generated.destinations.FlashScreenDestination
import com.ramcosta.composedestinations.navigation.DestinationsNavigator import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import com.ramcosta.composedestinations.navigation.EmptyDestinationsNavigator import com.ramcosta.composedestinations.navigation.EmptyDestinationsNavigator
import com.sukisu.ultra.R import zako.zako.zako.ui.component.DialogHandle
import com.sukisu.ultra.ui.component.DialogHandle import zako.zako.zako.ui.component.rememberConfirmDialog
import com.sukisu.ultra.ui.component.SlotSelectionDialog import zako.zako.zako.ui.component.rememberCustomDialog
import com.sukisu.ultra.ui.component.rememberConfirmDialog import zako.zako.zako.ui.theme.ThemeConfig
import com.sukisu.ultra.ui.component.rememberCustomDialog import zako.zako.zako.ui.theme.getCardColors
import com.sukisu.ultra.flash.HorizonKernelFlashProgress import zako.zako.zako.ui.theme.getCardElevation
import com.sukisu.ultra.flash.HorizonKernelState import zako.zako.zako.ui.util.*
import com.sukisu.ultra.flash.HorizonKernelWorker import zako.zako.zako.R
import com.sukisu.ultra.ui.theme.CardConfig import zako.zako.zako.utils.AssetsUtil
import com.sukisu.ultra.ui.theme.ThemeConfig import java.io.File
import com.sukisu.ultra.ui.theme.getCardColors import java.io.FileOutputStream
import com.sukisu.ultra.ui.theme.getCardElevation import java.io.IOException
import com.sukisu.ultra.ui.util.*
/** /**
* @author weishu * @author weishu
@@ -63,12 +60,8 @@ fun InstallScreen(navigator: DestinationsNavigator) {
var installMethod by remember { mutableStateOf<InstallMethod?>(null) } var installMethod by remember { mutableStateOf<InstallMethod?>(null) }
var lkmSelection by remember { mutableStateOf<LkmSelection>(LkmSelection.KmiNone) } var lkmSelection by remember { mutableStateOf<LkmSelection>(LkmSelection.KmiNone) }
val context = LocalContext.current val context = LocalContext.current
var showRebootDialog by remember { mutableStateOf(false) } var showRebootDialog by remember { mutableStateOf(false) }
var showSlotSelectionDialog by remember { mutableStateOf(false) }
var tempKernelUri by remember { mutableStateOf<Uri?>(null) }
val horizonKernelState = remember { HorizonKernelState() }
val flashState by horizonKernelState.state.collectAsState()
val summary = stringResource(R.string.horizon_kernel_summary)
val onFlashComplete = { val onFlashComplete = {
showRebootDialog = true showRebootDialog = true
@@ -86,7 +79,7 @@ fun InstallScreen(navigator: DestinationsNavigator) {
writer.write("svc power reboot\n") writer.write("svc power reboot\n")
writer.write("exit\n") writer.write("exit\n")
} }
} catch (_: Exception) { } catch (e: Exception) {
Toast.makeText(context, R.string.failed_reboot, Toast.LENGTH_SHORT).show() Toast.makeText(context, R.string.failed_reboot, Toast.LENGTH_SHORT).show()
} }
} }
@@ -98,11 +91,7 @@ fun InstallScreen(navigator: DestinationsNavigator) {
when (method) { when (method) {
is InstallMethod.HorizonKernel -> { is InstallMethod.HorizonKernel -> {
method.uri?.let { uri -> method.uri?.let { uri ->
val worker = HorizonKernelWorker( val worker = HorizonKernelWorker(context)
context = context,
state = horizonKernelState,
slot = method.slot
)
worker.uri = uri worker.uri = uri
worker.setOnFlashCompleteListener(onFlashComplete) worker.setOnFlashCompleteListener(onFlashComplete)
worker.start() worker.start()
@@ -121,22 +110,6 @@ fun InstallScreen(navigator: DestinationsNavigator) {
Unit Unit
} }
// 槽位选择
SlotSelectionDialog(
show = showSlotSelectionDialog,
onDismiss = { showSlotSelectionDialog = false },
onSlotSelected = { slot ->
showSlotSelectionDialog = false
val horizonMethod = InstallMethod.HorizonKernel(
uri = tempKernelUri,
slot = slot,
summary = summary
)
installMethod = horizonMethod
onInstall()
}
)
val currentKmi by produceState(initialValue = "") { val currentKmi by produceState(initialValue = "") {
value = getCurrentKmi() value = getCurrentKmi()
} }
@@ -192,25 +165,9 @@ fun InstallScreen(navigator: DestinationsNavigator) {
.nestedScroll(scrollBehavior.nestedScrollConnection) .nestedScroll(scrollBehavior.nestedScrollConnection)
.verticalScroll(rememberScrollState()) .verticalScroll(rememberScrollState())
) { ) {
SelectInstallMethod( SelectInstallMethod { method ->
onSelected = { method ->
if (method is InstallMethod.HorizonKernel && method.uri != null && method.slot == null) {
tempKernelUri = method.uri
showSlotSelectionDialog = true
} else {
installMethod = method installMethod = method
} }
horizonKernelState.reset()
}
)
AnimatedVisibility(
visible = flashState.isFlashing && installMethod is InstallMethod.HorizonKernel,
enter = fadeIn() + expandVertically(),
exit = fadeOut() + shrinkVertically()
) {
HorizonKernelFlashProgress(flashState)
}
Column( Column(
modifier = Modifier modifier = Modifier
@@ -225,20 +182,9 @@ fun InstallScreen(navigator: DestinationsNavigator) {
) )
) )
} }
(installMethod as? InstallMethod.HorizonKernel)?.let { method ->
if (method.slot != null) {
Text(
stringResource(
id = R.string.selected_slot,
if (method.slot == "a") stringResource(id = R.string.slot_a)
else stringResource(id = R.string.slot_b)
)
)
}
}
Button( Button(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
enabled = installMethod != null && !flashState.isFlashing, enabled = installMethod != null,
onClick = onClickNext onClick = onClickNext
) { ) {
Text( Text(
@@ -251,6 +197,7 @@ fun InstallScreen(navigator: DestinationsNavigator) {
} }
} }
@Composable @Composable
private fun RebootDialog( private fun RebootDialog(
show: Boolean, show: Boolean,
@@ -276,6 +223,133 @@ private fun RebootDialog(
} }
} }
private class HorizonKernelWorker(private val context: Context) : Thread() {
var uri: Uri? = null
private lateinit var filePath: String
private lateinit var binaryPath: String
private var onFlashComplete: (() -> Unit)? = null
fun setOnFlashCompleteListener(listener: () -> Unit) {
onFlashComplete = listener
}
override fun run() {
filePath = "${context.filesDir.absolutePath}/${DocumentFile.fromSingleUri(context, uri!!)?.name}"
binaryPath = "${context.filesDir.absolutePath}/META-INF/com/google/android/update-binary"
try {
cleanup()
if (!rootAvailable()) {
showError(context.getString(R.string.root_required))
return
}
copy()
if (!File(filePath).exists()) {
showError(context.getString(R.string.copy_failed))
return
}
getBinary()
patch()
flash()
(context as? Activity)?.runOnUiThread {
onFlashComplete?.invoke()
}
} catch (e: Exception) {
showError(e.message ?: context.getString(R.string.unknown_error))
}
}
private fun cleanup() {
runCommand(false, "find ${context.filesDir.absolutePath} -type f ! -name '*.jpg' ! -name '*.png' -delete")
}
private fun copy() {
uri?.let { safeUri ->
context.contentResolver.openInputStream(safeUri)?.use { input ->
FileOutputStream(File(filePath)).use { output ->
input.copyTo(output)
}
}
}
}
private fun getBinary() {
runCommand(false, "unzip \"$filePath\" \"*/update-binary\" -d ${context.filesDir.absolutePath}")
if (!File(binaryPath).exists()) {
throw IOException("Failed to extract update-binary")
}
}
private fun patch() {
val mkbootfsPath = "${context.filesDir.absolutePath}/mkbootfs"
AssetsUtil.exportFiles(context, "mkbootfs", mkbootfsPath)
runCommand(false, "sed -i '/chmod -R 755 tools bin;/i cp -f $mkbootfsPath \$AKHOME/tools;' $binaryPath")
}
private fun flash() {
val process = ProcessBuilder("su")
.redirectErrorStream(true)
.start()
try {
process.outputStream.bufferedWriter().use { writer ->
writer.write("export POSTINSTALL=${context.filesDir.absolutePath}\n")
writer.write("sh $binaryPath 3 1 \"$filePath\" && touch ${context.filesDir.absolutePath}/done\nexit\n")
writer.flush()
}
process.inputStream.bufferedReader().use { reader ->
reader.lineSequence().forEach { line ->
if (line.startsWith("ui_print")) {
showLog(line.removePrefix("ui_print"))
}
}
}
} finally {
process.destroy()
}
if (!File("${context.filesDir.absolutePath}/done").exists()) {
throw IOException("Flash failed")
}
}
private fun runCommand(su: Boolean, cmd: String): Int {
val process = ProcessBuilder(if (su) "su" else "sh")
.redirectErrorStream(true)
.start()
return try {
process.outputStream.bufferedWriter().use { writer ->
writer.write("$cmd\n")
writer.write("exit\n")
writer.flush()
}
process.waitFor()
} finally {
process.destroy()
}
}
private fun showError(message: String) {
(context as? Activity)?.runOnUiThread {
Toast.makeText(context, message, Toast.LENGTH_LONG).show()
}
}
private fun showLog(message: String) {
(context as? Activity)?.runOnUiThread {
Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
}
}
}
sealed class InstallMethod { sealed class InstallMethod {
data class SelectFile( data class SelectFile(
val uri: Uri? = null, val uri: Uri? = null,
@@ -295,7 +369,6 @@ sealed class InstallMethod {
data class HorizonKernel( data class HorizonKernel(
val uri: Uri? = null, val uri: Uri? = null,
val slot: String? = null,
@StringRes override val label: Int = R.string.horizon_kernel, @StringRes override val label: Int = R.string.horizon_kernel,
override val summary: String? = null override val summary: String? = null
) : InstallMethod() ) : InstallMethod()
@@ -308,7 +381,6 @@ sealed class InstallMethod {
private fun SelectInstallMethod(onSelected: (InstallMethod) -> Unit = {}) { private fun SelectInstallMethod(onSelected: (InstallMethod) -> Unit = {}) {
val rootAvailable = rootAvailable() val rootAvailable = rootAvailable()
val isAbDevice = isAbDevice() val isAbDevice = isAbDevice()
val horizonKernelSummary = stringResource(R.string.horizon_kernel_summary)
val selectFileTip = stringResource( val selectFileTip = stringResource(
id = R.string.select_file_tip, id = R.string.select_file_tip,
if (isInitBoot()) "init_boot" else "boot" if (isInitBoot()) "init_boot" else "boot"
@@ -323,7 +395,7 @@ private fun SelectInstallMethod(onSelected: (InstallMethod) -> Unit = {}) {
if (isAbDevice) { if (isAbDevice) {
radioOptions.add(InstallMethod.DirectInstallToInactiveSlot) radioOptions.add(InstallMethod.DirectInstallToInactiveSlot)
} }
radioOptions.add(InstallMethod.HorizonKernel(summary = horizonKernelSummary)) radioOptions.add(InstallMethod.HorizonKernel(summary = "Flashing the Anykernel3 Kernel"))
} }
var selectedOption by remember { mutableStateOf<InstallMethod?>(null) } var selectedOption by remember { mutableStateOf<InstallMethod?>(null) }
@@ -336,7 +408,7 @@ private fun SelectInstallMethod(onSelected: (InstallMethod) -> Unit = {}) {
it.data?.data?.let { uri -> it.data?.data?.let { uri ->
val option = when (currentSelectingMethod) { val option = when (currentSelectingMethod) {
is InstallMethod.SelectFile -> InstallMethod.SelectFile(uri, summary = selectFileTip) is InstallMethod.SelectFile -> InstallMethod.SelectFile(uri, summary = selectFileTip)
is InstallMethod.HorizonKernel -> InstallMethod.HorizonKernel(uri, summary = horizonKernelSummary) is InstallMethod.HorizonKernel -> InstallMethod.HorizonKernel(uri, summary = " Flashing the Anykernel3 Kernel")
else -> null else -> null
} }
option?.let { option?.let {
@@ -508,15 +580,8 @@ private fun TopBar(
onLkmUpload: () -> Unit = {}, onLkmUpload: () -> Unit = {},
scrollBehavior: TopAppBarScrollBehavior? = null scrollBehavior: TopAppBarScrollBehavior? = null
) { ) {
val cardColor = MaterialTheme.colorScheme.secondaryContainer
val cardAlpha = CardConfig.cardAlpha
TopAppBar( TopAppBar(
title = { Text(stringResource(R.string.install)) }, title = { Text(stringResource(R.string.install)) },
colors = TopAppBarDefaults.topAppBarColors(
containerColor = cardColor.copy(alpha = cardAlpha),
scrolledContainerColor = cardColor.copy(alpha = cardAlpha)
),
navigationIcon = { navigationIcon = {
IconButton(onClick = onBack) { IconButton(onClick = onBack) {
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null) Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null)

View File

@@ -1,4 +1,4 @@
package com.sukisu.ultra.ui.screen package zako.zako.zako.ui.screen
import android.app.Activity.* import android.app.Activity.*
import android.content.Context import android.content.Context
@@ -88,32 +88,32 @@ import com.ramcosta.composedestinations.navigation.EmptyDestinationsNavigator
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import com.sukisu.ultra.Natives import zako.zako.zako.Natives
import com.sukisu.ultra.ui.component.ConfirmResult import zako.zako.zako.ui.component.ConfirmResult
import com.sukisu.ultra.ui.component.SearchAppBar import zako.zako.zako.ui.component.SearchAppBar
import com.sukisu.ultra.ui.component.rememberConfirmDialog import zako.zako.zako.ui.component.rememberConfirmDialog
import com.sukisu.ultra.ui.component.rememberLoadingDialog import zako.zako.zako.ui.component.rememberLoadingDialog
import com.sukisu.ultra.ui.util.DownloadListener import zako.zako.zako.ui.util.DownloadListener
import com.sukisu.ultra.ui.util.* import zako.zako.zako.ui.util.*
import com.sukisu.ultra.ui.util.download import zako.zako.zako.ui.util.download
import com.sukisu.ultra.ui.util.hasMagisk import zako.zako.zako.ui.util.hasMagisk
import com.sukisu.ultra.ui.util.reboot import zako.zako.zako.ui.util.reboot
import com.sukisu.ultra.ui.util.restoreModule import zako.zako.zako.ui.util.restoreModule
import com.sukisu.ultra.ui.util.toggleModule import zako.zako.zako.ui.util.toggleModule
import com.sukisu.ultra.ui.util.uninstallModule import zako.zako.zako.ui.util.uninstallModule
import com.sukisu.ultra.ui.webui.WebUIActivity import zako.zako.zako.ui.webui.WebUIActivity
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import com.sukisu.ultra.ui.util.ModuleModify import zako.zako.zako.ui.util.ModuleModify
import com.sukisu.ultra.ui.theme.getCardColors import zako.zako.zako.ui.theme.getCardColors
import com.sukisu.ultra.ui.theme.getCardElevation import zako.zako.zako.ui.theme.getCardElevation
import com.sukisu.ultra.ui.viewmodel.ModuleViewModel import zako.zako.zako.ui.viewmodel.ModuleViewModel
import java.io.BufferedReader import java.io.BufferedReader
import java.io.InputStreamReader import java.io.InputStreamReader
import java.util.zip.ZipInputStream import java.util.zip.ZipInputStream
import androidx.core.content.edit import androidx.core.content.edit
import androidx.core.net.toUri import androidx.core.net.toUri
import com.sukisu.ultra.ui.theme.ThemeConfig import zako.zako.zako.ui.theme.ThemeConfig
import com.sukisu.ultra.R import zako.zako.zako.R
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)

View File

@@ -1,4 +1,4 @@
package com.sukisu.ultra.ui.screen package zako.zako.zako.ui.screen
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import android.content.Context import android.content.Context
@@ -26,6 +26,8 @@ import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.Slider
import androidx.compose.material3.SliderDefaults
import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.annotation.Destination
@@ -35,13 +37,21 @@ import com.topjohnwu.superuser.Shell
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import com.sukisu.ultra.ui.component.ImageEditorDialog import zako.zako.zako.ui.component.SwitchItem
import com.sukisu.ultra.ui.component.SwitchItem import zako.zako.zako.ui.theme.CardConfig
import com.sukisu.ultra.ui.theme.* import zako.zako.zako.ui.theme.ThemeColors
import com.sukisu.ultra.ui.util.* import zako.zako.zako.ui.theme.ThemeConfig
import zako.zako.zako.ui.theme.saveCustomBackground
import zako.zako.zako.ui.theme.saveThemeColors
import zako.zako.zako.ui.theme.saveThemeMode
import zako.zako.zako.ui.theme.saveDynamicColorState
import zako.zako.zako.ui.util.getSuSFS
import zako.zako.zako.ui.util.getSuSFSFeatures
import zako.zako.zako.ui.util.susfsSUS_SU_0
import zako.zako.zako.ui.util.susfsSUS_SU_2
import zako.zako.zako.ui.util.susfsSUS_SU_Mode
import androidx.core.content.edit import androidx.core.content.edit
import com.sukisu.ultra.R import zako.zako.zako.R
import com.sukisu.ultra.*
fun saveCardConfig(context: Context) { fun saveCardConfig(context: Context) {
@@ -141,10 +151,6 @@ fun MoreSettingsScreen(navigator: DestinationsNavigator) {
mutableStateOf(ThemeConfig.customBackgroundUri != null) mutableStateOf(ThemeConfig.customBackgroundUri != null)
} }
// 图片编辑状态
var showImageEditor by remember { mutableStateOf(false) }
var selectedImageUri by remember { mutableStateOf<Uri?>(null) }
// 初始化卡片配置 // 初始化卡片配置
val systemIsDark = isSystemInDarkTheme() val systemIsDark = isSystemInDarkTheme()
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
@@ -181,36 +187,17 @@ fun MoreSettingsScreen(navigator: DestinationsNavigator) {
) )
var showThemeColorDialog by remember { mutableStateOf(false) } var showThemeColorDialog by remember { mutableStateOf(false) }
val ksuIsValid = Natives.isKsuValid(ksuApp.packageName)
// 图片选择器 // 图片选择器
val pickImageLauncher = rememberLauncherForActivityResult( val pickImageLauncher = rememberLauncherForActivityResult(
ActivityResultContracts.GetContent() ActivityResultContracts.GetContent()
) { uri: Uri? -> ) { uri: Uri? ->
uri?.let { uri?.let {
selectedImageUri = it context.saveCustomBackground(it)
showImageEditor = true
}
}
// 显示图片编辑对话框
if (showImageEditor && selectedImageUri != null) {
ImageEditorDialog(
imageUri = selectedImageUri!!,
onDismiss = {
showImageEditor = false
selectedImageUri = null
},
onConfirm = { transformedUri ->
context.saveAndApplyCustomBackground(transformedUri)
isCustomBackgroundEnabled = true isCustomBackgroundEnabled = true
CardConfig.cardElevation = 0.dp CardConfig.cardElevation = 0.dp
CardConfig.isCustomBackgroundEnabled = true
saveCardConfig(context) saveCardConfig(context)
showImageEditor = false
selectedImageUri = null
} }
)
} }
Scaffold( Scaffold(
@@ -233,7 +220,6 @@ fun MoreSettingsScreen(navigator: DestinationsNavigator) {
.padding(top = 12.dp) .padding(top = 12.dp)
) { ) {
// SELinux 开关 // SELinux 开关
if (ksuIsValid) {
SwitchItem( SwitchItem(
icon = Icons.Filled.Security, icon = Icons.Filled.Security,
title = stringResource(R.string.selinux), title = stringResource(R.string.selinux),
@@ -247,7 +233,6 @@ fun MoreSettingsScreen(navigator: DestinationsNavigator) {
if (result.isSuccess) selinuxEnabled = enabled if (result.isSuccess) selinuxEnabled = enabled
} }
} }
}
var isExpanded by remember { mutableStateOf(false) } var isExpanded by remember { mutableStateOf(false) }
@@ -462,7 +447,6 @@ fun MoreSettingsScreen(navigator: DestinationsNavigator) {
CardConfig.cardElevation = CardConfig.defaultElevation CardConfig.cardElevation = CardConfig.defaultElevation
CardConfig.cardAlpha = 0.45f CardConfig.cardAlpha = 0.45f
CardConfig.isCustomAlphaSet = false CardConfig.isCustomAlphaSet = false
CardConfig.isCustomBackgroundEnabled = false
saveCardConfig(context) saveCardConfig(context)
cardAlpha = 0.35f cardAlpha = 0.35f
themeMode = 0 themeMode = 0
@@ -584,7 +568,7 @@ fun MoreSettingsScreen(navigator: DestinationsNavigator) {
} }
} }
} }
} }
@Composable @Composable

View File

@@ -1,4 +1,4 @@
package com.sukisu.ultra.ui.screen package zako.zako.zako.ui.screen
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
@@ -7,14 +7,32 @@ import android.widget.Toast
import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawing
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.Undo import androidx.compose.material.icons.automirrored.filled.Undo
import androidx.compose.material.icons.filled.* import androidx.compose.material.icons.filled.*
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.* import androidx.compose.material3.Icon
import androidx.compose.material3.ListItem
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.material3.rememberTopAppBarState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
@@ -46,14 +64,22 @@ import com.ramcosta.composedestinations.navigation.EmptyDestinationsNavigator
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import com.sukisu.ultra.BuildConfig import zako.zako.zako.BuildConfig
import com.sukisu.ultra.Natives import zako.zako.zako.Natives
import com.sukisu.ultra.R import zako.zako.zako.R
import com.sukisu.ultra.* import zako.zako.zako.ui.component.AboutDialog
import com.sukisu.ultra.ui.component.* import zako.zako.zako.ui.component.ConfirmResult
import com.sukisu.ultra.ui.theme.* import zako.zako.zako.ui.component.DialogHandle
import com.sukisu.ultra.ui.util.LocalSnackbarHost import zako.zako.zako.ui.component.SwitchItem
import com.sukisu.ultra.ui.util.getBugreportFile import zako.zako.zako.ui.component.rememberConfirmDialog
import zako.zako.zako.ui.component.rememberCustomDialog
import zako.zako.zako.ui.component.rememberLoadingDialog
import zako.zako.zako.ui.theme.CardConfig
import zako.zako.zako.ui.theme.ThemeConfig
import zako.zako.zako.ui.theme.getCardColors
import zako.zako.zako.ui.theme.getCardElevation
import zako.zako.zako.ui.util.LocalSnackbarHost
import zako.zako.zako.ui.util.getBugreportFile
import java.time.LocalDateTime import java.time.LocalDateTime
import java.time.format.DateTimeFormatter import java.time.format.DateTimeFormatter
@@ -69,7 +95,6 @@ fun SettingScreen(navigator: DestinationsNavigator) {
// region 界面基础设置 // region 界面基础设置
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()) val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
val snackBarHost = LocalSnackbarHost.current val snackBarHost = LocalSnackbarHost.current
val ksuIsValid = Natives.isKsuValid(ksuApp.packageName)
// endregion // endregion
Scaffold( Scaffold(
@@ -117,7 +142,6 @@ fun SettingScreen(navigator: DestinationsNavigator) {
// region 配置项列表 // region 配置项列表
// 配置文件模板入口 // 配置文件模板入口
val profileTemplate = stringResource(id = R.string.settings_profile_template) val profileTemplate = stringResource(id = R.string.settings_profile_template)
if (ksuIsValid) {
ListItem( ListItem(
leadingContent = { Icon(Icons.Filled.Fence, profileTemplate) }, leadingContent = { Icon(Icons.Filled.Fence, profileTemplate) },
headlineContent = { Text(profileTemplate) }, headlineContent = { Text(profileTemplate) },
@@ -126,13 +150,10 @@ fun SettingScreen(navigator: DestinationsNavigator) {
navigator.navigate(AppProfileTemplateScreenDestination) navigator.navigate(AppProfileTemplateScreenDestination)
} }
) )
}
// 卸载模块开关 // 卸载模块开关
var umountChecked by rememberSaveable { var umountChecked by rememberSaveable {
mutableStateOf(Natives.isDefaultUmountModules()) mutableStateOf(Natives.isDefaultUmountModules())
} }
if (ksuIsValid) {
SwitchItem( SwitchItem(
icon = Icons.Filled.FolderDelete, icon = Icons.Filled.FolderDelete,
title = stringResource(id = R.string.settings_umount_modules_default), title = stringResource(id = R.string.settings_umount_modules_default),
@@ -143,9 +164,7 @@ fun SettingScreen(navigator: DestinationsNavigator) {
umountChecked = it umountChecked = it
} }
} }
}
// SU 禁用开关(仅在兼容版本显示) // SU 禁用开关(仅在兼容版本显示)
if (ksuIsValid) {
if (Natives.version >= Natives.MINIMAL_SUPPORTED_SU_COMPAT) { if (Natives.version >= Natives.MINIMAL_SUPPORTED_SU_COMPAT) {
var isSuDisabled by rememberSaveable { var isSuDisabled by rememberSaveable {
mutableStateOf(!Natives.isSuEnabled()) mutableStateOf(!Natives.isSuEnabled())
@@ -162,7 +181,6 @@ fun SettingScreen(navigator: DestinationsNavigator) {
} }
} }
} }
}
val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE) val prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE)
@@ -188,7 +206,6 @@ fun SettingScreen(navigator: DestinationsNavigator) {
prefs.getBoolean("enable_web_debugging", false) prefs.getBoolean("enable_web_debugging", false)
) )
} }
if (Natives.isKsuValid(ksuApp.packageName)) {
SwitchItem( SwitchItem(
icon = Icons.Filled.DeveloperMode, icon = Icons.Filled.DeveloperMode,
title = stringResource(id = R.string.enable_web_debugging), title = stringResource(id = R.string.enable_web_debugging),
@@ -198,7 +215,6 @@ fun SettingScreen(navigator: DestinationsNavigator) {
prefs.edit { putBoolean("enable_web_debugging", it) } prefs.edit { putBoolean("enable_web_debugging", it) }
enableWebDebugging = it enableWebDebugging = it
} }
}
// 更多设置 // 更多设置
val newButtonTitle = stringResource(id = R.string.more_settings) val newButtonTitle = stringResource(id = R.string.more_settings)
ListItem( ListItem(

View File

@@ -1,4 +1,4 @@
package com.sukisu.ultra.ui.screen package zako.zako.zako.ui.screen
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.gestures.detectTapGestures
@@ -24,7 +24,7 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import com.sukisu.ultra.R import zako.zako.zako.R
import coil.compose.AsyncImage import coil.compose.AsyncImage
import coil.request.ImageRequest import coil.request.ImageRequest
import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.annotation.Destination
@@ -32,10 +32,10 @@ import com.ramcosta.composedestinations.annotation.RootGraph
import com.ramcosta.composedestinations.generated.destinations.AppProfileScreenDestination import com.ramcosta.composedestinations.generated.destinations.AppProfileScreenDestination
import com.ramcosta.composedestinations.navigation.DestinationsNavigator import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import com.sukisu.ultra.Natives import zako.zako.zako.Natives
import com.sukisu.ultra.ui.component.SearchAppBar import zako.zako.zako.ui.component.SearchAppBar
import com.sukisu.ultra.ui.util.ModuleModify import zako.zako.zako.ui.util.ModuleModify
import com.sukisu.ultra.ui.viewmodel.SuperUserViewModel import zako.zako.zako.ui.viewmodel.SuperUserViewModel
@OptIn(ExperimentalMaterialApi::class, ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterialApi::class, ExperimentalMaterial3Api::class)
@Destination<RootGraph> @Destination<RootGraph>

View File

@@ -1,7 +1,5 @@
package com.sukisu.ultra.ui.screen package zako.zako.zako.ui.screen
import android.content.ClipData
import android.content.ClipboardManager
import android.widget.Toast import android.widget.Toast
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
@@ -46,10 +44,11 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalClipboardManager
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.core.content.getSystemService
import androidx.lifecycle.compose.dropUnlessResumed import androidx.lifecycle.compose.dropUnlessResumed
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.annotation.Destination
@@ -60,9 +59,9 @@ import com.ramcosta.composedestinations.result.ResultRecipient
import com.ramcosta.composedestinations.result.getOr import com.ramcosta.composedestinations.result.getOr
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import com.sukisu.ultra.R import zako.zako.zako.R
import com.sukisu.ultra.ui.theme.ThemeConfig import zako.zako.zako.ui.theme.ThemeConfig
import com.sukisu.ultra.ui.viewmodel.TemplateViewModel import zako.zako.zako.ui.viewmodel.TemplateViewModel
/** /**
* @author weishu * @author weishu
@@ -100,8 +99,8 @@ fun AppProfileTemplateScreen(
Scaffold( Scaffold(
topBar = { topBar = {
val clipboardManager = LocalClipboardManager.current
val context = LocalContext.current val context = LocalContext.current
val clipboardManager = context.getSystemService<ClipboardManager>()
val showToast = fun(msg: String) { val showToast = fun(msg: String) {
scope.launch(Dispatchers.Main) { scope.launch(Dispatchers.Main) {
Toast.makeText(context, msg, Toast.LENGTH_SHORT).show() Toast.makeText(context, msg, Toast.LENGTH_SHORT).show()
@@ -113,21 +112,21 @@ fun AppProfileTemplateScreen(
scope.launch { viewModel.fetchTemplates(true) } scope.launch { viewModel.fetchTemplates(true) }
}, },
onImport = { onImport = {
scope.launch { clipboardManager.getText()?.text?.let {
val clipboardText = clipboardManager?.primaryClip?.getItemAt(0)?.text?.toString() if (it.isEmpty()) {
if (clipboardText.isNullOrEmpty()) {
showToast(context.getString(R.string.app_profile_template_import_empty)) showToast(context.getString(R.string.app_profile_template_import_empty))
return@launch return@let
} }
scope.launch {
viewModel.importTemplates( viewModel.importTemplates(
clipboardText, it, {
{
showToast(context.getString(R.string.app_profile_template_import_success)) showToast(context.getString(R.string.app_profile_template_import_success))
viewModel.fetchTemplates(false) viewModel.fetchTemplates(false)
}, },
showToast showToast
) )
} }
}
}, },
onExport = { onExport = {
scope.launch { scope.launch {
@@ -135,8 +134,8 @@ fun AppProfileTemplateScreen(
{ {
showToast(context.getString(R.string.app_profile_template_export_empty)) showToast(context.getString(R.string.app_profile_template_export_empty))
} }
) { text -> ) {
clipboardManager?.setPrimaryClip(ClipData.newPlainText("", text)) clipboardManager.setText(AnnotatedString(it))
} }
} }
}, },

View File

@@ -1,4 +1,4 @@
package com.sukisu.ultra.ui.screen package zako.zako.zako.ui.screen
import android.widget.Toast import android.widget.Toast
import androidx.activity.compose.BackHandler import androidx.activity.compose.BackHandler
@@ -47,14 +47,14 @@ import androidx.compose.ui.text.input.KeyboardType
import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.annotation.RootGraph import com.ramcosta.composedestinations.annotation.RootGraph
import com.ramcosta.composedestinations.result.ResultBackNavigator import com.ramcosta.composedestinations.result.ResultBackNavigator
import com.sukisu.ultra.Natives import zako.zako.zako.Natives
import com.sukisu.ultra.R import zako.zako.zako.R
import com.sukisu.ultra.ui.component.profile.RootProfileConfig import zako.zako.zako.ui.component.profile.RootProfileConfig
import com.sukisu.ultra.ui.util.deleteAppProfileTemplate import zako.zako.zako.ui.util.deleteAppProfileTemplate
import com.sukisu.ultra.ui.util.getAppProfileTemplate import zako.zako.zako.ui.util.getAppProfileTemplate
import com.sukisu.ultra.ui.util.setAppProfileTemplate import zako.zako.zako.ui.util.setAppProfileTemplate
import com.sukisu.ultra.ui.viewmodel.TemplateViewModel import zako.zako.zako.ui.viewmodel.TemplateViewModel
import com.sukisu.ultra.ui.viewmodel.toJSON import zako.zako.zako.ui.viewmodel.toJSON
import androidx.lifecycle.compose.dropUnlessResumed import androidx.lifecycle.compose.dropUnlessResumed
/** /**

View File

@@ -1,4 +1,4 @@
package com.sukisu.ultra.ui.screen package zako.zako.zako.ui.screen
import android.app.Activity.RESULT_OK import android.app.Activity.RESULT_OK
import android.content.Context import android.content.Context
@@ -25,23 +25,30 @@ import com.ramcosta.composedestinations.annotation.RootGraph
import com.ramcosta.composedestinations.navigation.DestinationsNavigator import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import com.sukisu.ultra.ui.component.* import zako.zako.zako.ui.component.ConfirmResult
import com.sukisu.ultra.ui.theme.* import zako.zako.zako.ui.component.SearchAppBar
import com.sukisu.ultra.ui.viewmodel.KpmViewModel import zako.zako.zako.ui.component.rememberConfirmDialog
import com.sukisu.ultra.ui.util.* import zako.zako.zako.ui.theme.getCardColors
import zako.zako.zako.ui.theme.getCardElevation
import zako.zako.zako.ui.viewmodel.KpmViewModel
import zako.zako.zako.ui.util.loadKpmModule
import zako.zako.zako.ui.util.unloadKpmModule
import java.io.File import java.io.File
import androidx.core.content.edit import androidx.core.content.edit
import com.sukisu.ultra.R import zako.zako.zako.ui.theme.ThemeConfig
import java.io.BufferedReader import zako.zako.zako.ui.component.rememberCustomDialog
import java.io.FileInputStream import zako.zako.zako.ui.component.ConfirmDialogHandle
import java.io.InputStreamReader import zako.zako.zako.R
import java.net.* import java.net.URLDecoder
import java.net.URLEncoder
/** /**
* KPM 管理界面 * KPM 管理界面
* 以下内核模块功能由KernelPatch开发经过修改后加入SukiSU Ultra的内核模块功能 * 以下内核模块功能由KernelPatch开发经过修改后加入SukiSU Ultra的内核模块功能
* 开发者ShirkNeko, Liaokong * 开发者zako, Liaokong
*/ */
var globalModuleFileName: String = ""
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Destination<RootGraph> @Destination<RootGraph>
@Composable @Composable
@@ -59,11 +66,6 @@ fun KpmScreen(
MaterialTheme.colorScheme.secondaryContainer MaterialTheme.colorScheme.secondaryContainer
} }
val moduleConfirmContentMap = viewModel.moduleList.associate { module ->
val moduleFileName = module.id
module.id to stringResource(R.string.confirm_uninstall_content, moduleFileName)
}
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()) val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
val kpmInstallSuccess = stringResource(R.string.kpm_install_success) val kpmInstallSuccess = stringResource(R.string.kpm_install_success)
@@ -76,33 +78,13 @@ fun KpmScreen(
val kpmInstallMode = stringResource(R.string.kpm_install_mode) val kpmInstallMode = stringResource(R.string.kpm_install_mode)
val kpmInstallModeLoad = stringResource(R.string.kpm_install_mode_load) val kpmInstallModeLoad = stringResource(R.string.kpm_install_mode_load)
val kpmInstallModeEmbed = stringResource(R.string.kpm_install_mode_embed) val kpmInstallModeEmbed = stringResource(R.string.kpm_install_mode_embed)
val kpmInstallModeDescription = stringResource(R.string.kpm_install_mode_description)
val invalidFileTypeMessage = stringResource(R.string.invalid_file_type) val invalidFileTypeMessage = stringResource(R.string.invalid_file_type)
val confirmTitle = stringResource(R.string.confirm_uninstall_title_with_filename) val confirmTitle = stringResource(R.string.confirm_uninstall_title_with_filename)
val confirmContent = stringResource(R.string.confirm_uninstall_content, globalModuleFileName)
var tempFileForInstall by remember { mutableStateOf<File?>(null) } var tempFileForInstall by remember { mutableStateOf<File?>(null) }
val installModeDialog = rememberCustomDialog { dismiss -> val installModeDialog = rememberCustomDialog { dismiss ->
var moduleName by remember { mutableStateOf<String?>(null) }
LaunchedEffect(tempFileForInstall) {
tempFileForInstall?.let { tempFile ->
try {
val command = arrayOf("su", "-c", "strings ${tempFile.absolutePath} | grep 'name='")
val process = Runtime.getRuntime().exec(command)
val inputStream = process.inputStream
val reader = BufferedReader(InputStreamReader(inputStream))
var line: String?
while (reader.readLine().also { line = it } != null) {
if (line!!.startsWith("name=")) {
moduleName = line.substringAfter("name=").trim()
break
}
}
process.waitFor()
} catch (e: Exception) {
Log.e("KsuCli", "Failed to get module name: ${e.message}", e)
}
}
}
AlertDialog( AlertDialog(
onDismissRequest = { onDismissRequest = {
dismiss() dismiss()
@@ -110,7 +92,7 @@ fun KpmScreen(
tempFileForInstall = null tempFileForInstall = null
}, },
title = { Text(kpmInstallMode) }, title = { Text(kpmInstallMode) },
text = { moduleName?.let { Text(stringResource(R.string.kpm_install_mode_description, it)) } }, text = { Text(kpmInstallModeDescription) },
confirmButton = { confirmButton = {
Column { Column {
Button( Button(
@@ -188,30 +170,15 @@ fun KpmScreen(
} }
} }
val mimeType = context.contentResolver.getType(uri) if (!tempFile.name.endsWith(".kpm")) {
val isCorrectMimeType = mimeType == null || mimeType.contains("application/octet-stream")
if (!isCorrectMimeType) {
var shouldShowSnackbar = true
try {
val matchCount = checkStringsCommand(tempFile)
val isElf = isElfFile(tempFile)
if (matchCount >= 1 || isElf) {
shouldShowSnackbar = false
}
} catch (e: Exception) {
Log.e("KsuCli", "Failed to execute checks: ${e.message}", e)
}
if (shouldShowSnackbar) {
snackBarHost.showSnackbar( snackBarHost.showSnackbar(
message = invalidFileTypeMessage, message = invalidFileTypeMessage,
duration = SnackbarDuration.Short duration = SnackbarDuration.Short
) )
}
tempFile.delete() tempFile.delete()
return@launch return@launch
} }
tempFileForInstall = tempFile tempFileForInstall = tempFile
installModeDialog.show() installModeDialog.show()
} }
@@ -250,7 +217,7 @@ fun KpmScreen(
onClick = { onClick = {
selectPatchLauncher.launch( selectPatchLauncher.launch(
Intent(Intent.ACTION_GET_CONTENT).apply { Intent(Intent.ACTION_GET_CONTENT).apply {
type = "application/octet-stream" type = "*/*"
} }
) )
}, },
@@ -314,7 +281,6 @@ fun KpmScreen(
module = module, module = module,
onUninstall = { onUninstall = {
scope.launch { scope.launch {
val confirmContent = moduleConfirmContentMap[module.id] ?: ""
handleModuleUninstall( handleModuleUninstall(
module = module, module = module,
viewModel = viewModel, viewModel = viewModel,
@@ -349,25 +315,8 @@ private suspend fun handleModuleInstall(
kpmInstallSuccess: String, kpmInstallSuccess: String,
kpmInstallFailed: String kpmInstallFailed: String
) { ) {
var moduleId: String? = null val moduleId = extractModuleId(tempFile.name)
try { if (moduleId == null) {
val command = arrayOf("su", "-c", "strings ${tempFile.absolutePath} | grep 'name='")
val process = Runtime.getRuntime().exec(command)
val inputStream = process.inputStream
val reader = BufferedReader(InputStreamReader(inputStream))
var line: String?
while (reader.readLine().also { line = it } != null) {
if (line!!.startsWith("name=")) {
moduleId = line.substringAfter("name=").trim()
break
}
}
process.waitFor()
} catch (e: Exception) {
Log.e("KsuCli", "Failed to get module ID from strings command: ${e.message}", e)
}
if (moduleId == null || moduleId.isEmpty()) {
Log.e("KsuCli", "Failed to extract module ID from file: ${tempFile.name}") Log.e("KsuCli", "Failed to extract module ID from file: ${tempFile.name}")
snackBarHost.showSnackbar( snackBarHost.showSnackbar(
message = kpmInstallFailed, message = kpmInstallFailed,
@@ -409,6 +358,18 @@ private suspend fun handleModuleInstall(
tempFile.delete() tempFile.delete()
} }
private fun extractModuleId(fileName: String): String? {
return try {
val decodedFileName = URLDecoder.decode(fileName, "UTF-8")
val pattern = "([^/]*?)\\.kpm$".toRegex()
val matchResult = pattern.find(decodedFileName)
matchResult?.groupValues?.get(1)
} catch (e: Exception) {
Log.e("KsuCli", "Failed to extract module ID: ${e.message}", e)
null
}
}
private suspend fun handleModuleUninstall( private suspend fun handleModuleUninstall(
module: KpmViewModel.ModuleInfo, module: KpmViewModel.ModuleInfo,
viewModel: KpmViewModel, viewModel: KpmViewModel,
@@ -423,6 +384,7 @@ private suspend fun handleModuleUninstall(
confirmDialog: ConfirmDialogHandle confirmDialog: ConfirmDialogHandle
) { ) {
val moduleFileName = "${module.id}.kpm" val moduleFileName = "${module.id}.kpm"
globalModuleFileName = moduleFileName
val moduleFilePath = "/data/adb/kpm/$moduleFileName" val moduleFilePath = "/data/adb/kpm/$moduleFileName"
val fileExists = try { val fileExists = try {
@@ -592,40 +554,3 @@ private fun KpmModuleItem(
} }
} }
} }
private fun checkStringsCommand(tempFile: File): Int {
val command = arrayOf("su", "-c", "strings ${tempFile.absolutePath} | grep -E 'name=|version=|license=|author='")
val process = Runtime.getRuntime().exec(command)
val inputStream = process.inputStream
val reader = BufferedReader(InputStreamReader(inputStream))
var line: String?
var matchCount = 0
val keywords = listOf("name=", "version=", "license=", "author=")
var nameExists = false
while (reader.readLine().also { line = it } != null) {
if (!nameExists && line!!.startsWith("name=")) {
nameExists = true
matchCount++
} else if (nameExists) {
for (keyword in keywords) {
if (line!!.startsWith(keyword)) {
matchCount++
break
}
}
}
}
process.waitFor()
return if (nameExists) matchCount else 0
}
private fun isElfFile(tempFile: File): Boolean {
val elfMagic = byteArrayOf(0x7F, 'E'.code.toByte(), 'L'.code.toByte(), 'F'.code.toByte())
val fileBytes = ByteArray(4)
FileInputStream(tempFile).use { input ->
input.read(fileBytes)
}
return fileBytes.contentEquals(elfMagic)
}

View File

@@ -1,4 +1,4 @@
package com.sukisu.ultra.ui.theme package zako.zako.zako.ui.theme
import android.content.Context import android.content.Context
import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.isSystemInDarkTheme

View File

@@ -1,4 +1,4 @@
package com.sukisu.ultra.ui.theme package zako.zako.zako.ui.theme
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color

View File

@@ -1,4 +1,4 @@
package com.sukisu.ultra.ui.theme package zako.zako.zako.ui.theme
import android.content.ContentResolver import android.content.ContentResolver
import android.content.Context import android.content.Context
@@ -27,14 +27,11 @@ import androidx.compose.ui.zIndex
import coil.compose.rememberAsyncImagePainter import coil.compose.rememberAsyncImagePainter
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.ui.graphics.luminance import androidx.compose.ui.graphics.luminance
import androidx.compose.ui.unit.dp
import java.io.File import java.io.File
import java.io.FileOutputStream import java.io.FileOutputStream
import java.io.InputStream import java.io.InputStream
import androidx.core.content.edit import androidx.core.content.edit
import androidx.core.net.toUri import androidx.core.net.toUri
import com.sukisu.ultra.ui.util.BackgroundTransformation
import com.sukisu.ultra.ui.util.saveTransformedBackground
object ThemeConfig { object ThemeConfig {
var customBackgroundUri by mutableStateOf<Uri?>(null) var customBackgroundUri by mutableStateOf<Uri?>(null)
@@ -91,6 +88,29 @@ private fun getLightColorScheme() = lightColorScheme(
outlineVariant = Color.Black.copy(alpha = 0.12f) outlineVariant = Color.Black.copy(alpha = 0.12f)
) )
// 复制图片到应用内部存储
fun Context.copyImageToInternalStorage(uri: Uri): Uri? {
try {
val contentResolver: ContentResolver = contentResolver
val inputStream: InputStream = contentResolver.openInputStream(uri)!!
val fileName = "custom_background.jpg"
val file = File(filesDir, fileName)
val outputStream = FileOutputStream(file)
val buffer = ByteArray(4 * 1024)
var read: Int
while (inputStream.read(buffer).also { read = it } != -1) {
outputStream.write(buffer, 0, read)
}
outputStream.flush()
outputStream.close()
inputStream.close()
return Uri.fromFile(file)
} catch (e: Exception) {
Log.e("ImageCopy", "Failed to copy image: ${e.message}")
return null
}
}
@Composable @Composable
fun KernelSUTheme( fun KernelSUTheme(
darkTheme: Boolean = when(ThemeConfig.forceDarkMode) { darkTheme: Boolean = when(ThemeConfig.forceDarkMode) {
@@ -111,6 +131,7 @@ fun KernelSUTheme(
if (darkTheme) { if (darkTheme) {
val originalScheme = dynamicDarkColorScheme(context) val originalScheme = dynamicDarkColorScheme(context)
originalScheme.copy( originalScheme.copy(
// 调整按钮相关颜色
primary = adjustColor(originalScheme.primary), primary = adjustColor(originalScheme.primary),
onPrimary = adjustColor(originalScheme.onPrimary), onPrimary = adjustColor(originalScheme.onPrimary),
primaryContainer = adjustColor(originalScheme.primaryContainer), primaryContainer = adjustColor(originalScheme.primaryContainer),
@@ -221,48 +242,6 @@ fun KernelSUTheme(
} }
} }
// 复制图片到应用内部存储
private fun Context.copyImageToInternalStorage(uri: Uri): Uri? {
try {
val contentResolver: ContentResolver = contentResolver
val inputStream: InputStream = contentResolver.openInputStream(uri)!!
val fileName = "custom_background.jpg"
val file = File(filesDir, fileName)
val outputStream = FileOutputStream(file)
val buffer = ByteArray(4 * 1024)
var read: Int
while (inputStream.read(buffer).also { read = it } != -1) {
outputStream.write(buffer, 0, read)
}
outputStream.flush()
outputStream.close()
inputStream.close()
return Uri.fromFile(file)
} catch (e: Exception) {
Log.e("ImageCopy", "Failed to copy image: ${e.message}")
return null
}
}
// 保存变换后的背景图片到应用内部存储并更新配置
fun Context.saveAndApplyCustomBackground(uri: Uri, transformation: BackgroundTransformation? = null) {
val finalUri = if (transformation != null) {
saveTransformedBackground(uri, transformation)
} else {
copyImageToInternalStorage(uri)
}
getSharedPreferences("theme_prefs", Context.MODE_PRIVATE)
.edit {
putString("custom_background", finalUri?.toString())
}
ThemeConfig.customBackgroundUri = finalUri
CardConfig.cardElevation = 0.dp
CardConfig.isCustomBackgroundEnabled = true
}
// 保存背景图片到应用内部存储并更新配置
fun Context.saveCustomBackground(uri: Uri?) { fun Context.saveCustomBackground(uri: Uri?) {
val newUri = uri?.let { copyImageToInternalStorage(it) } val newUri = uri?.let { copyImageToInternalStorage(it) }
getSharedPreferences("theme_prefs", Context.MODE_PRIVATE) getSharedPreferences("theme_prefs", Context.MODE_PRIVATE)
@@ -270,10 +249,6 @@ fun Context.saveCustomBackground(uri: Uri?) {
putString("custom_background", newUri?.toString()) putString("custom_background", newUri?.toString())
} }
ThemeConfig.customBackgroundUri = newUri ThemeConfig.customBackgroundUri = newUri
if (uri != null) {
CardConfig.cardElevation = 0.dp
CardConfig.isCustomBackgroundEnabled = true
}
} }
fun Context.loadCustomBackground() { fun Context.loadCustomBackground() {

View File

@@ -1,4 +1,4 @@
package com.sukisu.ultra.ui.theme package zako.zako.zako.ui.theme
import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontFamily

View File

@@ -1,4 +1,4 @@
package com.sukisu.ultra.ui.util package zako.zako.zako.ui.util
import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.SnackbarHostState
import androidx.compose.runtime.compositionLocalOf import androidx.compose.runtime.compositionLocalOf

View File

@@ -1,4 +1,4 @@
package com.sukisu.ultra.ui.util package zako.zako.zako.ui.util
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.DownloadManager import android.app.DownloadManager
@@ -12,8 +12,7 @@ import android.util.Log
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.DisposableEffect
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import com.sukisu.ultra.ui.util.module.LatestVersionInfo import zako.zako.zako.ui.util.module.LatestVersionInfo
import androidx.core.net.toUri
/** /**
* @author weishu * @author weishu
@@ -43,14 +42,14 @@ fun download(
onDownloading() onDownloading()
return return
} else if (status == DownloadManager.STATUS_SUCCESSFUL) { } else if (status == DownloadManager.STATUS_SUCCESSFUL) {
onDownloaded(localUri.toUri()) onDownloaded(Uri.parse(localUri))
return return
} }
} }
} }
} }
val request = DownloadManager.Request(url.toUri()) val request = DownloadManager.Request(Uri.parse(url))
.setDestinationInExternalPublicDir( .setDestinationInExternalPublicDir(
Environment.DIRECTORY_DOWNLOADS, Environment.DIRECTORY_DOWNLOADS,
fileName fileName
@@ -141,7 +140,7 @@ fun DownloadListener(context: Context, onDownloaded: (Uri) -> Unit) {
val uri = cursor.getString( val uri = cursor.getString(
cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI) cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI)
) )
onDownloaded(uri.toUri()) onDownloaded(Uri.parse(uri))
} }
} }
} }

View File

@@ -1,4 +1,4 @@
package com.sukisu.ultra.ui.util; package zako.zako.zako.ui.util;
/* /*
* Copyright (C) 2009 The Android Open Source Project * Copyright (C) 2009 The Android Open Source Project
* *

View File

@@ -1,4 +1,4 @@
package com.sukisu.ultra.ui.util package zako.zako.zako.ui.util
import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme

View File

@@ -1,4 +1,4 @@
package com.sukisu.ultra.ui.util package zako.zako.zako.ui.util
import android.content.ContentResolver import android.content.ContentResolver
import android.content.Context import android.content.Context
@@ -16,9 +16,9 @@ import com.topjohnwu.superuser.ShellUtils
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import com.sukisu.ultra.BuildConfig import zako.zako.zako.BuildConfig
import com.sukisu.ultra.Natives import zako.zako.zako.Natives
import com.sukisu.ultra.ksuApp import zako.zako.zako.ksuApp
import org.json.JSONArray import org.json.JSONArray
import java.io.File import java.io.File
@@ -436,7 +436,7 @@ fun restartApp(packageName: String) {
launchApp(packageName) launchApp(packageName)
} }
fun getSuSFSDaemonPath(): String { private fun getSuSFSDaemonPath(): String {
return ksuApp.applicationInfo.nativeLibraryDir + File.separator + "libzakozakozako.so" return ksuApp.applicationInfo.nativeLibraryDir + File.separator + "libzakozakozako.so"
} }

View File

@@ -1,11 +1,11 @@
package com.sukisu.ultra.ui.util package zako.zako.zako.ui.util
import android.content.Context import android.content.Context
import android.os.Build import android.os.Build
import android.system.Os import android.system.Os
import com.topjohnwu.superuser.ShellUtils import com.topjohnwu.superuser.ShellUtils
import com.sukisu.ultra.Natives import zako.zako.zako.Natives
import com.sukisu.ultra.ui.screen.getManagerVersion import zako.zako.zako.ui.screen.getManagerVersion
import java.io.File import java.io.File
import java.io.FileWriter import java.io.FileWriter
import java.io.PrintWriter import java.io.PrintWriter

View File

@@ -1,4 +1,4 @@
package com.sukisu.ultra.ui.util package zako.zako.zako.ui.util
import android.app.AlertDialog import android.app.AlertDialog
import android.content.Context import android.content.Context
@@ -16,7 +16,7 @@ import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import com.sukisu.ultra.R import zako.zako.zako.R
import java.io.BufferedReader import java.io.BufferedReader
import java.io.IOException import java.io.IOException
import java.io.InputStreamReader import java.io.InputStreamReader

View File

@@ -1,9 +1,9 @@
package com.sukisu.ultra.ui.util package zako.zako.zako.ui.util
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.Shell
import com.sukisu.ultra.R import zako.zako.zako.R
@Composable @Composable
fun getSELinuxStatus(): String { fun getSELinuxStatus(): String {

View File

@@ -1,4 +1,4 @@
package com.sukisu.ultra.ui.util.module package zako.zako.zako.ui.util.module
data class LatestVersionInfo( data class LatestVersionInfo(
val versionCode : Int = 0, val versionCode : Int = 0,

View File

@@ -1,4 +1,4 @@
package com.sukisu.ultra.ui.viewmodel package zako.zako.zako.ui.viewmodel
import android.util.Log import android.util.Log
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
@@ -9,7 +9,7 @@ import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import com.sukisu.ultra.ui.util.* import zako.zako.zako.ui.util.*
class KpmViewModel : ViewModel() { class KpmViewModel : ViewModel() {
var moduleList by mutableStateOf(emptyList<ModuleInfo>()) var moduleList by mutableStateOf(emptyList<ModuleInfo>())
@@ -143,6 +143,17 @@ class KpmViewModel : ViewModel() {
return result return result
} }
fun controlModule(moduleId: String, args: String? = null): Int {
return try {
val result = controlKpmModule(moduleId, args)
Log.d("KsuCli", "Control module $moduleId result: $result")
result
} catch (e: Exception) {
Log.e("KsuCli", "Failed to control module $moduleId", e)
-1
}
}
data class ModuleInfo( data class ModuleInfo(
val id: String, val id: String,
val name: String, val name: String,

View File

@@ -1,4 +1,4 @@
package com.sukisu.ultra.ui.viewmodel package zako.zako.zako.ui.viewmodel
import android.os.SystemClock import android.os.SystemClock
import android.util.Log import android.util.Log
@@ -10,8 +10,8 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import com.sukisu.ultra.ui.util.HanziToPinyin import zako.zako.zako.ui.util.HanziToPinyin
import com.sukisu.ultra.ui.util.listModules import zako.zako.zako.ui.util.listModules
import org.json.JSONArray import org.json.JSONArray
import org.json.JSONObject import org.json.JSONObject
import java.text.Collator import java.text.Collator
@@ -40,6 +40,13 @@ class ModuleViewModel : ViewModel() {
val dirId: String, // real module id (dir name) val dirId: String, // real module id (dir name)
) )
data class ModuleUpdateInfo(
val version: String,
val versionCode: Int,
val zipUrl: String,
val changelog: String,
)
var isRefreshing by mutableStateOf(false) var isRefreshing by mutableStateOf(false)
private set private set
var search by mutableStateOf("") var search by mutableStateOf("")

View File

@@ -1,4 +1,4 @@
package com.sukisu.ultra.ui.viewmodel package zako.zako.zako.ui.viewmodel
import android.content.ComponentName import android.content.ComponentName
import android.content.Intent import android.content.Intent
@@ -18,12 +18,12 @@ import com.topjohnwu.superuser.Shell
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import com.sukisu.zako.IKsuInterface import zako.zako.zako.IKsuInterface
import com.sukisu.ultra.Natives import zako.zako.zako.Natives
import com.sukisu.ultra.ksuApp import zako.zako.zako.ksuApp
import com.sukisu.ultra.ui.KsuService import zako.zako.zako.ui.KsuService
import com.sukisu.ultra.ui.util.HanziToPinyin import zako.zako.zako.ui.util.HanziToPinyin
import com.sukisu.ultra.ui.util.KsuCli import zako.zako.zako.ui.util.KsuCli
import java.text.Collator import java.text.Collator
import java.util.* import java.util.*
import kotlin.coroutines.resume import kotlin.coroutines.resume

View File

@@ -1,4 +1,4 @@
package com.sukisu.ultra.ui.viewmodel package zako.zako.zako.ui.viewmodel
import android.os.Parcelable import android.os.Parcelable
import android.util.Log import android.util.Log
@@ -10,12 +10,12 @@ import androidx.lifecycle.ViewModel
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import com.sukisu.ultra.Natives import zako.zako.zako.Natives
import com.sukisu.ultra.profile.Capabilities import zako.zako.zako.profile.Capabilities
import com.sukisu.ultra.profile.Groups import zako.zako.zako.profile.Groups
import com.sukisu.ultra.ui.util.getAppProfileTemplate import zako.zako.zako.ui.util.getAppProfileTemplate
import com.sukisu.ultra.ui.util.listAppProfileTemplates import zako.zako.zako.ui.util.listAppProfileTemplates
import com.sukisu.ultra.ui.util.setAppProfileTemplate import zako.zako.zako.ui.util.setAppProfileTemplate
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import org.json.JSONArray import org.json.JSONArray

View File

@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package com.sukisu.ultra.ui.webui; package zako.zako.zako.ui.webui;
import java.net.URLConnection; import java.net.URLConnection;

View File

@@ -1,4 +1,4 @@
package com.sukisu.ultra.ui.webui; package zako.zako.zako.ui.webui;
import android.content.Context; import android.content.Context;
import android.util.Log; import android.util.Log;

View File

@@ -1,4 +1,4 @@
package com.sukisu.ultra.ui.webui package zako.zako.zako.ui.webui
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.ActivityManager import android.app.ActivityManager
@@ -16,7 +16,7 @@ import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updateLayoutParams import androidx.core.view.updateLayoutParams
import androidx.webkit.WebViewAssetLoader import androidx.webkit.WebViewAssetLoader
import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.Shell
import com.sukisu.ultra.ui.util.createRootShell import zako.zako.zako.ui.util.createRootShell
import java.io.File import java.io.File
@SuppressLint("SetJavaScriptEnabled") @SuppressLint("SetJavaScriptEnabled")

View File

@@ -1,4 +1,4 @@
package com.sukisu.ultra.ui.webui package zako.zako.zako.ui.webui
import android.app.Activity import android.app.Activity
import android.content.Context import android.content.Context
@@ -14,13 +14,13 @@ import androidx.core.view.WindowInsetsControllerCompat
import com.topjohnwu.superuser.CallbackList import com.topjohnwu.superuser.CallbackList
import com.topjohnwu.superuser.ShellUtils import com.topjohnwu.superuser.ShellUtils
import com.topjohnwu.superuser.internal.UiThreadHandler import com.topjohnwu.superuser.internal.UiThreadHandler
import com.sukisu.ultra.ui.util.createRootShell import zako.zako.zako.ui.util.createRootShell
import com.sukisu.ultra.ui.util.listModules import zako.zako.zako.ui.util.listModules
import com.sukisu.ultra.ui.util.withNewRootShell import zako.zako.zako.ui.util.withNewRootShell
import org.json.JSONArray import org.json.JSONArray
import org.json.JSONObject import org.json.JSONObject
import com.sukisu.ultra.ui.util.controlKpmModule import zako.zako.zako.ui.util.controlKpmModule
import com.sukisu.ultra.ui.util.listKpmModules import zako.zako.zako.ui.util.listKpmModules
import java.io.File import java.io.File
import java.util.concurrent.CompletableFuture import java.util.concurrent.CompletableFuture

View File

@@ -1,4 +1,4 @@
package com.sukisu.ultra.utils package zako.zako.zako.utils
import android.content.Context import android.content.Context
import java.io.File import java.io.File

View File

@@ -1,5 +1,3 @@
libzakozako.so libzakozako.so
libzakozakozako.so libzakozakozako.so
libkpmmgr.so libkpmmgr.so
libzako.so
libandroidx.graphics.path.so

View File

@@ -50,6 +50,7 @@
<string name="home_click_to_learn_kernelsu">یاد بگیرید چگونه از کرنل اس یو و ماژول ها استفاده کنید</string> <string name="home_click_to_learn_kernelsu">یاد بگیرید چگونه از کرنل اس یو و ماژول ها استفاده کنید</string>
<string name="home_support_title">از ما حمایت کنید</string> <string name="home_support_title">از ما حمایت کنید</string>
<string name="home_support_content">KernelSU رایگان است و همیشه خواهد بود و منبع باز است. با این حال، می توانید با اهدای کمک مالی به ما نشان دهید که برایتان مهم است.</string> <string name="home_support_content">KernelSU رایگان است و همیشه خواهد بود و منبع باز است. با این حال، می توانید با اهدای کمک مالی به ما نشان دهید که برایتان مهم است.</string>
<string name="profile">پروفایل برنامه</string>
<string name="profile_default">پیش‌فرض</string> <string name="profile_default">پیش‌فرض</string>
<string name="profile_template">قالب</string> <string name="profile_template">قالب</string>
<string name="profile_custom">شخصی سازی شده</string> <string name="profile_custom">شخصی سازی شده</string>

View File

@@ -50,6 +50,7 @@
<string name="home_click_to_learn_kernelsu">Pelajari cara instal KernelSU dan menggunakan modul</string> <string name="home_click_to_learn_kernelsu">Pelajari cara instal KernelSU dan menggunakan modul</string>
<string name="home_support_title">Dukung Kami</string> <string name="home_support_title">Dukung Kami</string>
<string name="home_support_content">KernelSU akan selalu menjadi aplikasi gratis dan terbuka. Anda dapat memberikan donasi sebagai bentuk dukungan.</string> <string name="home_support_content">KernelSU akan selalu menjadi aplikasi gratis dan terbuka. Anda dapat memberikan donasi sebagai bentuk dukungan.</string>
<string name="profile">Profil Apl</string>
<string name="profile_default">Bawaan</string> <string name="profile_default">Bawaan</string>
<string name="profile_template">Templat</string> <string name="profile_template">Templat</string>
<string name="profile_custom">Khusus</string> <string name="profile_custom">Khusus</string>

View File

@@ -34,10 +34,10 @@
<string name="reboot">再起動</string> <string name="reboot">再起動</string>
<string name="settings">設定</string> <string name="settings">設定</string>
<string name="reboot_userspace">ソフトリブート</string> <string name="reboot_userspace">ソフトリブート</string>
<string name="reboot_recovery">リカバリー再起動</string> <string name="reboot_recovery">リカバリー再起動</string>
<string name="reboot_bootloader">ブートローダー再起動</string> <string name="reboot_bootloader">ブートローダー再起動</string>
<string name="reboot_download">ダウンロードモード再起動</string> <string name="reboot_download">ダウンロードモード再起動</string>
<string name="reboot_edl">EDL 再起動</string> <string name="reboot_edl">EDL 再起動</string>
<string name="about">アプリについて</string> <string name="about">アプリについて</string>
<string name="module_uninstall_confirm">モジュール %s をアンインストールしますか?</string> <string name="module_uninstall_confirm">モジュール %s をアンインストールしますか?</string>
<string name="module_uninstall_success">%s はアンインストールされました</string> <string name="module_uninstall_success">%s はアンインストールされました</string>
@@ -72,7 +72,7 @@
<string name="failed_to_update_app_profile">%s のアプリのプロファイルの更新をできませでした</string> <string name="failed_to_update_app_profile">%s のアプリのプロファイルの更新をできませでした</string>
<string name="require_kernel_version" formatted="false">現在の KernelSU のバージョン %d は低すぎるため、マネージャーは正常に動作しません。バージョン %d 以上に更新してください!</string> <string name="require_kernel_version" formatted="false">現在の KernelSU のバージョン %d は低すぎるため、マネージャーは正常に動作しません。バージョン %d 以上に更新してください!</string>
<string name="settings_umount_modules_default">デフォルトでモジュールのマウントを解除する</string> <string name="settings_umount_modules_default">デフォルトでモジュールのマウントを解除する</string>
<string name="settings_umount_modules_default_summary">アプリプロファイルの「モジュールのアンマウント」の共通となるデフォルト値です。 有効にすると、プロファイルセットを持たないアプリのシステムに対するすべてのモジュールの変更が削除されます。</string> <string name="settings_umount_modules_default_summary">アプリプロファイルの「モジュールのアンマウント」の共通デフォルト値です。 有効にすると、プロファイルセットを持たないアプリのシステムに対するすべてのモジュールの変更が削除されます。</string>
<string name="settings_susfs_toggle">Kprobe フックを非表示にする</string> <string name="settings_susfs_toggle">Kprobe フックを非表示にする</string>
<string name="settings_susfs_toggle_summary">KSU によって生成された Kprobe フックを無効化して、代替となる組み込みの非 Kprobe を有効化します。Kprobe をサポートしない 非 GKI カーネルに適用される同等の機能を実装します。</string> <string name="settings_susfs_toggle_summary">KSU によって生成された Kprobe フックを無効化して、代替となる組み込みの非 Kprobe を有効化します。Kprobe をサポートしない 非 GKI カーネルに適用される同等の機能を実装します。</string>
<string name="profile_umount_modules_summary">このオプションを有効にすると、KernelSU はこのアプリのモジュールによって変更されたファイルを復元できるようになります。</string> <string name="profile_umount_modules_summary">このオプションを有効にすると、KernelSU はこのアプリのモジュールによって変更されたファイルを復元できるようになります。</string>
@@ -88,7 +88,7 @@
<string name="failed_to_update_sepolicy">SELinux ルールの更新に失敗しました %s</string> <string name="failed_to_update_sepolicy">SELinux ルールの更新に失敗しました %s</string>
<string name="module_changelog">変更履歴</string> <string name="module_changelog">変更履歴</string>
<string name="settings_profile_template">アプリプロファイルのテンプレート</string> <string name="settings_profile_template">アプリプロファイルのテンプレート</string>
<string name="settings_profile_template_summary">アプリプロファイルのローカルおよびオンラインテンプレートを管理します</string> <string name="settings_profile_template_summary">アプリプロファイルのローカルおよびオンラインテンプレートを管理します</string>
<string name="app_profile_template_create">テンプレートの作成</string> <string name="app_profile_template_create">テンプレートの作成</string>
<string name="app_profile_template_edit">テンプレートの編集</string> <string name="app_profile_template_edit">テンプレートの編集</string>
<string name="app_profile_template_id">ID</string> <string name="app_profile_template_id">ID</string>
@@ -110,12 +110,12 @@
<string name="app_profile_template_import_empty">クリップボードが空です!</string> <string name="app_profile_template_import_empty">クリップボードが空です!</string>
<string name="module_changelog_failed">変更ログの取得に失敗しました: %s</string> <string name="module_changelog_failed">変更ログの取得に失敗しました: %s</string>
<string name="settings_check_update">更新を確認する</string> <string name="settings_check_update">更新を確認する</string>
<string name="settings_check_update_summary">アプリを開いたときに更新を自動的に確認します</string> <string name="settings_check_update_summary">アプリを開いたときに更新を自動的に確認します</string>
<string name="grant_root_failed">root の付与に失敗しました!</string> <string name="grant_root_failed">root の付与に失敗しました!</string>
<string name="action">アクション</string> <string name="action">アクション</string>
<string name="open">開く</string> <string name="open">開く</string>
<string name="enable_web_debugging">WebView デバッグを有効化する</string> <string name="enable_web_debugging">WebView デバッグを有効化する</string>
<string name="enable_web_debugging_summary">WebUI のデバッグに使用できます。必要な場合のみ有効してください</string> <string name="enable_web_debugging_summary">WebUI のデバッグに使用できます。必要な場合のみ有効してください</string>
<string name="direct_install">直接インストール (推奨)</string> <string name="direct_install">直接インストール (推奨)</string>
<string name="select_file">ファイルを選択</string> <string name="select_file">ファイルを選択</string>
<string name="install_inactive_slot">非アクティブなスロットにインストール (OTA 後)</string> <string name="install_inactive_slot">非アクティブなスロットにインストール (OTA 後)</string>
@@ -172,8 +172,9 @@
<string name="backup_allowlist">許可リストをバックアップ</string> <string name="backup_allowlist">許可リストをバックアップ</string>
<string name="restore_allowlist">許可リストを復元</string> <string name="restore_allowlist">許可リストを復元</string>
<string name="settings_custom_background">カスタム背景を設定</string> <string name="settings_custom_background">カスタム背景を設定</string>
<string name="settings_custom_background_summary">カスタム背景を設定します</string> <string name="settings_custom_background_summary">カスタム背景を設定します</string>
<string name="settings_card_alpha">ナビゲーションバーの透過</string> <string name="settings_card_manage">カードの管理</string>
<string name="settings_card_alpha">カードのアルファ</string>
<string name="settings_restore_default">デフォルトに復元</string> <string name="settings_restore_default">デフォルトに復元</string>
<string name="home_android_version">Android のバージョン</string> <string name="home_android_version">Android のバージョン</string>
<string name="home_device_model">デバイスモデル</string> <string name="home_device_model">デバイスモデル</string>
@@ -188,13 +189,13 @@
<string name="selinux_enabled">有効</string> <string name="selinux_enabled">有効</string>
<string name="selinux_disabled">無効</string> <string name="selinux_disabled">無効</string>
<string name="simple_mode">シンプルモード</string> <string name="simple_mode">シンプルモード</string>
<string name="simple_mode_summary">ON にすると不要なカードを非表示にします</string> <string name="simple_mode_summary">ON にすると不要なカードを非表示にします</string>
<string name="hide_kernel_kernelsu_version">カーネルのバージョンを非表示にする</string> <string name="hide_kernel_kernelsu_version">カーネルのバージョンを非表示にする</string>
<string name="hide_kernel_kernelsu_version_summary">カーネルのバージョンを非表示にします</string> <string name="hide_kernel_kernelsu_version_summary">カーネルのバージョンを非表示にします</string>
<string name="hide_other_info">その他の情報を非表示にする</string> <string name="hide_other_info">その他の情報を非表示にする</string>
<string name="hide_other_info_summary">ホームページ上のスーパーユーザー、モジュール、KPM モジュールの数に関する情報を非表示にします</string> <string name="hide_other_info_summary">ホームページ上のスーパーユーザー、モジュール、KPM モジュールの数に関する情報を非表示にします</string>
<string name="hide_susfs_status">SuSFS ステータスを非表示にする</string> <string name="hide_susfs_status">SuSFS ステータスを非表示にする</string>
<string name="hide_susfs_status_summary">ホームページ上の SuSFS ステータス情報を非表示にします</string> <string name="hide_susfs_status_summary">ホームページ上の SuSFS ステータス情報を非表示にします</string>
<string name="theme_mode">テーマモード</string> <string name="theme_mode">テーマモード</string>
<string name="theme_follow_system">システムに従う</string> <string name="theme_follow_system">システムに従う</string>
<string name="theme_light">ライトカラー</string> <string name="theme_light">ライトカラー</string>
@@ -214,7 +215,6 @@
<string name="flash_option">ブラシの設定</string> <string name="flash_option">ブラシの設定</string>
<string name="flash_option_tip">フラッシュするファイルを選択</string> <string name="flash_option_tip">フラッシュするファイルを選択</string>
<string name="horizon_kernel">AnyKernel3 をフラッシュ</string> <string name="horizon_kernel">AnyKernel3 をフラッシュ</string>
<string name="horizon_kernel_summary">AnyKernel3 をフラッシュします</string>
<string name="root_required">root 権限が必要です</string> <string name="root_required">root 権限が必要です</string>
<string name="copy_failed">ファイルのコピーに失敗しました</string> <string name="copy_failed">ファイルのコピーに失敗しました</string>
<string name="reboot_complete_title">スクラブが完了しました</string> <string name="reboot_complete_title">スクラブが完了しました</string>
@@ -251,12 +251,12 @@
<string name="home_kpm_module">KPM モジュール数: %d </string> <string name="home_kpm_module">KPM モジュール数: %d </string>
<string name="kpm_invalid_file">無効な KPM ファイル</string> <string name="kpm_invalid_file">無効な KPM ファイル</string>
<string name="kernel_patched">カーネルはパッチされていません</string> <string name="kernel_patched">カーネルはパッチされていません</string>
<string name="kernel_not_enabled">カーネルは設定です</string> <string name="kernel_not_enabled">カーネルは設定されていません</string>
<string name="custom_settings">カスタム設定</string> <string name="custom_settings">カスタム設定</string>
<string name="kpm_install_mode">インストール</string> <string name="kpm_install_mode">インストール</string>
<string name="kpm_install_mode_load">読み込む</string> <string name="kpm_install_mode_load">読み込む</string>
<string name="kpm_install_mode_embed">埋め込む</string> <string name="kpm_install_mode_embed">埋め込む</string>
<string name="kpm_install_mode_description">モジュール%1\$s のインストールモードを選択してください \n\n読み込む: モジュールを一時的に読み込みます\n埋め込む: システムに恒久的にインストールします</string> <string name="kpm_install_mode_description">モジュールのインストールモードを選択してください:\n\n読み込む: モジュールを一時的に読み込みます\n埋め込む: システムに恒久的にインストールします</string>
<string name="log_failed_to_check_module_file">モジュールファイルの存在を確認できませんでした</string> <string name="log_failed_to_check_module_file">モジュールファイルの存在を確認できませんでした</string>
<string name="snackbar_failed_to_check_module_file">モジュールファイルが存在するか確認できません</string> <string name="snackbar_failed_to_check_module_file">モジュールファイルが存在するか確認できません</string>
<string name="confirm_uninstall_title">アンインストールを確認</string> <string name="confirm_uninstall_title">アンインストールを確認</string>

View File

@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string name="app_name" translatable="false">KernelSU</string>
<string name="home">Strona główna</string> <string name="home">Strona główna</string>
<string name="home_not_installed">Nie zainstalowano</string> <string name="home_not_installed">Nie zainstalowano</string>
<string name="home_click_to_install">Kliknij, aby zainstalować</string> <string name="home_click_to_install">Kliknij, aby zainstalować</string>
@@ -50,6 +51,7 @@
<string name="home_click_to_learn_kernelsu">Dowiedz się jak zainstalować KernelSU i jak korzystać z modułów</string> <string name="home_click_to_learn_kernelsu">Dowiedz się jak zainstalować KernelSU i jak korzystać z modułów</string>
<string name="home_support_title">Wesprzyj nas</string> <string name="home_support_title">Wesprzyj nas</string>
<string name="home_support_content">KernelSU jest i zawsze będzie darmowy oraz otwarty. Niemniej jednak możesz nam pokazać, że Ci zależy, wysyłając darowiznę.</string> <string name="home_support_content">KernelSU jest i zawsze będzie darmowy oraz otwarty. Niemniej jednak możesz nam pokazać, że Ci zależy, wysyłając darowiznę.</string>
<string name="profile" translatable="false">Profil aplikacji</string>
<string name="profile_default">Domyślny</string> <string name="profile_default">Domyślny</string>
<string name="profile_template">Szablon</string> <string name="profile_template">Szablon</string>
<string name="profile_custom">Własny</string> <string name="profile_custom">Własny</string>

View File

@@ -50,6 +50,7 @@
<string name="home_click_to_learn_kernelsu">Saiba como instalar o KernelSU e usar os módulos</string> <string name="home_click_to_learn_kernelsu">Saiba como instalar o KernelSU e usar os módulos</string>
<string name="home_support_title">Apoie-nos</string> <string name="home_support_title">Apoie-nos</string>
<string name="home_support_content">KernelSU sempre foi e sempre será, gratuito e de código aberto. No entanto, você pode nos ajudar enviando uma pequena doação.</string> <string name="home_support_content">KernelSU sempre foi e sempre será, gratuito e de código aberto. No entanto, você pode nos ajudar enviando uma pequena doação.</string>
<string name="profile" translatable="false">Perfil do Aplicativo</string>
<string name="profile_default">Padrão</string> <string name="profile_default">Padrão</string>
<string name="profile_template">Modelo</string> <string name="profile_template">Modelo</string>
<string name="profile_custom">Personalizado</string> <string name="profile_custom">Personalizado</string>

View File

@@ -52,6 +52,7 @@
<string name="home_click_to_learn_kernelsu">Узнайте, как установить KernelSU и использовать модули</string> <string name="home_click_to_learn_kernelsu">Узнайте, как установить KernelSU и использовать модули</string>
<string name="home_support_title">Поддержите нас</string> <string name="home_support_title">Поддержите нас</string>
<string name="home_support_content">KernelSU был и всегда будет бесплатным и открытым проектом. Однако Вы всегда можете поддержать нас, отправив небольшое пожертвование.</string> <string name="home_support_content">KernelSU был и всегда будет бесплатным и открытым проектом. Однако Вы всегда можете поддержать нас, отправив небольшое пожертвование.</string>
<string name="profile" translatable="false">App Profile</string>
<!--Don't translate this string!--> <!--Don't translate this string!-->
<string name="profile_default">По умолчанию</string> <string name="profile_default">По умолчанию</string>
<string name="profile_template">Шаблон</string> <string name="profile_template">Шаблон</string>

View File

@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string name="app_name" translatable="false">KernelSU</string>
<string name="home">Ana Sayfa</string> <string name="home">Ana Sayfa</string>
<string name="home_not_installed">Kurulmadı</string> <string name="home_not_installed">Kurulmadı</string>
<string name="home_click_to_install">Kurmak için tıklayın</string> <string name="home_click_to_install">Kurmak için tıklayın</string>
@@ -50,6 +51,7 @@
<string name="home_click_to_learn_kernelsu">KernelSU\'nun nasıl kurulacağını ve modüllerin nasıl kullanılacağını öğrenin</string> <string name="home_click_to_learn_kernelsu">KernelSU\'nun nasıl kurulacağını ve modüllerin nasıl kullanılacağını öğrenin</string>
<string name="home_support_title">Bizi destekleyin</string> <string name="home_support_title">Bizi destekleyin</string>
<string name="home_support_content">KernelSU ücretsiz ve açık kaynaklı bir yazılımdır ve her zaman öyle kalacaktır. Ancak bağış yaparak bize destek olduğunuzu gösterebilirsiniz.</string> <string name="home_support_content">KernelSU ücretsiz ve açık kaynaklı bir yazılımdır ve her zaman öyle kalacaktır. Ancak bağış yaparak bize destek olduğunuzu gösterebilirsiniz.</string>
<string name="profile" translatable="false">Uygulama profili</string>
<string name="profile_default">Varsayılan</string> <string name="profile_default">Varsayılan</string>
<string name="profile_template">Şablon</string> <string name="profile_template">Şablon</string>
<string name="profile_custom">Özel</string> <string name="profile_custom">Özel</string>

View File

@@ -50,6 +50,7 @@
<string name="home_click_to_learn_kernelsu">Дізнайтеся, як інсталювати KernelSU і використовувати модулі</string> <string name="home_click_to_learn_kernelsu">Дізнайтеся, як інсталювати KernelSU і використовувати модулі</string>
<string name="home_support_title">Підтримати нас</string> <string name="home_support_title">Підтримати нас</string>
<string name="home_support_content">KernelSU є, і завжди буде безкоштовним та з відкритим кодом. Однак, якщо вам не байдуже, можете зробити невеличке пожертвування.</string> <string name="home_support_content">KernelSU є, і завжди буде безкоштовним та з відкритим кодом. Однак, якщо вам не байдуже, можете зробити невеличке пожертвування.</string>
<string name="profile">Профіль додатка</string>
<string name="profile_default">Типовий</string> <string name="profile_default">Типовий</string>
<string name="profile_template">Шаблон</string> <string name="profile_template">Шаблон</string>
<string name="profile_custom">Власний</string> <string name="profile_custom">Власний</string>

View File

@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string name="app_name" translatable="false">SukiSU Ultra</string>
<string name="home">Home</string> <string name="home">Home</string>
<string name="home_not_installed">Chưa cài đặt</string> <string name="home_not_installed">Chưa cài đặt</string>
<string name="home_click_to_install">Nhấn vào đây để cài đặt</string> <string name="home_click_to_install">Nhấn vào đây để cài đặt</string>
@@ -57,6 +58,7 @@
<string name="home_support_title">Hỗ trợ chúng tôi</string> <string name="home_support_title">Hỗ trợ chúng tôi</string>
<string name="home_support_content">KernelSU sẽ luôn là miễn phí và mã nguồn mở. Tuy nhiên, bạn có thể cho chúng tôi thấy rằng bạn quan tâm bằng cách quyên góp.</string> <string name="home_support_content">KernelSU sẽ luôn là miễn phí và mã nguồn mở. Tuy nhiên, bạn có thể cho chúng tôi thấy rằng bạn quan tâm bằng cách quyên góp.</string>
<string name="about_source_code"><![CDATA[View source code at %1$s<br/>Tham gia kênh %2$s của chúng tôi]]></string> <string name="about_source_code"><![CDATA[View source code at %1$s<br/>Tham gia kênh %2$s của chúng tôi]]></string>
<string name="profile" translatable="false">Hồ sơ ứng dụng</string>
<string name="profile_default">Mặc định</string> <string name="profile_default">Mặc định</string>
<string name="profile_template">Bản mẫu</string> <string name="profile_template">Bản mẫu</string>
<string name="profile_custom">Tùy chỉnh</string> <string name="profile_custom">Tùy chỉnh</string>
@@ -171,6 +173,7 @@
<string name="restore_allowlist">Khôi phục danh sách cho phép</string> <string name="restore_allowlist">Khôi phục danh sách cho phép</string>
<string name="settings_custom_background">Cài đặt nền tùy chỉnh</string> <string name="settings_custom_background">Cài đặt nền tùy chỉnh</string>
<string name="settings_custom_background_summary">Cài đặt tùy chỉnh nền</string> <string name="settings_custom_background_summary">Cài đặt tùy chỉnh nền</string>
<string name="settings_card_manage">Quản lý thẻ</string>
<string name="settings_card_alpha">Thẻ alpha</string> <string name="settings_card_alpha">Thẻ alpha</string>
<string name="settings_restore_default">Khôi phục mặc định</string> <string name="settings_restore_default">Khôi phục mặc định</string>
<string name="home_android_version">Phiên bản Android</string> <string name="home_android_version">Phiên bản Android</string>
@@ -253,7 +256,7 @@
<string name="kpm_install_mode">Cài đặt</string> <string name="kpm_install_mode">Cài đặt</string>
<string name="kpm_install_mode_load">Tải</string> <string name="kpm_install_mode_load">Tải</string>
<string name="kpm_install_mode_embed">Nhúng</string> <string name="kpm_install_mode_embed">Nhúng</string>
<string name="kpm_install_mode_description">Vui lòng%1\$s chọn chế độ cài đặt mô-đun \n\nTải: Tải tạm thời mô-đun \nNhúng: Cài đặt vĩnh viễn vào hệ thống</string> <string name="kpm_install_mode_description">Vui lòng chọn chế độ cài đặt mô-đun: \n\nTải: Tải tạm thời mô-đun \nNhúng: Cài đặt vĩnh viễn vào hệ thống</string>
<string name="log_failed_to_check_module_file">Không kiểm tra được sự tồn tại của tệp mô-đun</string> <string name="log_failed_to_check_module_file">Không kiểm tra được sự tồn tại của tệp mô-đun</string>
<string name="snackbar_failed_to_check_module_file">Không thể kiểm tra xem tệp mô-đun có tồn tại không</string> <string name="snackbar_failed_to_check_module_file">Không thể kiểm tra xem tệp mô-đun có tồn tại không</string>
<string name="confirm_uninstall_title">Xác nhận gỡ cài đặt</string> <string name="confirm_uninstall_title">Xác nhận gỡ cài đặt</string>

View File

@@ -172,6 +172,7 @@
<string name="restore_allowlist">还原应用列表</string> <string name="restore_allowlist">还原应用列表</string>
<string name="settings_custom_background">自定义背景</string> <string name="settings_custom_background">自定义背景</string>
<string name="settings_custom_background_summary">选择一张图片作为应用背景</string> <string name="settings_custom_background_summary">选择一张图片作为应用背景</string>
<string name="settings_card_manage">卡片管理</string>
<string name="settings_card_alpha">卡片透明度</string> <string name="settings_card_alpha">卡片透明度</string>
<string name="settings_restore_default">恢复默认</string> <string name="settings_restore_default">恢复默认</string>
<string name="home_android_version">Android 版本</string> <string name="home_android_version">Android 版本</string>
@@ -213,7 +214,6 @@
<string name="flash_option">刷入选项</string> <string name="flash_option">刷入选项</string>
<string name="flash_option_tip">选择要刷入的文件</string> <string name="flash_option_tip">选择要刷入的文件</string>
<string name="horizon_kernel">刷写 AnyKernel3 压缩包</string> <string name="horizon_kernel">刷写 AnyKernel3 压缩包</string>
<string name="horizon_kernel_summary">刷入 Anykernel3 内核</string>
<string name="root_required">需要 root 权限</string> <string name="root_required">需要 root 权限</string>
<string name="copy_failed">文件复制失败</string> <string name="copy_failed">文件复制失败</string>
<string name="reboot_complete_title">刷写完成</string> <string name="reboot_complete_title">刷写完成</string>
@@ -236,7 +236,7 @@
<string name="kpm_install_success">加载 kpm 模块成功</string> <string name="kpm_install_success">加载 kpm 模块成功</string>
<string name="kpm_install_failed">加载 kpm 模块失败</string> <string name="kpm_install_failed">加载 kpm 模块失败</string>
<string name="home_kpm_version">KPM 版本</string> <string name="home_kpm_version">KPM 版本</string>
<string name="kpm_control">调参</string> <string name="kpm_control">执行</string>
<string name="close_notice">关闭</string> <string name="close_notice">关闭</string>
<string name="kpm_control_success">成功</string> <string name="kpm_control_success">成功</string>
<string name="kpm_control_failed">错误</string> <string name="kpm_control_failed">错误</string>
@@ -253,7 +253,7 @@
<string name="kpm_install_mode">安装</string> <string name="kpm_install_mode">安装</string>
<string name="kpm_install_mode_load">加载</string> <string name="kpm_install_mode_load">加载</string>
<string name="kpm_install_mode_embed">嵌入</string> <string name="kpm_install_mode_embed">嵌入</string>
<string name="kpm_install_mode_description">请选择: %1\$s 模块安装模式 \n\n加载临时加载模块\n嵌入永久安装到系统</string> <string name="kpm_install_mode_description">请选择模块安装模式\n\n加载临时加载模块\n嵌入永久安装到系统</string>
<string name="snackbar_failed_to_check_module_file">无法检查模块文件是否存在</string> <string name="snackbar_failed_to_check_module_file">无法检查模块文件是否存在</string>
<string name="confirm_uninstall_title">确认卸载</string> <string name="confirm_uninstall_title">确认卸载</string>
<string name="confirm_uninstall_confirm">删除</string> <string name="confirm_uninstall_confirm">删除</string>
@@ -262,34 +262,4 @@
<string name="invalid_file_type">文件类型不正确,请选择 .kpm 文件</string> <string name="invalid_file_type">文件类型不正确,请选择 .kpm 文件</string>
<string name="confirm_uninstall_title_with_filename">卸载</string> <string name="confirm_uninstall_title_with_filename">卸载</string>
<string name="confirm_uninstall_content">将卸载以下 kpm 模块:\n%s</string> <string name="confirm_uninstall_content">将卸载以下 kpm 模块:\n%s</string>
<string name="image_editor_title">调整背景图片</string>
<string name="image_editor_hint">使用双指缩放图片,单指拖动调整位置</string>
<string name="background_image_error">无法加载图片</string>
<string name="reprovision">重置</string>
<!-- Anykernel3 Kernel刷写进度相关 -->
<string name="horizon_flash_title">刷写Kernel</string>
<string name="horizon_logs_label">日志:</string>
<string name="horizon_flash_complete">刷写完成</string>
<!-- 刷写状态相关 -->
<string name="horizon_preparing">准备中…</string>
<string name="horizon_cleaning_files">清理文件…</string>
<string name="horizon_copying_files">复制文件…</string>
<string name="horizon_extracting_tool">提取刷写工具…</string>
<string name="horizon_patching_script">修补刷写脚本…</string>
<string name="horizon_flashing">刷写内核中…</string>
<string name="horizon_flash_complete_status">刷写完成</string>
<!-- 槽位选择相关字符串 -->
<string name="select_slot_title">选择刷写槽位</string>
<string name="select_slot_description">请选择要刷写boot的目标槽位</string>
<string name="slot_a">A槽位</string>
<string name="slot_b">B槽位</string>
<string name="selected_slot">已选择槽位: %1$s</string>
<!-- 错误信息 -->
<string name="horizon_copy_failed">复制失败</string>
<string name="horizon_unknown_error">未知错误</string>
<string name="horizon_getting_original_slot">获取原有槽位</string>
<string name="horizon_setting_target_slot">设置指定槽位</string>
<string name="horizon_restoring_original_slot">恢复默认槽位</string>
<string name="current_slot">当前槽位:%1$s </string>
<string name="flash_failed_message">刷写失败</string>
</resources> </resources>

View File

@@ -12,7 +12,7 @@
<string name="home_unsupported_reason">No KernelSU driver detected on your kernel, wrong kernel?.</string> <string name="home_unsupported_reason">No KernelSU driver detected on your kernel, wrong kernel?.</string>
<string name="home_kernel">Kernel version</string> <string name="home_kernel">Kernel version</string>
<string name="home_susfs">SuSFS: %s</string> <string name="home_susfs">SuSFS: %s</string>
<string name="home_susfs_version">SuSFS Version</string> <string name="home_susfs_version">Version de SuSFS</string>
<string name="home_susfs_sus_su">SuS SU</string> <string name="home_susfs_sus_su">SuS SU</string>
<string name="home_manager_version">Manager version</string> <string name="home_manager_version">Manager version</string>
<string name="home_fingerprint">Fingerprint</string> <string name="home_fingerprint">Fingerprint</string>
@@ -75,7 +75,8 @@
<string name="require_kernel_version" formatted="false">The current KernelSU version %d is too low for the manager to work properly. Please upgrade to version %d or higher!</string> <string name="require_kernel_version" formatted="false">The current KernelSU version %d is too low for the manager to work properly. Please upgrade to version %d or higher!</string>
<string name="settings_umount_modules_default">Umount modules by default</string> <string name="settings_umount_modules_default">Umount modules by default</string>
<string name="settings_umount_modules_default_summary">The global default value for \"Umount modules\" in App Profile. If enabled, it will remove all module modifications to the system for apps that don\'t have a profile set.</string> <string name="settings_umount_modules_default_summary">The global default value for \"Umount modules\" in App Profile. If enabled, it will remove all module modifications to the system for apps that don\'t have a profile set.</string>
<string name="settings_susfs_toggle">Disable kprobe hooks</string> <string name="settings_susfs_toggle">Cacher les hooks kprobe</string>
<string name="settings_susfs_toggle_summary">Désactive les hooks kprobe créés par KSU et, à la place, active les hooks non-kprobe intégrés, implémentant les mêmes fonctionnalités qui seraient appliquées à un kernel non-GKI, qui ne supportent krpobe.</string>
<string name="profile_umount_modules_summary">Enabling this option will allow KernelSU to restore any modified files by the modules for this app.</string> <string name="profile_umount_modules_summary">Enabling this option will allow KernelSU to restore any modified files by the modules for this app.</string>
<string name="profile_selinux_domain">Domain</string> <string name="profile_selinux_domain">Domain</string>
<string name="profile_selinux_rules">Rules</string> <string name="profile_selinux_rules">Rules</string>
@@ -170,23 +171,24 @@
<string name="allowlist_restore_failed">Allowlist restore failed: %1$s</string> <string name="allowlist_restore_failed">Allowlist restore failed: %1$s</string>
<string name="backup_allowlist">Backup Allowlist</string> <string name="backup_allowlist">Backup Allowlist</string>
<string name="restore_allowlist">Restore Allowlist</string> <string name="restore_allowlist">Restore Allowlist</string>
<string name="settings_custom_background">Custom App Background</string> <string name="settings_custom_background">settings custom background</string>
<string name="settings_custom_background_summary">Select an image as background</string> <string name="settings_custom_background_summary">settings custom background summary</string>
<string name="settings_card_alpha">Navigation bar transparency</string> <string name="settings_card_manage">card manage</string>
<string name="settings_restore_default">Restore default</string> <string name="settings_card_alpha">card alpha</string>
<string name="settings_restore_default">restore default</string>
<string name="home_android_version">Android version</string> <string name="home_android_version">Android version</string>
<string name="home_device_model">Device model</string> <string name="home_device_model">device model</string>
<string name="su_not_allowed">Granting superuser to %s is not allowed</string> <string name="su_not_allowed">Granting superuser to %s is not allowed</string>
<string name="settings_disable_su">Disable su compatibility</string> <string name="settings_disable_su">Disable su compatibility</string>
<string name="settings_disable_su_summary">Temporarily disable any applications from obtaining root privileges via the su command (existing root processes will not be affected).</string> <string name="settings_disable_su_summary">Temporarily disable any applications from obtaining root privileges via the su command (existing root processes will not be affected).</string>
<string name="using_mksu_manager">You are using the SukiSU Beta manager</string> <string name="using_mksu_manager">You are using the SukiSU Beta manager</string>
<string name="module_install_multiple_confirm">Are you sure you want to install the selected %d modules?</string> <string name="module_install_multiple_confirm">Are you sure you want to install the selected %d modules?</string>
<string name="module_install_multiple_confirm_with_names">Sure you want to install the following %1$d modules? \n\n%2$s</string> <string name="module_install_multiple_confirm_with_names">Sure you want to install the following %1$d modules? \n\n%2$s</string>
<string name="more_settings">More settings</string> <string name="more_settings">more settings</string>
<string name="selinux">SELinux</string> <string name="selinux">SELinux</string>
<string name="selinux_enabled">Enabled</string> <string name="selinux_enabled">Enabled</string>
<string name="selinux_disabled">Disabled</string> <string name="selinux_disabled">Disabled</string>
<string name="simple_mode">Simplicity mode</string> <string name="simple_mode">simplicity mode</string>
<string name="simple_mode_summary">Hides unnecessary cards when turned on</string> <string name="simple_mode_summary">Hides unnecessary cards when turned on</string>
<string name="hide_kernel_kernelsu_version">Hide kernel version</string> <string name="hide_kernel_kernelsu_version">Hide kernel version</string>
<string name="hide_kernel_kernelsu_version_summary">Hide kernel version</string> <string name="hide_kernel_kernelsu_version_summary">Hide kernel version</string>
@@ -194,106 +196,74 @@
<string name="hide_other_info_summary">Hides information about the number of super users, modules and KPM modules on the home page</string> <string name="hide_other_info_summary">Hides information about the number of super users, modules and KPM modules on the home page</string>
<string name="hide_susfs_status">Hide SuSFS status</string> <string name="hide_susfs_status">Hide SuSFS status</string>
<string name="hide_susfs_status_summary">Hide SuSFS status information on the home page</string> <string name="hide_susfs_status_summary">Hide SuSFS status information on the home page</string>
<string name="theme_mode">Theme</string> <string name="theme_mode">Theme Mode</string>
<string name="theme_follow_system">Follow system</string> <string name="theme_follow_system">follow-up system</string>
<string name="theme_light">Light</string> <string name="theme_light">light color</string>
<string name="theme_dark">Dark</string> <string name="theme_dark">dark colored</string>
<string name="manual_hook">Manual Hook</string> <string name="manual_hook">Manual Hook</string>
<string name="dynamic_color_title">Dynamic colours</string> <string name="dynamic_color_title">Dynamic colours</string>
<string name="dynamic_color_summary">Dynamic colours using system themes</string> <string name="dynamic_color_summary">Dynamic colours using system themes</string>
<string name="choose_theme_color">Choose a theme colour</string> <string name="choose_theme_color">Choose a theme colour</string>
<string name="color_default">White</string> <string name="color_default">White</string>
<string name="color_blue">Blue</string> <string name="color_blue">blue</string>
<string name="color_green">Green</string> <string name="color_green">green</string>
<string name="color_purple">Purple</string> <string name="color_purple">purple</string>
<string name="color_orange">Orange</string> <string name="color_orange">orange</string>
<string name="color_pink">Pink</string> <string name="color_pink">pink</string>
<string name="color_gray">Gray</string> <string name="color_gray">gray</string>
<string name="color_ivory">Ivory</string> <string name="color_ivory">ivory</string>
<string name="flash_option">Brush Options</string> <string name="flash_option">Brush Options</string>
<string name="flash_option_tip">Select the file to be flashed</string> <string name="flash_option_tip">Select the file to be flashed</string>
<string name="horizon_kernel">Install Anykernel3</string> <string name="horizon_kernel">Anykernel3 Flash</string>
<string name="horizon_kernel_summary">Flash AnyKernel3 kernel file</string>
<string name="root_required">Requires root privileges</string> <string name="root_required">Requires root privileges</string>
<string name="copy_failed">File Copy Failure</string> <string name="copy_failed">File Copy Failure</string>
<string name="reboot_complete_title">Scrubbing complete</string> <string name="reboot_complete_title">Scrubbing complete</string>
<string name="reboot_complete_msg">Whether to reboot immediately</string> <string name="reboot_complete_msg">Whether to reboot immediately</string>
<string name="yes">Yes</string> <string name="yes">yes</string>
<string name="no">No</string> <string name="no">no</string>
<string name="failed_reboot">Reboot Failed</string> <string name="failed_reboot">Reboot Failed</string>
<string name="batch_authorization">Bulk license</string> <string name="batch_authorization">bulk license</string>
<string name="batch_cancel_authorization">Batch cancel authorization</string> <string name="batch_cancel_authorization">Batch cancel authorization</string>
<string name="backup">Backup</string> <string name="backup">Backup</string>
<string name="color_yellow">Yellow</string> <string name="color_yellow">Yellow</string>
<string name="kpm">Kernel Module</string> <string name="kpm">kpm module</string>
<string name="kpm_title">Kernel Module</string> <string name="kpm_title">kernel module</string>
<string name="kpm_empty">No installed kernel modules at this time</string> <string name="kpm_empty">No installed kernel modules at this time</string>
<string name="kpm_version">Version</string> <string name="kpm_version">releases</string>
<string name="kpm_author">Author</string> <string name="kpm_author">author</string>
<string name="kpm_uninstall">Uninstall</string> <string name="kpm_uninstall">uninstallation</string>
<string name="kpm_uninstall_success">Uninstalled successfully</string> <string name="kpm_uninstall_success">Uninstalled successfully</string>
<string name="kpm_uninstall_failed">Failed to uninstall</string> <string name="kpm_uninstall_failed">Failed to uninstall</string>
<string name="kpm_install">Install</string> <string name="kpm_install">Select Installation</string>
<string name="kpm_install_success">Load of kpm module successful</string> <string name="kpm_install_success">Load of kpm module successful</string>
<string name="kpm_install_failed">Load of kpm module failed</string> <string name="kpm_install_failed">Load of kpm module failed</string>
<string name="kpm_args">Parameters</string> <string name="kpm_args">kpm parameters</string>
<string name="kpm_control">Execute</string> <string name="kpm_control">fulfillment</string>
<string name="home_kpm_version">KPM Version</string> <string name="home_kpm_version">KPM Version</string>
<string name="close_notice">Close</string> <string name="close_notice">close</string>
<string name="kernel_module_notice">The following kernel module functions were developed by KernelPatch and modified to include the kernel module functions of SukiSU Ultra</string> <string name="kernel_module_notice">The following kernel module functions were developed by KernelPatch and modified to include the kernel module functions of SukiSU Ultra</string>
<string name="home_ContributionCard_kernelsu">SukiSU Ultra Look forward to</string> <string name="home_ContributionCard_kernelsu">SukiSU Ultra Look forward to</string>
<string name="kpm_control_success">Success</string> <string name="kpm_control_success">success</string>
<string name="kpm_control_failed">Failed</string> <string name="kpm_control_failed">failed</string>
<string name="home_click_to_ContributionCard_kernelsu">SukiSU Ultra will be a relatively independent branch of KSU in the future, but we still appreciate the official KernelSU and MKSU etc. for their contributions!</string> <string name="home_click_to_ContributionCard_kernelsu">SukiSU Ultra will be a relatively independent branch of KSU in the future, but thanks to the official KernelSU and MKSU etc. for their contributions!</string>
<string name="not_supported">Unsupported</string> <string name="not_supported">unsupported</string>
<string name="supported">Supported</string> <string name="supported">supported</string>
<string name="home_kpm_module">"Number of KPM modules: %d "</string> <string name="home_kpm_module">Number of KPM modules%d </string>
<string name="kpm_invalid_file">Invalid KPM file</string> <string name="kpm_invalid_file">Invalid KPM file</string>
<string name="kernel_patched">Kernel not patched</string> <string name="kernel_patched">Kernel not patched</string>
<string name="kernel_not_enabled">Kernel not configured</string> <string name="kernel_not_enabled">Kernel not configured</string>
<string name="custom_settings">Custom settings</string> <string name="custom_settings">Custom settings</string>
<string name="kpm_install_mode">KPM Install</string> <string name="kpm_install_mode">install</string>
<string name="kpm_install_mode_load">Load</string> <string name="kpm_install_mode_load">Load</string>
<string name="kpm_install_mode_embed">Embed</string> <string name="kpm_install_mode_embed">embed</string>
<string name="kpm_install_mode_description">Please select: %1\$s Module Installation Mode \n\nLoad: Temporarily load the module \nEmbedded: Permanently install into the system</string> <string name="kpm_install_mode_description">Please select the module installation mode: \n\nLoad: Temporarily load the module \nEmbedded: Permanently install into the system</string>
<string name="log_failed_to_check_module_file">Failed to check module file existence</string> <string name="log_failed_to_check_module_file">Failed to check module file existence</string>
<string name="snackbar_failed_to_check_module_file">Unable to check if module file exists</string> <string name="snackbar_failed_to_check_module_file">Unable to check if module file exists</string>
<string name="confirm_uninstall_title">Confirm uninstallation</string> <string name="confirm_uninstall_title">Confirm uninstallation</string>
<string name="confirm_uninstall_confirm">Uninstall</string> <string name="confirm_uninstall_confirm">removing</string>
<string name="confirm_uninstall_dismiss">Cancel</string> <string name="confirm_uninstall_dismiss">abolish</string>
<string name="theme_color">Theme Color</string> <string name="theme_color">Theme Color</string>
<string name="invalid_file_type">Incorrect file type! Please select .kpm file.</string> <string name="invalid_file_type">Incorrect file type, select .kpm file</string>
<string name="confirm_uninstall_title_with_filename">Uninstall</string> <string name="confirm_uninstall_title_with_filename">uninstallation</string>
<string name="confirm_uninstall_content">The following KPM will be uninstalled: %s</string> <string name="confirm_uninstall_content">The following kpm modules will be uninstalled\n%s</string>
<string name="settings_susfs_toggle_summary">Disable kprobe hooks created by KernelSU, using inline hooks instead, which is similar to non-GKI kernel hooking method.</string>
<string name="image_editor_title">Adjust background image</string>
<string name="image_editor_hint">Use two fingers to zoom the image, and one finger to drag it to adjust the position</string>
<string name="background_image_error">Could not load image</string>
<string name="reprovision">Reprovision</string>
<!-- Kernel Flash Progress Related -->
<string name="horizon_flash_title">Kernel Flashing</string>
<string name="horizon_logs_label">Logs:</string>
<string name="horizon_flash_complete">Flash Complete</string>
<!-- Flash Status Related -->
<string name="horizon_preparing">Preparing…</string>
<string name="horizon_cleaning_files">Cleaning files…</string>
<string name="horizon_copying_files">Copying files…</string>
<string name="horizon_extracting_tool">Extracting flash tool…</string>
<string name="horizon_patching_script">Patching flash script…</string>
<string name="horizon_flashing">Flashing kernel…</string>
<string name="horizon_flash_complete_status">Flash completed</string>
<!-- Slot selection related strings -->
<string name="select_slot_title">Select Flash Slot</string>
<string name="select_slot_description">Please select the target slot for flashing boot</string>
<string name="slot_a">Slot A</string>
<string name="slot_b">Slot B</string>
<string name="selected_slot">Selected slot: %1$s</string>
<!-- Error Messages -->
<string name="horizon_copy_failed">Copy failed</string>
<string name="horizon_unknown_error">Unknown error</string>
<string name="horizon_getting_original_slot">Getting the original slot</string>
<string name="horizon_setting_target_slot">Setting the specified slot</string>
<string name="horizon_restoring_original_slot">Restore Default Slot</string>
<string name="current_slot">Current Slot%1$s </string>
<string name="flash_failed_message">Flash failed</string>
</resources> </resources>

View File

@@ -28,8 +28,8 @@ cmaker {
} }
val androidMinSdkVersion = 26 val androidMinSdkVersion = 26
val androidTargetSdkVersion = 36 val androidTargetSdkVersion = 35
val androidCompileSdkVersion = 36 val androidCompileSdkVersion = 35
val androidCompileNdkVersion = "28.0.13004108" val androidCompileNdkVersion = "28.0.13004108"
val androidSourceCompatibility = JavaVersion.VERSION_21 val androidSourceCompatibility = JavaVersion.VERSION_21
val androidTargetCompatibility = JavaVersion.VERSION_21 val androidTargetCompatibility = JavaVersion.VERSION_21

View File

@@ -1,25 +1,25 @@
[versions] [versions]
agp = "8.9.2" agp = "8.9.1"
kotlin = "2.1.10" kotlin = "2.1.10"
ksp = "2.1.10-1.0.30" ksp = "2.1.10-1.0.30"
compose-bom = "2025.04.01" compose-bom = "2025.02.00"
lifecycle = "2.8.7" lifecycle = "2.8.7"
navigation = "2.8.9" navigation = "2.8.7"
activity-compose = "1.10.1" activity-compose = "1.10.0"
kotlinx-coroutines = "1.10.2" kotlinx-coroutines = "1.10.1"
coil-compose = "2.7.0" coil-compose = "2.7.0"
compose-destination = "2.1.0" compose-destination = "2.1.0-beta16"
sheets-compose-dialogs = "1.3.0" sheets-compose-dialogs = "1.3.0"
markdown = "4.6.2" markdown = "4.6.2"
webkit = "1.13.0" webkit = "1.12.1"
appiconloader-coil = "1.5.0" appiconloader-coil = "1.5.0"
parcelablelist = "2.0.1" parcelablelist = "2.0.1"
libsu = "6.0.0" libsu = "6.0.0"
apksign = "1.4" apksign = "1.4"
cmaker = "1.2" cmaker = "1.2"
compose-material = "1.8.0" compose-material = "1.7.8"
compose-material3 = "1.3.2" compose-material3 = "1.3.1"
compose-ui = "1.8.0" compose-ui = "1.7.8"
compose-foundation = "1.7.8" compose-foundation = "1.7.8"
documentfile = "1.0.1" documentfile = "1.0.1"